'use client' import { useState, useEffect, useCallback, useMemo } from 'react' /** Unified command item for the palette */ export interface CommandItem { id: string icon?: React.ElementType label: string detail?: string shortcut?: string group: string /** Navigate path or action callback */ onSelect: (() => void) | string /** Optional color class for icon container */ colorClass?: string } export interface CommandGroup { label: string items: CommandItem[] } export interface UseCommandPaletteOptions { commands: CommandItem[] onExecute: (command: CommandItem) => void isOpen: boolean } export interface UseCommandPaletteReturn { query: string setQuery: (q: string) => void filteredGroups: CommandGroup[] selectedIndex: number onKeyDown: (e: React.KeyboardEvent) => void } export function useCommandPaletteSearch(opts: UseCommandPaletteOptions): UseCommandPaletteReturn { const { commands, onExecute, isOpen } = opts const [query, setQuery] = useState('') const [selectedIndex, setSelectedIndex] = useState(0) // Filter commands by query and group them const filteredGroups = useMemo(() => { const lowerQuery = query.toLowerCase() const filtered = query ? commands.filter( (cmd) => cmd.label.toLowerCase().includes(lowerQuery) || (cmd.detail && cmd.detail.toLowerCase().includes(lowerQuery)) ) : commands // Group by group label, preserving insertion order const groupMap = new Map() for (const cmd of filtered) { const existing = groupMap.get(cmd.group) if (existing) { existing.push(cmd) } else { groupMap.set(cmd.group, [cmd]) } } const groups: CommandGroup[] = [] for (const [label, items] of groupMap) { groups.push({ label, items }) } return groups }, [commands, query]) // Flat list for keyboard nav const flatItems = useMemo( () => filteredGroups.flatMap((g) => g.items), [filteredGroups] ) // Reset state when palette opens/closes useEffect(() => { if (isOpen) { setQuery('') setSelectedIndex(0) } }, [isOpen]) // Reset selection when filtered results change useEffect(() => { setSelectedIndex(0) }, [filteredGroups]) const onKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === 'ArrowDown') { e.preventDefault() setSelectedIndex((prev) => Math.min(prev + 1, flatItems.length - 1)) } else if (e.key === 'ArrowUp') { e.preventDefault() setSelectedIndex((prev) => Math.max(prev - 1, 0)) } else if (e.key === 'Enter' && flatItems[selectedIndex]) { e.preventDefault() onExecute(flatItems[selectedIndex]) } }, [flatItems, selectedIndex, onExecute] ) return { query, setQuery, filteredGroups, selectedIndex, onKeyDown, } }