{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sidebar-nav",
  "title": "Sidebar navigation",
  "description": "Expanded or collapsed workspace navigation with sections, counts, actions, and quick search.",
  "registryDependencies": [
    "https://beadkit.dev/r/ai-core.json"
  ],
  "files": [
    {
      "path": "components/ai/SidebarNav.tsx",
      "content": "\"use client\";\n\nimport { useState, type ReactNode } from \"react\";\n\nexport interface SidebarNavItem { id: string; label: string; section?: string; icon?: ReactNode; count?: number; action?: boolean; }\nexport interface SidebarNavProps { items: readonly SidebarNavItem[]; activeId?: string; workspace?: { name: string; description?: string; mark?: ReactNode }; collapsed?: boolean; onActiveChange?: (item: SidebarNavItem) => void; onSearch?: () => void; onWorkspaceClick?: () => void; onItemAction?: (item: SidebarNavItem) => void; className?: string; }\nconst SearchIcon = () => <svg aria-hidden=\"true\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.9\" strokeLinecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\" /><path d=\"m21 21-4.3-4.3\" /></svg>;\nexport function SidebarNav({ items, activeId, workspace, collapsed = false, onActiveChange, onSearch, onWorkspaceClick, onItemAction, className = \"\" }: SidebarNavProps) {\n  const [active, setActive] = useState(activeId ?? items[0]?.id); const [hovered, setHovered] = useState<string | null>(null); const sections = Array.from(new Set(items.map((item) => item.section ?? \"\")));\n  const select = (item: SidebarNavItem) => { setActive(item.id); onActiveChange?.(item); };\n  return <aside data-ai-state=\"sidebar-nav\" data-collapsed={collapsed} aria-label=\"Workspace navigation\" className={`bg-card border-border w-full max-w-xs rounded-xl border p-2 shadow-sm ${collapsed ? \"max-w-14\" : \"\"} ${className}`}>\n    {workspace ? <button type=\"button\" aria-label={`Switch workspace: ${workspace.name}`} disabled={!onWorkspaceClick} onClick={onWorkspaceClick} className=\"hover:bg-muted mb-2 flex w-full items-center gap-2.5 rounded-lg p-1.5 text-left disabled:cursor-default\">{workspace.mark ?? <span className=\"bg-primary text-primary-foreground flex size-8 shrink-0 items-center justify-center rounded-lg text-sm font-semibold\">{workspace.name.slice(0, 1).toUpperCase()}</span>}{!collapsed ? <span className=\"min-w-0 flex-1\"><span className=\"block truncate text-sm font-medium\">{workspace.name}</span>{workspace.description ? <span className=\"text-muted-foreground block truncate text-[11px]\">{workspace.description}</span> : null}</span> : null}<span className=\"text-muted-foreground\"><svg aria-hidden=\"true\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\"><path d=\"m7 15 5 5 5-5M7 9l5-5 5 5\" /></svg></span></button> : null}\n    {!collapsed ? <button type=\"button\" disabled={!onSearch} onClick={onSearch} className=\"bg-muted text-muted-foreground hover:bg-secondary hover:text-secondary-foreground mb-2 flex h-9 w-full items-center gap-2 rounded-lg px-2.5 text-left text-xs disabled:cursor-default\"><SearchIcon /><span className=\"flex-1\">Quick search</span><kbd className=\"bg-card text-muted-foreground rounded px-1.5 py-0.5 text-[10px]\">/</kbd></button> : <button type=\"button\" aria-label=\"Quick search\" disabled={!onSearch} onClick={onSearch} className=\"text-muted-foreground hover:bg-secondary hover:text-secondary-foreground mb-2 flex size-9 items-center justify-center rounded-lg disabled:cursor-default\"><SearchIcon /></button>}\n    <nav className=\"flex flex-col gap-2\">{sections.map((section) => <div key={section || \"default\"}>{!collapsed && section ? <div className=\"text-muted-foreground px-2 pb-1 pt-1 text-[10px] font-medium uppercase tracking-wider\">{section}</div> : null}<div className=\"flex flex-col gap-px\">{items.filter((item) => (item.section ?? \"\") === section).map((item) => { const selected = (activeId ?? active) === item.id; return <div key={item.id} onMouseEnter={() => setHovered(item.id)} onMouseLeave={() => setHovered(null)} onBlur={(event) => { if (!event.currentTarget.contains(event.relatedTarget)) setHovered(null); }} className=\"group relative\"><button type=\"button\" title={collapsed ? item.label : undefined} aria-current={selected ? \"page\" : undefined} onFocus={() => setHovered(item.id)} onClick={() => select(item)} className={`relative z-10 flex min-h-9 w-full items-center gap-2 rounded-lg px-2 text-left text-sm transition-colors duration-150 motion-reduce:transition-none ${selected || hovered === item.id ? \"bg-muted\" : \"hover:bg-muted\"}`}><span className={`flex size-5 shrink-0 items-center justify-center ${selected ? \"text-foreground\" : \"text-muted-foreground\"}`}>{item.icon ?? <span className=\"size-1.5 rounded-full bg-current\" />}</span>{!collapsed ? <><span className=\"min-w-0 flex-1 truncate\">{item.label}</span>{item.count !== undefined ? <span className=\"text-muted-foreground mr-7 text-[11px] tabular-nums\">{item.count}</span> : null}</> : null}</button>{item.action && !collapsed ? <button type=\"button\" aria-label={`Add ${item.label}`} onClick={() => onItemAction?.(item)} className=\"text-muted-foreground hover:bg-accent absolute right-1 top-1/2 z-20 hidden size-7 -translate-y-1/2 items-center justify-center rounded-md group-hover:flex group-focus-within:flex\"><span aria-hidden=\"true\">+</span></button> : null}</div>; })}</div></div>)}</nav>\n  </aside>;\n}\nexport default SidebarNav;\n",
      "type": "registry:component",
      "target": "@components/ai/SidebarNav.tsx"
    }
  ],
  "type": "registry:component"
}
