CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-clack--core

Low-level primitives for building command-line interface applications with customizable prompt components.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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

docs

advanced-selection.md

base-prompt.md

confirmation.md

index.md

selection.md

text-input.md

utilities.md

tile.json