59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import { h } from 'preact';
|
|
import { useState, useMemo } from 'preact/hooks';
|
|
|
|
// A dropdown with an inline search box. Extracted from SyntheticWizard so the
|
|
// same searchable picker can be reused (e.g. the logic editor's signal field).
|
|
export default function SearchableSelect({
|
|
value,
|
|
options,
|
|
onSelect,
|
|
placeholder = 'Select…',
|
|
}: {
|
|
value: string;
|
|
options: string[];
|
|
onSelect: (val: string) => void;
|
|
placeholder?: string;
|
|
}) {
|
|
const [open, setOpen] = useState(false);
|
|
const [filter, setFilter] = useState('');
|
|
|
|
const filtered = useMemo(() => {
|
|
const f = filter.toLowerCase();
|
|
return options.filter(o => o.toLowerCase().includes(f));
|
|
}, [options, filter]);
|
|
|
|
return (
|
|
<div class="search-select">
|
|
<div class="search-select-trigger" onClick={() => setOpen(!open)}>
|
|
{value || <span class="search-select-placeholder">{placeholder}</span>}
|
|
<span class="search-select-arrow">{open ? '▴' : '▾'}</span>
|
|
</div>
|
|
{open && (
|
|
<div class="search-select-dropdown">
|
|
<input
|
|
class="prop-input"
|
|
autoFocus
|
|
placeholder="Search…"
|
|
value={filter}
|
|
onInput={(e) => setFilter((e.target as HTMLInputElement).value)}
|
|
onClick={(e) => e.stopPropagation()}
|
|
/>
|
|
<div class="search-select-list">
|
|
{filtered.length === 0 && <div class="search-select-item hint">No matches</div>}
|
|
{filtered.map(opt => (
|
|
<div
|
|
key={opt}
|
|
class={`search-select-item${opt === value ? ' search-select-item-selected' : ''}`}
|
|
onClick={() => { onSelect(opt); setOpen(false); setFilter(''); }}
|
|
>
|
|
{opt}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{open && <div class="search-select-backdrop" onClick={() => setOpen(false)} />}
|
|
</div>
|
|
);
|
|
}
|