|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { |
|
|
createContext, |
|
|
useCallback, |
|
|
useContext, |
|
|
useState, |
|
|
useEffect, |
|
|
} from 'react'; |
|
|
|
|
|
const ThemeContext = createContext(null); |
|
|
export const useTheme = () => useContext(ThemeContext); |
|
|
|
|
|
const ActualThemeContext = createContext(null); |
|
|
export const useActualTheme = () => useContext(ActualThemeContext); |
|
|
|
|
|
const SetThemeContext = createContext(null); |
|
|
export const useSetTheme = () => useContext(SetThemeContext); |
|
|
|
|
|
|
|
|
const getSystemTheme = () => { |
|
|
if (typeof window !== 'undefined' && window.matchMedia) { |
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches |
|
|
? 'dark' |
|
|
: 'light'; |
|
|
} |
|
|
return 'light'; |
|
|
}; |
|
|
|
|
|
export const ThemeProvider = ({ children }) => { |
|
|
const [theme, _setTheme] = useState(() => { |
|
|
try { |
|
|
return localStorage.getItem('theme-mode') || 'auto'; |
|
|
} catch { |
|
|
return 'auto'; |
|
|
} |
|
|
}); |
|
|
|
|
|
const [systemTheme, setSystemTheme] = useState(getSystemTheme()); |
|
|
|
|
|
|
|
|
const actualTheme = theme === 'auto' ? systemTheme : theme; |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (typeof window !== 'undefined' && window.matchMedia) { |
|
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); |
|
|
|
|
|
const handleSystemThemeChange = (e) => { |
|
|
setSystemTheme(e.matches ? 'dark' : 'light'); |
|
|
}; |
|
|
|
|
|
mediaQuery.addEventListener('change', handleSystemThemeChange); |
|
|
|
|
|
return () => { |
|
|
mediaQuery.removeEventListener('change', handleSystemThemeChange); |
|
|
}; |
|
|
} |
|
|
}, []); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
const body = document.body; |
|
|
if (actualTheme === 'dark') { |
|
|
body.setAttribute('theme-mode', 'dark'); |
|
|
document.documentElement.classList.add('dark'); |
|
|
} else { |
|
|
body.removeAttribute('theme-mode'); |
|
|
document.documentElement.classList.remove('dark'); |
|
|
} |
|
|
}, [actualTheme]); |
|
|
|
|
|
const setTheme = useCallback((newTheme) => { |
|
|
let themeValue; |
|
|
|
|
|
if (typeof newTheme === 'boolean') { |
|
|
|
|
|
themeValue = newTheme ? 'dark' : 'light'; |
|
|
} else if (typeof newTheme === 'string') { |
|
|
|
|
|
themeValue = newTheme; |
|
|
} else { |
|
|
themeValue = 'auto'; |
|
|
} |
|
|
|
|
|
_setTheme(themeValue); |
|
|
localStorage.setItem('theme-mode', themeValue); |
|
|
}, []); |
|
|
|
|
|
return ( |
|
|
<SetThemeContext.Provider value={setTheme}> |
|
|
<ActualThemeContext.Provider value={actualTheme}> |
|
|
<ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider> |
|
|
</ActualThemeContext.Provider> |
|
|
</SetThemeContext.Provider> |
|
|
); |
|
|
}; |
|
|
|