import React, { useState } from "react";
import { Box, Text, useInput } from "ink";
import { useTerminalTheme } from "../hooks/useTerminalTheme";
export function ExpandableOutput(props: {
label: string;
content: string;
durationMs?: number;
startExpanded?: boolean;
/** Caps rendered lines once expanded (with ↑/↓-scrollable overflow) — expanded output can otherwise be arbitrarily long. Defaults to 20. */
maxHeight?: number;
}) {
const theme = useTerminalTheme();
const [expanded, setExpanded] = useState(props.startExpanded ?? false);
const [scrollOffset, setScrollOffset] = useState(0);
const lines = props.content.split("\n");
const lineCount = lines.length;
const maxHeight = props.maxHeight ?? 20;
const maxOffset = Math.max(0, lineCount - maxHeight);
const needsPaging = expanded && lineCount > maxHeight;
const clampedOffset = Math.min(scrollOffset, maxOffset);
useInput((_input, key) => {
if (key.return) {
setExpanded((current) => !current);
setScrollOffset(0);
return;
}
if (!needsPaging) {
return;
}
if (key.upArrow) {
setScrollOffset((current) => Math.max(0, current - 1));
return;
}
if (key.downArrow) {
setScrollOffset((current) => Math.min(maxOffset, current + 1));
}
});
if (!expanded) {
return (
{props.label} — {lineCount} output line{lineCount === 1 ? "" : "s"}
{props.durationMs !== undefined ? ` ${(props.durationMs / 1000).toFixed(1)} sec` : ""}
Press Enter to expand
);
}
const visibleLines = needsPaging ? lines.slice(clampedOffset, clampedOffset + maxHeight) : lines;
return (
{props.label}
{visibleLines.join("\n")}
{needsPaging ? (
Lines {clampedOffset + 1}-{Math.min(clampedOffset + maxHeight, lineCount)} of {lineCount} — ↑/↓ to scroll
) : null}
Press Enter to collapse
);
}