Skip to content

Commit 8f1c380

Browse files
authored
feat(prompts): add showInstructions opt-out (#574)
1 parent 06c16c7 commit 8f1c380

10 files changed

Lines changed: 212 additions & 3 deletions

.changeset/fresh-facts-search.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@clack/prompts": minor
3+
---
4+
5+
Add `showInstructions` option to `select`, `multiselect`, and `groupMultiselect`. Keyboard hints remain shown by default; pass `showInstructions: false` to hide them.

packages/prompts/src/group-multi-select.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ export interface GroupMultiSelectOptions<Value> extends CommonOptions {
6060
* @default 0
6161
*/
6262
groupSpacing?: number;
63+
64+
/**
65+
* Show keyboard instructions below the option list.
66+
* @default true
67+
*/
68+
showInstructions?: boolean;
6369
}
6470

6571
/**
@@ -191,6 +197,7 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
191197
);
192198
};
193199
const required = opts.required ?? true;
200+
const showInstructions = opts.showInstructions ?? true;
194201

195202
return new GroupMultiSelectPrompt({
196203
options: opts.options,
@@ -288,7 +295,11 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
288295
default: {
289296
const guidePrefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : '';
290297
const titleLineCount = title.split('\n').length;
291-
const footerLines = formatInstructionFooter(MULTISELECT_INSTRUCTIONS, hasGuide);
298+
const footerLines = showInstructions
299+
? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, hasGuide)
300+
: hasGuide
301+
? [styleText('cyan', S_BAR_END)]
302+
: [];
292303
const footerText = footerLines.join('\n');
293304
const footerLineCount = footerLines.length + 1;
294305
const optionsText = limitOptions({

packages/prompts/src/multi-select.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ export interface MultiSelectOptions<Value> extends CommonOptions {
2727
maxItems?: number;
2828
required?: boolean;
2929
cursorAt?: Value;
30+
/**
31+
* Show keyboard instructions below the option list.
32+
* @default true
33+
*/
34+
showInstructions?: boolean;
3035
}
3136
const computeLabel = (label: string, format: (text: string) => string) => {
3237
return label
@@ -77,6 +82,7 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
7782
return `${styleText('dim', S_CHECKBOX_INACTIVE)} ${computeLabel(label, (text) => styleText('dim', text))}`;
7883
};
7984
const required = opts.required ?? true;
85+
const showInstructions = opts.showInstructions ?? true;
8086

