'use client'; import { useEffect, useState } from 'react'; import { Archive, AtSign, BookOpen, Calculator, Calendar, Code, Command as CommandIcon, Copy, CreditCard, Database, Download, Edit, File, Folder, Mail, MessageSquare, Phone, Plus, Search, Settings, Share, Smile, Star, Trash, Upload, User, } from 'lucide-react'; import { Avatar, AvatarFallback } from '@kit/ui/avatar'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@kit/ui/card'; import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, } from '@kit/ui/command'; import { Input } from '@kit/ui/input'; import { Label } from '@kit/ui/label'; import { Switch } from '@kit/ui/switch'; import { cn } from '@kit/ui/utils'; import { generateImportStatement, generatePropsString, } from '../lib/story-utils'; import { ComponentStoryLayout } from './story-layout'; interface CommandControlsProps { placeholder: string; emptyMessage: string; loop: boolean; shouldFilter: boolean; className: string; onPlaceholderChange: (placeholder: string) => void; onEmptyMessageChange: (message: string) => void; onLoopChange: (loop: boolean) => void; onShouldFilterChange: (shouldFilter: boolean) => void; onClassNameChange: (className: string) => void; } const examples = [ { title: 'Command Palette Dialog', description: 'Full-screen command palette triggered by keyboard shortcut, commonly used in editors and productivity apps', component: () => { const [open, setOpen] = useState(false); useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen((open) => !open); } }; document.addEventListener('keydown', down); return () => document.removeEventListener('keydown', down); }, []); return ( Command Palette Press{' '} K {' '} to open the command palette No results found. setOpen(false)}> New File ⌘N setOpen(false)}> New Folder ⇧⌘N setOpen(false)}> Search Files ⌘⇧F setOpen(false)}> components/ui/button.tsx setOpen(false)}> lib/utils.ts setOpen(false)}> app/page.tsx setOpen(false)}> Profile ⌘P setOpen(false)}> Settings ⌘, ); }, }, { title: 'File & Folder Browser', description: 'Navigate and search through files and folders with live filtering', component: () => { const [selectedItem, setSelectedItem] = useState(null); const items = [ { type: 'folder', name: 'src', icon: Folder }, { type: 'folder', name: 'components', icon: Folder }, { type: 'folder', name: 'lib', icon: Folder }, { type: 'file', name: 'package.json', icon: Code }, { type: 'file', name: 'README.md', icon: BookOpen }, { type: 'file', name: 'button.tsx', icon: Code }, { type: 'file', name: 'utils.ts', icon: Code }, { type: 'file', name: 'globals.css', icon: Code }, { type: 'file', name: 'config.json', icon: Settings }, { type: 'file', name: 'database.db', icon: Database }, ]; return ( File Explorer Search and navigate through your project files No files found. {items.map((item) => { const IconComponent = item.icon; return ( setSelectedItem(item.name)} className="cursor-pointer" > {item.name} {item.type} ); })} {selectedItem && (

