{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sidebar",
  "title": "Sidebar",
  "description": "Floating app navigation with team and user menus, badges, and a collapsed rail.",
  "dependencies": [
    "lucide-react@^1.33.0"
  ],
  "registryDependencies": [
    "https://components.exe.xyz/r/avatar.json",
    "https://components.exe.xyz/r/badge.json",
    "https://components.exe.xyz/r/button.json",
    "https://components.exe.xyz/r/chip.json",
    "https://components.exe.xyz/r/close-button.json",
    "https://components.exe.xyz/r/dropdown-menu.json",
    "https://components.exe.xyz/r/icon-button.json",
    "https://components.exe.xyz/r/kbd.json",
    "https://components.exe.xyz/r/sheet.json",
    "https://components.exe.xyz/r/tooltip.json",
    "https://components.exe.xyz/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/blocks/sidebar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  ChevronsLeft,\n  ChevronsRight,\n  GitBranch,\n  LifeBuoy,\n  Moon,\n  Search,\n  Settings,\n  Sparkles,\n  Sun,\n  Workflow,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport { Chip } from \"@/components/ui/chip\"\nimport { CloseButton } from \"@/components/ui/close-button\"\nimport { IconButton } from \"@/components/ui/icon-button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { Kbd } from \"@/components/ui/kbd\"\nimport {\n  Sheet,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetTitle,\n} from \"@/components/ui/sheet\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\n\nexport type SidebarIcon = React.ComponentType<{ className?: string }>\n\nexport type SidebarItem = {\n  id: string\n  href: string\n  label: string\n  icon: SidebarIcon\n  badge?: React.ReactNode\n  current?: boolean\n}\n\nexport type SidebarFooterItem = {\n  id: string\n  label: string\n  onPress?: () => void\n  href?: string\n}\n\nexport type SidebarUser = {\n  name: string\n  email?: string\n  avatarSrc?: string\n  initials?: string\n}\n\nexport type SidebarTeam = {\n  name: string\n  email?: string\n  avatarSrc?: string\n  initials?: string\n}\n\nexport type SidebarRepository = {\n  id: string\n  name: string\n  chats: { id: string; title: string; relativeTime: string }[]\n}\n\nexport function Sidebar({\n  variant = \"dashboard\",\n  collapsed,\n  onCollapsedChange,\n  items,\n  footerItems,\n  user,\n  team,\n  onOpenSettings,\n  onQuickSearch,\n  announcement,\n  mobileOpen,\n  onMobileOpenChange,\n  repositories,\n  plan,\n  onNavigate,\n  agentPrimaryAction,\n  agentSecondaryAction,\n  className,\n}: {\n  variant?: \"dashboard\" | \"agent\"\n  collapsed?: boolean\n  onCollapsedChange?: (collapsed: boolean) => void\n  items: SidebarItem[]\n  footerItems?: SidebarFooterItem[]\n  user: SidebarUser\n  team?: SidebarTeam\n  onOpenSettings?: () => void\n  onQuickSearch?: () => void\n  announcement?: React.ReactNode\n  mobileOpen?: boolean\n  onMobileOpenChange?: (open: boolean) => void\n  repositories?: SidebarRepository[]\n  plan?: { name: string; description: string; onUpgrade?: () => void }\n  onNavigate?: (id: string) => void\n  agentPrimaryAction?: { label: string; href?: string; onPress?: () => void }\n  agentSecondaryAction?: { label: string; href?: string; onPress?: () => void }\n  className?: string\n}) {\n  const [uncontrolled, setUncontrolled] = React.useState(false)\n  const isCollapsed = collapsed ?? uncontrolled\n\n  function setCollapsed(next: boolean) {\n    if (collapsed === undefined) setUncontrolled(next)\n    onCollapsedChange?.(next)\n  }\n\n  const panel = (\n    <SidebarPanel\n      variant={variant}\n      collapsed={isCollapsed}\n      onCollapsedChange={setCollapsed}\n      items={items}\n      footerItems={footerItems}\n      user={user}\n      team={team}\n      onOpenSettings={onOpenSettings}\n      onQuickSearch={onQuickSearch}\n      announcement={announcement}\n      repositories={repositories}\n      plan={plan}\n      onNavigate={onNavigate}\n      agentPrimaryAction={agentPrimaryAction}\n      agentSecondaryAction={agentSecondaryAction}\n    />\n  )\n\n  return (\n    <>\n      <aside\n        data-slot=\"sidebar\"\n        data-collapsed={isCollapsed ? \"true\" : undefined}\n        className={cn(\n          \"relative z-10 hidden h-full shrink-0 flex-col self-stretch rounded-[2px] border border-border bg-card shadow-sm md:flex\",\n          isCollapsed ? \"w-14\" : \"w-60\",\n          className\n        )}\n      >\n        {panel}\n      </aside>\n      <Sheet open={mobileOpen} onOpenChange={onMobileOpenChange}>\n        <SheetContent\n          side=\"left\"\n          showCloseButton={false}\n          className=\"w-72 p-0 md:hidden\"\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Primary</SheetTitle>\n          </SheetHeader>\n          <SheetClose asChild>\n            <CloseButton className=\"absolute top-3 right-3\" />\n          </SheetClose>\n          <SidebarPanel\n            variant={variant}\n            collapsed={false}\n            onCollapsedChange={setCollapsed}\n            items={items}\n            footerItems={footerItems}\n            user={user}\n            team={team}\n            onOpenSettings={onOpenSettings}\n            onQuickSearch={onQuickSearch}\n            announcement={announcement}\n            repositories={repositories}\n            plan={plan}\n            onNavigate={onNavigate}\n            agentPrimaryAction={agentPrimaryAction}\n            agentSecondaryAction={agentSecondaryAction}\n            hideCollapse\n          />\n        </SheetContent>\n      </Sheet>\n    </>\n  )\n}\n\nfunction SidebarPanel({\n  variant,\n  collapsed,\n  onCollapsedChange,\n  items,\n  footerItems,\n  user,\n  team,\n  onOpenSettings,\n  onQuickSearch,\n  announcement,\n  repositories,\n  plan,\n  onNavigate,\n  agentPrimaryAction,\n  agentSecondaryAction,\n  hideCollapse,\n}: {\n  variant: \"dashboard\" | \"agent\"\n  collapsed: boolean\n  onCollapsedChange: (collapsed: boolean) => void\n  items: SidebarItem[]\n  footerItems?: SidebarFooterItem[]\n  user: SidebarUser\n  team?: SidebarTeam\n  onOpenSettings?: () => void\n  onQuickSearch?: () => void\n  announcement?: React.ReactNode\n  repositories?: SidebarRepository[]\n  plan?: { name: string; description: string; onUpgrade?: () => void }\n  onNavigate?: (id: string) => void\n  agentPrimaryAction?: { label: string; href?: string; onPress?: () => void }\n  agentSecondaryAction?: { label: string; href?: string; onPress?: () => void }\n  hideCollapse?: boolean\n}) {\n  const footers = footerItems ?? []\n\n  return (\n    <div className=\"flex h-full min-h-0 flex-col\">\n      <div\n        className={cn(\"flex items-center gap-1 p-2\", collapsed && \"flex-col\")}\n      >\n        {!hideCollapse ? (\n          <IconButton\n            icon={collapsed ? ChevronsRight : ChevronsLeft}\n            size=\"small\"\n            variant=\"ghost\"\n            aria-label={collapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\n            onClick={() => onCollapsedChange(!collapsed)}\n          />\n        ) : null}\n        {variant === \"dashboard\" ? (\n          <UserChip\n            user={user}\n            collapsed={collapsed}\n            onOpenSettings={onOpenSettings}\n          />\n        ) : null}\n      </div>\n\n      {variant === \"dashboard\" ? (\n        onQuickSearch ? (\n          <div className=\"px-2 pb-2\">\n            <QuickSearch collapsed={collapsed} onQuickSearch={onQuickSearch} />\n          </div>\n        ) : null\n      ) : (\n        <div className=\"flex flex-col gap-1 px-2 pb-2\">\n          {agentPrimaryAction ? (\n            <QuickAction\n              icon={Sparkles}\n              collapsed={collapsed}\n              {...agentPrimaryAction}\n            />\n          ) : null}\n          {agentSecondaryAction ? (\n            <QuickAction\n              icon={Workflow}\n              collapsed={collapsed}\n              {...agentSecondaryAction}\n            />\n          ) : null}\n          {onOpenSettings ? (\n            <QuickAction\n              icon={Settings}\n              label=\"Customize\"\n              collapsed={collapsed}\n              onPress={onOpenSettings}\n            />\n          ) : null}\n        </div>\n      )}\n\n      {announcement && !collapsed ? (\n        <div className=\"px-2 pb-2\">{announcement}</div>\n      ) : null}\n\n      <nav\n        aria-label=\"Primary\"\n        className=\"flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2\"\n      >\n        {items.map((item) => (\n          <NavItem\n            key={item.id}\n            item={item}\n            collapsed={collapsed}\n            onNavigate={onNavigate}\n          />\n        ))}\n        {variant === \"agent\" && repositories?.length && !collapsed ? (\n          <RepositoryTree repositories={repositories} />\n        ) : null}\n      </nav>\n\n      <div className=\"mt-auto flex flex-col gap-1 border-t border-border p-2\">\n        {footers.map((item) => (\n          <FooterRow key={item.id} item={item} collapsed={collapsed} />\n        ))}\n        {variant === \"dashboard\" && team ? (\n          <TeamMenu team={team} collapsed={collapsed} />\n        ) : null}\n        {variant === \"agent\" && !collapsed ? <PlanCard plan={plan} /> : null}\n        <div className={cn(\"pt-1\", collapsed && \"flex justify-center\")}>\n          <SidebarThemeToggle collapsed={collapsed} />\n        </div>\n      </div>\n    </div>\n  )\n}\n\nfunction NavItem({\n  item,\n  collapsed,\n  onNavigate,\n}: {\n  item: SidebarItem\n  collapsed: boolean\n  onNavigate?: (id: string) => void\n}) {\n  const Icon = item.icon\n  const content = (\n    <a\n      href={item.href}\n      aria-current={item.current ? \"page\" : undefined}\n      aria-label={collapsed ? item.label : undefined}\n      onClick={(event) => {\n        if (!onNavigate) return\n        event.preventDefault()\n        onNavigate(item.id)\n      }}\n      className={cn(\n        \"flex items-center gap-2 rounded-[2px] px-2 py-1.5 text-sm outline-none hover:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50\",\n        item.current\n          ? \"bg-muted text-foreground\"\n          : \"text-muted-foreground hover:text-foreground\",\n        collapsed && \"justify-center px-0\"\n      )}\n    >\n      <Icon className=\"size-4 shrink-0\" />\n      {collapsed ? null : (\n        <>\n          <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n          {item.badge ? (\n            typeof item.badge === \"string\" || typeof item.badge === \"number\" ? (\n              <Badge variant=\"secondary\">{item.badge}</Badge>\n            ) : (\n              item.badge\n            )\n          ) : null}\n        </>\n      )}\n    </a>\n  )\n  if (!collapsed) return content\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>{content}</TooltipTrigger>\n      <TooltipContent side=\"right\">{item.label}</TooltipContent>\n    </Tooltip>\n  )\n}\n\nfunction QuickSearch({\n  collapsed,\n  onQuickSearch,\n}: {\n  collapsed: boolean\n  onQuickSearch?: () => void\n}) {\n  const button = (\n    <Button\n      type=\"button\"\n      variant=\"outline\"\n      size={collapsed ? \"icon-sm\" : \"sm\"}\n      className={cn(\"w-full\", !collapsed && \"justify-between\")}\n      aria-label=\"Quick Search\"\n      aria-keyshortcuts=\"Meta+L Control+L\"\n      onClick={onQuickSearch}\n    >\n      <Search className=\"size-3.5\" />\n      {collapsed ? null : (\n        <>\n          <span className=\"flex-1 text-left\">Quick Search</span>\n          <Kbd>⌘L</Kbd>\n        </>\n      )}\n    </Button>\n  )\n  if (!collapsed) return button\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>{button}</TooltipTrigger>\n      <TooltipContent side=\"right\">Quick Search</TooltipContent>\n    </Tooltip>\n  )\n}\n\nfunction QuickAction({\n  icon: Icon,\n  label,\n  collapsed,\n  onPress,\n  href,\n}: {\n  icon: SidebarIcon\n  label: string\n  collapsed: boolean\n  onPress?: () => void\n  href?: string\n}) {\n  const classes = cn(\n    \"flex h-7 items-center gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] font-medium text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.97] motion-reduce:transform-none\",\n    collapsed ? \"size-7 justify-center px-0\" : \"w-full justify-start\"\n  )\n  const content = (\n    <>\n      <Icon className=\"size-3.5\" />\n      {collapsed ? null : label}\n    </>\n  )\n  const button = href ? (\n    <a href={href} className={classes} aria-label={label}>\n      {content}\n    </a>\n  ) : (\n    <button\n      type=\"button\"\n      className={classes}\n      aria-label={label}\n      onClick={onPress}\n    >\n      {content}\n    </button>\n  )\n  if (!collapsed) return button\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>{button}</TooltipTrigger>\n      <TooltipContent side=\"right\">{label}</TooltipContent>\n    </Tooltip>\n  )\n}\n\nfunction FooterRow({\n  item,\n  collapsed,\n}: {\n  item: SidebarFooterItem\n  collapsed: boolean\n}) {\n  const Icon = item.id === \"settings\" ? Settings : LifeBuoy\n  const inner = (\n    <>\n      <Icon className=\"size-3.5\" />\n      {collapsed ? null : item.label}\n    </>\n  )\n  const classes = cn(\n    \"flex items-center gap-2 rounded-[2px] px-2 py-1.5 text-sm text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50\",\n    collapsed && \"justify-center px-0\"\n  )\n  const node = item.href ? (\n    <a\n      href={item.href}\n      aria-label={collapsed ? item.label : undefined}\n      className={classes}\n    >\n      {inner}\n    </a>\n  ) : (\n    <button\n      type=\"button\"\n      aria-label={collapsed ? item.label : undefined}\n      className={cn(classes, \"w-full text-left\")}\n      onClick={item.onPress}\n    >\n      {inner}\n    </button>\n  )\n  if (!collapsed) return node\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>{node}</TooltipTrigger>\n      <TooltipContent side=\"right\">{item.label}</TooltipContent>\n    </Tooltip>\n  )\n}\n\nfunction UserChip({\n  user,\n  collapsed,\n  onOpenSettings,\n}: {\n  user: SidebarUser\n  collapsed: boolean\n  onOpenSettings?: () => void\n}) {\n  const initials = user.initials ?? user.name.slice(0, 2).toUpperCase()\n  const avatar = (\n    <Avatar size=\"sm\">\n      {user.avatarSrc ? <AvatarImage src={user.avatarSrc} alt=\"\" /> : null}\n      <AvatarFallback>{initials}</AvatarFallback>\n    </Avatar>\n  )\n  if (!onOpenSettings) {\n    return (\n      <div\n        className={cn(\n          \"flex min-w-0 flex-1 items-center gap-2 px-1 py-1\",\n          collapsed && \"flex-none justify-center px-0\"\n        )}\n      >\n        {avatar}\n        {collapsed ? null : (\n          <span className=\"min-w-0 truncate text-sm font-medium\">\n            {user.name}\n          </span>\n        )}\n      </div>\n    )\n  }\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <button\n          type=\"button\"\n          className={cn(\n            \"flex min-w-0 flex-1 items-center gap-2 rounded-[2px] px-1 py-1 text-left outline-none hover:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50\",\n            collapsed && \"flex-none justify-center px-0\"\n          )}\n          aria-label={user.name}\n        >\n          {avatar}\n          {collapsed ? null : (\n            <span className=\"min-w-0 truncate text-sm font-medium\">\n              {user.name}\n            </span>\n          )}\n        </button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"start\">\n        <DropdownMenuItem disabled>{user.email ?? user.name}</DropdownMenuItem>\n        <DropdownMenuSeparator />\n        <DropdownMenuItem onSelect={onOpenSettings}>Settings</DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\nfunction TeamMenu({\n  team,\n  collapsed,\n}: {\n  team: SidebarTeam\n  collapsed: boolean\n}) {\n  const initials = team.initials ?? team.name.slice(0, 2).toUpperCase()\n  return (\n    <div\n      className={cn(\n        \"flex w-full items-center gap-2 rounded-[2px] border border-border bg-background px-2 py-1.5 text-left\",\n        collapsed && \"justify-center px-1\"\n      )}\n    >\n      <Avatar size=\"sm\">\n        {team.avatarSrc ? <AvatarImage src={team.avatarSrc} alt=\"\" /> : null}\n        <AvatarFallback>{initials}</AvatarFallback>\n      </Avatar>\n      {collapsed ? null : (\n        <span className=\"min-w-0 flex-1\">\n          <span className=\"block truncate text-sm font-medium\">\n            {team.name}\n          </span>\n          {team.email ? (\n            <span className=\"block truncate font-mono text-[11px] text-muted-foreground\">\n              {team.email}\n            </span>\n          ) : null}\n        </span>\n      )}\n    </div>\n  )\n}\n\nfunction RepositoryTree({\n  repositories,\n}: {\n  repositories: SidebarRepository[]\n}) {\n  return (\n    <div className=\"mt-3\">\n      <p className=\"px-2 pb-1 font-mono text-[11px] tracking-wide text-muted-foreground uppercase\">\n        Repositories\n      </p>\n      {repositories.map((repo) => (\n        <details key={repo.id} open className=\"px-1\">\n          <summary className=\"flex cursor-pointer list-none items-center gap-1 rounded-[2px] px-1 py-1 text-sm outline-none hover:bg-muted [&::-webkit-details-marker]:hidden\">\n            <GitBranch className=\"size-3.5 text-muted-foreground\" />\n            <span className=\"truncate\">{repo.name}</span>\n          </summary>\n          <ul className=\"ml-3 border-l border-border\">\n            {repo.chats.map((chat) => (\n              <li key={chat.id}>\n                <a\n                  href=\"#thread\"\n                  className=\"flex items-center justify-between gap-2 py-1 pr-1 pl-3 text-sm text-muted-foreground hover:text-foreground\"\n                >\n                  <span className=\"truncate\">{chat.title}</span>\n                  <Chip variant=\"outline\">{chat.relativeTime}</Chip>\n                </a>\n              </li>\n            ))}\n          </ul>\n        </details>\n      ))}\n    </div>\n  )\n}\n\nfunction PlanCard({\n  plan,\n}: {\n  plan?: { name: string; description: string; onUpgrade?: () => void }\n}) {\n  const name = plan?.name ?? \"Studio plan\"\n  const description = plan?.description ?? \"12k tokens left this cycle\"\n  return (\n    <div className=\"rounded-[2px] border border-border bg-background p-2\">\n      <p className=\"font-heading text-sm font-medium\">{name}</p>\n      <p className=\"text-xs text-muted-foreground\">{description}</p>\n      {plan?.onUpgrade ? (\n        <Button\n          type=\"button\"\n          size=\"sm\"\n          className=\"mt-2 w-full\"\n          onClick={plan.onUpgrade}\n        >\n          Upgrade\n        </Button>\n      ) : null}\n    </div>\n  )\n}\n\nfunction SidebarThemeToggle({ collapsed }: { collapsed: boolean }) {\n  const [theme, setTheme] = React.useState<\"light\" | \"dark\">(\"light\")\n\n  React.useEffect(() => {\n    setTheme(\n      document.documentElement.classList.contains(\"dark\") ? \"dark\" : \"light\"\n    )\n  }, [])\n\n  function toggle() {\n    const next = theme === \"dark\" ? \"light\" : \"dark\"\n    document.documentElement.classList.toggle(\"dark\", next === \"dark\")\n    try {\n      localStorage.setItem(\"components:theme\", next)\n    } catch {\n      /* Storage is optional. */\n    }\n    setTheme(next)\n  }\n\n  return (\n    <button\n      type=\"button\"\n      onClick={toggle}\n      aria-label={\n        theme === \"dark\" ? \"Switch to light theme\" : \"Switch to dark theme\"\n      }\n      aria-pressed={theme === \"dark\"}\n      className={cn(\n        \"flex min-h-9 items-center gap-2 rounded-[2px] px-2 text-sm text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50\",\n        collapsed ? \"size-9 justify-center px-0\" : \"w-full\"\n      )}\n    >\n      {theme === \"dark\" ? (\n        <Moon className=\"size-3.5\" />\n      ) : (\n        <Sun className=\"size-3.5\" />\n      )}\n      {collapsed ? null : <span>{theme === \"dark\" ? \"Dark\" : \"Light\"}</span>}\n    </button>\n  )\n}\n",
      "type": "registry:block"
    }
  ],
  "type": "registry:block"
}