{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sidebar-nav",
  "title": "Sidebar Nav",
  "description": "Sidebar Nav from the Get IT Done design system.",
  "dependencies": [
    "@radix-ui/react-collapsible@^1.2.1",
    "class-variance-authority@^0.7.0",
    "lucide-react@^0.379.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/sheet.json",
    "https://zero-design.itdone.de/r/sidebar.json",
    "https://zero-design.itdone.de/r/use-escape-key.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/sidebar-nav.tsx",
      "content": "'use client';\n\nimport * as CollapsiblePrimitive from '@radix-ui/react-collapsible';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { ArrowLeft, ChevronDown, Search, X } from 'lucide-react';\nimport * as React from 'react';\n\nimport { useEscapeKey } from '@/lib/use-escape-key';\nimport { cn } from '@/lib/utils';\nimport { SidebarKbd, SidebarNavItem, SidebarSection, SidebarTree, type SidebarNavItemProps } from '@/components/ui/sidebar';\nimport { Sheet, SheetContent, SheetDescription, SheetTitle } from '@/components/ui/sheet';\nimport { useLabels, usePreset } from '@/components/ui/labels-context';\n\n// Additive sidebar building blocks (Figma \"Sidebar navigation\"): data-driven app trees, the settings mode\n// (back row, search), the Dual-tier / Slim tiers, the featured card, the account row and the mobile menu.\n// The existing Sidebar, SidebarSection, SidebarTree and SidebarNavItem keep their API.\n\nconst focusRing =\n  'outline-none focus-visible:ring-2 focus-visible:ring-sidebar-ring focus-visible:ring-offset-2 focus-visible:ring-offset-sidebar-background';\n\n/* -------------------------------------------------------------------------------------------------\n * Data-driven navigation tree\n * -----------------------------------------------------------------------------------------------*/\n\nexport interface SidebarNavNode\n  extends Pick<SidebarNavItemProps, 'icon' | 'secondary' | 'count' | 'trailing' | 'active' | 'disabled' | 'dot' | 'statusDot' | 'locked'> {\n  id: string;\n  label: React.ReactNode;\n  /** Makes the row a link. A node without href but with children toggles its group instead. */\n  href?: string;\n  /** Accessible name for the group toggle when `label` is not a string. */\n  toggleLabel?: string;\n  /** Opens the group initially. Groups with an active descendant open automatically. */\n  defaultOpen?: boolean;\n  children?: SidebarNavNode[];\n}\n\nexport interface SidebarNavTreeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {\n  nodes: SidebarNavNode[];\n  /** Passed to every SidebarNavItem. Default `nav`. */\n  variant?: SidebarNavItemProps['variant'];\n  /** Controlled open group ids. */\n  openIds?: string[];\n  /** Initial open group ids when uncontrolled. Merged with `defaultOpen` and active descendants. */\n  defaultOpenIds?: string[];\n  onOpenIdsChange?: (ids: string[]) => void;\n  /** Custom link element (e.g. a router Link). Receives the node and the props to spread on the anchor. */\n  renderLink?: (node: SidebarNavNode, props: SidebarNavItemProps) => React.ReactElement;\n}\n\nconst hasActive = (node: SidebarNavNode): boolean =>\n  Boolean(node.active) || (node.children?.some(hasActive) ?? false);\n\nconst collectInitialOpen = (nodes: SidebarNavNode[], acc: string[] = []): string[] => {\n  nodes.forEach((node) => {\n    if (node.children?.length) {\n      if (node.defaultOpen || node.children.some(hasActive)) acc.push(node.id);\n      collectInitialOpen(node.children, acc);\n    }\n  });\n  return acc;\n};\n\nconst SidebarNavTree = React.forwardRef<HTMLDivElement, SidebarNavTreeProps>(\n  (\n    { className, nodes, variant = 'nav', openIds, defaultOpenIds, onOpenIdsChange, renderLink, ...props },\n    ref,\n  ) => {\n    const l = useLabels();\n    const [uncontrolled, setUncontrolled] = React.useState<string[]>(() => [\n      ...(defaultOpenIds ?? []),\n      ...collectInitialOpen(nodes),\n    ]);\n    const open = openIds ?? uncontrolled;\n    const setOpen = (id: string, next: boolean) => {\n      const ids = next ? [...open, id] : open.filter((value) => value !== id);\n      if (openIds === undefined) setUncontrolled(ids);\n      onOpenIdsChange?.(ids);\n    };\n\n    const renderNodes = (list: SidebarNavNode[], depth = 0): React.ReactNode =>\n      list.map((node) => {\n        const itemProps: SidebarNavItemProps = {\n          variant,\n          icon: node.icon,\n          label: node.label,\n          secondary: node.secondary,\n          count: node.count,\n          trailing: node.trailing,\n          active: node.active,\n          disabled: node.disabled,\n          dot: node.dot,\n          statusDot: node.statusDot,\n          locked: node.locked,\n          href: node.href ?? '#',\n        };\n        const link = renderLink ? renderLink(node, itemProps) : <SidebarNavItem {...itemProps} />;\n        if (!node.children?.length) return <React.Fragment key={node.id}>{link}</React.Fragment>;\n\n        const isOpen = open.includes(node.id);\n        const toggleName = node.toggleLabel ?? (typeof node.label === 'string' ? node.label : l.group);\n        return (\n          <CollapsiblePrimitive.Root\n            key={node.id}\n            open={isOpen}\n            onOpenChange={(next) => setOpen(node.id, next)}\n            className=\"flex flex-col gap-[2px]\"\n          >\n            <div className=\"relative flex items-center\">\n              {node.href ? (\n                <>\n                  <div className=\"min-w-0 flex-1 [&>a]:pr-8\">{link}</div>\n                  <CollapsiblePrimitive.Trigger\n                    aria-label={toggleName}\n                    className={cn(\n                      'absolute right-1 inline-flex h-[24px] w-[24px] items-center justify-center rounded-[6px] text-sidebar-muted-foreground transition-colors hover:bg-sidebar-accent group-data-[rail]/sb:hidden',\n                      focusRing,\n                    )}\n                  >\n                    <ChevronDown\n                      className={cn('h-[14px] w-[14px] transition-transform', !isOpen && '-rotate-90')}\n                      aria-hidden\n                    />\n                  </CollapsiblePrimitive.Trigger>\n                </>\n              ) : (\n                <SidebarNavItem asChild label={node.label} variant={variant} className=\"w-full\" active={node.active} disabled={node.disabled}>\n                  <CollapsiblePrimitive.Trigger>\n                    {node.icon && (\n                      <span\n                        className=\"flex h-[18px] w-[18px] shrink-0 items-center justify-center [&>svg]:h-4 [&>svg]:w-4\"\n                        aria-hidden\n                      >\n                        {node.icon}\n                      </span>\n                    )}\n                    <span className=\"truncate text-left group-data-[rail]/sb:sr-only\">{node.label}</span>\n                    <ChevronDown\n                      className={cn(\n                        'ml-auto h-[14px] w-[14px] shrink-0 text-sidebar-muted-foreground transition-transform group-data-[rail]/sb:hidden',\n                        !isOpen && '-rotate-90',\n                      )}\n                      aria-hidden\n                    />\n                  </CollapsiblePrimitive.Trigger>\n                </SidebarNavItem>\n              )}\n            </div>\n            <CollapsiblePrimitive.Content>\n              <SidebarTree level={depth > 0 ? 1 : 0}>{renderNodes(node.children, depth + 1)}</SidebarTree>\n            </CollapsiblePrimitive.Content>\n          </CollapsiblePrimitive.Root>\n        );\n      });\n\n    return (\n      <div ref={ref} className={cn('flex flex-col gap-[2px]', className)} {...props}>\n        {renderNodes(nodes)}\n      </div>\n    );\n  },\n);\nSidebarNavTree.displayName = 'SidebarNavTree';\n\n/* -------------------------------------------------------------------------------------------------\n * Grouped sections (settings mode: Finance, Development and APIs, Admin)\n * -----------------------------------------------------------------------------------------------*/\n\nexport interface SidebarNavGroup {\n  id: string;\n  /** Group heading, e.g. `Integrations · Finance`. */\n  label: React.ReactNode;\n  nodes: SidebarNavNode[];\n  /** Renders the heading as a collapsible section toggle. Default false (static heading). */\n  collapsible?: boolean;\n  /** Initial state for a collapsible group. Default true. */\n  defaultOpen?: boolean;\n}\n\nexport interface SidebarNavGroupsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,\n    Pick<SidebarNavTreeProps, 'variant' | 'renderLink'> {\n  groups: SidebarNavGroup[];\n}\n\n// Data-driven grouped sections: a heading per group followed by its navigation tree.\nconst SidebarNavGroups = React.forwardRef<HTMLDivElement, SidebarNavGroupsProps>(\n  ({ className, groups, variant, renderLink, ...props }, ref) => {\n    const baseId = React.useId();\n    return (\n      <div ref={ref} className={cn('flex flex-col', className)} {...props}>\n        {groups.map((group) => {\n          const tree = <SidebarNavTree nodes={group.nodes} variant={variant} renderLink={renderLink} />;\n          if (group.collapsible) {\n            return (\n              <SidebarSection key={group.id} label={group.label} defaultOpen={group.defaultOpen ?? true}>\n                {tree}\n              </SidebarSection>\n            );\n          }\n          const headingId = `${baseId}-${group.id}`;\n          return (\n            <div key={group.id} role=\"group\" aria-labelledby={headingId} className=\"mt-[14px] flex flex-col gap-[2px]\">\n              <div\n                id={headingId}\n                className=\"typography-ui-sm px-[6px] text-sidebar-muted-foreground group-data-[rail]/sb:hidden\"\n              >\n                {group.label}\n              </div>\n              {tree}\n            </div>\n          );\n        })}\n      </div>\n    );\n  },\n);\nSidebarNavGroups.displayName = 'SidebarNavGroups';\n\n/* -------------------------------------------------------------------------------------------------\n * Settings mode: back row and search\n * -----------------------------------------------------------------------------------------------*/\n\nexport interface SidebarBackRowProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {\n  /** Visible title, e.g. `Settings`. */\n  label?: string;\n  /** Shortcut hint on the right. Default `Esc`. */\n  shortcut?: string;\n  /** Also leaves on the Escape key while mounted. Needs `onClick`. */\n  closeOnEscape?: boolean;\n  /** Screen reader prefix before the title, default `Back from `. Wins over the provider text. */\n  backFromLabel?: string;\n}\n\nconst SidebarBackRow = React.forwardRef<HTMLButtonElement, SidebarBackRowProps>(\n  ({ className, label: labelProp, shortcut = 'Esc', closeOnEscape = false, backFromLabel, onClick, ...props }, ref) => {\n    const l = useLabels({ backFrom: backFromLabel });\n    const label = labelProp ?? l.settings;\n    const zero = usePreset() === 'zero';\n    useEscapeKey(\n      onClick ? () => onClick(undefined as unknown as React.MouseEvent<HTMLButtonElement>) : undefined,\n      closeOnEscape,\n    );\n    return (\n      <button\n        ref={ref}\n        type=\"button\"\n        aria-keyshortcuts=\"Escape\"\n        onClick={onClick}\n        className={cn(\n          'flex items-center gap-2 px-2 text-left text-sidebar-accent-foreground transition-colors hover:bg-sidebar-accent',\n          zero ? 'typography-nav-item-current h-8 rounded-item' : 'h-nav-item rounded-[8px] text-[14px] font-medium',\n          focusRing,\n          className,\n        )}\n        {...props}\n      >\n        <ArrowLeft className=\"h-4 w-4 shrink-0\" aria-hidden />\n        <span className=\"sr-only\">{l.backFrom}</span>\n        <span className=\"truncate\">{label}</span>\n        <SidebarKbd aria-hidden>{shortcut}</SidebarKbd>\n      </button>\n    );\n  },\n);\nSidebarBackRow.displayName = 'SidebarBackRow';\n\nexport interface SidebarSearchFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {\n  /** Accessible name. Defaults to the placeholder. */\n  label?: string;\n}\n\nconst SidebarSearchField = React.forwardRef<HTMLInputElement, SidebarSearchFieldProps>(\n  ({ className, label, placeholder: placeholderProp, ...props }, ref) => {\n    const l = useLabels();\n    const placeholder = placeholderProp ?? l.find;\n    const zero = usePreset() === 'zero';\n    return (\n    <div\n      role=\"search\"\n      className={cn(\n        'flex h-nav-item items-center gap-2 border border-solid bg-background px-2 text-sidebar-muted-foreground focus-within:ring-2 focus-within:ring-sidebar-ring',\n        zero ? 'rounded-control border-input' : 'rounded-[8px] border-sidebar-border shadow-sm',\n      )}\n    >\n      <Search className=\"h-4 w-4 shrink-0\" aria-hidden />\n      <input\n        ref={ref}\n        type=\"search\"\n        aria-label={label ?? placeholder}\n        placeholder={placeholder}\n        className={cn(\n          'min-w-0 flex-1 appearance-none border-0 bg-transparent p-0 text-ui text-sidebar-accent-foreground outline-none placeholder:text-sidebar-muted-foreground',\n          className,\n        )}\n        {...props}\n      />\n    </div>\n  );\n  },\n);\nSidebarSearchField.displayName = 'SidebarSearchField';\n\n/* -------------------------------------------------------------------------------------------------\n * Tiers (Dual-tier, Slim) and rail buttons\n * -----------------------------------------------------------------------------------------------*/\n\nconst sidebarTierVariants = cva(\n  'flex h-full shrink-0 flex-col overflow-x-hidden border-0 border-r border-solid border-sidebar-border bg-sidebar-background text-sidebar-foreground',\n  {\n    variants: {\n      width: {\n        // icon-only rail of the zero sidebar\n        collapsed: 'w-sidebar-collapsed',\n        // 80, Slim icon column (Figma 82 incl. border)\n        rail: 'w-sidebar-rail',\n        // 248, regular sidebar width\n        panel: 'w-sidebar',\n        // 280, Dual-tier primary column and Slim sub navigation\n        wide: 'w-aside-compact',\n      },\n    },\n    defaultVariants: { width: 'panel' },\n  },\n);\n\nexport interface SidebarTierProps extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof sidebarTierVariants> {}\n\nconst SidebarTier = React.forwardRef<HTMLElement, SidebarTierProps>(({ className, width, ...props }, ref) => (\n  <nav ref={ref} className={cn(sidebarTierVariants({ width }), className)} {...props} />\n));\nSidebarTier.displayName = 'SidebarTier';\n\nexport interface SidebarRailButtonProps\n  extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement> & React.ButtonHTMLAttributes<HTMLButtonElement>, 'type'> {\n  icon: React.ReactNode;\n  /** Required accessible name and tooltip text. */\n  label: string;\n  active?: boolean;\n}\n\n// Icon-only navigation button for the Slim rail. Renders an anchor when `href` is set, a button otherwise.\nconst SidebarRailButton = React.forwardRef<HTMLAnchorElement & HTMLButtonElement, SidebarRailButtonProps>(\n  ({ className, icon, label, active = false, href, ...props }, ref) => {\n    const classes = cn(\n      'inline-flex aspect-square h-sidebar-rail-button shrink-0 items-center justify-center rounded-item text-sidebar-muted-foreground no-underline transition-colors',\n      'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n      'data-[active]:bg-sidebar-accent data-[active]:text-sidebar-accent-foreground',\n      // Theme selection indicator and list hover (kette-tokens 5.26.0), nothing by default\n      '[&:not([data-active])]:hover:bg-[image:var(--image-sidebar-item-hover)] data-[active]:shadow-[shadow:inset_var(--indicator-selection-edge)_0_0_var(--indicator-selection-edge-color),inset_0_0_0_var(--indicator-selection-ring)_var(--indicator-selection-ring-color)]',\n      '[&>svg]:h-icon-lg [&>svg]:w-icon-lg',\n      focusRing,\n      className,\n    );\n    const shared = { 'aria-label': label, title: label, 'data-active': active || undefined, className: classes };\n    return href !== undefined ? (\n      <a ref={ref} href={href} aria-current={active ? 'page' : undefined} {...shared} {...props}>\n        {icon}\n      </a>\n    ) : (\n      <button ref={ref} type=\"button\" aria-pressed={active} {...shared} {...props}>\n        {icon}\n      </button>\n    );\n  },\n);\nSidebarRailButton.displayName = 'SidebarRailButton';\n\n/* -------------------------------------------------------------------------------------------------\n * Featured card + account row\n * -----------------------------------------------------------------------------------------------*/\n\nexport interface SidebarFeaturedCardProps extends Omit<React.HTMLAttributes<HTMLElement>, 'title'> {\n  title: React.ReactNode;\n  description?: React.ReactNode;\n  /** Leading visual: progress ring, bar or image. */\n  media?: React.ReactNode;\n  /** Primary action, e.g. an upgrade link. */\n  action?: React.ReactNode;\n  /** Renders the close button and the Dismiss action when provided. */\n  onDismiss?: () => void;\n  dismissLabel?: string;\n}\n\nconst SidebarFeaturedCard = React.forwardRef<HTMLElement, SidebarFeaturedCardProps>(\n  ({ className, title, description, media, action, onDismiss, dismissLabel: dismissLabelProp, ...props }, ref) => {\n    const l = useLabels();\n    const dismissLabel = dismissLabelProp ?? l.dismiss;\n    const titleId = React.useId();\n    return (\n      <section\n        ref={ref}\n        aria-labelledby={titleId}\n        className={cn('relative flex flex-col gap-4 rounded-control bg-muted px-4 py-5 text-text-default', className)}\n        {...props}\n      >\n        {onDismiss && (\n          <button\n            type=\"button\"\n            onClick={onDismiss}\n            aria-label={dismissLabel}\n            className={cn(\n              'absolute right-2 top-2 inline-flex h-control-sm w-control-sm items-center justify-center rounded-control text-icon-subtle hover:bg-muted-strong',\n              focusRing,\n            )}\n          >\n            <X className=\"h-icon-sm w-icon-sm\" aria-hidden />\n          </button>\n        )}\n        {media}\n        <div className=\"flex flex-col gap-1\">\n          <p id={titleId} className=\"typography-label-highlight m-0\">\n            {title}\n          </p>\n          {description && <p className=\"typography-ui m-0 text-text-subtle-on-muted\">{description}</p>}\n        </div>\n        {(onDismiss || action) && (\n          <div className=\"flex items-center gap-3\">\n            {onDismiss && (\n              <button\n                type=\"button\"\n                onClick={onDismiss}\n                className={cn('typography-label-highlight rounded-item text-text-subtle-on-muted hover:text-text-default', focusRing)}\n              >\n                {dismissLabel}\n              </button>\n            )}\n            {action}\n          </div>\n        )}\n      </section>\n    );\n  },\n);\nSidebarFeaturedCard.displayName = 'SidebarFeaturedCard';\n\nexport interface SidebarAccountProps extends React.HTMLAttributes<HTMLDivElement> {\n  avatar: React.ReactNode;\n  name: React.ReactNode;\n  secondary?: React.ReactNode;\n  /** Trailing action, e.g. a log out icon button with an aria-label. */\n  action?: React.ReactNode;\n}\n\nconst SidebarAccount = React.forwardRef<HTMLDivElement, SidebarAccountProps>(\n  ({ className, avatar, name, secondary, action, ...props }, ref) => (\n    <div\n      ref={ref}\n      className={cn(\n        'flex items-center gap-3 border-0 border-t border-solid border-sidebar-border px-2 pt-4',\n        className,\n      )}\n      {...props}\n    >\n      {avatar}\n      <div className=\"flex min-w-0 flex-1 flex-col\">\n        <span className=\"typography-label-highlight truncate text-sidebar-accent-foreground\">{name}</span>\n        {secondary && <span className=\"typography-ui truncate text-sidebar-muted-foreground\">{secondary}</span>}\n      </div>\n      {action}\n    </div>\n  ),\n);\nSidebarAccount.displayName = 'SidebarAccount';\n\n/* -------------------------------------------------------------------------------------------------\n * Mobile menu\n * -----------------------------------------------------------------------------------------------*/\n\nexport interface SidebarMobileMenuProps {\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  /** Accessible name of the dialog. Default `Menu`. */\n  title?: string;\n  children: React.ReactNode;\n  className?: string;\n}\n\n// Full-height navigation drawer for small screens, built on Sheet (focus trap, Esc, scrim close).\nconst SidebarMobileMenu = ({ open, onOpenChange, title: titleProp, children, className }: SidebarMobileMenuProps) => {\n  const l = useLabels();\n  const title = titleProp ?? l.menu;\n  return (\n  <Sheet open={open} onOpenChange={onOpenChange}>\n    <SheetContent\n      side=\"left\"\n      padded={false}\n      className={cn('w-5/6 bg-sidebar-background text-sidebar-foreground', className)}\n    >\n      <SheetTitle className=\"sr-only\">{title}</SheetTitle>\n      <SheetDescription className=\"sr-only\">{l.navigation}</SheetDescription>\n      <nav aria-label={title} className=\"flex min-h-0 flex-1 flex-col gap-[2px] overflow-y-auto p-4\">\n        {children}\n      </nav>\n    </SheetContent>\n  </Sheet>\n  );\n};\n\nexport {\n  SidebarAccount,\n  SidebarBackRow,\n  SidebarFeaturedCard,\n  SidebarMobileMenu,\n  SidebarNavGroups,\n  SidebarNavTree,\n  SidebarRailButton,\n  SidebarSearchField,\n  SidebarTier,\n  sidebarTierVariants,\n};\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}