{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "sidebar",
  "title": "Sidebar",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/sidebar/sidebar.component.ts",
      "content": "import {\n  type AfterViewInit,\n  Component,\n  DestroyRef,\n  ElementRef,\n  EventEmitter,\n  Input,\n  OnInit,\n  Output,\n  booleanAttribute,\n  inject,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { buttonVariants } from '@/ui/button/button.variants'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport {\n  UiSheetComponent,\n  UiSheetContentComponent,\n  UiSheetDescriptionComponent,\n  UiSheetHeaderComponent,\n  UiSheetTitleComponent,\n} from '@/ui/sheet/sheet.component'\nimport { UiSkeletonComponent } from '@/ui/skeleton/skeleton.component'\nimport { UiRenderTemplateDirective } from '@/ui/popper/popper'\nimport { UI_TOOLTIP_CONFIG, UiTooltipDirective } from '@/ui/tooltip/tooltip.component'\nimport { sidebarMenuButtonVariants } from './sidebar.variants'\nimport {\n  SIDEBAR_COOKIE_MAX_AGE,\n  SIDEBAR_COOKIE_NAME,\n  SIDEBAR_KEYBOARD_SHORTCUT,\n  SIDEBAR_WIDTH,\n  SIDEBAR_WIDTH_ICON,\n  SIDEBAR_WIDTH_MOBILE,\n} from './sidebar-context'\nimport type { SidebarCollapsible, SidebarSide, SidebarState, SidebarVariant } from './sidebar-context'\n\n/**\n * Angular port of the UIPKGE Sidebar, 1:1 with the React / Vue primitive:\n * - SidebarProvider owns the state and shares it through DI (`injectSidebar()` is\n *   React's `useSidebar()`): open / state / isMobile / openMobile / setOpen /\n *   setOpenMobile / toggleSidebar, Cmd/Ctrl+B, the `sidebar_state` cookie, and a\n *   0ms tooltip delay for everything inside.\n * - Sidebar renders the desktop gap + fixed rail (offcanvas / icon / none), or a\n *   Sheet below 768px -- exactly the React markup, so the same Tailwind classes apply.\n * - Trigger / Rail toggle the provider; MenuButton shows its tooltip only on the\n *   collapsed icon rail.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-provider, [ui-sidebar-provider]',\n  standalone: true,\n  providers: [\n    {\n      provide: UI_TOOLTIP_CONFIG,\n      useValue: { delayDuration: 0, skipDelayDuration: 300, disableHoverableContent: false },\n    },\n  ],\n  host: {\n    '[attr.data-slot]': '\"sidebar-wrapper\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[style.--sidebar-width]': 'sidebarWidth',\n    '[style.--sidebar-width-icon]': 'sidebarWidthIcon',\n    '[class]': 'hostClass',\n    '(window:keydown)': 'onKeydown($event)',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarProviderComponent implements OnInit {\n  @Input() defaultOpen = true\n  /** Controlled open state (pair with openChange); omit for uncontrolled. */\n  @Input('open') controlledOpen?: boolean\n  @Output() openChange = new EventEmitter<boolean>()\n  @Input('class') className?: string\n\n  readonly sidebarWidth = SIDEBAR_WIDTH\n  readonly sidebarWidthIcon = SIDEBAR_WIDTH_ICON\n  private readonly _open = signal(true)\n  private readonly _isMobile = signal(false)\n  private readonly _openMobile = signal(false)\n\n  constructor() {\n    // Viewport detection runs client-side only (SSR renders the desktop branch, as React does).\n    if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n      const mql = window.matchMedia('(max-width: 768px)')\n      const onChange = () => this._isMobile.set(mql.matches)\n      onChange()\n      mql.addEventListener('change', onChange)\n      inject(DestroyRef).onDestroy(() => mql.removeEventListener('change', onChange))\n    }\n  }\n\n  ngOnInit(): void {\n    this._open.set(this.defaultOpen)\n  }\n\n  get open(): boolean {\n    return this.controlledOpen ?? this._open()\n  }\n\n  get state(): SidebarState {\n    return this.open ? 'expanded' : 'collapsed'\n  }\n\n  get isMobile(): boolean {\n    return this._isMobile()\n  }\n\n  get openMobile(): boolean {\n    return this._openMobile()\n  }\n\n  get hostClass(): string {\n    return cn('group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full', this.className)\n  }\n\n  setOpen(value: boolean): void {\n    if (this.controlledOpen === undefined) this._open.set(value)\n    this.openChange.emit(value)\n    // Persist like React / Vue so server renders can restore it.\n    if (typeof document !== 'undefined') {\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${value}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`\n    }\n  }\n\n  setOpenMobile(value: boolean): void {\n    this._openMobile.set(value)\n  }\n\n  toggleSidebar(): void {\n    if (this.isMobile) this.setOpenMobile(!this.openMobile)\n    else this.setOpen(!this.open)\n  }\n\n  matchesShortcut(event: { key: string; metaKey?: boolean; ctrlKey?: boolean }): boolean {\n    return event.key === SIDEBAR_KEYBOARD_SHORTCUT && !!(event.metaKey || event.ctrlKey)\n  }\n\n  onKeydown(event: KeyboardEvent): void {\n    if (this.matchesShortcut(event)) {\n      event.preventDefault()\n      this.toggleSidebar()\n    }\n  }\n}\n\n/** React's `useSidebar()`: the nearest provider, or a clear error when there is none. */\nexport function injectSidebar(): UiSidebarProviderComponent {\n  const sidebar = inject(UiSidebarProviderComponent, { optional: true })\n  if (!sidebar) throw new Error('injectSidebar() must be used within a <ui-sidebar-provider>.')\n  return sidebar\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar, [ui-sidebar]',\n  standalone: true,\n  imports: [\n    UiRenderTemplateDirective,\n    UiSheetComponent,\n    UiSheetContentComponent,\n    UiSheetHeaderComponent,\n    UiSheetTitleComponent,\n    UiSheetDescriptionComponent,\n  ],\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': 'mode === \"mobile\" ? null : \"sidebar\"',\n    '[attr.data-state]': 'mode === \"desktop\" ? sidebar.state : null',\n    '[attr.data-collapsible]': 'mode === \"desktop\" ? (sidebar.state === \"collapsed\" ? collapsible : \"\") : null',\n    '[attr.data-variant]': 'mode === \"desktop\" ? variant : null',\n    '[attr.data-side]': 'mode === \"desktop\" ? side : null',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <ng-template #body><ng-content /></ng-template>\n    @switch (mode) {\n      @case ('none') {\n        <ng-container [uiRenderTemplate]=\"body\" />\n      }\n      @case ('mobile') {\n        <ui-sheet [open]=\"sidebar.openMobile\" (openChange)=\"sidebar.setOpenMobile($event)\">\n          <ui-sheet-content\n            [side]=\"side\"\n            [attributes]=\"mobileAttributes\"\n            class=\"bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden\"\n          >\n            <ui-sheet-header class=\"sr-only\">\n              <ui-sheet-title>Sidebar</ui-sheet-title>\n              <ui-sheet-description>Displays the mobile sidebar.</ui-sheet-description>\n            </ui-sheet-header>\n            <div class=\"flex h-full w-full flex-col\"><ng-container [uiRenderTemplate]=\"body\" /></div>\n          </ui-sheet-content>\n        </ui-sheet>\n      }\n      @default {\n        <!-- This is what handles the sidebar gap on desktop -->\n        <div [class]=\"gapClass\"></div>\n        <div [class]=\"containerClass\">\n          <div\n            data-sidebar=\"sidebar\"\n            class=\"bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm\"\n          >\n            <ng-container [uiRenderTemplate]=\"body\" />\n          </div>\n        </div>\n      }\n    }\n  `,\n})\nexport class UiSidebarComponent implements AfterViewInit {\n  readonly sidebar = injectSidebar()\n  private readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  /** Static `class=\"...\"` tokens from the template (React's className goes to the inner container). */\n  private readonly staticClasses = [...this.el.classList]\n  @Input() side: SidebarSide = 'left'\n  @Input() variant: SidebarVariant = 'sidebar'\n  @Input() collapsible: SidebarCollapsible = 'offcanvas'\n  @Input('class') className?: string\n\n  readonly mobileAttributes = {\n    'data-sidebar': 'sidebar',\n    'data-slot': 'sidebar',\n    'data-mobile': 'true',\n    style: `--sidebar-width: ${SIDEBAR_WIDTH_MOBILE}`,\n  }\n\n  /**\n   * Angular always applies a static class attribute to the host; React only puts className on\n   * the fixed container (desktop) or the sheet (mobile). Drop the static tokens the host class\n   * does not contain after the first render -- later host-class diffs keep it that way.\n   */\n  ngAfterViewInit(): void {\n    const keep = new Set(this.hostClass.split(/\\s+/))\n    for (const token of this.staticClasses) if (!keep.has(token)) this.el.classList.remove(token)\n  }\n\n  get mode(): 'none' | 'mobile' | 'desktop' {\n    if (this.collapsible === 'none') return 'none'\n    return this.sidebar.isMobile ? 'mobile' : 'desktop'\n  }\n\n  get hostClass(): string {\n    if (this.mode === 'none')\n      return cn('bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col', this.className)\n    // Mobile renders only the portalled Sheet, like React: the host takes no space.\n    if (this.mode === 'mobile') return 'contents'\n    return 'group peer text-sidebar-foreground hidden md:block'\n  }\n\n  get gapClass(): string {\n    return cn(\n      'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n      'group-data-[collapsible=offcanvas]:w-0',\n      'group-data-[side=right]:rotate-180',\n      this.variant === 'floating' || this.variant === 'inset'\n        ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n        : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',\n    )\n  }\n\n  get containerClass(): string {\n    return cn(\n      'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',\n      this.side === 'left'\n        ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'\n        : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',\n      // Adjust the padding for floating and inset variants.\n      this.variant === 'floating' || this.variant === 'inset'\n        ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n        : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n      this.className,\n    )\n  }\n}\n\n/** Native buttons/anchors keep their semantics; custom-element hosts get button semantics. */\nfunction buttonSemantics(el: HTMLElement): { role: string | null; tabindex: string | null } {\n  const native = el.tagName === 'BUTTON' || el.tagName === 'A'\n  return { role: native ? null : 'button', tabindex: native ? null : '0' }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-trigger, [ui-sidebar-trigger]',\n  standalone: true,\n  host: {\n    '[attr.data-sidebar]': '\"trigger\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-trigger\"',\n    '[attr.role]': 'semantics.role',\n    '[attr.tabindex]': 'semantics.tabindex',\n    '[attr.type]': 'isNativeButton ? \"button\" : null',\n    '[class]': 'hostClass',\n    '(click)': 'onToggle()',\n    '(keydown.enter)': 'onKey($event)',\n    '(keydown.space)': 'onKey($event)',\n  },\n  template: `\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      stroke-width=\"2\"\n      stroke-linecap=\"round\"\n      stroke-linejoin=\"round\"\n      aria-hidden=\"true\"\n    >\n      <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" />\n      <path d=\"M9 3v18\" />\n    </svg>\n    <span class=\"sr-only\">Toggle Sidebar</span>\n    <ng-content />\n  `,\n})\nexport class UiSidebarTriggerComponent {\n  private readonly sidebar = injectSidebar()\n  private readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  readonly semantics = buttonSemantics(this.el)\n  readonly isNativeButton = this.el.tagName === 'BUTTON'\n  @Input('class') className?: string\n  @Output() toggle = new EventEmitter<void>()\n\n  get hostClass(): string {\n    return cn(\n      buttonVariants({ variant: 'ghost', size: 'icon' }),\n      'focus-visible:ring-ring h-7 w-7 focus-visible:ring-2 focus-visible:outline-none',\n      this.className,\n    )\n  }\n\n  onToggle(): void {\n    this.toggle.emit()\n    this.sidebar.toggleSidebar()\n  }\n\n  onKey(event: Event): void {\n    if (this.isNativeButton) return\n    event.preventDefault()\n    this.onToggle()\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-rail, [ui-sidebar-rail]',\n  standalone: true,\n  host: {\n    '[attr.data-sidebar]': '\"rail\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-rail\"',\n    '[attr.aria-label]': '\"Toggle Sidebar\"',\n    '[attr.title]': '\"Toggle Sidebar\"',\n    '[attr.tabindex]': '\"-1\"',\n    '[attr.role]': 'isNativeButton ? null : \"button\"',\n    '[attr.type]': 'isNativeButton ? \"button\" : null',\n    '[class]': 'hostClass',\n    '(click)': 'onToggle()',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarRailComponent {\n  private readonly sidebar = injectSidebar()\n  readonly isNativeButton = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement.tagName === 'BUTTON'\n  @Input('class') className?: string\n  @Output() toggle = new EventEmitter<void>()\n\n  get hostClass(): string {\n    return cn(\n      'hover:after:bg-sidebar-border focus-visible:ring-ring absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-colors duration-200 ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] focus-visible:ring-2 focus-visible:outline-none 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      'hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',\n      '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n      '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n      this.className,\n    )\n  }\n\n  onToggle(): void {\n    this.toggle.emit()\n    this.sidebar.toggleSidebar()\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-inset, [ui-sidebar-inset]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-inset\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarInsetComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn(\n      // min-w-0 is load-bearing: a flex-1 child without it inherits min-width: auto,\n      // which means any single wide descendant (chart, table, code block) blows the\n      // inset's width past its grid track. The upstream shadcn-ui sidebar omits this.\n      'bg-background relative flex w-full min-w-0 flex-1 flex-col',\n      'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',\n      this.className,\n    )\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-input, [ui-sidebar-input]',\n  standalone: true,\n  imports: [UiInputComponent],\n  host: { class: 'contents' },\n  template: `\n    <ui-input\n      data-uipkge=\"\"\n      data-slot=\"sidebar-input\"\n      data-sidebar=\"input\"\n      [value]=\"value\"\n      [placeholder]=\"placeholder\"\n      [class]=\"inputClass\"\n      (valueChange)=\"valueChange.emit($event)\"\n    />\n  `,\n})\nexport class UiSidebarInputComponent {\n  @Input() value?: string\n  @Input() placeholder = ''\n  @Input('class') className?: string\n  @Output() valueChange = new EventEmitter<string>()\n\n  get inputClass(): string {\n    return cn('bg-background h-8 w-full shadow-none', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-header, [ui-sidebar-header]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-header\"',\n    '[attr.data-sidebar]': '\"header\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarHeaderComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('flex flex-col gap-2 p-2', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-footer, [ui-sidebar-footer]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-footer\"',\n    '[attr.data-sidebar]': '\"footer\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarFooterComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('flex flex-col gap-2 p-2', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-separator, [ui-sidebar-separator]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-separator\"',\n    '[attr.data-sidebar]': '\"separator\"',\n    '[attr.role]': '\"separator\"',\n    '[attr.data-orientation]': '\"horizontal\"',\n    '[class]': 'hostClass',\n  },\n  template: ``,\n})\nexport class UiSidebarSeparatorComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    // The React part wraps <Separator>; same base classes + the sidebar overrides.\n    return cn(\n      'bg-border block shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',\n      'bg-sidebar-border mx-2 w-auto',\n      this.className,\n    )\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-content, [ui-sidebar-content]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-content\"',\n    '[attr.data-sidebar]': '\"content\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarContentComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn(\n      'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n      this.className,\n    )\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-group, [ui-sidebar-group]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-group\"',\n    '[attr.data-sidebar]': '\"group\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarGroupComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('relative flex w-full min-w-0 flex-col p-2', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-group-label, [ui-sidebar-group-label]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-group-label\"',\n    '[attr.data-sidebar]': '\"group-label\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarGroupLabelComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn(\n      'text-sidebar-foreground/70 ring-sidebar-ring mt-2 mb-1 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg,&>lucide-icon>svg]:size-4 [&>svg,&>lucide-icon>svg]:shrink-0',\n      'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',\n      this.className,\n    )\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-group-action, [ui-sidebar-group-action]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-group-action\"',\n    '[attr.data-sidebar]': '\"group-action\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarGroupActionComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn(\n      'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg,&>lucide-icon>svg]:size-4 [&>svg,&>lucide-icon>svg]:shrink-0',\n      'after:absolute after:-inset-2 md:after:hidden',\n      'group-data-[collapsible=icon]:hidden',\n      this.className,\n    )\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-group-content, [ui-sidebar-group-content]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-group-content\"',\n    '[attr.data-sidebar]': '\"group-content\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarGroupContentComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('block w-full text-sm', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-menu, [ui-sidebar-menu]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-menu\"',\n    '[attr.data-sidebar]': '\"menu\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarMenuComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('flex w-full min-w-0 flex-col gap-1', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-menu-item, [ui-sidebar-menu-item]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-menu-item\"',\n    '[attr.data-sidebar]': '\"menu-item\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarMenuItemComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('group/menu-item relative block', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-menu-button, [ui-sidebar-menu-button]',\n  standalone: true,\n  // React's `tooltip` prop: a right-side tooltip shown only on the collapsed icon rail.\n  hostDirectives: [{ directive: UiTooltipDirective, inputs: ['uiTooltip: tooltip'] }],\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-menu-button\"',\n    '[attr.data-sidebar]': '\"menu-button\"',\n    '[attr.data-size]': 'size',\n    '[attr.data-active]': 'isActive',\n    '[attr.role]': 'semantics.role',\n    '[attr.tabindex]': 'semantics.tabindex',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarMenuButtonComponent {\n  private readonly sidebar = injectSidebar()\n  readonly semantics = buttonSemantics(inject<ElementRef<HTMLElement>>(ElementRef).nativeElement)\n  @Input() variant: 'default' | 'outline' = 'default'\n  @Input() size: 'default' | 'sm' | 'lg' = 'default'\n  @Input({ transform: booleanAttribute }) isActive = false\n  @Input('class') className?: string\n\n  constructor() {\n    const tooltip = inject(UiTooltipDirective, { self: true })\n    tooltip.tooltipSide = 'right'\n    tooltip.tooltipAlign = 'center'\n    tooltip.disabledWhen = () => this.sidebar.state !== 'collapsed' || this.sidebar.isMobile\n  }\n\n  get hostClass(): string {\n    return cn(sidebarMenuButtonVariants({ variant: this.variant, size: this.size }), this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-menu-action, [ui-sidebar-menu-action]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-menu-action\"',\n    '[attr.data-sidebar]': '\"menu-action\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarMenuActionComponent {\n  @Input({ transform: booleanAttribute }) showOnHover = false\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn(\n      'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg,&>lucide-icon>svg]:size-4 [&>svg,&>lucide-icon>svg]:shrink-0',\n      'after:absolute after:-inset-2 md:after:hidden',\n      'peer-data-[size=sm]/menu-button:top-1',\n      'peer-data-[size=default]/menu-button:top-1.5',\n      'peer-data-[size=lg]/menu-button:top-2.5',\n      'group-data-[collapsible=icon]:hidden',\n      this.showOnHover &&\n        'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',\n      this.className,\n    )\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-menu-badge, [ui-sidebar-menu-badge]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-menu-badge\"',\n    '[attr.data-sidebar]': '\"menu-badge\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarMenuBadgeComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn(\n      'text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none',\n      'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',\n      'peer-data-[size=sm]/menu-button:top-1',\n      'peer-data-[size=default]/menu-button:top-1.5',\n      'peer-data-[size=lg]/menu-button:top-2.5',\n      'group-data-[collapsible=icon]:hidden',\n      this.className,\n    )\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-menu-skeleton, [ui-sidebar-menu-skeleton]',\n  standalone: true,\n  imports: [UiSkeletonComponent],\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-menu-skeleton\"',\n    '[attr.data-sidebar]': '\"menu-skeleton\"',\n    '[class]': 'hostClass',\n  },\n  template: `\n    @if (showIcon) {\n      <ui-skeleton class=\"size-4 rounded-md\" data-sidebar=\"menu-skeleton-icon\" />\n    }\n    <ui-skeleton\n      class=\"h-4 max-w-(--skeleton-width) flex-1\"\n      data-sidebar=\"menu-skeleton-text\"\n      [style.--skeleton-width]=\"width\"\n    />\n  `,\n})\nexport class UiSidebarMenuSkeletonComponent {\n  @Input({ transform: booleanAttribute }) showIcon = false\n  @Input() width = '70%'\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('flex h-8 items-center gap-2 rounded-md px-2', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-menu-sub, [ui-sidebar-menu-sub]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-menu-sub\"',\n    '[attr.data-sidebar]': '\"menu-badge\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarMenuSubComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn(\n      'border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5',\n      'group-data-[collapsible=icon]:hidden',\n      this.className,\n    )\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-menu-sub-item, [ui-sidebar-menu-sub-item]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-menu-sub-item\"',\n    '[attr.data-sidebar]': '\"menu-sub-item\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarMenuSubItemComponent {\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn('group/menu-sub-item relative block', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-sidebar-menu-sub-button, [ui-sidebar-menu-sub-button]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"sidebar-menu-sub-button\"',\n    '[attr.data-sidebar]': '\"menu-sub-button\"',\n    '[attr.data-size]': 'size',\n    '[attr.data-active]': 'isActive',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiSidebarMenuSubButtonComponent {\n  @Input() size: 'sm' | 'md' = 'md'\n  @Input({ transform: booleanAttribute }) isActive = false\n  @Input('class') className?: string\n\n  get hostClass(): string {\n    return cn(\n      'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg,&>lucide-icon>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg,&>lucide-icon>svg]:size-4 [&>svg,&>lucide-icon>svg]:shrink-0',\n      'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',\n      this.size === 'sm' && 'text-xs',\n      this.size === 'md' && 'text-sm',\n      'group-data-[collapsible=icon]:hidden',\n      this.className,\n    )\n  }\n}\n\nexport { sidebarMenuButtonVariants, type SidebarMenuButtonVariants } from './sidebar.variants'\nexport * from './sidebar-context'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/sidebar/sidebar.component.ts"
    },
    {
      "path": "packages/registry-angular/components/sidebar/sidebar.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\n/**\n * Variant definitions live in their own file (rather than the package\n * `index.ts`) so consuming Vue SFCs can import without creating a circular\n * dependency through the index. See card.variants.ts for the canonical\n * example + the SSR symptom that motivated the split.\n */\n\nexport const sidebarMenuButtonVariants = cva(\n  'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm text-sidebar-foreground/80 outline-hidden ring-sidebar-ring transition-[width,height,padding,color,background-color] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-foreground/70',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-foreground',\n        outline:\n          'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n)\n\nexport type SidebarMenuButtonVariants = VariantProps<typeof sidebarMenuButtonVariants>\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/sidebar/sidebar.variants.ts"
    },
    {
      "path": "packages/registry-angular/components/sidebar/sidebar-context.ts",
      "content": "/**\n * Angular port of the sidebar constants. Same values as the Vue `utils.ts`\n * but framework-free (no vue/reka imports) so registry consumers and vitest\n * stay dependency-light.\n */\n\nexport const SIDEBAR_COOKIE_NAME = 'sidebar_state'\nexport const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7\nexport const SIDEBAR_WIDTH = '16rem'\nexport const SIDEBAR_WIDTH_MOBILE = '18rem'\nexport const SIDEBAR_WIDTH_ICON = '3rem'\nexport const SIDEBAR_KEYBOARD_SHORTCUT = 'b'\n\nexport type SidebarState = 'expanded' | 'collapsed'\nexport type SidebarSide = 'left' | 'right'\nexport type SidebarVariant = 'sidebar' | 'floating' | 'inset'\nexport type SidebarCollapsible = 'offcanvas' | 'icon' | 'none'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/sidebar/sidebar-context.ts"
    },
    {
      "path": "packages/registry-angular/components/sidebar/index.ts",
      "content": "export {\n  UiSidebarProviderComponent,\n  UiSidebarComponent,\n  UiSidebarHeaderComponent,\n  UiSidebarContentComponent,\n  UiSidebarFooterComponent,\n  UiSidebarGroupComponent,\n  UiSidebarGroupLabelComponent,\n  UiSidebarGroupContentComponent,\n  UiSidebarGroupActionComponent,\n  UiSidebarInputComponent,\n  UiSidebarInsetComponent,\n  UiSidebarSeparatorComponent,\n  UiSidebarMenuComponent,\n  UiSidebarMenuItemComponent,\n  UiSidebarMenuButtonComponent,\n  UiSidebarMenuActionComponent,\n  UiSidebarMenuBadgeComponent,\n  UiSidebarMenuSkeletonComponent,\n  UiSidebarMenuSubComponent,\n  UiSidebarMenuSubItemComponent,\n  UiSidebarMenuSubButtonComponent,\n  UiSidebarRailComponent,\n  UiSidebarTriggerComponent,\n  injectSidebar,\n  sidebarMenuButtonVariants,\n  type SidebarMenuButtonVariants,\n} from './sidebar.component'\nexport * from './sidebar-context'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/sidebar/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/popper.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/sheet.json",
    "https://uipkge.dev/r/angular/skeleton.json",
    "https://uipkge.dev/r/angular/tooltip.json"
  ],
  "description": "Full-height app sidebar — collapsible to icons, with grouping, sub-grouping, and integrated search. The navigation surface for product apps with many sections.",
  "categories": [
    "navigation"
  ]
}