import { type PropsWithChildren, createContext, useContext, useEffect, useMemo, useState, } from 'react'; type Theme = 'light' | 'dark' | 'system'; type ResolvedTheme = 'light' | 'dark'; type ThemeContextValue = { theme: Theme; resolvedTheme: ResolvedTheme; setTheme: (theme: Theme) => void; }; const THEME_STORAGE_KEY = 'theme'; const ThemeContext = createContext(null); function getSystemTheme(): ResolvedTheme { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } function applyResolvedTheme(theme: ResolvedTheme) { document.documentElement.classList.toggle('dark', theme === 'dark'); } export function ThemeProvider({ children }: PropsWithChildren) { const [theme, setThemeState] = useState('system'); const [resolvedTheme, setResolvedTheme] = useState('light'); useEffect(() => { const saved = localStorage.getItem(THEME_STORAGE_KEY); const initialTheme: Theme = saved === 'light' || saved === 'dark' || saved === 'system' ? saved : 'system'; setThemeState(initialTheme); }, []); useEffect(() => { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const updateTheme = () => { const nextResolvedTheme = theme === 'system' ? getSystemTheme() : theme; setResolvedTheme(nextResolvedTheme); applyResolvedTheme(nextResolvedTheme); }; updateTheme(); mediaQuery.addEventListener('change', updateTheme); return () => mediaQuery.removeEventListener('change', updateTheme); }, [theme]); const setTheme = (nextTheme: Theme) => { setThemeState(nextTheme); localStorage.setItem(THEME_STORAGE_KEY, nextTheme); }; const value = useMemo( () => ({ theme, resolvedTheme, setTheme, }), [theme, resolvedTheme] ); return {children}; } export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within ThemeProvider'); } return context; } export type { Theme };