Skip to content
Merged
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions .changeset/happy-parents-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clack/prompts": minor
---

Adds a new `groupSpacing` option to grouped multi-select prompts. If set to an integer greater than 0, it will add that number of new lines between each group.
176 changes: 176 additions & 0 deletions packages/prompts/src/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,94 @@ exports[`prompts (isCI = false) > groupMultiselect > cursorAt sets initial selec
]
`;

exports[`prompts (isCI = false) > groupMultiselect > groupSpacing > negative spacing is ignored 1`] = `
[
"[?25l",
"│
◆ foo
│ ◻ group1
│ └ ◻ group1value0
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"│ ◻ group1
│ └ ◻ group1value0
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"│ ◼ group1
│ └ ◼ group1value0
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"◇ foo
│ group1value0",
"
",
"[?25h",
]
`;

exports[`prompts (isCI = false) > groupMultiselect > groupSpacing > renders spaced groups 1`] = `
[
"[?25l",
"│
◆ foo
│
│
│ ◻ group1
│ └ ◻ group1value0
│
│
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"│ ◻ group1
│ └ ◻ group1value0
│
│
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"│ ◼ group1
│ └ ◼ group1value0
│
│
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"◇ foo
│ group1value0",
"
",
"[?25h",
]
`;

exports[`prompts (isCI = false) > groupMultiselect > initial values can be set 1`] = `
[
"[?25l",
Expand Down Expand Up @@ -2154,6 +2242,94 @@ exports[`prompts (isCI = true) > groupMultiselect > cursorAt sets initial select
]
`;

exports[`prompts (isCI = true) > groupMultiselect > groupSpacing > negative spacing is ignored 1`] = `
[
"[?25l",
"│
◆ foo
│ ◻ group1
│ └ ◻ group1value0
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"│ ◻ group1
│ └ ◻ group1value0
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"│ ◼ group1
│ └ ◼ group1value0
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"◇ foo
│ group1value0",
"
",
"[?25h",
]
`;

exports[`prompts (isCI = true) > groupMultiselect > groupSpacing > renders spaced groups 1`] = `
[
"[?25l",
"│
◆ foo
│
│
│ ◻ group1
│ └ ◻ group1value0
│
│
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"│ ◻ group1
│ └ ◻ group1value0
│
│
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"│ ◼ group1
│ └ ◼ group1value0
│
│
│ ◻ group2
│ └ ◻ group2value0
└
",
"",
"",
"",
"◇ foo
│ group1value0",
"
",
"[?25h",
]
`;

exports[`prompts (isCI = true) > groupMultiselect > initial values can be set 1`] = `
[
"[?25l",
Expand Down
44 changes: 44 additions & 0 deletions packages/prompts/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1204,5 +1204,49 @@ describe.each(['true', 'false'])('prompts (isCI = %s)', (isCI) => {
expect(value).toEqual([value0]);
expect(output.buffer).toMatchSnapshot();
});

describe('groupSpacing', () => {
test('renders spaced groups', async () => {
const result = prompts.groupMultiselect({
message: 'foo',
input,
output,
options: {
group1: [{ value: 'group1value0' }],
group2: [{ value: 'group2value0' }],
},
groupSpacing: 2,
});

input.emit('keypress', '', { name: 'down' });
input.emit('keypress', '', { name: 'space' });
input.emit('keypress', '', { name: 'return' });

await result;

expect(output.buffer).toMatchSnapshot();
});

test('negative spacing is ignored', async () => {
const result = prompts.groupMultiselect({
message: 'foo',
input,
output,
options: {
group1: [{ value: 'group1value0' }],
group2: [{ value: 'group2value0' }],
},
groupSpacing: -2,
});

input.emit('keypress', '', { name: 'down' });
input.emit('keypress', '', { name: 'space' });
input.emit('keypress', '', { name: 'return' });

await result;

expect(output.buffer).toMatchSnapshot();
});
});
});
});
17 changes: 10 additions & 7 deletions packages/prompts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,9 +489,10 @@ export interface GroupMultiSelectOptions<Value> extends CommonOptions {
required?: boolean;
cursorAt?: Value;
selectableGroups?: boolean;
groupSpacing?: number;
}
export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) => {
const { selectableGroups = true } = opts;
const { selectableGroups = true, groupSpacing = 0 } = opts;
const opt = (
option: Option<Value>,
state:
Expand All @@ -510,37 +511,39 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true });
const isLast = isItem && (next as any).group === true;
const prefix = isItem ? (selectableGroups ? `${isLast ? S_BAR_END : S_BAR} ` : ' ') : '';
const spacingPrefix =
groupSpacing > 0 && !isItem ? `\n${color.cyan(S_BAR)} `.repeat(groupSpacing) : '';

if (state === 'active') {
return `${color.dim(prefix)}${color.cyan(S_CHECKBOX_ACTIVE)} ${label} ${
return `${spacingPrefix}${color.dim(prefix)}${color.cyan(S_CHECKBOX_ACTIVE)} ${label} ${
option.hint ? color.dim(`(${option.hint})`) : ''
}`;
}
if (state === 'group-active') {
return `${prefix}${color.cyan(S_CHECKBOX_ACTIVE)} ${color.dim(label)}`;
return `${spacingPrefix}${prefix}${color.cyan(S_CHECKBOX_ACTIVE)} ${color.dim(label)}`;
}
if (state === 'group-active-selected') {
return `${prefix}${color.green(S_CHECKBOX_SELECTED)} ${color.dim(label)}`;
return `${spacingPrefix}${prefix}${color.green(S_CHECKBOX_SELECTED)} ${color.dim(label)}`;
}
if (state === 'selected') {
const selectedCheckbox = isItem || selectableGroups ? color.green(S_CHECKBOX_SELECTED) : '';
return `${color.dim(prefix)}${selectedCheckbox} ${color.dim(label)} ${
return `${spacingPrefix}${color.dim(prefix)}${selectedCheckbox} ${color.dim(label)} ${
option.hint ? color.dim(`(${option.hint})`) : ''
}`;
}
if (state === 'cancelled') {
return `${color.strikethrough(color.dim(label))}`;
}
if (state === 'active-selected') {
return `${color.dim(prefix)}${color.green(S_CHECKBOX_SELECTED)} ${label} ${
return `${spacingPrefix}${color.dim(prefix)}${color.green(S_CHECKBOX_SELECTED)} ${label} ${
option.hint ? color.dim(`(${option.hint})`) : ''
}`;
}
if (state === 'submitted') {
return `${color.dim(label)}`;
}
const unselectedCheckbox = isItem || selectableGroups ? color.dim(S_CHECKBOX_INACTIVE) : '';
return `${color.dim(prefix)}${unselectedCheckbox} ${color.dim(label)}`;
return `${spacingPrefix}${color.dim(prefix)}${unselectedCheckbox} ${color.dim(label)}`;
};

return new GroupMultiSelectPrompt({
Expand Down