import { createContext, useCallback, useContext, useMemo, useState, type CSSProperties, type ReactNode, } from 'react'; import { isDesktopViewport, useSidePanelMachine } from '../panels/useSidePanelMachine'; const RIGHT_SIDEBAR_WIDTH_KEY = 'authRightSidebarWidth'; const RIGHT_SIDEBAR_DEFAULT_WIDTH = 320; const RIGHT_SIDEBAR_MIN_WIDTH = 260; const RIGHT_SIDEBAR_MAX_WIDTH = 480; export type RightSidebarSizing = { defaultWidth?: number; minWidth?: number; maxWidth?: number; }; export type RightSidebarContent = { title: string; content: ReactNode; ariaLabel?: string; }; export type RightSidebarStyle = CSSProperties & { '--auth-right-sidebar-width': string; }; type RightSidebarContextValue = { isOpen: boolean; content: RightSidebarContent | null; openSidebar: (content?: RightSidebarContent) => void; closeSidebar: () => void; toggleSidebar: (content?: RightSidebarContent) => void; setSidebarContent: (content: RightSidebarContent | null) => void; desktopSidebarStyle: RightSidebarStyle; startResize: ReturnType['startResize']; }; type RightSidebarProviderProps = { children: ReactNode; closeOnPathname?: string; onMobileOpenRequest?: () => void; sizing?: RightSidebarSizing; }; const RightSidebarContext = createContext(undefined); function readSizingValue(value: number | undefined, fallback: number): number { if (!Number.isFinite(value) || value == null || value <= 0) { return fallback; } return value; } function resolveRightSidebarSizing(sizing: RightSidebarSizing | undefined) { const requestedMinWidth = readSizingValue(sizing?.minWidth, RIGHT_SIDEBAR_MIN_WIDTH); const requestedMaxWidth = readSizingValue(sizing?.maxWidth, RIGHT_SIDEBAR_MAX_WIDTH); const minWidth = Math.min(requestedMinWidth, requestedMaxWidth); const maxWidth = Math.max(requestedMinWidth, requestedMaxWidth); const requestedDefaultWidth = readSizingValue( sizing?.defaultWidth, RIGHT_SIDEBAR_DEFAULT_WIDTH, ); const defaultWidth = Math.min(maxWidth, Math.max(minWidth, requestedDefaultWidth)); return { defaultWidth, minWidth, maxWidth, }; } export function RightSidebarProvider({ children, closeOnPathname, onMobileOpenRequest, sizing, }: Readonly) { const { defaultWidth, minWidth, maxWidth } = resolveRightSidebarSizing(sizing); const [isOpen, setIsOpen] = useState(false); const [content, setContent] = useState(null); const closeSidebar = useCallback(() => { setIsOpen(false); setContent(null); }, []); const setSidebarContent = useCallback((nextContent: RightSidebarContent | null) => { setContent(nextContent); }, []); const openSidebar = useCallback( (nextContent?: RightSidebarContent) => { const resolvedContent = nextContent ?? content; if (!resolvedContent) { return; } if (nextContent) { setContent(nextContent); } if (!isDesktopViewport()) { onMobileOpenRequest?.(); } setIsOpen(true); }, [content, onMobileOpenRequest], ); const toggleSidebar = useCallback( (nextContent?: RightSidebarContent) => { if (isOpen) { closeSidebar(); return; } openSidebar(nextContent); }, [isOpen, closeSidebar, openSidebar], ); const { width, startResize } = useSidePanelMachine({ storageKey: RIGHT_SIDEBAR_WIDTH_KEY, defaultWidth, minWidth, maxWidth, resizeAxis: 'from-right', resizingBodyClass: 'auth-right-sidebar-resizing', isOpen, canResize: isOpen, shouldPersistWidth: true, closeOnPathname, onCloseOnPathname: closeSidebar, onEscape: closeSidebar, }); const desktopSidebarStyle = useMemo( () => ({ '--auth-right-sidebar-width': `${width}px`, }), [width], ); const value = useMemo( () => ({ isOpen, content, openSidebar, closeSidebar, toggleSidebar, setSidebarContent, desktopSidebarStyle, startResize, }), [ isOpen, content, openSidebar, closeSidebar, toggleSidebar, setSidebarContent, desktopSidebarStyle, startResize, ], ); return {children}; } export function useRightSidebar() { const ctx = useContext(RightSidebarContext); if (!ctx) { throw new Error('useRightSidebar must be used within RightSidebarProvider'); } return ctx; }