[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: timer indicator for `spinner` (#230)

Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>

authored by

Maciej Jastrzebski
Nate Moore
and committed by
GitHub
(Feb 4, 2025, 11:31 PM -0600) 613179d5 251518bd

+72 -7
+13
.changeset/bright-chefs-double.md
··· 1 + --- 2 + '@clack/prompts': minor 3 + --- 4 + 5 + Adds a new `indicator` option to `spinner`, which supports the original `"dots"` loading animation or a new `"timer"` loading animation. 6 + 7 + ```ts 8 + import * as p from '@clack/prompts'; 9 + 10 + const spin = p.spinner({ indicator: 'timer' }); 11 + spin.start('Loading'); 12 + await sleep(3000); 13 + spin.stop('Loaded');
+2 -1
examples/basic/package.json
··· 12 12 "start": "jiti ./index.ts", 13 13 "stream": "jiti ./stream.ts", 14 14 "spinner": "jiti ./spinner.ts", 15 - "spinner-ci": "npx cross-env CI=\"true\" jiti ./spinner-ci.ts" 15 + "spinner-ci": "npx cross-env CI=\"true\" jiti ./spinner-ci.ts", 16 + "spinner-timer": "jiti ./spinner-timer.ts" 16 17 }, 17 18 "devDependencies": { 18 19 "jiti": "^1.17.0"
+26
examples/basic/spinner-timer.ts
··· 1 + import * as p from '@clack/prompts'; 2 + 3 + p.intro('spinner start...'); 4 + 5 + async function main() { 6 + const spin = p.spinner({ indicator: 'timer' }); 7 + 8 + spin.start('First spinner'); 9 + 10 + await sleep(3_000); 11 + 12 + spin.stop('Done first spinner'); 13 + 14 + spin.start('Second spinner'); 15 + await sleep(5_000); 16 + 17 + spin.stop('Done second spinner'); 18 + 19 + p.outro('spinner stop.'); 20 + } 21 + 22 + function sleep(ms: number) { 23 + return new Promise((resolve) => setTimeout(resolve, ms)); 24 + } 25 + 26 + main();
+31 -6
packages/prompts/src/index.ts
··· 720 720 }, 721 721 }; 722 722 723 - export const spinner = () => { 723 + export interface SpinnerOptions { 724 + indicator?: 'dots' | 'timer'; 725 + } 726 + 727 + export const spinner = ({ indicator = 'dots' }: SpinnerOptions = {}) => { 724 728 const frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0']; 725 729 const delay = unicode ? 80 : 120; 726 730 const isCI = process.env.CI === 'true'; ··· 730 734 let isSpinnerActive = false; 731 735 let _message = ''; 732 736 let _prevMessage: string | undefined = undefined; 737 + let _origin: number = performance.now(); 733 738 734 739 const handleExit = (code: number) => { 735 740 const msg = code > 1 ? 'Something went wrong' : 'Canceled'; ··· 770 775 return msg.replace(/\.+$/, ''); 771 776 }; 772 777 778 + const formatTimer = (origin: number): string => { 779 + const duration = (performance.now() - origin) / 1000; 780 + const min = Math.floor(duration / 60); 781 + const secs = Math.floor(duration % 60); 782 + return min > 0 ? `[${min}m ${secs}s]` : `[${secs}s]`; 783 + }; 784 + 773 785 const start = (msg = ''): void => { 774 786 isSpinnerActive = true; 775 787 unblock = block(); 776 788 _message = parseMessage(msg); 789 + _origin = performance.now(); 777 790 process.stdout.write(`${color.gray(S_BAR)}\n`); 778 791 let frameIndex = 0; 779 - let dotsTimer = 0; 792 + let indicatorTimer = 0; 780 793 registerHooks(); 781 794 loop = setInterval(() => { 782 795 if (isCI && _message === _prevMessage) { ··· 785 798 clearPrevMessage(); 786 799 _prevMessage = _message; 787 800 const frame = color.magenta(frames[frameIndex]); 788 - const loadingDots = isCI ? '...' : '.'.repeat(Math.floor(dotsTimer)).slice(0, 3); 789 - process.stdout.write(`${frame} ${_message}${loadingDots}`); 801 + 802 + if (isCI) { 803 + process.stdout.write(`${frame} ${_message}...`); 804 + } else if (indicator === 'timer') { 805 + process.stdout.write(`${frame} ${_message} ${formatTimer(_origin)}`); 806 + } else { 807 + const loadingDots = '.'.repeat(Math.floor(indicatorTimer)).slice(0, 3); 808 + process.stdout.write(`${frame} ${_message}${loadingDots}`); 809 + } 810 + 790 811 frameIndex = frameIndex + 1 < frames.length ? frameIndex + 1 : 0; 791 - dotsTimer = dotsTimer < frames.length ? dotsTimer + 0.125 : 0; 812 + indicatorTimer = indicatorTimer < frames.length ? indicatorTimer + 0.125 : 0; 792 813 }, delay); 793 814 }; 794 815 ··· 803 824 ? color.red(S_STEP_CANCEL) 804 825 : color.red(S_STEP_ERROR); 805 826 _message = parseMessage(msg ?? _message); 806 - process.stdout.write(`${step} ${_message}\n`); 827 + if (indicator === 'timer') { 828 + process.stdout.write(`${step} ${_message} ${formatTimer(_origin)}\n`); 829 + } else { 830 + process.stdout.write(`${step} ${_message}\n`); 831 + } 807 832 clearHooks(); 808 833 unblock(); 809 834 };