| import React, { createContext, useContext, useEffect, useMemo, useCallback, useRef } from 'react'; |
| import { useAtom } from 'jotai'; |
| import { IThemeRGB } from '../types'; |
| import applyTheme from '../utils/applyTheme'; |
| import { themeModeAtom, themeColorsAtom, themeNameAtom } from '../atoms/themeAtoms'; |
|
|
| type ThemeContextType = { |
| theme: string; |
| setTheme: (theme: string) => void; |
| themeRGB?: IThemeRGB; |
| setThemeRGB: (colors?: IThemeRGB) => void; |
| themeName?: string; |
| setThemeName: (name?: string) => void; |
| resetTheme: () => void; |
| }; |
|
|
| |
| export const ThemeContext = createContext<ThemeContextType>({ |
| theme: 'system', |
| setTheme: () => undefined, |
| setThemeRGB: () => undefined, |
| setThemeName: () => undefined, |
| resetTheme: () => undefined, |
| }); |
|
|
| export interface ThemeProviderProps { |
| children: React.ReactNode; |
| themeRGB?: IThemeRGB; |
| themeName?: string; |
| initialTheme?: string; |
| } |
|
|
| |
| |
| |
| export const isDark = (theme: string): boolean => { |
| if (theme === 'system') { |
| return window.matchMedia('(prefers-color-scheme: dark)').matches; |
| } |
| return theme === 'dark'; |
| }; |
|
|
| |
| |
| |
| |
| export function ThemeProvider({ |
| children, |
| themeRGB: propThemeRGB, |
| themeName: propThemeName, |
| initialTheme, |
| }: ThemeProviderProps) { |
| |
| const [theme, setTheme] = useAtom(themeModeAtom); |
| const [storedThemeRGB, setStoredThemeRGB] = useAtom(themeColorsAtom); |
| const [storedThemeName, setStoredThemeName] = useAtom(themeNameAtom); |
|
|
| |
| const propsInitialized = useRef(false); |
|
|
| |
| useEffect(() => { |
| if (!propsInitialized.current) { |
| propsInitialized.current = true; |
|
|
| |
| if (initialTheme) { |
| setTheme(initialTheme); |
| } |
|
|
| |
| if (propThemeRGB) { |
| setStoredThemeRGB(propThemeRGB); |
| } |
|
|
| |
| if (propThemeName) { |
| setStoredThemeName(propThemeName); |
| } |
| } |
| }, [initialTheme, propThemeRGB, propThemeName, setTheme, setStoredThemeRGB, setStoredThemeName]); |
|
|
| |
| const applyThemeMode = useCallback((rawTheme: string) => { |
| const root = window.document.documentElement; |
| const darkMode = isDark(rawTheme); |
|
|
| root.classList.remove(darkMode ? 'light' : 'dark'); |
| root.classList.add(darkMode ? 'dark' : 'light'); |
| }, []); |
|
|
| |
| useEffect(() => { |
| const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); |
| const changeThemeOnSystemChange = () => { |
| if (theme === 'system') { |
| applyThemeMode('system'); |
| } |
| }; |
|
|
| mediaQuery.addEventListener('change', changeThemeOnSystemChange); |
| return () => { |
| mediaQuery.removeEventListener('change', changeThemeOnSystemChange); |
| }; |
| }, [theme, applyThemeMode]); |
|
|
| |
| useEffect(() => { |
| applyThemeMode(theme); |
| }, [theme, applyThemeMode]); |
|
|
| |
| useEffect(() => { |
| if (storedThemeRGB) { |
| applyTheme(storedThemeRGB); |
| } |
| }, [storedThemeRGB]); |
|
|
| |
| const resetTheme = useCallback(() => { |
| setTheme('system'); |
| setStoredThemeRGB(undefined); |
| setStoredThemeName(undefined); |
| |
| const root = document.documentElement; |
| const customProps = Array.from(root.style).filter((prop) => prop.startsWith('--')); |
| customProps.forEach((prop) => root.style.removeProperty(prop)); |
| }, [setTheme, setStoredThemeRGB, setStoredThemeName]); |
|
|
| const value = useMemo( |
| () => ({ |
| theme, |
| setTheme, |
| themeRGB: storedThemeRGB, |
| setThemeRGB: setStoredThemeRGB, |
| themeName: storedThemeName, |
| setThemeName: setStoredThemeName, |
| resetTheme, |
| }), |
| [ |
| theme, |
| setTheme, |
| storedThemeRGB, |
| setStoredThemeRGB, |
| storedThemeName, |
| setStoredThemeName, |
| resetTheme, |
| ], |
| ); |
|
|
| return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>; |
| } |
|
|
| |
| |
| |
| export function useTheme() { |
| const context = useContext(ThemeContext); |
| if (!context) { |
| throw new Error('useTheme must be used within a ThemeProvider'); |
| } |
| return context; |
| } |
|
|
| export default ThemeProvider; |
|
|