1import { InlineButton } from "../ui/button";
2import { useId, useState } from "react";
3
4import { cn } from "../../lib/utils";
5
6/**
7 * Long error text clamped to a few lines with a toggle to reveal the rest.
8 * Short single-line text renders as-is without the toggle.
9 */
10export function ExpandableText({
11 text,
12 className,
13 collapsedClassName = "line-clamp-3",
14 expandLabel = "Show full error",
15}: {
16 text: string;
17 className?: string;
18 collapsedClassName?: string;
19 expandLabel?: string;
20}) {
21 const textId = useId();
22 const [expanded, setExpanded] = useState(false);
23 const canExpand = text.length > 180 || text.includes("\n");
24
25 return (
26 <div className={cn("min-w-0", className)}>
27 <div
28 id={textId}
29 className={cn(
30 "whitespace-pre-wrap break-words",
31 !expanded && canExpand ? collapsedClassName : null,
32 )}
33 >
34 {text}
35 </div>
36 {canExpand ? (
37 <InlineButton
38 aria-expanded={expanded}
39 aria-controls={textId}
40 tone="muted"
41 className="mt-1"
42 onClick={() => setExpanded((value) => !value)}
43 >
44 {expanded ? "Show less" : expandLabel}
45 </InlineButton>
46 ) : null}
47 </div>
48 );
49}