| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071 |
- 'use client';
- import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
- type Theme = 'light' | 'dark' | 'system';
- interface ThemeContextType {
- theme: Theme;
- setTheme: (theme: Theme) => void;
- resolvedTheme: 'light' | 'dark';
- }
- const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
- export function ThemeProvider({ children }: { children: ReactNode }) {
- const [theme, setThemeState] = useState<Theme>('system');
- const [systemTheme, setSystemTheme] = useState<'light' | 'dark'>('light');
- const resolvedTheme = theme === 'system' ? systemTheme : theme;
- // Hydrate from localStorage after mount (avoids SSR mismatch)
- /* eslint-disable react-hooks/set-state-in-effect -- Hydration from localStorage must happen in effect */
- useEffect(() => {
- const stored = localStorage.getItem('theme') as Theme | null;
- if (stored && ['light', 'dark', 'system'].includes(stored)) {
- setThemeState(stored);
- }
- setSystemTheme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
- }, []);
- /* eslint-enable react-hooks/set-state-in-effect */
- // Apply theme to document
- useEffect(() => {
- const root = document.documentElement;
- if (resolvedTheme === 'dark') {
- root.classList.add('dark');
- } else {
- root.classList.remove('dark');
- }
- }, [resolvedTheme]);
- // Listen to system theme changes
- useEffect(() => {
- const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
- const handleChange = () => {
- setSystemTheme(mediaQuery.matches ? 'dark' : 'light');
- };
- mediaQuery.addEventListener('change', handleChange);
- return () => mediaQuery.removeEventListener('change', handleChange);
- }, []);
- // Save theme to localStorage
- const handleSetTheme = (newTheme: Theme) => {
- setThemeState(newTheme);
- localStorage.setItem('theme', newTheme);
- };
- return (
- <ThemeContext.Provider value={{ theme, setTheme: handleSetTheme, resolvedTheme }}>
- {children}
- </ThemeContext.Provider>
- );
- }
- export function useTheme() {
- const context = useContext(ThemeContext);
- if (!context) {
- throw new Error('useTheme must be used within ThemeProvider');
- }
- return context;
- }
|