mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-27 23:40:57 +00:00
91 lines
2.7 KiB
TypeScript
91 lines
2.7 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Group, Select, Text, type ComboboxItem, type SelectProps } from '@mantine/core';
|
|
import { registerLocale, getNames, getName } from 'i18n-iso-countries';
|
|
import * as Flags from 'country-flag-icons/react/3x2';
|
|
import en from 'i18n-iso-countries/langs/en.json';
|
|
import am from 'i18n-iso-countries/langs/am.json';
|
|
|
|
// Registered once at module load — locale data is static.
|
|
registerLocale(en);
|
|
registerLocale(am);
|
|
|
|
type CountryLang = 'en' | 'am';
|
|
|
|
function resolveLang(lng: string): CountryLang {
|
|
return lng === 'am' ? 'am' : 'en';
|
|
}
|
|
|
|
/** Localized country name for an alpha-2 code; '' when unset/unknown. */
|
|
export function getCountryName(code: string | null | undefined, lang: CountryLang = 'en'): string {
|
|
return code ? (getName(code, lang) ?? '') : '';
|
|
}
|
|
|
|
function CountryFlag({ code }: { code: string }) {
|
|
const Flag = Flags[code as keyof typeof Flags];
|
|
return Flag ? <Flag style={{ width: 22, borderRadius: 2, display: 'block' }} /> : null;
|
|
}
|
|
|
|
// Case-insensitive substring on name, plus ISO code prefix ("et" → Ethiopia).
|
|
const filterCountries: SelectProps['filter'] = ({ options, search }) => {
|
|
const q = search.trim().toLowerCase();
|
|
if (!q) return options;
|
|
return (options as ComboboxItem[]).filter(
|
|
(o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().startsWith(q),
|
|
);
|
|
};
|
|
|
|
// Module scope → stable reference, no re-render churn.
|
|
const renderCountryOption: SelectProps['renderOption'] = ({ option }) => (
|
|
<Group gap="xs" wrap="nowrap">
|
|
<CountryFlag code={option.value} />
|
|
<Text fz="sm">{option.label}</Text>
|
|
</Group>
|
|
);
|
|
|
|
export interface CountrySelectProps {
|
|
value: string | null;
|
|
onChange: (value: string | null) => void;
|
|
label?: string;
|
|
placeholder?: string;
|
|
description?: React.ReactNode;
|
|
error?: React.ReactNode;
|
|
required?: boolean;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export function CountrySelect({
|
|
value,
|
|
onChange,
|
|
placeholder,
|
|
...rest
|
|
}: CountrySelectProps) {
|
|
const { t, i18n } = useTranslation();
|
|
const lang = resolveLang(i18n.language);
|
|
|
|
const countries = useMemo(
|
|
() =>
|
|
Object.entries(getNames(lang))
|
|
.map(([code, label]) => ({ value: code, label }))
|
|
.sort((a, b) => a.label.localeCompare(b.label, lang)),
|
|
[lang],
|
|
);
|
|
|
|
return (
|
|
<Select
|
|
data={countries}
|
|
value={value}
|
|
onChange={onChange}
|
|
placeholder={placeholder ?? t('country.select')}
|
|
leftSection={value ? <CountryFlag code={value} /> : undefined}
|
|
renderOption={renderCountryOption}
|
|
filter={filterCountries}
|
|
nothingFoundMessage={t('country.notFound')}
|
|
searchable
|
|
clearable
|
|
maxDropdownHeight={320}
|
|
{...rest}
|
|
/>
|
|
);
|
|
}
|