// Theme control. `toggle` flips light/dark; `select` also offers "system", which // clears the stored choice so the OS takes over again. All strings come from the // catalog, and the DOM work happens in theme.ts, never here. import type { JSX } from 'solid-js'; import { cx } from '../cx'; import { UI_KEYS } from '../i18n-keys'; import { useUi } from '../theme/context'; import { solid } from '../theme/solid-adapter'; import { browserThemeEnv, clearTheme, resolveTheme, setTheme, storedTheme, type Theme, type ThemeEnv, toggleTheme, watchOsTheme, } from '../theme/theme'; import { IconButton } from './IconButton'; import { Select } from './Select'; import styles from './ThemeToggle.module.scss'; export type ThemeChoice = Theme | 'system'; export interface ThemeToggleProps { mode?: 'toggle' | 'select' | undefined; /** * The theme in force at server render — the toggle's glyph. Never the select's choice: nothing * on the server knows whether the visitor chose it, so the select shows "system" until the * effect reads storage. */ initial?: Theme | undefined; /** Injectable for tests and for non-DOM hosts. */ env?: ThemeEnv | undefined; class?: string | undefined; } export function ThemeToggle(props: ThemeToggleProps): JSX.Element { const ui = useUi(); const rt = solid(); // "system" until the effect reads storage, whatever `initial` says: `initial` is the RESOLVED // theme the boot stamped on , and seeding the choice with it made the select claim the // visitor had picked it — so picking "system" looked like a no-op on every first paint. const [choice, setChoice] = rt.createSignal('system'); const [resolved, setResolved] = rt.createSignal(props.initial ?? 'light'); const env = (): ThemeEnv => props.env ?? browserThemeEnv(); const sync = (): void => { const current = env(); setChoice(storedTheme(current) ?? 'system'); setResolved(resolveTheme(current)); }; rt.createEffect(() => { if (props.env === undefined && typeof window === 'undefined') return; sync(); const stop = watchOsTheme(env()); rt.onCleanup(stop); }); const apply = (next: ThemeChoice): void => { if (next === 'system') clearTheme(env()); else setTheme(next, env()); sync(); }; if (props.mode === 'select') { return (