UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

15 lines 6.83 kB
{ "name": "metalens-components-metalensthread", "type": "registry:component", "dependencies": [], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "components/metalens/components/MetaLensThread.tsx", "type": "registry:component", "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Button } from \"../../ui/button.js\";\nimport { Card, CardContent } from \"../../ui/card.js\";\n// Removed: import { responsive } from \"../../../lib/layout-constants.js\";\nimport type { MetaLensComment } from \"../types/index.js\";\n\n/**\n * Threaded comment view with collapsible replies\n *\n * Based on patterns from:\n * - Reddit-style threading\n * - /Users/satchmo/code/metalens-web/src_old/models/comment.js (reply structure)\n *\n * @example\n * ```tsx\n * <MetaLensThread\n * rootComment={comment}\n * depth={0}\n * maxDepth={5}\n * onReply={(parentId) => handleReply(parentId)}\n * />\n * ```\n */\n\ninterface MetaLensThreadProps {\n\trootComment: MetaLensComment;\n\tdepth?: number;\n\tmaxDepth?: number;\n\tcollapsed?: boolean;\n\tonToggle?: () => void;\n\tonReply?: (parentId: string) => void;\n\tonReact?: (commentId: string, emoji: string) => void;\n}\n\nexport function MetaLensThread({\n\trootComment,\n\tdepth = 0,\n\tmaxDepth = 5,\n\tcollapsed: initialCollapsed = false,\n\tonToggle,\n\tonReply,\n\tonReact,\n}: MetaLensThreadProps) {\n\tconst [isCollapsed, setIsCollapsed] = useState(initialCollapsed);\n\tconst [showReplyForm, setShowReplyForm] = useState(false);\n\n\tconst handleToggle = () => {\n\t\tsetIsCollapsed(!isCollapsed);\n\t\tonToggle?.();\n\t};\n\n\t// PSEUDOCODE for thread rendering:\n\n\t// 1. Indentation calculation\n\t// - Each depth level adds padding\n\t// - Visual thread line on left side\n\t// - Max indentation to prevent overflow\n\tconst indentSize = Math.min(depth * 24, maxDepth * 24);\n\n\t// 2. Thread line styling\n\t// - Vertical line connecting parent to children\n\t// - Different colors for different depths\n\t// - Dashed line for collapsed threads\n\n\t// 3. Collapse behavior\n\t// - Show [+] or [-] indicator\n\t// - Collapse count (e.g., \"12 more replies\")\n\t// - Preserve reply form state\n\n\t// 4. Performance optimization\n\t// - Virtualize long threads\n\t// - Lazy load deep threads\n\t// - Batch render updates\n\n\treturn (\n\t\t<div style={{ marginLeft: indentSize }}>\n\t\t\t{/* Thread line */}\n\t\t\t{depth > 0 && (\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"absolute bg-border opacity-50\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tleft: indentSize - 12,\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tbottom: 0,\n\t\t\t\t\t\twidth: 2,\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t{/* Comment content */}\n\t\t\t<Card className=\"bg-transparent border-0 shadow-none relative\">\n\t\t\t\t<CardContent className=\"p-3\">\n\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t{/* Header with collapse button */}\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t{rootComment.replies && rootComment.replies.length > 0 && (\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\tonClick={handleToggle}\n\t\t\t\t\t\t\t\t\tclassName=\"h-auto min-w-0 px-1.5 py-0.5\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isCollapsed ? \"+\" : \"\"}\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t<span className=\"text-sm font-medium\">{rootComment.author}</span>\n\n\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t• {new Date(rootComment.timestamp).toRelativeTime()}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* Comment content (if not collapsed) */}\n\t\t\t\t\t\t{!isCollapsed && (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t<p className=\"text-sm\">{rootComment.content}</p>\n\n\t\t\t\t\t\t\t\t{/* Actions */}\n\t\t\t\t\t\t\t\t<div className=\"flex gap-4\">\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\tonClick={() => setShowReplyForm(!showReplyForm)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\tReply\n\t\t\t\t\t\t\t\t\t</Button>\n\n\t\t\t\t\t\t\t\t\t{/* Reaction buttons */}\n\t\t\t\t\t\t\t\t\t{[\"👍\", \"👎\", \"❤️\"].map((emoji) => (\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tkey={emoji}\n\t\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => onReact?.(rootComment.txid, emoji)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{emoji} {rootComment.reactions?.[emoji] || 0}\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t{/* Reply form */}\n\t\t\t\t\t\t\t\t{showReplyForm && (\n\t\t\t\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t\t\t\t<CardContent className=\"p-3\">\n\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\tReply form will be implemented here\n\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{/* Collapsed indicator */}\n\t\t\t\t\t\t{isCollapsed && rootComment.replies && (\n\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t{rootComment.replies.length}{\" \"}\n\t\t\t\t\t\t\t\t{rootComment.replies.length === 1 ? \"reply\" : \"replies\"} hidden\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\n\t\t\t{/* Child threads (if not collapsed and within depth limit) */}\n\t\t\t{!isCollapsed && rootComment.replies && depth < maxDepth && (\n\t\t\t\t<div className=\"flex flex-col gap-2 mt-2\">\n\t\t\t\t\t{rootComment.replies.map((reply) => (\n\t\t\t\t\t\t<MetaLensThread\n\t\t\t\t\t\t\tkey={reply.txid}\n\t\t\t\t\t\t\trootComment={reply}\n\t\t\t\t\t\t\tdepth={depth + 1}\n\t\t\t\t\t\t\tmaxDepth={maxDepth}\n\t\t\t\t\t\t\tonReply={onReply}\n\t\t\t\t\t\t\tonReact={onReact}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Load more indicator for deep threads */}\n\t\t\t{!isCollapsed && rootComment.replies && depth >= maxDepth && (\n\t\t\t\t<Button variant=\"secondary\" size=\"sm\" className=\"ml-6 mt-2\">\n\t\t\t\t\tContinue thread →\n\t\t\t\t</Button>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// Helper extension for relative time\ndeclare global {\n\tinterface Date {\n\t\ttoRelativeTime(): string;\n\t}\n}\n\nDate.prototype.toRelativeTime = function () {\n\t// PSEUDOCODE: Convert to relative time\n\t// const seconds = Math.floor((Date.now() - this.getTime()) / 1000);\n\t// if (seconds < 60) return 'just now';\n\t// if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;\n\t// etc...\n\treturn this.toLocaleString();\n};\n", "target": "<%- config.aliases.components %>/metalensthread.tsx" } ] }