{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "comment-thread-shadcnui",
  "type": "registry:component",
  "title": "Comment Thread",
  "description": "Nested comment thread with rich interactions and animations",
  "registryDependencies": [
    "button"
  ],
  "dependencies": [
    "framer-motion",
    "react"
  ],
  "files": [
    {
      "path": "@uitripled/react-shadcn/src/components/components/comments/comment-thread.tsx",
      "content": "\"use client\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn } from \"@/lib/utils\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport {\n  CornerDownRight,\n  Heart,\n  Image as ImageIcon,\n  MessageCircle,\n  MoreHorizontal,\n  Paperclip,\n  Send,\n  Share2,\n  Smile,\n} from \"lucide-react\";\nimport { useEffect, useRef, useState } from \"react\";\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface User {\n  name: string;\n  avatar: string;\n  role?: string;\n}\n\ninterface Comment {\n  id: string;\n  user: User;\n  content: string;\n  timestamp: string;\n  likes: number;\n  replies?: Comment[];\n  isLiked?: boolean;\n}\n\n// ============================================================================\n// DUMMY DATA\n// ============================================================================\n\nconst INITIAL_COMMENTS: Comment[] = [\n  {\n    id: \"1\",\n    user: {\n      name: \"Alex Morgan\",\n      avatar: \"https://i.pravatar.cc/150?u=alex\",\n      role: \"Product Designer\",\n    },\n    content:\n      \"The new glassmorphism trend is really interesting. I love how it adds depth without cluttering the interface. Has anyone tried implementing this with pure CSS vs using backdrop-filter?\",\n    timestamp: \"2h ago\",\n    likes: 24,\n    isLiked: true,\n    replies: [\n      {\n        id: \"1-1\",\n        user: {\n          name: \"Sarah Chen\",\n          avatar: \"https://i.pravatar.cc/150?u=sarah\",\n          role: \"Frontend Dev\",\n        },\n        content:\n          \"I've been using backdrop-filter extensively. It's much more performant now across modern browsers. The only catch is Firefox sometimes needs a fallback.\",\n        timestamp: \"1h ago\",\n        likes: 12,\n        isLiked: false,\n        replies: [],\n      },\n      {\n        id: \"1-2\",\n        user: {\n          name: \"Mike Ross\",\n          avatar: \"https://i.pravatar.cc/150?u=mike\",\n        },\n        content:\n          \"Agreed! It gives such a premium feel. I usually pair it with subtle noise textures to avoid banding.\",\n        timestamp: \"45m ago\",\n        likes: 8,\n        isLiked: false,\n        replies: [],\n      },\n    ],\n  },\n  {\n    id: \"2\",\n    user: {\n      name: \"Emily Watson\",\n      avatar: \"https://i.pravatar.cc/150?u=emily\",\n      role: \"UX Researcher\",\n    },\n    content:\n      \"Great article! I'm curious about the accessibility implications of these high-contrast dark modes. Do we have any data on user preference?\",\n    timestamp: \"3h ago\",\n    likes: 45,\n    isLiked: false,\n    replies: [],\n  },\n];\n\n// ============================================================================\n// COMPONENTS\n// ============================================================================\n\nfunction CommentInput({\n  placeholder = \"What are your thoughts?\",\n  onSubmit,\n  onCancel,\n  autoFocus = false,\n  className,\n  inputId,\n  labelId,\n}: {\n  placeholder?: string;\n  onSubmit: (content: string) => void;\n  onCancel?: () => void;\n  autoFocus?: boolean;\n  className?: string;\n  inputId?: string;\n  labelId?: string;\n}) {\n  const [content, setContent] = useState(\"\");\n  const [isFocused, setIsFocused] = useState(autoFocus);\n  const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n  useEffect(() => {\n    if (autoFocus && textareaRef.current) {\n      textareaRef.current.focus();\n    }\n  }, [autoFocus]);\n\n  const handleSubmit = () => {\n    if (!content.trim()) return;\n    onSubmit(content);\n    setContent(\"\");\n    setIsFocused(false);\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n    if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n      e.preventDefault();\n      handleSubmit();\n    } else if (e.key === \"Escape\" && onCancel) {\n      e.preventDefault();\n      onCancel();\n    }\n  };\n\n  const uniqueId =\n    inputId || `comment-input-${Math.random().toString(36).substr(2, 9)}`;\n  const uniqueLabelId =\n    labelId || `comment-label-${Math.random().toString(36).substr(2, 9)}`;\n\n  return (\n    <div\n      className={cn(\n        \"relative rounded-xl border bg-background/50 backdrop-blur-sm transition-all duration-200\",\n        isFocused\n          ? \"border-primary/50 ring-4 ring-primary/5 shadow-lg\"\n          : \"border-border/40\",\n        className\n      )}\n      role=\"form\"\n      aria-label=\"Comment input\"\n    >\n      <div className=\"p-4\">\n        <div className=\"flex gap-4\">\n          <Avatar\n            className=\"h-8 w-8 border border-border/50\"\n            aria-hidden=\"true\"\n          >\n            <AvatarImage src=\"https://github.com/shadcn.png\" alt=\"\" />\n            <AvatarFallback>YO</AvatarFallback>\n          </Avatar>\n          <div className=\"flex-1\">\n            <label htmlFor={uniqueId} id={uniqueLabelId} className=\"sr-only\">\n              {placeholder}\n            </label>\n            <Textarea\n              id={uniqueId}\n              ref={textareaRef}\n              placeholder={placeholder}\n              value={content}\n              onChange={(e) => setContent(e.target.value)}\n              onFocus={() => setIsFocused(true)}\n              onKeyDown={handleKeyDown}\n              autoFocus={autoFocus}\n              aria-label={placeholder}\n              aria-describedby={uniqueLabelId}\n              className=\"min-h-[60px] border-none bg-transparent p-0 resize-none focus-visible:ring-0 placeholder:text-muted-foreground/70 text-sm\"\n            />\n          </div>\n        </div>\n      </div>\n\n      {/* Toolbar */}\n      <div\n        className=\"flex items-center justify-between px-4 py-2 border-t border-border/30 bg-muted/20 rounded-b-xl\"\n        role=\"toolbar\"\n        aria-label=\"Comment formatting options\"\n      >\n        <div\n          className=\"flex items-center gap-1\"\n          role=\"group\"\n          aria-label=\"Attachments\"\n        >\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            type=\"button\"\n            className=\"h-7 w-7 text-muted-foreground hover:text-foreground\"\n            aria-label=\"Add image\"\n            title=\"Add image\"\n          >\n            <ImageIcon className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            type=\"button\"\n            className=\"h-7 w-7 text-muted-foreground hover:text-foreground\"\n            aria-label=\"Attach file\"\n            title=\"Attach file\"\n          >\n            <Paperclip className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            type=\"button\"\n            className=\"h-7 w-7 text-muted-foreground hover:text-foreground\"\n            aria-label=\"Add emoji\"\n            title=\"Add emoji\"\n          >\n            <Smile className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n          </Button>\n        </div>\n        <div className=\"flex items-center gap-2\">\n          {onCancel && (\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              type=\"button\"\n              onClick={onCancel}\n              className=\"text-xs h-8\"\n              aria-label=\"Cancel reply\"\n            >\n              Cancel\n            </Button>\n          )}\n          <Button\n            onClick={handleSubmit}\n            disabled={!content.trim()}\n            size=\"sm\"\n            type=\"submit\"\n            className=\"gap-2 transition-all h-8 text-xs\"\n            aria-label={onCancel ? \"Submit reply\" : \"Submit comment\"}\n          >\n            {onCancel ? \"Reply\" : \"Post\"}\n            <Send className=\"h-3 w-3\" aria-hidden=\"true\" />\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction CommentItem({\n  comment,\n  isReply = false,\n  activeReplyId,\n  setActiveReplyId,\n  onAddReply,\n}: {\n  comment: Comment;\n  isReply?: boolean;\n  activeReplyId: string | null;\n  setActiveReplyId: (id: string | null) => void;\n  onAddReply: (parentId: string, content: string) => void;\n}) {\n  const [isLiked, setIsLiked] = useState(comment.isLiked);\n  const [likesCount, setLikesCount] = useState(comment.likes);\n  const [isExpanded, setIsExpanded] = useState(true);\n  const replyInputRef = useRef<HTMLDivElement>(null);\n\n  const handleLike = () => {\n    if (isLiked) {\n      setLikesCount((prev) => prev - 1);\n    } else {\n      setLikesCount((prev) => prev + 1);\n    }\n    setIsLiked(!isLiked);\n  };\n\n  const isReplying = activeReplyId === comment.id;\n\n  useEffect(() => {\n    if (isReplying && replyInputRef.current) {\n      const textarea = replyInputRef.current.querySelector(\"textarea\");\n      if (textarea) {\n        setTimeout(() => textarea.focus(), 100);\n      }\n    }\n  }, [isReplying]);\n\n  const commentId = `comment-${comment.id}`;\n  const repliesId = `replies-${comment.id}`;\n\n  return (\n    <motion.article\n      initial={{ opacity: 0, y: 10 }}\n      animate={{ opacity: 1, y: 0 }}\n      exit={{ opacity: 0, height: 0 }}\n      className={cn(\n        \"relative group\",\n        isReply ? \"ml-8 pl-4 border-l-2 border-border/40\" : \"mb-6\"\n      )}\n      id={commentId}\n      aria-label={`Comment by ${comment.user.name}`}\n    >\n      <div className=\"flex gap-4\">\n        <Avatar\n          className={cn(\n            \"border border-border/50\",\n            isReply ? \"h-8 w-8\" : \"h-10 w-10\"\n          )}\n        >\n          <AvatarImage\n            src={comment.user.avatar}\n            alt={`${comment.user.name}'s avatar`}\n          />\n          <AvatarFallback aria-hidden=\"true\">\n            {comment.user.name[0]}\n          </AvatarFallback>\n        </Avatar>\n\n        <div className=\"flex-1 space-y-1.5\">\n          {/* Header */}\n          <header className=\"flex items-center justify-between\">\n            <div className=\"flex items-center gap-2\">\n              <span className=\"text-sm font-semibold text-foreground\">\n                {comment.user.name}\n              </span>\n              {comment.user.role && (\n                <span\n                  className=\"text-[10px] px-1.5 py-0.5 rounded-full bg-primary/10 text-primary font-medium\"\n                  aria-label={`Role: ${comment.user.role}`}\n                >\n                  {comment.user.role}\n                </span>\n              )}\n              <time\n                className=\"text-xs text-muted-foreground\"\n                dateTime={comment.timestamp}\n                aria-label={`Posted ${comment.timestamp}`}\n              >\n                • {comment.timestamp}\n              </time>\n            </div>\n            <DropdownMenu>\n              <DropdownMenuTrigger asChild>\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  type=\"button\"\n                  className=\"h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity focus:opacity-100\"\n                  aria-label={`More options for ${comment.user.name}'s comment`}\n                  aria-haspopup=\"true\"\n                >\n                  <MoreHorizontal\n                    className=\"h-4 w-4 text-muted-foreground\"\n                    aria-hidden=\"true\"\n                  />\n                </Button>\n              </DropdownMenuTrigger>\n              <DropdownMenuContent align=\"end\">\n                <DropdownMenuItem>Report</DropdownMenuItem>\n                <DropdownMenuItem>Copy Link</DropdownMenuItem>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </header>\n\n          {/* Content */}\n          <p className=\"text-sm text-foreground/90 leading-relaxed\">\n            {comment.content}\n          </p>\n\n          {/* Actions */}\n          <nav\n            className=\"flex items-center gap-4 pt-1\"\n            aria-label=\"Comment actions\"\n          >\n            <button\n              onClick={handleLike}\n              type=\"button\"\n              className={cn(\n                \"flex items-center gap-1.5 text-xs font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 rounded\",\n                isLiked\n                  ? \"text-red-500\"\n                  : \"text-muted-foreground hover:text-foreground\"\n              )}\n              aria-label={\n                isLiked\n                  ? `Unlike comment (${likesCount} likes)`\n                  : `Like comment (${likesCount} likes)`\n              }\n              aria-pressed={isLiked}\n            >\n              <Heart\n                className={cn(\"h-3.5 w-3.5\", isLiked && \"fill-current\")}\n                aria-hidden=\"true\"\n              />\n              <span aria-live=\"polite\" aria-atomic=\"true\">\n                {likesCount}\n              </span>\n            </button>\n            <button\n              onClick={() => setActiveReplyId(isReplying ? null : comment.id)}\n              type=\"button\"\n              className={cn(\n                \"flex items-center gap-1.5 text-xs font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 rounded\",\n                isReplying\n                  ? \"text-primary\"\n                  : \"text-muted-foreground hover:text-foreground\"\n              )}\n              aria-label={\n                isReplying ? \"Cancel reply\" : `Reply to ${comment.user.name}`\n              }\n              aria-expanded={isReplying}\n              aria-controls={\n                isReplying ? `reply-input-${comment.id}` : undefined\n              }\n            >\n              <MessageCircle className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n              Reply\n            </button>\n            <button\n              type=\"button\"\n              className=\"flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 rounded\"\n              aria-label={`Share ${comment.user.name}'s comment`}\n            >\n              <Share2 className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n              Share\n            </button>\n          </nav>\n\n          {/* Inline Reply Input */}\n          <AnimatePresence>\n            {isReplying && (\n              <motion.div\n                ref={replyInputRef}\n                initial={{ opacity: 0, height: 0 }}\n                animate={{ opacity: 1, height: \"auto\" }}\n                exit={{ opacity: 0, height: 0 }}\n                className=\"pt-4 overflow-hidden\"\n                id={`reply-input-${comment.id}`}\n                role=\"region\"\n                aria-label={`Reply to ${comment.user.name}`}\n              >\n                <CommentInput\n                  autoFocus\n                  placeholder={`Reply to ${comment.user.name}...`}\n                  onSubmit={(content) => onAddReply(comment.id, content)}\n                  onCancel={() => setActiveReplyId(null)}\n                />\n              </motion.div>\n            )}\n          </AnimatePresence>\n        </div>\n      </div>\n\n      {/* Nested Replies */}\n      {comment.replies && comment.replies.length > 0 && (\n        <section\n          className=\"mt-4 space-y-4\"\n          id={repliesId}\n          aria-label={`${comment.replies.length} ${comment.replies.length === 1 ? \"reply\" : \"replies\"}`}\n        >\n          {isExpanded ? (\n            <AnimatePresence>\n              {comment.replies.map((reply) => (\n                <CommentItem\n                  key={reply.id}\n                  comment={reply}\n                  isReply={true}\n                  activeReplyId={activeReplyId}\n                  setActiveReplyId={setActiveReplyId}\n                  onAddReply={onAddReply}\n                />\n              ))}\n            </AnimatePresence>\n          ) : null}\n\n          <button\n            onClick={() => setIsExpanded(!isExpanded)}\n            type=\"button\"\n            className=\"ml-12 text-xs font-medium text-primary hover:underline flex items-center gap-1 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 rounded\"\n            aria-label={\n              isExpanded\n                ? \"Hide replies\"\n                : `Show ${comment.replies.length} replies`\n            }\n            aria-expanded={isExpanded}\n            aria-controls={repliesId}\n          >\n            {isExpanded ? (\n              <div\n                className=\"h-[1px] w-4 bg-primary/50 mr-1\"\n                aria-hidden=\"true\"\n              />\n            ) : (\n              <CornerDownRight className=\"h-3 w-3\" aria-hidden=\"true\" />\n            )}\n            {isExpanded\n              ? \"Hide replies\"\n              : `Show ${comment.replies.length} ${comment.replies.length === 1 ? \"reply\" : \"replies\"}`}\n          </button>\n        </section>\n      )}\n    </motion.article>\n  );\n}\n\nexport function CommentThread() {\n  const [comments, setComments] = useState(INITIAL_COMMENTS);\n  const [activeReplyId, setActiveReplyId] = useState<string | null>(null);\n  const [announcement, setAnnouncement] = useState(\"\");\n\n  // Recursive function to add reply\n  const addReplyToTree = (\n    comments: Comment[],\n    parentId: string,\n    newReply: Comment\n  ): Comment[] => {\n    return comments.map((comment) => {\n      if (comment.id === parentId) {\n        return {\n          ...comment,\n          replies: [...(comment.replies || []), newReply],\n        };\n      } else if (comment.replies && comment.replies.length > 0) {\n        return {\n          ...comment,\n          replies: addReplyToTree(comment.replies, parentId, newReply),\n        };\n      }\n      return comment;\n    });\n  };\n\n  const handleAddComment = (content: string) => {\n    const newComment: Comment = {\n      id: Date.now().toString(),\n      user: {\n        name: \"You\",\n        avatar: \"https://github.com/shadcn.png\",\n        role: \"Guest\",\n      },\n      content,\n      timestamp: \"Just now\",\n      likes: 0,\n      replies: [],\n    };\n\n    setComments([newComment, ...comments]);\n    setAnnouncement(\"Comment posted successfully\");\n    setTimeout(() => setAnnouncement(\"\"), 1000);\n  };\n\n  const handleAddReply = (parentId: string, content: string) => {\n    const newReply: Comment = {\n      id: Date.now().toString(),\n      user: {\n        name: \"You\",\n        avatar: \"https://github.com/shadcn.png\",\n        role: \"Guest\",\n      },\n      content,\n      timestamp: \"Just now\",\n      likes: 0,\n      replies: [],\n    };\n\n    setComments((prevComments) =>\n      addReplyToTree(prevComments, parentId, newReply)\n    );\n    setActiveReplyId(null);\n    setAnnouncement(\"Reply posted successfully\");\n    setTimeout(() => setAnnouncement(\"\"), 1000);\n  };\n\n  return (\n    <div\n      className=\"w-full mx-auto space-y-8\"\n      role=\"region\"\n      aria-label=\"Comments section\"\n    >\n      {/* Screen reader announcements */}\n      <div\n        role=\"status\"\n        aria-live=\"polite\"\n        aria-atomic=\"true\"\n        className=\"sr-only\"\n      >\n        {announcement}\n      </div>\n\n      {/* Header */}\n      <header className=\"flex items-center justify-between\">\n        <h2\n          className=\"text-xl font-semibold tracking-tight\"\n          id=\"comments-heading\"\n        >\n          Comments\n          <span className=\"sr-only\">\n            , {comments.length} {comments.length === 1 ? \"comment\" : \"comments\"}\n          </span>\n        </h2>\n        <div\n          className=\"flex items-center gap-2\"\n          role=\"group\"\n          aria-label=\"Sort comments\"\n        >\n          <Button\n            variant=\"ghost\"\n            size=\"sm\"\n            type=\"button\"\n            className=\"text-xs text-muted-foreground\"\n            aria-label=\"Sort by newest\"\n            aria-pressed={true}\n          >\n            Newest\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"sm\"\n            type=\"button\"\n            className=\"text-xs text-muted-foreground\"\n            aria-label=\"Sort by top\"\n            aria-pressed={false}\n          >\n            Top\n          </Button>\n        </div>\n      </header>\n\n      {/* Main Input Area */}\n      <section aria-labelledby=\"new-comment-heading\">\n        <h3 id=\"new-comment-heading\" className=\"sr-only\">\n          Write a new comment\n        </h3>\n        <CommentInput onSubmit={handleAddComment} />\n      </section>\n\n      {/* Comments List */}\n      <section aria-labelledby=\"comments-heading\">\n        <div className=\"space-y-2\" role=\"list\" aria-label=\"Comment thread\">\n          <AnimatePresence mode=\"popLayout\">\n            {comments.map((comment) => (\n              <CommentItem\n                key={comment.id}\n                comment={comment}\n                activeReplyId={activeReplyId}\n                setActiveReplyId={setActiveReplyId}\n                onAddReply={handleAddReply}\n              />\n            ))}\n          </AnimatePresence>\n        </div>\n      </section>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/uitripled/comment-thread-shadcnui.tsx"
    }
  ]
}