{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "message",
  "type": "registry:component",
  "title": "Message",
  "description": "One turn of a conversation with an assistant: a person's bubble, or an answer set as prose with actions under it.",
  "dependencies": [
    "@base-ui/react",
    "tailwind-variants"
  ],
  "registryDependencies": [],
  "cssVars": {
    "light": {
      "success": "oklch(0.696 0.17 162.48)"
    },
    "dark": {
      "success": "oklch(0.696 0.17 162.48)"
    }
  },
  "files": [
    {
      "path": "components/panelui/message.tsx",
      "type": "registry:component",
      "target": "@components/panelui/message.tsx",
      "content": "'use client';\n\n/**\n * Message — one turn of a conversation with an assistant.\n *\n * ```tsx\n * <Message from=\"user\">\n *   <Message.Content>\n *     <Message.Bubble>\n *       <Message.BubbleContent>What changed in the Q3 numbers?</Message.BubbleContent>\n *     </Message.Bubble>\n *   </Message.Content>\n * </Message>\n *\n * <Message from=\"assistant\">\n *   <Message.Content>\n *     <Message.Bubble>\n *       <Message.BubbleContent>Revenue rose 12%…</Message.BubbleContent>\n *     </Message.Bubble>\n *     <Message.Actions>\n *       <Message.Action label=\"Copy\" copy={text} />\n *     </Message.Actions>\n *   </Message.Content>\n * </Message>\n * ```\n *\n * ## Two kinds of turn, drawn differently\n *\n * What the person writes is short and is theirs, so it sits in a bubble on\n * the trailing side, the way a sent message does. What the assistant writes is\n * long, has paragraphs, lists and code, and is the thing being read, so it is\n * not boxed at all: it runs the full width as prose, and the page is its\n * surface. A bubble round a page of text only narrows it.\n *\n * ## Actions under the answer\n *\n * Copy, retry, rate and the switcher between versions of an answer sit in a\n * row under it. On the latest answer they stay in view, because that is the\n * one they are for. On earlier ones, `reveal=\"hover\"` keeps them out of the\n * way until the turn is hovered or focused, and on a touch screen, where\n * there is no hover, they always show.\n *\n * ## While it is being written\n *\n * `streaming` puts a caret after the last word and marks the turn busy, so a\n * screen reader waits for it to finish. Before the first word arrives,\n * `Message.Status` says what the assistant is doing, as a line that shimmers.\n */\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport {\n  createContext,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n  type ComponentProps,\n  type MouseEvent,\n  type ReactNode,\n} from 'react';\nimport { tv } from 'tailwind-variants';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport type { ElementType as RenderElementType } from 'react';\n\n/** Draws a part as another element, or composes it with a component: `render={<a href=\"/\" />}`. */\ntype RenderProp<Tag extends RenderElementType> = { render?: useRender.ComponentProps<Tag>['render'] };\n\nexport type MessageFrom = 'user' | 'assistant';\n\nconst messageVariants = tv({\n  slots: {\n    root: 'group/message flex w-full gap-3',\n    group: 'flex w-full flex-col gap-1.5',\n    avatar: 'flex w-8 shrink-0 justify-center pt-0.5 empty:invisible',\n    content: 'flex min-w-0 flex-col gap-1.5',\n    header: 'flex items-baseline gap-2 text-xs text-muted-foreground [&_strong]:font-medium [&_strong]:text-foreground',\n    bubble: 'min-w-0',\n    bubbleContent: [\n      'min-w-0 text-[0.9375rem] leading-7 text-foreground [overflow-wrap:anywhere]',\n      // Prose: what an answer is usually made of.\n      '[&_p]:my-3 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0',\n      '[&_ul]:my-3 [&_ul]:list-disc [&_ul]:ps-5 [&_ol]:my-3 [&_ol]:list-decimal [&_ol]:ps-5 [&_li]:my-1 [&_li]:ps-1 [&_li::marker]:text-muted-foreground',\n      // Links in the text; a component's own link inside it keeps its own look.\n      '[&_strong]:font-semibold [&_a:not([data-slot])]:underline [&_a:not([data-slot])]:underline-offset-4',\n      '[&_:not(pre)>code]:rounded-md [&_:not(pre)>code]:bg-muted [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-[0.8125rem]',\n      '[&_pre]:my-3 [&_pre]:overflow-x-auto [&_pre]:rounded-xl [&_pre]:border [&_pre]:bg-muted/50 [&_pre]:p-3.5 [&_pre]:font-mono [&_pre]:text-[0.8125rem] [&_pre]:leading-6',\n      '[&_h3]:mt-5 [&_h3]:mb-2 [&_h3]:font-semibold',\n    ],\n    // The caret is a soft block that breathes rather than blinks. Written out\n    // twice: after the text itself, and inside its last block.\n    caret:\n      \"after:ms-0.5 after:inline-block after:h-[1.05em] after:w-[0.5em] after:translate-y-[0.18em] after:rounded-[3px] after:bg-foreground after:content-[''] after:animate-pulse motion-reduce:after:animate-none\",\n    caretLast:\n      \"[&>:last-child]:after:ms-0.5 [&>:last-child]:after:inline-block [&>:last-child]:after:h-[1.05em] [&>:last-child]:after:w-[0.5em] [&>:last-child]:after:translate-y-[0.18em] [&>:last-child]:after:rounded-[3px] [&>:last-child]:after:bg-foreground [&>:last-child]:after:content-[''] [&>:last-child]:after:animate-pulse motion-reduce:[&>:last-child]:after:animate-none\",\n    footer: 'flex items-center gap-2 text-xs text-muted-foreground',\n    actions: [\n      '-ms-1.5 flex items-center gap-0.5 text-muted-foreground',\n      'transition-opacity duration-200 motion-reduce:transition-none',\n    ],\n    action: [\n      'inline-flex size-8 cursor-pointer items-center justify-center rounded-lg outline-none',\n      'transition-[background-color,color,scale] duration-150 hover:bg-muted hover:text-foreground active:scale-90',\n      'focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none',\n      'disabled:pointer-events-none disabled:opacity-40',\n      'aria-pressed:text-foreground [&_svg]:size-4 [&_svg]:shrink-0',\n    ],\n    // Copied: the check is drawn in the success colour, then the icon comes back.\n    actionDone: 'text-[var(--success)] hover:text-[var(--success)]',\n    tooltip: [\n      'z-50 rounded-md bg-foreground px-2 py-1 text-xs font-medium text-background shadow-md',\n      'origin-(--transform-origin) transition-[opacity,scale] duration-150 data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:opacity-0',\n      'motion-reduce:transition-none',\n    ],\n    branch: 'flex items-center text-xs tabular-nums text-muted-foreground',\n    branchButton: [\n      'inline-flex size-7 cursor-pointer items-center justify-center rounded-lg outline-none transition-colors',\n      'hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring',\n      'disabled:pointer-events-none disabled:opacity-35 [&_svg]:size-4',\n    ],\n    status: [\n      'inline-flex w-fit items-center gap-2 text-[0.9375rem] leading-7',\n      // A highlight sweeping across muted text: working, without a spinner.\n      'bg-[linear-gradient(90deg,var(--muted-foreground)_0%,var(--muted-foreground)_40%,var(--foreground)_50%,var(--muted-foreground)_60%,var(--muted-foreground)_100%)] bg-[length:250%_100%] bg-clip-text text-transparent',\n      'animate-[message-shimmer_2.2s_linear_infinite] motion-reduce:animate-none motion-reduce:bg-none motion-reduce:text-muted-foreground',\n    ],\n    attachments: 'flex flex-wrap gap-2',\n    attachment: 'flex items-center gap-2.5 overflow-hidden rounded-xl border bg-background text-xs',\n    attachmentImage: 'size-20 object-cover',\n    attachmentFile: 'h-14 max-w-60 py-2 pe-3.5 ps-2',\n    attachmentGlyph:\n      'flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground [&_svg]:size-4',\n    attachmentName: 'block truncate font-medium text-foreground',\n    attachmentMeta: 'block truncate text-muted-foreground',\n  },\n  variants: {\n    from: {\n      user: {\n        root: 'flex-row-reverse',\n        content: 'max-w-[min(80%,36rem)] items-end',\n        header: 'justify-end',\n        // A squared corner on the sender's side, at the top, where the turn starts.\n        bubble: 'rounded-[1.25rem] rounded-se-md bg-muted px-4 py-2.5',\n        bubbleContent: 'leading-6 [&_p]:my-2',\n        footer: 'flex-row-reverse',\n        attachments: 'justify-end',\n      },\n      assistant: {\n        content: 'flex-1',\n      },\n    },\n    reveal: {\n      always: {},\n      // Earlier answers: out of the way until the turn is hovered or focused,\n      // and always there on a touch screen, which has no hover.\n      hover: {\n        actions:\n          'opacity-0 group-hover/message:opacity-100 group-focus-within/message:opacity-100 has-data-popup-open:opacity-100 pointer-coarse:opacity-100',\n      },\n    },\n  },\n  defaultVariants: { from: 'assistant', reveal: 'always' },\n});\n\ninterface MessageContextValue {\n  from: MessageFrom;\n  streaming: boolean;\n}\n\nconst MessageContext = createContext<MessageContextValue>({ from: 'assistant', streaming: false });\nconst GroupContext = createContext<{ from: MessageFrom } | null>(null);\n\n/* -------------------------------------------------------------------------- */\n/* Message and Group                                                          */\n/* -------------------------------------------------------------------------- */\n\nexport interface MessageProps extends ComponentProps<'div'> {\n  /** Who wrote it. A person's turn is a bubble on the trailing side; the assistant's is prose. */\n  from?: MessageFrom;\n  /** Still being written: a caret follows the last word and the turn is marked busy. */\n  streaming?: boolean;\n}\n\n/** One turn. Put `Message.Avatar` and `Message.Content` in it. */\nfunction MessageRoot({ from: fromProp, streaming = false, className, ...props }: MessageProps) {\n  const group = useContext(GroupContext);\n  const from = fromProp ?? group?.from ?? 'assistant';\n  return (\n    <MessageContext.Provider value={{ from, streaming }}>\n      <div\n        data-slot=\"message\"\n        data-from={from}\n        data-streaming={streaming ? '' : undefined}\n        aria-busy={streaming || undefined}\n        className={messageVariants({ from }).root({ className })}\n        {...props}\n      />\n    </MessageContext.Provider>\n  );\n}\nMessageRoot.displayName = 'Message';\n\nexport interface MessageGroupProps extends ComponentProps<'div'> {\n  /** Who wrote every turn in it. */\n  from?: MessageFrom;\n}\n\n/**\n * Turns in a row from the same side, set closer together. Give the avatar to\n * the first and an empty `Message.Avatar` to the rest, so they line up.\n */\nfunction MessageGroup({ from = 'assistant', className, ...props }: MessageGroupProps) {\n  return (\n    <GroupContext.Provider value={{ from }}>\n      <div data-slot=\"message-group\" className={messageVariants().group({ className })} {...props} />\n    </GroupContext.Provider>\n  );\n}\nMessageGroup.displayName = 'Message.Group';\n\nexport interface MessageAvatarProps extends ComponentProps<'div'> {}\n\n/** Who it is from, beside the turn's first line. Leave it empty to keep the space. */\nfunction MessageAvatar({ className, render, ...props }: MessageAvatarProps & RenderProp<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps({ 'data-slot': 'message-avatar', className: messageVariants().avatar({ className }) }, props),\n  });\n}\nMessageAvatar.displayName = 'Message.Avatar';\n\nexport interface MessageContentProps extends ComponentProps<'div'> {}\n\n/** The column with the header, the bubble, the attachments and the actions. */\nfunction MessageContent({ className, render, ...props }: MessageContentProps & RenderProp<'div'>) {\n  const { from } = useContext(MessageContext);\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      { 'data-slot': 'message-content', className: messageVariants({ from }).content({ className }) },\n      props\n    ),\n  });\n}\nMessageContent.displayName = 'Message.Content';\n\nexport interface MessageHeaderProps extends ComponentProps<'div'> {}\n\n/** A line above the turn: a name in `<strong>`, a time, a model. */\nfunction MessageHeader({ className, render, ...props }: MessageHeaderProps & RenderProp<'div'>) {\n  const { from } = useContext(MessageContext);\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      { 'data-slot': 'message-header', className: messageVariants({ from }).header({ className }) },\n      props\n    ),\n  });\n}\nMessageHeader.displayName = 'Message.Header';\n\nexport interface MessageBubbleProps extends ComponentProps<'div'> {}\n\n/** The surface of a person's turn. On the assistant's it draws nothing, and the text is the page's. */\nfunction MessageBubble({ className, render, ...props }: MessageBubbleProps & RenderProp<'div'>) {\n  const { from } = useContext(MessageContext);\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      { 'data-slot': 'message-bubble', className: messageVariants({ from }).bubble({ className }) },\n      props\n    ),\n  });\n}\nMessageBubble.displayName = 'Message.Bubble';\n\nexport interface MessageBubbleContentProps extends ComponentProps<'div'> {}\n\n/**\n * The text. Paragraphs, lists, inline code, code blocks and headings inside\n * it are set as prose, so rendered Markdown can go straight in.\n */\nfunction MessageBubbleContent({\n  className,\n  children,\n  render,\n  ...props\n}: MessageBubbleContentProps & RenderProp<'div'>) {\n  const { from, streaming } = useContext(MessageContext);\n  const slots = messageVariants({ from });\n  // The caret follows the last word: after the text itself, or inside the last block.\n  const plain = typeof children === 'string' || typeof children === 'number';\n  const caret = streaming ? (plain ? slots.caret() : slots.caretLast()) : undefined;\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      {\n        'data-slot': 'message-bubble-content',\n        className: slots.bubbleContent({ className: [caret, className] }),\n        children: <>{children}</>,\n      },\n      props\n    ),\n  });\n}\nMessageBubbleContent.displayName = 'Message.BubbleContent';\n\nexport interface MessageFooterProps extends ComponentProps<'div'> {}\n\n/** A line under the turn: sent, read, a model and how long it took. */\nfunction MessageFooter({ className, render, ...props }: MessageFooterProps & RenderProp<'div'>) {\n  const { from } = useContext(MessageContext);\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      { 'data-slot': 'message-footer', className: messageVariants({ from }).footer({ className }) },\n      props\n    ),\n  });\n}\nMessageFooter.displayName = 'Message.Footer';\n\n/* -------------------------------------------------------------------------- */\n/* Actions                                                                    */\n/* -------------------------------------------------------------------------- */\n\nexport interface MessageActionsProps extends ComponentProps<'div'> {\n  /**\n   * `always`, the default, for the latest answer. `hover` for earlier ones:\n   * hidden until the turn is hovered or focused, and always shown on touch.\n   */\n  reveal?: 'always' | 'hover';\n}\n\n/** The row of actions under a turn. */\nfunction MessageActions({ reveal, className, ...props }: MessageActionsProps) {\n  return (\n    <Tooltip.Provider delay={400} closeDelay={0}>\n      <div\n        role=\"toolbar\"\n        aria-label=\"Message actions\"\n        data-slot=\"message-actions\"\n        className={messageVariants({ reveal }).actions({ className })}\n        {...props}\n      />\n    </Tooltip.Provider>\n  );\n}\nMessageActions.displayName = 'Message.Actions';\n\nexport interface MessageActionProps extends Omit<ComponentProps<'button'>, 'children'> {\n  /** Said by a screen reader and shown in the tooltip. Required, since the button is an icon. */\n  label: string;\n  /** The icon. Optional when `copy` is set, which draws its own. */\n  children?: ReactNode;\n  /**\n   * Text to put on the clipboard when pressed. The icon turns to a check for\n   * a moment, and the label to \"Copied\".\n   */\n  copy?: string | (() => string);\n  /** For a toggle such as a thumbs-up: whether it is on. */\n  pressed?: boolean;\n}\n\n/** An icon button with a tooltip: copy, retry, rate, share. */\nfunction MessageAction({ label, children, copy, pressed, onClick, className, ...props }: MessageActionProps) {\n  const [copied, setCopied] = useState(false);\n  const timer = useRef<ReturnType<typeof setTimeout>>(undefined);\n  useEffect(() => () => clearTimeout(timer.current), []);\n  const slots = messageVariants();\n\n  const press = (event: MouseEvent<HTMLButtonElement>) => {\n    onClick?.(event);\n    if (event.defaultPrevented || copy === undefined) return;\n    const text = typeof copy === 'function' ? copy() : copy;\n    // A browser can refuse the clipboard by throwing or by rejecting; either way the check still shows.\n    try {\n      void navigator.clipboard?.writeText(text).catch(() => {});\n    } catch {}\n    setCopied(true);\n    clearTimeout(timer.current);\n    timer.current = setTimeout(() => setCopied(false), 1600);\n  };\n\n  const shown = copied ? 'Copied' : label;\n  return (\n    <Tooltip.Root>\n      <Tooltip.Trigger\n        type=\"button\"\n        aria-label={shown}\n        aria-pressed={pressed}\n        data-slot=\"message-action\"\n        className={slots.action({ className: [copied && slots.actionDone(), className] })}\n        onClick={press}\n        {...props}\n      >\n        {copied ? <CheckGlyph /> : (children ?? (copy !== undefined ? <CopyGlyph /> : null))}\n      </Tooltip.Trigger>\n      <Tooltip.Portal>\n        <Tooltip.Positioner side=\"bottom\" sideOffset={6} className=\"z-50\">\n          <Tooltip.Popup className={slots.tooltip()}>{shown}</Tooltip.Popup>\n        </Tooltip.Positioner>\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  );\n}\nMessageAction.displayName = 'Message.Action';\n\nexport interface MessageBranchProps extends Omit<ComponentProps<'div'>, 'onChange'> {\n  /** How many versions of the answer there are. */\n  count: number;\n  /** Which one is shown, from 0. */\n  index: number;\n  /** Called with the index of the version asked for. */\n  onIndexChange: (index: number) => void;\n}\n\n/** \"‹ 2 / 3 ›\": steps between the versions of an answer that was retried or edited. */\nfunction MessageBranch({\n  count,\n  index,\n  onIndexChange,\n  className,\n  render,\n  ...props\n}: MessageBranchProps & RenderProp<'div'>) {\n  const slots = messageVariants();\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      {\n        role: 'group',\n        'aria-label': 'Versions',\n        'data-slot': 'message-branch',\n        className: slots.branch({ className }),\n        children: (\n          <>\n            <button\n              type=\"button\"\n              aria-label=\"Previous version\"\n              className={slots.branchButton()}\n              disabled={index <= 0}\n              onClick={() => onIndexChange(index - 1)}\n            >\n              <ChevronGlyph flip />\n            </button>\n            <span aria-live=\"polite\" className=\"min-w-9 text-center\">\n              {index + 1} / {count}\n            </span>\n            <button\n              type=\"button\"\n              aria-label=\"Next version\"\n              className={slots.branchButton()}\n              disabled={index >= count - 1}\n              onClick={() => onIndexChange(index + 1)}\n            >\n              <ChevronGlyph />\n            </button>\n          </>\n        ),\n      },\n      props\n    ),\n  });\n}\nMessageBranch.displayName = 'Message.Branch';\n\n/* -------------------------------------------------------------------------- */\n/* Status and attachments                                                     */\n/* -------------------------------------------------------------------------- */\n\nexport interface MessageStatusProps extends ComponentProps<'div'> {}\n\n/**\n * What the assistant is doing before its first word: \"Thinking\", \"Reading 4\n * files\". Announced as it changes. Replace it with the text once that arrives.\n */\nfunction MessageStatus({ className, ...props }: MessageStatusProps) {\n  return (\n    <>\n      {/* The sweep. Inline, so the component brings its own keyframes. */}\n      <style href=\"panelui-message-shimmer\" precedence=\"default\">\n        {'@keyframes message-shimmer{from{background-position:100% 0}to{background-position:-150% 0}}'}\n      </style>\n      <div role=\"status\" data-slot=\"message-status\" className={messageVariants().status({ className })} {...props} />\n    </>\n  );\n}\nMessageStatus.displayName = 'Message.Status';\n\nexport interface MessageAttachmentsProps extends ComponentProps<'div'> {}\n\n/** The files sent with a turn, above its bubble. */\nfunction MessageAttachments({ className, render, ...props }: MessageAttachmentsProps & RenderProp<'div'>) {\n  const { from } = useContext(MessageContext);\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      { 'data-slot': 'message-attachments', className: messageVariants({ from }).attachments({ className }) },\n      props\n    ),\n  });\n}\nMessageAttachments.displayName = 'Message.Attachments';\n\nexport interface MessageAttachmentProps extends Omit<ComponentProps<'div'>, 'children'> {\n  /** The file's name. */\n  name: string;\n  /** A picture to show in place of the file's card. */\n  src?: string;\n  /** A line under the name: its type and size. */\n  meta?: string;\n  /** The glyph on a file's card. A page outline when left out. */\n  icon?: ReactNode;\n}\n\n/** One sent file: a picture, or a card with a glyph, the name and its size. */\nfunction MessageAttachment({\n  name,\n  src,\n  meta,\n  icon,\n  className,\n  render,\n  ...props\n}: MessageAttachmentProps & RenderProp<'div'>) {\n  const slots = messageVariants();\n  // Called before any branch, so a prop that changes later cannot change the hook count.\n  const rendered = useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      {\n        'data-slot': 'message-attachment',\n        className: slots.attachment({ className: [slots.attachmentFile(), className] }),\n        children: (\n          <>\n            <span className={slots.attachmentGlyph()}>{icon ?? <FileGlyph />}</span>\n            <span className=\"min-w-0\">\n              <span className={slots.attachmentName()}>{name}</span>\n              {meta ? <span className={slots.attachmentMeta()}>{meta}</span> : null}\n            </span>\n          </>\n        ),\n      },\n      props\n    ),\n  });\n  if (src) {\n    return (\n      <div data-slot=\"message-attachment\" className={slots.attachment({ className })} {...props}>\n        <img src={src} alt={name} className={slots.attachmentImage()} />\n      </div>\n    );\n  }\n  return rendered;\n}\nMessageAttachment.displayName = 'Message.Attachment';\n\n/* -------------------------------------------------------------------------- */\n/* Glyphs                                                                     */\n/* -------------------------------------------------------------------------- */\n\nconst STROKE = {\n  fill: 'none',\n  stroke: 'currentColor',\n  strokeWidth: 2,\n  strokeLinecap: 'round' as const,\n  strokeLinejoin: 'round' as const,\n};\n\nfunction CopyGlyph() {\n  return (\n    <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" {...STROKE}>\n      <rect x=\"8\" y=\"8\" width=\"13\" height=\"13\" rx=\"3\" />\n      <path d=\"M16 8V6a3 3 0 0 0-3-3H6a3 3 0 0 0-3 3v7a3 3 0 0 0 3 3h2\" />\n    </svg>\n  );\n}\n\nfunction CheckGlyph() {\n  return (\n    <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" {...STROKE}>\n      <path d=\"M5 12.5l4.5 4.5L19 7.5\" />\n    </svg>\n  );\n}\n\nfunction ChevronGlyph({ flip = false }: { flip?: boolean }) {\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      aria-hidden=\"true\"\n      {...STROKE}\n      className={flip ? '-scale-x-100 rtl:scale-x-100' : 'rtl:-scale-x-100'}\n    >\n      <path d=\"M9 6l6 6-6 6\" />\n    </svg>\n  );\n}\n\nfunction FileGlyph() {\n  return (\n    <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" {...STROKE}>\n      <path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z\" />\n      <path d=\"M14 3v5h5\" />\n    </svg>\n  );\n}\n\nexport const Message = Object.assign(MessageRoot, {\n  Group: MessageGroup,\n  Avatar: MessageAvatar,\n  Content: MessageContent,\n  Header: MessageHeader,\n  Bubble: MessageBubble,\n  BubbleContent: MessageBubbleContent,\n  Footer: MessageFooter,\n  Actions: MessageActions,\n  Action: MessageAction,\n  Branch: MessageBranch,\n  Status: MessageStatus,\n  Attachments: MessageAttachments,\n  Attachment: MessageAttachment,\n});\n\nexport {\n  MessageGroup,\n  MessageAvatar,\n  MessageContent,\n  MessageHeader,\n  MessageBubble,\n  MessageBubbleContent,\n  MessageFooter,\n  MessageActions,\n  MessageAction,\n  MessageBranch,\n  MessageStatus,\n  MessageAttachments,\n  MessageAttachment,\n};\n"
    }
  ]
}
