import { useMemo, useState } from 'react'; import { PickerShell } from './PickerShell'; export interface SelectFieldOption { value: string; label: string; } export interface SelectFieldProps { label?: string; required?: boolean; hint?: string; className?: string; /** Either strings or {value,label} objects. */ options?: Array; value: string; onChange: (value: string) => void; placeholder?: string; /** Force the search box on/off. Defaults to on when there are >7 options. */ searchable?: boolean; /** Allow clearing back to ''. Defaults to true for non-required fields. */ clearable?: boolean; disabled?: boolean; } const norm = (o: string | SelectFieldOption): SelectFieldOption => typeof o === 'string' ? { value: o, label: o } : o; /** Single-select that renders through the shared PickerShell, so it matches * LookupField exactly. Static options, searched/filtered locally. An empty- * value option (e.g. {value:'', label:'Choose…'}) is treated as the * placeholder rather than a list row, and option values are de-duplicated. */ export function SelectField({ label, required, hint, className, options = [], value, onChange, placeholder, searchable, clearable, disabled, }: SelectFieldProps) { const [search, setSearch] = useState(''); // Normalize, lift any empty-value option's label to the placeholder, dedupe. const { opts, ph } = useMemo(() => { const all = options.map(norm); const empty = all.find((o) => o.value === ''); const seen = new Set(); const opts = all.filter((o) => { if (o.value === '' || seen.has(o.value)) return false; seen.add(o.value); return true; }); return { opts, ph: placeholder ?? empty?.label }; }, [options, placeholder]); const items = useMemo(() => { const q = search.trim().toLowerCase(); return q ? opts.filter((o) => o.label.toLowerCase().includes(q)) : opts; }, [opts, search]); const display = opts.find((o) => o.value === value)?.label ?? ''; const canSearch = searchable ?? opts.length > 7; const canClear = (clearable ?? !required) && value !== ''; return ( label={label} required={required} hint={hint} className={className} displayLabel={display} placeholder={ph} items={items} renderItem={(o) => o.label} itemKey={(o) => o.value} onPick={(o) => onChange(o.value)} onClear={canClear ? () => onChange('') : undefined} searchable={canSearch} search={search} onSearch={setSearch} onOpenChange={(open) => !open && setSearch('')} emptyText="No options." disabled={disabled} /> ); }