{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "sidebar", "title": "Sidebar", "description": "Terminal-style responsive sidebar with desktop collapse modes and mobile fallback through Sheet. Includes provider, trigger, rail, groups and menu composition.", "dependencies": [ "class-variance-authority", "@radix-ui/react-slot" ], "registryDependencies": [ "utils", "button", "input", "sheet", "theme-catppuccin-mocha", "font-jetbrains-mono" ], "files": [ { "path": "registry/ui/sidebar.tsx", "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Sheet, SheetContent, SheetTitle } from \"@/components/ui/sheet\";\nimport { cn } from \"@/lib/utils\";\n\nconst SIDEBAR_WIDTH = \"18rem\";\nconst SIDEBAR_WIDTH_ICON = \"3.5rem\";\nconst MOBILE_BREAKPOINT = 768;\n\ntype SidebarContextValue = {\n\tstate: \"expanded\" | \"collapsed\";\n\topen: boolean;\n\tsetOpen: (open: boolean) => void;\n\topenMobile: boolean;\n\tsetOpenMobile: (open: boolean) => void;\n\tisMobile: boolean;\n\ttoggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext(null);\n\nfunction useSidebar() {\n\tconst context = React.useContext(SidebarContext);\n\tif (!context) {\n\t\tthrow new Error(\"useSidebar must be used within a SidebarProvider.\");\n\t}\n\n\treturn context;\n}\n\nfunction useIsMobile() {\n\tconst [isMobile, setIsMobile] = React.useState(false);\n\n\tReact.useEffect(() => {\n\t\tconst mediaQuery = window.matchMedia(\n\t\t\t`(max-width: ${MOBILE_BREAKPOINT - 1}px)`,\n\t\t);\n\n\t\tconst update = () => setIsMobile(mediaQuery.matches);\n\t\tupdate();\n\n\t\tmediaQuery.addEventListener(\"change\", update);\n\t\treturn () => mediaQuery.removeEventListener(\"change\", update);\n\t}, []);\n\n\treturn isMobile;\n}\n\nfunction SidebarProvider({\n\tdefaultOpen = true,\n\topen: openProp,\n\tonOpenChange,\n\tclassName,\n\tstyle,\n\tchildren,\n\t...props\n}: React.ComponentProps<\"div\"> & {\n\tdefaultOpen?: boolean;\n\topen?: boolean;\n\tonOpenChange?: (open: boolean) => void;\n}) {\n\tconst isMobile = useIsMobile();\n\tconst [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);\n\tconst [openMobile, setOpenMobile] = React.useState(false);\n\tconst open = openProp ?? uncontrolledOpen;\n\n\tconst setOpen = React.useCallback(\n\t\t(nextOpen: boolean) => {\n\t\t\tonOpenChange?.(nextOpen);\n\t\t\tif (openProp === undefined) {\n\t\t\t\tsetUncontrolledOpen(nextOpen);\n\t\t\t}\n\t\t},\n\t\t[onOpenChange, openProp],\n\t);\n\n\tconst toggleSidebar = React.useCallback(() => {\n\t\tif (isMobile) {\n\t\t\tsetOpenMobile((current) => !current);\n\t\t\treturn;\n\t\t}\n\n\t\tsetOpen(!open);\n\t}, [isMobile, open, setOpen]);\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t{children}\n\t\t\t\n\t\t\n\t);\n}\n\nfunction Sidebar({\n\tside = \"left\",\n\tcollapsible = \"icon\",\n\tclassName,\n\tchildren,\n\t...props\n}: React.ComponentProps<\"div\"> & {\n\tside?: \"left\" | \"right\";\n\tcollapsible?: \"offcanvas\" | \"icon\" | \"none\";\n}) {\n\tconst { isMobile, openMobile, setOpenMobile, state } = useSidebar();\n\n\tif (collapsible === \"none\") {\n\t\treturn (\n\t\t\t\n\t\t\t\t{children}\n\t\t\t\n\t\t);\n\t}\n\n\tif (isMobile) {\n\t\treturn (\n\t\t\t\n\t\t\t\t[data-slot=sheet-close]]:hidden\"\n\t\t\t\t>\n\t\t\t\t\tNavigation\n\t\t\t\t\t
\n\t\t\t\t\t\t{children}\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\tconst isCollapsed = state === \"collapsed\";\n\tconst gapClassName =\n\t\tcollapsible === \"offcanvas\"\n\t\t\t? \"w-0 group-data-[state=expanded]/sidebar:w-[var(--sidebar-width)]\"\n\t\t\t: collapsible === \"icon\"\n\t\t\t\t? \"w-[var(--sidebar-width-icon)] group-data-[state=expanded]/sidebar:w-[var(--sidebar-width)]\"\n\t\t\t\t: \"w-[var(--sidebar-width)]\";\n\n\tconst panelClassName =\n\t\tcollapsible === \"offcanvas\"\n\t\t\t? cn(\n\t\t\t\t\t\"w-[var(--sidebar-width)] transition-[transform] duration-150 ease-out\",\n\t\t\t\t\tisCollapsed &&\n\t\t\t\t\t\t(side === \"left\" ? \"-translate-x-full\" : \"translate-x-full\"),\n\t\t\t\t)\n\t\t\t: cn(\n\t\t\t\t\t\"transition-[width] duration-150 ease-out\",\n\t\t\t\t\tisCollapsed && collapsible === \"icon\"\n\t\t\t\t\t\t? \"w-[var(--sidebar-width-icon)]\"\n\t\t\t\t\t\t: \"w-[var(--sidebar-width)]\",\n\t\t\t\t);\n\n\treturn (\n\t\t