{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bubble-story",
  "title": "Bubble Story",
  "author": "Lloyd Richards <lloyd.d.richards@gmail.com>",
  "description": "Storybook stories demonstrating bubble component variants, alignment, reactions, and interactive content",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "bubble",
    "button",
    "collapsible",
    "popover",
    "tooltip"
  ],
  "files": [
    {
      "path": "registry/ui/bubble-story/bubble-radix.stories.tsx",
      "content": "// Replace nextjs-vite with the name of your framework\nimport type { Meta, StoryObj } from \"@storybook/nextjs-vite\";\nimport { CheckIcon, InfoIcon, ThumbsUpIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { expect, userEvent, within } from \"storybook/test\";\nimport {\n  Bubble,\n  BubbleContent,\n  BubbleGroup,\n  BubbleReactions,\n} from \"@/components/ui/bubble\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\n/**\n * Displays conversational content in a framed or unframed message bubble.\n */\nconst meta: Meta<typeof Bubble> = {\n  title: \"ui/radix/Bubble\",\n  component: Bubble,\n  tags: [\"autodocs\"],\n  parameters: {\n    layout: \"centered\",\n  },\n  argTypes: {\n    variant: {\n      control: \"select\",\n      options: [\n        \"default\",\n        \"secondary\",\n        \"muted\",\n        \"tinted\",\n        \"outline\",\n        \"ghost\",\n        \"destructive\",\n      ],\n    },\n    align: {\n      control: \"select\",\n      options: [\"start\", \"end\"],\n    },\n  },\n  args: {\n    variant: \"default\",\n    align: \"start\",\n  },\n  decorators: (Story) => (\n    <div className=\"w-full min-w-sm max-w-md\">\n      <Story />\n    </div>\n  ),\n} satisfies Meta<typeof Bubble>;\n\nexport default meta;\n\ntype Story = StoryObj<typeof meta>;\n\nfunction BubbleExample({\n  children,\n  ...props\n}: React.ComponentProps<typeof Bubble>) {\n  return (\n    <Bubble {...props}>\n      <BubbleContent>{children}</BubbleContent>\n    </Bubble>\n  );\n}\n\n/**\n * The default bubble is a strong primary surface for current-user messages.\n */\nexport const Default: Story = {\n  render: (args) => (\n    <BubbleExample {...args}>\n      I checked the registry output and removed the stale route.\n    </BubbleExample>\n  ),\n};\n\n/**\n * Use the secondary variant for standard conversation content.\n */\nexport const Secondary: Story = {\n  args: {\n    variant: \"secondary\",\n  },\n  render: (args) => (\n    <BubbleExample {...args}>\n      The component JSON now lives under the UI registry.\n    </BubbleExample>\n  ),\n};\n\n/**\n * Use the muted variant for quiet supporting content.\n */\nexport const Muted: Story = {\n  args: {\n    variant: \"muted\",\n  },\n  render: (args) => (\n    <BubbleExample {...args}>\n      This note has lower emphasis than the main reply.\n    </BubbleExample>\n  ),\n};\n\n/**\n * Use the tinted variant for a softer primary treatment.\n */\nexport const Tinted: Story = {\n  args: {\n    variant: \"tinted\",\n  },\n  render: (args) => (\n    <BubbleExample {...args}>\n      I can preserve the chat tone without using the full primary color.\n    </BubbleExample>\n  ),\n};\n\n/**\n * Use the outline variant for secondary rich content.\n */\nexport const Outline: Story = {\n  args: {\n    variant: \"outline\",\n  },\n  render: (args) => (\n    <BubbleExample {...args}>\n      Here is the command output you asked me to review.\n    </BubbleExample>\n  ),\n};\n\n/**\n * Use the destructive variant for failed actions or error messages.\n */\nexport const Destructive: Story = {\n  args: {\n    variant: \"destructive\",\n  },\n  render: (args) => (\n    <BubbleExample {...args}>\n      Failed to send. Check your connection and try again.\n    </BubbleExample>\n  ),\n};\n\n/**\n * Use the ghost variant for assistant text or markdown-like content.\n */\nexport const Ghost: Story = {\n  args: {\n    variant: \"ghost\",\n  },\n  render: (args) => (\n    <BubbleExample {...args}>\n      Ghost bubbles work well when assistant messages should read like regular\n      text instead of framed chat surfaces.\n    </BubbleExample>\n  ),\n};\n\n/**\n * Align a bubble to the end for current-user messages.\n */\nexport const AlignedEnd: Story = {\n  args: {\n    align: \"end\",\n  },\n  render: (args) => (\n    <BubbleExample {...args}>Sure. Hit me with your best demo.</BubbleExample>\n  ),\n};\n\n/**\n * Group consecutive bubbles from the same sender.\n */\nexport const Group: Story = {\n  render: () => (\n    <BubbleGroup>\n      <Bubble variant=\"secondary\">\n        <BubbleContent>Can you tell me what changed?</BubbleContent>\n      </Bubble>\n      <Bubble variant=\"secondary\">\n        <BubbleContent>The registry entries now match the files.</BubbleContent>\n      </Bubble>\n    </BubbleGroup>\n  ),\n};\n\n/**\n * Render bubble content as a button for quick replies.\n */\nexport const ButtonBubble: Story = {\n  args: {\n    variant: \"muted\",\n  },\n  render: (args) => (\n    <Bubble {...args}>\n      <BubbleContent asChild>\n        <button type=\"button\">I forgot my password</button>\n      </BubbleContent>\n    </Bubble>\n  ),\n};\n\n/**\n * Use reactions for emoji counts or compact quick actions.\n */\nexport const Reactions: Story = {\n  args: {\n    variant: \"secondary\",\n  },\n  render: (args) => (\n    <Bubble {...args} className=\"mb-4\">\n      <BubbleContent>\n        Tests passed on the first try. Looking good.\n      </BubbleContent>\n      <BubbleReactions role=\"img\" aria-label=\"Reactions: thumbs up and party\">\n        <span>👍</span>\n        <span>🎉</span>\n      </BubbleReactions>\n    </Bubble>\n  ),\n};\n\n/**\n * Compose long content with Collapsible for show more interactions.\n */\nexport const CollapsibleBubble: Story = {\n  args: {\n    variant: \"secondary\",\n  },\n  render: (args) => (\n    <Collapsible className=\"flex flex-col gap-2\">\n      <Bubble {...args}>\n        <BubbleContent>\n          The accessibility review found two focus states that were visually too\n          subtle in dark mode.\n        </BubbleContent>\n      </Bubble>\n      <CollapsibleContent>\n        <Bubble {...args}>\n          <BubbleContent>\n            I checked the dialog, menu, and drawer paths because each one\n            renders focusable controls inside an overlay.\n          </BubbleContent>\n        </Bubble>\n      </CollapsibleContent>\n      <CollapsibleTrigger className=\"w-fit text-sm underline underline-offset-4 hover:text-primary\">\n        Show more\n      </CollapsibleTrigger>\n    </Collapsible>\n  ),\n};\n\n/**\n * Pair a bubble with Tooltip to reveal metadata on hover or focus.\n */\nexport const TooltipBubble: Story = {\n  args: {\n    variant: \"secondary\",\n  },\n  render: (args) => (\n    <TooltipProvider>\n      <Tooltip>\n        <TooltipTrigger asChild>\n          <Bubble {...args}>\n            <BubbleContent>Yes, removed it from the registry.</BubbleContent>\n          </Bubble>\n        </TooltipTrigger>\n        <TooltipContent>Read yesterday</TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  ),\n};\n\n/**\n * Pair a bubble with Popover to show additional detail on demand.\n */\nexport const PopoverBubble: Story = {\n  args: {\n    variant: \"destructive\",\n  },\n  render: (args) => (\n    <Popover>\n      <Bubble {...args}>\n        <PopoverTrigger asChild>\n          <BubbleContent asChild>\n            <button type=\"button\">Failed to run the command.</button>\n          </BubbleContent>\n        </PopoverTrigger>\n      </Bubble>\n      <PopoverContent className=\"max-w-xs text-sm\">\n        The process exited before Storybook finished loading.\n      </PopoverContent>\n    </Popover>\n  ),\n};\n\n/**\n * Verify quick-reply bubbles expose a clickable button.\n */\nexport const ButtonInteraction: Story = {\n  tags: [\"!dev\", \"!autodocs\"],\n  args: {\n    variant: \"muted\",\n  },\n  render: (args) => (\n    <Bubble {...args}>\n      <BubbleContent asChild>\n        <button type=\"button\">I need help with my subscription</button>\n      </BubbleContent>\n    </Bubble>\n  ),\n  play: async ({ canvasElement }) => {\n    const canvas = within(canvasElement);\n    const button = canvas.getByRole(\"button\", { name: /subscription/i });\n\n    await userEvent.click(button);\n    expect(button).toHaveFocus();\n  },\n};\n\n/**\n * Verify collapsible bubble content can be expanded.\n */\nexport const CollapsibleExpanded: Story = {\n  tags: [\"!dev\", \"!autodocs\"],\n  args: {\n    variant: \"secondary\",\n  },\n  render: (args) => (\n    <Collapsible className=\"flex flex-col gap-2\">\n      <Bubble {...args}>\n        <BubbleContent>The short summary is visible first.</BubbleContent>\n      </Bubble>\n      <CollapsibleContent>\n        <Bubble {...args}>\n          <BubbleContent>\n            The hidden implementation detail is now visible.\n          </BubbleContent>\n        </Bubble>\n      </CollapsibleContent>\n      <CollapsibleTrigger className=\"w-fit text-sm underline underline-offset-4 hover:text-primary\">\n        Show more\n      </CollapsibleTrigger>\n    </Collapsible>\n  ),\n  play: async ({ canvasElement }) => {\n    const canvas = within(canvasElement);\n\n    await userEvent.click(canvas.getByRole(\"button\", { name: /show more/i }));\n    expect(canvas.getByText(/hidden implementation detail/i)).toBeVisible();\n  },\n};\n\n/**\n * Verify interactive reaction buttons have accessible labels.\n */\nexport const AccessibleReactions: Story = {\n  tags: [\"!dev\", \"!autodocs\"],\n  args: {\n    variant: \"secondary\",\n  },\n  render: (args) => (\n    <Bubble {...args} className=\"mb-4\">\n      <BubbleContent>Can I run the formatter?</BubbleContent>\n      <BubbleReactions>\n        <Button aria-label=\"Approve\" size=\"icon-xs\" variant=\"secondary\">\n          <CheckIcon />\n        </Button>\n        <Button aria-label=\"Thumbs up\" size=\"icon-xs\" variant=\"secondary\">\n          <ThumbsUpIcon />\n        </Button>\n        <Button\n          aria-label=\"More information\"\n          size=\"icon-xs\"\n          variant=\"secondary\"\n        >\n          <InfoIcon />\n        </Button>\n      </BubbleReactions>\n    </Bubble>\n  ),\n  play: async ({ canvasElement }) => {\n    const canvas = within(canvasElement);\n\n    expect(\n      canvas.getByRole(\"button\", { name: /approve/i }),\n    ).toBeInTheDocument();\n    expect(\n      canvas.getByRole(\"button\", { name: /thumbs up/i }),\n    ).toBeInTheDocument();\n  },\n};\n",
      "type": "registry:component",
      "target": "@ui/bubble.stories.tsx"
    }
  ],
  "categories": [
    "ui",
    "storybook",
    "bubble",
    "chat"
  ],
  "type": "registry:component"
}