Skip to content

Commit

Permalink
Feat(@inquirer/checkbox): Add support for choice description (like se…
Browse files Browse the repository at this point in the history
…lect and search prompts)

Fix #1512
  • Loading branch information
SBoudrias committed Sep 1, 2024
1 parent 5f695f3 commit e17b0c7
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 1 deletion.
3 changes: 3 additions & 0 deletions packages/checkbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ The `Choice` object is typed as
type Choice<Value> = {
value: Value;
name?: string;
description?: string;
short?: string;
checked?: boolean;
disabled?: boolean | string;
Expand All @@ -103,6 +104,7 @@ Here's each property:

- `value`: The value is what will be returned by `await checkbox()`.
- `name`: This is the string displayed in the choice list.
- `description`: Option for a longer description string that'll appear under the list when the cursor highlight a given choice.
- `short`: Once the prompt is done (press enter), we'll use `short` if defined to render next to the question. By default we'll use `name`.
- `checked`: If `true`, the option will be checked by default.
- `disabled`: Disallow the option from being selected. If `disabled` is a string, it'll be used as a help tip explaining why the choice isn't available.
Expand Down Expand Up @@ -131,6 +133,7 @@ type Theme = {
highlight: (text: string) => string;
key: (text: string) => string;
disabledChoice: (text: string) => string;
description: (text: string) => string;
renderSelectedChoices: <T>(
selectedChoices: ReadonlyArray<Choice<T>>,
allChoices: ReadonlyArray<Choice<T> | Separator>,
Expand Down
44 changes: 44 additions & 0 deletions packages/checkbox/checkbox.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,50 @@ describe('checkbox prompt', () => {
await expect(answer).resolves.toEqual([1]);
});

it('shows description of the highlighted choice', async () => {
const choices = [
{ value: 'Stark', description: 'Winter is coming' },
{ value: 'Lannister', description: 'Hear me roar' },
{ value: 'Targaryen', description: 'Fire and blood' },
];

const { answer, events, getScreen } = await render(checkbox, {
message: 'Select a family',
choices: choices,
});

expect(getScreen()).toMatchInlineSnapshot(`
"? Select a family (Press <space> to select, <a> to toggle all, <i> to invert
selection, and <enter> to proceed)
❯◯ Stark
◯ Lannister
◯ Targaryen
Winter is coming"
`);

events.keypress('down');
expect(getScreen()).toMatchInlineSnapshot(`
"? Select a family (Press <space> to select, <a> to toggle all, <i> to invert
selection, and <enter> to proceed)
◯ Stark
❯◯ Lannister
◯ Targaryen
Hear me roar"
`);

events.keypress('space');
expect(getScreen()).toMatchInlineSnapshot(`
"? Select a family
◯ Stark
❯◉ Lannister
◯ Targaryen
Hear me roar"
`);

events.keypress('enter');
await expect(answer).resolves.toEqual(['Lannister']);
});

it('uses custom validation', async () => {
const { answer, events, getScreen } = await render(checkbox, {
message: 'Select a number',
Expand Down
16 changes: 15 additions & 1 deletion packages/checkbox/src/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type CheckboxTheme = {
selectedChoices: ReadonlyArray<NormalizedChoice<T>>,
allChoices: ReadonlyArray<NormalizedChoice<T> | Separator>,
) => string;
description: (text: string) => string;
};
helpMode: 'always' | 'never' | 'auto';
};
Expand All @@ -47,13 +48,15 @@ const checkboxTheme: CheckboxTheme = {
disabledChoice: (text: string) => colors.dim(`- ${text}`),
renderSelectedChoices: (selectedChoices) =>
selectedChoices.map((choice) => choice.short).join(', '),
description: (text: string) => colors.cyan(text),
},
helpMode: 'auto',
};

type Choice<Value> = {
value: Value;
name?: string;
description?: string;
short?: string;
disabled?: boolean | string;
checked?: boolean;
Expand All @@ -63,6 +66,7 @@ type Choice<Value> = {
type NormalizedChoice<Value> = {
value: Value;
name: string;
description?: string;
short: string;
disabled: boolean | string;
checked: boolean;
Expand Down Expand Up @@ -130,6 +134,7 @@ function normalizeChoices<Value>(
value: choice.value,
name,
short: choice.short ?? name,
description: choice.description,
disabled: choice.disabled ?? false,
checked: choice.checked ?? false,
};
Expand Down Expand Up @@ -217,6 +222,7 @@ export default createPrompt(

const message = theme.style.message(config.message);

let description;
const page = usePagination({
items,
active,
Expand All @@ -231,6 +237,10 @@ export default createPrompt(
return theme.style.disabledChoice(`${item.name} ${disabledLabel}`);
}

if (isActive) {
description = item.description;
}

const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
const color = isActive ? theme.style.highlight : (x: string) => x;
const cursor = isActive ? theme.icon.cursor : ' ';
Expand Down Expand Up @@ -279,12 +289,16 @@ export default createPrompt(
}
}

const choiceDescription = description
? `\n${theme.style.description(description)}`
: ``;

let error = '';
if (errorMsg) {
error = `\n${theme.style.error(errorMsg)}`;
}

return `${prefix} ${message}${helpTipTop}\n${page}${helpTipBottom}${error}${ansiEscapes.cursorHide}`;
return `${prefix} ${message}${helpTipTop}\n${page}${helpTipBottom}${choiceDescription}${error}${ansiEscapes.cursorHide}`;
},
);

Expand Down

0 comments on commit e17b0c7

Please sign in to comment.