Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/three-boxes-follow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@clack/prompts": patch
"@clack/core": patch
---

select-key: Fixed wrapping and added new `caseSensitive` option
21 changes: 17 additions & 4 deletions packages/core/src/prompts/select-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Prompt, { type PromptOptions } from './prompt.js';
export interface SelectKeyOptions<T extends { value: string }>
extends PromptOptions<T['value'], SelectKeyPrompt<T>> {
options: T[];
caseSensitive?: boolean;
}
export default class SelectKeyPrompt<T extends { value: string }> extends Prompt<T['value']> {
options: T[];
Expand All @@ -12,12 +13,24 @@ export default class SelectKeyPrompt<T extends { value: string }> extends Prompt
super(opts, false);

this.options = opts.options;
const keys = this.options.map(({ value: [initial] }) => initial?.toLowerCase());
const caseSensitive = opts.caseSensitive === true;
const keys = this.options.map(({ value: [initial] }) => {
return caseSensitive ? initial : initial?.toLowerCase();
});
this.cursor = Math.max(keys.indexOf(opts.initialValue), 0);

this.on('key', (key) => {
if (!key || !keys.includes(key)) return;
const value = this.options.find(({ value: [initial] }) => initial?.toLowerCase() === key);
this.on('key', (key, keyInfo) => {
if (!key) {
return;
}
const casedKey = caseSensitive && keyInfo.shift ? key.toUpperCase() : key;
if (!keys.includes(casedKey)) {
return;
}

const value = this.options.find(({ value: [initial] }) => {
return caseSensitive ? initial === casedKey : initial?.toLowerCase() === key;
});
if (value) {
this.value = value.value;
this.state = 'submit';
Expand Down
67 changes: 47 additions & 20 deletions packages/prompts/src/select-key.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { SelectKeyPrompt } from '@clack/core';
import { SelectKeyPrompt, wrapTextWithPrefix } from '@clack/core';
import color from 'picocolors';
import { S_BAR, S_BAR_END, symbol } from './common.js';
import type { Option, SelectOptions } from './select.js';
import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js';
import type { Option } from './select.js';

export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
export interface SelectKeyOptions<Value extends string> extends CommonOptions {
message: string;
options: Option<Value>[];
initialValue?: Value;
caseSensitive?: boolean;
}

export const selectKey = <Value extends string>(opts: SelectKeyOptions<Value>) => {
const opt = (
option: Option<Value>,
state: 'inactive' | 'active' | 'selected' | 'cancelled' = 'inactive'
Expand All @@ -16,12 +23,12 @@ export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
return `${color.strikethrough(color.dim(label))}`;
}
if (state === 'active') {
return `${color.bgCyan(color.gray(` ${option.value} `))} ${label} ${
option.hint ? color.dim(`(${option.hint})`) : ''
return `${color.bgCyan(color.gray(` ${option.value} `))} ${label}${
option.hint ? ` ${color.dim(`(${option.hint})`)}` : ''
}`;
}
return `${color.gray(color.bgWhite(color.inverse(` ${option.value} `)))} ${label} ${
option.hint ? color.dim(`(${option.hint})`) : ''
return `${color.gray(color.bgWhite(color.inverse(` ${option.value} `)))} ${label}${
option.hint ? ` ${color.dim(`(${option.hint})`)}` : ''
}`;
};

Expand All @@ -31,23 +38,43 @@ export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
input: opts.input,
output: opts.output,
initialValue: opts.initialValue,
caseSensitive: opts.caseSensitive,
render() {
const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;

switch (this.state) {
case 'submit':
return `${title}${color.gray(S_BAR)} ${opt(
this.options.find((opt) => opt.value === this.value) ?? opts.options[0],
'selected'
)}`;
case 'cancel':
return `${title}${color.gray(S_BAR)} ${opt(this.options[0], 'cancelled')}\n${color.gray(
S_BAR
)}`;
case 'submit': {
const submitPrefix = `${color.gray(S_BAR)} `;
const selectedOption =
this.options.find((opt) => opt.value === this.value) ?? opts.options[0];
const wrapped = wrapTextWithPrefix(
opts.output,
opt(selectedOption, 'selected'),
submitPrefix
);
return `${title}${wrapped}`;
}
case 'cancel': {
const cancelPrefix = `${color.gray(S_BAR)} `;
const wrapped = wrapTextWithPrefix(
opts.output,
opt(this.options[0], 'cancelled'),
cancelPrefix
);
return `${title}${wrapped}\n${color.gray(S_BAR)}`;
}
default: {
return `${title}${color.cyan(S_BAR)} ${this.options
.map((option, i) => opt(option, i === this.cursor ? 'active' : 'inactive'))
.join(`\n${color.cyan(S_BAR)} `)}\n${color.cyan(S_BAR_END)}\n`;
const defaultPrefix = `${color.cyan(S_BAR)} `;
const wrapped = this.options
.map((option, i) =>
wrapTextWithPrefix(
opts.output,
opt(option, i === this.cursor ? 'active' : 'inactive'),
defaultPrefix
)
)
.join('\n');
return `${title}${wrapped}\n${color.cyan(S_BAR_END)}\n`;
}
}
},
Expand Down
Loading
Loading