'use client'; import { Search, X } from 'lucide-react'; import { useEffect, useMemo, useRef, useState } from 'react'; import { t } from '@/i18n'; import { cn } from '@/lib/cn'; import type { CountryLite } from '@/lib/types-compare'; import { BottomSheet } from '@/components/data/bottom-sheet'; /** Case/diacritic-insensitive prefix-then-substring match on name / ISO3 / slug. */ export function filterCountries(items: CountryLite[], q: string, exclude: ReadonlySet = new Set()): CountryLite[] { const ql = q.trim().toLowerCase(); const pool = items.filter((c) => !exclude.has(c.slug)); if (!ql) return pool; const starts = pool.filter((c) => c.name.toLowerCase().startsWith(ql) || c.id.toLowerCase() === ql || c.slug.startsWith(ql)); const contains = pool.filter((c) => !starts.includes(c) && (c.name.toLowerCase().includes(ql) || c.slug.includes(ql))); return [...starts, ...contains]; } /** * Country typeahead list (used inline on desktop and inside the bottom sheet on phones). Arrow keys move, * Enter picks, Esc clears. Already-selected countries are excluded. */ export function CountryTypeahead({ countries, exclude, onPick, autoFocus, placeholder, max = 12, className, disabled }: { countries: CountryLite[]; exclude: ReadonlySet; onPick: (c: CountryLite) => void; autoFocus?: boolean; placeholder?: string; max?: number; className?: string; disabled?: boolean }) { const [q, setQ] = useState(''); const [open, setOpen] = useState(false); const [cursor, setCursor] = useState(0); const boxRef = useRef(null); const results = useMemo(() => filterCountries(countries, q, exclude).slice(0, max), [countries, q, exclude, max]); useEffect(() => { if (!open) return; const onDoc = (e: MouseEvent) => { if (boxRef.current && !boxRef.current.contains(e.target as Node)) setOpen(false); }; document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc); }, [open]); const pick = (c: CountryLite) => { onPick(c); setQ(''); setCursor(0); }; return (
0} aria-controls="country-typeahead-list" aria-autocomplete="list" autoFocus={autoFocus} disabled={disabled} value={q} onChange={(e) => { setQ(e.target.value); setOpen(true); setCursor(0); }} onFocus={() => setOpen(true)} onKeyDown={(e) => { if (e.key === 'ArrowDown') { e.preventDefault(); setCursor((c) => Math.min(results.length - 1, c + 1)); setOpen(true); } else if (e.key === 'ArrowUp') { e.preventDefault(); setCursor((c) => Math.max(0, c - 1)); } else if (e.key === 'Enter') { const r = results[cursor]; if (r) { e.preventDefault(); pick(r); } } else if (e.key === 'Escape') { setQ(''); setOpen(false); } }} placeholder={placeholder ?? t('compare.builder.search')} aria-label={placeholder ?? t('compare.builder.search')} className="h-11 w-full rounded-sm border border-rule bg-surface pl-9 pr-9 text-sm text-ink outline-none placeholder:text-ink-3 focus:border-accent disabled:opacity-50 md:h-10" /> {q ? ( ) : null}
{open && (q || results.length) ? (
    {results.length === 0 ? (
  • {t('compare.builder.noMatch', { q })}
  • ) : ( results.map((c, i) => (
  • )) )}
) : null}
); } /** Country picker in a sheet (bottom sheet on phones, centred panel on desktop): search + tappable list. */ export function CountryPickerSheet({ open, onClose, countries, exclude, onPick, title, closeOnPick = true }: { open: boolean; onClose: () => void; countries: CountryLite[]; exclude: ReadonlySet; onPick: (c: CountryLite) => void; title?: string; closeOnPick?: boolean }) { const [q, setQ] = useState(''); const results = useMemo(() => filterCountries(countries, q, exclude), [countries, q, exclude]); useEffect(() => { if (!open) setQ(''); }, [open]); return (
setQ(e.target.value)} placeholder={t('compare.builder.search')} aria-label={t('compare.builder.search')} className="h-11 w-full rounded-sm border border-rule bg-surface pl-9 pr-3 text-sm text-ink outline-none placeholder:text-ink-3 focus:border-accent" />
{results.length === 0 ? (

{t('compare.builder.noMatch', { q })}

) : (
    {results.map((c) => (
  • ))}
)}
); }