import { useCallback, useMemo, useSyncExternalStore } from 'react'; export type ResolvedAppearance = 'light' | 'dark'; export type Appearance = ResolvedAppearance | 'system'; export type UseAppearanceReturn = { readonly appearance: Appearance; readonly resolvedAppearance: ResolvedAppearance; readonly updateAppearance: (mode: Appearance) => void; }; const listeners = new Set<() => void>(); let currentAppearance: Appearance = 'system'; const prefersDark = (): boolean => { if (typeof window === 'undefined') return false; return window.matchMedia('(prefers-color-scheme: dark)').matches; }; const setCookie = (name: string, value: string, days = 365): void => { if (typeof document === 'undefined') return; const maxAge = days * 24 * 60 * 60; document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`; }; const getStoredAppearance = (): Appearance => { if (typeof window === 'undefined') return 'system'; return (localStorage.getItem('appearance') as Appearance) || 'system'; }; const isDarkMode = (appearance: Appearance): boolean => { return appearance === 'dark' || (appearance === 'system' && prefersDark()); }; const applyTheme = (appearance: Appearance): void => { if (typeof document === 'undefined') return; const isDark = isDarkMode(appearance); document.documentElement.classList.toggle('dark', isDark); document.documentElement.style.colorScheme = isDark ? 'dark' : 'light'; }; const subscribe = (callback: () => void) => { listeners.add(callback); return () => listeners.delete(callback); }; const notify = (): void => listeners.forEach((listener) => listener()); const mediaQuery = (): MediaQueryList | null => { if (typeof window === 'undefined') return null; return window.matchMedia('(prefers-color-scheme: dark)'); }; const handleSystemThemeChange = (): void => { applyTheme(currentAppearance); notify(); }; export function initializeTheme(): void { if (typeof window === 'undefined') return; if (!localStorage.getItem('appearance')) { localStorage.setItem('appearance', 'system'); setCookie('appearance', 'system'); } currentAppearance = getStoredAppearance(); applyTheme(currentAppearance); // Set up system theme change listener mediaQuery()?.addEventListener('change', handleSystemThemeChange); } export function useAppearance(): UseAppearanceReturn { const appearance: Appearance = useSyncExternalStore( subscribe, () => currentAppearance, () => 'system', ); const resolvedAppearance: ResolvedAppearance = useMemo( () => (isDarkMode(appearance) ? 'dark' : 'light'), [appearance], ); const updateAppearance = useCallback((mode: Appearance): void => { currentAppearance = mode; // Store in localStorage for client-side persistence... localStorage.setItem('appearance', mode); // Store in cookie for SSR... setCookie('appearance', mode); applyTheme(mode); notify(); }, []); return { appearance, resolvedAppearance, updateAppearance } as const; }