{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "dock",
  "title": "Dock",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/dock/dock.component.ts",
      "content": "import {\n  Component,\n  ElementRef,\n  EventEmitter,\n  Input,\n  Output,\n  TemplateRef,\n  booleanAttribute,\n  inject,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser'\nimport { NgTemplateOutlet } from '@angular/common'\nimport { cn } from '@/lib/utils'\n\nexport interface DockItem {\n  id: string\n  label: string\n  icon?: string | TemplateRef<unknown> | any\n  handler?: () => void\n  active?: boolean\n}\n\n/**\n * Angular port of UIPKGE Dock. macOS-style dock menu with cosine-bell\n * magnification. Class strings mirror the React and Vue source.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-dock, [ui-dock]',\n  standalone: true,\n  imports: [NgTemplateOutlet],\n  host: {\n    '[attr.data-slot]': '\"dock\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-orientation]': 'orientation',\n    '[class]': 'hostClass',\n    '(mousemove)': 'onMove($event)',\n    '(mouseleave)': 'onLeave()',\n  },\n  template: `\n    @for (item of items; track item.id; let i = $index) {\n      <div\n        data-slot=\"dock-item\"\n        [attr.data-active]=\"item.active ? '' : null\"\n        role=\"button\"\n        tabindex=\"0\"\n        [attr.aria-label]=\"item.label\"\n        [attr.aria-current]=\"item.active ? 'true' : null\"\n        class=\"group focus-visible:ring-ring/50 relative flex shrink-0 cursor-pointer items-end justify-center rounded-xl outline-none focus-visible:ring-[3px]\"\n        (mouseenter)=\"hoveredId = item.id\"\n        (mouseleave)=\"hoveredId = null\"\n        (click)=\"onSelect(item)\"\n        (keydown.enter)=\"onSelect(item)\"\n        (keydown.space)=\"$event.preventDefault(); onSelect(item)\"\n      >\n        <!-- Tooltip -->\n        @if (showTooltips && hoveredId === item.id) {\n          <span\n            class=\"border-border bg-popover text-popover-foreground pointer-events-none absolute -top-10 left-1/2 -translate-x-1/2 rounded-md border px-2 py-1 text-xs whitespace-nowrap shadow-md\"\n          >\n            {{ item.label }}\n          </span>\n        }\n\n        <!-- Icon tile -->\n        <span\n          class=\"flex items-center justify-center rounded-xl border transition-[width,height] duration-100 ease-out will-change-[width,height]\"\n          [class]=\"\n            item.active\n              ? 'border-primary/40 bg-primary/10 text-primary'\n              : 'border-border/50 bg-muted/40 text-foreground hover:bg-muted'\n          \"\n          [style.width.px]=\"sizeFor(i)\"\n          [style.height.px]=\"sizeFor(i)\"\n        >\n          @if (isSvg(item.icon)) {\n            <span\n              class=\"flex items-center justify-center [&>svg,&>lucide-icon>svg]:size-full\"\n              [style.width.px]=\"sizeFor(i) * 0.5\"\n              [style.height.px]=\"sizeFor(i) * 0.5\"\n              [innerHTML]=\"sanitize(item.icon)\"\n            ></span>\n          } @else if (isTemplate(item.icon)) {\n            <ng-container [ngTemplateOutlet]=\"item.icon\" />\n          } @else {\n            <span [style.fontSize.px]=\"sizeFor(i) * 0.4\">{{ item.icon ?? '●' }}</span>\n          }\n        </span>\n\n        <!-- Active indicator dot -->\n        @if (item.active) {\n          <span\n            data-slot=\"dock-indicator\"\n            class=\"bg-primary absolute -bottom-2 size-1 rounded-full\"\n            aria-hidden=\"true\"\n          ></span>\n        }\n      </div>\n    }\n  `,\n})\nexport class UiDockComponent {\n  @Input() items: DockItem[] = []\n  @Input() baseSize = 48\n  @Input() magnification = 1.6\n  @Input() distance = 120\n  /** Orientation. Only 'horizontal' (bottom dock) is supported, like React/Vue. */\n  @Input() orientation: 'horizontal' = 'horizontal'\n  @Input({ transform: booleanAttribute }) showTooltips = true\n  @Input('class') set classAttr(v: string | undefined) {\n    this._class = v\n  }\n  @Input() set className(v: string | undefined) {\n    this._class = v\n  }\n  get className(): string | undefined {\n    return this._class\n  }\n  @Output() select = new EventEmitter<DockItem>()\n\n  hoveredId: string | null = null\n  mouseX: number | null = null\n\n  private _class?: string\n  private sanitizedCache = new Map<string, SafeHtml>()\n  private readonly sanitizer: DomSanitizer | null = null\n  private readonly el: ElementRef<HTMLElement> | null = null\n\n  constructor() {\n    try {\n      this.sanitizer = inject(DomSanitizer, { optional: true })\n    } catch {}\n    try {\n      this.el = inject<ElementRef<HTMLElement>>(ElementRef, { optional: true })\n    } catch {}\n  }\n\n  get hostClass(): string {\n    return cn(\n      'border-border/60 bg-background/60 flex items-end justify-center gap-3 rounded-2xl border px-3 py-2 backdrop-blur-md',\n      this._class,\n    )\n  }\n\n  onMove(event: MouseEvent): void {\n    this.mouseX = event.clientX\n  }\n\n  onLeave(): void {\n    this.mouseX = null\n    this.hoveredId = null\n  }\n\n  sizeFor(index: number): number {\n    if (this.mouseX === null) return this.baseSize\n    let center: number\n    const root = this.el?.nativeElement\n    const itemEl = root?.querySelectorAll<HTMLElement>('[data-slot=\"dock-item\"]')?.[index]\n    if (itemEl) {\n      const rect = itemEl.getBoundingClientRect()\n      center = rect.left + rect.width / 2\n    } else {\n      center = index * (this.baseSize + 8) + this.baseSize / 2\n    }\n    const dist = Math.abs(this.mouseX - center)\n    if (dist > this.distance) return this.baseSize\n    const t = 1 - dist / this.distance\n    const scale = 1 + (this.magnification - 1) * t\n    return this.baseSize * scale\n  }\n\n  onSelect(item: DockItem): void {\n    item.handler?.()\n    this.select.emit(item)\n  }\n\n  isSvg(val: unknown): boolean {\n    return typeof val === 'string' && val.includes('<svg')\n  }\n\n  isTemplate(val: unknown): boolean {\n    return val instanceof TemplateRef\n  }\n\n  sanitize(svg: string): SafeHtml | string {\n    if (!this.sanitizer) return svg\n    let safe = this.sanitizedCache.get(svg)\n    if (!safe) {\n      safe = this.sanitizer.bypassSecurityTrustHtml(svg)\n      this.sanitizedCache.set(svg, safe)\n    }\n    return safe\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/dock/dock.component.ts"
    },
    {
      "path": "packages/registry-angular/components/dock/index.ts",
      "content": "export { UiDockComponent, type DockItem } from './dock.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/dock/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "macOS-style dock menu with magnification on hover. Items expand as the cursor approaches using a cosine bell curve. Supports an items array (icon + label + handler), magnification scale, base size, tooltips on hover, click handlers, and an active state indicator.",
  "categories": [
    "navigation"
  ]
}