"use client" import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { type VariantProps, cva } from "class-variance-authority" import { PanelLeft } from "lucide-react" import { useIsMobile } from "@/hooks/use-mobile" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { Sheet, SheetContent } from "@/components/ui/sheet" const SIDEBAR_COOKIE_NAME = "sidebar:state" const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 const SIDEBAR_WIDTH = "16rem" const SIDEBAR_WIDTH_MOBILE = "18rem" const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_KEYBOARD_SHORTCUT = "b" type SidebarContext = { 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"> & { variant?: "sidebar" | "floating" | "inset" collapsible?: "offcanvas" | "icon" | "none" } >(({ variant = "sidebar", collapsible = "offcanvas", className, children, ...props }, ref) => { const { isMobile, state, openMobile, setOpenMobile } = useSidebar() if (collapsible === "none") { return (
{children}
) } if (isMobile) { return (
{children}
) } return (
) }) Sidebar.displayName = "Sidebar" const SidebarTrigger = React.forwardRef, React.ComponentProps>( ({ className, onClick, ...props }, ref) => { const { toggleSidebar } = useSidebar() return ( ) }, ) SidebarTrigger.displayName = "SidebarTrigger" const SidebarInset = React.forwardRef>(({ className, ...props }, ref) => { return (
) }) SidebarInset.displayName = "SidebarInset" const SidebarInput = React.forwardRef, React.ComponentProps>( ({ className, ...props }, ref) => { return ( ) }, ) SidebarInput.displayName = "SidebarInput" const SidebarHeader = React.forwardRef>(({ className, ...props }, ref) => { return
}) SidebarHeader.displayName = "SidebarHeader" const SidebarFooter = React.forwardRef>(({ className, ...props }, ref) => { return
}) SidebarFooter.displayName = "SidebarFooter" const SidebarSeparator = React.forwardRef, React.ComponentProps>( ({ className, ...props }, ref) => { return }, ) SidebarSeparator.displayName = "SidebarSeparator" const SidebarContent = React.forwardRef>(({ className, ...props }, ref) => { return (
) }) SidebarContent.displayName = "SidebarContent" const SidebarGroup = React.forwardRef>(({ className, ...props }, ref) => { return (
) }) SidebarGroup.displayName = "SidebarGroup" const SidebarGroupLabel = React.forwardRef>( ({ className, ...props }, ref) => { return (
svg]:size-4 [&>svg]:shrink-0", "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", className, )} style={{ color: "var(--text-primary)", opacity: 0.7, }} {...props} /> ) }, ) SidebarGroupLabel.displayName = "SidebarGroupLabel" const SidebarGroupContent = React.forwardRef>( ({ className, ...props }, ref) => { return
}, ) SidebarGroupContent.displayName = "SidebarGroupContent" const SidebarMenu = React.forwardRef>(({ className, ...props }, ref) => { return
    }) SidebarMenu.displayName = "SidebarMenu" const SidebarMenuItem = React.forwardRef>(({ className, ...props }, ref) => { return
  • }) SidebarMenuItem.displayName = "SidebarMenuItem" const sidebarMenuButtonVariants = cva( "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none transition-[width,height,padding] disabled:pointer-events-none disabled:opacity-50 group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", { variants: { variant: { default: "", outline: "shadow-[0_0_0_1px_hsl(var(--border))]", }, size: { default: "h-8 text-sm", sm: "h-7 text-xs", lg: "h-12 text-sm group-data-[collapsible=icon]:!size-8", }, }, defaultVariants: { variant: "default", size: "default", }, }, ) const SidebarMenuButton = React.forwardRef< HTMLButtonElement, React.ComponentProps<"button"> & { asChild?: boolean isActive?: boolean tooltip?: string } & VariantProps >(({ asChild = false, isActive = false, variant = "default", size = "default", tooltip, className, ...props }, ref) => { const Comp = asChild ? Slot : "button" return ( ) }) SidebarMenuButton.displayName = "SidebarMenuButton" const SidebarMenuSub = React.forwardRef>( ({ className, ...props }, ref) => { return (
      ) }, ) SidebarMenuSub.displayName = "SidebarMenuSub" const SidebarMenuSubItem = React.forwardRef>(({ ...props }, ref) => { return
    • }) SidebarMenuSubItem.displayName = "SidebarMenuSubItem" const SidebarMenuSubButton = React.forwardRef< HTMLAnchorElement, React.ComponentProps<"a"> & { asChild?: boolean size?: "sm" | "md" isActive?: boolean } >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => { const Comp = asChild ? Slot : "a" return ( span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", size === "sm" && "text-xs", size === "md" && "text-sm", "group-data-[collapsible=icon]:hidden", className, )} style={{ backgroundColor: isActive ? "var(--background-primary)" : undefined, color: "var(--text-primary)", }} onMouseEnter={(e) => { if (!isActive) { e.currentTarget.style.backgroundColor = "var(--background-primary)" } }} onMouseLeave={(e) => { if (!isActive) { e.currentTarget.style.backgroundColor = "" } }} {...props} /> ) }) SidebarMenuSubButton.displayName = "SidebarMenuSubButton" export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarSeparator, SidebarTrigger, useSidebar, }