import { ChevronDown, ChevronUp, History } from "lucide-react"; import * as React from "react"; import type { ProjectIssue } from "@/features/projects/hooks"; import { formatExactTimestamp, pluralize, relativeTime, } from "@/features/projects/lib/projectsViewHelpers"; import { resolveUserLabel, type UserProfileLookup, } from "@/features/profile/lib/identity"; import { normalizePubkey } from "@/shared/lib/pubkey"; import { UserAvatar } from "@/shared/ui/UserAvatar"; import { ProfileAuthorName } from "./ProjectProfileIdentity"; import { ProjectRichContent } from "./ProjectRichContent"; const COLLAPSED_COMMENT_COUNT = 3; export function ProjectIssueCommentTimeline({ comments, profiles, }: { comments: ProjectIssue["comments"]; profiles?: UserProfileLookup; }) { const [isCollapsed, setIsCollapsed] = React.useState(false); const [isExpanded, setIsExpanded] = React.useState(false); const orderedComments = React.useMemo( () => [...comments].sort( (left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id), ), [comments], ); const earlierCommentCount = Math.max( 0, orderedComments.length - COLLAPSED_COMMENT_COUNT, ); const visibleComments = isExpanded || earlierCommentCount === 0 ? orderedComments : orderedComments.slice(-COLLAPSED_COMMENT_COUNT); const displayedComments = isCollapsed ? [] : visibleComments; if (orderedComments.length === 0) { return null; } return (
{!isCollapsed && earlierCommentCount > 0 && !isExpanded ? ( ) : null} {displayedComments.map((comment, index) => { const authorLabel = resolveUserLabel({ profiles, pubkey: comment.author, }); return (
{index < displayedComments.length - 1 ? ( ) : null} {/* bg-background keeps the connector line from showing through while the avatar image (or delayed fallback) loads. */}
{/* h-5 matches the avatar so the header line centers on it. */}
{authorLabel} {relativeTime(comment.createdAt)}
); })}
); }