{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "chat-message",
	"type": "registry:ui",
	"title": "Chat Message",
	"description": "A fully composable component for displaying chat messages with rich features like timestamps, actions, and threading.",
	"registryDependencies": [
		"@simple-ai/markdown-content",
		"avatar",
		"button",
		"card",
		"tooltip"
	],
	"files": [
		{
			"path": "./src/registry/ui/chat-message.tsx",
			"content": "\"use client\";\n\nimport {\n\tCheck,\n\tChevronRight,\n\tCopy,\n\tSparklesIcon,\n\tUserIcon,\n} from \"lucide-react\";\nimport { type ComponentProps, type ReactNode, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport {\n\tTooltip,\n\tTooltipContent,\n\tTooltipProvider,\n\tTooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { MarkdownContent } from \"@/registry/ui/markdown-content\";\n\nfunction ChatMessage({ className, ...props }: ComponentProps<\"div\">) {\n\treturn (\n\t\t<TooltipProvider>\n\t\t\t<div\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"group/chat-message relative flex w-full gap-2.5 py-2 px-3 rounded-md hover:bg-muted/50\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</TooltipProvider>\n\t);\n}\n\nfunction ChatMessageContainer({ className, ...props }: ComponentProps<\"div\">) {\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\"flex w-full flex-col items-start gap-1\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction ChatMessageHeader({ className, ...props }: ComponentProps<\"div\">) {\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\"flex items-center gap-2 px-2 text-sm\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction ChatMessageAuthor({ className, ...props }: ComponentProps<\"span\">) {\n\treturn (\n\t\t<span\n\t\t\tclassName={cn(\"font-medium text-foreground\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\ninterface ChatMessageTimestampProps extends ComponentProps<\"span\"> {\n\tcreatedAt: number | Date | string;\n\tformat?: Intl.DateTimeFormatOptions;\n}\n\nfunction ChatMessageTimestamp({\n\tclassName,\n\tcreatedAt,\n\tformat = { hour: \"numeric\", minute: \"numeric\" },\n\t...props\n}: ChatMessageTimestampProps) {\n\tconst date = createdAt instanceof Date ? createdAt : new Date(createdAt);\n\n\treturn (\n\t\t<Tooltip>\n\t\t\t<TooltipTrigger asChild>\n\t\t\t\t<span\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"text-xs text-muted-foreground cursor-default\",\n\t\t\t\t\t\tclassName,\n\t\t\t\t\t)}\n\t\t\t\t\t{...props}\n\t\t\t\t>\n\t\t\t\t\t{date.toLocaleTimeString(\"en-US\", format)}\n\t\t\t\t</span>\n\t\t\t</TooltipTrigger>\n\t\t\t<TooltipContent>\n\t\t\t\t<p>{date.toLocaleString()}</p>\n\t\t\t</TooltipContent>\n\t\t</Tooltip>\n\t);\n}\n\nfunction ChatMessageContent({ className, ...props }: ComponentProps<\"div\">) {\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\"w-full flex flex-col gap-2 p-2\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\ninterface ChatMessageMarkdownProps {\n\tcontent: string;\n\tclassName?: string;\n}\n\nfunction ChatMessageMarkdown({ content, className }: ChatMessageMarkdownProps) {\n\treturn <MarkdownContent content={content || \"\"} className={className} />;\n}\n\nfunction ChatMessageFooter({ className, ...props }: ComponentProps<\"div\">) {\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\n\t\t\t\t\"mt-1 flex items-center gap-2 px-2 text-xs text-muted-foreground\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction ChatMessageActions({\n\tclassName,\n\tchildren,\n\t...props\n}: ComponentProps<typeof Card>) {\n\treturn (\n\t\t<Card\n\t\t\tclassName={cn(\n\t\t\t\t\"absolute -top-5 right-5 z-20 flex flex-row gap-1 p-1 opacity-0 transition-opacity group-hover/chat-message:opacity-100\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t<TooltipProvider>{children}</TooltipProvider>\n\t\t</Card>\n\t);\n}\n\ninterface ChatMessageActionProps extends ComponentProps<typeof Button> {\n\tclassName?: string;\n\tchildren?: ReactNode;\n\tlabel: string;\n}\n\nfunction ChatMessageAction({\n\tclassName,\n\tchildren,\n\tlabel,\n\t...props\n}: ChatMessageActionProps) {\n\treturn (\n\t\t<Tooltip>\n\t\t\t<TooltipTrigger asChild>\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\tclassName={cn(\"h-7 w-7\", className)}\n\t\t\t\t\t{...props}\n\t\t\t\t>\n\t\t\t\t\t{children}\n\t\t\t\t\t<span className=\"sr-only\">{label}</span>\n\t\t\t\t</Button>\n\t\t\t</TooltipTrigger>\n\t\t\t<TooltipContent>\n\t\t\t\t<p>{label}</p>\n\t\t\t</TooltipContent>\n\t\t</Tooltip>\n\t);\n}\n\nfunction ChatMessageActionCopy({\n\tclassName,\n\tonClick,\n\t...props\n}: ComponentProps<typeof Button>) {\n\tconst [hasCopied, setHasCopied] = useState(false);\n\treturn (\n\t\t<ChatMessageAction\n\t\t\tlabel=\"Copy\"\n\t\t\tonClick={(e) => {\n\t\t\t\tonClick?.(e);\n\t\t\t\tsetHasCopied(true);\n\t\t\t\tsetTimeout(() => setHasCopied(false), 2000);\n\t\t\t}}\n\t\t\t{...props}\n\t\t>\n\t\t\t{hasCopied ? (\n\t\t\t\t<Check className=\"size-4\" />\n\t\t\t) : (\n\t\t\t\t<Copy className=\"size-4\" />\n\t\t\t)}\n\t\t</ChatMessageAction>\n\t);\n}\n\nfunction ChatMessageAvatar(props: ComponentProps<typeof Avatar>) {\n\treturn (\n\t\t<Avatar\n\t\t\tclassName=\"[&_svg]:size-4 [&:has(svg)]:border [&:has(svg)]:border-border [&:has(svg)]:items-center [&:has(svg)]:justify-center\"\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction ChatMessageAvatarFallback(\n\tprops: ComponentProps<typeof AvatarFallback>,\n) {\n\treturn <AvatarFallback {...props} />;\n}\n\nfunction ChatMessageAvatarImage(props: ComponentProps<typeof AvatarImage>) {\n\treturn <AvatarImage {...props} />;\n}\n\nfunction ChatMessageAvatarUserIcon(props: ComponentProps<typeof UserIcon>) {\n\treturn <UserIcon {...props} />;\n}\n\nfunction ChatMessageAvatarAssistantIcon(\n\tprops: ComponentProps<typeof SparklesIcon>,\n) {\n\treturn <SparklesIcon {...props} />;\n}\n\nfunction ChatMessageThread({\n\tclassName,\n\t...props\n}: ComponentProps<typeof Button>) {\n\treturn (\n\t\t<Button\n\t\t\tvariant=\"ghost\"\n\t\t\tclassName={cn(\n\t\t\t\t\"group/button flex h-auto w-full border border-none items-center justify-start gap-2 px-2 py-1.5 transition-all\",\n\t\t\t\t\"hover:border-input hover:bg-background hover:shadow-sm\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction ChatMessageThreadReplyCount(props: ComponentProps<\"span\">) {\n\treturn <span className=\"text-sm font-medium\" {...props} />;\n}\n\ninterface ChatMessageThreadTimestampProps extends ComponentProps<\"span\"> {\n\tdate: Date | number | string;\n}\nfunction ChatMessageThreadTimestamp({\n\tdate: dateProp,\n\t...props\n}: ChatMessageThreadTimestampProps) {\n\tconst date = new Date(dateProp).toLocaleTimeString([], {\n\t\thour: \"2-digit\",\n\t\tminute: \"2-digit\",\n\t});\n\treturn (\n\t\t<span\n\t\t\tclassName=\"block text-sm text-muted-foreground group-hover/button:hidden\"\n\t\t\t{...props}\n\t\t>\n\t\t\tLast reply at {date}\n\t\t</span>\n\t);\n}\n\nfunction ChatMessageThreadAction(props: ComponentProps<\"span\">) {\n\treturn (\n\t\t<span\n\t\t\tclassName=\"hidden w-full items-center gap-1 text-sm text-muted-foreground group-hover/button:flex\"\n\t\t\t{...props}\n\t\t>\n\t\t\tView thread\n\t\t\t<ChevronRight className=\"ml-auto h-4 w-4\" />\n\t\t</span>\n\t);\n}\n\nexport {\n\tChatMessage,\n\tChatMessageAvatar,\n\tChatMessageAvatarImage,\n\tChatMessageAvatarFallback,\n\tChatMessageAvatarUserIcon,\n\tChatMessageAvatarAssistantIcon,\n\tChatMessageContainer,\n\tChatMessageHeader,\n\tChatMessageAuthor,\n\tChatMessageTimestamp,\n\tChatMessageContent,\n\tChatMessageMarkdown,\n\tChatMessageFooter,\n\tChatMessageActions,\n\tChatMessageAction,\n\tChatMessageActionCopy,\n\tChatMessageThread,\n\tChatMessageThreadReplyCount,\n\tChatMessageThreadTimestamp,\n\tChatMessageThreadAction,\n};\n",
			"type": "registry:ui"
		}
	]
}
