import { PropsWithChildren, createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react' export type UpdateThemeFn = (themeName: Theme) => void export type SetAccentColorFn = (accentColor: string | undefined) => void export enum Theme { Light = 'light', Dark = 'dark', } export interface IThemeContext { theme: Theme themeChangeCount: number updateTheme: UpdateThemeFn // Automatically falls back to brand color. accentColor: string setAccentColor: SetAccentColorFn } export const DEFAULT_THEME_NAME = Theme.Dark export const DEFAULT_THEME: IThemeContext = { theme: DEFAULT_THEME_NAME, themeChangeCount: 0, updateTheme: (themeName: string) => { console.error(`do-nothing update for ${themeName}`) }, accentColor: '', setAccentColor: (accentColor: string | undefined) => { console.error(`do-nothing update for ${accentColor}`) }, } export const ThemeContext = createContext(DEFAULT_THEME) export const ThemeProvider = ({ children, theme, themeChangeCount, updateTheme, }: PropsWithChildren< Omit >) => { // This is the same as the `useNamedThemeColor` hook, but that hook assumes it // is wrapped in this ThemeProvider, so let's just get the brand manually. const [accentColor, _setAccentColor] = useState( `rgb(${getNamedColorFromDOM('brand') || '123, 97, 255'})` ) const setAccentColor = useCallback( (accentColor: string | undefined) => _setAccentColor(accentColor || `rgb(${getNamedColorFromDOM('brand')})`), [] ) return ( ({ ...DEFAULT_THEME, theme, themeChangeCount, updateTheme, accentColor, setAccentColor, }), [accentColor, setAccentColor, theme, themeChangeCount, updateTheme] )} > {children} ) } export const useThemeContext = () => useContext(ThemeContext) export const useNamedThemeColor = (colorName: string) => { const { themeChangeCount } = useThemeContext() const [color, setColor] = useState( getNamedColorFromDOM(colorName) ) useEffect( () => { setColor(getNamedColorFromDOM(colorName)) }, // Re-fetch color when theme changes. // eslint-disable-next-line react-hooks/exhaustive-deps [themeChangeCount] ) return color } const getNamedColorFromDOM = (colorName: string) => typeof getComputedStyle !== 'undefined' ? getComputedStyle(document.body).getPropertyValue(`--${colorName}`).trim() : undefined