Skip to content

feat(@clack/prompts): add help on prompt #241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
34 changes: 23 additions & 11 deletions packages/prompts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const S_STEP_SUBMIT = s('◇', 'o');

const S_BAR_START = s('┌', 'T');
const S_BAR = s('│', '|');
const S_BAR_DETAIL = s('│ⓘ', `|${color.italic('i')}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagging this since I want to play with it locally and come up with a good indicator. Having an icon on the inside margin feels a bit off, but I don't know what will feel better just yet.

Thanks for including your explorations in the PR description!

const S_BAR_END = s('└', '—');

const S_RADIO_ACTIVE = s('●', '>');
Expand Down Expand Up @@ -60,6 +61,11 @@ const symbol = (state: State) => {
}
};

const help = (text: string | undefined, style: (input: string) => string) => {
if (text === undefined) return '';
return `${style(S_BAR_DETAIL)} ${color.dim(color.italic(text))}\n`;
};

interface LimitOptionsParams<TOption> {
options: TOption[];
maxItems: number | undefined;
Expand Down Expand Up @@ -99,6 +105,7 @@ const limitOptions = <TOption>(params: LimitOptionsParams<TOption>): string[] =>

export interface TextOptions {
message: string;
help?: string;
placeholder?: string;
defaultValue?: string;
initialValue?: string;
Expand All @@ -119,7 +126,7 @@ export const text = (opts: TextOptions) => {

switch (this.state) {
case 'error':
return `${title.trim()}\n${color.yellow(S_BAR)} ${value}\n${color.yellow(
return `${title.trim()}\n${help(opts.help, color.yellow)}${color.yellow(S_BAR)} ${value}\n${color.yellow(
S_BAR_END
)} ${color.yellow(this.error)}\n`;
case 'submit':
Expand All @@ -129,14 +136,15 @@ export const text = (opts: TextOptions) => {
color.dim(this.value ?? '')
)}${this.value?.trim() ? `\n${color.gray(S_BAR)}` : ''}`;
default:
return `${title}${color.cyan(S_BAR)} ${value}\n${color.cyan(S_BAR_END)}\n`;
return `${title}${help(opts.help, color.cyan)}${color.cyan(S_BAR)} ${value}\n${color.cyan(S_BAR_END)}\n`;
}
},
}).prompt() as Promise<string | symbol>;
};

export interface PasswordOptions {
message: string;
help?: string;
mask?: string;
validate?: (value: string) => string | Error | undefined;
}
Expand All @@ -151,7 +159,7 @@ export const password = (opts: PasswordOptions) => {

switch (this.state) {
case 'error':
return `${title.trim()}\n${color.yellow(S_BAR)} ${masked}\n${color.yellow(
return `${title.trim()}\n${help(opts.help, color.yellow)}${color.yellow(S_BAR)} ${masked}\n${color.yellow(
S_BAR_END
)} ${color.yellow(this.error)}\n`;
case 'submit':
Expand All @@ -161,14 +169,15 @@ export const password = (opts: PasswordOptions) => {
masked ? `\n${color.gray(S_BAR)}` : ''
}`;
default:
return `${title}${color.cyan(S_BAR)} ${value}\n${color.cyan(S_BAR_END)}\n`;
return `${title}${help(opts.help, color.cyan)}${color.cyan(S_BAR)} ${value}\n${color.cyan(S_BAR_END)}\n`;
}
},
}).prompt() as Promise<string | symbol>;
};

export interface ConfirmOptions {
message: string;
help?: string;
active?: string;
inactive?: string;
initialValue?: boolean;
Expand All @@ -192,7 +201,7 @@ export const confirm = (opts: ConfirmOptions) => {
color.dim(value)
)}\n${color.gray(S_BAR)}`;
default: {
return `${title}${color.cyan(S_BAR)} ${
return `${title}${help(opts.help, color.cyan)}${color.cyan(S_BAR)} ${
this.value
? `${color.green(S_RADIO_ACTIVE)} ${active}`
: `${color.dim(S_RADIO_INACTIVE)} ${color.dim(active)}`
Expand Down Expand Up @@ -249,6 +258,7 @@ export type Option<Value> = Value extends Primitive

export interface SelectOptions<Value> {
message: string;
help?: string;
options: Option<Value>[];
initialValue?: Value;
maxItems?: number;
Expand Down Expand Up @@ -286,7 +296,7 @@ export const select = <Value>(opts: SelectOptions<Value>) => {
'cancelled'
)}\n${color.gray(S_BAR)}`;
default: {
return `${title}${color.cyan(S_BAR)} ${limitOptions({
return `${title}${help(opts.help, color.cyan)}${color.cyan(S_BAR)} ${limitOptions({
cursor: this.cursor,
options: this.options,
maxItems: opts.maxItems,
Expand Down Expand Up @@ -337,7 +347,7 @@ export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
S_BAR
)}`;
default: {
return `${title}${color.cyan(S_BAR)} ${this.options
return `${title}${help(opts.help, color.cyan)}${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`;
}
Expand All @@ -348,6 +358,7 @@ export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {

export interface MultiSelectOptions<Value> {
message: string;
help?: string;
options: Option<Value>[];
initialValues?: Value[];
maxItems?: number;
Expand Down Expand Up @@ -436,15 +447,15 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}`
)
.join('\n');
return `${title + color.yellow(S_BAR)} ${limitOptions({
return `${title + help(opts.help, color.yellow) + color.yellow(S_BAR)} ${limitOptions({
options: this.options,
cursor: this.cursor,
maxItems: opts.maxItems,
style: styleOption,
}).join(`\n${color.yellow(S_BAR)} `)}\n${footer}\n`;
}
default: {
return `${title}${color.cyan(S_BAR)} ${limitOptions({
return `${title}${help(opts.help, color.cyan)}${color.cyan(S_BAR)} ${limitOptions({
options: this.options,
cursor: this.cursor,
maxItems: opts.maxItems,
Expand All @@ -458,6 +469,7 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {

export interface GroupMultiSelectOptions<Value> {
message: string;
help?: string;
options: Record<string, Option<Value>[]>;
initialValues?: Value[];
required?: boolean;
Expand Down Expand Up @@ -552,7 +564,7 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}`
)
.join('\n');
return `${title}${color.yellow(S_BAR)} ${this.options
return `${title}${help(opts.help, color.yellow)}${color.yellow(S_BAR)} ${this.options
.map((option, i, options) => {
const selected =
this.value.includes(option.value) ||
Expand All @@ -576,7 +588,7 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
.join(`\n${color.yellow(S_BAR)} `)}\n${footer}\n`;
}
default: {
return `${title}${color.cyan(S_BAR)} ${this.options
return `${title}${help(opts.help, color.cyan)}${color.cyan(S_BAR)} ${this.options
.map((option, i, options) => {
const selected =
this.value.includes(option.value) ||
Expand Down