/**
* progress.tsx — Ink ProgressView component for long-running operations.
*
* Replaces the neo-blessed ProgressScreen class with a declarative React
* component. The parent manages state (spinning, status, result, logLines)
* and passes them as props.
*
* Features:
* - Animated braille spinner (when spinning=true)
* - Title and optional context
* - Status text updated by the parent
* - Result display: success (✔), error (✘), info (ℹ)
* - Scrollable log area for result messages
* - Footer dismiss hint
*
* Usage:
*
*/
import React from "react";
import { Box, Text } from "ink";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
/** Result state for the progress view. */
export interface ProgressResult {
type: "success" | "error" | "info";
message: string;
}
export interface ProgressViewProps {
/** Title of the operation. */
title: string;
/** Optional context string shown next to the title. */
context?: string;
/** Whether the spinner is currently animating. Default: false. */
spinning?: boolean;
/** Current spinner frame index (for animated rendering). Default: 0. */
spinFrame?: number;
/** Status text shown next to the spinner. */
status?: string;
/** Result to display (replaces spinner when set). */
result?: ProgressResult;
/** Log lines to display in the scrollable area. */
logLines?: string[];
/** Whether to show the "Press any key to continue" footer. */
showDismiss?: boolean;
}
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const SPIN_CHARS = ["⠂", "⠆", "⠇", "⠃", "⠉", "⠌", "⠎", "⠋"];
// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
/**
* ProgressView — a declarative progress display component.
*
* The parent component manages the spinner interval and result state,
* passing updated props to trigger re-renders.
*/
export function ProgressView({
title,
context,
spinning = false,
spinFrame = 0,
status,
result,
logLines,
showDismiss = false,
}: ProgressViewProps): React.ReactElement {
// Determine the icon and status text to display
let icon: React.ReactElement | null = null;
let statusText: React.ReactElement | null = null;
if (result) {
// Result overrides spinner
switch (result.type) {
case "success":
icon = ✔;
statusText = {result.message};
break;
case "error":
icon = ✘;
statusText = {result.message};
break;
case "info":
icon = ℹ;
statusText = {result.message};
break;
}
} else if (spinning) {
const ch = SPIN_CHARS[spinFrame % SPIN_CHARS.length];
icon = {ch};
statusText = status ? {status} : null;
}
return (
{/* Title */}
{title}
{context && (
<>
—
{context}
>
)}
{/* Spinner + status row */}
{(icon || statusText) && (
{icon && {icon}}
{statusText}
)}
{/* Log area */}
{logLines && logLines.length > 0 && (
{logLines.map((line, i) => (
{line}
))}
)}
{/* Footer */}
{showDismiss && (
Press any key to continue...
)}
);
}