diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-03-23 22:05:30 +0100 | 
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-03-24 15:01:03 +0100 | 
| commit | 9226671f49b507ce6f71e6e2c3621014f05f74e9 (patch) | |
| tree | 73a148d12ceebbf0d8a95b82353d3d84f6a7a76a /src/utils/providers/prism-theme.tsx | |
| parent | 4e7a96c5a831882463802cdd4f84fe1464969cb0 (diff) | |
refactor: load prism plugins without babel
Diffstat (limited to 'src/utils/providers/prism-theme.tsx')
| -rw-r--r-- | src/utils/providers/prism-theme.tsx | 165 | 
1 files changed, 165 insertions, 0 deletions
| diff --git a/src/utils/providers/prism-theme.tsx b/src/utils/providers/prism-theme.tsx new file mode 100644 index 0000000..2ed8454 --- /dev/null +++ b/src/utils/providers/prism-theme.tsx @@ -0,0 +1,165 @@ +import { LocalStorage } from '@services/local-storage'; +import { +  createContext, +  FC, +  useCallback, +  useContext, +  useEffect, +  useState, +} from 'react'; + +export type PrismTheme = 'dark' | 'light' | 'system'; +export type ResolvedPrismTheme = 'dark' | 'light'; + +export type UsePrismThemeProps = { +  themes: PrismTheme[]; +  theme?: PrismTheme; +  setTheme: (theme: PrismTheme) => void; +  resolvedTheme?: ResolvedPrismTheme; +  codeBlocks?: NodeListOf<HTMLPreElement>; +  setCodeBlocks: (codeBlocks: NodeListOf<HTMLPreElement>) => void; +}; + +export type PrismThemeProviderProps = { +  attribute?: string; +  storageKey?: string; +  themes?: PrismTheme[]; +}; + +export const PrismThemeContext = createContext<UsePrismThemeProps>({ +  themes: ['dark', 'light', 'system'], +  setTheme: (_) => { +    // This is intentional. +  }, +  setCodeBlocks: (_) => { +    // This is intentional. +  }, +}); + +export const usePrismTheme = () => useContext(PrismThemeContext); + +const prefersDarkScheme = () => { +  if (typeof window === 'undefined') return; + +  return ( +    window.matchMedia && +    window.matchMedia('(prefers-color-scheme: dark)').matches +  ); +}; + +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(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, +}) => { +  const getThemeFromSystem = useCallback(() => { +    return prefersDarkScheme() ? 'dark' : 'light'; +  }, []); + +  const [prismTheme, setPrismTheme] = useState<PrismTheme>( +    getTheme(storageKey) || 'system' +  ); + +  const updateTheme = (theme: PrismTheme) => { +    setPrismTheme(theme); +  }; + +  useEffect(() => { +    LocalStorage.set(storageKey, prismTheme); +  }, [prismTheme, storageKey]); + +  const [resolvedTheme, setResolvedTheme] = useState<ResolvedPrismTheme>(); + +  useEffect(() => { +    if (prismTheme === 'dark' || prismTheme === 'light') { +      setResolvedTheme(prismTheme); +    } else { +      setResolvedTheme(getThemeFromSystem()); +    } +  }, [prismTheme, getThemeFromSystem]); + +  const updateResolvedTheme = useCallback(() => { +    setResolvedTheme(getThemeFromSystem()); +  }, [getThemeFromSystem]); + +  useEffect(() => { +    window +      .matchMedia('(prefers-color-scheme: dark)') +      .addEventListener('change', updateResolvedTheme); + +    return () => +      window +        .matchMedia('(prefers-color-scheme: dark)') +        .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 listenAttributeChange = useCallback( +    (pre: HTMLPreElement) => { +      var observer = new MutationObserver(function (mutations) { +        mutations.forEach((record) => { +          var mutatedPre = record.target as HTMLPreElement; +          var newTheme = mutatedPre.getAttribute(attribute) as PrismTheme; +          setPrismTheme(newTheme); +        }); +      }); +      observer.observe(pre, { +        attributes: true, +        attributeFilter: [attribute], +      }); +    }, +    [attribute] +  ); + +  useEffect(() => { +    if (!preTags) return; + +    preTags.forEach((pre) => { +      listenAttributeChange(pre); +    }); +  }, [preTags, listenAttributeChange]); + +  return ( +    <PrismThemeContext.Provider +      value={{ +        themes, +        theme: prismTheme, +        setTheme: updateTheme, +        codeBlocks: preTags, +        setCodeBlocks: updatePreTags, +        resolvedTheme, +      }} +    > +      {children} +    </PrismThemeContext.Provider> +  ); +}; | 
