Advanced selection functionality including grouped multi-select options with hierarchical organization.
Multiple selection from grouped options with hierarchical organization and group-level selection support.
/**
* Prompt for selecting multiple options from grouped lists
* @template T - Type of option objects, must have a 'value' property
*/
class GroupMultiSelectPrompt<T extends { value: any }> extends Prompt<T['value'][]> {
constructor(opts: GroupMultiSelectOptions<T>);
/** Array of available options with group information */
options: (T & { group: string | boolean })[];
/** Current cursor position (index of highlighted option) */
cursor: number;
/** Get all items belonging to a specific group */
getGroupItems(group: string): T[];
/** Check if all items in a group are selected */
isGroupSelected(group: string): boolean;
}
/**
* Configuration options for GroupMultiSelectPrompt
* @template T - Type of option objects
*/
interface GroupMultiSelectOptions<T> extends PromptOptions<GroupMultiSelectPrompt<T>> {
/** Object mapping group names to arrays of options */
options: Record<string, T[]>;
/** Initial values to select (array of option.value) */
initialValues?: T['value'][];
/** Whether at least one selection is required */
required?: boolean;
/** Initial cursor position (option.value to start at) */
cursorAt?: T['value'];
/** Whether group headers can be selected to toggle all group items */
selectableGroups?: boolean;
}Usage Examples:
import { GroupMultiSelectPrompt, isCancel } from '@clack/core';
// Basic grouped multi-selection
const componentsPrompt = new GroupMultiSelectPrompt({
render() {
const selectedCount = this.value?.length || 0;
let output = `Select components to install (${selectedCount} selected):\n`;
const groups = [...new Set(this.options.map(opt => opt.group))];
for (const group of groups) {
if (typeof group === 'string') {
const groupItems = this.getGroupItems(group);
const groupSelected = this.isGroupSelected(group);
output += `\n${group}:\n`;
groupItems.forEach((option, i) => {
const globalIndex = this.options.findIndex(opt => opt.value === option.value);
const isSelected = globalIndex === this.cursor;
const isChecked = this.value?.includes(option.value);
const cursor = isSelected ? '→' : ' ';
const checkbox = isChecked ? '☑' : '☐';
output += `${cursor} ${checkbox} ${option.label}\n`;
});
}
}
return output;
},
options: {
'UI Components': [
{ value: 'button', label: 'Button component' },
{ value: 'input', label: 'Input component' },
{ value: 'modal', label: 'Modal component' }
],
'Layout Components': [
{ value: 'grid', label: 'Grid system' },
{ value: 'flex', label: 'Flexbox utilities' },
{ value: 'container', label: 'Container component' }
],
'Data Components': [
{ value: 'table', label: 'Data table' },
{ value: 'chart', label: 'Chart components' },
{ value: 'form', label: 'Form components' }
]
}
});
const selectedComponents = await componentsPrompt.prompt();
if (isCancel(selectedComponents)) {
process.exit(0);
}
// Grouped selection with selectable groups
const permissionsPrompt = new GroupMultiSelectPrompt({
render() {
let output = 'Configure permissions:\n';
const groups = [...new Set(this.options.map(opt => opt.group))].filter(g => typeof g === 'string');
for (const group of groups) {
const groupItems = this.getGroupItems(group);
const groupSelected = this.isGroupSelected(group);
const groupIndex = this.options.findIndex(opt => opt.group === group && opt.value === `group:${group}`);
const isGroupCursor = groupIndex === this.cursor;
const groupCursor = isGroupCursor ? '→' : ' ';
const groupCheckbox = groupSelected ? '☑' : '☐';
output += `\n${groupCursor} ${groupCheckbox} ${group} (all)\n`;
groupItems.forEach((option) => {
if (!option.value.startsWith('group:')) {
const globalIndex = this.options.findIndex(opt => opt.value === option.value);
const isSelected = globalIndex === this.cursor;
const isChecked = this.value?.includes(option.value);
const cursor = isSelected ? '→' : ' ';
const checkbox = isChecked ? '☑' : '☐';
output += `${cursor} ${checkbox} ${option.label}\n`;
}
});
}
return output;
},
options: {
'User Management': [
{ value: 'user:create', label: 'Create users' },
{ value: 'user:read', label: 'View users' },
{ value: 'user:update', label: 'Edit users' },
{ value: 'user:delete', label: 'Delete users' }
],
'Content Management': [
{ value: 'content:create', label: 'Create content' },
{ value: 'content:read', label: 'View content' },
{ value: 'content:update', label: 'Edit content' },
{ value: 'content:delete', label: 'Delete content' }
],
'System Settings': [
{ value: 'system:config', label: 'Modify settings' },
{ value: 'system:backup', label: 'Create backups' },
{ value: 'system:logs', label: 'View system logs' }
]
},
selectableGroups: true,
required: true
});
const permissions = await permissionsPrompt.prompt();
// Advanced grouped selection with initial values
const featuresPrompt = new GroupMultiSelectPrompt({
render() {
const selectedCount = this.value?.length || 0;
let output = `Project features (${selectedCount} selected):\n`;
const groups = [...new Set(this.options.map(opt => opt.group))];
for (const group of groups) {
if (typeof group === 'string') {
const groupItems = this.getGroupItems(group);
output += `\n📁 ${group}:\n`;
groupItems.forEach((option) => {
const globalIndex = this.options.findIndex(opt => opt.value === option.value);
const isSelected = globalIndex === this.cursor;
const isChecked = this.value?.includes(option.value);
const cursor = isSelected ? '▶' : ' ';
const checkbox = isChecked ? '✅' : '⬜';
output += `${cursor} ${checkbox} ${option.label} - ${option.description}\n`;
});
}
}
return output;
},
options: {
'Frontend': [
{ value: 'react', label: 'React', description: 'React.js framework' },
{ value: 'vue', label: 'Vue', description: 'Vue.js framework' },
{ value: 'tailwind', label: 'Tailwind CSS', description: 'Utility-first CSS' }
],
'Backend': [
{ value: 'express', label: 'Express', description: 'Node.js web framework' },
{ value: 'prisma', label: 'Prisma', description: 'Database ORM' },
{ value: 'auth', label: 'Authentication', description: 'User auth system' }
],
'DevOps': [
{ value: 'docker', label: 'Docker', description: 'Containerization' },
{ value: 'ci', label: 'CI/CD', description: 'Continuous integration' },
{ value: 'monitoring', label: 'Monitoring', description: 'App monitoring' }
]
},
initialValues: ['react', 'express'],
cursorAt: 'react'
});
const projectFeatures = await featuresPrompt.prompt();