/** * Theme-aware TUI primitives for Pisces terminal overlays. * * All functions are pure — they take a TuiTheme and return plain strings or * string arrays. No Pi SDK imports; safe to test with an identity theme. * * Design language (Editorial Noir terminal): * - Box corners: ╭ ╮ ╰ ╯ Walls: │ Horizontal: ─ * - Color keys: "accent" (default), "warning", "muted", "dim", "error" * - Bar chars: █ (filled) ░ (empty) * - Badge style: [ LABEL ] with color surround */ // ── Theme contract ───────────────────────────────────────────────────────────── export interface TuiTheme { fg(colorKey: string, text: string): string; getFgAnsi(colorKey: string): string; } // ── Panel framing ────────────────────────────────────────────────────────────── const H = "─"; const FILLED = "█"; const EMPTY = "░"; /** * Top border of a panel. * innerW = content width (excluding the two wall chars + two spaces of padding). */ export function panelTop(innerW: number, colorKey: string, t: TuiTheme): string { return t.fg(colorKey, "╭" + H.repeat(innerW + 2) + "╮"); } /** * Bottom border with a centered dismiss hint. * hint is rendered dim; remaining ─ fill is rendered in colorKey. */ export function panelBottom( innerW: number, hint: string, colorKey: string, t: TuiTheme, ): string { const fill = H.repeat(Math.max(0, innerW + 2 - hint.length)); return t.fg(colorKey, "╰") + t.fg("dim", hint) + t.fg(colorKey, fill + "╯"); } /** Blank row — an empty padded line inside the panel. */ export function panelBlank(innerW: number, colorKey: string, t: TuiTheme): string { return t.fg(colorKey, "│") + " ".repeat(innerW + 2) + t.fg(colorKey, "│"); } /** * Content row — left-pads by 1, right-pads to innerW, then closes with wall. * content must already be visually innerW chars wide (strip ANSI before measuring). */ export function panelRow( content: string, contentVisualW: number, innerW: number, colorKey: string, t: TuiTheme, ): string { const pad = " ".repeat(Math.max(0, innerW - contentVisualW)); return `${t.fg(colorKey, "│")} ${content}${pad} ${t.fg(colorKey, "│")}`; } // ── Content atoms ────────────────────────────────────────────────────────────── /** [ LABEL ] badge — accent-colored surround, label in normal fg. */ export function badge(label: string, colorKey: string, t: TuiTheme): string { return `${t.fg(colorKey, "[")} ${label} ${t.fg(colorKey, "]")}`; } /** * Key-value stat row: label left-padded to labelW, value accent-colored. * Returns { text, visualW } so callers can pass visualW to panelRow. */ export function kv( label: string, value: string, labelW: number, colorKey: string, t: TuiTheme, ): { text: string; visualW: number } { const paddedLabel = t.fg("muted", label.padEnd(labelW)); const coloredValue = t.fg(colorKey, value); const text = `${paddedLabel} ${coloredValue}`; const visualW = labelW + 2 + value.length; return { text, visualW }; } /** * Section divider: ───