Comment thread
A recursive thread of comments. The same component renders the root and the replies — depth is handled by indentation, not by separate components.
'use client';
import type { ReactNode } from 'react';import { Avatar, Button, Stack, Text } from '@helixui/core';
export interface Comment { id: string; author: { name: string; avatarUrl?: string }; body: ReactNode; postedAt: string; // ISO 8601 replies?: Comment[];}
export function CommentThread({ comment, depth = 0, onReply,}: { comment: Comment; depth?: number; onReply?: (parentId: string) => void;}) { const fmt = new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }); return ( <div style={{ paddingInlineStart: depth === 0 ? 0 : 'var(--helixui-space-6)' }}> <Stack gap={2}> <Stack direction="row" gap={3} align="start"> <Avatar src={comment.author.avatarUrl} fallback={comment.author.name.split(' ').map((p) => p[0]).slice(0, 2).join('')} size="sm" /> <Stack gap={1} style={{ flex: 1 }}> <Stack direction="row" align="center" gap={2}> <Text size="sm" weight="semibold">{comment.author.name}</Text> <Text size="xs" tone="muted">{fmt.format(new Date(comment.postedAt))}</Text> </Stack> <Text size="sm">{comment.body}</Text> {onReply ? ( <Button variant="ghost" tone="neutral" size="sm" onClick={() => onReply(comment.id)}> Reply </Button> ) : null} </Stack> </Stack> {comment.replies?.length ? ( <div style={{ borderInlineStart: '2px solid var(--helixui-color-border-subtle)', paddingInlineStart: 'var(--helixui-space-3)', marginInlineStart: 'var(--helixui-space-3)', }} > <Stack gap={3}> {comment.replies.map((reply) => ( <CommentThread key={reply.id} comment={reply} depth={depth + 1} onReply={onReply} /> ))} </Stack> </div> ) : null} </Stack> </div> );}Usage
<CommentThread comment={{ id: '1', author: { name: 'Wayne Ryu' }, body: 'Should the DataTable v0 accept a render-row prop?', postedAt: '2026-05-26T09:12:00Z', replies: [ { id: '2', author: { name: 'Reviewer A' }, body: 'Probably not — would complicate the virtualization story.', postedAt: '2026-05-26T09:30:00Z', }, ], }} onReply={(id) => openReplyFormFor(id)}/>The recursion bottoms out cleanly when a comment has no replies.
Depth is unbounded in code; in practice cap visual depth at 5 with a
“continued in thread →” link.