'use client'; import { useState, useEffect, useCallback, useRef } from 'react'; import { Command, Hash, Clock, Star, Moon, Sun } from 'lucide-react'; import { useTheme } from '@/components/providers/ThemeProvider'; import { getAllMeasures, formatMeasureName, getCategoryColor, getCategoryColorHex, type Measure, } from '@/lib/units'; import { getHistory, getFavorites } from '@/lib/storage'; import { cn } from '@/lib/utils'; interface CommandPaletteProps { onSelectMeasure: (measure: Measure) => void; onSelectUnit: (unit: string, measure: Measure) => void; } export default function CommandPalette({ onSelectMeasure, onSelectUnit, }: CommandPaletteProps) { const [isOpen, setIsOpen] = useState(false); const [query, setQuery] = useState(''); const [selectedIndex, setSelectedIndex] = useState(0); const { theme, setTheme } = useTheme(); const inputRef = useRef(null); // Commands const commands: Array<{ id: string; label: string; icon: any; action: () => void; keywords: string[]; color?: string; }> = [ { id: 'theme-light', label: 'Switch to Light Mode', icon: Sun, action: () => setTheme('light'), keywords: ['theme', 'light', 'mode'], }, { id: 'theme-dark', label: 'Switch to Dark Mode', icon: Moon, action: () => setTheme('dark'), keywords: ['theme', 'dark', 'mode'], }, { id: 'theme-system', label: 'Use System Theme', icon: Command, action: () => setTheme('system'), keywords: ['theme', 'system', 'auto'], }, ]; // Add measure commands const measures = getAllMeasures(); const measureCommands = measures.map(measure => ({ id: `measure-${measure}`, label: `Convert ${formatMeasureName(measure)}`, icon: Hash, action: () => onSelectMeasure(measure), keywords: ['convert', measure, formatMeasureName(measure).toLowerCase()], color: getCategoryColorHex(measure), })); const allCommands = [...commands, ...measureCommands]; // Filter commands const filteredCommands = query ? allCommands.filter(cmd => cmd.keywords.some(kw => kw.toLowerCase().includes(query.toLowerCase())) || cmd.label.toLowerCase().includes(query.toLowerCase()) ) : allCommands; // Keyboard shortcut to open useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); setIsOpen(prev => !prev); } if (e.key === 'Escape') { setIsOpen(false); } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, []); // Focus input when opened useEffect(() => { if (isOpen) { inputRef.current?.focus(); setQuery(''); setSelectedIndex(0); } }, [isOpen]); // Keyboard navigation useEffect(() => { if (!isOpen) return; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'ArrowDown') { e.preventDefault(); setSelectedIndex(prev => prev < filteredCommands.length - 1 ? prev + 1 : prev ); } else if (e.key === 'ArrowUp') { e.preventDefault(); setSelectedIndex(prev => (prev > 0 ? prev - 1 : prev)); } else if (e.key === 'Enter') { e.preventDefault(); const command = filteredCommands[selectedIndex]; if (command) { command.action(); setIsOpen(false); } } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [isOpen, selectedIndex, filteredCommands]); // Reset selected index when query changes useEffect(() => { setSelectedIndex(0); }, [query]); if (!isOpen) return null; return ( <> {/* Backdrop */}
setIsOpen(false)} /> {/* Command Palette */}
{/* Search Input */}
setQuery(e.target.value)} className="flex-1 bg-transparent py-4 px-4 outline-none placeholder:text-muted-foreground" /> ESC
{/* Commands List */}
{filteredCommands.length === 0 ? (
No commands found
) : ( filteredCommands.map((command, index) => { const Icon = command.icon; return ( ); }) )}
{/* Footer */}
Navigate
Enter Select
ESC Close
); }