From 6a724b4629be780d272ef254f8cf324b56ee0dba Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sat, 25 Jul 2026 10:05:01 +0000 Subject: [PATCH] fix(prompts): style box guide bar to match other gutters --- .changeset/box-guide-colour.md | 5 + packages/prompts/src/box.ts | 3 +- .../test/__snapshots__/box.test.ts.snap | 296 +++++++++--------- packages/prompts/test/box.test.ts | 17 + 4 files changed, 172 insertions(+), 149 deletions(-) create mode 100644 .changeset/box-guide-colour.md diff --git a/.changeset/box-guide-colour.md b/.changeset/box-guide-colour.md new file mode 100644 index 00000000..396a597c --- /dev/null +++ b/.changeset/box-guide-colour.md @@ -0,0 +1,5 @@ +--- +"@clack/prompts": patch +--- + +Style the guide bar rendered by `box({ withGuide: true })` in grey so it matches the gutter drawn by `log`, `note` and the spinner diff --git a/packages/prompts/src/box.ts b/packages/prompts/src/box.ts index 99d6f7c3..d3407bca 100644 --- a/packages/prompts/src/box.ts +++ b/packages/prompts/src/box.ts @@ -1,4 +1,5 @@ import type { Writable } from 'node:stream'; +import { styleText } from 'node:util'; import { getColumns, settings } from '@clack/core'; import stringWidth from 'fast-string-width'; import { wrapAnsi } from 'fast-wrap-ansi'; @@ -129,7 +130,7 @@ export const box = (message = '', title = '', opts?: BoxOptions) => { const contentPadding = opts?.contentPadding ?? 2; const width = opts?.width === undefined || opts.width === 'auto' ? 1 : Math.min(1, opts.width); const hasGuide = opts?.withGuide ?? settings.withGuide; - const linePrefix = !hasGuide ? '' : `${S_BAR} `; + const linePrefix = !hasGuide ? '' : `${styleText('gray', S_BAR)} `; const formatBorder = opts?.formatBorder ?? defaultFormatBorder; const symbols = (opts?.rounded ? roundedSymbols : squareSymbols).map(formatBorder); const hSymbol = formatBorder(S_BAR_H); diff --git a/packages/prompts/test/__snapshots__/box.test.ts.snap b/packages/prompts/test/__snapshots__/box.test.ts.snap index b8926767..3dfdb317 100644 --- a/packages/prompts/test/__snapshots__/box.test.ts.snap +++ b/packages/prompts/test/__snapshots__/box.test.ts.snap @@ -2,234 +2,234 @@ exports[`box (isCI = false) > cannot have width larger than 100% 1`] = ` [ - "│ ┌─title──────────────────────────────────────────────────────────────────────┐ + "│ ┌─title──────────────────────────────────────────────────────────────────────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders as specified width 1`] = ` [ - "│ ┌─title──────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ │ short │ + "│ │ short │ ", - "│ │ somewhat questionably long line │ + "│ │ somewhat questionably long line │ ", - "│ └────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders as wide as longest line with width: auto 1`] = ` [ - "│ ┌─title──────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ │ short │ + "│ │ short │ ", - "│ │ somewhat questionably long line │ + "│ │ somewhat questionably long line │ ", - "│ └────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders auto width with content longer than title 1`] = ` [ - "│ ┌─title──────────────────────────┐ + "│ ┌─title──────────────────────────┐ ", - "│ │ messagemessagemessagemessage │ + "│ │ messagemessagemessagemessage │ ", - "│ └────────────────────────────────┘ + "│ └────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders auto width with title longer than content 1`] = ` [ - "│ ┌─titletitletitletitle─┐ + "│ ┌─titletitletitletitle─┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └──────────────────────┘ + "│ └──────────────────────┘ ", ] `; exports[`box (isCI = false) > renders center aligned content 1`] = ` [ - "│ ┌─title──────┐ + "│ ┌─title──────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders center aligned title 1`] = ` [ - "│ ┌───title────┐ + "│ ┌───title────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders left aligned content 1`] = ` [ - "│ ┌─title──────┐ + "│ ┌─title──────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders left aligned title 1`] = ` [ - "│ ┌─title──────┐ + "│ ┌─title──────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders message 1`] = ` [ - "│ ┌────────────────────────────────────────────────────────────────────────────┐ + "│ ┌────────────────────────────────────────────────────────────────────────────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders message with title 1`] = ` [ - "│ ┌─some title─────────────────────────────────────────────────────────────────┐ + "│ ┌─some title─────────────────────────────────────────────────────────────────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders right aligned content 1`] = ` [ - "│ ┌─title──────┐ + "│ ┌─title──────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders right aligned title 1`] = ` [ - "│ ┌──────title─┐ + "│ ┌──────title─┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders rounded corners when rounded is true 1`] = ` [ - "│ ╭─title──────╮ + "│ ╭─title──────╮ ", - "│ │ message │ + "│ │ message │ ", - "│ ╰────────────╯ + "│ ╰────────────╯ ", ] `; exports[`box (isCI = false) > renders specified contentPadding 1`] = ` [ - "│ ┌─title──────────────┐ + "│ ┌─title──────────────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────────────┘ + "│ └────────────────────┘ ", ] `; exports[`box (isCI = false) > renders specified titlePadding 1`] = ` [ - "│ ┌──────title───────┐ + "│ ┌──────title───────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └──────────────────┘ + "│ └──────────────────┘ ", ] `; exports[`box (isCI = false) > renders truncated long titles 1`] = ` [ - "│ ┌─foofoof...─┐ + "│ ┌─foofoof...─┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders wide characters with auto width 1`] = ` [ - "│ ┌─这是标题─────────────────┐ + "│ ┌─这是标题─────────────────┐ ", - "│ │ 이게 첫 번째 줄이에요 │ + "│ │ 이게 첫 번째 줄이에요 │ ", - "│ │ これは次の行です │ + "│ │ これは次の行です │ ", - "│ └──────────────────────────┘ + "│ └──────────────────────────┘ ", ] `; exports[`box (isCI = false) > renders wide characters with specified width 1`] = ` [ - "│ ┌─这是标题───┐ + "│ ┌─这是标题───┐ ", - "│ │ 이게 첫 │ + "│ │ 이게 첫 │ ", - "│ │ 번째 │ + "│ │ 번째 │ ", - "│ │ 줄이에요 │ + "│ │ 줄이에요 │ ", - "│ │ これは次 │ + "│ │ これは次 │ ", - "│ │ の行です │ + "│ │ の行です │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = false) > renders with formatBorder formatting 1`] = ` [ - "│ ┌─title──────┐ + "│ ┌─title──────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; @@ -258,253 +258,253 @@ exports[`box (isCI = false) > renders without guide when withGuide is false 1`] exports[`box (isCI = false) > wraps content to fit within specified width 1`] = ` [ - "│ ┌─title──────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ │ foo barfoo barfoo barfoo barfoo │ + "│ │ foo barfoo barfoo barfoo barfoo │ ", - "│ │ barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ │ barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ │ barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ │ barfoo barfoo barfoo bar │ + "│ │ barfoo barfoo barfoo bar │ ", - "│ └────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > cannot have width larger than 100% 1`] = ` [ - "│ ┌─title──────────────────────────────────────────────────────────────────────┐ + "│ ┌─title──────────────────────────────────────────────────────────────────────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders as specified width 1`] = ` [ - "│ ┌─title──────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ │ short │ + "│ │ short │ ", - "│ │ somewhat questionably long line │ + "│ │ somewhat questionably long line │ ", - "│ └────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders as wide as longest line with width: auto 1`] = ` [ - "│ ┌─title──────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ │ short │ + "│ │ short │ ", - "│ │ somewhat questionably long line │ + "│ │ somewhat questionably long line │ ", - "│ └────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders auto width with content longer than title 1`] = ` [ - "│ ┌─title──────────────────────────┐ + "│ ┌─title──────────────────────────┐ ", - "│ │ messagemessagemessagemessage │ + "│ │ messagemessagemessagemessage │ ", - "│ └────────────────────────────────┘ + "│ └────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders auto width with title longer than content 1`] = ` [ - "│ ┌─titletitletitletitle─┐ + "│ ┌─titletitletitletitle─┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └──────────────────────┘ + "│ └──────────────────────┘ ", ] `; exports[`box (isCI = true) > renders center aligned content 1`] = ` [ - "│ ┌─title──────┐ + "│ ┌─title──────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders center aligned title 1`] = ` [ - "│ ┌───title────┐ + "│ ┌───title────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders left aligned content 1`] = ` [ - "│ ┌─title──────┐ + "│ ┌─title──────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders left aligned title 1`] = ` [ - "│ ┌─title──────┐ + "│ ┌─title──────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders message 1`] = ` [ - "│ ┌────────────────────────────────────────────────────────────────────────────┐ + "│ ┌────────────────────────────────────────────────────────────────────────────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders message with title 1`] = ` [ - "│ ┌─some title─────────────────────────────────────────────────────────────────┐ + "│ ┌─some title─────────────────────────────────────────────────────────────────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────────────────────────────────────────────────────────────────────┘ + "│ └────────────────────────────────────────────────────────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders right aligned content 1`] = ` [ - "│ ┌─title──────┐ + "│ ┌─title──────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders right aligned title 1`] = ` [ - "│ ┌──────title─┐ + "│ ┌──────title─┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders rounded corners when rounded is true 1`] = ` [ - "│ ╭─title──────╮ + "│ ╭─title──────╮ ", - "│ │ message │ + "│ │ message │ ", - "│ ╰────────────╯ + "│ ╰────────────╯ ", ] `; exports[`box (isCI = true) > renders specified contentPadding 1`] = ` [ - "│ ┌─title──────────────┐ + "│ ┌─title──────────────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────────────┘ + "│ └────────────────────┘ ", ] `; exports[`box (isCI = true) > renders specified titlePadding 1`] = ` [ - "│ ┌──────title───────┐ + "│ ┌──────title───────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └──────────────────┘ + "│ └──────────────────┘ ", ] `; exports[`box (isCI = true) > renders truncated long titles 1`] = ` [ - "│ ┌─foofoof...─┐ + "│ ┌─foofoof...─┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders wide characters with auto width 1`] = ` [ - "│ ┌─这是标题─────────────────┐ + "│ ┌─这是标题─────────────────┐ ", - "│ │ 이게 첫 번째 줄이에요 │ + "│ │ 이게 첫 번째 줄이에요 │ ", - "│ │ これは次の行です │ + "│ │ これは次の行です │ ", - "│ └──────────────────────────┘ + "│ └──────────────────────────┘ ", ] `; exports[`box (isCI = true) > renders wide characters with specified width 1`] = ` [ - "│ ┌─这是标题───┐ + "│ ┌─这是标题───┐ ", - "│ │ 이게 첫 │ + "│ │ 이게 첫 │ ", - "│ │ 번째 │ + "│ │ 번째 │ ", - "│ │ 줄이에요 │ + "│ │ 줄이에요 │ ", - "│ │ これは次 │ + "│ │ これは次 │ ", - "│ │ の行です │ + "│ │ の行です │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; exports[`box (isCI = true) > renders with formatBorder formatting 1`] = ` [ - "│ ┌─title──────┐ + "│ ┌─title──────┐ ", - "│ │ message │ + "│ │ message │ ", - "│ └────────────┘ + "│ └────────────┘ ", ] `; @@ -533,19 +533,19 @@ exports[`box (isCI = true) > renders without guide when withGuide is false 1`] = exports[`box (isCI = true) > wraps content to fit within specified width 1`] = ` [ - "│ ┌─title──────────────────────────────┐ + "│ ┌─title──────────────────────────────┐ ", - "│ │ foo barfoo barfoo barfoo barfoo │ + "│ │ foo barfoo barfoo barfoo barfoo │ ", - "│ │ barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ │ barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ │ barfoo barfoo barfoo barfoo │ + "│ │ barfoo barfoo barfoo barfoo │ ", - "│ │ barfoo barfoo barfoo bar │ + "│ │ barfoo barfoo barfoo bar │ ", - "│ └────────────────────────────────────┘ + "│ └────────────────────────────────────┘ ", ] `; diff --git a/packages/prompts/test/box.test.ts b/packages/prompts/test/box.test.ts index 54378210..4163c7b5 100644 --- a/packages/prompts/test/box.test.ts +++ b/packages/prompts/test/box.test.ts @@ -98,6 +98,23 @@ describe.each(['true', 'false'])('box (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); + test('renders the guide in the same style as log', () => { + const logOutput = new MockWritable(); + prompts.log.info('message', { input, output: logOutput }); + const [logGuide] = logOutput.buffer.join('').split('\n'); + + prompts.box('message', 'title', { + input, + output, + width: 'auto', + formatBorder: (text) => styleText('green', text), + }); + + for (const line of output.buffer) { + expect(line.startsWith(`${logGuide} `)).toBe(true); + } + }); + test('renders without guide when withGuide is false', () => { prompts.box('message', 'title', { input,