import stringWidth from "string-width" /** Layout options for a terminal detail block. */ type DetailBlockOptions = { keyAlignment?: "left" | "right" lineSpacing?: number padding?: number separator?: string | null } /** * Renders labeled values as an aligned, multiline terminal detail block. * * @param details - Labels mapped to values; nullish values are omitted. * @param options - Alignment, spacing, padding, and separator settings. */ export function renderDetailBlock( details: Record, options: DetailBlockOptions = {}, ) { const { keyAlignment = "left", lineSpacing = 0, padding = 2, separator = "", } = options const entries = Object.entries(details).filter(([, v]) => v != null) const keyWidth = Math.max(0, ...entries.map(([key]) => stringWidth(key))) return entries .map(([key, value]) => { const keyPadding = " ".repeat(keyWidth - stringWidth(key)) const valuePadding = " ".repeat(padding) const separatorValue = separator || "" const continuationPadding = " ".repeat( keyWidth + stringWidth(separatorValue) + padding, ) const [firstLine = "", ...remainingLines] = String(value).split("\n") return [ `${keyAlignment === "right" ? `${keyPadding}${key}` : key}${separatorValue}${keyAlignment === "right" ? valuePadding : `${keyPadding}${valuePadding}`}${firstLine}`, ...remainingLines.map((line) => `${continuationPadding}${line}`), ].join("\n") }) .join("\n".repeat(Math.max(0, Math.trunc(lineSpacing)) + 1)) }