97 lines
2.8 KiB
TypeScript
97 lines
2.8 KiB
TypeScript
/**
|
|
* Isomorphic theme metadata.
|
|
*
|
|
* Deliberately framework-free: it is imported from server code (project
|
|
* theme validation in lib/actions/projects.ts, the no-FOUC script rendered
|
|
* by the project page) *and* client code (theme provider, pickers), so it
|
|
* must stay free of React/DOM imports. What *applies* themes to the DOM
|
|
* lives in components/theme/theme-provider.tsx; the display options
|
|
* (labels, icons, swatches) live in components/theme/theme-toggle.tsx.
|
|
*/
|
|
|
|
/* Order is the picker order: all the light looks, then the dark looks. */
|
|
export const THEMES = [
|
|
"default",
|
|
"sunset",
|
|
"meadow",
|
|
"honey",
|
|
"rose",
|
|
"lavender",
|
|
"slate",
|
|
"blueprint",
|
|
"vaporwave",
|
|
"notebook",
|
|
"dark",
|
|
"ocean",
|
|
"pine",
|
|
"plum",
|
|
"midnight",
|
|
"ember",
|
|
"rosewood",
|
|
"cyberpunk",
|
|
"starfield",
|
|
] as const;
|
|
export type ThemeName = (typeof THEMES)[number];
|
|
export type RawTheme = ThemeName | "system";
|
|
|
|
/** Theme name -> class applied to <html> (see app/globals.css). */
|
|
export const THEME_CLASSES: Record<ThemeName, string> = {
|
|
default: "theme-default",
|
|
sunset: "theme-sunset",
|
|
meadow: "theme-meadow",
|
|
honey: "theme-honey",
|
|
rose: "theme-rose",
|
|
lavender: "theme-lavender",
|
|
slate: "theme-slate",
|
|
blueprint: "theme-blueprint",
|
|
vaporwave: "theme-vaporwave",
|
|
notebook: "theme-notebook",
|
|
dark: "dark",
|
|
ocean: "ocean",
|
|
pine: "theme-pine",
|
|
plum: "theme-plum",
|
|
midnight: "theme-midnight",
|
|
ember: "theme-ember",
|
|
rosewood: "theme-rosewood",
|
|
cyberpunk: "theme-cyberpunk",
|
|
starfield: "theme-starfield",
|
|
};
|
|
/** Themes whose *surfaces* are dark (see app/globals.css). The single source
|
|
* of truth for every dark-surface check: isDarkTheme, the sonner toaster,
|
|
* color-scheme, and the `dark:` custom-variant in globals.css. */
|
|
export const DARK_SURFACES: ThemeName[] = [
|
|
"dark",
|
|
"ocean",
|
|
"pine",
|
|
"plum",
|
|
"midnight",
|
|
"ember",
|
|
"rosewood",
|
|
"cyberpunk",
|
|
"starfield",
|
|
];
|
|
|
|
export function themeColorScheme(name: ThemeName): "light" | "dark" {
|
|
return DARK_SURFACES.includes(name) ? "dark" : "light";
|
|
}
|
|
|
|
/**
|
|
* Inline-script source that switches <html> over to `theme` immediately
|
|
* (before first paint), the same way the global no-FOUC script in
|
|
* app/layout.tsx works, and leaves a `data-project-theme` marker behind so
|
|
* the ThemeProvider can pick the theme up as its initial scope at
|
|
* hydration. Render it from a server component with
|
|
* `dangerouslySetInnerHTML`.
|
|
*/
|
|
export function themeSwitchScript(theme: ThemeName): string {
|
|
const classes = JSON.stringify([
|
|
...Object.values(THEME_CLASSES),
|
|
"light",
|
|
]);
|
|
return `try{var r=document.documentElement;var c=${classes};for(var i=0;i<c.length;i++){r.classList.remove(c[i]);}r.classList.add(${JSON.stringify(
|
|
THEME_CLASSES[theme]
|
|
)});r.style.colorScheme=${JSON.stringify(
|
|
themeColorScheme(theme)
|
|
)};r.dataset.projectTheme=${JSON.stringify(theme)};}catch(e){}`;
|
|
}
|