import { useState, useRef, useEffect, useCallback, useMemo } from 'react'; import { useNavigate } from '@tanstack/react-router'; import type { LucideIcon } from 'lucide-react'; import { Search, Music, Disc3, Users, Tag, Zap, Compass, Settings, ShieldAlert, Terminal, Home, ArrowRight, } from 'lucide-react'; interface CommandItem { id: string; label: string; description?: string; icon: LucideIcon; action: () => void; keywords?: string[]; } const NAV_COMMANDS: CommandItem[] = [ { id: 'nav-home', label: 'Home', icon: Home, action: () => {}, keywords: ['dashboard', 'start'] }, { id: 'nav-tracks', label: 'Songs', description: 'Browse all tracks', icon: Music, action: () => {}, keywords: ['tracks', 'music', 'songs'] }, { id: 'nav-albums', label: 'Albums', icon: Disc3, action: () => {}, keywords: ['albums', 'records'] }, { id: 'nav-artists', label: 'Artists', icon: Users, action: () => {}, keywords: ['artists', 'bands'] }, { id: 'nav-genres', label: 'Genres', icon: Tag, action: () => {}, keywords: ['genres', 'tags', 'categories'] }, { id: 'nav-vibe', label: 'Vibe', description: 'Endless recommendations', icon: Zap, action: () => {}, keywords: ['vibe', 'recommendations', 'radio'] }, { id: 'nav-discover', label: 'Discover', description: 'Browse by genre', icon: Compass, action: () => {}, keywords: ['discover', 'explore'] }, { id: 'nav-quarantine', label: 'Quarantine', icon: ShieldAlert, action: () => {}, keywords: ['quarantine', 'disliked', 'trash'] }, { id: 'nav-jobs', label: 'Jobs', description: 'Background tasks', icon: Terminal, action: () => {}, keywords: ['jobs', 'tasks', 'queue'] }, { id: 'nav-settings', label: 'Settings', icon: Settings, action: () => {}, keywords: ['settings', 'preferences', 'config'] }, ]; interface CommandPaletteProps { open: boolean; onClose: () => void; } export function CommandPalette({ open, onClose }: CommandPaletteProps) { const navigate = useNavigate(); const inputRef = useRef(null); const listRef = useRef(null); const [query, setQuery] = useState(''); const [selectedIndex, setSelectedIndex] = useState(0); // Bind navigation to each command action const commands = useMemo( () => NAV_COMMANDS.map((cmd) => ({ ...cmd, action: () => { const pathMap: Record = { 'nav-home': '/', 'nav-tracks': '/tracks', 'nav-albums': '/albums', 'nav-artists': '/artists', 'nav-genres': '/genres', 'nav-vibe': '/vibe', 'nav-discover': '/discover', 'nav-quarantine': '/quarantine', 'nav-jobs': '/jobs', 'nav-settings': '/settings', }; const path = pathMap[cmd.id] ?? '/'; void navigate({ to: path as any }); onClose(); }, })), [navigate, onClose], ); const filtered = useMemo(() => { const q = query.toLowerCase().trim(); if (!q) return commands; return commands.filter( (cmd) => cmd.label.toLowerCase().includes(q) || cmd.keywords?.some((kw) => kw.includes(q)) || cmd.description?.toLowerCase().includes(q), ); }, [query, commands]); // Reset search when opened useEffect(() => { if (open) { setQuery(''); setSelectedIndex(0); setTimeout(() => inputRef.current?.focus(), 50); } }, [open]); // Scroll selected item into view useEffect(() => { if (!listRef.current) return; const el = listRef.current.children[selectedIndex] as HTMLElement | undefined; el?.scrollIntoView({ block: 'nearest' }); }, [selectedIndex]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { switch (e.key) { case 'ArrowDown': e.preventDefault(); setSelectedIndex((i) => Math.min(i + 1, filtered.length - 1)); break; case 'ArrowUp': e.preventDefault(); setSelectedIndex((i) => Math.max(i - 1, 0)); break; case 'Enter': e.preventDefault(); if (filtered[selectedIndex]) { filtered[selectedIndex].action(); } break; case 'Escape': e.preventDefault(); onClose(); break; } }, [filtered, selectedIndex, onClose], ); if (!open) return null; return ( <> {/* Backdrop */}