{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"bubble","title":"Bubble","description":"Show the text of a chat message on a surface that fits its sender and purpose. Search terms: chat bubble, message bubble, speech bubble.","dependencies":[],"registryDependencies":["@siteplane/base"],"files":[{"path":"packages/ui/src/components/bubble/bubble.tsx","content":"import type React from \"react\";\n\nimport { cn } from \"../../system/utils/cn\";\nimport { slotClass } from \"../../system/utils/slot-class\";\n\nimport \"./bubble.css\";\n\nexport type BubbleVariant =\n  \"default\" | \"muted\" | \"tinted\" | \"outline\" | \"ghost\" | \"destructive\";\n\nexport type BubbleAlign = \"start\" | \"end\";\n\nexport type BubbleProps = React.ComponentProps<\"div\"> & {\n  /**\n   * The look of the surface. `default` is the accent fill, for the current\n   * person's own text; `muted` is the quiet neutral fill; `tinted` is an accent\n   * tint; `outline` is the bordered message surface of a Card; `ghost` has no\n   * surface and takes the full width, for the assistant's text and rich\n   * content; `destructive` marks a failure in the error tint. Pair a tone with\n   * words, since color alone does not say what happened.\n   */\n  variant?: BubbleVariant | undefined;\n  /**\n   * The side of the conversation the bubble sits on. Inside a Message, give\n   * the bubble the Message's `align` as well.\n   */\n  align?: BubbleAlign | undefined;\n};\n\n/**\n * The surface of a message. Bubble places it, up to 80% of the width except for\n * `ghost`, and its BubbleContent draws it. Put avatars, names, timestamps and\n * the actions of a turn in a Message, not in the bubble.\n */\nexport function Bubble({\n  align = \"start\",\n  className,\n  variant = \"muted\",\n  ...props\n}: BubbleProps): React.ReactElement {\n  return (\n    <div\n      className={cn(slotClass(\"bubble\", props), className)}\n      data-align={align}\n      data-slot=\"bubble\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\n/** The text of a bubble, on the surface that the bubble's variant draws. */\nexport function BubbleContent({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">): React.ReactElement {\n  return (\n    <div\n      className={cn(slotClass(\"bubble-content\", props), className)}\n      data-slot=\"bubble-content\"\n      {...props}\n    />\n  );\n}\n\n/**\n * Consecutive bubbles from the same sender. Give each bubble its own `align`.\n */\nexport function BubbleGroup({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">): React.ReactElement {\n  return (\n    <div\n      className={cn(slotClass(\"bubble-group\", props), className)}\n      data-slot=\"bubble-group\"\n      {...props}\n    />\n  );\n}\n","type":"registry:ui","target":"~/src/siteplane/components/bubble/bubble.tsx"},{"path":"packages/ui/src/components/bubble/bubble.css","content":"@layer theme, base, components, siteplane, utilities;\n\n@layer siteplane {\n  /* The surface of a message. Bubble places it and BubbleContent draws it.\n   * A bubble is as wide as its text, up to 80% of the width it sits in, and\n   * ghost takes the full width (below). Placement follows `align`; inside a\n   * Message the turn gathers its bubbles at its own end as well (message.css).\n   *\n   * The proportions are those of a chat bubble: 8 px above and below the text,\n   * 16 px beside it, and the 16 px radius of a dialog. Their custom\n   * properties are the tuning points, set on a bubble or on any ancestor; the\n   * Message reads --sp-bubble-padding-inline too, so its header and footer\n   * start where the text starts. */\n  :where(.sp-bubble) {\n    display: flex;\n    flex-direction: column;\n    gap: var(--sp-rw-space-1);\n    width: fit-content;\n    max-width: 80%;\n    min-width: 0;\n  }\n\n  .sp-bubble[data-align=\"end\"] {\n    align-self: flex-end;\n  }\n\n  .sp-bubble[data-variant=\"ghost\"] {\n    width: 100%;\n    max-width: 100%;\n  }\n\n  /* Every variant has the same box: the 1 px border is transparent unless a\n   * variant draws it, so the outline bubble is no larger than the filled ones.\n   * Long values wrap instead of widening the bubble (K5). */\n  :where(.sp-bubble-content) {\n    --sp-bubble-surface-radius: var(\n      --sp-bubble-radius,\n      var(--sp-rw-radius-dialog)\n    );\n\n    width: fit-content;\n    max-width: 100%;\n    min-width: 0;\n    padding-block: var(--sp-bubble-padding-block, var(--sp-rw-space-2));\n    padding-inline: var(--sp-bubble-padding-inline, var(--sp-rw-space-4));\n    border: var(--sp-rw-border-default) solid transparent;\n    border-radius: var(--sp-bubble-surface-radius);\n    font-size: var(--sp-rw-font-14);\n    line-height: var(--sp-rw-line-21);\n    overflow-wrap: anywhere;\n  }\n\n  /* The accent fill, for the current person's own text. */\n  .sp-bubble[data-variant=\"default\"] > .sp-bubble-content {\n    background: var(--sp-rw-bg-accent);\n    color: var(--sp-rw-fg-on-accent);\n  }\n\n  .sp-bubble[data-variant=\"muted\"] > .sp-bubble-content {\n    background: var(--sp-rw-bg-muted);\n    color: var(--sp-rw-fg-primary);\n  }\n\n  .sp-bubble[data-variant=\"tinted\"] > .sp-bubble-content {\n    background: var(--sp-rw-bg-accent-translucent);\n    color: var(--sp-rw-fg-primary);\n  }\n\n  /* The border, the small corner at the start and the missing shadow of the\n   * outline bubble are the Card's message surface: its rule in\n   * design-rework.css lists this bubble as a second selector. Only the fill\n   * and the text color are set here. */\n  .sp-bubble[data-variant=\"outline\"] > .sp-bubble-content {\n    background: var(--sp-rw-bg-secondary);\n    color: var(--sp-rw-fg-primary);\n  }\n\n  /* An end bubble has its small corner at the end, on the sender's side. The\n   * Message's align counts as well as the bubble's own, so either one is\n   * enough. */\n  :root\n    .sp-bubble[data-variant=\"outline\"][data-align=\"end\"]\n    > .sp-bubble-content,\n  :root\n    .sp-message[data-align=\"end\"]\n    .sp-bubble[data-variant=\"outline\"]\n    > .sp-bubble-content {\n    border-start-start-radius: var(--sp-bubble-surface-radius);\n    border-start-end-radius: var(--sp-rw-radius-xs);\n  }\n\n  /* Ghost has no surface: the text sits on whatever is behind it, full width. */\n  .sp-bubble[data-variant=\"ghost\"] > .sp-bubble-content {\n    width: 100%;\n    padding: 0;\n    border-width: 0;\n    border-radius: 0;\n    background: transparent;\n  }\n\n  /* A failure: the error tint of a Badge, with the error text color. */\n  .sp-bubble[data-variant=\"destructive\"] > .sp-bubble-content {\n    background: var(--sp-rw-bg-destructive-translucent);\n    color: var(--sp-rw-fg-destructive);\n  }\n\n  /* Consecutive bubbles from the same sender. */\n  :where(.sp-bubble-group) {\n    display: flex;\n    flex-direction: column;\n    gap: var(--sp-rw-space-2);\n    width: 100%;\n    min-width: 0;\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/bubble/bubble.css"},{"path":"packages/ui/src/components/bubble/index.ts","content":"export * from \"./bubble\";\n","type":"registry:ui","target":"~/src/siteplane/components/bubble/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/bubble.md","categories":["Chat & AI"],"meta":{"avoidWhen":["Avoid for a message people click; a bubble is not interactive, so put a Button or a link inside it or in the footer of its Message.","Avoid for a status, a note or a date in a conversation; use Marker."],"description":"Show the text of a chat message on a surface that fits its sender and purpose.","knownLimitations":["Bubbles show no reactions and have no render prop for a link or a button.","A variant's color alone does not say what happened, so state a failure in the text of a destructive bubble."],"providerNotes":[],"releaseStatus":"preview","searchTerms":["bubble","chat bubble","message bubble","speech bubble","chat"],"useWhen":["Use for the text of a message in an AI chat, with muted for the person's turns and ghost for the assistant's text.","Use default or tinted for the person's own messages in a conversation between people, and destructive for a message or a reply that failed."]}}