{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "kanban",
  "title": "Kanban",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/kanban/kanban.component.ts",
      "content": "import {\n  AfterViewInit,\n  ApplicationRef,\n  Component,\n  ElementRef,\n  EventEmitter,\n  Input,\n  Output,\n  booleanAttribute,\n  effect,\n  inject,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { kanbanCardVariants, kanbanColumnVariants } from './kanban.variants'\n\nexport interface KanbanMoveEvent {\n  cardId: string\n  fromColumnId: string\n  toColumnId: string\n  toIndex?: number\n}\n\n/* ------------------------------------------------------------------ */\n/* Kanban (root provider)                                             */\n/* ------------------------------------------------------------------ */\n\n/**\n * Angular port of UIPKGE Kanban (React parity). The root holds the drag state shared by\n * columns and cards (pointer drag via native HTML5 drag-and-drop, keyboard drag via Space /\n * arrows / Escape on a card) and narrates keyboard moves through a polite live region.\n * The consumer owns the data: every move is emitted through `cardMove`\n * `{ cardId, fromColumnId, toColumnId, toIndex? }` and the consumer re-renders.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban, [ui-kanban]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"kanban\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />\n    <div data-slot=\"kanban-live-region\" class=\"sr-only\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n      {{ announcement() }}\n    </div>`,\n})\nexport class UiKanbanComponent {\n  @Input('class') className?: string\n  /** React `onCardMove`. */\n  @Output() cardMove = new EventEmitter<KanbanMoveEvent>()\n\n  readonly draggingCardId = signal<string | null>(null)\n  readonly draggingColumnId = signal<string | null>(null)\n  readonly overColumnId = signal<string | null>(null)\n  /** Set while a card is held by keyboard (Space), not by pointer drag. */\n  readonly grabbedCardId = signal<string | null>(null)\n  readonly announcement = signal('')\n  /**\n   * True for one task after a keyboard move is emitted. Angular re-renders the moved card\n   * by destroying / re-inserting its DOM node, which fires a blur that is not the user\n   * leaving the card (React detaches its handlers first, so it never sees that blur).\n   */\n  moving = false\n\n  private readonly appRef = inject(ApplicationRef)\n\n  /** Emit a keyboard move and re-render at once (React flushes discrete events synchronously),\n   *  so an immediate second arrow press reads the new card order. */\n  emitMove(event: KanbanMoveEvent): void {\n    this.moving = true\n    setTimeout(() => (this.moving = false))\n    this.cardMove.emit(event)\n    this.appRef.tick()\n  }\n\n  get hostClass(): string {\n    return cn('block w-full', this.className)\n  }\n\n  setDraggingCard(cardId: string | null, columnId: string | null): void {\n    this.draggingCardId.set(cardId)\n    this.draggingColumnId.set(columnId)\n  }\n\n  setOverColumn(columnId: string | null): void {\n    this.overColumnId.set(columnId)\n  }\n\n  setGrabbedCard(cardId: string | null): void {\n    this.grabbedCardId.set(cardId)\n  }\n\n  /** Speak a message through the board's polite live region. */\n  announce(message: string): void {\n    // Re-assigning the same string would not re-trigger the live region.\n    this.announcement.update((current) => (current === message ? `${message} ` : message))\n  }\n}\n\n/* ------------------------------------------------------------------ */\n/* KanbanBoard                                                        */\n/* ------------------------------------------------------------------ */\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-board, [ui-kanban-board]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-board\"', '[class]': 'hostClass' },\n  template: `<ng-content />`,\n})\nexport class UiKanbanBoardComponent {\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn('flex w-full items-start gap-4 overflow-x-auto pb-4', this.className)\n  }\n}\n\n/* ------------------------------------------------------------------ */\n/* KanbanColumn                                                       */\n/* ------------------------------------------------------------------ */\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-column, [ui-kanban-column]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"kanban-column\"',\n    role: 'group',\n    '[attr.aria-label]': 'label ?? id',\n    '[attr.data-column-id]': 'id',\n    '[attr.data-over]': 'isOver ? \"\" : null',\n    '[class]': 'hostClass',\n    '(dragover)': 'onDragOver($event)',\n    '(dragleave)': 'onDragLeave($event)',\n    '(drop)': 'onDrop($event)',\n  },\n  template: `<ng-content />`,\n})\nexport class UiKanbanColumnComponent {\n  readonly kanban = inject(UiKanbanComponent)\n  private readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  @Input({ required: true }) id!: string\n  /** Accessible name for the column, also used in keyboard move announcements. Falls back to the id. */\n  @Input() label?: string\n  @Input('class') className?: string\n\n  get isOver(): boolean {\n    return this.kanban.overColumnId() === this.id\n  }\n\n  get hostClass(): string {\n    return cn(kanbanColumnVariants({ isOver: this.isOver }), this.className)\n  }\n\n  onDragOver(e: DragEvent): void {\n    e.preventDefault()\n    if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'\n    if (this.kanban.overColumnId() !== this.id) this.kanban.setOverColumn(this.id)\n  }\n\n  onDragLeave(e: DragEvent): void {\n    if (this.el.contains(e.relatedTarget as Node | null)) return\n    if (this.kanban.overColumnId() === this.id) this.kanban.setOverColumn(null)\n  }\n\n  onDrop(e: DragEvent): void {\n    e.preventDefault()\n    const cardId = this.kanban.draggingCardId()\n    const fromColumnId = this.kanban.draggingColumnId()\n    if (cardId && fromColumnId) this.kanban.cardMove.emit({ cardId, fromColumnId, toColumnId: this.id })\n    this.kanban.setDraggingCard(null, null)\n    this.kanban.setOverColumn(null)\n  }\n}\n\n/* ------------------------------------------------------------------ */\n/* KanbanColumnHeader & sub-components                                */\n/* ------------------------------------------------------------------ */\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-column-header, [ui-kanban-column-header]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-column-header\"', '[class]': 'hostClass' },\n  template: `<ng-content />`,\n})\nexport class UiKanbanColumnHeaderComponent {\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn('flex items-center justify-between gap-2 px-1 py-0.5', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-column-dot, [ui-kanban-column-dot]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-column-dot\"', '[class]': 'hostClass' },\n  template: ``,\n})\nexport class UiKanbanColumnDotComponent {\n  /** Any background utility. */\n  @Input() color = 'bg-primary'\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn('size-2 shrink-0 rounded-full', this.color, this.className)\n  }\n}\n\n/** Put it on an <h3> for React's DOM. */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-column-title, [ui-kanban-column-title]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-column-title\"', '[class]': 'hostClass' },\n  template: `<ng-content />`,\n})\nexport class UiKanbanColumnTitleComponent {\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn('block text-foreground text-sm font-semibold tracking-tight', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-column-count, [ui-kanban-column-count]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-column-count\"', '[class]': 'hostClass' },\n  template: `@if (count != null) {\n      <ng-container>{{ count }}</ng-container>\n    } @else {\n      <ng-content />\n    }`,\n})\nexport class UiKanbanColumnCountComponent {\n  /** Wins over projected content (React `count ?? children`). */\n  @Input() count?: number | string | null\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn(\n      'bg-muted text-muted-foreground rounded-md px-1.5 py-0.5 text-xs font-medium tabular-nums',\n      this.className,\n    )\n  }\n}\n\n/** Put it on a <button> for React's DOM. */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-column-add, [ui-kanban-column-add]',\n  standalone: true,\n  host: { '[attr.type]': '\"button\"', '[attr.data-slot]': '\"kanban-column-add\"', '[class]': 'hostClass' },\n  template: `<ng-content\n    ><svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\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      class=\"lucide lucide-plus size-3.5\"\n      aria-hidden=\"true\"\n    >\n      <path d=\"M5 12h14\" />\n      <path d=\"M12 5v14\" /></svg\n  ></ng-content>`,\n})\nexport class UiKanbanColumnAddComponent {\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn(\n      'text-muted-foreground hover:bg-background hover:text-foreground focus-visible:ring-ring inline-flex size-6 items-center justify-center rounded-md transition-colors focus-visible:ring-1 focus-visible:outline-none',\n      this.className,\n    )\n  }\n}\n\n/* ------------------------------------------------------------------ */\n/* KanbanColumnBody & Empty                                           */\n/* ------------------------------------------------------------------ */\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-column-body, [ui-kanban-column-body]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-column-body\"', '[class]': 'hostClass' },\n  template: `<ng-content />`,\n})\nexport class UiKanbanColumnBodyComponent {\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn('flex flex-1 flex-col gap-2 overflow-y-auto py-1', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-column-empty, [ui-kanban-column-empty]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-column-empty\"', '[class]': 'hostClass' },\n  template: `<ng-content>No cards</ng-content>`,\n})\nexport class UiKanbanColumnEmptyComponent {\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn(\n      'border-border/60 text-muted-foreground/60 flex flex-1 items-center justify-center rounded-lg border border-dashed py-8 text-xs',\n      this.className,\n    )\n  }\n}\n\n/* ------------------------------------------------------------------ */\n/* KanbanCard & sub-components                                        */\n/* ------------------------------------------------------------------ */\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-card, [ui-kanban-card]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"kanban-card\"',\n    '[attr.data-card-id]': 'id',\n    '[attr.data-state]': 'isGrabbed ? \"grabbed\" : isDragging ? \"dragging\" : \"idle\"',\n    '[attr.data-disabled]': 'disabled || null',\n    '[attr.role]': 'canKeyboardDrag ? \"button\" : null',\n    '[attr.tabindex]': 'canKeyboardDrag ? 0 : null',\n    '[attr.aria-disabled]': 'disabled || null',\n    '[attr.aria-roledescription]': 'canKeyboardDrag ? \"draggable card\" : null',\n    '[attr.aria-pressed]': 'canKeyboardDrag ? isGrabbed : null',\n    '[attr.draggable]': '!disabled',\n    '[class]': 'hostClass',\n    '(dragstart)': 'onDragStart($event)',\n    '(dragend)': 'onDragEnd()',\n    '(keydown)': 'onKeyDown($event)',\n    '(blur)': 'onBlur()',\n  },\n  template: `<ng-content />`,\n})\nexport class UiKanbanCardComponent implements AfterViewInit {\n  readonly kanban = inject(UiKanbanComponent)\n  readonly column = inject(UiKanbanColumnComponent)\n  private readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  @Input({ required: true }) id!: string\n  @Input({ transform: booleanAttribute }) disabled = false\n  /** Disable the keyboard grab (Space / arrows) while leaving pointer dragging intact. */\n  @Input({ transform: booleanAttribute }) keyboardDraggable = true\n  @Input('class') className?: string\n\n  constructor() {\n    // The consumer owns the data, so a moved card is destroyed and re-created under its new\n    // column; whichever instance is mounted while the card is held takes focus back.\n    effect(() => {\n      if (this.kanban.grabbedCardId() === this.id) queueMicrotask(() => this.refocus())\n    })\n  }\n\n  get isGrabbed(): boolean {\n    return this.kanban.grabbedCardId() === this.id\n  }\n  get isDragging(): boolean {\n    return this.kanban.draggingCardId() === this.id || this.isGrabbed\n  }\n  get canKeyboardDrag(): boolean {\n    return this.keyboardDraggable && !this.disabled\n  }\n\n  get hostClass(): string {\n    return cn(\n      kanbanCardVariants({ isDragging: this.isDragging }),\n      this.disabled && 'pointer-events-none opacity-50',\n      this.className,\n    )\n  }\n\n  ngAfterViewInit(): void {\n    if (this.isGrabbed) this.refocus()\n  }\n\n  private refocus(): void {\n    if (this.isGrabbed && this.el.isConnected && document.activeElement !== this.el) this.el.focus()\n  }\n\n  onDragStart(e: DragEvent): void {\n    if (this.disabled) {\n      e.preventDefault()\n      return\n    }\n    if (e.dataTransfer) {\n      e.dataTransfer.effectAllowed = 'move'\n      e.dataTransfer.setData('text/plain', this.id)\n    }\n    this.kanban.setDraggingCard(this.id, this.column.id)\n  }\n\n  onDragEnd(): void {\n    this.kanban.setDraggingCard(null, null)\n    this.kanban.setOverColumn(null)\n  }\n\n  private columnName(el: HTMLElement | null): string {\n    return el?.getAttribute('aria-label') || el?.dataset['columnId'] || 'column'\n  }\n\n  /** Ordered columns of the board this card sits in. */\n  private boardColumns(): HTMLElement[] {\n    const board: ParentNode = this.el.closest('[data-slot=\"kanban\"]') ?? document\n    return Array.from(board.querySelectorAll<HTMLElement>('[data-slot=\"kanban-column\"]'))\n  }\n\n  private grab(): void {\n    this.kanban.setGrabbedCard(this.id)\n    this.kanban.setDraggingCard(this.id, this.column.id)\n    this.kanban.setOverColumn(this.column.id)\n    this.kanban.announce('Picked up card. Use the arrow keys to move it, space to drop, escape to cancel.')\n  }\n\n  private release(cancelled: boolean): void {\n    this.kanban.setGrabbedCard(null)\n    this.kanban.setDraggingCard(null, null)\n    this.kanban.setOverColumn(null)\n    this.kanban.announce(cancelled ? 'Move cancelled.' : 'Card dropped.')\n  }\n\n  private moveToColumn(delta: -1 | 1): void {\n    const columns = this.boardColumns()\n    // The held card's column lives in the root: a second arrow press can land on this\n    // instance before change detection has re-rendered it under its new column.\n    const fromColumnId = this.kanban.draggingColumnId() ?? this.column.id\n    const currentIdx = columns.findIndex((el) => el.dataset['columnId'] === fromColumnId)\n    if (currentIdx === -1) return\n    const target = columns[currentIdx + delta]\n    // Deliberately not wrapping: running off the end of a board stops.\n    const toColumnId = target?.dataset['columnId']\n    if (!toColumnId) return\n    this.kanban.emitMove({ cardId: this.id, fromColumnId, toColumnId })\n    this.kanban.setDraggingCard(this.id, toColumnId)\n    this.kanban.setOverColumn(toColumnId)\n    this.kanban.announce(`Moved to ${this.columnName(target)}.`)\n  }\n\n  private moveWithinColumn(delta: -1 | 1): void {\n    const columnEl = this.el.closest<HTMLElement>('[data-slot=\"kanban-column\"]')\n    if (!columnEl) return\n    const cards = Array.from(columnEl.querySelectorAll<HTMLElement>('[data-slot=\"kanban-card\"]'))\n    const currentIdx = cards.findIndex((el) => el.dataset['cardId'] === this.id)\n    if (currentIdx === -1) return\n    const targetIdx = currentIdx + delta\n    if (targetIdx < 0 || targetIdx > cards.length - 1) return\n    this.kanban.emitMove({\n      cardId: this.id,\n      fromColumnId: this.column.id,\n      toColumnId: this.column.id,\n      toIndex: targetIdx,\n    })\n    this.kanban.announce(`Position ${targetIdx + 1} of ${cards.length}.`)\n  }\n\n  onKeyDown(e: KeyboardEvent): void {\n    if (!this.canKeyboardDrag || e.defaultPrevented) return\n    if (e.key === ' ' || e.key === 'Spacebar') {\n      e.preventDefault()\n      if (this.isGrabbed) this.release(false)\n      else this.grab()\n      return\n    }\n    if (!this.isGrabbed) return\n    if (e.key === 'Escape') {\n      e.preventDefault()\n      this.release(true)\n      return\n    }\n    if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n      e.preventDefault()\n      this.moveToColumn(e.key === 'ArrowLeft' ? -1 : 1)\n      return\n    }\n    if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n      e.preventDefault()\n      this.moveWithinColumn(e.key === 'ArrowUp' ? -1 : 1)\n    }\n  }\n\n  onBlur(): void {\n    // A grabbed card that loses focus (click elsewhere, Tab) would otherwise stay held.\n    if (!this.isGrabbed) return\n    if (this.kanban.moving) {\n      // Blur from the move re-render: the mounted instance takes focus back.\n      setTimeout(() => this.refocus())\n      return\n    }\n    this.release(true)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-card-header, [ui-kanban-card-header]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-card-header\"', '[class]': 'hostClass' },\n  template: `<ng-content />`,\n})\nexport class UiKanbanCardHeaderComponent {\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn('flex flex-col gap-1', this.className)\n  }\n}\n\n/** Put it on a <p> for React's DOM. */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-card-title, [ui-kanban-card-title]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-card-title\"', '[class]': 'hostClass' },\n  template: `<ng-content />`,\n})\nexport class UiKanbanCardTitleComponent {\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn('block text-foreground text-sm leading-snug font-medium', this.className)\n  }\n}\n\n/** Put it on a <p> for React's DOM. */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-card-description, [ui-kanban-card-description]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-card-description\"', '[class]': 'hostClass' },\n  template: `<ng-content />`,\n})\nexport class UiKanbanCardDescriptionComponent {\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn('text-muted-foreground line-clamp-2 text-xs', this.className)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-kanban-card-footer, [ui-kanban-card-footer]',\n  standalone: true,\n  host: { '[attr.data-slot]': '\"kanban-card-footer\"', '[class]': 'hostClass' },\n  template: `<ng-content />`,\n})\nexport class UiKanbanCardFooterComponent {\n  @Input('class') className?: string\n  get hostClass(): string {\n    return cn('text-muted-foreground mt-1 flex items-center justify-between gap-2 pt-1 text-xs', this.className)\n  }\n}\n\nexport { kanbanColumnVariants, kanbanCardVariants }\nexport type { KanbanColumnVariantsProps, KanbanCardVariantsProps } from './kanban.variants'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/kanban/kanban.component.ts"
    },
    {
      "path": "packages/registry-angular/components/kanban/kanban.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\nexport const kanbanColumnVariants = cva(\n  'flex min-h-[300px] w-72 shrink-0 flex-col gap-2 rounded-xl border bg-muted/40 p-3 transition-colors duration-200 ease-out',\n  {\n    variants: {\n      isOver: {\n        true: 'border-primary/50 bg-primary/5 ring-2 ring-primary/20',\n        false: 'border-border/70',\n      },\n    },\n    defaultVariants: {\n      isOver: false,\n    },\n  },\n)\n\nexport const kanbanCardVariants = cva(\n  'group relative flex cursor-grab flex-col gap-2 rounded-lg border bg-card p-3 text-card-foreground shadow-xs transition-[border-color,box-shadow,opacity] duration-150 ease-out active:cursor-grabbing hover:border-border hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n  {\n    variants: {\n      isDragging: {\n        // Keyboard grab reuses this state, so the held card stays fully\n        // opaque — unlike a pointer drag there is no drag image to look at.\n        true: 'shadow-none ring-2 ring-primary/40 opacity-100 data-[state=dragging]:opacity-40',\n        false: 'opacity-100',\n      },\n    },\n    defaultVariants: {\n      isDragging: false,\n    },\n  },\n)\n\nexport type KanbanColumnVariantsProps = VariantProps<typeof kanbanColumnVariants>\nexport type KanbanCardVariantsProps = VariantProps<typeof kanbanCardVariants>\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/kanban/kanban.variants.ts"
    },
    {
      "path": "packages/registry-angular/components/kanban/index.ts",
      "content": "export {\n  UiKanbanComponent,\n  UiKanbanBoardComponent,\n  UiKanbanColumnComponent,\n  UiKanbanColumnHeaderComponent,\n  UiKanbanColumnDotComponent,\n  UiKanbanColumnTitleComponent,\n  UiKanbanColumnCountComponent,\n  UiKanbanColumnAddComponent,\n  UiKanbanColumnBodyComponent,\n  UiKanbanColumnEmptyComponent,\n  UiKanbanCardComponent,\n  UiKanbanCardHeaderComponent,\n  UiKanbanCardTitleComponent,\n  UiKanbanCardDescriptionComponent,\n  UiKanbanCardFooterComponent,\n  kanbanColumnVariants,\n  kanbanCardVariants,\n  type KanbanMoveEvent,\n  type KanbanColumnVariantsProps,\n  type KanbanCardVariantsProps,\n} from './kanban.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/kanban/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Composable compound Kanban primitive for building board, pipeline, and agile workflows with tactile card drag-and-drop mechanics.",
  "categories": [
    "data-display",
    "layout"
  ]
}