{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "message",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "message.tsx",
      "content": "\"use client\";\n\nimport { motion, useReducedMotion } from \"motion/react\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype MessageAlign = \"start\" | \"end\";\ntype MessageBubbleVariant = \"default\" | \"primary\" | \"ghost\";\n\ntype MessageGroupProps = React.ComponentProps<\"div\">;\n\ntype MessageProps = React.ComponentProps<typeof motion.div> & {\n  /** Which side the message belongs to. \"end\" reads as sent — row reversed,\n   *  entrance from the right. \"start\" reads as received — entrance from\n   *  the left. */\n  align?: MessageAlign;\n  /** Play the slide-in entrance. Set to false for history that is already\n   *  on screen, so only newly arriving messages animate. */\n  animated?: boolean;\n};\n\ntype MessageBubbleProps = React.ComponentProps<\"div\"> & {\n  /** \"default\" is a muted pill for received messages, \"primary\" a filled\n   *  primary pill for sent messages, and \"ghost\" drops the pill entirely\n   *  for bare text like streamed AI replies. */\n  variant?: MessageBubbleVariant;\n};\n\nfunction MessageGroup({ className, ...props }: MessageGroupProps) {\n  return (\n    <div\n      className={cn(\n        // clip (not hidden) swallows the sideways entrance travel without\n        // creating a scroll container, so a message sliding in can never\n        // flash a horizontal scrollbar — while vertical overflow like the\n        // avatar's footer lift stays visible.\n        \"flex min-w-0 flex-col gap-2 overflow-x-clip\",\n        className\n      )}\n      data-slot=\"message-group\"\n      {...props}\n    />\n  );\n}\n\n/** The slide distance in px. Small on purpose: the message should feel like\n *  it surfaces beside the composer, not like it crosses the screen. */\nconst ENTER_OFFSET = 24;\n\n/** One spring drives x, scale, and the corner morph together, so the bubble\n *  arrives as a single fluid gesture. Damping is tuned to let it overshoot\n *  just slightly and settle in one soft bounce — noticeable, not springy. */\nconst ENTER_TRANSITION = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 20,\n  mass: 0.9,\n  opacity: { duration: 0.25, ease: \"easeOut\" },\n} as const;\n\n/** The resting pose. Always passed to `animate`, so values resolve to their\n *  final state on mount no matter how the row was server-rendered. */\nconst ENTER_TARGET = { opacity: 1, scale: 1, x: 0 };\n\nfunction Message({\n  className,\n  align = \"start\",\n  animated = true,\n  ...props\n}: MessageProps) {\n  // Reduced motion drops the entrance entirely — the message just appears.\n  const reduceMotion = useReducedMotion() ?? false;\n  const enter = animated && !reduceMotion;\n\n  return (\n    <motion.div\n      animate={ENTER_TARGET}\n      className={cn(\n        // The transform origin sits at the bubble's tail corner, so the\n        // slight scale-up reads as the bubble morphing out of that corner\n        // rather than the whole row zooming.\n        \"group/message relative flex w-full min-w-0 origin-bottom-left gap-2 text-sm data-[align=end]:origin-bottom-right data-[align=end]:flex-row-reverse\",\n        className\n      )}\n      data-align={align}\n      data-slot=\"message\"\n      // `false` is Motion's \"mount already settled\" — no entrance styles are\n      // rendered at all, so static rows never flash or hydrate mismatched.\n      initial={\n        enter\n          ? {\n              opacity: 0,\n              scale: 0.9,\n              x: align === \"end\" ? ENTER_OFFSET : -ENTER_OFFSET,\n            }\n          : false\n      }\n      transition={ENTER_TRANSITION}\n      {...props}\n    />\n  );\n}\n\nfunction MessageAvatar({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      className={cn(\n        \"flex w-fit min-w-8 shrink-0 items-center justify-center self-end overflow-hidden rounded-full bg-muted group-has-data-[slot=message-footer]/message:-translate-y-8\",\n        className\n      )}\n      data-slot=\"message-avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      className={cn(\n        \"wrap-break-word flex w-full min-w-0 flex-col gap-2.5 group-data-[align=end]/message:*:data-slot:self-end\",\n        className\n      )}\n      data-slot=\"message-content\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageBubble({\n  className,\n  variant = \"default\",\n  ...props\n}: MessageBubbleProps) {\n  return (\n    <div\n      className={cn(\n        \"wrap-break-word w-fit max-w-full rounded-3xl px-4 py-2.5\",\n        variant === \"default\" && \"bg-muted text-foreground\",\n        variant === \"primary\" && \"bg-primary text-primary-foreground\",\n        variant === \"ghost\" && \"rounded-none px-0 py-0\",\n        className\n      )}\n      data-slot=\"message-bubble\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction MessageHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      className={cn(\n        \"flex min-w-0 max-w-full items-center px-3 font-medium text-muted-foreground text-xs group-has-data-[variant=ghost]/message:px-0\",\n        className\n      )}\n      data-slot=\"message-header\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      className={cn(\n        \"flex min-w-0 max-w-full items-center px-3 font-medium text-muted-foreground text-xs group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end\",\n        className\n      )}\n      data-slot=\"message-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  MessageGroup,\n  Message,\n  MessageAvatar,\n  MessageBubble,\n  MessageContent,\n  MessageFooter,\n  MessageHeader,\n};\nexport type {\n  MessageAlign,\n  MessageBubbleProps,\n  MessageBubbleVariant,\n  MessageGroupProps,\n  MessageProps,\n};\n",
      "type": "registry:ui"
    }
  ],
  "title": "Message",
  "description": "Composable chat message primitives — group, row, avatar, bubble, header, and footer — where each message slides in beside its composer with a bouncy spring: sent messages arrive from the right, received from the left, morphing up from the bubble's tail corner and bouncing back once as they land, plus reduced-motion aware behavior."
}
