Files
nuzlocke-tracker/frontend/src/components/admin/AbilitySelector.tsx

70 lines
2.1 KiB
TypeScript
Raw Normal View History

import { useState, useRef, useEffect } from 'react'
import { useSearchAbilities } from '../../hooks/useMoves'
interface AbilitySelectorProps {
label: string
selectedId: number | null
initialName?: string
onChange: (id: number | null, name: string) => void
}
export function AbilitySelector({
label,
selectedId,
initialName,
onChange,
}: AbilitySelectorProps) {
const [search, setSearch] = useState(initialName ?? '')
const [open, setOpen] = useState(false)
const ref = useRef<HTMLDivElement>(null)
const { data } = useSearchAbilities(search)
const abilities = data?.items ?? []
useEffect(() => {
function handleClick(e: MouseEvent) {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false)
}
}
document.addEventListener('mousedown', handleClick)
return () => document.removeEventListener('mousedown', handleClick)
}, [])
return (
<div ref={ref} className="relative">
<label className="block text-xs font-medium mb-0.5 text-text-secondary">{label}</label>
<input
type="text"
value={search}
onChange={(e) => {
setSearch(e.target.value)
setOpen(true)
if (!e.target.value) onChange(null, '')
}}
onFocus={() => search && setOpen(true)}
placeholder="Search ability..."
className="w-full px-2 py-1.5 text-sm border rounded bg-surface-2 border-border-default"
/>
{open && abilities.length > 0 && (
<ul className="absolute z-20 mt-1 w-full bg-surface-1 border border-border-default rounded shadow-lg max-h-40 overflow-y-auto">
{abilities.map((a) => (
<li
key={a.id}
onClick={() => {
onChange(a.id, a.name)
setSearch(a.name)
setOpen(false)
}}
className={`px-2 py-1.5 cursor-pointer hover:bg-surface-2 text-sm ${
a.id === selectedId ? 'bg-accent-900/30' : ''
}`}
>
{a.name}
</li>
))}
</ul>
)}
</div>
)
}