[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 select-key option (#59)

authored by

Nate Moore and committed by
GitHub
(Feb 21, 2023, 9:23 PM -0600) 0ef3dc17 57df9009

+87 -1
+6
.changeset/four-guests-give.md
··· 1 + --- 2 + '@clack/prompts': minor 3 + '@clack/core': minor 4 + --- 5 + 6 + Adds a `selectKey` prompt type
+1 -1
examples/basic/index.ts
··· 1 1 import * as p from '@clack/prompts'; 2 - import color from 'picocolors'; 3 2 import { setTimeout } from 'node:timers/promises'; 3 + import color from 'picocolors'; 4 4 5 5 async function main() { 6 6 console.clear();
+1
packages/core/src/index.ts
··· 4 4 export { default as Prompt, isCancel } from './prompts/prompt'; 5 5 export type { State } from './prompts/prompt'; 6 6 export { default as SelectPrompt } from './prompts/select'; 7 + export { default as SelectKeyPrompt } from './prompts/select-key'; 7 8 export { default as TextPrompt } from './prompts/text'; 8 9 export { block } from './utils';
+3
packages/core/src/prompts/prompt.ts
··· 162 162 if (char && (char.toLowerCase() === 'y' || char.toLowerCase() === 'n')) { 163 163 this.emit('confirm', char.toLowerCase() === 'y'); 164 164 } 165 + if (char) { 166 + this.emit('key', char.toLowerCase()); 167 + } 165 168 166 169 if (key?.name === 'return') { 167 170 if (this.opts.validate) {
+27
packages/core/src/prompts/select-key.ts
··· 1 + import Prompt, { PromptOptions } from './prompt'; 2 + 3 + interface SelectKeyOptions<T extends { value: any }> extends PromptOptions<SelectKeyPrompt<T>> { 4 + options: T[]; 5 + } 6 + export default class SelectKeyPrompt<T extends { value: any }> extends Prompt { 7 + options: T[]; 8 + cursor: number = 0; 9 + 10 + constructor(opts: SelectKeyOptions<T>) { 11 + super(opts, false); 12 + 13 + this.options = opts.options; 14 + const keys = this.options.map(({ value: [initial] }) => initial?.toLowerCase()); 15 + this.cursor = Math.max(keys.indexOf(opts.initialValue), 0); 16 + 17 + this.on('key', (key) => { 18 + if (!keys.includes(key)) return; 19 + const value = this.options.find(({ value: [initial] }) => initial?.toLowerCase() === key); 20 + if (value) { 21 + this.value = value.value; 22 + this.state = 'submit'; 23 + this.emit('submit'); 24 + } 25 + }); 26 + } 27 + }
+49
packages/prompts/src/index.ts
··· 4 4 isCancel, 5 5 MultiSelectPrompt, 6 6 PasswordPrompt, 7 + SelectKeyPrompt, 7 8 SelectPrompt, 8 9 State, 9 10 TextPrompt, ··· 206 207 this.options[this.cursor], 207 208 'cancelled' 208 209 )}\n${color.gray(S_BAR)}`; 210 + default: { 211 + return `${title}${color.cyan(S_BAR)} ${this.options 212 + .map((option, i) => opt(option, i === this.cursor ? 'active' : 'inactive')) 213 + .join(`\n${color.cyan(S_BAR)} `)}\n${color.cyan(S_BAR_END)}\n`; 214 + } 215 + } 216 + }, 217 + }).prompt() as Promise<Options[number]['value'] | symbol>; 218 + }; 219 + 220 + export const selectKey = <Options extends Option<Value>[], Value extends string>( 221 + opts: SelectOptions<Options, Value> 222 + ) => { 223 + const opt = ( 224 + option: Options[number], 225 + state: 'inactive' | 'active' | 'selected' | 'cancelled' = 'inactive' 226 + ) => { 227 + const label = option.label ?? String(option.value); 228 + if (state === 'selected') { 229 + return `${color.dim(label)}`; 230 + } else if (state === 'cancelled') { 231 + return `${color.strikethrough(color.dim(label))}`; 232 + } else if (state === 'active') { 233 + return `${color.bgCyan(color.gray(` ${option.value} `))} ${label} ${ 234 + option.hint ? color.dim(`(${option.hint})`) : '' 235 + }`; 236 + } 237 + return `${color.gray(color.bgWhite(color.inverse(` ${option.value} `)))} ${label} ${ 238 + option.hint ? color.dim(`(${option.hint})`) : '' 239 + }`; 240 + }; 241 + 242 + return new SelectKeyPrompt({ 243 + options: opts.options, 244 + initialValue: opts.initialValue, 245 + render() { 246 + const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; 247 + 248 + switch (this.state) { 249 + case 'submit': 250 + return `${title}${color.gray(S_BAR)} ${opt( 251 + this.options.find((opt) => opt.value === this.value)!, 252 + 'selected' 253 + )}`; 254 + case 'cancel': 255 + return `${title}${color.gray(S_BAR)} ${opt(this.options[0], 'cancelled')}\n${color.gray( 256 + S_BAR 257 + )}`; 209 258 default: { 210 259 return `${title}${color.cyan(S_BAR)} ${this.options 211 260 .map((option, i) => opt(option, i === this.cursor ? 'active' : 'inactive'))