{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "chip",
  "title": "Chip",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/chip/chip.component.ts",
      "content": "import {\n  Component,\n  EventEmitter,\n  Input,\n  Output,\n  booleanAttribute,\n  signal,\n  ViewEncapsulation,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { chipVariants, type ChipVariants } from './chip.variants'\n\n// Copied verbatim from the React chip (injected there as a global <style>). Unscoped\n// (ViewEncapsulation.None) so it reaches the host and projected content; Angular adds it once.\nconst CHIP_MOTION_STYLES = `\n@keyframes chip-enter {\n  from { opacity: 0; transform: scale(0.88); }\n  to { opacity: 1; transform: scale(1); }\n}\n@keyframes chip-leave {\n  to { opacity: 0; transform: scale(0.88); }\n}\n[data-slot='chip'].chip-enter {\n  animation: chip-enter 180ms cubic-bezier(0.22, 1.2, 0.36, 1) both;\n}\n[data-slot='chip'].chip-leave {\n  animation: chip-leave 160ms ease-in both;\n  pointer-events: none;\n}\n@media (prefers-reduced-motion: reduce) {\n  [data-slot='chip'].chip-enter,\n  [data-slot='chip'].chip-leave {\n    animation: none !important;\n  }\n}\n`\n\nexport type ChipVariant =\n  'default' | 'filled' | 'outlined' | 'outline' | 'elevated' | 'success' | 'warning' | 'destructive'\nexport type ChipSize = NonNullable<ChipVariants['size']>\n\n/**\n * Angular port of UIPKGE Chip (React `Chip`, a <span>). Compact removable tag / filter pill.\n * `closable` renders the built-in Lucide X dismiss button; clicking it plays the chip-leave\n * animation (skipped under prefers-reduced-motion) and then emits `close` (React `onClose`),\n * so the host removes the chip after it has faded. Class strings and the injected motion CSS\n * are copied from React.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-chip, [ui-chip]',\n  standalone: true,\n  encapsulation: ViewEncapsulation.None,\n  styles: [CHIP_MOTION_STYLES],\n  host: {\n    '[attr.data-slot]': '\"chip\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-leaving]': 'leaving() || null',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <ng-content />\n    @if (closable) {\n      <button\n        type=\"button\"\n        aria-label=\"Remove item\"\n        class=\"focus-visible:ring-ring hover:bg-foreground/10 ml-1 inline-flex min-h-6 min-w-6 items-center justify-center rounded-full transition-transform duration-150 focus-visible:ring-1 focus-visible:outline-none active:scale-90\"\n        (click)=\"onClose($event)\"\n      >\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-x size-3\"\n          aria-hidden=\"true\"\n        >\n          <path d=\"M18 6 6 18\" />\n          <path d=\"m6 6 12 12\" />\n        </svg>\n      </button>\n    }\n  `,\n})\nexport class UiChipComponent {\n  @Input() variant: ChipVariant = 'default'\n  @Input() size: ChipSize = 'default'\n  @Input({ transform: booleanAttribute }) wrap = false\n  @Input({ transform: booleanAttribute }) closable = false\n  @Input('class') className?: string\n\n  @Output() close = new EventEmitter<void>()\n\n  /** Signal: set from the click handler, read by the host binding (zoneless-safe). */\n  readonly leaving = signal(false)\n\n  get hostClass(): string {\n    return cn(\n      chipVariants({ variant: this.variant, size: this.size, wrap: this.wrap ? true : undefined }),\n      'chip-enter',\n      this.leaving() && 'chip-leave',\n      this.className,\n    )\n  }\n\n  onClose(event: Event): void {\n    event.stopPropagation()\n    if (this.leaving()) return\n    this.leaving.set(true)\n    const reduce = typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches\n    setTimeout(() => this.close.emit(), reduce ? 0 : 160)\n  }\n}\n\n/**\n * React `ChipGroup`: a role=\"group\" wrap row with controlled `selected` + `selectedChange`\n * (React `onSelectedChange`) and single / multiple / mandatory / max selection rules. React\n * hands `{ selected, multiple, filter, isSelected, toggle }` to a render-prop child; in\n * Angular take the same API from a template reference:\n * `<div ui-chip-group #g=\"uiChipGroup\"> ... g.isSelected('x') / g.toggle('x') ... </div>`.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-chip-group, [ui-chip-group]',\n  exportAs: 'uiChipGroup',\n  standalone: true,\n  encapsulation: ViewEncapsulation.None,\n  styles: [CHIP_MOTION_STYLES],\n  host: {\n    '[attr.data-chip-group]': '\"true\"',\n    '[attr.data-multiple]': 'multiple || null',\n    '[attr.data-filter]': 'filter || null',\n    '[attr.role]': '\"group\"',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content />`,\n})\nexport class UiChipGroupComponent {\n  @Input() selected: string[] = []\n  @Input({ transform: booleanAttribute }) multiple = false\n  @Input({ transform: booleanAttribute }) filter = false\n  @Input({ transform: booleanAttribute }) column = false\n  @Input({ transform: booleanAttribute }) mandatory = false\n  @Input() max?: number\n  @Input({ transform: booleanAttribute }) disabled = false\n  @Input('class') className?: string\n\n  @Output() selectedChange = new EventEmitter<string[]>()\n\n  get hostClass(): string {\n    return cn('flex flex-wrap gap-2', this.column && 'flex-col', this.className)\n  }\n\n  isSelected(value: string): boolean {\n    return this.selected.includes(value)\n  }\n\n  toggle(value: string): void {\n    if (this.disabled) return\n    let next: string[]\n    if (this.multiple) {\n      if (this.isSelected(value)) {\n        next = this.selected.filter((v) => v !== value)\n      } else if (this.max && this.selected.length >= this.max) {\n        next = [...this.selected.slice(1), value]\n      } else {\n        next = [...this.selected, value]\n      }\n    } else if (this.isSelected(value) && !this.mandatory) {\n      next = []\n    } else {\n      next = [value]\n    }\n    this.selectedChange.emit(next)\n  }\n}\n\nexport { chipVariants, type ChipVariants }\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/chip/chip.component.ts"
    },
    {
      "path": "packages/registry-angular/components/chip/chip.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 chipVariants = cva(\n  // text-overflow/ellipsis cannot apply to a flex container's anonymous text,\n  // so truncation is opt-in via a child span (`<Chip><span class=\"truncate\">\n  // …</span></Chip>`); min-w-0 lets that span shrink inside the flex root.\n  'inline-flex items-center justify-center gap-1 rounded-full text-xs font-medium w-fit max-w-full whitespace-nowrap shrink-0 [&>span]:min-w-0 transition-colors duration-200 overflow-hidden focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n  {\n    variants: {\n      variant: {\n        default: 'bg-muted text-muted-foreground hover:bg-muted/80',\n        filled: 'bg-primary text-primary-foreground hover:bg-primary/90',\n        outlined: 'border border-current bg-transparent hover:bg-accent',\n        outline: 'border border-current bg-transparent hover:bg-accent',\n        elevated: 'bg-primary/10 text-primary shadow-sm hover:bg-primary/20',\n        success: 'bg-success/10 text-success dark:text-success hover:bg-success/20',\n        warning: 'bg-warning/10 text-warning dark:text-warning hover:bg-warning/20',\n        destructive: 'bg-destructive/10 text-destructive dark:text-destructive hover:bg-destructive/20',\n      },\n      size: {\n        sm: 'h-6 px-2 text-xs',\n        default: 'h-7 px-2.5 text-xs',\n        lg: 'h-8 px-3 text-sm',\n      },\n      // Allow multi-line labels. rounded-lg + extra leading keeps a wrapped\n      // chip readable instead of a cramped two-line stadium pill.\n      wrap: {\n        true: 'whitespace-normal h-auto rounded-lg py-1 leading-snug',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n)\n\nexport type ChipVariants = VariantProps<typeof chipVariants>\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/chip/chip.variants.ts"
    },
    {
      "path": "packages/registry-angular/components/chip/index.ts",
      "content": "export { UiChipComponent, UiChipGroupComponent, chipVariants } from './chip.component'\nexport type { ChipSize, ChipVariant, ChipVariants } from './chip.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/chip/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Compact, removable tag — typically used inside `tags-input` or as a filter pill. Seven variants (`default`, `filled`, `outlined`, `elevated`, `success`, `warning`, `destructive`), three sizes, and an optional close button.",
  "categories": [
    "data-display"
  ]
}