Skip to content

Commit 68dbf9b

Browse files
authored
feat: add caseSensitive option to select-key (#443)
1 parent 55eb280 commit 68dbf9b

File tree

5 files changed

+847
-24
lines changed

5 files changed

+847
-24
lines changed

‎.changeset/three-boxes-follow.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@clack/prompts": patch
3+
"@clack/core": patch
4+
---
5+
6+
select-key: Fixed wrapping and added new `caseSensitive` option

‎packages/core/src/prompts/select-key.ts‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Prompt, { type PromptOptions } from './prompt.js';
33
export interface SelectKeyOptions<T extends { value: string }>
44
extends PromptOptions<T['value'], SelectKeyPrompt<T>> {
55
options: T[];
6+
caseSensitive?: boolean;
67
}
78
export default class SelectKeyPrompt<T extends { value: string }> extends Prompt<T['value']> {
89
options: T[];
@@ -12,12 +13,24 @@ export default class SelectKeyPrompt<T extends { value: string }> extends Prompt
1213
super(opts, false);
1314

1415
this.options = opts.options;
15-
const keys = this.options.map(({ value: [initial] }) => initial?.toLowerCase());
16+
const caseSensitive = opts.caseSensitive === true;
17+
const keys = this.options.map(({ value: [initial] }) => {
18+
return caseSensitive ? initial : initial?.toLowerCase();
19+
});
1620
this.cursor = Math.max(keys.indexOf(opts.initialValue), 0);
1721

18-
this.on('key', (key) => {
19-
if (!key || !keys.includes(key)) return;
20-
const value = this.options.find(({ value: [initial] }) => initial?.toLowerCase() === key);
22+
this.on('key', (key, keyInfo) => {
23+
if (!key) {
24+
return;
25+
}
26+
const casedKey = caseSensitive && keyInfo.shift ? key.toUpperCase() : key;
27+
if (!keys.includes(casedKey)) {
28+
return;
29+
}
30+
31+
const value = this.options.find(({ value: [initial] }) => {
32+
return caseSensitive ? initial === casedKey : initial?.toLowerCase() === key;
33+
});
2134
if (value) {
2235
this.value = value.value;
2336
this.state = 'submit';

‎packages/prompts/src/select-key.ts‎

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
import { SelectKeyPrompt } from '@clack/core';
1+
import { SelectKeyPrompt, wrapTextWithPrefix } from '@clack/core';
22
import color from 'picocolors';
3-
import { S_BAR, S_BAR_END, symbol } from './common.js';
4-
import type { Option, SelectOptions } from './select.js';
3+
import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js';
4+
import type { Option } from './select.js';
55

6-
export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
6+
export interface SelectKeyOptions<Value extends string> extends CommonOptions {
7+
message: string;
8+
options: Option<Value>[];
9+
initialValue?: Value;
10+
caseSensitive?: boolean;
11+
}
12+
13+
export const selectKey = <Value extends string>(opts: SelectKeyOptions<Value>) => {
714
const opt = (
815
option: Option<Value>,
916
state: 'inactive' | 'active' | 'selected' | 'cancelled' = 'inactive'
@@ -16,12 +23,12 @@ export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
1623
return `${color.strikethrough(color.dim(label))}`;
1724
}
1825
if (state === 'active') {
19-
return `${color.bgCyan(color.gray(` ${option.value} `))} ${label} ${
20-
option.hint ? color.dim(`(${option.hint})`) : ''
26+
return `${color.bgCyan(color.gray(` ${option.value} `))} ${label}${
27+
option.hint ? ` ${color.dim(`(${option.hint})`)}` : ''
2128
}`;
2229
}
23-
return `${color.gray(color.bgWhite(color.inverse(` ${option.value} `)))} ${label} ${
24-
option.hint ? color.dim(`(${option.hint})`) : ''
30+
return `${color.gray(color.bgWhite(color.inverse(` ${option.value} `)))} ${label}${
31+
option.hint ? ` ${color.dim(`(${option.hint})`)}` : ''
2532
}`;
2633
};
2734

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

3745
switch (this.state) {
38-
case 'submit':
39-
return `${title}${color.gray(S_BAR)} ${opt(
40-
this.options.find((opt) => opt.value === this.value) ?? opts.options[0],
41-
'selected'
42-
)}`;
43-
case 'cancel':
44-
return `${title}${color.gray(S_BAR)} ${opt(this.options[0], 'cancelled')}\n${color.gray(
45-
S_BAR
46-
)}`;
46+
case 'submit': {
47+
const submitPrefix = `${color.gray(S_BAR)} `;
48+
const selectedOption =
49+
this.options.find((opt) => opt.value === this.value) ?? opts.options[0];
50+
const wrapped = wrapTextWithPrefix(
51+
opts.output,
52+
opt(selectedOption, 'selected'),
53+
submitPrefix
54+
);
55+
return `${title}${wrapped}`;
56+
}
57+
case 'cancel': {
58+
const cancelPrefix = `${color.gray(S_BAR)} `;
59+
const wrapped = wrapTextWithPrefix(
60+
opts.output,
61+
opt(this.options[0], 'cancelled'),
62+
cancelPrefix
63+
);
64+
return `${title}${wrapped}\n${color.gray(S_BAR)}`;
65+
}
4766
default: {
48-
return `${title}${color.cyan(S_BAR)} ${this.options
49-
.map((option, i) => opt(option, i === this.cursor ? 'active' : 'inactive'))
50-
.join(`\n${color.cyan(S_BAR)} `)}\n${color.cyan(S_BAR_END)}\n`;
67+
const defaultPrefix = `${color.cyan(S_BAR)} `;
68+
const wrapped = this.options
69+
.map((option, i) =>
70+
wrapTextWithPrefix(
71+
opts.output,
72+
opt(option, i === this.cursor ? 'active' : 'inactive'),
73+
defaultPrefix
74+
)
75+
)
76+
.join('\n');
77+
return `${title}${wrapped}\n${color.cyan(S_BAR_END)}\n`;
5178
}
5279
}
5380
},

0 commit comments

Comments
 (0)