UNPKG

@kadconsulting/dry

Version:
83 lines 4.58 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { useMemo, useState, useEffect, useCallback, createContext, useLayoutEffect, } from 'react'; import { ThemeTypes, } from './types'; import { useLocalStorageForKey, LocalStorageValueTypes, } from '../../hooks/useLocalStorageForKey'; import { LocalStorageKeys } from '../../config/LocalStorageKeys'; export const ThemeContext = createContext(undefined); export const ThemeProvider = ({ themes, children, overrides = {}, selector = ':root', }) => { const storageTheme = useLocalStorageForKey(LocalStorageKeys.THEME, { valueType: LocalStorageValueTypes.STRING }); const computeInitialThemeValue = useCallback(() => { /** 1. Prioritize the library consumer's manual override if this is a test or Storybook render */ if (overrides.themeType !== undefined) return overrides.themeType; /** Window may not exist in certain contexts, like SSR (i.e. Next.js) */ if (typeof window === 'undefined') return ThemeTypes.LIGHT; /** 2. Prioritize the user's manual preference */ const localStorageTheme = storageTheme.get(); if (localStorageTheme) return localStorageTheme; /** 3. If no manual preference, check for a system preference */ if (window.matchMedia('(prefers-color-scheme: dark)').matches) return ThemeTypes.DARK; return ThemeTypes.LIGHT; }, [storageTheme.get, overrides.themeType]); const [themeType, setThemeType] = useState(computeInitialThemeValue()); /** Update state values when overrides change */ useEffect(() => { if (overrides.themeType !== undefined) { setThemeValue(overrides.themeType); setThemeType(overrides.themeType); } }, [overrides.themeType]); /** Add the appropriate class to the `selector` node on mount */ useLayoutEffect(() => { const themedNode = document.querySelector(selector); if (themedNode?.classList.contains(themeType)) return; themedNode?.classList.add(themeType); }, [themeType, selector]); const updateSelectorHTMLClass = useCallback((newThemeType) => { /** Replace the themed node's old theme class with the new theme class; this effectively toggles the theme */ const themedNode = document.querySelector(selector); themedNode?.classList.replace(themeType, newThemeType); }, [themeType, selector]); const updateRootVariables = () => { console.log(themes[themeType]); // TODO-p1: This is a temporary solution // @ts-ignore for (const [key, val] of Object.entries(themes[themeType]['palette'])) { document.documentElement.style.setProperty('--' + key, val); } }; updateRootVariables(); /** Essentially a public setter, preventing consumers from updating the value without the required side-effects */ const setThemeValue = useCallback((newThemeType) => { updateSelectorHTMLClass(newThemeType); /** Now that the themeType has been used to remove the old class, it's safe to overwrite it */ setThemeType(newThemeType); /** Presumably, this has been called from some UI where the user is setting their preference; save the preference so it can be persisted between page loads */ storageTheme.set(newThemeType); }, [updateSelectorHTMLClass, storageTheme]); /** Remove the user's stored preference so that computeInitialTheme returns the prefers-color-scheme system preference */ const useSystemPreference = useCallback(() => { storageTheme.remove(); /** Compute the new theme based on OS system preferences */ const newTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? ThemeTypes.DARK : ThemeTypes.LIGHT; updateSelectorHTMLClass(newTheme); /** Now that the themeType has been used to remove the old class, it's safe to overwrite it */ setThemeValue(newTheme); }, [setThemeValue, storageTheme, updateSelectorHTMLClass]); /** Expose the appropriate JavaScript theme object to context consumers */ const theme = useMemo(() => themes[themeType], [themes, themeType]); const contextValue = useMemo(() => ({ theme, themeType, setTheme: setThemeValue, useSystemPreference, }), [theme, themeType, setThemeValue, useSystemPreference]); return (_jsx(ThemeContext.Provider, { value: contextValue, children: children })); }; //# sourceMappingURL=ThemeProvider.js.map