Skip to content

Commit 01f8ae1

Browse files
committed
feat: add usage for prompt
Add (optional) `usage` to `prompt` options in the `@clack/core`. Add usage support for `multiselect` and `groupMultiselect` in the `@clack/prompts`.
1 parent 4d50be6 commit 01f8ae1

File tree

4 files changed

+52
-16
lines changed

4 files changed

+52
-16
lines changed

.changeset/wicked-pears-begin.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@clack/prompts": minor
3+
"@clack/core": minor
4+
---
5+
6+
Add (optional) `usage` to `prompt` options in the `@clack/core`.
7+
Add usage support for `multiselect` and `groupMultiselect` in the `@clack/prompts`.

packages/core/src/prompts/prompt.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616

1717
export interface PromptOptions<TValue, Self extends Prompt<TValue>> {
1818
render(this: Omit<Self, 'prompt'>): string | undefined;
19+
usage?: boolean | string;
1920
initialValue?: any;
2021
initialUserInput?: string;
2122
validate?: ((value: TValue | undefined) => string | Error | undefined) | undefined;

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { Option } from './select.js';
1414
export interface GroupMultiSelectOptions<Value> extends CommonOptions {
1515
message: string;
1616
options: Record<string, Option<Value>[]>;
17+
usage?: boolean | string;
1718
initialValues?: Value[];
1819
required?: boolean;
1920
cursorAt?: Value;
@@ -77,6 +78,18 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
7778
const unselectedCheckbox = isItem || selectableGroups ? color.dim(S_CHECKBOX_INACTIVE) : '';
7879
return `${spacingPrefix}${color.dim(prefix)}${unselectedCheckbox} ${color.dim(label)}`;
7980
};
81+
82+
const defaultUsageMessage = `${color.reset(
83+
color.dim(
84+
`Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray(
85+
color.bgWhite(color.inverse(' enter '))
86+
)} to submit`
87+
)
88+
)}`;
89+
90+
const usage = opts.usage ?? false;
91+
const usageMessage = typeof usage === 'string' ? usage : defaultUsageMessage;
92+
8093
const required = opts.required ?? true;
8194

8295
return new GroupMultiSelectPrompt({
@@ -90,13 +103,7 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
90103
selectableGroups,
91104
validate(selected: Value[] | undefined) {
92105
if (required && (selected === undefined || selected.length === 0))
93-
return `Please select at least one option.\n${color.reset(
94-
color.dim(
95-
`Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray(
96-
color.bgWhite(color.inverse(' enter '))
97-
)} to submit`
98-
)
99-
)}`;
106+
return `Please select at least one option.\n${defaultUsageMessage}`;
100107
},
101108
render() {
102109
const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
@@ -180,7 +187,14 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
180187
})
181188
.join(`\n${color.cyan(S_BAR)}`);
182189
const optionsPrefix = optionsText.startsWith('\n') ? '' : ' ';
183-
return `${title}${color.cyan(S_BAR)}${optionsPrefix}${optionsText}\n${color.cyan(S_BAR_END)}\n`;
190+
const footer =
191+
this.state === 'initial' && usage
192+
? usageMessage
193+
.split('\n')
194+
.map((ln, i) => (i === 0 ? `${color.cyan(S_BAR_END)} ${ln}` : ` ${ln}`))
195+
.join('\n')
196+
: color.cyan(S_BAR_END);
197+
return `${title}${color.cyan(S_BAR)}${optionsPrefix}${optionsText}\n${footer}\n`;
184198
}
185199
}
186200
},

packages/prompts/src/multi-select.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type { Option } from './select.js';
1616
export interface MultiSelectOptions<Value> extends CommonOptions {
1717
message: string;
1818
options: Option<Value>[];
19+
usage?: boolean | string;
1920
initialValues?: Value[];
2021
maxItems?: number;
2122
required?: boolean;
@@ -69,6 +70,18 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
6970
}
7071
return `${color.dim(S_CHECKBOX_INACTIVE)} ${computeLabel(label, color.dim)}`;
7172
};
73+
74+
const defaultUsageMessage = `${color.reset(
75+
color.dim(
76+
`Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray(
77+
color.bgWhite(color.inverse(' enter '))
78+
)} to submit`
79+
)
80+
)}`;
81+
82+
const usage = opts.usage ?? false;
83+
const usageMessage = typeof usage === 'string' ? usage : defaultUsageMessage;
84+
7285
const required = opts.required ?? true;
7386

7487
return new MultiSelectPrompt({
@@ -81,13 +94,7 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
8194
cursorAt: opts.cursorAt,
8295
validate(selected: Value[] | undefined) {
8396
if (required && (selected === undefined || selected.length === 0))
84-
return `Please select at least one option.\n${color.reset(
85-
color.dim(
86-
`Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray(
87-
color.bgWhite(color.inverse(' enter '))
88-
)} to submit`
89-
)
90-
)}`;
97+
return `Please select at least one option.\n${defaultUsageMessage}`;
9198
},
9299
render() {
93100
const wrappedMessage = wrapTextWithPrefix(
@@ -161,6 +168,13 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
161168
}
162169
default: {
163170
const prefix = `${color.cyan(S_BAR)} `;
171+
const footer =
172+
this.state === 'initial' && usage
173+
? usageMessage
174+
.split('\n')
175+
.map((ln, i) => (i === 0 ? `${color.cyan(S_BAR_END)} ${ln}` : ` ${ln}`))
176+
.join('\n')
177+
: color.cyan(S_BAR_END);
164178
// Calculate rowPadding: title lines + footer lines (S_BAR_END + trailing newline)
165179
const titleLineCount = title.split('\n').length;
166180
const footerLineCount = 2; // S_BAR_END + trailing newline
@@ -172,7 +186,7 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
172186
columnPadding: prefix.length,
173187
rowPadding: titleLineCount + footerLineCount,
174188
style: styleOption,
175-
}).join(`\n${prefix}`)}\n${color.cyan(S_BAR_END)}\n`;
189+
}).join(`\n${prefix}`)}\n${footer}\n`;
176190
}
177191
}
178192
},

0 commit comments

Comments
 (0)