{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "attachment",
  "title": "Attachment",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/attachment/attachment.component.ts",
      "content": "import { Component, EventEmitter, Input, Output, booleanAttribute, ChangeDetectionStrategy } from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { attachmentMediaVariants, attachmentVariants, type AttachmentVariants } from './attachment.variants'\n\nexport type AttachmentState = 'idle' | 'uploading' | 'processing' | 'error' | 'done'\nexport type AttachmentSize = NonNullable<AttachmentVariants['size']>\nexport type AttachmentOrientation = NonNullable<AttachmentVariants['orientation']>\nexport type AttachmentMedia = 'file' | 'image' | 'code'\n\n/**\n * Angular port of UIPKGE Attachment. Single file/image chip driven by\n * `state`, `size`, `orientation`, `media`, `src`, `removable` props.\n * Class strings come from shared `attachmentVariants` — identical to Vue/React.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-attachment, [ui-attachment]',\n  standalone: true,\n  host: {\n    '[attr.data-slot]': '\"attachment\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-state]': 'state',\n    '[attr.data-size]': 'size',\n    '[attr.data-orientation]': 'orientation',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <div data-uipkge data-slot=\"attachment-media\" [class]=\"mediaClass\">\n      @if (src && media === 'image' && !busy) {\n        <img [src]=\"src\" [alt]=\"alt\" class=\"size-full object-cover\" />\n      } @else if (busy) {\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-loader-circle size-4 motion-safe:animate-spin\"\n          aria-hidden=\"true\"\n        >\n          <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" />\n        </svg>\n      } @else if (media === 'code') {\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-file-code\"\n          aria-hidden=\"true\"\n        >\n          <path d=\"M10 12.5 8 15l2 2.5\" />\n          <path d=\"m14 12.5 2 2.5-2 2.5\" />\n          <path d=\"M14 2v4a2 2 0 0 0 2 2h4\" />\n          <path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z\" />\n        </svg>\n      } @else if (media === 'image') {\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-image\"\n          aria-hidden=\"true\"\n        >\n          <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" />\n          <circle cx=\"9\" cy=\"9\" r=\"2\" />\n          <path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\" />\n        </svg>\n      } @else {\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-file-text\"\n          aria-hidden=\"true\"\n        >\n          <path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\" />\n          <path d=\"M14 2v4a2 2 0 0 0 2 2h4\" />\n          <path d=\"M10 9H8\" />\n          <path d=\"M16 13H8\" />\n          <path d=\"M16 17H8\" />\n        </svg>\n      }\n    </div>\n    <div data-uipkge data-slot=\"attachment-content\" class=\"min-w-0 flex-1 leading-tight\">\n      <span data-uipkge data-slot=\"attachment-title\" [class]=\"titleClass\">{{ title }}</span>\n      @if (description) {\n        <span data-uipkge data-slot=\"attachment-description\" [class]=\"descriptionClass\">{{ description }}</span>\n      }\n    </div>\n    @if (removable) {\n      <button\n        type=\"button\"\n        data-uipkge\n        data-slot=\"attachment-remove\"\n        class=\"text-muted-foreground hover:bg-accent hover:text-foreground relative z-10 inline-flex size-7 shrink-0 items-center justify-center rounded-md\"\n        [attr.aria-label]=\"'Remove ' + title\"\n        (click)=\"remove.emit()\"\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.5\"\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    <ng-content />\n  `,\n})\nexport class UiAttachmentComponent {\n  @Input() title = ''\n  @Input() description?: string\n  @Input() state: AttachmentState = 'done'\n  @Input() size: AttachmentSize = 'default'\n  @Input() orientation: AttachmentOrientation = 'horizontal'\n  @Input() media: AttachmentMedia = 'file'\n  @Input() src?: string\n  @Input() alt = ''\n  @Input({ transform: booleanAttribute }) removable = false\n  @Input('class') className?: string\n\n  @Output() remove = new EventEmitter<void>()\n\n  get busy(): boolean {\n    return this.state === 'uploading' || this.state === 'processing'\n  }\n\n  get hostClass(): string {\n    return cn(attachmentVariants({ size: this.size, orientation: this.orientation }), this.className)\n  }\n\n  get mediaClass(): string {\n    return cn(attachmentMediaVariants({ size: this.size }))\n  }\n\n  get titleClass(): string {\n    return cn('block truncate font-medium', this.busy && 'animate-pulse')\n  }\n\n  get descriptionClass(): string {\n    return cn('text-muted-foreground mt-0.5 block truncate text-xs', this.state === 'error' && 'text-destructive/80')\n  }\n}\n\nexport { attachmentMediaVariants, attachmentVariants, type AttachmentVariants }\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/attachment/attachment.component.ts"
    },
    {
      "path": "packages/registry-angular/components/attachment/attachment.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 attachmentVariants = cva(\n  'group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 rounded-xl border bg-card text-card-foreground transition-colors data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed',\n  {\n    variants: {\n      size: {\n        default: 'gap-2 p-2 text-sm',\n        sm: 'gap-2 p-1.5 text-xs',\n        xs: 'gap-1.5 rounded-lg p-1 text-xs',\n      },\n      orientation: {\n        horizontal: 'min-w-40 items-center',\n        vertical: 'w-24 flex-col',\n      },\n    },\n    defaultVariants: {\n      size: 'default',\n      orientation: 'horizontal',\n    },\n  },\n)\n\nexport const attachmentMediaVariants = cva(\n  \"relative flex aspect-square shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      size: {\n        default: 'w-10',\n        sm: 'w-8',\n        xs: 'w-7 rounded-md [&_svg:not([class*=\"size-\"])]:size-3.5',\n      },\n    },\n    defaultVariants: {\n      size: 'default',\n    },\n  },\n)\n\nexport type AttachmentVariants = VariantProps<typeof attachmentVariants>\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/attachment/attachment.variants.ts"
    },
    {
      "path": "packages/registry-angular/components/attachment/index.ts",
      "content": "export {\n  UiAttachmentComponent,\n  attachmentMediaVariants,\n  attachmentVariants,\n  type AttachmentMedia,\n  type AttachmentOrientation,\n  type AttachmentSize,\n  type AttachmentState,\n  type AttachmentVariants,\n} from './attachment.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/attachment/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Single file or image chip. Drive look with props: state, size, orientation, media, src, removable. Not a dropzone (see file-upload).",
  "categories": [
    "data-display"
  ]
}