{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sidebar",
  "title": "Sidebar",
  "description": "The shadcn sidebar composite on Base UI, not Radix: the rail is the second neutral layer (--sidebar → --surface-sunken), the active item wears the accent WASH plus a saturated leading rule (never orange text), hover lifts to the paper surface, and mobile is a Base UI Dialog drawer. Supports offcanvas and none; icon-collapse and its tooltip are left out on purpose.",
  "dependencies": [
    "@base-ui/react@1.9.0",
    "class-variance-authority@0.7.1",
    "lucide-react@1.33.0"
  ],
  "registryDependencies": [
    "@waimakers/utils",
    "@waimakers/theme",
    "@waimakers/button",
    "@waimakers/separator"
  ],
  "files": [
    {
      "path": "src/components/ui/sidebar.tsx",
      "content": "import { Dialog } from \"@base-ui/react/dialog\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport { PanelLeftIcon } from \"lucide-react\";\nimport {\n  type CSSProperties,\n  type ComponentProps,\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from \"react\";\n\nimport { cn, focusRing } from \"../../lib/utils\";\nimport { Separator } from \"./separator\";\nimport { Button } from \"./button\";\n\nconst SIDEBAR_COOKIE_NAME = \"waimakers_admin_sidebar\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_TITLE = \"Admin sidebar\";\nconst SIDEBAR_WIDTH = \"16rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\nconst MOBILE_BREAKPOINT = 768;\n\ntype SidebarState = \"expanded\" | \"collapsed\";\n\ninterface SidebarContextValue {\n  state: SidebarState;\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n  title: string;\n}\n\nconst SidebarContext = createContext<SidebarContextValue | null>(null);\n\nfunction useSidebar(): SidebarContextValue {\n  const context = useContext(SidebarContext);\n  if (context === null) {\n    throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n  }\n  return context;\n}\n\nfunction useIsMobile(): boolean {\n  const [isMobile, setIsMobile] = useState(false);\n  useEffect(() => {\n    if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") {\n      return;\n    }\n    const query = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const sync = () => setIsMobile(query.matches);\n    sync();\n    query.addEventListener(\"change\", sync);\n    return () => query.removeEventListener(\"change\", sync);\n  }, []);\n\n  return isMobile;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange,\n  cookieName = SIDEBAR_COOKIE_NAME,\n  title = SIDEBAR_TITLE,\n  className,\n  style,\n  children,\n  ...props\n}: ComponentProps<\"div\"> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  cookieName?: string;\n  title?: string;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = useState(false);\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n  const open = openProp ?? uncontrolledOpen;\n\n  const setOpen = useCallback(\n    (value: boolean) => {\n      if (onOpenChange) {\n        onOpenChange(value);\n      } else {\n        setUncontrolledOpen(value);\n      }\n      if (typeof document !== \"undefined\") {\n        document.cookie = `${cookieName}=${value}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n      }\n    },\n    [onOpenChange, cookieName],\n  );\n\n  const toggleSidebar = useCallback(() => {\n    if (isMobile) {\n      setOpenMobile((current) => !current);\n    } else {\n      setOpen(!open);\n    }\n  }, [isMobile, open, setOpen]);\n  useEffect(() => {\n    if (typeof window === \"undefined\") {\n      return;\n    }\n    const onKeyDown = (event: KeyboardEvent) => {\n      if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n    window.addEventListener(\"keydown\", onKeyDown);\n    return () => window.removeEventListener(\"keydown\", onKeyDown);\n  }, [toggleSidebar]);\n\n  const state: SidebarState = open ? \"expanded\" : \"collapsed\";\n\n  const context = useMemo<SidebarContextValue>(\n    () => ({ state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar, title }),\n    [state, open, setOpen, openMobile, isMobile, toggleSidebar, title],\n  );\n  return (\n    <SidebarContext.Provider value={context}>\n      <div\n        data-slot=\"sidebar-wrapper\"\n        style={{ \"--sidebar-width\": SIDEBAR_WIDTH, ...style } as CSSProperties}\n        className={cn(\"group/sidebar-wrapper flex min-h-svh w-full\", className)}\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = \"left\",\n  collapsible = \"offcanvas\",\n  className,\n  children,\n  ...props\n}: ComponentProps<\"div\"> & {\n  side?: \"left\" | \"right\";\n  collapsible?: \"offcanvas\" | \"none\";\n}) {\n  const { isMobile, state, openMobile, setOpenMobile, title } = useSidebar();\n  if (collapsible === \"none\") {\n    return (\n      <div\n        data-slot=\"sidebar\"\n        className={cn(\n          \"flex h-full w-[var(--sidebar-width)] flex-col bg-sidebar text-sidebar-foreground\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n  if (isMobile) {\n    return (\n      <Dialog.Root open={openMobile} onOpenChange={(nextOpen) => setOpenMobile(nextOpen)}>\n        <Dialog.Portal>\n          <Dialog.Backdrop className=\"fixed inset-0 z-(--z-overlay,40) bg-brand-scrim transition-opacity duration-(--dur-base,220ms) ease-brand motion-reduce:transition-none data-ending-style:opacity-0 data-starting-style:opacity-0\" />\n          <Dialog.Popup\n            data-slot=\"sidebar\"\n            data-mobile=\"true\"\n            onClick={(event) => {\n              if (event.target instanceof Element && event.target.closest(\"a[href]\")) {\n                setOpenMobile(false);\n              }\n            }}\n            className={cn(\n              \"fixed inset-y-0 z-(--z-modal,50) flex w-72 flex-col bg-sidebar text-sidebar-foreground shadow-brand-overlay transition-transform duration-(--dur-base,220ms) ease-brand motion-reduce:transition-none\",\n              side === \"left\"\n                ? \"left-0 border-r border-sidebar-border data-ending-style:-translate-x-full data-starting-style:-translate-x-full\"\n                : \"right-0 border-l border-sidebar-border data-ending-style:translate-x-full data-starting-style:translate-x-full\",\n            )}\n          >\n            <Dialog.Title className=\"sr-only\">{title}</Dialog.Title>\n            {children}\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    );\n  }\n  return (\n    <div\n      className=\"group peer hidden text-sidebar-foreground md:block\"\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n      data-side={side}\n    >\n      <div className=\"relative w-[var(--sidebar-width)] bg-transparent transition-[width] duration-(--dur-base,220ms) ease-brand motion-reduce:transition-none group-data-[collapsible=offcanvas]:w-0\" />\n      <div\n        className={cn(\n          \"fixed inset-y-0 z-(--z-sticky,30) hidden h-svh w-[var(--sidebar-width)] transition-[left,right] duration-(--dur-base,220ms) ease-brand motion-reduce:transition-none md:flex\",\n          side === \"left\"\n            ? \"left-0 border-r border-sidebar-border group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]\"\n            : \"right-0 border-l border-sidebar-border group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]\",\n          className,\n        )}\n        {...props}\n      >\n        <div className=\"flex h-full w-full flex-col bg-sidebar\">{children}</div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarInset({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-inset\"\n      className={cn(\"relative flex min-h-svh min-w-0 flex-1 flex-col bg-background\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarTrigger({ className, onClick, ...props }: ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n  return (\n    <Button\n      type=\"button\"\n      data-slot=\"sidebar-trigger\"\n      variant=\"ghost\"\n      size=\"icon\"\n      aria-label=\"Toggle the sidebar\"\n      className={cn(\"shrink-0\", className)}\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      {...props}\n    >\n      <PanelLeftIcon aria-hidden />\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: ComponentProps<\"button\">) {\n  const { toggleSidebar } = useSidebar();\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"sidebar-rail\"\n      aria-label=\"Toggle the sidebar\"\n      tabIndex={-1}\n      title=\"Toggle the sidebar\"\n      onClick={toggleSidebar}\n      className={cn(\n        \"absolute inset-y-0 right-0 z-20 hidden w-3 translate-x-1/2 transition-colors ease-brand motion-reduce:transition-none md:flex\",\n        \"after:absolute after:inset-y-0 after:left-1/2 after:w-px after:bg-transparent hover:after:bg-sidebar-border\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-header\"\n      className={cn(\"flex flex-col gap-2 px-5 py-6\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-content\"\n      className={cn(\n        \"flex min-h-0 flex-1 flex-col gap-2 overflow-auto px-3 py-2 [scrollbar-width:thin]\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-footer\"\n      className={cn(\n        \"flex flex-col gap-1 border-t border-sidebar-border px-5 py-4 text-xs text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({ className, ...props }: ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"sidebar-separator\"\n      className={cn(\"mx-2 my-1 bg-sidebar-border data-[orientation=horizontal]:w-auto\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-group\"\n      className={cn(\"relative flex w-full min-w-0 flex-col\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"sidebar-group-label\"\n      className={cn(\n        \"flex h-8 shrink-0 items-center px-3 text-brand-eyebrow font-semibold tracking-brand-eyebrow text-muted-foreground uppercase\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupContent({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div data-slot=\"sidebar-group-content\" className={cn(\"w-full text-sm\", className)} {...props} />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu\"\n      className={cn(\"flex w-full min-w-0 flex-col gap-0.5\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-item\"\n      className={cn(\"group/menu-item relative\", className)}\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  cn(\n    \"relative flex w-full items-center gap-2.5 overflow-hidden rounded-md px-3 py-2 text-left text-sm transition-colors ease-brand motion-reduce:transition-none disabled:pointer-events-none disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0\",\n    focusRing,\n  ),\n  {\n    variants: {\n      active: {\n        true: \"bg-sidebar-accent font-semibold text-sidebar-accent-foreground before:absolute before:top-1/2 before:left-1 before:h-5 before:w-[3px] before:-translate-y-1/2 before:rounded-full before:bg-primary before:content-['']\",\n        false: \"text-muted-foreground hover:bg-card hover:text-foreground\",\n      },\n      size: {\n        default: \"\",\n        sm: \"py-1.5\",\n        lg: \"min-h-12\",\n      },\n    },\n    defaultVariants: { active: false, size: \"default\" },\n  },\n);\n\nfunction SidebarMenuButton({\n  isActive = false,\n  size,\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<\"button\"> &\n  VariantProps<typeof sidebarMenuButtonVariants> & { isActive?: boolean }) {\n  return useRender({\n    defaultTagName: \"button\",\n    render,\n    props: {\n      type: render === undefined ? \"button\" : undefined,\n      \"data-slot\": \"sidebar-menu-button\",\n      \"data-active\": isActive ? \"true\" : undefined,\n      \"aria-current\": isActive ? \"page\" : undefined,\n      className: cn(sidebarMenuButtonVariants({ active: isActive, size }), className),\n      ...props,\n    },\n  });\n}\n\nfunction SidebarMenuSub({ className, ...props }: ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu-sub\"\n      className={cn(\n        \"ml-5 flex min-w-0 flex-col gap-0.5 border-l border-sidebar-border py-0.5 pl-2\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({ className, ...props }: ComponentProps<\"li\">) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-sub-item\"\n      className={cn(\"group/menu-sub-item relative\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  isActive = false,\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<\"a\"> & { isActive?: boolean }) {\n  return useRender({\n    defaultTagName: \"a\",\n    render,\n    props: {\n      \"data-slot\": \"sidebar-menu-sub-button\",\n      \"data-active\": isActive ? \"true\" : undefined,\n      \"aria-current\": isActive ? \"page\" : undefined,\n      className: cn(sidebarMenuButtonVariants({ active: isActive, size: \"sm\" }), className),\n      ...props,\n    },\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  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  sidebarMenuButtonVariants,\n  useSidebar,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/sidebar.tsx"
    }
  ],
  "type": "registry:ui"
}