Files
frontend-helper/src/components/CommentsPanel.tsx
T

67 lines
2.3 KiB
TypeScript

import type { CommentItem } from "@/lib/comment-model";
import { HexAvatar } from "@/components/HexAvatar";
import { IconButton, SendIcon } from "@/components/Buttons";
import { relativeTime } from "@/lib/feed-model";
export interface CommentsPanelProps {
comments: CommentItem[];
loading?: boolean;
/** Error text naming the action that failed. The list stays shown. */
error?: string | null;
value: string;
busy?: boolean;
onChange?: (v: string) => void;
onSubmit?: () => void;
onRetry?: () => void;
now?: number;
}
export function CommentsPanel(p: CommentsPanelProps) {
const empty = !p.value.trim();
return (
<div className="post-cm" data-testid="comments-panel">
{p.loading ? <span className="spinner" role="status" aria-label="Loading comments" /> : null}
{!p.loading && p.comments.length === 0 && !p.error ? (
<p className="post-cm-empty">No comments yet.</p>
) : null}
<ul className="post-cm-list">
{p.comments.map((c) => (
<li key={c.key} className="post-c" data-testid="comment">
<HexAvatar name={c.authorName} size="sm" />
<div className={c.removed ? "post-c-body post-c-removed" : "post-c-body"}>
<div className="post-c-head">
<strong>{c.authorName}</strong>
<time dateTime={c.createdAt ?? undefined}>{relativeTime(c.createdAt, p.now)}</time>
</div>
{c.removed ? <p>This comment was removed.</p> : <p>{c.content}</p>}
</div>
</li>
))}
</ul>
{p.error ? (
<p className="post-error" role="alert">
{p.error}{" "}
{p.onRetry ? <button type="button" className="post-link" onClick={p.onRetry}>Try again</button> : null}
</p>
) : null}
<form
className="post-cm-form"
onSubmit={(e) => {
e.preventDefault();
if (!empty && !p.busy) p.onSubmit?.();
}}
>
<input
type="text"
aria-label="Add a comment"
placeholder="Add a comment"
value={p.value}
maxLength={5000}
onChange={(e) => p.onChange?.(e.target.value)}
/>
<IconButton type="submit" disabled={empty || p.busy} label={p.busy ? "Posting" : "Post"} icon={<SendIcon />} />
</form>
</div>
);
}