[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

feat: add style function option to spinner (#400)

Co-authored-by: James Garbutt <43081j@users.noreply.github.com>

authored by

Hyper-Z11
James Garbutt
and committed by
GitHub
(Sep 30, 2025, 9:19 AM +0100) 8409f2ca aea4573c

+65 -1
+5
.changeset/crazy-ducks-shine.md
··· 1 + --- 2 + "@clack/prompts": minor 3 + --- 4 + 5 + feat: add styleFrame option for spinner
+6 -1
packages/prompts/src/spinner.ts
··· 19 19 errorMessage?: string; 20 20 frames?: string[]; 21 21 delay?: number; 22 + styleFrame?: (frame: string) => string; 22 23 } 23 24 24 25 export interface SpinnerResult { ··· 27 28 message(msg?: string): void; 28 29 readonly isCancelled: boolean; 29 30 } 31 + 32 + const defaultStyleFn: SpinnerOptions['styleFrame'] = color.magenta; 30 33 31 34 export const spinner = ({ 32 35 indicator = 'dots', ··· 37 40 frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0'], 38 41 delay = unicode ? 80 : 120, 39 42 signal, 43 + ...opts 40 44 }: SpinnerOptions = {}): SpinnerResult => { 41 45 const isCI = isCIFn(); 42 46 ··· 48 52 let _prevMessage: string | undefined; 49 53 let _origin: number = performance.now(); 50 54 const columns = getColumns(output); 55 + const styleFn = opts?.styleFrame ?? defaultStyleFn; 51 56 52 57 const handleExit = (code: number) => { 53 58 const msg = ··· 134 139 } 135 140 clearPrevMessage(); 136 141 _prevMessage = _message; 137 - const frame = color.magenta(frames[frameIndex]); 142 + const frame = styleFn(frames[frameIndex]); 138 143 let outputMessage: string; 139 144 140 145 if (isCI) {
+39
packages/prompts/test/__snapshots__/spinner.test.ts.snap
··· 34 34 ] 35 35 `; 36 36 37 + exports[`spinner (isCI = false) > indicator customization > custom frame style 1`] = ` 38 + [ 39 + "<cursor.hide>", 40 + "│ 41 + ", 42 + "◒ ", 43 + "<cursor.left count=1>", 44 + "<erase.down>", 45 + "◐ ", 46 + "<cursor.left count=1>", 47 + "<erase.down>", 48 + "◓ ", 49 + "<cursor.left count=1>", 50 + "<erase.down>", 51 + "◑ ", 52 + "<cursor.left count=1>", 53 + "<erase.down>", 54 + "◇ 55 + ", 56 + "<cursor.show>", 57 + ] 58 + `; 59 + 37 60 exports[`spinner (isCI = false) > indicator customization > custom frames 1`] = ` 38 61 [ 39 62 "<cursor.hide>", ··· 525 548 "│ 526 549 ", 527 550 "◒ ...", 551 + " 552 + ", 553 + "<cursor.left count=1>", 554 + "<erase.down>", 555 + "◇ 556 + ", 557 + "<cursor.show>", 558 + ] 559 + `; 560 + 561 + exports[`spinner (isCI = true) > indicator customization > custom frame style 1`] = ` 562 + [ 563 + "<cursor.hide>", 564 + "│ 565 + ", 566 + "◒ ...", 528 567 " 529 568 ", 530 569 "<cursor.left count=1>",
+15
packages/prompts/test/spinner.test.ts
··· 1 1 import { EventEmitter } from 'node:stream'; 2 2 import { getColumns } from '@clack/core'; 3 + import color from 'picocolors'; 3 4 import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; 4 5 import * as prompts from '../src/index.js'; 5 6 import { MockWritable } from './test-utils.js'; ··· 227 228 // there are 4 frames 228 229 for (let i = 0; i < 4; i++) { 229 230 vi.advanceTimersByTime(200); 231 + } 232 + 233 + result.stop(); 234 + 235 + expect(output.buffer).toMatchSnapshot(); 236 + }); 237 + 238 + test('custom frame style', () => { 239 + const result = prompts.spinner({ output, styleFrame: color.red }); 240 + 241 + result.start(); 242 + 243 + for (let i = 0; i < 4; i++) { 244 + vi.advanceTimersByTime(80); 230 245 } 231 246 232 247 result.stop();