▚ProgressBarcustom
Animated progress bar with label and percentage
$ termino progress-bar
deploy
░░░░░░░░░░░░░░░░░░░░░░ 0
push
░░░░░░░░░░░░░░░░░░░░░░ 0
release
░░░░░░░░░░░░░░░░░░░░░░ 0
tick 0 · useTimeline → onUpdate
##Variants
blocks · Solid background-color fill (default)
$ termino progress-bar
deploy
░░░░░░░░░░░░░░░░░░░░░░ 0
push
░░░░░░░░░░░░░░░░░░░░░░ 0
release
░░░░░░░░░░░░░░░░░░░░░░ 0
tick 0 · useTimeline → onUpdate
line · Box-drawing ━/─ track glyphs
$ termino progress-bar
deploy
────────────────────── 0
push
────────────────────── 0
release
────────────────────── 0
tick 0 · useTimeline → onUpdate
dots · Dot-matrix ●/· style
$ termino progress-bar
deploy
······················ 0
push
······················ 0
release
······················ 0
tick 0 · useTimeline → onUpdate
gradient · Fill lerps toward white across the track
$ termino progress-bar
deploy
░░░░░░░░░░░░░░░░░░░░░░ 0
push
░░░░░░░░░░░░░░░░░░░░░░ 0
release
░░░░░░░░░░░░░░░░░░░░░░ 0
tick 0 · useTimeline → onUpdate
##API
Composition-only component: `<ProgressBar />` builds on `<box>` + `<text>`. No `extend()` registration needed.
properties
| prop | type | default | description |
|---|---|---|---|
| value | number | - | Current value (clamped to [0, max]) |
| max | number | 100 | Maximum value |
| width | number | 20 | Track width in characters |
| label | string | - | Optional label on the left of the row |
| color | string | "#7aa2f7" | Fill color |
| trackColor | string | "#2f3449" | Track color |
| percentColor | string | "#565f89" | Percentage text color |
| showPercent | boolean | true | Show the percentage value |
| variant | "blocks" | "line" | "dots" | "gradient" | "blocks" | Bar rendering style |
##Keymap
—No keyboard input (display only)
value propDrive progress from state or useTimeline
##Notes
- ▸Fill is a nested `<box>` with computed width — the same pattern the system-monitor example uses.
- ▸Animate by feeding it state updated from useTimeline's onUpdate, or plain setInterval.
##Source
Real implementation — this is the code that runs in your terminal.
1import { createElement as h } from "react";2import { mixColor } from "./chart";34export type ProgressBarVariant = "blocks" | "line" | "dots" | "gradient";56export interface ProgressBarProps {7 value: number;8 max?: number;9 width?: number;10 label?: string;11 color?: string;12 trackColor?: string;13 percentColor?: string;14 showPercent?: boolean;15 variant?: ProgressBarVariant;16}1718const VARIANT_GLYPHS: Record<Exclude<ProgressBarVariant, "blocks">, [string, string]> = {19 line: ["━", "─"],20 dots: ["●", "·"],21 gradient: ["█", "░"],22};2324/** Colour of one filled cell. The gradient variant lightens towards the right25 * end of the bar; every other variant paints a flat colour. */26function fillColor(27 variant: ProgressBarVariant,28 color: string,29 index: number,30 width: number,31): string {32 if (variant !== "gradient") return color;33 return mixColor(color, "#ffffff", (index / Math.max(1, width - 1)) * 0.55);34}3536export function ProgressBar({37 value,38 max = 100,39 width = 20,40 label,41 color = "#7aa2f7",42 trackColor = "#2f3449",43 percentColor = "#565f89",44 showPercent = true,45 variant = "blocks",46}: Readonly<ProgressBarProps>) {47 const clamped = Math.max(0, Math.min(max, value));48 const pct = max === 0 ? 0 : Math.round((clamped / max) * 100);49 const filled = Math.max(0, Math.min(width, Math.round((pct / 100) * width)));5051 const bar =52 variant === "blocks"53 ? h(54 "box",55 { style: { width: width + 4, height: 1, backgroundColor: trackColor } },56 h("box", { style: { width: filled, height: 1, backgroundColor: color } }),57 )58 : h(59 "box",60 { flexDirection: "row" },61 Array.from({ length: width }, (_, i) => {62 const [fillCh, trackCh] = VARIANT_GLYPHS[variant];63 const isFill = i < filled;64 const fg = isFill ? fillColor(variant, color, i, width) : trackColor;65 return h("text", { key: i, fg }, isFill ? fillCh : trackCh);66 }),67 );6869 const headerParts = [];70 if (label) headerParts.push(h("text", { fg: "#565f89" }, label));71 if (showPercent) headerParts.push(h("text", { fg: percentColor }, `${pct}%`));72 const header = headerParts.length73 ? h(74 "box",75 { flexDirection: "row", justifyContent: "space-between", width: width + 4 },76 ...headerParts,77 )78 : null;7980 return h(81 "box",82 { flexDirection: "column", gap: 0 },83 header,84 bar,85 );86}87