"use client"; import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, type MutableRefObject, type ReactNode, } from "react"; import type { AnnotationChannel } from "@prototype/lib/prototype-comments/core/annotation-channel"; import { scrollPrototypeTargetIntoView } from "@prototype/lib/prototypes/prototype-share-link"; import { hasReviewUrlParams, parseReviewStateFromSearchParams, syncReviewStateToUrl, type PrototypeReviewSidebarPanel, type PrototypeReviewTab, } from "@prototype/lib/prototypes/prototype-review-url"; import { focusPrototypeTweak } from "@prototype/lib/prototypes/prototype-tweak-registry"; import type { PrototypeStateCanvasConfig } from "@prototype/lib/prototypes/prototype-state-canvas-types"; import type { PrototypePreviewState } from "@prototype/lib/prototypes/prototype-preview-state-types"; import { defaultReferenceDocsConfigPath, type PrototypeReferenceDoc, } from "@prototype/lib/prototypes/reference-docs"; import { prototypeReviewPreferenceKey, usePersistedLocalString, } from "@prototype/lib/prototypes/use-persisted-local-state"; export type { PrototypeReviewSidebarPanel, PrototypeReviewTab }; export type PrototypeViewportLayout = "desktop" | "mobile"; export type PrototypeHandoffSidebarPanel = Extract< PrototypeReviewSidebarPanel, "change-log" | "spec" >; function isHandoffSidebarPanel( panel: string | null | undefined, ): panel is PrototypeHandoffSidebarPanel { return panel === "change-log" || panel === "spec"; } /** iPhone 14/15 logical width */ export const PROTOTYPE_MOBILE_VIEWPORT_WIDTH_PX = 390; /** iPhone 14/15 logical height */ export const PROTOTYPE_MOBILE_VIEWPORT_HEIGHT_PX = 844; export type PrototypeVariantSet = { id: string; label: string; onActivate?: () => void; }; function hasPrototypeTweaksContent(content: ReactNode | null | undefined): boolean { return content != null && content !== false && content !== true; } type PrototypeReviewContextValue = { slug: string; open: boolean; sidebarPanel: PrototypeReviewSidebarPanel | null; activeTab: PrototypeReviewTab; captureChannel: AnnotationChannel; setCaptureChannel: (channel: AnnotationChannel) => void; prodReferenceAvailable: boolean; showProdReference: boolean; setShowProdReference: (show: boolean) => void; toggleProdReference: () => void; registerProdReference: (available: boolean) => void; /** Latest prototype controls panel content — read via ref after `setTweaksContent`. */ tweaksContentRef: MutableRefObject; /** Whether prototype controls content is registered (drives States toolbar button). */ hasTweaksContent: boolean; setTweaksContent: (content: ReactNode | null) => void; /** Latest spec panel content — read via ref after `setSpecContent`. */ specContentRef: MutableRefObject; /** Whether spec content is registered (drives Spec toolbar button). */ hasSpecContent: boolean; setSpecContent: (content: ReactNode | null) => void; /** Latest state canvas config — read via ref after `setStateCanvasConfig`. */ stateCanvasConfigRef: MutableRefObject; /** Whether a state canvas is registered (drives Canvas toolbar button). */ hasStateCanvas: boolean; setStateCanvasConfig: ( config: PrototypeStateCanvasConfig | null, ) => void; stateCanvasPagePath: string | null; setStateCanvasPagePath: (path: string | null) => void; /** Preview states from the controls panel select (read via ref for New state modal). */ previewStatesRef: MutableRefObject; registerPreviewStates: (states: PrototypePreviewState[]) => void; /** Currently previewed state id (from the States menu select). */ activePreviewStateId: string | null; setActivePreviewStateId: (id: string | null) => void; registerOpenCreateStateModal: (handler: (() => void) | null) => void; openCreateStateModal: () => void; referenceDocs: PrototypeReferenceDoc[]; referenceDocsConfigFilePath: string | undefined; registerReferenceDocs: ( docs: PrototypeReferenceDoc[], configFilePath?: string, ) => void; variantSets: PrototypeVariantSet[]; variantSetPopoverTargets: Record; /** Bumped when variant sidebar panel content updates (read via getters). */ variantSidebarRevision: number; legacyVariantsAvailable: boolean; legacyVariantsPopoverTarget: HTMLElement | null; activeVariantSetId: string | null; highlightTargetId: string | null; tweaksOpen: boolean; variantsOpen: boolean; settingsOpen: boolean; shareModeActive: boolean; openSidebar: (panel?: PrototypeReviewSidebarPanel) => void; /** Opens the last-used handoff panel (Overview or PRs). */ openLastHandoffPanel: () => void; closeSidebar: () => void; toggleSidebar: () => void; setActiveTab: (tab: PrototypeReviewTab) => void; registerVariantSet: (set: { id: string; label: string; onActivate?: () => void; }) => void; registerVariantSetPopover: (id: string, element: HTMLElement | null) => void; registerVariantSidebarContent: (id: string, content: ReactNode | null) => void; updateVariantSidebarContent: (id: string, content: ReactNode) => void; getVariantSidebarContent: (id: string) => ReactNode | null; registerLegacyVariantSidebarContent: (content: ReactNode | null) => void; updateLegacyVariantSidebarContent: (content: ReactNode) => void; getLegacyVariantSidebarContent: () => ReactNode | null; unregisterVariantSet: (id: string) => void; setLegacyVariantsAvailable: (available: boolean) => void; registerLegacyVariantsPopover: (element: HTMLElement | null) => void; focusTweak: (tweakId: string) => void; clearHighlight: () => void; openTweaks: () => void; closeTweaks: () => void; toggleTweaks: () => void; openVariantSet: (id: string) => void; /** Opens the last-used design exploration, or the first registered set. */ openLastVariantSet: () => void; closeVariants: () => void; toggleVariantSet: (id: string) => void; /** @deprecated Use toggleVariantSet instead. */ openVariants: () => void; /** @deprecated Use closeVariants instead. */ toggleVariants: () => void; openSettings: () => void; closeSettings: () => void; toggleSettings: () => void; enterShareMode: () => void; exitShareMode: () => void; focusShareTarget: (targetId: string) => void; viewportLayout: PrototypeViewportLayout; setViewportLayout: (layout: PrototypeViewportLayout) => void; toggleViewportLayout: () => void; }; const PrototypeReviewContext = createContext( null, ); export function PrototypeReviewProvider({ slug, children, }: { slug: string; children: ReactNode; }) { const [open, setOpen] = useState(false); const [sidebarPanel, setSidebarPanel] = useState(null); const [activeTab, setActiveTabState] = useState("tweaks"); const tweaksContentRef = useRef(null); const [hasTweaksContent, setHasTweaksContent] = useState(false); const specContentRef = useRef(null); const [hasSpecContent, setHasSpecContent] = useState(false); const stateCanvasConfigRef = useRef(null); const [hasStateCanvas, setHasStateCanvas] = useState(false); const [stateCanvasPagePath, setStateCanvasPagePathState] = useState< string | null >(null); const [variantSetMap, setVariantSetMap] = useState< Record >({}); const [variantSetPopoverTargets, setVariantSetPopoverTargets] = useState< Record >({}); const [legacyVariantsAvailable, setLegacyVariantsAvailable] = useState(false); const [legacyVariantsPopoverTarget, setLegacyVariantsPopoverTarget] = useState(null); const variantSidebarContentRef = useRef>({}); const legacyVariantSidebarContentRef = useRef(null); const [registeredVariantSidebarIds, setRegisteredVariantSidebarIds] = useState>({}); const [legacyVariantSidebarRegistered, setLegacyVariantSidebarRegistered] = useState(false); const [variantSidebarRevision, setVariantSidebarRevision] = useState(0); const [variantSetOrder, setVariantSetOrder] = useState([]); const previewStatesRef = useRef([]); const [activePreviewStateId, setActivePreviewStateId] = useState( null, ); const openCreateStateModalRef = useRef<(() => void) | null>(null); const [referenceDocs, setReferenceDocs] = useState( [], ); const [referenceDocsConfigFilePath, setReferenceDocsConfigFilePath] = useState(undefined); const [activeVariantSetId, setActiveVariantSetId] = useState( null, ); const [highlightTargetId, setHighlightTargetId] = useState( null, ); const [tweaksOpen, setTweaksOpen] = useState(false); const [variantsOpen, setVariantsOpen] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false); const [shareModeActive, setShareModeActive] = useState(false); const [captureChannel, setCaptureChannelState] = useState("comment"); const [prodReferenceAvailable, setProdReferenceAvailable] = useState(false); const [showProdReference, setShowProdReference] = useState(false); const { value: viewportLayoutRaw, updateValue: persistViewportLayout } = usePersistedLocalString( prototypeReviewPreferenceKey(slug, "viewport-layout"), "desktop", ); const viewportLayout: PrototypeViewportLayout = viewportLayoutRaw === "mobile" ? "mobile" : "desktop"; const { value: lastActiveVariantSetId, updateValue: persistLastActiveVariantSetId, } = usePersistedLocalString( prototypeReviewPreferenceKey(slug, "last-variant-set"), "", ); const { value: lastHandoffPanelRaw, updateValue: persistLastHandoffPanel, } = usePersistedLocalString( prototypeReviewPreferenceKey(slug, "last-handoff-panel"), "change-log", ); const lastHandoffPanel: PrototypeHandoffSidebarPanel = lastHandoffPanelRaw === "spec" ? "spec" : "change-log"; const closePanelTimerRef = useRef | null>(null); const pendingUrlVariantSetRef = useRef(null); const urlHydratingRef = useRef(false); const urlSyncReadyRef = useRef(false); const setCaptureChannel = useCallback((channel: AnnotationChannel) => { setCaptureChannelState(channel); }, []); const registerProdReference = useCallback((available: boolean) => { setProdReferenceAvailable(available); if (!available) { setShowProdReference(false); } }, []); const setShowProdReferenceState = useCallback((show: boolean) => { setShowProdReference(show); }, []); const toggleProdReference = useCallback(() => { setShowProdReference((current) => !current); }, []); const variantSets = useMemo( () => variantSetOrder.map((id) => variantSetMap[id]).filter(Boolean), [variantSetMap, variantSetOrder], ); const setTweaksContent = useCallback((content: ReactNode | null) => { tweaksContentRef.current = content; const nextHasContent = hasPrototypeTweaksContent(content); setHasTweaksContent((current) => current === nextHasContent ? current : nextHasContent, ); }, []); const registerPreviewStates = useCallback((states: PrototypePreviewState[]) => { previewStatesRef.current = states; }, []); const registerOpenCreateStateModal = useCallback((handler: (() => void) | null) => { openCreateStateModalRef.current = handler; }, []); const openCreateStateModal = useCallback(() => { openCreateStateModalRef.current?.(); }, []); const registerReferenceDocs = useCallback( (docs: PrototypeReferenceDoc[], configFilePath?: string) => { setReferenceDocs(docs); setReferenceDocsConfigFilePath( configFilePath ?? defaultReferenceDocsConfigPath(slug), ); }, [slug], ); const setSpecContent = useCallback((content: ReactNode | null) => { specContentRef.current = content; const nextHasContent = content != null; setHasSpecContent((current) => current === nextHasContent ? current : nextHasContent, ); }, []); const setStateCanvasConfig = useCallback( (config: PrototypeStateCanvasConfig | null) => { stateCanvasConfigRef.current = config as PrototypeStateCanvasConfig | null; const nextHasCanvas = config != null; setHasStateCanvas((current) => current === nextHasCanvas ? current : nextHasCanvas, ); }, [], ); const setStateCanvasPagePath = useCallback((path: string | null) => { setStateCanvasPagePathState(path); setHasStateCanvas((current) => { const next = path != null || stateCanvasConfigRef.current != null; return current === next ? current : next; }); }, []); const closeVariants = useCallback(() => { setVariantsOpen(false); setActiveVariantSetId(null); setSidebarPanel((current) => { if (current === "variants") { setOpen(false); // Keep sidebarPanel as "variants" during the 250ms close animation, // then clear it so the sidebar doesn't flash comments content. if (closePanelTimerRef.current) clearTimeout(closePanelTimerRef.current); closePanelTimerRef.current = setTimeout(() => { setSidebarPanel((c) => (c === "variants" ? null : c)); closePanelTimerRef.current = null; }, 280); return current; } return current; }); }, []); const openSidebar = useCallback( (panel?: PrototypeReviewSidebarPanel) => { const nextPanel = panel ?? "comments"; if (isHandoffSidebarPanel(nextPanel)) { persistLastHandoffPanel(nextPanel); } if (nextPanel === "comments" || nextPanel === "change-log") { setVariantsOpen(false); setActiveVariantSetId(null); } if (closePanelTimerRef.current) { clearTimeout(closePanelTimerRef.current); closePanelTimerRef.current = null; } setSidebarPanel(nextPanel); setOpen(true); if (nextPanel === "comments") { setActiveTabState(tweaksContentRef.current ? "tweaks" : "comments"); } }, [persistLastHandoffPanel], ); const openLastHandoffPanel = useCallback(() => { openSidebar(lastHandoffPanel); }, [lastHandoffPanel, openSidebar]); const closeSidebar = useCallback(() => { if (closePanelTimerRef.current) { clearTimeout(closePanelTimerRef.current); closePanelTimerRef.current = null; } setOpen(false); setSidebarPanel(null); setVariantsOpen(false); setActiveVariantSetId(null); }, []); const toggleSidebar = useCallback(() => { setOpen((current) => { if (current) { setSidebarPanel(null); setVariantsOpen(false); setActiveVariantSetId(null); return false; } setSidebarPanel("comments"); setActiveTabState(tweaksContentRef.current ? "tweaks" : "comments"); return true; }); }, []); const setActiveTab = useCallback((tab: PrototypeReviewTab) => { setActiveTabState(tab); }, []); const clearHighlight = useCallback(() => { setHighlightTargetId(null); }, []); const focusTweak = useCallback((tweakId: string) => { const targetId = focusPrototypeTweak(tweakId); if (targetId) { setHighlightTargetId(targetId); } }, []); const variantSetActivateHandlersRef = useRef void) | undefined>>( {}, ); const registerVariantSet = useCallback( (set: { id: string; label: string; onActivate?: () => void }) => { variantSetActivateHandlersRef.current[set.id] = set.onActivate; setVariantSetMap((current) => { const existing = current[set.id]; if (existing?.label === set.label) return current; return { ...current, [set.id]: { id: set.id, label: set.label, }, }; }); setVariantSetOrder((current) => current.includes(set.id) ? current : [...current, set.id], ); }, [], ); const registerVariantSetPopover = useCallback( (id: string, element: HTMLElement | null) => { setVariantSetPopoverTargets((current) => { if (current[id] === element) return current; return { ...current, [id]: element }; }); }, [], ); const registerLegacyVariantsPopover = useCallback( (element: HTMLElement | null) => { setLegacyVariantsPopoverTarget((current) => current === element ? current : element, ); }, [], ); const registerVariantSidebarContent = useCallback( (id: string, content: ReactNode | null) => { if (content == null) { if (!(id in variantSidebarContentRef.current)) return; delete variantSidebarContentRef.current[id]; setRegisteredVariantSidebarIds((current) => { if (!(id in current)) return current; const next = { ...current }; delete next[id]; return next; }); setVariantSidebarRevision((revision) => revision + 1); return; } variantSidebarContentRef.current[id] = content; setRegisteredVariantSidebarIds((current) => { if (id in current) return current; return { ...current, [id]: true }; }); }, [], ); const updateVariantSidebarContent = useCallback( (id: string, content: ReactNode) => { if (!(id in variantSidebarContentRef.current)) return; variantSidebarContentRef.current[id] = content; setVariantSidebarRevision((revision) => revision + 1); }, [], ); const getVariantSidebarContent = useCallback((id: string) => { return variantSidebarContentRef.current[id] ?? null; }, []); const registerLegacyVariantSidebarContent = useCallback( (content: ReactNode | null) => { if (content == null) { legacyVariantSidebarContentRef.current = null; setLegacyVariantSidebarRegistered((current) => current ? false : current, ); setVariantSidebarRevision((revision) => revision + 1); return; } legacyVariantSidebarContentRef.current = content; setLegacyVariantSidebarRegistered((current) => (current ? current : true)); }, [], ); const updateLegacyVariantSidebarContent = useCallback((content: ReactNode) => { if (legacyVariantSidebarContentRef.current == null) return; legacyVariantSidebarContentRef.current = content; setVariantSidebarRevision((revision) => revision + 1); }, []); const getLegacyVariantSidebarContent = useCallback( () => legacyVariantSidebarContentRef.current, [], ); const unregisterVariantSet = useCallback((id: string) => { delete variantSetActivateHandlersRef.current[id]; setVariantSetMap((current) => { if (!(id in current)) return current; const next = { ...current }; delete next[id]; return next; }); setVariantSetOrder((current) => current.filter((entry) => entry !== id)); setActiveVariantSetId((current) => (current === id ? null : current)); setVariantSetPopoverTargets((current) => { if (!(id in current)) return current; const next = { ...current }; delete next[id]; return next; }); delete variantSidebarContentRef.current[id]; setRegisteredVariantSidebarIds((current) => { if (!(id in current)) return current; const next = { ...current }; delete next[id]; return next; }); }, []); const openVariantSet = useCallback( (id: string) => { variantSetActivateHandlersRef.current[id]?.(); setSettingsOpen(false); setTweaksOpen(false); if (closePanelTimerRef.current) { clearTimeout(closePanelTimerRef.current); closePanelTimerRef.current = null; } setActiveVariantSetId(id); persistLastActiveVariantSetId(id); setVariantsOpen(true); setSidebarPanel("variants"); setOpen(true); }, [persistLastActiveVariantSetId], ); const openLastVariantSet = useCallback(() => { const preferred = lastActiveVariantSetId && variantSetMap[lastActiveVariantSetId] ? lastActiveVariantSetId : null; const id = preferred ?? variantSetOrder[0] ?? null; if (id) { openVariantSet(id); return; } setSettingsOpen(false); setTweaksOpen(false); if (closePanelTimerRef.current) { clearTimeout(closePanelTimerRef.current); closePanelTimerRef.current = null; } setVariantsOpen(true); setSidebarPanel("variants"); setOpen(true); }, [ lastActiveVariantSetId, openVariantSet, variantSetMap, variantSetOrder, ]); const toggleVariantSet = useCallback( (id: string) => { if (variantsOpen && activeVariantSetId === id) { closeVariants(); return; } openVariantSet(id); }, [activeVariantSetId, closeVariants, openVariantSet, variantsOpen], ); const openVariants = useCallback(() => { openLastVariantSet(); }, [openLastVariantSet]); const toggleVariants = useCallback(() => { openLastVariantSet(); }, [openLastVariantSet]); const openTweaks = useCallback(() => { setSettingsOpen(false); setTweaksOpen(true); }, []); const closeTweaks = useCallback(() => setTweaksOpen(false), []); const toggleTweaks = useCallback(() => { setTweaksOpen((current) => { const next = !current; if (next) { setSettingsOpen(false); } return next; }); }, []); const openSettings = useCallback(() => { setTweaksOpen(false); setSettingsOpen(true); }, []); const closeSettings = useCallback(() => setSettingsOpen(false), []); const toggleSettings = useCallback(() => { setSettingsOpen((current) => { const next = !current; if (next) { setTweaksOpen(false); } return next; }); }, []); const enterShareMode = useCallback(() => { setSettingsOpen(false); setTweaksOpen(false); setShareModeActive(true); }, []); const exitShareMode = useCallback(() => { setShareModeActive(false); }, []); const focusShareTarget = useCallback((targetId: string) => { scrollPrototypeTargetIntoView(targetId); setHighlightTargetId(targetId); }, []); const setViewportLayout = useCallback( (layout: PrototypeViewportLayout) => { persistViewportLayout(layout); }, [persistViewportLayout], ); const toggleViewportLayout = useCallback(() => { persistViewportLayout(viewportLayout === "desktop" ? "mobile" : "desktop"); }, [persistViewportLayout, viewportLayout]); const applyReviewStateFromUrl = useCallback( (parsed: ReturnType) => { setShowProdReference(parsed.prodReference); if (parsed.settingsOpen) { setSettingsOpen(true); setTweaksOpen(false); } else if (parsed.tweaksOpen) { setTweaksOpen(true); setSettingsOpen(false); } else { setTweaksOpen(false); setSettingsOpen(false); } if (!parsed.panel) { if (closePanelTimerRef.current) { clearTimeout(closePanelTimerRef.current); closePanelTimerRef.current = null; } setOpen(false); setSidebarPanel(null); setVariantsOpen(false); setActiveVariantSetId(null); return; } if (closePanelTimerRef.current) { clearTimeout(closePanelTimerRef.current); closePanelTimerRef.current = null; } if (parsed.panel === "variants") { setTweaksOpen(false); setSettingsOpen(false); setVariantsOpen(true); setSidebarPanel("variants"); setOpen(true); if (parsed.variantSetId) { pendingUrlVariantSetRef.current = parsed.variantSetId; setActiveVariantSetId(parsed.variantSetId); persistLastActiveVariantSetId(parsed.variantSetId); } else { pendingUrlVariantSetRef.current = null; setActiveVariantSetId(null); } return; } pendingUrlVariantSetRef.current = null; setVariantsOpen(false); setActiveVariantSetId(null); setSidebarPanel(parsed.panel); setOpen(true); if (isHandoffSidebarPanel(parsed.panel)) { persistLastHandoffPanel(parsed.panel); } if (parsed.panel === "comments") { setActiveTabState( parsed.activeTab === "comments" && !tweaksContentRef.current ? "comments" : parsed.activeTab, ); } }, [persistLastActiveVariantSetId, persistLastHandoffPanel], ); const hydrateReviewStateFromUrl = useCallback(() => { if (typeof window === "undefined") return; const searchParams = new URLSearchParams(window.location.search); if (!hasReviewUrlParams(searchParams)) { urlSyncReadyRef.current = true; return; } urlHydratingRef.current = true; applyReviewStateFromUrl(parseReviewStateFromSearchParams(searchParams)); urlHydratingRef.current = false; urlSyncReadyRef.current = true; }, [applyReviewStateFromUrl]); useEffect(() => { hydrateReviewStateFromUrl(); }, [hydrateReviewStateFromUrl]); useEffect(() => { const onPopState = () => { hydrateReviewStateFromUrl(); }; window.addEventListener("popstate", onPopState); return () => window.removeEventListener("popstate", onPopState); }, [hydrateReviewStateFromUrl]); useLayoutEffect(() => { const pendingId = pendingUrlVariantSetRef.current; if (!pendingId) return; const handler = variantSetActivateHandlersRef.current[pendingId]; if (!handler) return; handler(); pendingUrlVariantSetRef.current = null; }, [variantSetOrder, variantSets]); useEffect(() => { if (!urlSyncReadyRef.current || urlHydratingRef.current) return; syncReviewStateToUrl({ open, sidebarPanel, activeVariantSetId, activeTab, tweaksOpen, settingsOpen, prodReferenceAvailable, showProdReference, }); }, [ open, sidebarPanel, activeVariantSetId, activeTab, tweaksOpen, settingsOpen, prodReferenceAvailable, showProdReference, ]); const value = useMemo(() => { return { slug, open, sidebarPanel, activeTab, captureChannel, setCaptureChannel, prodReferenceAvailable, showProdReference, setShowProdReference: setShowProdReferenceState, toggleProdReference, registerProdReference, tweaksContentRef, hasTweaksContent, setTweaksContent, specContentRef, hasSpecContent, setSpecContent, stateCanvasConfigRef, hasStateCanvas, setStateCanvasConfig, stateCanvasPagePath, setStateCanvasPagePath, previewStatesRef, registerPreviewStates, activePreviewStateId, setActivePreviewStateId, registerOpenCreateStateModal, openCreateStateModal, referenceDocs, referenceDocsConfigFilePath, registerReferenceDocs, variantSets, variantSetPopoverTargets, variantSidebarRevision, legacyVariantsAvailable, legacyVariantsPopoverTarget, activeVariantSetId, highlightTargetId, tweaksOpen, variantsOpen, settingsOpen, shareModeActive, openSidebar, openLastHandoffPanel, closeSidebar, toggleSidebar, setActiveTab, registerVariantSet, registerVariantSetPopover, registerVariantSidebarContent, updateVariantSidebarContent, getVariantSidebarContent, registerLegacyVariantSidebarContent, updateLegacyVariantSidebarContent, getLegacyVariantSidebarContent, unregisterVariantSet, setLegacyVariantsAvailable, registerLegacyVariantsPopover, focusTweak, clearHighlight, openTweaks, closeTweaks, toggleTweaks, openVariantSet, openLastVariantSet, closeVariants, toggleVariantSet, openVariants, toggleVariants, openSettings, closeSettings, toggleSettings, enterShareMode, exitShareMode, focusShareTarget, viewportLayout, setViewportLayout, toggleViewportLayout, }; }, [ slug, open, sidebarPanel, activeTab, captureChannel, setCaptureChannel, prodReferenceAvailable, showProdReference, setShowProdReferenceState, toggleProdReference, registerProdReference, tweaksContentRef, hasTweaksContent, setTweaksContent, specContentRef, hasSpecContent, setSpecContent, stateCanvasConfigRef, hasStateCanvas, setStateCanvasConfig, stateCanvasPagePath, setStateCanvasPagePath, previewStatesRef, registerPreviewStates, activePreviewStateId, setActivePreviewStateId, registerOpenCreateStateModal, openCreateStateModal, referenceDocs, referenceDocsConfigFilePath, registerReferenceDocs, variantSets, variantSetPopoverTargets, variantSidebarRevision, legacyVariantsAvailable, legacyVariantsPopoverTarget, activeVariantSetId, highlightTargetId, tweaksOpen, variantsOpen, settingsOpen, shareModeActive, openSidebar, openLastHandoffPanel, closeSidebar, toggleSidebar, setActiveTab, focusTweak, clearHighlight, registerVariantSet, registerVariantSetPopover, registerVariantSidebarContent, updateVariantSidebarContent, getVariantSidebarContent, registerLegacyVariantSidebarContent, updateLegacyVariantSidebarContent, getLegacyVariantSidebarContent, unregisterVariantSet, setLegacyVariantsAvailable, registerLegacyVariantsPopover, openTweaks, closeTweaks, toggleTweaks, openVariantSet, openLastVariantSet, closeVariants, toggleVariantSet, openVariants, toggleVariants, openSettings, closeSettings, toggleSettings, enterShareMode, exitShareMode, focusShareTarget, viewportLayout, setViewportLayout, toggleViewportLayout, ], ); return ( {children} ); } export function usePrototypeReview(): PrototypeReviewContextValue { const context = useContext(PrototypeReviewContext); if (!context) { throw new Error( "usePrototypeReview must be used within PrototypeReviewProvider", ); } return context; } export function usePrototypeReviewOptional(): PrototypeReviewContextValue | null { return useContext(PrototypeReviewContext); }