{
  "schemaVersion": 1,
  "name": "bubble",
  "useFor": "Use for the lines of an agent conversation, inside Message. Use default for the agent's own messages (align end), secondary for the person or store it talks to, tinted for another agent, muted for a system line, destructive for a message that failed. Group consecutive bubbles in BubbleGroup.",
  "constraints": [
    "Gradients or shadows on bubbles",
    "Animating bubbles in",
    "Round pill-shaped bubbles"
  ],
  "documentation": "",
  "source": "Extrapolated from opencommerce (the blue key, the window edge and mist, 12px window corners)",
  "exports": [
    "Bubble",
    "BubbleContent",
    "BubbleGroup",
    "BubbleReactions",
    "bubbleVariants"
  ],
  "client": false,
  "docsUrl": "https://opencommerce-ds.vercel.app/components/bubble",
  "markdownUrl": "https://opencommerce-ds.vercel.app/r/bubble.md",
  "examples": [
    {
      "path": "examples/bubble.tsx",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { ThumbsUp } from \"lucide-react\";\n\nimport { Bubble, BubbleContent, BubbleGroup, BubbleReactions } from \"@/components/ui/bubble\";\n\nexport function Site() {\n  return (\n    <div className=\"mx-auto grid w-full max-w-xl gap-4\">\n      <BubbleGroup>\n        <Bubble variant=\"secondary\">\n          <BubbleContent>Can you order a USB-C hub under $40?</BubbleContent>\n        </Bubble>\n        <Bubble variant=\"secondary\">\n          <BubbleContent>Same-day delivery if possible.</BubbleContent>\n        </Bubble>\n      </BubbleGroup>\n      <BubbleGroup>\n        <Bubble align=\"end\">\n          <BubbleContent>Found one at Best Buy for $34.99, arriving today by 6pm.</BubbleContent>\n        </Bubble>\n        <Bubble align=\"end\">\n          <BubbleContent>Placing the order now.</BubbleContent>\n        </Bubble>\n      </BubbleGroup>\n    </div>\n  );\n}\n\nexport function Variants() {\n  const [liked, setLiked] = useState(false);\n  return (\n    <div className=\"mx-auto grid w-full max-w-xl gap-5\">\n      <Bubble variant=\"tinted\">\n        <BubbleContent>travel-agent: the hotel is booked; pass me the card for the flight.</BubbleContent>\n      </Bubble>\n      <Bubble variant=\"muted\">\n        <BubbleContent>Route to Wayfair warmed.</BubbleContent>\n      </Bubble>\n      <Bubble variant=\"outline\">\n        <BubbleContent>Order #A-10293 shipped.</BubbleContent>\n        <BubbleReactions>\n          <button type=\"button\" aria-label=\"Like\" aria-pressed={liked} onClick={() => setLiked((l) => !l)} className=\"inline-flex items-center gap-1 rounded-pill px-1.5 py-0.5 font-mono text-bar text-fg-muted outline-none transition-[color] duration-160 ease-out aria-pressed:text-fg-brand focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-focus-ring\">\n            <ThumbsUp aria-hidden strokeWidth={1.5} className=\"size-3.5\" />\n            {liked ? \"1\" : null}\n          </button>\n        </BubbleReactions>\n      </Bubble>\n      <Bubble variant=\"destructive\" align=\"end\">\n        <BubbleContent>Could not send: the store closed the cart.</BubbleContent>\n      </Bubble>\n      <Bubble variant=\"ghost\">\n        <BubbleContent>A plain line with no bubble, for long agent output.</BubbleContent>\n      </Bubble>\n      <Bubble variant=\"secondary\">\n        <BubbleContent asChild>\n          <button type=\"button\">Show the cart</button>\n        </BubbleContent>\n      </Bubble>\n    </div>\n  );\n}\n",
      "exports": [
        "Site",
        "Variants"
      ],
      "requires": [
        "bubble"
      ],
      "packages": [
        "lucide-react"
      ],
      "previews": [
        {
          "name": "Site",
          "url": "https://opencommerce-ds.vercel.app/preview/bubble/Site"
        },
        {
          "name": "Variants",
          "url": "https://opencommerce-ds.vercel.app/preview/bubble/Variants"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Bubble",
  "description": "A message bubble: the agent's own in the blue with white text, the other side on the mist, 12px corners with the tail squared.",
  "surface": "site",
  "kinds": [],
  "requires": [
    "theme",
    "utils"
  ],
  "packages": [
    "@radix-ui/react-slot",
    "class-variance-authority"
  ],
  "files": [
    {
      "path": "components/ui/bubble.tsx",
      "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\n// A message bubble in an agent conversation, in the site's two voices (extrapolated from the keys and windows in\n// globals.css; computed): the agent's own message is the blue of the main action with white text; the other side is the\n// mist on the 9 percent ink edge with ink text. 14px Inter on a 1.625 line, 14px by 10px inside, 12px corners (the\n// window's) with the tail corner (bottom left incoming, bottom right outgoing) squared to 2px, as the brand keeps its\n// chrome nearly square. In a BubbleGroup only the last bubble keeps the tail. Bubbles never animate in: a conversation\n// is read, not watched.\nfunction BubbleGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"bubble-group\" className={cn(\"flex min-w-0 flex-col gap-1 [&>[data-slot=bubble]:not(:last-child)>[data-slot=bubble-content]]:rounded-panel\", className)} {...props} />;\n}\n\nconst bubbleVariants = cva(\n  \"group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[align=start]:*:data-[slot=bubble-content]:rounded-bl-chip data-[align=end]:*:data-[slot=bubble-content]:rounded-br-chip data-[variant=ghost]:max-w-full\",\n  {\n    variants: {\n      variant: {\n        // The agent's own: the blue key.\n        default: \"*:data-[slot=bubble-content]:border-line-brand-edge *:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-on-primary [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary-hover\",\n        // The other side: the mist.\n        secondary: \"*:data-[slot=bubble-content]:border-line *:data-[slot=bubble-content]:bg-subtle *:data-[slot=bubble-content]:text-fg [&>[data-slot=bubble-content]:is(button,a):hover]:bg-wash-hover\",\n        // A system line or a quote: the pill wash at 64 percent ink.\n        muted: \"*:data-[slot=bubble-content]:bg-wash-pill *:data-[slot=bubble-content]:text-fg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-wash-hover\",\n        // Another agent: the pale blue.\n        tinted: \"*:data-[slot=bubble-content]:bg-blue-5 *:data-[slot=bubble-content]:text-fg [&>[data-slot=bubble-content]:is(button,a):hover]:bg-blue-10\",\n        outline: \"*:data-[slot=bubble-content]:border-hairline *:data-[slot=bubble-content]:bg-surface *:data-[slot=bubble-content]:text-fg [&>[data-slot=bubble-content]:is(button,a):hover]:bg-subtle\",\n        ghost: \"*:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 *:data-[slot=bubble-content]:text-fg\",\n        // A message that failed to send.\n        destructive: \"*:data-[slot=bubble-content]:border-danger *:data-[slot=bubble-content]:bg-surface *:data-[slot=bubble-content]:text-danger [&>[data-slot=bubble-content]:is(button,a):hover]:bg-subtle\",\n      },\n    },\n    defaultVariants: { variant: \"default\" },\n  },\n);\n\nfunction Bubble({ variant = \"default\", align = \"start\", className, ...props }: React.ComponentProps<\"div\"> & VariantProps<typeof bubbleVariants> & { align?: \"start\" | \"end\" }) {\n  return <div data-slot=\"bubble\" data-variant={variant} data-align={align} className={cn(bubbleVariants({ variant }), className)} {...props} />;\n}\n\nfunction BubbleContent({ asChild = false, className, ...props }: React.ComponentProps<\"div\"> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot : \"div\";\n  return (\n    <Comp\n      data-slot=\"bubble-content\"\n      className={cn(\n        \"w-fit max-w-full min-w-0 overflow-hidden rounded-panel border border-transparent px-3.5 py-2.5 font-sans text-body-sm leading-relaxed break-words group-data-[align=end]/bubble:self-end [button]:cursor-pointer [button]:text-left [button,a]:outline-none [button,a]:transition-[background-color] [button,a]:duration-160 [button,a]:ease-out [button,a]:focus-visible:outline-2 [button,a]:focus-visible:outline-offset-2 [button,a]:focus-visible:outline-solid [button,a]:focus-visible:outline-focus-ring motion-reduce:transition-none\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nconst bubbleReactionsVariants = cva(\n  \"absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-pill border border-line bg-surface px-1.5 py-0.5 text-caption leading-tight text-fg shadow-tile has-[button]:p-0\",\n  {\n    variants: {\n      side: { top: \"top-0 -translate-y-3/4\", bottom: \"bottom-0 translate-y-3/4\" },\n      align: { start: \"left-3\", end: \"right-3\" },\n    },\n    defaultVariants: { side: \"bottom\", align: \"end\" },\n  },\n);\n\n/** Reactions pinned to a bubble's edge: a small white pill on the 9 percent edge. */\nfunction BubbleReactions({ side = \"bottom\", align = \"end\", className, ...props }: React.ComponentProps<\"div\"> & { align?: \"start\" | \"end\"; side?: \"top\" | \"bottom\" }) {\n  return <div data-slot=\"bubble-reactions\" data-align={align} data-side={side} className={cn(bubbleReactionsVariants({ side, align }), className)} {...props} />;\n}\n\nexport { Bubble, BubbleContent, BubbleGroup, BubbleReactions, bubbleVariants };\n"
    }
  ]
}
