import { createContext, useContext, useEffect, useState } from 'react'; import { CONFIG } from './config'; export type Theme = 'dark' | 'light' | 'auto'; interface ThemeContextValue { theme: Theme; resolved: 'dark' | 'light'; setTheme: (t: Theme) => void; } const ThemeContext = createContext({ theme: CONFIG.DEFAULT_THEME, resolved: 'dark', setTheme: () => {}, }); const STORAGE_KEY = CONFIG.THEME_STORAGE_KEY; function resolveTheme(theme: Theme): 'dark' | 'light' { if (theme === 'auto') { return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'; } return theme; } export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setThemeState] = useState(() => { const stored = localStorage.getItem(STORAGE_KEY) as Theme | null; return stored ?? CONFIG.DEFAULT_THEME; }); const [resolved, setResolved] = useState<'dark' | 'light'>(() => resolveTheme( (localStorage.getItem(STORAGE_KEY) as Theme | null) ?? CONFIG.DEFAULT_THEME )); const applyTheme = (t: Theme) => { const r = resolveTheme(t); setResolved(r); document.documentElement.setAttribute('data-theme', r); }; const setTheme = (t: Theme) => { setThemeState(t); localStorage.setItem(STORAGE_KEY, t); applyTheme(t); }; // Apply on mount useEffect(() => { applyTheme(theme); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Watch system preference changes when in 'auto' mode useEffect(() => { if (theme !== 'auto') return; const mq = window.matchMedia('(prefers-color-scheme: light)'); const handler = () => applyTheme('auto'); mq.addEventListener('change', handler); return () => mq.removeEventListener('change', handler); // eslint-disable-next-line react-hooks/exhaustive-deps }, [theme]); return ( {children} ); } export function useTheme() { return useContext(ThemeContext); }