{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sidebar",
  "title": "sidebar",
  "description": "shadcn's Sidebar family for an app shell: SidebarProvider, Sidebar (variant sidebar, floating or inset; collapsible offcanvas, icon or none; side left or right; position fixed or contained), its header, content, groups, menu rows with badges, actions, sub-menus and skeletons, SidebarTrigger, SidebarRail, SidebarInset and useSidebar. Below the md breakpoint it opens as a Sheet.",
  "dependencies": [
    "@radix-ui/react-slot@^1",
    "class-variance-authority@^0.7"
  ],
  "registryDependencies": [
    "@dwellir/dwellir-theme",
    "@dwellir/utils",
    "@dwellir/button",
    "@dwellir/input",
    "@dwellir/separator",
    "@dwellir/sheet",
    "@dwellir/skeleton",
    "@dwellir/tooltip",
    "@dwellir/rune-icons"
  ],
  "files": [
    {
      "path": "components/ui/sidebar.tsx",
      "content": "'use client';\n\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { type ComponentProps, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { RuneSidebar } from '@/components/brand/rune-icons';\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\nimport { Separator } from '@/components/ui/separator';\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from '@/components/ui/sheet';\nimport { useIsMobile } from '@/hooks/use-mobile';\nimport { SidebarContext, type SidebarContextProps, useSidebar } from '@/hooks/use-sidebar';\nimport { cn } from '@/lib/utils';\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\n/*\n * Holds the open state for a Sidebar and the content beside it. The sidebar\n * widths are custom properties set by the sidebar-wrapper utility; a caller\n * may set --sidebar-width on it. Cmd/Ctrl+B toggles the sidebar, and the\n * desktop state is kept in the sidebar_state cookie.\n */\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  children,\n  ...props\n}: ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = useState(false);\n  const [ownOpen, setOwnOpen] = useState(defaultOpen);\n  const open = openProp ?? ownOpen;\n  const setOpen = useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const next = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) setOpenProp(next);\n      else setOwnOpen(next);\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${next}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open],\n  );\n  const toggleSidebar = useCallback(\n    () => (isMobile ? setOpenMobile((value) => !value) : setOpen((value) => !value)),\n    [isMobile, setOpen],\n  );\n\n  useEffect(() => {\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 = open ? 'expanded' : 'collapsed';\n  const context = useMemo<SidebarContextProps>(\n    () => ({ state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar }),\n    [state, open, setOpen, isMobile, openMobile, toggleSidebar],\n  );\n\n  return (\n    <SidebarContext.Provider value={context}>\n      <div\n        data-slot=\"sidebar-wrapper\"\n        className={cn(\n          'sidebar-wrapper group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\n/* The space the sidebar keeps in the page's flow. It resizes at once: one page layout a toggle. */\nconst sidebarGapVariants = cva(\n  'relative w-(--sidebar-width) bg-transparent group-data-[collapsible=offcanvas]/sidebar:w-0 group-data-[side=right]/sidebar:rotate-180',\n  {\n    variants: {\n      variant: {\n        sidebar: 'group-data-[collapsible=icon]/sidebar:w-(--sidebar-width-icon)',\n        floating: 'group-data-[collapsible=icon]/sidebar:w-(--sidebar-width-icon-gap)',\n        inset: 'group-data-[collapsible=icon]/sidebar:w-(--sidebar-width-icon-gap)',\n      },\n    },\n    defaultVariants: { variant: 'sidebar' },\n  },\n);\n\n/* sidebar-motion slides (offcanvas) or clips (icon) the container: no layout per frame. */\nconst sidebarContainerVariants = cva(\n  'sidebar-motion inset-y-0 z-10 hidden w-(--sidebar-width) md:flex',\n  {\n    variants: {\n      side: {\n        left: 'left-0 group-data-[collapsible=offcanvas]/sidebar:-translate-x-full',\n        right: 'right-0 group-data-[collapsible=offcanvas]/sidebar:translate-x-full',\n      },\n      collapsible: { offcanvas: '', icon: 'sidebar-icon-clip', none: '' },\n      /* sidebar: flush with the screen edge; floating: a panel with a gap; inset: the content is the panel. */\n      variant: {\n        sidebar:\n          'border-sidebar-border group-data-[collapsible=icon]/sidebar:w-(--sidebar-width-icon) group-data-[side=left]/sidebar:border-r group-data-[side=right]/sidebar:border-l',\n        floating: 'p-2 group-data-[collapsible=icon]/sidebar:w-(--sidebar-width-icon-framed)',\n        inset: 'p-2 group-data-[collapsible=icon]/sidebar:w-(--sidebar-width-icon-framed)',\n      },\n      /*\n       * fixed: the app shell, the height of the screen as it is now (dvh: it follows a\n       * phone's URL bar), clear of the status bar, the notch and the home indicator;\n       * contained: inside a positioned frame, such as a preview.\n       */\n      position: {\n        fixed: 'fixed h-dvh sidebar-safe-area',\n        contained: 'absolute h-full',\n      },\n    },\n    defaultVariants: {\n      side: 'left',\n      variant: 'sidebar',\n      position: 'fixed',\n      collapsible: 'offcanvas',\n    },\n  },\n);\n\ntype SidebarProps = ComponentProps<'div'> &\n  VariantProps<typeof sidebarContainerVariants> & {\n    /** offcanvas slides it off the screen, icon keeps a rail of icons, none keeps it open. */\n    collapsible?: 'offcanvas' | 'icon' | 'none';\n    /** The phone sheet's surface: grain, as on the website, or plain (the card fill) for app screens. */\n    mobileSurface?: ComponentProps<typeof SheetContent>['surface'];\n  };\n\n/*\n * The sidebar below the md breakpoint. SidebarTrigger is no SheetTrigger, so\n * Radix has no trigger to refocus: the sheet keeps what had focus when it\n * opened and returns focus there when it closes.\n */\nfunction SidebarSheet({\n  side,\n  surface,\n  children,\n  ...props\n}: ComponentProps<'div'> & {\n  side: 'left' | 'right';\n  surface: ComponentProps<typeof SheetContent>['surface'];\n}) {\n  const { openMobile, setOpenMobile } = useSidebar();\n  const opener = useRef<HTMLElement | null>(null);\n  return (\n    <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n      <SheetContent\n        data-sidebar=\"sidebar\"\n        data-slot=\"sidebar\"\n        data-mobile=\"true\"\n        side={side}\n        surface={surface}\n        showCloseButton={false}\n        className=\"max-w-72 gap-0 p-0 text-sidebar-foreground\"\n        onOpenAutoFocus={() => {\n          const active = document.activeElement;\n          opener.current =\n            active instanceof HTMLElement && active !== document.body ? active : null;\n        }}\n        onCloseAutoFocus={(event) => {\n          if (!opener.current?.isConnected) return;\n          event.preventDefault();\n          opener.current.focus();\n        }}\n      >\n        <SheetHeader className=\"sr-only\">\n          <SheetTitle>Navigation</SheetTitle>\n          <SheetDescription>The app’s main navigation.</SheetDescription>\n        </SheetHeader>\n        <div data-slot=\"sidebar-inner\" className=\"flex h-full w-full flex-col\">\n          {children}\n        </div>\n      </SheetContent>\n    </Sheet>\n  );\n}\n\n/*\n * The app shell's navigation. On a desktop it collapses to icons or off the\n * screen; below the md breakpoint it opens as a Sheet from SidebarTrigger.\n */\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  position = 'fixed',\n  mobileSurface,\n  className,\n  children,\n  ...props\n}: SidebarProps) {\n  const { isMobile, state } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        data-slot=\"sidebar\"\n        className={cn(\n          'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <SidebarSheet side={side ?? 'left'} surface={mobileSurface} {...props}>\n        {children}\n      </SidebarSheet>\n    );\n  }\n\n  return (\n    <div\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-variant={variant}\n      data-side={side}\n      className=\"group/sidebar peer hidden text-sidebar-foreground md:block\"\n    >\n      <div data-slot=\"sidebar-gap\" className={sidebarGapVariants({ variant })} />\n      <div\n        data-slot=\"sidebar-container\"\n        className={cn(\n          sidebarContainerVariants({ side, variant, position, collapsible }),\n          className,\n        )}\n        {...props}\n      >\n        <div\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n          className=\"flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]/sidebar:rounded-2xl group-data-[variant=floating]/sidebar:border group-data-[variant=floating]/sidebar:border-sidebar-border group-data-[variant=floating]/sidebar:shadow-xs\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\n/* A ghost icon button that opens or closes the sidebar. */\nfunction SidebarTrigger({ className, onClick, ...props }: ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n  return (\n    <Button\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      variant=\"ghost\"\n      size=\"icon-sm\"\n      shape=\"rounded\"\n      className={className}\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      {...props}\n    >\n      <RuneSidebar size={24} data-slot=\"sidebar-trigger-icon\" aria-hidden=\"true\" />\n      <span data-slot=\"sidebar-trigger-label\" className=\"sr-only\">\n        Toggle sidebar\n      </span>\n    </Button>\n  );\n}\n\n/* A strip on the sidebar's edge that toggles it on click; a line shows on hover. Mouse only: the trigger serves the keyboard. */\nfunction SidebarRail({ className, ...props }: ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n  return (\n    <button\n      type=\"button\"\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      aria-label=\"Toggle sidebar\"\n      tabIndex={-1}\n      onClick={toggleSidebar}\n      title=\"Toggle sidebar\"\n      className={cn(\n        'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-[translate,background-color] ease-linear motion-reduce:transition-none group-data-[side=left]/sidebar:-right-4 group-data-[side=right]/sidebar:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-0.5 hover:after:bg-sidebar-border sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'group-data-[collapsible=offcanvas]/sidebar:translate-x-0 group-data-[collapsible=offcanvas]/sidebar:after:left-full hover:group-data-[collapsible=offcanvas]/sidebar:bg-sidebar',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/* The page beside the sidebar. With variant=\"inset\" it is a raised panel on the sidebar's ground. */\nfunction SidebarInset({\n  className,\n  asChild = false,\n  ...props\n}: ComponentProps<'main'> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot : 'main';\n  return (\n    <Comp\n      data-slot=\"sidebar-inset\"\n      className={cn(\n        'relative flex w-full min-w-0 flex-1 flex-col bg-background',\n        'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-2xl md:peer-data-[variant=inset]:border md:peer-data-[variant=inset]:border-sidebar-border md:peer-data-[variant=inset]:shadow-xs md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/* A search or filter field in the sidebar's header. */\nfunction SidebarInput({ className, ...props }: ComponentProps<typeof Input>) {\n  return (\n    <Input\n      data-slot=\"sidebar-input\"\n      data-sidebar=\"input\"\n      className={cn('w-full', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-header\"\n      data-sidebar=\"header\"\n      className={cn('flex flex-col gap-2 p-2', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-footer\"\n      data-sidebar=\"footer\"\n      className={cn('flex flex-col gap-2 p-2', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({ className, ...props }: ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      data-slot=\"sidebar-separator\"\n      data-sidebar=\"separator\"\n      className={cn('mx-2 w-auto bg-sidebar-border', className)}\n      {...props}\n    />\n  );\n}\n\n/* The scrolling middle of the sidebar, between header and footer. */\nfunction SidebarContent({ className, ...props }: ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-content\"\n      data-sidebar=\"content\"\n      className={cn(\n        'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]/sidebar:overflow-hidden',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-group\"\n      data-sidebar=\"group\"\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      {...props}\n    />\n  );\n}\n\n/*\n * A group's quiet label: small, medium weight and muted, in sentence case.\n * It fades out when the sidebar collapses to icons.\n */\nfunction SidebarGroupLabel({\n  className,\n  asChild = false,\n  ...props\n}: ComponentProps<'div'> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot : 'div';\n  return (\n    <Comp\n      data-slot=\"sidebar-group-label\"\n      data-sidebar=\"group-label\"\n      className={cn(\n        'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-muted-foreground outline-none transition-opacity duration-200 ease-linear focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-sidebar-ring [&>svg]:size-4 [&>svg]:shrink-0',\n        'group-data-[collapsible=icon]/sidebar:-mt-8 group-data-[collapsible=icon]/sidebar:opacity-0',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/* An icon button at the end of a group's label, such as Add. */\nfunction SidebarGroupAction({\n  className,\n  asChild = false,\n  ...props\n}: ComponentProps<'button'> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot : 'button';\n  return (\n    <Comp\n      data-slot=\"sidebar-group-action\"\n      data-sidebar=\"group-action\"\n      className={cn(\n        'sidebar-action top-3.5 right-3 w-5 group-data-[collapsible=icon]/sidebar:hidden',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupContent({ className, ...props }: ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-group-content\"\n      data-sidebar=\"group-content\"\n      className={cn('w-full text-sm', className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  sidebarMenuButtonVariants,\n} from '@/components/ui/sidebar-menu';\nexport { useSidebar } from '@/hooks/use-sidebar';\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  sidebarContainerVariants,\n};\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/sidebar-menu.tsx",
      "content": "'use client';\n\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { type ComponentProps, useState } from 'react';\nimport { Skeleton } from '@/components/ui/skeleton';\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';\nimport { useSidebar } from '@/hooks/use-sidebar';\nimport { cn } from '@/lib/utils';\n\n/* The menu parts of the sidebar. Import them from @/components/ui/sidebar. */\n\nfunction SidebarMenu({ className, ...props }: ComponentProps<'ul'>) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu\"\n      data-sidebar=\"menu\"\n      className={cn('flex w-full min-w-0 flex-col gap-1', className)}\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: ComponentProps<'li'>) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-item\"\n      data-sidebar=\"menu-item\"\n      className={cn('group/menu-item relative', className)}\n      {...props}\n    />\n  );\n}\n\n/*\n * A row of the menu: an icon and a label. Hover takes the muted fill; the\n * current page (isActive) takes the accent tint and bold text. Collapsed to\n * icons, the row is a square and its tooltip names it.\n */\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button sidebar-menu-button w-full min-w-0 p-2 group-data-[collapsible=icon]/sidebar:size-8! group-data-[collapsible=icon]/sidebar:p-2!',\n  {\n    variants: {\n      variant: {\n        default: '',\n        /* An edged row on the page colour, for a sidebar on a busy ground. */\n        outline: 'bg-background ring-1 ring-sidebar-border',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        /* Two lines, such as a workspace or account switcher. */\n        lg: 'h-12 text-sm group-data-[collapsible=icon]/sidebar:p-0!',\n      },\n    },\n    defaultVariants: { variant: 'default', size: 'default' },\n  },\n);\n\nfunction SidebarMenuButton({\n  asChild = false,\n  isActive = false,\n  variant,\n  size,\n  tooltip,\n  className,\n  ...props\n}: ComponentProps<'button'> & {\n  asChild?: boolean;\n  /** The current page. Pair it with aria-current=\"page\" on a link. */\n  isActive?: boolean;\n  /** Shown when the sidebar is collapsed to icons. */\n  tooltip?: string | ComponentProps<typeof TooltipContent>;\n} & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const Comp = asChild ? Slot : 'button';\n  const { isMobile, state } = useSidebar();\n  /*\n   * The tooltip opens only on the desktop icon rail. Elsewhere it never\n   * mounts: a mounted, hidden tooltip would take the first Escape meant for\n   * the mobile sheet. Radix reports no close while open is forced false, so\n   * the state resets each time the rail appears or goes.\n   */\n  const onRail = state === 'collapsed' && !isMobile;\n  const [tooltipOpen, setTooltipOpen] = useState(false);\n  const [wasOnRail, setWasOnRail] = useState(onRail);\n  if (wasOnRail !== onRail) {\n    setWasOnRail(onRail);\n    setTooltipOpen(false);\n  }\n  const button = (\n    <Comp\n      data-slot=\"sidebar-menu-button\"\n      data-sidebar=\"menu-button\"\n      data-size={size ?? 'default'}\n      data-active={isActive}\n      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n      {...props}\n    />\n  );\n  if (!tooltip) return button;\n  const content = typeof tooltip === 'string' ? { children: tooltip } : tooltip;\n  /* The Tooltip stays in the tree, so the button keeps its focus when the sidebar collapses. */\n  return (\n    <Tooltip open={onRail && tooltipOpen} onOpenChange={setTooltipOpen}>\n      <TooltipTrigger asChild>{button}</TooltipTrigger>\n      <TooltipContent side=\"right\" align=\"center\" {...content} />\n    </Tooltip>\n  );\n}\n\n/* An icon button at the end of a row, such as More. showOnHover hides it until the row is hovered or focused, on a desktop. */\nfunction SidebarMenuAction({\n  className,\n  asChild = false,\n  showOnHover = false,\n  ...props\n}: ComponentProps<'button'> & { asChild?: boolean; showOnHover?: boolean }) {\n  const Comp = asChild ? Slot : 'button';\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-action\"\n      data-sidebar=\"menu-action\"\n      className={cn(\n        'sidebar-action right-1 w-5 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-3.5 peer-data-[size=sm]/menu-button:top-1 group-data-[collapsible=icon]/sidebar:hidden',\n        showOnHover &&\n          'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/* A count at the end of a row, such as new log entries. Never a status dot. */\nfunction SidebarMenuBadge({ className, ...props }: ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"sidebar-menu-badge\"\n      data-sidebar=\"menu-badge\"\n      className={cn(\n        'pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-muted px-1.5 text-xs font-bold text-foreground tabular-nums select-none',\n        'peer-data-[active=true]/menu-button:bg-transparent peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',\n        'peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-3.5 peer-data-[size=sm]/menu-button:top-1',\n        'group-data-[collapsible=icon]/sidebar:hidden',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/* A row's placeholder while the menu loads. Pick its text width so the rows differ. */\nconst sidebarMenuSkeletonVariants = cva('h-4 flex-1 rounded-sm', {\n  variants: {\n    width: { sm: 'max-w-1/2', md: 'max-w-2/3', lg: 'max-w-4/5' },\n  },\n  defaultVariants: { width: 'md' },\n});\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  width,\n  ...props\n}: ComponentProps<'div'> & { showIcon?: boolean } & VariantProps<\n    typeof sidebarMenuSkeletonVariants\n  >) {\n  return (\n    <div\n      data-slot=\"sidebar-menu-skeleton\"\n      data-sidebar=\"menu-skeleton\"\n      className={cn('flex h-8 items-center gap-2 rounded-lg px-2', className)}\n      {...props}\n    >\n      {showIcon ? (\n        <Skeleton className=\"size-4 rounded-sm\" data-sidebar=\"menu-skeleton-icon\" />\n      ) : null}\n      <Skeleton\n        className={sidebarMenuSkeletonVariants({ width })}\n        data-sidebar=\"menu-skeleton-text\"\n      />\n    </div>\n  );\n}\n\n/* A nested list under a row, with a line down its left. Hidden when collapsed to icons. */\nfunction SidebarMenuSub({ className, ...props }: ComponentProps<'ul'>) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu-sub\"\n      data-sidebar=\"menu-sub\"\n      className={cn(\n        'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 group-data-[collapsible=icon]/sidebar:hidden',\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      data-sidebar=\"menu-sub-item\"\n      className={cn('group/menu-sub-item relative', className)}\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuSubButtonVariants = cva(\n  'sidebar-menu-button h-7 min-w-0 -translate-x-px px-2 group-data-[collapsible=icon]/sidebar:hidden [&>svg]:text-sidebar-accent-foreground',\n  {\n    variants: { size: { sm: 'text-xs', md: 'text-sm' } },\n    defaultVariants: { size: 'md' },\n  },\n);\n\nfunction SidebarMenuSubButton({\n  asChild = false,\n  size,\n  isActive = false,\n  className,\n  ...props\n}: ComponentProps<'a'> & {\n  asChild?: boolean;\n  isActive?: boolean;\n} & VariantProps<typeof sidebarMenuSubButtonVariants>) {\n  const Comp = asChild ? Slot : 'a';\n  return (\n    <Comp\n      data-slot=\"sidebar-menu-sub-button\"\n      data-sidebar=\"menu-sub-button\"\n      data-size={size ?? 'md'}\n      data-active={isActive}\n      className={cn(sidebarMenuSubButtonVariants({ size }), className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  sidebarMenuButtonVariants,\n};\n",
      "type": "registry:ui"
    },
    {
      "path": "hooks/use-sidebar.ts",
      "content": "import { createContext, useContext } from 'react';\n\nexport type SidebarContextProps = {\n  /** \"collapsed\" on desktop when the sidebar is closed to its icons or off the screen. */\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  /** The sheet below the md breakpoint. */\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  /** Opens or closes the sheet on a phone and the sidebar on a desktop. */\n  toggleSidebar: () => void;\n};\n\nexport const SidebarContext = createContext<SidebarContextProps | null>(null);\n\n/** The state of the nearest SidebarProvider. */\nexport function useSidebar() {\n  const context = useContext(SidebarContext);\n  if (!context) throw new Error('useSidebar must be used within a SidebarProvider.');\n  return context;\n}\n",
      "type": "registry:hook"
    },
    {
      "path": "hooks/use-mobile.ts",
      "content": "import { useSyncExternalStore } from 'react';\n\n/* Tailwind's md breakpoint: below it the sidebar is a sheet. Media queries read rem at 16px. */\nconst MOBILE_QUERY = '(max-width: 47.999rem)';\n\nfunction subscribe(onChange: () => void) {\n  const query = window.matchMedia(MOBILE_QUERY);\n  query.addEventListener('change', onChange);\n  return () => query.removeEventListener('change', onChange);\n}\n\n/** True below the md breakpoint. False on the server and in the first client render. */\nexport function useIsMobile() {\n  return useSyncExternalStore(\n    subscribe,\n    () => window.matchMedia(MOBILE_QUERY).matches,\n    () => false,\n  );\n}\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:ui"
}