{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "sidebar-02",
  "title": "Team Switcher Sidebar",
  "type": "registry:block",
  "description": "Full-featured app sidebar -- collapsible=\"icon\" rail with TeamSwitcher header, primary nav, projects section, secondary nav, and user dropdown footer. Each section ships as a sibling file in the same folder so consumers can edit one piece at a time.",
  "categories": [
    "layout",
    "sidebar"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/sidebar-02/sidebar-02.ts",
      "content": "import { LitElement, css, html } from 'lit'\nimport {\n  AudioWaveform,\n  BadgeCheck,\n  Bell,\n  BookOpen,\n  Bot,\n  Check,\n  ChevronRight,\n  ChevronsUpDown,\n  Command,\n  CreditCard,\n  Folder,\n  Forward,\n  Frame,\n  LifeBuoy,\n  LogOut,\n  Map,\n  Monitor,\n  Moon,\n  MoreHorizontal,\n  Palette,\n  PieChart,\n  Plus,\n  Send,\n  Settings2,\n  SquareTerminal,\n  Sun,\n  Trash2,\n  Zap,\n  type IconNode,\n} from 'lucide'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect imports: register the composed elements for standalone use\n// (consumers who install only this block still get working primitives).\nimport '../../components/avatar/avatar'\nimport '../../components/collapsible/collapsible'\nimport '../../components/dropdown-menu/dropdown-menu'\nimport '../../components/sidebar/sidebar'\n\ntype ThemeValue = 'light' | 'dark' | 'system'\n\nfunction getTheme(): ThemeValue {\n  try {\n    const stored = localStorage.getItem('uipkge-theme')\n    if (stored === 'light' || stored === 'dark' || stored === 'system') return stored\n  } catch {\n    // ignore\n  }\n  return document.documentElement.classList.contains('dark') ? 'dark' : 'light'\n}\n\nfunction setTheme(value: ThemeValue) {\n  try {\n    localStorage.setItem('uipkge-theme', value)\n  } catch {\n    // ignore\n  }\n  const dark = value === 'dark' || (value === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)\n  document.documentElement.classList.toggle('dark', dark)\n}\n\nfunction brandMark(classes: string) {\n  return html`<svg viewBox=\"0 0 24 24\" class=${classes} fill=\"currentColor\" aria-hidden=\"true\">\n    <rect x=\"2\" y=\"2\" width=\"9\" height=\"9\" rx=\"1.5\" />\n    <rect x=\"13\" y=\"2\" width=\"9\" height=\"9\" rx=\"1.5\" opacity=\"0.55\" />\n    <rect x=\"2\" y=\"13\" width=\"9\" height=\"9\" rx=\"1.5\" opacity=\"0.55\" />\n    <rect x=\"13\" y=\"13\" width=\"9\" height=\"9\" rx=\"1.5\" />\n  </svg>`\n}\n\ninterface Team {\n  name: string\n  logo: IconNode | 'mark'\n  plan: string\n}\n\nconst teams: Team[] = [\n  { name: 'uipkge', logo: 'mark', plan: 'UI registry' },\n  { name: 'uipkge HRMS', logo: AudioWaveform, plan: 'Vertical' },\n  { name: 'uipkge Hospital', logo: Command, plan: 'Vertical' },\n]\n\ninterface NavSubItem {\n  title: string\n  url: string\n  isActive?: boolean\n}\n\ninterface NavMainItem {\n  title: string\n  url: string\n  icon: IconNode\n  iconName: string\n  isActive?: boolean\n  items?: NavSubItem[]\n}\n\n// Sample data (React's `data`, inlined so consumers edit routes in place).\nconst navMain: NavMainItem[] = [\n  {\n    title: 'Playground',\n    url: '#',\n    icon: SquareTerminal,\n    iconName: 'square-terminal',\n    isActive: true,\n    items: [\n      { title: 'History', url: '#' },\n      { title: 'Starred', url: '#' },\n      { title: 'Settings', url: '#' },\n    ],\n  },\n  {\n    title: 'Models',\n    url: '#',\n    icon: Bot,\n    iconName: 'bot',\n    items: [\n      { title: 'Genesis', url: '#' },\n      { title: 'Explorer', url: '#' },\n      { title: 'Quantum', url: '#' },\n    ],\n  },\n  {\n    title: 'Documentation',\n    url: '#',\n    icon: BookOpen,\n    iconName: 'book-open',\n    items: [\n      { title: 'Introduction', url: '#' },\n      { title: 'Get Started', url: '#' },\n      { title: 'Tutorials', url: '#' },\n      { title: 'Changelog', url: '#' },\n    ],\n  },\n  {\n    title: 'Settings',\n    url: '#',\n    icon: Settings2,\n    iconName: 'settings-2',\n    items: [\n      { title: 'General', url: '#' },\n      { title: 'Team', url: '#' },\n      { title: 'Billing', url: '#' },\n      { title: 'Limits', url: '#' },\n    ],\n  },\n]\n\nconst navSecondary: { title: string; url: string; icon: IconNode; iconName: string; isActive?: boolean }[] = [\n  { title: 'Support', url: '#', icon: LifeBuoy, iconName: 'life-buoy' },\n  { title: 'Feedback', url: '#', icon: Send, iconName: 'send' },\n]\n\nconst projects = [\n  { name: 'Design Engineering', url: '#', icon: Frame, iconName: 'frame' },\n  { name: 'Sales & Marketing', url: '#', icon: PieChart, iconName: 'chart-pie' },\n  { name: 'Travel', url: '#', icon: Map, iconName: 'map' },\n]\n\nconst user = { name: 'shadcn', email: 'm@example.com' }\n\n/**\n * <uip-sidebar-02> — the registry Sidebar02 block as a web component\n * (TeamSwitcher + NavMain + NavProjects + NavSecondary + NavUser inlined —\n * one block, one install unit).\n *\n * Class strings are React's verbatim. Sample data is inlined so consumers\n * edit routes in place; edit teams + activeTeam below to match your tenant\n * model. The theme submenu writes the `dark` class on documentElement\n * directly (the `next-themes` dependency is not ported — same visual result,\n * persisted to localStorage). No props, no events.\n */\nexport class UipSidebar02 extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: block; }`]\n\n  static properties = {\n    activeTeam: { state: true },\n    theme: { state: true },\n  }\n\n  private activeTeam: Team = teams[0]!\n  private theme: ThemeValue = 'light'\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'sidebar-02')\n    this.theme = getTheme()\n  }\n\n  private get initials() {\n    const parts = user.name.trim().split(/\\s+/).slice(0, 2)\n    return parts.map((p) => p[0]?.toUpperCase()).join('') || 'U'\n  }\n\n  render() {\n    const ActiveLogo = this.activeTeam.logo\n    return html`\n      <uip-sidebar collapsible=\"icon\">\n        <uip-sidebar-header>\n          <uip-sidebar-menu>\n            <uip-sidebar-menu-item>\n              <uip-dropdown-menu class=\"[&::part(content)]:min-w-56 [&::part(content)]:rounded-lg\">\n                <uip-sidebar-menu-button\n                  slot=\"trigger\"\n                  size=\"lg\"\n                  class=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center\"\n                >\n                  <div class=\"bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg group-data-[collapsible=icon]:size-6\">\n                    ${ActiveLogo === 'mark'\n                      ? brandMark('size-4 group-data-[collapsible=icon]:size-3.5')\n                      : icon(ActiveLogo as IconNode, 'team', 'size-4 group-data-[collapsible=icon]:size-3.5')}\n                  </div>\n                  <div class=\"grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden\">\n                    <span class=\"font-display truncate font-bold\">${this.activeTeam.name}</span>\n                    <span class=\"text-muted-foreground truncate text-xs\">${this.activeTeam.plan}</span>\n                  </div>\n                  ${icon(ChevronsUpDown, 'chevrons-up-down', 'ml-auto size-4 group-data-[collapsible=icon]:hidden')}\n                </uip-sidebar-menu-button>\n                <uip-dropdown-menu-content side=\"right\" align=\"start\" side-offset=\"4\">\n                  <uip-dropdown-menu-label class=\"text-muted-foreground text-xs\">Teams</uip-dropdown-menu-label>\n                  ${teams.map(\n                    (team, i) => html`\n                      <uip-dropdown-menu-item\n                        @select=${() => {\n                          this.activeTeam = team\n                        }}\n                      >\n                        <div class=\"flex size-6 items-center justify-center rounded-sm border\">\n                          ${team.logo === 'mark' ? brandMark('size-3.5 shrink-0') : icon(team.logo as IconNode, 'team', 'size-3.5 shrink-0')}\n                        </div>\n                        ${team.name}\n                        ${this.activeTeam === team\n                          ? icon(Check, 'check', 'ml-auto size-4')\n                          : html`<uip-dropdown-menu-shortcut>⌘${i + 1}</uip-dropdown-menu-shortcut>`}\n                      </uip-dropdown-menu-item>\n                    `,\n                  )}\n                  <uip-dropdown-menu-separator></uip-dropdown-menu-separator>\n                  <uip-dropdown-menu-item>\n                    <div class=\"bg-background flex size-6 items-center justify-center rounded-md border\">\n                      ${icon(Plus, 'plus', 'size-4')}\n                    </div>\n                    <div class=\"text-muted-foreground font-medium\">Add team</div>\n                  </uip-dropdown-menu-item>\n                </uip-dropdown-menu-content>\n              </uip-dropdown-menu>\n            </uip-sidebar-menu-item>\n          </uip-sidebar-menu>\n        </uip-sidebar-header>\n\n        <uip-sidebar-content>\n          <uip-sidebar-group>\n            <uip-sidebar-group-label>Platform</uip-sidebar-group-label>\n            <uip-sidebar-menu>\n              ${navMain.map(\n                (item) => html`\n                  <uip-collapsible class=\"contents\" ?default-open=${!!item.isActive}>\n                    <uip-sidebar-menu-item>\n                      <uip-sidebar-menu-button href=${item.url} tooltip=${item.title} ?is-active=${!!item.isActive}>\n                        ${icon(item.icon, item.iconName)} <span>${item.title}</span>\n                      </uip-sidebar-menu-button>\n                      ${item.items?.length\n                        ? html`\n                            <uip-collapsible-trigger as-child>\n                              <uip-sidebar-menu-action show-on-hover aria-label=\"Toggle\">\n                                ${icon(ChevronRight, 'chevron-right')}\n                                <span class=\"sr-only\">Toggle</span>\n                              </uip-sidebar-menu-action>\n                            </uip-collapsible-trigger>\n                            <uip-collapsible-content>\n                              <uip-sidebar-menu-sub>\n                                ${item.items.map(\n                                  (subItem) => html`\n                                    <uip-sidebar-menu-sub-item>\n                                      <uip-sidebar-menu-sub-button href=${subItem.url} ?is-active=${!!subItem.isActive}>\n                                        <span>${subItem.title}</span>\n                                      </uip-sidebar-menu-sub-button>\n                                    </uip-sidebar-menu-sub-item>\n                                  `,\n                                )}\n                              </uip-sidebar-menu-sub>\n                            </uip-collapsible-content>\n                          `\n                        : ''}\n                    </uip-sidebar-menu-item>\n                  </uip-collapsible>\n                `,\n              )}\n            </uip-sidebar-menu>\n          </uip-sidebar-group>\n\n          <uip-sidebar-group>\n            <uip-sidebar-group-label>Projects</uip-sidebar-group-label>\n            <uip-sidebar-menu>\n              ${projects.map(\n                (item) => html`\n                  <uip-sidebar-menu-item>\n                    <uip-sidebar-menu-button href=${item.url}>\n                      ${icon(item.icon, item.iconName)} <span>${item.name}</span>\n                    </uip-sidebar-menu-button>\n                    <uip-dropdown-menu class=\"[&::part(content)]:w-48 [&::part(content)]:rounded-lg\">\n                      <uip-sidebar-menu-action slot=\"trigger\" show-on-hover aria-label=\"More\">\n                        ${icon(MoreHorizontal, 'ellipsis')} <span class=\"sr-only\">More</span>\n                      </uip-sidebar-menu-action>\n                      <uip-dropdown-menu-content side=\"right\" align=\"start\">\n                        <uip-dropdown-menu-item>\n                          ${icon(Folder, 'folder', 'text-muted-foreground')} <span>View Project</span>\n                        </uip-dropdown-menu-item>\n                        <uip-dropdown-menu-item>\n                          ${icon(Forward, 'forward', 'text-muted-foreground')} <span>Share Project</span>\n                        </uip-dropdown-menu-item>\n                        <uip-dropdown-menu-separator></uip-dropdown-menu-separator>\n                        <uip-dropdown-menu-item>\n                          ${icon(Trash2, 'trash-2', 'text-muted-foreground')} <span>Delete Project</span>\n                        </uip-dropdown-menu-item>\n                      </uip-dropdown-menu-content>\n                    </uip-dropdown-menu>\n                  </uip-sidebar-menu-item>\n                `,\n              )}\n              <uip-sidebar-menu-item>\n                <uip-sidebar-menu-button>\n                  ${icon(MoreHorizontal, 'ellipsis')} <span>More</span>\n                </uip-sidebar-menu-button>\n              </uip-sidebar-menu-item>\n            </uip-sidebar-menu>\n          </uip-sidebar-group>\n\n          <uip-sidebar-group class=\"[&::part(base)]:mt-auto\">\n            <uip-sidebar-group-content>\n              <uip-sidebar-menu>\n                ${navSecondary.map(\n                  (item) => html`\n                    <uip-sidebar-menu-item>\n                      <uip-sidebar-menu-button href=${item.url} size=\"sm\" ?is-active=${!!item.isActive}>\n                        ${icon(item.icon, item.iconName)} <span>${item.title}</span>\n                      </uip-sidebar-menu-button>\n                    </uip-sidebar-menu-item>\n                  `,\n                )}\n              </uip-sidebar-menu>\n            </uip-sidebar-group-content>\n          </uip-sidebar-group>\n        </uip-sidebar-content>\n\n        <uip-sidebar-footer>\n          <uip-sidebar-menu>\n            <uip-sidebar-menu-item>\n              <uip-dropdown-menu class=\"[&::part(content)]:min-w-56 [&::part(content)]:rounded-lg\">\n                <uip-sidebar-menu-button\n                  slot=\"trigger\"\n                  size=\"lg\"\n                  class=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center\"\n                >\n                  <uip-avatar class=\"h-8 w-8 shrink-0 rounded-lg group-data-[collapsible=icon]:size-6\">\n                    <uip-avatar-fallback class=\"rounded-lg text-xs\">${this.initials}</uip-avatar-fallback>\n                  </uip-avatar>\n                  <div class=\"grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden\">\n                    <span class=\"truncate font-medium\">${user.name}</span>\n                    <span class=\"truncate text-xs\">${user.email}</span>\n                  </div>\n                  ${icon(ChevronsUpDown, 'chevrons-up-down', 'ml-auto size-4 group-data-[collapsible=icon]:hidden')}\n                </uip-sidebar-menu-button>\n                <uip-dropdown-menu-content side=\"right\" align=\"end\" side-offset=\"4\">\n                  <uip-dropdown-menu-label>\n                    <div class=\"flex items-center gap-2 px-1 py-1.5 text-left text-sm\">\n                      <uip-avatar class=\"h-8 w-8 rounded-lg\">\n                        <uip-avatar-fallback class=\"rounded-lg text-xs\">${this.initials}</uip-avatar-fallback>\n                      </uip-avatar>\n                      <div class=\"grid flex-1 text-left text-sm leading-tight\">\n                        <span class=\"truncate font-semibold\">${user.name}</span>\n                        <span class=\"truncate text-xs\">${user.email}</span>\n                      </div>\n                    </div>\n                  </uip-dropdown-menu-label>\n                  <uip-dropdown-menu-separator></uip-dropdown-menu-separator>\n                  <uip-dropdown-menu-group>\n                    <uip-dropdown-menu-item>${icon(Zap, 'zap')} Upgrade to Pro</uip-dropdown-menu-item>\n                  </uip-dropdown-menu-group>\n                  <uip-dropdown-menu-separator></uip-dropdown-menu-separator>\n                  <uip-dropdown-menu-group>\n                    <uip-dropdown-menu-item>${icon(BadgeCheck, 'badge-check')} Account</uip-dropdown-menu-item>\n                    <uip-dropdown-menu-item>${icon(CreditCard, 'credit-card')} Billing</uip-dropdown-menu-item>\n                    <uip-dropdown-menu-item>${icon(Bell, 'bell')} Notifications</uip-dropdown-menu-item>\n                  </uip-dropdown-menu-group>\n                  <uip-dropdown-menu-separator></uip-dropdown-menu-separator>\n                  <uip-dropdown-menu-sub>\n                    <uip-dropdown-menu-sub-trigger>\n                      ${icon(Palette, 'palette')} Theme\n                      <span class=\"text-muted-foreground ml-auto text-xs capitalize\">${this.theme}</span>\n                    </uip-dropdown-menu-sub-trigger>\n                    <uip-dropdown-menu-sub-content>\n                      <uip-dropdown-menu-radio-group\n                        value=${this.theme}\n                        @value-change=${(e: CustomEvent<{ value: string }>) => {\n                          this.theme = e.detail.value as ThemeValue\n                          setTheme(this.theme)\n                        }}\n                      >\n                        <uip-dropdown-menu-radio-item value=\"light\">${icon(Sun, 'sun')} Light</uip-dropdown-menu-radio-item>\n                        <uip-dropdown-menu-radio-item value=\"dark\">${icon(Moon, 'moon')} Dark</uip-dropdown-menu-radio-item>\n                        <uip-dropdown-menu-radio-item value=\"system\">${icon(Monitor, 'monitor')} System</uip-dropdown-menu-radio-item>\n                      </uip-dropdown-menu-radio-group>\n                    </uip-dropdown-menu-sub-content>\n                  </uip-dropdown-menu-sub>\n                  <uip-dropdown-menu-separator></uip-dropdown-menu-separator>\n                  <uip-dropdown-menu-item>${icon(LogOut, 'log-out')} Log out</uip-dropdown-menu-item>\n                </uip-dropdown-menu-content>\n              </uip-dropdown-menu>\n            </uip-sidebar-menu-item>\n          </uip-sidebar-menu>\n        </uip-sidebar-footer>\n\n        <uip-sidebar-rail></uip-sidebar-rail>\n      </uip-sidebar>\n    `\n  }\n}\n\ncustomElements.get('uip-sidebar-02') || customElements.define('uip-sidebar-02', UipSidebar02)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-sidebar-02': UipSidebar02\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/sidebar-02/sidebar-02.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/avatar.json",
    "https://uipkge.dev/r/lit/collapsible.json",
    "https://uipkge.dev/r/lit/dropdown-menu.json",
    "https://uipkge.dev/r/lit/lit-core.json",
    "https://uipkge.dev/r/lit/sidebar.json"
  ]
}
