feat(web): add threading lines to comment replies

This commit is contained in:
CREDO23 2026-01-16 12:59:38 +02:00
parent 09317cd9f7
commit 111ecc79fd

View file

@ -45,65 +45,91 @@ export function CommentThread({
setIsReplyComposerOpen(false); setIsReplyComposerOpen(false);
}; };
const hasReplies = thread.replies.length > 0;
const showReplies = thread.replies.length === 1 || isRepliesExpanded;
return ( return (
<div className="space-y-2"> <div>
{/* Parent comment */}
<CommentItem <CommentItem
comment={parentComment} comment={parentComment}
onEdit={(id) => onEditComment(id, "")} onEdit={(id) => onEditComment(id, "")}
onDelete={onDeleteComment} onDelete={onDeleteComment}
/> />
{thread.replies.length > 1 && ( {/* Replies and actions - using flex layout with connector */}
<div className="ml-11"> {(hasReplies || isReplyComposerOpen) && (
<Button <div className="flex">
variant="ghost" {/* Connector column - vertical line */}
size="sm" <div className="flex w-7 flex-col items-center">
className="h-7 px-2 text-xs text-muted-foreground hover:text-foreground" <div className="w-px flex-1 bg-border" />
onClick={() => setIsRepliesExpanded(!isRepliesExpanded)} </div>
>
{isRepliesExpanded ? ( {/* Content column */}
<ChevronDown className="mr-1 size-3" /> <div className="min-w-0 flex-1 space-y-2 pb-1">
) : ( {/* Expand/collapse for multiple replies */}
<ChevronRight className="mr-1 size-3" /> {thread.replies.length > 1 && (
<Button
variant="ghost"
size="sm"
className="h-6 px-2 text-xs text-muted-foreground hover:text-foreground"
onClick={() => setIsRepliesExpanded(!isRepliesExpanded)}
>
{isRepliesExpanded ? (
<ChevronDown className="mr-1 size-3" />
) : (
<ChevronRight className="mr-1 size-3" />
)}
{thread.replies.length} replies
</Button>
)} )}
{thread.replies.length} replies
</Button> {/* Reply items */}
{showReplies && hasReplies && (
<div className="space-y-2">
{thread.replies.map((reply) => (
<CommentItem
key={reply.id}
comment={reply}
isReply
onEdit={(id) => onEditComment(id, "")}
onDelete={onDeleteComment}
/>
))}
</div>
)}
{/* Reply composer or button */}
{isReplyComposerOpen ? (
<CommentComposer
members={members}
membersLoading={membersLoading}
placeholder="Write a reply..."
submitLabel="Reply"
isSubmitting={isSubmitting}
onSubmit={handleReplySubmit}
onCancel={handleReplyCancel}
autoFocus
/>
) : (
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs" onClick={handleReply}>
<MessageSquare className="mr-1.5 size-3" />
Reply
</Button>
)}
</div>
</div> </div>
)} )}
{thread.replies.length > 0 && (thread.replies.length === 1 || isRepliesExpanded) && ( {/* Reply button when no replies yet */}
<div className="ml-11 space-y-3"> {!hasReplies && !isReplyComposerOpen && (
{thread.replies.map((reply) => ( <div className="ml-7 mt-1">
<CommentItem <Button variant="ghost" size="sm" className="h-7 px-2 text-xs" onClick={handleReply}>
key={reply.id}
comment={reply}
isReply
onEdit={(id) => onEditComment(id, "")}
onDelete={onDeleteComment}
/>
))}
</div>
)}
<div className="ml-11">
{isReplyComposerOpen ? (
<CommentComposer
members={members}
membersLoading={membersLoading}
placeholder="Write a reply..."
submitLabel="Reply"
isSubmitting={isSubmitting}
onSubmit={handleReplySubmit}
onCancel={handleReplyCancel}
autoFocus
/>
) : (
<Button variant="outline" size="sm" className="h-7 px-3 text-xs" onClick={handleReply}>
<MessageSquare className="mr-1.5 size-3" /> <MessageSquare className="mr-1.5 size-3" />
Reply Reply
</Button> </Button>
)} </div>
</div> )}
</div> </div>
); );
} }