'use client' import { useId, useState, type KeyboardEvent } from 'react' import { cn } from '../../lib/cn' import { Avatar } from '../Avatar' import { Badge } from '../Badge' import { Button } from '../Button' import { RelativeTime } from '../RelativeTime' import { Text } from '../Text' import { Textarea } from '../Textarea' import { CheckIcon } from '../internal/icons' export interface CommentThreadAuthor { id: string name: string avatarSrc?: string } export interface CommentThreadReaction { emoji: string count: number /** Whether the current person is one of the count. */ reacted: boolean } export interface CommentThreadComment { id: string author: CommentThreadAuthor body: string createdAt: Date | number | string /** Set once the body has been changed after posting. */ editedAt?: Date | number | string reactions?: CommentThreadReaction[] replies?: CommentThreadComment[] } export interface CommentThreadProps { /** Top-level comments, oldest first. The caller owns the tree. */ comments: CommentThreadComment[] /** Whose comments get Edit and Delete. */ currentUserId: string /** A reply to a comment, or a new top-level comment when `parentId` is null. */ onReply: (parentId: string | null, body: string) => void onEdit?: (id: string, body: string) => void onDelete?: (id: string) => void /** Toggles one reaction for the current person. Omit to hide reactions. */ onReact?: (id: string, emoji: string) => void /** The reactions offered on every comment. */ reactionChoices?: string[] /** Nesting stops here. Replying at the deepest level answers the parent, with a mention. */ maxDepth?: number /** Resolved threads hide their composers and read as settled. */ resolved?: boolean /** Shows the Resolve / Reopen control. */ onResolvedChange?: (resolved: boolean) => void /** Merged last, so it wins. */ className?: string } function InlineComposer({ label, initial = '', submitLabel, onSubmit, onCancel }: { label: string; initial?: string; submitLabel: string; onSubmit: (body: string) => void; onCancel?: () => void }) { const [body, setBody] = useState(initial) const submit = () => { if (!body.trim()) return onSubmit(body.trim()) setBody('') } const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape' && onCancel) { event.preventDefault() onCancel() } if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { event.preventDefault() submit() } } return (