Selected: {selectedItem}

)}
); }, }, { title: 'Team Member Search', description: 'Search and select team members with avatars and role information', component: () => { const [selectedMember, setSelectedMember] = useState(null); const teamMembers = [ { id: '1', name: 'Alice Johnson', role: 'Product Manager', email: 'alice@company.com', avatar: 'AJ', status: 'online', }, { id: '2', name: 'Bob Smith', role: 'Developer', email: 'bob@company.com', avatar: 'BS', status: 'away', }, { id: '3', name: 'Carol Davis', role: 'Designer', email: 'carol@company.com', avatar: 'CD', status: 'online', }, { id: '4', name: 'David Wilson', role: 'QA Engineer', email: 'david@company.com', avatar: 'DW', status: 'offline', }, { id: '5', name: 'Eva Martinez', role: 'DevOps', email: 'eva@company.com', avatar: 'EM', status: 'online', }, { id: '6', name: 'Frank Chen', role: 'Data Analyst', email: 'frank@company.com', avatar: 'FC', status: 'away', }, ]; const getStatusColor = (status: string) => { switch (status) { case 'online': return 'bg-green-500'; case 'away': return 'bg-yellow-500'; case 'offline': return 'bg-gray-400'; default: return 'bg-gray-400'; } }; return ( Find Team Members Search by name, role, or email address No team members found. {teamMembers.map((member) => ( setSelectedMember(member.name)} className="cursor-pointer" >
{member.avatar}

{member.name}

{member.role}

{member.status}
))} {selectedMember && (

Selected member: {selectedMember}

)} ); }, }, { title: 'Action Menu with Categories', description: 'Organized command menu with different action categories and keyboard shortcuts', component: () => { const [lastAction, setLastAction] = useState(null); return ( Quick Actions Organize and execute commands across different categories No actions found. setLastAction('New Document')}> New Document ⌘N setLastAction('Upload File')}> Upload File ⌘U setLastAction('Download')}> Download ⌘D setLastAction('Copy')}> Copy ⌘C setLastAction('Edit')}> Edit ⌘E setLastAction('Archive')}> Archive ⌘A setLastAction('Send Message')}> Send Message ⌘M setLastAction('Send Email')}> Send Email ⌘⇧M setLastAction('Share')}> Share ⌘S setLastAction('Add to Favorites')} > Add to Favorites ⌘⇧S setLastAction('Delete')}> Delete ⌘⌫ {lastAction && (

Action executed: {lastAction}

)}
); }, }, { title: 'Contact Picker with Search', description: 'Search through contacts with additional metadata and quick actions', component: () => { const [selectedContact, setSelectedContact] = useState( null, ); const contacts = [ { id: '1', name: 'Alice Johnson', phone: '+1 (555) 123-4567', email: 'alice@example.com', company: 'Tech Corp', }, { id: '2', name: 'Bob Smith', phone: '+1 (555) 234-5678', email: 'bob@example.com', company: 'Design Studio', }, { id: '3', name: 'Carol Davis', phone: '+1 (555) 345-6789', email: 'carol@example.com', company: 'Marketing Inc', }, { id: '4', name: 'David Wilson', phone: '+1 (555) 456-7890', email: 'david@example.com', company: 'Dev Agency', }, { id: '5', name: 'Eva Martinez', phone: '+1 (555) 567-8901', email: 'eva@example.com', company: 'Startup XYZ', }, ]; return ( Contact Search Find contacts by name, company, or contact information No contacts found. {contacts.map((contact) => ( setSelectedContact(contact.name)} className="cursor-pointer" >
{contact.name .split(' ') .map((n) => n[0]) .join('')}

{contact.name}

{contact.phone}
{contact.email}
{contact.company}
))}
Add New Contact ⌘N Import Contacts ⌘I
{selectedContact && (

Selected contact: {selectedContact}

)}
); }, }, ]; const apiReference = { title: 'Command API Reference', description: 'Complete API documentation for the Command component family.', props: [ { name: 'value', type: 'string', description: 'The controlled value of the command menu. Can be used for controlling which command is selected.', }, { name: 'onValueChange', type: '(value: string) => void', description: 'Event handler called when the selected command changes.', }, { name: 'filter', type: '(value: string, search: string) => boolean | number', description: 'Custom filter function. Return a number between 0-1 for score-based filtering.', }, { name: 'shouldFilter', type: 'boolean', default: 'true', description: 'Whether to filter items based on search input.', }, { name: 'loop', type: 'boolean', default: 'false', description: 'Whether to loop through items when navigating with keyboard.', }, { name: 'vimBindings', type: 'boolean', default: 'false', description: 'Enable vim-style keyboard navigation (j/k for up/down).', }, { name: 'defaultValue', type: 'string', description: 'The default selected item when the component mounts.', }, ], examples: [ { title: 'Basic Command Menu', code: `import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '@kit/ui/command'; No results found. Calendar Search Emoji Calculator `, }, { title: 'Command Dialog', code: `import { CommandDialog } from '@kit/ui/command'; const [open, setOpen] = useState(false); No results found. Profile Settings `, }, { title: 'With Icons and Shortcuts', code: ` Profile ⌘P `, }, { title: 'Custom Filtering', code: ` { if (value.includes(search)) return 1; return 0; }} > {/* Command content */} `, }, ], }; const usageGuidelines = { title: 'Command Usage Guidelines', description: 'Best practices for implementing command interfaces that provide excellent user experience.', guidelines: [ { title: 'When to Use Command Components', items: [ 'Building application command palettes (⌘K style interfaces)', 'Creating searchable lists of actions, files, or data', 'Implementing keyboard-driven navigation and selection', 'Building search interfaces with categorized results', 'Creating quick switchers for projects, tabs, or workspaces', ], }, { title: 'Content Organization', items: [ 'Group related commands using CommandGroup with clear headings', 'Place most frequently used commands at the top', 'Use CommandSeparator to visually separate different types of actions', 'Keep command labels concise and action-oriented', 'Provide keyboard shortcuts for power users when appropriate', ], }, { title: 'Search and Filtering', items: [ 'Use clear, contextual placeholder text in search inputs', 'Implement fuzzy search for better user experience', 'Show helpful empty states when no results are found', 'Consider search aliases for common terms', 'Filter results in real-time as users type', ], }, { title: 'Visual Design', items: [ 'Use consistent icons throughout the command interface', 'Apply proper spacing and typography for readability', 'Highlight the selected item clearly with appropriate contrast', 'Show keyboard shortcuts using CommandShortcut component', 'Use badges or labels to provide additional context', ], }, { title: 'Keyboard Navigation', items: [ 'Support arrow keys for navigation up and down', 'Use Enter to select the highlighted item', 'Implement Escape to close command dialogs', 'Consider vim bindings (j/k) for power users', 'Enable loop navigation for seamless keyboard experience', ], }, { title: 'Performance', items: [ 'Implement virtualization for large lists of commands', 'Debounce search input to avoid excessive filtering', 'Use lazy loading for dynamic command lists', 'Cache frequently accessed command results', 'Consider memoization for expensive filter operations', ], }, { title: 'Accessibility', items: [ 'Command components include proper ARIA attributes automatically', 'Ensure sufficient color contrast for all command items', 'Provide clear focus indicators for keyboard navigation', 'Use semantic headings for command groups', 'Announce search results and selection changes to screen readers', ], }, ], }; export default function CommandStory() { const [controls, setControls] = useState({ placeholder: 'Type a command or search...', emptyMessage: 'No results found.', loop: false, shouldFilter: true, className: '', }); const generateCode = () => { const components = [ 'Command', 'CommandInput', 'CommandList', 'CommandEmpty', 'CommandGroup', 'CommandItem', ]; const importStatement = generateImportStatement( components, '@kit/ui/command', ); const iconImport = generateImportStatement( ['User', 'Settings', 'CreditCard'], 'lucide-react', ); const commandProps = generatePropsString( { shouldFilter: !controls.shouldFilter ? false : undefined, loop: controls.loop ? true : undefined, className: controls.className || undefined, }, { shouldFilter: true, loop: false, }, ); const inputProps = generatePropsString({ placeholder: controls.placeholder !== 'Type a command or search...' ? controls.placeholder : undefined, }); const emptyProps = generatePropsString({ children: controls.emptyMessage !== 'No results found.' ? controls.emptyMessage : undefined, }); const commandStructure = `\n \n \n No results found.\n \n \n \n Profile\n \n \n \n Billing\n \n \n \n Settings\n \n \n \n`; return `${importStatement}\n${iconImport}\n\n${commandStructure}`; }; const controlsContent = (
setControls((prev) => ({ ...prev, placeholder: e.target.value })) } placeholder="Enter placeholder text" />
setControls((prev) => ({ ...prev, emptyMessage: e.target.value })) } placeholder="Enter empty message" />
setControls((prev) => ({ ...prev, loop }))} />
setControls((prev) => ({ ...prev, shouldFilter })) } />
setControls((prev) => ({ ...prev, className: e.target.value })) } placeholder="Additional CSS classes" />
); const previewContent = (
{controls.emptyMessage} Profile Billing Settings Create New Search
); return ( {examples.map((example, index) => (

{example.title}

{example.description}

))}
} apiReference={

{apiReference.title}

{apiReference.description}

{apiReference.props.map((prop, index) => ( ))}
Prop Type Default Description
{prop.name} {prop.type} {(prop as any).default || '-'} {prop.description}

Code Examples

{apiReference.examples.map((example, index) => (

{example.title}

                    {example.code}
                  
))}
} usageGuidelines={

{usageGuidelines.title}

{usageGuidelines.description}

{usageGuidelines.guidelines.map((section, index) => (

{section.title}

    {section.items.map((item, itemIndex) => (
  • {item}
  • ))}
))}
} /> ); }