"use client" import { cva, VariantProps } from 'class-variance-authority'; import { Menu, PanelLeft } from 'lucide-react'; import * as React from 'react'; import { cn } from '../../../lib'; import { useIsMobile } from '../../../hooks/media'; import { useShortcutModLabel } from '../../../hooks/device'; import { Slot } from '@radix-ui/react-slot'; import { Button } from '../../forms/button'; import { Input } from '../../forms/input'; import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle } from '../../overlay/drawer'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../overlay/tooltip'; import { Separator } from '../../layout/separator'; import { Skeleton } from '../../layout/skeleton'; import { Link } from '../link'; const SIDEBAR_COOKIE_NAME = "sidebar_state" const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 const SIDEBAR_WIDTH = "16rem" const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_KEYBOARD_SHORTCUT = "b" type SidebarContextProps = { state: "expanded" | "collapsed" open: boolean setOpen: (open: boolean) => void openMobile: boolean setOpenMobile: (open: boolean) => void isMobile: boolean toggleSidebar: () => void } const SidebarContext = React.createContext(null) function useSidebar() { const context = React.useContext(SidebarContext) if (!context) { throw new Error("useSidebar must be used within a SidebarProvider.") } return context } const SidebarProvider = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { defaultOpen?: boolean open?: boolean onOpenChange?: (open: boolean) => void } >( ( { defaultOpen = true, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }, ref ) => { const isMobile = useIsMobile() const [openMobile, setOpenMobile] = React.useState(false) const [_open, _setOpen] = React.useState(defaultOpen) const open = openProp ?? _open const setOpen = React.useCallback( (value: boolean | ((value: boolean) => boolean)) => { const openState = typeof value === "function" ? value(open) : value if (setOpenProp) { setOpenProp(openState) } else { _setOpen(openState) } document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` }, [setOpenProp, open] ) const toggleSidebar = React.useCallback(() => { return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open) }, [isMobile, setOpen, setOpenMobile]) React.useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if ( event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey) ) { event.preventDefault() toggleSidebar() } } window.addEventListener("keydown", handleKeyDown) return () => window.removeEventListener("keydown", handleKeyDown) }, [toggleSidebar]) const state = open ? "expanded" : "collapsed" const contextValue = React.useMemo( () => ({ state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar, }), [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] ) return (
{children}
) } ) SidebarProvider.displayName = "SidebarProvider" const Sidebar = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { side?: "left" | "right" variant?: "sidebar" | "floating" | "inset" collapsible?: "offcanvas" | "icon" | "none" } >( ( { side = "left", variant = "sidebar", collapsible = "offcanvas", className, children, ...props }, ref ) => { const { isMobile, state, openMobile, setOpenMobile } = useSidebar() if (collapsible === "none") { return (
{children}
) } if (isMobile) { const drawerSide = side === "right" ? "right" : "left" return (
Sidebar Mobile navigation sidebar.
{children}
) } const getGapWidth = () => { if (state === "collapsed") { if (collapsible === "offcanvas") return "0" if (collapsible === "icon") { return variant === "floating" || variant === "inset" ? "calc(var(--sidebar-width-icon) + 1rem)" : "var(--sidebar-width-icon)" } } return "var(--sidebar-width)" } const getFixedWidth = () => { if (state === "collapsed" && collapsible === "icon") { return variant === "floating" || variant === "inset" ? "calc(var(--sidebar-width-icon) + 1rem + 2px)" : "var(--sidebar-width-icon)" } return "var(--sidebar-width)" } return (
) } ) Sidebar.displayName = "Sidebar" const SidebarTrigger = React.forwardRef< React.ElementRef, React.ComponentProps >(({ className, onClick, ...props }, ref) => { const { toggleSidebar, openMobile } = useSidebar() const mod = useShortcutModLabel() const isMobile = useIsMobile() const desktopHint = `Toggle sidebar (${mod}+B)` const handleClick = (event: React.MouseEvent) => { onClick?.(event) toggleSidebar() } const buttonClass = cn("h-7 w-7", className) if (isMobile) { return ( ) } return ( {desktopHint} ) }) SidebarTrigger.displayName = "SidebarTrigger" const SidebarRail = React.forwardRef< HTMLButtonElement, React.ComponentProps<"button"> >(({ className, ...props }, ref) => { const { toggleSidebar } = useSidebar() return (