theme.ts
1 import { create } from 'zustand' 2 import { persist } from 'zustand/middleware' 3 4 type Theme = 'light' | 'dark' | 'system' 5 6 interface ThemeState { 7 theme: Theme 8 resolvedTheme: 'light' | 'dark' 9 setTheme: (theme: Theme) => void 10 } 11 12 function getSystemTheme(): 'light' | 'dark' { 13 if (typeof window === 'undefined') return 'light' 14 return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' 15 } 16 17 function resolveTheme(theme: Theme): 'light' | 'dark' { 18 if (theme === 'system') { 19 return getSystemTheme() 20 } 21 return theme 22 } 23 24 export const useThemeStore = create<ThemeState>()( 25 persist( 26 (set) => ({ 27 theme: 'system', 28 resolvedTheme: resolveTheme('system'), 29 setTheme: (theme: Theme) => { 30 const resolvedTheme = resolveTheme(theme) 31 set({ theme, resolvedTheme }) 32 33 // Apply to document 34 if (resolvedTheme === 'dark') { 35 document.documentElement.classList.add('dark') 36 } else { 37 document.documentElement.classList.remove('dark') 38 } 39 }, 40 }), 41 { 42 name: 'acdc-theme', 43 onRehydrateStorage: () => (state) => { 44 // Apply theme on load 45 if (state) { 46 const resolvedTheme = resolveTheme(state.theme) 47 if (resolvedTheme === 'dark') { 48 document.documentElement.classList.add('dark') 49 } else { 50 document.documentElement.classList.remove('dark') 51 } 52 } 53 }, 54 } 55 ) 56 ) 57 58 // Listen for system theme changes 59 if (typeof window !== 'undefined') { 60 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { 61 const state = useThemeStore.getState() 62 if (state.theme === 'system') { 63 const newResolvedTheme = e.matches ? 'dark' : 'light' 64 useThemeStore.setState({ resolvedTheme: newResolvedTheme }) 65 if (newResolvedTheme === 'dark') { 66 document.documentElement.classList.add('dark') 67 } else { 68 document.documentElement.classList.remove('dark') 69 } 70 } 71 }) 72 }