import { useEffect, useRef, useMemo } from 'react'
import { useCommandStore } from '@/stores/useCommandStore'
function SearchIcon() {
return (
)
}
function Kbd({ keys }: { keys: string }) {
return (
{keys.split('+').map((k) => (
{k === 'Ctrl' ? '⌃' : k === 'Shift' ? '⇧' : k === 'Cmd' ? '⌘' : k}
))}
)
}
function CommandGroup({ category, children }: { category: string; children: React.ReactNode }) {
return (
)
}
export default function CommandPalette() {
const isOpen = useCommandStore((s) => s.isOpen)
const query = useCommandStore((s) => s.query)
const selectedIndex = useCommandStore((s) => s.selectedIndex)
const commands = useCommandStore((s) => s.commands)
const setQuery = useCommandStore((s) => s.setQuery)
const setSelectedIndex = useCommandStore((s) => s.setSelectedIndex)
const executeSelected = useCommandStore((s) => s.executeSelected)
const close = useCommandStore((s) => s.close)
const inputRef = useRef(null)
const filtered = useMemo(
() => commands.filter(
(c) =>
c.label.toLowerCase().includes(query.toLowerCase()) ||
c.category.toLowerCase().includes(query.toLowerCase())
),
[commands, query]
)
const grouped = useMemo(() => {
const map = new Map()
filtered.forEach((c) => {
const group = map.get(c.category) ?? []
group.push(c)
map.set(c.category, group)
})
return Array.from(map.entries())
}, [filtered])
useEffect(() => {
if (isOpen) {
inputRef.current?.focus()
}
}, [isOpen])
useEffect(() => {
if (!isOpen) return
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Escape') {
e.preventDefault()
close()
return
}
if (e.key === 'Enter') {
e.preventDefault()
executeSelected()
return
}
if (e.key === 'ArrowDown') {
e.preventDefault()
setSelectedIndex(Math.min(selectedIndex + 1, filtered.length - 1))
return
}
if (e.key === 'ArrowUp') {
e.preventDefault()
setSelectedIndex(Math.max(selectedIndex - 1, 0))
return
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [isOpen, filtered.length, selectedIndex, setSelectedIndex, executeSelected, close])
if (!isOpen) return null
return (
e.stopPropagation()}
>
setQuery(e.target.value)}
placeholder="Type a command..."
className="flex-1 bg-transparent text-ui-base text-text-primary outline-none placeholder:text-text-muted"
/>
{filtered.length === 0 && (
No results found
)}
{grouped.map(([category, items]) => (
{items.map((cmd) => {
const idx = filtered.indexOf(cmd)
const isSelected = idx === selectedIndex
return (
)
})}
))}
navigate
select
close
)
}