8187
return new MultiSelectPrompt({
8288
options: opts.options,
@@ -179,7 +185,11 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
179185
default: {
180186
const prefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : '';
181187
const titleLineCount = title.split('\n').length;
182-
const footerLines = formatInstructionFooter(MULTISELECT_INSTRUCTIONS, hasGuide);
188+
const footerLines = showInstructions
189+
? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, hasGuide)
190+
: hasGuide
191+
? [styleText('cyan', S_BAR_END)]
192+
: [];
183193
const footerText = footerLines.join('\n');
184194
const footerLineCount = footerLines.length + 1;
185195
return `${title}${prefix}${limitOptions({

packages/prompts/src/select.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
type CommonOptions,
55
formatInstructionFooter,
66
S_BAR,
7+
S_BAR_END,
78
S_RADIO_ACTIVE,
89
S_RADIO_INACTIVE,
910
symbol,
@@ -75,6 +76,11 @@ export interface SelectOptions<Value> extends CommonOptions {
7576
options: Option<Value>[];
7677
initialValue?: Value;
7778
maxItems?: number;
79+
/**
80+
* Show keyboard instructions below the option list.
81+
* @default true
82+
*/
83+
showInstructions?: boolean;
7884
}
7985

8086
const computeLabel = (label: string, format: (text: string) => string) => {
@@ -111,6 +117,8 @@ export const select = <Value>(opts: SelectOptions<Value>) => {
111117
}
112118
};
113119

120+
const showInstructions = opts.showInstructions ?? true;
121+
114122
return new SelectPrompt({
115123
options: opts.options,
116124
signal: opts.signal,
@@ -151,7 +159,11 @@ export const select = <Value>(opts: SelectOptions<Value>) => {
151159
default: {
152160
const prefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : '';
153161
const titleLineCount = title.split('\n').length;
154-
const footerLines = formatInstructionFooter(SELECT_INSTRUCTIONS, hasGuide);
162+
const footerLines = showInstructions
163+
? formatInstructionFooter(SELECT_INSTRUCTIONS, hasGuide)
164+
: hasGuide
165+
? [styleText('cyan', S_BAR_END)]
166+
: [];
155167
const footerText = footerLines.join('\n');
156168
const footerLineCount = footerLines.length + 1;
157169
return `${title}${prefix}${limitOptions({

packages/prompts/test/__snapshots__/group-multi-select.test.ts.snap

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,27 @@ exports[`groupMultiselect (isCI = false) > selectableGroups = false > selecting
664664
]
665665
`;
666666
667+
exports[`groupMultiselect (isCI = false) > showInstructions: false hides instruction footer 1`] = `
668+
[
669+
"<cursor.hide>",
670+
"│
671+
◆ foo
672+
│ ◻ group1
673+
│ │ ◻ group1value0
674+
│ └ ◻ group1value1
675+
└
676+
",
677+
"<cursor.backward count=999><cursor.up count=6>",
678+
"<cursor.down count=1>",
679+
"<erase.down>",
680+
"◇ foo
681+
│",
682+
"
683+
",
684+
"<cursor.show>",
685+
]
686+
`;
687+
667688
exports[`groupMultiselect (isCI = false) > sliding window loops downwards 1`] = `
668689
[
669690
"<cursor.hide>",
@@ -1631,6 +1652,27 @@ exports[`groupMultiselect (isCI = true) > selectableGroups = false > selecting a
16311652
]
16321653
`;
16331654
1655+
exports[`groupMultiselect (isCI = true) > showInstructions: false hides instruction footer 1`] = `
1656+
[
1657+
"<cursor.hide>",
1658+
"│
1659+
◆ foo
1660+
│ ◻ group1
1661+
│ │ ◻ group1value0
1662+
│ └ ◻ group1value1
1663+
└
1664+
",
1665+
"<cursor.backward count=999><cursor.up count=6>",
1666+
"<cursor.down count=1>",
1667+
"<erase.down>",
1668+
"◇ foo
1669+
│",
1670+
"
1671+
",
1672+
"<cursor.show>",
1673+
]
1674+
`;
1675+
16341676
exports[`groupMultiselect (isCI = true) > sliding window loops downwards 1`] = `
16351677
[
16361678
"<cursor.hide>",

packages/prompts/test/__snapshots__/multi-select.test.ts.snap

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,26 @@ exports[`multiselect (isCI = false) > renders validation errors 1`] = `
575575
]
576576
`;
577577
578+
exports[`multiselect (isCI = false) > showInstructions: false hides instruction footer 1`] = `
579+
[
580+
"<cursor.hide>",
581+
"│
582+
◆ foo
583+
│ ◻ opt0
584+
│ ◻ opt1
585+
└
586+
",
587+
"<cursor.backward count=999><cursor.up count=5>",
588+
"<cursor.down count=1>",
589+
"<erase.down>",
590+
"◇ foo
591+
│ none",
592+
"
593+
",
594+
"<cursor.show>",
595+
]
596+
`;
597+
578598
exports[`multiselect (isCI = false) > shows hints for all selected options 1`] = `
579599
[
580600
"<cursor.hide>",
@@ -1514,6 +1534,26 @@ exports[`multiselect (isCI = true) > renders validation errors 1`] = `
15141534
]
15151535
`;
15161536
1537+
exports[`multiselect (isCI = true) > showInstructions: false hides instruction footer 1`] = `
1538+
[
1539+
"<cursor.hide>",
1540+
"│
1541+
◆ foo
1542+
│ ◻ opt0
1543+
│ ◻ opt1
1544+
└
1545+
",
1546+
"<cursor.backward count=999><cursor.up count=5>",
1547+
"<cursor.down count=1>",
1548+
"<erase.down>",
1549+
"◇ foo
1550+
│ none",
1551+
"
1552+
",
1553+
"<cursor.show>",
1554+
]
1555+
`;
1556+
15171557
exports[`multiselect (isCI = true) > shows hints for all selected options 1`] = `
15181558
[
15191559
"<cursor.hide>",

packages/prompts/test/__snapshots__/select.test.ts.snap

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,26 @@ exports[`select (isCI = false) > renders options and message 1`] = `
461461
]
462462
`;
463463
464+
exports[`select (isCI = false) > showInstructions: false hides instruction footer 1`] = `
465+
[
466+
"<cursor.hide>",
467+
"│
468+
◆ foo
469+
│ ● opt0
470+
│ ○ opt1
471+
└
472+
",
473+
"<cursor.backward count=999><cursor.up count=5>",
474+
"<cursor.down count=1>",
475+
"<erase.down>",
476+
"◇ foo
477+
│ opt0",
478+
"
479+
",
480+
"<cursor.show>",
481+
]
482+
`;
483+
464484
exports[`select (isCI = false) > up arrow selects previous option 1`] = `
465485
[
466486
"<cursor.hide>",
@@ -1061,6 +1081,26 @@ exports[`select (isCI = true) > renders options and message 1`] = `
10611081
]
10621082
`;
10631083
1084+
exports[`select (isCI = true) > showInstructions: false hides instruction footer 1`] = `
1085+
[
1086+
"<cursor.hide>",
1087+
"│
1088+
◆ foo
1089+
│ ● opt0
1090+
│ ○ opt1
1091+
└
1092+
",
1093+
"<cursor.backward count=999><cursor.up count=5>",
1094+
"<cursor.down count=1>",
1095+
"<erase.down>",
1096+
"◇ foo
1097+
│ opt0",
1098+
"
1099+
",
1100+
"<cursor.show>",
1101+
]
1102+
`;
1103+
10641104
exports[`select (isCI = true) > up arrow selects previous option 1`] = `
10651105
[
10661106
"<cursor.hide>",

packages/prompts/test/group-multi-select.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,4 +481,22 @@ describe.each(['true', 'false'])('groupMultiselect (isCI = %s)', (isCI) => {
481481
expect(value).toEqual(['group1value0']);
482482
expect(output.buffer).toMatchSnapshot();
483483
});
484+
485+
test('showInstructions: false hides instruction footer', async () => {
486+
const result = prompts.groupMultiselect({
487+
message: 'foo',
488+
input,
489+
output,
490+
showInstructions: false,
491+
required: false,
492+
options: {
493+
group1: [{ value: 'group1value0' }, { value: 'group1value1' }],
494+
},
495+
});
496+
497+
input.emit('keypress', '', { name: 'return' });
498+
499+
await result;
500+
expect(output.buffer).toMatchSnapshot();
501+
});
484502
});

packages/prompts/test/multi-select.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,4 +476,20 @@ describe.each(['true', 'false'])('multiselect (isCI = %s)', (isCI) => {
476476
expect(value).toEqual(['opt6']);
477477
expect(output.buffer).toMatchSnapshot();
478478
});
479+
480+
test('showInstructions: false hides instruction footer', async () => {
481+
const result = prompts.multiselect({
482+
message: 'foo',
483+
options: [{ value: 'opt0' }, { value: 'opt1' }],
484+
showInstructions: false,
485+
required: false,
486+
input,
487+
output,
488+
});
489+
490+
input.emit('keypress', '', { name: 'return' });
491+
492+
await result;
493+
expect(output.buffer).toMatchSnapshot();
494+
});
479495
});

packages/prompts/test/select.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,21 @@ describe.each(['true', 'false'])('select (isCI = %s)', (isCI) => {
377377
expect(output.buffer).toMatchSnapshot();
378378
});
379379

380+
test('showInstructions: false hides instruction footer', async () => {
381+
const result = prompts.select({
382+
message: 'foo',
383+
options: [{ value: 'opt0' }, { value: 'opt1' }],
384+
showInstructions: false,
385+
input,
386+
output,
387+
});
388+
389+
input.emit('keypress', '', { name: 'return' });
390+
391+
await result;
392+
expect(output.buffer).toMatchSnapshot();
393+
});
394+
380395
test('correctly limits options with explicit multiline message', async () => {
381396
output.rows = 12;
382397

0 commit comments

Comments
 (0)