Skip to content

Commit

Permalink
feat: timer indicator for spinner (#230)
Browse files Browse the repository at this point in the history
Co-authored-by: Nate Moore <[email protected]>
  • Loading branch information
mdjastrzebski and natemoo-re authored Feb 5, 2025
1 parent 251518b commit 613179d
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 7 deletions.
13 changes: 13 additions & 0 deletions .changeset/bright-chefs-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@clack/prompts': minor
---

Adds a new `indicator` option to `spinner`, which supports the original `"dots"` loading animation or a new `"timer"` loading animation.

```ts
import * as p from '@clack/prompts';

const spin = p.spinner({ indicator: 'timer' });
spin.start('Loading');
await sleep(3000);
spin.stop('Loaded');
3 changes: 2 additions & 1 deletion examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"start": "jiti ./index.ts",
"stream": "jiti ./stream.ts",
"spinner": "jiti ./spinner.ts",
"spinner-ci": "npx cross-env CI=\"true\" jiti ./spinner-ci.ts"
"spinner-ci": "npx cross-env CI=\"true\" jiti ./spinner-ci.ts",
"spinner-timer": "jiti ./spinner-timer.ts"
},
"devDependencies": {
"jiti": "^1.17.0"
Expand Down
26 changes: 26 additions & 0 deletions examples/basic/spinner-timer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as p from '@clack/prompts';

p.intro('spinner start...');

async function main() {
const spin = p.spinner({ indicator: 'timer' });

spin.start('First spinner');

await sleep(3_000);

spin.stop('Done first spinner');

spin.start('Second spinner');
await sleep(5_000);

spin.stop('Done second spinner');

p.outro('spinner stop.');
}

function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

main();
37 changes: 31 additions & 6 deletions packages/prompts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,11 @@ export const stream = {
},
};

export const spinner = () => {
export interface SpinnerOptions {
indicator?: 'dots' | 'timer';
}

export const spinner = ({ indicator = 'dots' }: SpinnerOptions = {}) => {
const frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0'];
const delay = unicode ? 80 : 120;
const isCI = process.env.CI === 'true';
Expand All @@ -730,6 +734,7 @@ export const spinner = () => {
let isSpinnerActive = false;
let _message = '';
let _prevMessage: string | undefined = undefined;
let _origin: number = performance.now();

const handleExit = (code: number) => {
const msg = code > 1 ? 'Something went wrong' : 'Canceled';
Expand Down Expand Up @@ -770,13 +775,21 @@ export const spinner = () => {
return msg.replace(/\.+$/, '');
};

const formatTimer = (origin: number): string => {
const duration = (performance.now() - origin) / 1000;
const min = Math.floor(duration / 60);
const secs = Math.floor(duration % 60);
return min > 0 ? `[${min}m ${secs}s]` : `[${secs}s]`;
};

const start = (msg = ''): void => {
isSpinnerActive = true;
unblock = block();
_message = parseMessage(msg);
_origin = performance.now();
process.stdout.write(`${color.gray(S_BAR)}\n`);
let frameIndex = 0;
let dotsTimer = 0;
let indicatorTimer = 0;
registerHooks();
loop = setInterval(() => {
if (isCI && _message === _prevMessage) {
Expand All @@ -785,10 +798,18 @@ export const spinner = () => {
clearPrevMessage();
_prevMessage = _message;
const frame = color.magenta(frames[frameIndex]);
const loadingDots = isCI ? '...' : '.'.repeat(Math.floor(dotsTimer)).slice(0, 3);
process.stdout.write(`${frame} ${_message}${loadingDots}`);

if (isCI) {
process.stdout.write(`${frame} ${_message}...`);
} else if (indicator === 'timer') {
process.stdout.write(`${frame} ${_message} ${formatTimer(_origin)}`);
} else {
const loadingDots = '.'.repeat(Math.floor(indicatorTimer)).slice(0, 3);
process.stdout.write(`${frame} ${_message}${loadingDots}`);
}

frameIndex = frameIndex + 1 < frames.length ? frameIndex + 1 : 0;
dotsTimer = dotsTimer < frames.length ? dotsTimer + 0.125 : 0;
indicatorTimer = indicatorTimer < frames.length ? indicatorTimer + 0.125 : 0;
}, delay);
};

Expand All @@ -803,7 +824,11 @@ export const spinner = () => {
? color.red(S_STEP_CANCEL)
: color.red(S_STEP_ERROR);
_message = parseMessage(msg ?? _message);
process.stdout.write(`${step} ${_message}\n`);
if (indicator === 'timer') {
process.stdout.write(`${step} ${_message} ${formatTimer(_origin)}\n`);
} else {
process.stdout.write(`${step} ${_message}\n`);
}
clearHooks();
unblock();
};
Expand Down

0 comments on commit 613179d

Please sign in to comment.