diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-05-19 19:46:24 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-05-19 19:46:24 +0200 |
| commit | bbd63400f94b43fde04449e0c71d14763d893e6a (patch) | |
| tree | 057055dce19fc71c7c2e2fa05b691144224dfbd0 /src/utils/providers/prism-theme.tsx | |
| parent | 806004ab79ac4e1cb49cef93ab3f35a08c5c82b5 (diff) | |
refactor: rewrite Prism hooks and providers
It avoid some hydratation errors on project pages (not in article
however) and the hooks are now reusable.
Diffstat (limited to 'src/utils/providers/prism-theme.tsx')
| -rw-r--r-- | src/utils/providers/prism-theme.tsx | 78 |
1 files changed, 31 insertions, 47 deletions
diff --git a/src/utils/providers/prism-theme.tsx b/src/utils/providers/prism-theme.tsx index 23a2a36..dd8feb7 100644 --- a/src/utils/providers/prism-theme.tsx +++ b/src/utils/providers/prism-theme.tsx @@ -1,4 +1,6 @@ -import { LocalStorage } from '@services/local-storage'; +import useAttributes from '@utils/hooks/use-attributes'; +import useLocalStorage from '@utils/hooks/use-local-storage'; +import useQuerySelectorAll from '@utils/hooks/use-query-selector-all'; import { createContext, FC, @@ -10,7 +12,7 @@ import { } from 'react'; export type PrismTheme = 'dark' | 'light' | 'system'; -export type ResolvedPrismTheme = 'dark' | 'light'; +export type ResolvedPrismTheme = Exclude<PrismTheme, 'system'>; export type UsePrismThemeProps = { themes: PrismTheme[]; @@ -18,7 +20,6 @@ export type UsePrismThemeProps = { setTheme: (theme: PrismTheme) => void; resolvedTheme?: ResolvedPrismTheme; codeBlocks?: NodeListOf<HTMLPreElement>; - setCodeBlocks: (codeBlocks: NodeListOf<HTMLPreElement>) => void; }; export type PrismThemeProviderProps = { @@ -33,14 +34,16 @@ export const PrismThemeContext = createContext<UsePrismThemeProps>({ setTheme: (_) => { // This is intentional. }, - setCodeBlocks: (_) => { - // This is intentional. - }, }); export const usePrismTheme = () => useContext(PrismThemeContext); -const prefersDarkScheme = () => { +/** + * Check if user prefers dark color scheme. + * + * @returns {boolean|undefined} True if `prefers-color-scheme` is set to `dark`. + */ +const prefersDarkScheme = (): boolean | undefined => { if (typeof window === 'undefined') return; return ( @@ -49,40 +52,35 @@ const prefersDarkScheme = () => { ); }; +/** + * Check if a given string is a Prism theme name. + * + * @param {string} theme - A string. + * @returns {boolean} True if the given string match a Prism theme name. + */ const isValidTheme = (theme: string): boolean => { return theme === 'dark' || theme === 'light' || theme === 'system'; }; -const getTheme = (key: string): PrismTheme | undefined => { - if (typeof window === 'undefined') return undefined; - const storageValue = LocalStorage.get<string>(key); - - return storageValue && isValidTheme(storageValue) - ? (storageValue as PrismTheme) - : undefined; -}; - export const PrismThemeProvider: FC<PrismThemeProviderProps> = ({ attribute = 'data-prismjs-color-scheme-current', storageKey = 'prismjs-color-scheme', themes = ['dark', 'light', 'system'], children, }) => { + /** + * Retrieve the theme to use depending on `prefers-color-scheme`. + */ const getThemeFromSystem = useCallback(() => { return prefersDarkScheme() ? 'dark' : 'light'; }, []); - const [prismTheme, setPrismTheme] = useState<PrismTheme>( - getTheme(storageKey) || 'system' - ); - - const updateTheme = (theme: PrismTheme) => { - setPrismTheme(theme); - }; + const { value: prismTheme, setValue: setPrismTheme } = + useLocalStorage<PrismTheme>(storageKey, 'system'); useEffect(() => { - LocalStorage.set(storageKey, prismTheme); - }, [prismTheme, storageKey]); + if (!isValidTheme(prismTheme)) setPrismTheme('system'); + }, [prismTheme, setPrismTheme]); const [resolvedTheme, setResolvedTheme] = useState<ResolvedPrismTheme>(); @@ -109,22 +107,12 @@ export const PrismThemeProvider: FC<PrismThemeProviderProps> = ({ .removeEventListener('change', updateResolvedTheme); }, [updateResolvedTheme]); - const [preTags, setPreTags] = useState<NodeListOf<HTMLPreElement>>(); - - const updatePreTags = useCallback((tags: NodeListOf<HTMLPreElement>) => { - setPreTags(tags); - }, []); - - const updatePreTagsAttribute = useCallback(() => { - preTags?.forEach((pre) => { - pre.setAttribute(attribute, prismTheme); - }); - }, [attribute, preTags, prismTheme]); - - useEffect(() => { - updatePreTagsAttribute(); - }, [updatePreTagsAttribute, prismTheme]); + const preTags = useQuerySelectorAll<'pre'>('pre'); + useAttributes({ elements: preTags, attribute, value: prismTheme }); + /** + * Listen for changes on pre attributes and update theme. + */ const listenAttributeChange = useCallback( (pre: HTMLPreElement) => { var observer = new MutationObserver(function (mutations) { @@ -139,15 +127,12 @@ export const PrismThemeProvider: FC<PrismThemeProviderProps> = ({ attributeFilter: [attribute], }); }, - [attribute] + [attribute, setPrismTheme] ); useEffect(() => { if (!preTags) return; - - preTags.forEach((pre) => { - listenAttributeChange(pre); - }); + preTags.forEach(listenAttributeChange); }, [preTags, listenAttributeChange]); return ( @@ -155,9 +140,8 @@ export const PrismThemeProvider: FC<PrismThemeProviderProps> = ({ value={{ themes, theme: prismTheme, - setTheme: updateTheme, + setTheme: setPrismTheme, codeBlocks: preTags, - setCodeBlocks: updatePreTags, resolvedTheme, }} > |
