From 91c279fd2a908447eb560361b66a5c8220d415e1 Mon Sep 17 00:00:00 2001 From: Jiayuan Date: Tue, 31 Mar 2026 14:41:44 +0800 Subject: [PATCH] feat(issues): make entire comment card collapsible with toggle Each comment card now has a clickable header with a chevron toggle. When collapsed, shows author, timestamp, and a content preview. When expanded, shows the full comment body, replies, and reply input. --- .../issues/components/comment-card.tsx | 108 ++++++++++-------- 1 file changed, 62 insertions(+), 46 deletions(-) diff --git a/apps/web/features/issues/components/comment-card.tsx b/apps/web/features/issues/components/comment-card.tsx index 948e0f86..b2b9727b 100644 --- a/apps/web/features/issues/components/comment-card.tsx +++ b/apps/web/features/issues/components/comment-card.tsx @@ -191,7 +191,8 @@ function CommentCard({ onDelete, onToggleReaction, }: CommentCardProps) { - const [repliesOpen, setRepliesOpen] = useState(true); + const { getActorName } = useActorName(); + const [open, setOpen] = useState(true); // Collect all nested replies recursively into a flat list const allNestedReplies: TimelineEntry[] = []; @@ -205,57 +206,72 @@ function CommentCard({ collectReplies(entry.id); const replyCount = allNestedReplies.length; + const contentPreview = (entry.content ?? "").replace(/\n/g, " ").slice(0, 80); return ( - {/* Parent comment */} -
- -
- - {/* Replies — collapsible when there are replies */} - {replyCount > 0 && ( - -
- - - + + {/* Collapsed header — always visible */} +
+ + + + + {getActorName(entry.actor_type, entry.actor_id)} + + + {timeAgo(entry.created_at)} + + {!open && contentPreview && ( + + {contentPreview}{(entry.content ?? "").length > 80 ? "..." : ""} + + )} + {!open && replyCount > 0 && ( + {replyCount} {replyCount === 1 ? "reply" : "replies"} - -
- - {allNestedReplies.map((reply) => ( -
- -
- ))} -
-
- )} + )} +
+
- {/* Reply input — always visible at bottom */} -
- onReply(entry.id, content)} - /> -
+ {/* Expanded content */} + +
+ +
+ + {/* Replies */} + {allNestedReplies.map((reply) => ( +
+ +
+ ))} + + {/* Reply input */} +
+ onReply(entry.id, content)} + /> +
+
+
); }