diff --git a/.changeset/little-horses-sort.md b/.changeset/little-horses-sort.md new file mode 100644 index 00000000..efaf8ab3 --- /dev/null +++ b/.changeset/little-horses-sort.md @@ -0,0 +1,5 @@ +--- +'@clack/prompts': patch +--- + +feat: add `disabled` option to `select` and `multiselect` prompts diff --git a/packages/prompts/src/index.ts b/packages/prompts/src/index.ts index e50ecf3a..af89b9d2 100644 --- a/packages/prompts/src/index.ts +++ b/packages/prompts/src/index.ts @@ -228,6 +228,12 @@ export type Option = Value extends Primitive * By default, no `hint` is displayed. */ hint?: string; + /** + * If true, this option will be disabled. + * + * By default, no option is disabled. + */ + disabled?: boolean; } : { /** @@ -245,6 +251,12 @@ export type Option = Value extends Primitive * By default, no `hint` is displayed. */ hint?: string; + /** + * If true, this option will be disabled. + * + * By default, no option is disabled. + */ + disabled?: boolean; }; export interface SelectOptions { @@ -274,6 +286,11 @@ export const select = (opts: SelectOptions) => { return new SelectPrompt({ options: opts.options, initialValue: opts.initialValue, + validate(value) { + if (this.options.find((o) => o.value === value)?.disabled) { + return 'Selected option is disabled.'; + } + }, render() { const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; @@ -285,6 +302,15 @@ export const select = (opts: SelectOptions) => { this.options[this.cursor], 'cancelled' )}\n${color.gray(S_BAR)}`; + case 'error': + return `${title}${color.yellow(S_BAR)} ${limitOptions({ + cursor: this.cursor, + options: this.options, + maxItems: opts.maxItems, + style: (item, active) => opt(item, active ? 'active' : 'inactive'), + }).join( + `\n${color.yellow(S_BAR)} ` + )}\n${color.yellow(S_BAR_END)} ${color.yellow(this.error)}\n`; default: { return `${title}${color.cyan(S_BAR)} ${limitOptions({ cursor: this.cursor, @@ -388,7 +414,7 @@ export const multiselect = (opts: MultiSelectOptions) => { required: opts.required ?? true, cursorAt: opts.cursorAt, validate(selected: Value[]) { - if (this.required && selected.length === 0) + if (this.required && selected.length === 0) { return `Please select at least one option.\n${color.reset( color.dim( `Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray( @@ -396,6 +422,20 @@ export const multiselect = (opts: MultiSelectOptions) => { )} to submit` ) )}`; + } + const disabledOptions = opts.options + .map((option) => { + if (selected.includes(option.value) && option.disabled) { + return option.label ?? option.value; + } + return undefined; + }) + .filter(Boolean); + if (disabledOptions.length) { + return `${disabledOptions.join(', ')} ${ + disabledOptions.length > 1 ? 'options are' : 'option is' + } disabled.`; + } }, render() { const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;