import { useState, useCallback, type ReactNode } from "react"; import { Button } from "@/src/components/ui/button"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { SubHeader } from "@/src/components/layouts/header"; import { cn } from "@/src/utils/tailwind"; import React from "react"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, } from "@/src/components/ui/sheet"; import { useIsMobile } from "@/src/hooks/use-mobile"; import { Separator } from "@/src/components/ui/separator"; import useSessionStorage from "@/src/components/useSessionStorage"; const SidePanelContext = React.createContext<{ showPanel: boolean; setShowPanel: (value: boolean) => void; isMobile: boolean; isControlled: boolean; } | null>(null); /** * Side panel component with controlled/uncontrolled modes. * * **Uncontrolled mode** (default): * - Omit `openState` prop * - Panel state persisted in session storage * - User can toggle via chevron button * - Example: `{content}` * * **Controlled mode**: * - Provide `openState` with `open` and `onOpenChange` * - No session storage, no toggle button * - Parent controls open/close state * - Example: `{content}` */ const SidePanel = ({ id, children, className, mobileTitle, scrollable = true, openState, }: { id: string; children: ReactNode; className?: string; mobileTitle?: string; scrollable?: boolean; /** Controlled mode: provide to control panel state externally */ openState?: { open: boolean; onOpenChange: (open: boolean) => void; }; }) => { const isControlled = openState !== undefined; const controlledOpen = openState?.open; const onOpenChange = openState?.onOpenChange; const [uncontrolledOpen, setUncontrolledOpen] = useSessionStorage( `${id}-showPanel`, true, ); const [mobileSheetOpen, setMobileSheetOpen] = useState(false); const isMobile = useIsMobile(); const showPanel = isControlled ? (controlledOpen ?? false) : uncontrolledOpen; const setShowPanel = useCallback( (value: boolean) => { if (isControlled) { onOpenChange?.(value); } else { setUncontrolledOpen(value); } }, [isControlled, onOpenChange, setUncontrolledOpen], ); const handleMobileOpenChange = useCallback( (open: boolean) => { if (isControlled) { onOpenChange?.(open); } else { setMobileSheetOpen(open); } }, [isControlled, onOpenChange], ); const contextValue = React.useMemo( () => ({ showPanel, setShowPanel, isMobile, isControlled, }), [showPanel, setShowPanel, isMobile, isControlled], ); if (isMobile) { return ( {!isControlled && (
)} {mobileTitle}
{children}
); } return (
{children}
); }; const SidePanelHeader = ({ children }: { children: ReactNode }) => { const context = React.useContext(SidePanelContext); if (!context) return null; const { showPanel, setShowPanel, isControlled } = context; if (isControlled) { if (!showPanel) return null; return (
{children}
); } if (!showPanel) { return ( ); } return (
{children}
); }; const SidePanelTitle = ({ children, className, }: { children: ReactNode; className?: string; }) => ; const SidePanelContent = ({ children, className, }: { children: ReactNode; className?: string; }) => { const context = React.useContext(SidePanelContext); if (!context) return children; const { showPanel } = context; if (!showPanel) return null; return (
{children}
); }; export { SidePanel, SidePanelHeader, SidePanelTitle, SidePanelContent };