@kadconsulting/dry
Version:
KAD Reusable Component Library
83 lines • 4.58 kB
JavaScript
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