{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sidebar",
  "title": "Sidebar",
  "description": "Responsive application navigation with desktop collapse, mobile Sheet, keyboard shortcut and menu primitives.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "@neumorphism-ui/utils",
    "@neumorphism-ui/button",
    "@neumorphism-ui/sheet",
    "@neumorphism-ui/tooltip"
  ],
  "files": [
    {
      "path": "src/components/ui/sidebar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetTitle,\n} from \"@/components/ui/sheet\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nconst SIDEBAR_WIDTH = \"16rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\";\nconst SIDEBAR_WIDTH_ICON = \"3.5rem\";\nconst SIDEBAR_SHORTCUT = \"b\";\n\ntype SidebarContextValue = {\n  state: \"expanded\" | \"collapsed\";\n  open: boolean;\n  setOpen: React.Dispatch<React.SetStateAction<boolean>>;\n  openMobile: boolean;\n  setOpenMobile: React.Dispatch<React.SetStateAction<boolean>>;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextValue | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);\n  const open = openProp ?? uncontrolledOpen;\n\n  const setOpen = React.useCallback<React.Dispatch<React.SetStateAction<boolean>>>(\n    (next) => {\n      const value = typeof next === \"function\" ? next(open) : next;\n      if (onOpenChange) onOpenChange(value);\n      else setUncontrolledOpen(value);\n    },\n    [onOpenChange, open],\n  );\n\n  const toggleSidebar = React.useCallback(() => {\n    if (isMobile) setOpenMobile((value) => !value);\n    else setOpen((value) => !value);\n  }, [isMobile, setOpen]);\n\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key.toLowerCase() === SIDEBAR_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n    window.addEventListener(\"keydown\", handleKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleKeyDown);\n  }, [toggleSidebar]);\n\n  const state: SidebarContextValue[\"state\"] = open ? \"expanded\" : \"collapsed\";\n  const contextValue = React.useMemo(\n    () => ({\n      state,\n      open,\n      setOpen,\n      openMobile,\n      setOpenMobile,\n      isMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, openMobile, isMobile, toggleSidebar],\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <TooltipProvider delay={0}>\n        <div\n          data-slot=\"sidebar-wrapper\"\n          data-state={state}\n          style={\n            {\n              \"--sidebar-width\": SIDEBAR_WIDTH,\n              \"--sidebar-width-mobile\": SIDEBAR_WIDTH_MOBILE,\n              \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n              ...style,\n            } as React.CSSProperties\n          }\n          className={cn(\n            \"flex min-h-0 w-full min-w-0 bg-[var(--background)] text-[var(--foreground)]\",\n            className,\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </TooltipProvider>\n    </SidebarContext.Provider>\n  );\n}\n\ntype SidebarProps = React.ComponentProps<\"aside\"> & {\n  side?: \"left\" | \"right\";\n  variant?: \"sidebar\" | \"floating\" | \"inset\";\n  collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n  mobileTitle?: string;\n  mobileDescription?: string;\n  closeLabel?: string;\n};\n\nfunction Sidebar({\n  side = \"left\",\n  variant = \"sidebar\",\n  collapsible = \"offcanvas\",\n  mobileTitle = \"Sidebar\",\n  mobileDescription = \"Application navigation\",\n  closeLabel = \"Close sidebar\",\n  className,\n  style,\n  children,\n  ...props\n}: SidebarProps) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n  const collapsed = state === \"collapsed\";\n\n  if (isMobile) {\n    return (\n      <Sheet open={openMobile} onOpenChange={setOpenMobile}>\n        <SheetContent\n          data-sidebar=\"sidebar\"\n          data-mobile=\"true\"\n          side={side}\n          closeLabel={closeLabel}\n          className={cn(\n            \"w-[min(var(--sidebar-width-mobile),calc(100vw-1rem))] gap-0 p-0 pt-0\",\n            className,\n          )}\n        >\n          <div className=\"sr-only\">\n            <SheetTitle>{mobileTitle}</SheetTitle>\n            <SheetDescription>{mobileDescription}</SheetDescription>\n          </div>\n          <div className=\"flex h-full min-h-0 flex-col bg-[var(--neu-surface)]\">\n            {children}\n          </div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  const width =\n    collapsible === \"none\" || !collapsed\n      ? \"var(--sidebar-width)\"\n      : collapsible === \"icon\"\n        ? \"var(--sidebar-width-icon)\"\n        : \"0px\";\n\n  const innerClass =\n    variant === \"sidebar\"\n      ? cn(\n          \"border-[color:var(--neu-edge)] bg-[var(--neu-surface)]\",\n          side === \"left\" ? \"border-r\" : \"border-l\",\n        )\n      : \"m-2 rounded-[var(--neu-radius-surface)] border border-[color:var(--neu-edge)] bg-[var(--neu-surface)] [box-shadow:var(--neu-shadow-raised-sm)]\";\n\n  return (\n    <aside\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-side={side}\n      data-variant={variant}\n      data-collapsible={collapsible}\n      style={{ width, ...style }}\n      className={cn(\n        \"group/sidebar hidden min-h-0 shrink-0 overflow-hidden transition-[width,opacity] duration-[var(--neu-duration)] motion-reduce:transition-none md:flex\",\n        collapsed && collapsible === \"offcanvas\" && \"pointer-events-none opacity-0\",\n        className,\n      )}\n      {...props}\n    >\n      <div\n        data-slot=\"sidebar-inner\"\n        className={cn(\n          \"flex h-full min-h-0 shrink-0 flex-col overflow-hidden text-[var(--foreground)]\",\n          collapsed && collapsible === \"icon\"\n            ? \"w-[var(--sidebar-width-icon)]\"\n            : \"w-[var(--sidebar-width)]\",\n          innerClass,\n        )}\n      >\n        {children}\n      </div>\n    </aside>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  label = \"Toggle sidebar\",\n  ...props\n}: React.ComponentProps<typeof Button> & { label?: string }) {\n  const { isMobile, open, openMobile, toggleSidebar } = useSidebar();\n  return (\n    <Button\n      data-slot=\"sidebar-trigger\"\n      variant=\"ghost\"\n      size=\"icon\"\n      aria-label={label}\n      aria-expanded={isMobile ? openMobile : open}\n      className={cn(\"size-9\", className)}\n      onClick={(event) => {\n        props.onClick?.(event);\n        if (!event.defaultPrevented) toggleSidebar();\n      }}\n      {...props}\n    >\n      <span aria-hidden=\"true\">☰</span>\n    </Button>\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<\"main\">) {\n  return (\n    <main\n      data-slot=\"sidebar-inset\"\n      className={cn(\"flex min-h-0 min-w-0 flex-1 flex-col\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-header\"\n      className={cn(\n        \"flex min-h-14 shrink-0 items-center gap-2 overflow-hidden border-b border-[var(--border)] px-3 py-2\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-footer\"\n      className={cn(\n        \"mt-auto flex shrink-0 flex-col gap-2 border-t border-[var(--border)] p-3\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-content\"\n      className={cn(\"flex min-h-0 flex-1 flex-col overflow-y-auto p-2\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<\"section\">) {\n  return (\n    <section\n      data-slot=\"sidebar-group\"\n      className={cn(\"grid min-w-0 gap-2 py-2\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({ className, ...props }: React.ComponentProps<\"h3\">) {\n  const { state } = useSidebar();\n  return (\n    <h3\n      data-slot=\"sidebar-group-label\"\n      className={cn(\n        \"px-2 text-xs font-semibold uppercase tracking-[0.14em] text-[var(--muted-foreground)]\",\n        state === \"collapsed\" && \"sr-only\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-group-content\"\n      className={cn(\"min-w-0\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu\"\n      className={cn(\"grid min-w-0 gap-1\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-item\"\n      className={cn(\"relative min-w-0\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction menuClassName(\n  collapsed: boolean,\n  isActive: boolean,\n  className?: string,\n) {\n  return cn(\n    \"flex h-9 w-full min-w-0 items-center gap-2 overflow-hidden rounded-[var(--neu-radius-control)] px-3 text-left text-sm font-medium text-[var(--foreground)] outline-none transition-[background-color,box-shadow,color] duration-[var(--neu-duration)] motion-reduce:transition-none hover:bg-[var(--neu-surface-soft)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] disabled:pointer-events-none disabled:opacity-50\",\n    isActive &&\n      \"bg-[var(--neu-surface)] text-[var(--neu-accent-ink)] [box-shadow:var(--neu-shadow-inset-sm)]\",\n    collapsed &&\n      \"justify-center px-2 [&>span:last-child]:sr-only\",\n    className,\n  );\n}\n\nfunction SidebarMenuButton({\n  className,\n  isActive = false,\n  tooltip,\n  children,\n  \"aria-label\": ariaLabel,\n  ...props\n}: React.ComponentProps<\"button\"> & {\n  isActive?: boolean;\n  tooltip?: string;\n}) {\n  const { state, isMobile } = useSidebar();\n  const collapsed = state === \"collapsed\" && !isMobile;\n  const element = (\n    <button\n      type=\"button\"\n      data-slot=\"sidebar-menu-button\"\n      data-active={isActive}\n      aria-label={ariaLabel ?? (collapsed ? tooltip : undefined)}\n      className={menuClassName(collapsed, isActive, className)}\n      {...props}\n    >\n      {children}\n    </button>\n  );\n\n  if (!tooltip || !collapsed) return element;\n  return (\n    <Tooltip>\n      <TooltipTrigger render={element} />\n      <TooltipContent side=\"right\">{tooltip}</TooltipContent>\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuLink({\n  className,\n  isActive = false,\n  tooltip,\n  children,\n  \"aria-label\": ariaLabel,\n  ...props\n}: React.ComponentProps<\"a\"> & {\n  isActive?: boolean;\n  tooltip?: string;\n}) {\n  const { state, isMobile } = useSidebar();\n  const collapsed = state === \"collapsed\" && !isMobile;\n  const element = (\n    <a\n      data-slot=\"sidebar-menu-link\"\n      data-active={isActive}\n      aria-current={isActive ? \"page\" : props[\"aria-current\"]}\n      aria-label={ariaLabel ?? (collapsed ? tooltip : undefined)}\n      className={menuClassName(collapsed, isActive, className)}\n      {...props}\n    >\n      {children}\n    </a>\n  );\n\n  if (!tooltip || !collapsed) return element;\n  return (\n    <Tooltip>\n      <TooltipTrigger render={element} />\n      <TooltipContent side=\"right\">{tooltip}</TooltipContent>\n    </Tooltip>\n  );\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuLink,\n  SidebarProvider,\n  SidebarTrigger,\n  useSidebar,\n};\n",
      "type": "registry:ui"
    },
    {
      "path": "src/hooks/use-mobile.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState(false);\n\n  React.useEffect(() => {\n    const media = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const sync = () => setIsMobile(media.matches);\n    sync();\n    media.addEventListener(\"change\", sync);\n    return () => media.removeEventListener(\"change\", sync);\n  }, []);\n\n  return isMobile;\n}\n",
      "type": "registry:hook",
      "target": "@hooks/use-mobile.ts"
    }
  ],
  "categories": [
    "navigation-disclosure"
  ],
  "type": "registry:ui"
}