103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
import { useId, useState } from 'react';
|
|
|
|
export type Country = { code: string; name: string; name_en: string };
|
|
|
|
export default function CountrySelect({
|
|
countries,
|
|
value,
|
|
onChange,
|
|
id,
|
|
disabled = false,
|
|
label = 'País',
|
|
required = false,
|
|
allowLegacyValue = false,
|
|
error,
|
|
}: {
|
|
countries: Country[];
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
id?: string;
|
|
disabled?: boolean;
|
|
label?: string;
|
|
required?: boolean;
|
|
allowLegacyValue?: boolean;
|
|
error?: string;
|
|
}) {
|
|
const generatedId = useId();
|
|
const [search, setSearch] = useState('');
|
|
const matches = countries.filter(
|
|
(country) =>
|
|
country.code === value ||
|
|
`${country.name} ${country.name_en} ${country.code}`
|
|
.normalize('NFD')
|
|
.replace(/\p{Diacritic}/gu, '')
|
|
.toLowerCase()
|
|
.includes(
|
|
search
|
|
.normalize('NFD')
|
|
.replace(/\p{Diacritic}/gu, '')
|
|
.toLowerCase(),
|
|
),
|
|
);
|
|
return (
|
|
<div className="space-y-2">
|
|
<input
|
|
disabled={disabled}
|
|
className="w-full rounded border p-2"
|
|
aria-label={`Pesquisar ${label.toLowerCase()}`}
|
|
maxLength={100}
|
|
value={search}
|
|
onChange={(event) => setSearch(event.target.value)}
|
|
placeholder="Pesquisar nome ou código do país"
|
|
/>
|
|
{search.trim() && (
|
|
<p role="status" aria-live="polite">
|
|
Países:{' '}
|
|
{matches.length
|
|
? `${matches.length} opções disponíveis`
|
|
: 'Nenhuma opção encontrada'}
|
|
</p>
|
|
)}
|
|
<select
|
|
disabled={disabled}
|
|
id={id ?? generatedId}
|
|
aria-label={label}
|
|
aria-invalid={!!error}
|
|
aria-describedby={
|
|
error ? (id ?? generatedId) + '-error' : undefined
|
|
}
|
|
required={required}
|
|
className="w-full rounded border p-2"
|
|
value={value}
|
|
onChange={(event) => onChange(event.target.value)}
|
|
>
|
|
<option value="">Não informado</option>
|
|
{allowLegacyValue &&
|
|
value &&
|
|
!countries.some((country) => country.code === value) && (
|
|
<option value={value}>
|
|
{value} (valor anterior conservado)
|
|
</option>
|
|
)}
|
|
{matches.map((country) => (
|
|
<option key={country.code} value={country.code}>
|
|
{country.name} ({country.code})
|
|
</option>
|
|
))}
|
|
</select>
|
|
{error && (
|
|
<p
|
|
id={(id ?? generatedId) + '-error'}
|
|
role="alert"
|
|
className="text-red-700"
|
|
>
|
|
{error}
|
|
</p>
|
|
)}
|
|
{matches.length === 0 && (
|
|
<p role="status">Nenhum país corresponde à pesquisa.</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|