From 7dafb5127db7f3286a5fcc9e7c73f3d9ef4f968b Mon Sep 17 00:00:00 2001 From: Jiayuan Date: Tue, 31 Mar 2026 14:33:09 +0800 Subject: [PATCH] feat(issues): add collapsible toggle for comment replies Wrap the replies section in a Collapsible component so users can collapse/expand replies on a comment thread. The parent comment and reply input remain always visible. A chevron trigger shows the reply count (e.g. "3 replies") and rotates on open. Default state is expanded to preserve existing behavior. --- .../issues/components/comment-card.tsx | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/apps/web/features/issues/components/comment-card.tsx b/apps/web/features/issues/components/comment-card.tsx index eef902d4..5596c7cf 100644 --- a/apps/web/features/issues/components/comment-card.tsx +++ b/apps/web/features/issues/components/comment-card.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import { MoreHorizontal } from "lucide-react"; +import { MoreHorizontal, ChevronRight } from "lucide-react"; import { toast } from "sonner"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; @@ -13,9 +13,11 @@ import { DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; +import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible"; import { ActorAvatar } from "@/components/common/actor-avatar"; import { ReactionBar } from "@/components/common/reaction-bar"; import { Markdown } from "@/components/markdown"; +import { cn } from "@/lib/utils"; import { useActorName } from "@/features/workspace"; import { timeAgo } from "@/shared/utils"; import { ReplyInput } from "./reply-input"; @@ -173,6 +175,8 @@ function CommentCard({ onDelete, onToggleReaction, }: CommentCardProps) { + const [repliesOpen, setRepliesOpen] = useState(true); + // Collect all nested replies recursively into a flat list const allNestedReplies: TimelineEntry[] = []; const collectReplies = (parentId: string) => { @@ -184,6 +188,8 @@ function CommentCard({ }; collectReplies(entry.id); + const replyCount = allNestedReplies.length; + return ( {/* Parent comment */} @@ -197,18 +203,32 @@ function CommentCard({ /> - {/* Replies — flat, separated by border */} - {allNestedReplies.map((reply) => ( -
- -
- ))} + {/* Replies — collapsible when there are replies */} + {replyCount > 0 && ( + +
+ + + + {replyCount} {replyCount === 1 ? "reply" : "replies"} + + +
+ + {allNestedReplies.map((reply) => ( +
+ +
+ ))} +
+
+ )} {/* Reply input — always visible at bottom */}