diff --git a/.changeset/crazy-ducks-shine.md b/.changeset/crazy-ducks-shine.md new file mode 100644 index 00000000..101bd876 --- /dev/null +++ b/.changeset/crazy-ducks-shine.md @@ -0,0 +1,5 @@ +--- +"@clack/prompts": minor +--- + +feat: add styleFrame option for spinner diff --git a/packages/prompts/src/spinner.ts b/packages/prompts/src/spinner.ts index 7b4c8cec..498a1f56 100644 --- a/packages/prompts/src/spinner.ts +++ b/packages/prompts/src/spinner.ts @@ -19,6 +19,7 @@ export interface SpinnerOptions extends CommonOptions { errorMessage?: string; frames?: string[]; delay?: number; + styleFrame?: (frame: string) => string; } export interface SpinnerResult { @@ -28,6 +29,8 @@ export interface SpinnerResult { readonly isCancelled: boolean; } +const defaultStyleFn: SpinnerOptions['styleFrame'] = color.magenta; + export const spinner = ({ indicator = 'dots', onCancel, @@ -37,6 +40,7 @@ export const spinner = ({ frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0'], delay = unicode ? 80 : 120, signal, + ...opts }: SpinnerOptions = {}): SpinnerResult => { const isCI = isCIFn(); @@ -48,6 +52,7 @@ export const spinner = ({ let _prevMessage: string | undefined; let _origin: number = performance.now(); const columns = getColumns(output); + const styleFn = opts?.styleFrame ?? defaultStyleFn; const handleExit = (code: number) => { const msg = @@ -134,7 +139,7 @@ export const spinner = ({ } clearPrevMessage(); _prevMessage = _message; - const frame = color.magenta(frames[frameIndex]); + const frame = styleFn(frames[frameIndex]); let outputMessage: string; if (isCI) { diff --git a/packages/prompts/test/__snapshots__/spinner.test.ts.snap b/packages/prompts/test/__snapshots__/spinner.test.ts.snap index 5bc4a650..62a94c3f 100644 --- a/packages/prompts/test/__snapshots__/spinner.test.ts.snap +++ b/packages/prompts/test/__snapshots__/spinner.test.ts.snap @@ -34,6 +34,29 @@ exports[`spinner (isCI = false) > indicator customization > custom delay 1`] = ` ] `; +exports[`spinner (isCI = false) > indicator customization > custom frame style 1`] = ` +[ + "", + "│ +", + "◒ ", + "", + "", + "◐ ", + "", + "", + "◓ ", + "", + "", + "◑ ", + "", + "", + "◇ +", + "", +] +`; + exports[`spinner (isCI = false) > indicator customization > custom frames 1`] = ` [ "", @@ -535,6 +558,22 @@ exports[`spinner (isCI = true) > indicator customization > custom delay 1`] = ` ] `; +exports[`spinner (isCI = true) > indicator customization > custom frame style 1`] = ` +[ + "", + "│ +", + "◒ ...", + " +", + "", + "", + "◇ +", + "", +] +`; + exports[`spinner (isCI = true) > indicator customization > custom frames 1`] = ` [ "", diff --git a/packages/prompts/test/spinner.test.ts b/packages/prompts/test/spinner.test.ts index 3db54cde..63ecc533 100644 --- a/packages/prompts/test/spinner.test.ts +++ b/packages/prompts/test/spinner.test.ts @@ -1,5 +1,6 @@ import { EventEmitter } from 'node:stream'; import { getColumns } from '@clack/core'; +import color from 'picocolors'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; import * as prompts from '../src/index.js'; import { MockWritable } from './test-utils.js'; @@ -233,6 +234,20 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); + + test('custom frame style', () => { + const result = prompts.spinner({ output, styleFrame: color.red }); + + result.start(); + + for (let i = 0; i < 4; i++) { + vi.advanceTimersByTime(80); + } + + result.stop(); + + expect(output.buffer).toMatchSnapshot(); + }); }); describe('process exit handling', () => {