Mercurial
view hg-web/src/components/theme.tsx @ 201:6cdee35a7ba9
[MrJuneJune] notes
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sun, 15 Feb 2026 07:07:50 -0800 |
| parents | 9f4429c49733 |
| children |
line wrap: on
line source
import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react'; interface ThemeContextType { isDark: boolean; toggleTheme: () => void; } const ThemeContext = createContext<ThemeContextType | undefined>(undefined); // Apply theme class to document root function applyTheme(isDark: boolean) { const root = document.documentElement; if (isDark) { root.classList.add('dark'); root.classList.remove('light'); } else { root.classList.add('light'); root.classList.remove('dark'); } } interface ThemeProviderProps { children: ReactNode; } function ThemeProvider({ children }: ThemeProviderProps) { const [isDark, setIsDark] = useState(() => { const saved = localStorage.getItem('theme'); if (saved) return saved === 'dark'; return window.matchMedia('(prefers-color-scheme: dark)').matches; }); // Apply theme on mount and change useEffect(() => { applyTheme(isDark); localStorage.setItem('theme', isDark ? 'dark' : 'light'); }, [isDark]); // Listen for system theme changes useEffect(() => { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handleChange = (e: MediaQueryListEvent) => { // Only apply if no explicit preference is saved if (!localStorage.getItem('theme')) { setIsDark(e.matches); } }; mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); }, []); const toggleTheme = useCallback(() => { setIsDark(prev => !prev); }, []); return ( <ThemeContext.Provider value={{ isDark, toggleTheme }}> {children} </ThemeContext.Provider> ); } function useTheme(): ThemeContextType { const context = useContext(ThemeContext); if (context === undefined) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; } export { ThemeProvider, useTheme };