{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "team-chat",
  "title": "Team Chat",
  "description": "Team chat interface with messages and channels.",
  "registryDependencies": [
    "avatar",
    "button",
    "card",
    "empty",
    "input-group",
    "separator"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/team/team-chat.tsx",
      "content": "\"use client\";\n\nimport { Bot, Loader2, Paperclip, Send } from \"lucide-react\";\nimport { useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Avatar,\n  AvatarFallback,\n  AvatarImage,\n} from \"@/registry/new-york/ui/avatar\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport {\n  Empty,\n  EmptyDescription,\n  EmptyHeader,\n  EmptyMedia,\n  EmptyTitle,\n} from \"@/registry/new-york/ui/empty\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupTextarea,\n} from \"@/registry/new-york/ui/input-group\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\n\nexport interface ChatMessage {\n  id: string;\n  content: string;\n  author: {\n    id: string;\n    name: string;\n    avatar?: string;\n  };\n  timestamp: Date;\n  mentions?: string[];\n  attachments?: {\n    id: string;\n    name: string;\n    url: string;\n    type: string;\n  }[];\n  isAIMention?: boolean;\n}\n\nexport interface TeamChatProps {\n  messages?: ChatMessage[];\n  currentUserId?: string;\n  onSendMessage?: (content: string, mentions?: string[]) => Promise<void>;\n  onMentionAI?: () => void;\n  onUploadFile?: (file: File) => Promise<string>;\n  className?: string;\n  placeholder?: string;\n  showAIButton?: boolean;\n}\n\nfunction formatTime(date: Date): string {\n  return new Intl.DateTimeFormat(\"en-US\", {\n    hour: \"numeric\",\n    minute: \"2-digit\",\n  }).format(date);\n}\n\nfunction formatDate(date: Date): string {\n  const today = new Date();\n  const yesterday = new Date(today);\n  yesterday.setDate(yesterday.getDate() - 1);\n\n  if (date.toDateString() === today.toDateString()) {\n    return \"Today\";\n  }\n  if (date.toDateString() === yesterday.toDateString()) {\n    return \"Yesterday\";\n  }\n  return new Intl.DateTimeFormat(\"en-US\", {\n    month: \"short\",\n    day: \"numeric\",\n  }).format(date);\n}\n\nfunction getInitials(name: string): string {\n  return name\n    .split(\" \")\n    .map((n) => n[0])\n    .join(\"\")\n    .toUpperCase()\n    .slice(0, 2);\n}\n\nexport default function TeamChat({\n  messages = [],\n  currentUserId,\n  onSendMessage,\n  onMentionAI,\n  onUploadFile,\n  className,\n  placeholder = \"Type a message…\",\n  showAIButton = true,\n}: TeamChatProps) {\n  const [input, setInput] = useState(\"\");\n  const [isSending, setIsSending] = useState(false);\n  const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n  const handleSend = async () => {\n    if (!input.trim() || isSending) return;\n\n    const content = input.trim();\n    setInput(\"\");\n    setIsSending(true);\n\n    try {\n      // Extract mentions (e.g., @username or @ai)\n      const mentionRegex = /@(\\w+)/g;\n      const mentions: string[] = [];\n      let match;\n      while ((match = mentionRegex.exec(content)) !== null) {\n        mentions.push(match[1]);\n      }\n\n      await onSendMessage?.(content, mentions);\n    } catch (error) {\n      // Restore input on error\n      setInput(content);\n    } finally {\n      setIsSending(false);\n      textareaRef.current?.focus();\n    }\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n    if ((e.metaKey || e.ctrlKey) && e.key === \"Enter\") {\n      e.preventDefault();\n      handleSend();\n    }\n  };\n\n  const groupedMessages = messages.reduce(\n    (groups, message) => {\n      const dateKey = formatDate(message.timestamp);\n      if (!groups[dateKey]) {\n        groups[dateKey] = [];\n      }\n      groups[dateKey].push(message);\n      return groups;\n    },\n    {} as Record<string, ChatMessage[]>\n  );\n\n  return (\n    <Card className={cn(\"flex h-[600px] flex-col shadow-xs\", className)}>\n      <CardHeader className=\"shrink-0\">\n        <div className=\"flex flex-col gap-1\">\n          <CardTitle>Team Chat</CardTitle>\n          <CardDescription>\n            Chat with your team members in real-time\n          </CardDescription>\n        </div>\n      </CardHeader>\n      <CardContent className=\"flex min-h-0 flex-1 flex-col gap-0 overflow-hidden\">\n        {/* Messages */}\n        <div className=\"flex-1 overflow-y-auto py-4\">\n          {messages.length === 0 ? (\n            <Empty>\n              <EmptyHeader>\n                <EmptyMedia variant=\"icon\">\n                  <Send className=\"size-6\" />\n                </EmptyMedia>\n                <EmptyTitle>No messages yet</EmptyTitle>\n                <EmptyDescription>Start the conversation!</EmptyDescription>\n              </EmptyHeader>\n            </Empty>\n          ) : (\n            <div className=\"flex flex-col gap-6\">\n              {Object.entries(groupedMessages).map(\n                ([dateKey, dateMessages]) => (\n                  <div key={dateKey}>\n                    <div className=\"relative mb-4\">\n                      <Separator />\n                      <div className=\"absolute inset-0 flex items-center justify-center\">\n                        <span className=\"bg-background px-2 text-muted-foreground text-xs\">\n                          {dateKey}\n                        </span>\n                      </div>\n                    </div>\n                    {dateMessages.map((message, idx) => {\n                      const isCurrentUser = message.author.id === currentUserId;\n                      const showAvatar =\n                        idx === 0 ||\n                        dateMessages[idx - 1].author.id !== message.author.id;\n\n                      return (\n                        <div\n                          className={cn(\n                            \"my-8 flex gap-3\",\n                            isCurrentUser && \"flex-row-reverse\"\n                          )}\n                          key={message.id}\n                        >\n                          {showAvatar ? (\n                            <Avatar className=\"size-8 shrink-0\">\n                              <AvatarImage\n                                alt={message.author.name}\n                                src={message.author.avatar}\n                              />\n                              <AvatarFallback className=\"text-xs\">\n                                {getInitials(message.author.name)}\n                              </AvatarFallback>\n                            </Avatar>\n                          ) : (\n                            <div className=\"w-8\" />\n                          )}\n                          <div\n                            className={cn(\n                              \"flex min-w-0 flex-1 flex-col gap-2\",\n                              isCurrentUser && \"items-end\"\n                            )}\n                          >\n                            {showAvatar && (\n                              <div className=\"flex items-center gap-2\">\n                                <span className=\"font-medium text-sm\">\n                                  {message.author.name}\n                                </span>\n                                <span className=\"text-muted-foreground text-xs\">\n                                  {formatTime(message.timestamp)}\n                                </span>\n                              </div>\n                            )}\n                            <div\n                              className={cn(\n                                \"max-w-[80%] rounded-lg px-3 py-2\",\n                                isCurrentUser\n                                  ? \"bg-primary text-primary-foreground\"\n                                  : \"bg-muted\",\n                                !showAvatar && \"ml-11\"\n                              )}\n                            >\n                              <p className=\"wrap-break-word whitespace-pre-wrap text-sm\">\n                                {message.content}\n                              </p>\n                              {message.isAIMention && (\n                                <div className=\"mt-2 flex items-center gap-2 rounded bg-background/20 px-2 py-1\">\n                                  <Bot className=\"size-3\" />\n                                  <span className=\"text-xs\">AI mentioned</span>\n                                </div>\n                              )}\n                            </div>\n                          </div>\n                        </div>\n                      );\n                    })}\n                  </div>\n                )\n              )}\n            </div>\n          )}\n        </div>\n\n        {/* Input */}\n        <div className=\"shrink-0 rounded-lg bg-muted/30 p-4\">\n          <div className=\"flex items-center\">\n            <div className=\"flex shrink-0 items-center gap-1\">\n              {onUploadFile && (\n                <Button\n                  aria-label=\"Attach file\"\n                  className=\"size-9\"\n                  size=\"icon\"\n                  type=\"button\"\n                  variant=\"ghost\"\n                >\n                  <Paperclip className=\"size-4\" />\n                </Button>\n              )}\n              {showAIButton && onMentionAI && (\n                <Button\n                  aria-label=\"Mention AI\"\n                  className=\"size-9\"\n                  onClick={onMentionAI}\n                  size=\"icon\"\n                  type=\"button\"\n                  variant=\"ghost\"\n                >\n                  <Bot className=\"size-4\" />\n                </Button>\n              )}\n            </div>\n            <div className=\"relative flex min-w-0 flex-1 flex-col gap-2.5\">\n              <InputGroup>\n                <InputGroupTextarea\n                  className=\"min-h-[52px] resize-none pr-12\"\n                  onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>\n                    setInput(e.target.value)\n                  }\n                  onKeyDown={handleKeyDown}\n                  placeholder={placeholder}\n                  ref={textareaRef}\n                  rows={1}\n                  value={input}\n                />\n                <InputGroupAddon align=\"inline-end\">\n                  <InputGroupButton\n                    aria-busy={isSending}\n                    className=\"size-9\"\n                    data-loading={isSending}\n                    disabled={!input.trim() || isSending}\n                    onClick={handleSend}\n                    type=\"button\"\n                  >\n                    {isSending ? (\n                      <Loader2 className=\"size-4 animate-spin\" />\n                    ) : (\n                      <Send className=\"size-4\" />\n                    )}\n                  </InputGroupButton>\n                </InputGroupAddon>\n              </InputGroup>\n              <div className=\"flex items-center justify-between px-1\">\n                <span className=\"text-muted-foreground text-xs\">\n                  ⌘ + Enter to send\n                </span>\n                {input.trim() && (\n                  <span className=\"text-muted-foreground text-xs\">\n                    {input.length} character{input.length !== 1 ? \"s\" : \"\"}\n                  </span>\n                )}\n              </div>\n            </div>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "team"
  ],
  "type": "registry:ui"
}