/** * 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... )} ); }