import { useCoordinatorWorkspaceEnabled } from "@/hooks/useCoordinatorWorkspaceEnabled"; import { createContext, useCallback, useContext, useLayoutEffect, useState, type ReactNode } from "react"; import type { SidePanelContentMode } from "@/components/side-panel"; const STORAGE_KEY = "paperclip:panel-visible"; interface PanelContextValue { panelContent: ReactNode | null; panelContentMode: SidePanelContentMode; panelVisible: boolean; openPanel: (content: ReactNode, options?: { contentMode?: SidePanelContentMode }) => void; closePanel: () => void; setPanelVisible: (visible: boolean) => void; togglePanelVisible: () => void; /** * One-shot maximize request (LOOA-2181): deep links with `viewer=full` ask * the resizable panel host to open maximized. The request stays pending * until the host consumes it (the panel may not be mounted yet when the * deep link routes), so consumers must clear it after acting. */ panelMaximizeRequested: boolean; requestPanelMaximize: () => void; clearPanelMaximizeRequest: () => void; } const PanelContext = createContext(null); function readPreference(): boolean { try { const raw = localStorage.getItem(STORAGE_KEY); return raw === null ? true : raw === "true"; } catch { return true; } } function writePreference(visible: boolean) { try { localStorage.setItem(STORAGE_KEY, String(visible)); } catch { // Ignore storage failures. } } export function PanelProvider({ children }: { children: ReactNode }) { const [panelContent, setPanelContent] = useState(null); const [panelContentMode, setPanelContentMode] = useState("padded"); const [classicPanelVisible, setPanelVisibleState] = useState(readPreference); const [coordinatorPanelVisible, setCoordinatorPanelVisible] = useState(false); const { enabled: coordinatorWorkspaceEnabled, userId } = useCoordinatorWorkspaceEnabled(); // The experiment has an on-demand panel. Its visibility never overwrites // the classic workspace preference, including when the experiment is off. const panelVisible = coordinatorWorkspaceEnabled ? coordinatorPanelVisible : classicPanelVisible; useLayoutEffect(() => setCoordinatorPanelVisible(false), [coordinatorWorkspaceEnabled, userId]); const [panelMaximizeRequested, setPanelMaximizeRequested] = useState(false); const requestPanelMaximize = useCallback(() => { setPanelMaximizeRequested(true); }, []); const clearPanelMaximizeRequest = useCallback(() => { setPanelMaximizeRequested(false); }, []); const openPanel = useCallback((content: ReactNode, options?: { contentMode?: SidePanelContentMode }) => { setPanelContent(content); setPanelContentMode(options?.contentMode ?? "padded"); }, []); const closePanel = useCallback(() => { setPanelContent(null); setPanelContentMode("padded"); }, []); const setPanelVisible = useCallback((visible: boolean) => { if (coordinatorWorkspaceEnabled) setCoordinatorPanelVisible(visible); else { setPanelVisibleState(visible); writePreference(visible); } }, [coordinatorWorkspaceEnabled]); const togglePanelVisible = useCallback(() => { if (coordinatorWorkspaceEnabled) { setCoordinatorPanelVisible(previous => !previous); return; } setPanelVisibleState((prev) => { const next = !prev; writePreference(next); return next; }); }, [coordinatorWorkspaceEnabled]); return ( {children} ); } export function usePanel() { const ctx = useContext(PanelContext); if (!ctx) { throw new Error("usePanel must be used within PanelProvider"); } return ctx; }