{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"message","title":"Message","description":"Lay out one turn of a conversation with the message, its actions and, in a chat between people, an avatar and a header. Search terms: chat message, message actions, message group.","dependencies":[],"registryDependencies":["@siteplane/base"],"files":[{"path":"packages/ui/src/components/message/message.tsx","content":"import type React from \"react\";\n\nimport { cn } from \"../../system/utils/cn\";\nimport { slotClass } from \"../../system/utils/slot-class\";\n\nimport \"./message.css\";\n\nexport type MessageAlign = \"start\" | \"end\";\n\nexport type MessageProps = React.ComponentProps<\"div\"> & {\n  /**\n   * The side of the conversation the turn belongs to: `start` for the other\n   * party or the assistant, `end` for the person who is writing. The avatar,\n   * the content and the footer follow it; the header stays at the start.\n   * Give a Bubble in the turn the same `align`.\n   */\n  align?: MessageAlign | undefined;\n};\n\n/**\n * One turn of a conversation: an avatar column beside a content column that\n * holds a header, the bubbles or other content, and a footer. The stylesheet\n * (message.css) places the parts; the text surface is a Bubble and the scroll\n * region around the turns is a Message Scroller.\n */\nexport function Message({\n  align = \"start\",\n  className,\n  ...props\n}: MessageProps): React.ReactElement {\n  return (\n    <div\n      className={cn(slotClass(\"message\", props), className)}\n      data-align={align}\n      data-slot=\"message\"\n      {...props}\n    />\n  );\n}\n\n/**\n * Consecutive turns from the same sender. Give the earlier turns an empty\n * `MessageAvatar`, so that their content lines up with the turn that shows the\n * avatar.\n */\nexport function MessageGroup({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">): React.ReactElement {\n  return (\n    <div\n      className={cn(slotClass(\"message-group\", props), className)}\n      data-slot=\"message-group\"\n      {...props}\n    />\n  );\n}\n\n/**\n * The avatar column. It ends with the last bubble, above the footer when there\n * is one, and the turn grows to fit an avatar taller than the content. It\n * keeps its width when it is empty. An absent `MessageAvatar` leaves no column\n * behind.\n */\nexport function MessageAvatar({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">): React.ReactElement {\n  return (\n    <div\n      className={cn(slotClass(\"message-avatar\", props), className)}\n      data-slot=\"message-avatar\"\n      {...props}\n    />\n  );\n}\n\n/** A line above the content, such as the sender's name. It stays at the start. */\nexport function MessageHeader({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">): React.ReactElement {\n  return (\n    <div\n      className={cn(slotClass(\"message-header\", props), className)}\n      data-slot=\"message-header\"\n      {...props}\n    />\n  );\n}\n\n/** The content column: the header, the bubbles or other content, and the footer. */\nexport function MessageContent({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">): React.ReactElement {\n  return (\n    <div\n      className={cn(slotClass(\"message-content\", props), className)}\n      data-slot=\"message-content\"\n      {...props}\n    />\n  );\n}\n\n/**\n * A line under the content: a status such as \"Delivered\", or the actions of\n * the turn. It follows the side of the message. Icon-only actions need an\n * `aria-label` each.\n */\nexport function MessageFooter({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">): React.ReactElement {\n  return (\n    <div\n      className={cn(slotClass(\"message-footer\", props), className)}\n      data-slot=\"message-footer\"\n      {...props}\n    />\n  );\n}\n","type":"registry:ui","target":"~/src/siteplane/components/message/message.tsx"},{"path":"packages/ui/src/components/message/message.css","content":"@layer theme, base, components, siteplane, utilities;\n\n@layer siteplane {\n  /* One turn of a conversation. The turn is a grid of an avatar column and a\n   * content column. The two-column template applies only while a MessageAvatar\n   * is there, so a turn without one leaves no track and no gap (S11); an empty\n   * MessageAvatar keeps its column on purpose, so the earlier turns of a group\n   * line up with the turn that shows the avatar. `align=\"end\"` mirrors the\n   * grid. The areas follow the writing direction, so a right-to-left page\n   * mirrors it once more.\n   *\n   * The custom properties are the tuning points of a turn. A className or a\n   * theme sets them on the turn or on the thread around it. The inset of the\n   * header and footer is the text inset of a Bubble, so both read the same\n   * property (--sp-bubble-padding-inline, set on an ancestor of the two). */\n  :where(.sp-message) {\n    --sp-message-avatar-gap: var(--sp-rw-space-2);\n    --sp-message-avatar-size: var(--sp-rw-space-8);\n    --sp-message-footer-size: var(--sp-rw-control-xs);\n    --sp-message-gap: var(--sp-rw-space-2);\n    --sp-message-inset: var(--sp-bubble-padding-inline, var(--sp-rw-space-4));\n\n    display: grid;\n    grid-template-areas: \"content\";\n    grid-template-columns: minmax(0, 1fr);\n    column-gap: var(--sp-message-avatar-gap);\n    min-width: 0;\n  }\n\n  .sp-message:has(> .sp-message-avatar) {\n    grid-template-areas: \"avatar content\";\n    grid-template-columns: auto minmax(0, 1fr);\n  }\n\n  .sp-message[data-align=\"end\"]:has(> .sp-message-avatar) {\n    grid-template-areas: \"content avatar\";\n    grid-template-columns: minmax(0, 1fr) auto;\n  }\n\n  /* Consecutive turns from the same sender. */\n  :where(.sp-message-group) {\n    display: flex;\n    flex-direction: column;\n    gap: var(--sp-rw-space-2);\n    min-width: 0;\n  }\n\n  /* The avatar anchors to the bottom of the content. The column is at least as\n   * wide as the default avatar, so an empty one still holds the place. The\n   * avatar is a grid item like the content, so the row is as high as the\n   * taller of the two: a turn shorter than its avatar grows to fit it, and the\n   * avatar never leaves the turn's box. */\n  :where(.sp-message-avatar) {\n    display: flex;\n    grid-area: avatar;\n    align-items: center;\n    align-self: end;\n    justify-content: center;\n    min-width: var(--sp-message-avatar-size);\n  }\n\n  /* With a footer, the avatar's slot reserves the footer's row and the gap\n   * above it below the avatar. The slot still ends at the bottom of the turn,\n   * so the avatar ends where the last bubble ends instead of at the footer.\n   * The footer row is at least --sp-message-footer-size high (below), which\n   * makes the reserved room exact for actions and for a line of text alike;\n   * taller footer content needs the property set to its height. The room is\n   * padding of the slot, which grid sizing counts. */\n  .sp-message:has(> .sp-message-content > .sp-message-footer:not(:empty))\n    > .sp-message-avatar {\n    padding-block-end: calc(\n      var(--sp-message-footer-size) + var(--sp-message-gap)\n    );\n  }\n\n  /* Beside an avatar the content also ends at the bottom of the row. When the\n   * avatar is the taller of the two, the content stays level with its bottom\n   * and the extra room is above it, so the avatar and the last bubble end\n   * together. */\n  .sp-message:has(> .sp-message-avatar) > .sp-message-content {\n    align-self: end;\n  }\n\n  /* The content column stacks the header, the bubbles and the footer with\n   * gap (S10). Long values wrap instead of widening the turn (K5). */\n  :where(.sp-message-content) {\n    display: flex;\n    flex-direction: column;\n    grid-area: content;\n    gap: var(--sp-message-gap);\n    min-width: 0;\n    overflow-wrap: anywhere;\n  }\n\n  /* An end turn gathers its content at the end: bubbles, attachments and the\n   * footer hug that side, and a Bubble Group aligns its bubbles to it. The\n   * header stays at the start. */\n  .sp-message[data-align=\"end\"] > .sp-message-content {\n    align-items: flex-end;\n  }\n\n  .sp-message[data-align=\"end\"] .sp-bubble-group {\n    align-items: flex-end;\n  }\n\n  /* An Attachment Group fills its column so that a long row can scroll; in an\n   * end turn it is only as wide as its cards, so they hug the end like the\n   * bubble, and it still scrolls once the cards are wider than the column. */\n  .sp-message[data-align=\"end\"] .sp-attachment-group {\n    width: fit-content;\n  }\n\n  .sp-message[data-align=\"end\"] > .sp-message-content > .sp-message-header {\n    align-self: flex-start;\n  }\n\n  /* The header and the footer are quiet lines of 12 px in the tertiary text\n   * color. */\n  :where(.sp-message-header) {\n    display: flex;\n    align-items: center;\n    max-width: 100%;\n    min-width: 0;\n    color: var(--sp-rw-fg-tertiary);\n    font-size: var(--sp-rw-font-12);\n    font-weight: var(--sp-rw-font-weight-label);\n    line-height: var(--sp-rw-line-18);\n  }\n\n  :where(.sp-message-footer) {\n    display: flex;\n    align-items: center;\n    gap: var(--sp-rw-space-1);\n    max-width: 100%;\n    min-width: 0;\n    min-height: var(--sp-message-footer-size);\n    color: var(--sp-rw-fg-tertiary);\n    font-size: var(--sp-rw-font-12);\n    font-weight: var(--sp-rw-font-weight-label);\n    line-height: var(--sp-rw-line-18);\n  }\n\n  .sp-message[data-align=\"end\"] > .sp-message-content > .sp-message-footer {\n    justify-content: flex-end;\n  }\n\n  /* An empty header or footer, such as a footer whose actions are not there\n   * yet, leaves no row and no gap behind (S11). */\n  :where(.sp-message-header:empty),\n  :where(.sp-message-footer:empty) {\n    display: none;\n  }\n\n  /* Under a framed bubble the header and the footer start where the bubble's\n   * text starts. Unframed content, a ghost bubble, starts at the edge. */\n  .sp-message:has(.sp-bubble:not([data-variant=\"ghost\"])) .sp-message-header,\n  .sp-message:has(.sp-bubble:not([data-variant=\"ghost\"])) .sp-message-footer {\n    padding-inline: var(--sp-message-inset);\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/message/message.css"},{"path":"packages/ui/src/components/message/index.ts","content":"export * from \"./message\";\n","type":"registry:ui","target":"~/src/siteplane/components/message/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/message.md","categories":["Chat & AI"],"meta":{"avoidWhen":["Avoid for a line in a conversation that is not a message, such as a status, a note or a date; use Marker.","Avoid as the surface of a message; Message draws none, so put the text in a Bubble."],"description":"Lay out one turn of a conversation with the message, its actions and, in a chat between people, an avatar and a header.","knownLimitations":["Message lays out a turn and keeps no state. Selection, editing, reactions and read receipts belong to the application.","With a footer, the avatar's slot reserves a footer row of 24 px. A taller footer needs --sp-message-footer-size set to its height."],"providerNotes":[],"releaseStatus":"preview","searchTerms":["message","chat message","turn","conversation","message actions","message group"],"useWhen":["Use for each turn of a chat or an AI conversation, to place the message and its actions, and in a chat between people the avatar, the sender's name and the delivery status.","Use MessageGroup for consecutive turns from one sender, so that their content lines up beside one avatar."]}}