{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "attachment-story",
  "title": "Attachment Story",
  "author": "Lloyd Richards <lloyd.d.richards@gmail.com>",
  "description": "Storybook stories demonstrating attachment component files, previews, states, actions, and triggers",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "attachment",
    "button",
    "dialog",
    "spinner"
  ],
  "files": [
    {
      "path": "registry/ui/attachment-story/attachment-radix.stories.tsx",
      "content": "// Replace nextjs-vite with the name of your framework\nimport type { Meta, StoryObj } from \"@storybook/nextjs-vite\";\nimport {\n  CheckIcon,\n  ClockIcon,\n  CopyIcon,\n  DownloadIcon,\n  FileCodeIcon,\n  FileSearchIcon,\n  FileTextIcon,\n  ImageIcon,\n  XIcon,\n} from \"lucide-react\";\nimport Image from \"next/image\";\nimport type * as React from \"react\";\nimport { expect, userEvent, within } from \"storybook/test\";\n\nimport {\n  Attachment,\n  AttachmentAction,\n  AttachmentActions,\n  AttachmentContent,\n  AttachmentDescription,\n  AttachmentGroup,\n  AttachmentMedia,\n  AttachmentTitle,\n  AttachmentTrigger,\n} from \"@/components/ui/attachment\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { Spinner } from \"@/components/ui/spinner\";\n\n/**\n * Displays file and image attachments with metadata, states, and actions.\n */\nconst meta: Meta<typeof Attachment> = {\n  title: \"ui/radix/Attachment\",\n  component: Attachment,\n  tags: [\"autodocs\"],\n  parameters: {\n    layout: \"centered\",\n  },\n  argTypes: {\n    state: {\n      control: \"select\",\n      options: [\"idle\", \"uploading\", \"processing\", \"error\", \"done\"],\n    },\n    size: {\n      control: \"select\",\n      options: [\"default\", \"sm\", \"xs\"],\n    },\n    orientation: {\n      control: \"select\",\n      options: [\"horizontal\", \"vertical\"],\n    },\n  },\n  args: {\n    state: \"done\",\n    size: \"default\",\n    orientation: \"horizontal\",\n  },\n  decorators: (Story) => (\n    <div className=\"w-full min-w-sm max-w-md\">\n      <Story />\n    </div>\n  ),\n} satisfies Meta<typeof Attachment>;\n\nexport default meta;\n\ntype Story = StoryObj<typeof meta>;\n\nfunction FileAttachment({\n  name = \"sales-dashboard.pdf\",\n  description = \"PDF · 2.4 MB\",\n  icon = <FileTextIcon />,\n  actionLabel = \"Remove sales-dashboard.pdf\",\n  ...props\n}: React.ComponentProps<typeof Attachment> & {\n  name?: string;\n  description?: string;\n  icon?: React.ReactNode;\n  actionLabel?: string;\n}) {\n  return (\n    <Attachment {...props}>\n      <AttachmentMedia>{icon}</AttachmentMedia>\n      <AttachmentContent>\n        <AttachmentTitle>{name}</AttachmentTitle>\n        <AttachmentDescription>{description}</AttachmentDescription>\n      </AttachmentContent>\n      <AttachmentActions>\n        <AttachmentAction aria-label={actionLabel}>\n          <XIcon />\n        </AttachmentAction>\n      </AttachmentActions>\n    </Attachment>\n  );\n}\n\n/**\n * The default attachment uses an icon, title, metadata, and an action.\n */\nexport const Default: Story = {\n  render: (args) => <FileAttachment {...args} />,\n};\n\n/**\n * Use image media and vertical orientation for image previews.\n */\nexport const ImagePreview: Story = {\n  args: {\n    orientation: \"vertical\",\n  },\n  render: (args) => (\n    <Attachment {...args}>\n      <AttachmentMedia variant=\"image\">\n        <Image\n          src=\"https://images.unsplash.com/photo-1497366754035-f200968a6e72?auto=format&fit=crop&w=240&q=80\"\n          alt=\"Workspace\"\n          width={96}\n          height={96}\n          unoptimized\n        />\n      </AttachmentMedia>\n      <AttachmentContent>\n        <AttachmentTitle>workspace.png</AttachmentTitle>\n        <AttachmentDescription>PNG · 820 KB</AttachmentDescription>\n      </AttachmentContent>\n      <AttachmentActions>\n        <AttachmentAction aria-label=\"Remove workspace.png\">\n          <XIcon />\n        </AttachmentAction>\n      </AttachmentActions>\n    </Attachment>\n  ),\n};\n\n/**\n * Use the uploading state for active file uploads.\n */\nexport const Uploading: Story = {\n  args: {\n    state: \"uploading\",\n  },\n  render: (args) => (\n    <FileAttachment\n      {...args}\n      name=\"design-system.zip\"\n      description=\"Uploading · 64%\"\n      icon={<Spinner />}\n    />\n  ),\n};\n\n/**\n * Use the processing state after upload while a file is being prepared.\n */\nexport const Processing: Story = {\n  args: {\n    state: \"processing\",\n  },\n  render: (args) => (\n    <FileAttachment\n      {...args}\n      name=\"market-research.pdf\"\n      description=\"Processing document\"\n      icon={<ClockIcon />}\n    />\n  ),\n};\n\n/**\n * Use the error state when an attachment needs user attention.\n */\nexport const ErrorState: Story = {\n  args: {\n    state: \"error\",\n  },\n  render: (args) => (\n    <FileAttachment\n      {...args}\n      name=\"financial-model.xlsx\"\n      description=\"Upload failed. Try again.\"\n      icon={<XIcon />}\n    />\n  ),\n};\n\n/**\n * Use the small size for compact attachment rows.\n */\nexport const Small: Story = {\n  args: {\n    size: \"sm\",\n  },\n  render: (args) => (\n    <FileAttachment\n      {...args}\n      name=\"briefing-notes.pdf\"\n      description=\"PDF · 1.4 MB\"\n    />\n  ),\n};\n\n/**\n * Use the extra small size for dense composer previews.\n */\nexport const ExtraSmall: Story = {\n  args: {\n    size: \"xs\",\n  },\n  render: (args) => (\n    <FileAttachment\n      {...args}\n      name=\"renderer.tsx\"\n      description=\"TSX · 12 KB\"\n      icon={<FileCodeIcon />}\n    />\n  ),\n};\n\n/**\n * Group attachments in a horizontally scrollable row.\n */\nexport const Group: Story = {\n  render: () => (\n    <AttachmentGroup tabIndex={0} role=\"group\" aria-label=\"Attachments\">\n      <FileAttachment name=\"briefing-notes.pdf\" description=\"PDF · 1.4 MB\" />\n      <FileAttachment\n        name=\"workspace.png\"\n        description=\"PNG · 820 KB\"\n        icon={<ImageIcon />}\n      />\n      <FileAttachment\n        name=\"customers.csv\"\n        description=\"CSV · 18 KB\"\n        icon={<FileTextIcon />}\n      />\n    </AttachmentGroup>\n  ),\n};\n\n/**\n * Add multiple icon actions when the attachment supports file operations.\n */\nexport const WithActions: Story = {\n  render: (args) => (\n    <Attachment {...args}>\n      <AttachmentMedia>\n        <FileTextIcon />\n      </AttachmentMedia>\n      <AttachmentContent>\n        <AttachmentTitle>release-notes.pdf</AttachmentTitle>\n        <AttachmentDescription>PDF · 980 KB</AttachmentDescription>\n      </AttachmentContent>\n      <AttachmentActions>\n        <AttachmentAction aria-label=\"Download release-notes.pdf\">\n          <DownloadIcon />\n        </AttachmentAction>\n        <AttachmentAction aria-label=\"Copy link to release-notes.pdf\">\n          <CopyIcon />\n        </AttachmentAction>\n      </AttachmentActions>\n    </Attachment>\n  ),\n};\n\n/**\n * Use AttachmentTrigger for card-level preview while keeping attachment actions separate.\n */\nexport const Trigger: Story = {\n  render: (args) => (\n    <Dialog>\n      <Attachment {...args}>\n        <AttachmentMedia>\n          <FileSearchIcon />\n        </AttachmentMedia>\n        <AttachmentContent>\n          <AttachmentTitle>research-summary.pdf</AttachmentTitle>\n          <AttachmentDescription>Open preview dialog</AttachmentDescription>\n        </AttachmentContent>\n        <AttachmentActions>\n          <AttachmentAction aria-label=\"Remove research-summary.pdf\">\n            <XIcon />\n          </AttachmentAction>\n        </AttachmentActions>\n        <DialogTrigger asChild>\n          <AttachmentTrigger aria-label=\"Preview research-summary.pdf\" />\n        </DialogTrigger>\n      </Attachment>\n      <DialogContent>\n        <DialogHeader>\n          <DialogTitle>research-summary.pdf</DialogTitle>\n          <DialogDescription>\n            Preview the selected attachment before sending it.\n          </DialogDescription>\n        </DialogHeader>\n      </DialogContent>\n    </Dialog>\n  ),\n};\n\n/**\n * Verify attachment actions are independently clickable.\n */\nexport const ActionInteraction: Story = {\n  tags: [\"!dev\", \"!autodocs\"],\n  render: () => <FileAttachment actionLabel=\"Remove sales-dashboard.pdf\" />,\n  play: async ({ canvasElement }) => {\n    const canvas = within(canvasElement);\n    const button = canvas.getByRole(\"button\", {\n      name: /remove sales-dashboard.pdf/i,\n    });\n\n    await userEvent.click(button);\n    expect(button).toHaveFocus();\n  },\n};\n\n/**\n * Verify triggers and actions remain separate interactive controls.\n */\nexport const TriggerInteraction: Story = {\n  tags: [\"!dev\", \"!autodocs\"],\n  render: () => (\n    <Attachment>\n      <AttachmentMedia>\n        <CheckIcon />\n      </AttachmentMedia>\n      <AttachmentContent>\n        <AttachmentTitle>handoff.md</AttachmentTitle>\n        <AttachmentDescription>Markdown · 8 KB</AttachmentDescription>\n      </AttachmentContent>\n      <AttachmentActions>\n        <AttachmentAction aria-label=\"Remove handoff.md\">\n          <XIcon />\n        </AttachmentAction>\n      </AttachmentActions>\n      <AttachmentTrigger aria-label=\"Open handoff.md\" />\n    </Attachment>\n  ),\n  play: async ({ canvasElement }) => {\n    const canvas = within(canvasElement);\n\n    expect(\n      canvas.getByRole(\"button\", { name: /open handoff.md/i }),\n    ).toBeInTheDocument();\n    expect(\n      canvas.getByRole(\"button\", { name: /remove handoff.md/i }),\n    ).toBeInTheDocument();\n  },\n};\n",
      "type": "registry:component",
      "target": "@ui/attachment.stories.tsx"
    }
  ],
  "categories": [
    "ui",
    "storybook",
    "attachment",
    "chat"
  ],
  "type": "registry:component"
}