CommandPalette.tsx
1 import React, { useState, useEffect, KeyboardEvent } from 'react'; 2 import { useApp } from '../contexts/AppContext'; 3 import { mockCommands } from '../data/mockData'; 4 import type { Command } from '../types'; 5 import './CommandPalette.css'; 6 7 export const CommandPalette: React.FC = () => { 8 const { commandPaletteVisible, hideCommandPalette, executeCommand } = useApp(); 9 const [query, setQuery] = useState(''); 10 const [filteredCommands, setFilteredCommands] = useState<Command[]>(mockCommands); 11 const [selectedIndex, setSelectedIndex] = useState(0); 12 13 useEffect(() => { 14 if (!commandPaletteVisible) { 15 setQuery(''); 16 setSelectedIndex(0); 17 } 18 }, [commandPaletteVisible]); 19 20 useEffect(() => { 21 const filtered = mockCommands.filter(cmd => 22 query === '' || 23 cmd.name.toLowerCase().includes(query.toLowerCase()) || 24 cmd.description.toLowerCase().includes(query.toLowerCase()) || 25 cmd.id.toLowerCase().includes(query.toLowerCase()) 26 ); 27 setFilteredCommands(filtered); 28 setSelectedIndex(0); 29 }, [query]); 30 31 const handleKeyDown = (e: KeyboardEvent) => { 32 switch (e.key) { 33 case 'Escape': 34 hideCommandPalette(); 35 break; 36 case 'ArrowUp': 37 e.preventDefault(); 38 setSelectedIndex(prev => Math.max(0, prev - 1)); 39 break; 40 case 'ArrowDown': 41 e.preventDefault(); 42 setSelectedIndex(prev => Math.min(filteredCommands.length - 1, prev + 1)); 43 break; 44 case 'Enter': 45 e.preventDefault(); 46 if (filteredCommands[selectedIndex]) { 47 executeCommand(filteredCommands[selectedIndex]); 48 hideCommandPalette(); 49 } 50 break; 51 } 52 }; 53 54 const handleCommandClick = (cmd: Command) => { 55 executeCommand(cmd); 56 hideCommandPalette(); 57 }; 58 59 if (!commandPaletteVisible) return null; 60 61 const visibleCommands = filteredCommands.slice(0, 12); 62 63 return ( 64 <div className="command-palette-overlay" onClick={hideCommandPalette}> 65 <div className="command-palette" onClick={(e) => e.stopPropagation()}> 66 <div className="palette-header"> 67 🔥 KAMAJI'S FURNACE COMMANDS 🔥 68 </div> 69 70 <div className="palette-search"> 71 <span className="search-icon">⚙</span> 72 <input 73 type="text" 74 value={query} 75 onChange={(e) => setQuery(e.target.value)} 76 onKeyDown={handleKeyDown} 77 placeholder="Type to search..." 78 className="search-input" 79 autoFocus 80 /> 81 </div> 82 83 <div className="palette-commands"> 84 {visibleCommands.map((cmd, idx) => ( 85 <div 86 key={cmd.id} 87 className={`command-item ${idx === selectedIndex ? 'selected' : ''}`} 88 onClick={() => handleCommandClick(cmd)} 89 > 90 <span className="command-prefix"> 91 {idx === selectedIndex ? '▶' : ' '} 92 </span> 93 <span className="command-name">{cmd.name}</span> 94 <span className="command-description">{cmd.description}</span> 95 </div> 96 ))} 97 98 {filteredCommands.length === 0 && ( 99 <div className="no-results">No commands found</div> 100 )} 101 </div> 102 103 <div className="palette-footer"> 104 ↑↓ Navigate • ENTER Execute • ESC Exit 105 </div> 106 </div> 107 </div> 108 ); 109 };