or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-selection.mdbase-prompt.mdconfirmation.mdindex.mdselection.mdtext-input.mdutilities.md
tile.json

advanced-selection.mddocs/

Advanced Selection

Advanced selection functionality including grouped multi-select options with hierarchical organization.

Capabilities

Group Multi-Select Prompt

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();