import React, { useCallback, useState } from "react"; import Toast, { ToastData } from "./ui/atoms/toast"; import { Theme } from "./types/theme"; import { QueueEntry } from "./types/playground"; interface UIStatus { toggleMobileOverlay: (id: Overlay, shown?: boolean) => void; isSidebarOpen: boolean; setIsSidebarOpen: React.Dispatch>; setToastData: React.Dispatch>; colorScheme: Theme; setColorScheme: React.Dispatch>; queuedExamples: Set; queue: QueueEntry[]; setQueue: React.Dispatch>; highlightedQueueExample: null | string; setHighlightedQueueExample: (value: string | null) => void; } export enum Overlay { Sidebar, ArticleActions, WatchMenu, BookmarkMenu, } const UIContext = React.createContext({ toggleMobileOverlay: () => {}, isSidebarOpen: false, setIsSidebarOpen: () => {}, setToastData: () => {}, colorScheme: "os-default", setColorScheme: () => {}, queuedExamples: new Set(), queue: [], setQueue: () => {}, highlightedQueueExample: null, setHighlightedQueueExample: () => {}, }); export function UIProvider(props: any) { const initialTheme = window.localStorage.getItem("theme"); const [mobileOverlays, setMobileOverlays] = useState>(new Set()); const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [toastData, setToastData] = useState(null); const [colorScheme, setColorScheme] = useState( (initialTheme as Theme) || "os-default" ); const [queuedExamples, setQueuedExamples] = useState>(new Set()); const [queue, setQueue] = useState([]); const [highlightedQueueExample, setHighlightedQueueExample] = useState< string | null >(null); const toggleMobileOverlay = useCallback( (overlay: Overlay, shown?: boolean) => { setMobileOverlays((oldOverlays) => { const overlays = new Set(oldOverlays); if (typeof shown !== "boolean") { overlays.has(overlay) ? overlays.delete(overlay) : overlays.add(overlay); } else if (shown) { overlays.add(overlay); } else { overlays.delete(overlay); } // if the set hasn't changed, return the old set object // so react doesn't think it's been mutated return oldOverlays.size === overlays.size ? oldOverlays : overlays; }); }, [] ); React.useEffect(() => { const setDark = (event) => { if (event.matches) { setColorScheme("dark"); } }; const setLight = (event) => { if (event.matches) { setColorScheme("light"); } }; const dark = window.matchMedia("(prefers-color-scheme: dark)"); if (dark.matches) { setColorScheme("dark"); } if (!("addEventListener" in dark)) { // MediaQueryList doesn't inherit EventTarget in Safari < 14. return; } dark.addEventListener("change", setDark); const light = window.matchMedia("(prefers-color-scheme: light)"); light.addEventListener("change", setLight); return () => { light.removeEventListener("change", setLight); dark.removeEventListener("change", setDark); }; }, []); React.useEffect(() => { toggleMobileOverlay(Overlay.Sidebar, isSidebarOpen); }, [isSidebarOpen, toggleMobileOverlay]); React.useEffect(() => { mobileOverlays.size ? document.body.classList.add("mobile-overlay-active") : document.body.classList.remove("mobile-overlay-active"); }, [mobileOverlays]); React.useEffect(() => { setQueuedExamples(new Set(queue.map((item) => item.id))); }, [queue]); return ( {props.children} {toastData ? ( { if (toastData?.closeHandler) toastData.closeHandler(e); setToastData(null); }} /> ) : null} ); } export function useUIStatus() { return React.useContext(UIContext); }