Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697type Mode = "light" | "dark";
interface Swatch { background: string; text: string; accent: string;}
const PRESETS = { dark: { label: "Default Dark", mode: "dark", swatch: { background: "#141414", text: "#fafafa", accent: "#6a63f0" } }, light: { label: "Default Light", mode: "light", swatch: { background: "#ffffff", text: "#0a0a0a", accent: "#5750f1" } }, "classic-light": { label: "Classic Light", mode: "light", swatch: { background: "#ffffff", text: "#111827", accent: "#5750f1" } }, "classic-dark": { label: "Classic Dark", mode: "dark", swatch: { background: "#1f2937", text: "#f3f4f6", accent: "#6a63f0" } }, ash: { label: "Ash", mode: "dark", swatch: { background: "#313338", text: "#eceef0", accent: "#5865f2" } }, snow: { label: "Snow", mode: "light", swatch: { background: "#ffffff", text: "#282a30", accent: "#5e6ad2" } }} as const satisfies Record<string, { label: string; mode: Mode; swatch: Swatch }>;
export type Preset = keyof typeof PRESETS;export type Theme = "auto" | Preset;
export const PRESET_NAMES = Object.keys(PRESETS) as Preset[];export const THEMES: Theme[] = ["auto", ...PRESET_NAMES];
const COOKIE = "theme";const YEAR = 60 * 60 * 24 * 365;
export const themeLabel = (theme: Theme): string => theme === "auto" ? "System" : PRESETS[theme].label;
export const themeSwatch = (theme: Theme, systemDark: boolean): Swatch => PRESETS[theme === "auto" ? (systemDark ? "dark" : "light") : theme].swatch;
export const parseTheme = (raw: string | null | undefined): Theme => raw && Object.hasOwn(PRESETS, raw) ? (raw as Preset) : "auto";
// the server reads the raw header so the hook works on any request shapeexport const themeFromCookies = (header: string | null): Theme => { for (const part of header?.split(";") ?? []) { const [name, value] = part.trim().split("="); if (name === COOKIE) return parseTheme(value); } return "auto";};
const themeMode = (theme: Theme): Mode | undefined => theme === "auto" ? undefined : PRESETS[theme].mode;
const themePalette = (theme: Theme): Preset | undefined => theme === "auto" || theme === themeMode(theme) ? undefined : theme;
// auto has no data-theme, so the dark variant uses prefers-color-schemeexport const themeAttribute = (theme: Theme): string => { const mode = themeMode(theme); const palette = themePalette(theme); return (mode ? ` data-theme="${mode}"` : "") + (palette ? ` data-palette="${palette}"` : "");};
export const readTheme = (): Theme => themeFromCookies(document.cookie);
export const applyTheme = (theme: Theme): void => { const root = document.documentElement; const mode = themeMode(theme); const palette = themePalette(theme); if (mode) root.dataset.theme = mode; else root.removeAttribute("data-theme"); if (palette) root.dataset.palette = palette; else root.removeAttribute("data-palette"); document.cookie = theme === "auto" ? `${COOKIE}=; path=/; max-age=0; samesite=lax` : `${COOKIE}=${theme}; path=/; max-age=${YEAR}; samesite=lax`;};