mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-30 13:28:13 +00:00
feat: implement formatNational utility and improve PhoneInput formatting and interaction logic
This commit is contained in:
@@ -2,14 +2,13 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Group, Select, Text, TextInput, type ComboboxItem, type SelectProps } from '@mantine/core';
|
||||
import {
|
||||
AsYouType,
|
||||
getCountries,
|
||||
getCountryCallingCode,
|
||||
parsePhoneNumberFromString,
|
||||
type CountryCode,
|
||||
} from 'libphonenumber-js';
|
||||
import { CountryFlag, getCountryName, resolveLang } from './CountrySelect';
|
||||
import { nextNationalDigits, toE164, toNationalDigits } from './phone';
|
||||
import { formatNational, nextNationalDigits, toE164, toNationalDigits } from './phone';
|
||||
|
||||
// Static list, computed once at module load — same as CountrySelect's dataset.
|
||||
const COUNTRY_CODES = getCountries();
|
||||
@@ -87,8 +86,11 @@ export function PhoneInput({
|
||||
const initialCountry = parsePhoneNumberFromString(value || '')?.country ?? 'ET';
|
||||
const [country, setCountry] = useState<CountryCode>(initialCountry);
|
||||
const [national, setNational] = useState(() =>
|
||||
new AsYouType(initialCountry).input(toNationalDigits(value, initialCountry)),
|
||||
formatNational(toNationalDigits(value, initialCountry), initialCountry),
|
||||
);
|
||||
// Mantine keeps the selected label ("+251") as the search text, so typing
|
||||
// would search for "+251u". Cleared while the dropdown is open instead.
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
// What this field last pushed upward, so an echo of our own value isn't
|
||||
// mistaken for the parent resetting the form.
|
||||
@@ -99,7 +101,7 @@ export function PhoneInput({
|
||||
const parsedCountry = parsePhoneNumberFromString(value || '')?.country;
|
||||
const next = parsedCountry ?? country;
|
||||
if (parsedCountry) setCountry(parsedCountry);
|
||||
setNational(new AsYouType(next).input(toNationalDigits(value, next)));
|
||||
setNational(formatNational(toNationalDigits(value, next), next));
|
||||
emitted.current = value;
|
||||
// Adopting an outside change only — `country` is state written here, so
|
||||
// re-running on it would fight the user's typing.
|
||||
@@ -122,12 +124,14 @@ export function PhoneInput({
|
||||
}
|
||||
|
||||
function applyDigits(digits: string, forCountry: CountryCode) {
|
||||
// Once the digits parse, show the true national number: someone who
|
||||
// Once the number is valid, show the true national part: someone who
|
||||
// types a trunk prefix (0911111111) or the country code (251911111111)
|
||||
// shouldn't end up with it doubled beside the "+251" selector.
|
||||
// shouldn't end up with it doubled beside the "+251" selector. Not
|
||||
// before: a partial number can parse too, and rewriting it mid-typing
|
||||
// makes digits vanish under the caret.
|
||||
const parsed = parsePhoneNumberFromString(digits, forCountry);
|
||||
setNational(new AsYouType(forCountry).input(parsed?.nationalNumber ?? digits));
|
||||
push(parsed?.number ?? toE164(digits, forCountry));
|
||||
setNational(formatNational(parsed?.isValid() ? parsed.nationalNumber : digits, forCountry));
|
||||
push(toE164(digits, forCountry));
|
||||
}
|
||||
|
||||
function handleText(text: string) {
|
||||
@@ -139,7 +143,7 @@ export function PhoneInput({
|
||||
const parsed = parsePhoneNumberFromString(trimmed);
|
||||
if (parsed?.country) {
|
||||
setCountry(parsed.country);
|
||||
setNational(new AsYouType(parsed.country).input(parsed.nationalNumber));
|
||||
setNational(formatNational(parsed.nationalNumber, parsed.country));
|
||||
push(parsed.number);
|
||||
return;
|
||||
}
|
||||
@@ -169,7 +173,13 @@ export function PhoneInput({
|
||||
placeholder={placeholder}
|
||||
value={national}
|
||||
onChange={(e) => handleText(e.currentTarget.value)}
|
||||
onBlur={onBlur}
|
||||
// Blur of the whole field, not of the number box: moving into the
|
||||
// country select mustn't validate a half-typed number.
|
||||
wrapperProps={{
|
||||
onBlur: (e: React.FocusEvent<HTMLDivElement>) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) onBlur?.();
|
||||
},
|
||||
}}
|
||||
leftSectionWidth={92}
|
||||
leftSectionPointerEvents={disabled || readOnly ? 'none' : 'all'}
|
||||
leftSection={
|
||||
@@ -181,6 +191,12 @@ export function PhoneInput({
|
||||
renderOption={renderCountryOption}
|
||||
filter={filterCountries}
|
||||
searchable
|
||||
searchValue={search}
|
||||
onSearchChange={setSearch}
|
||||
onDropdownOpen={() => setSearch('')}
|
||||
onDropdownClose={() => setSearch(`+${getCountryCallingCode(country)}`)}
|
||||
nothingFoundMessage={t('phone.noCountry', 'No matching country')}
|
||||
allowDeselect={false}
|
||||
disabled={disabled || readOnly}
|
||||
variant="unstyled"
|
||||
size="xs"
|
||||
|
||||
Reference in New Issue
Block a user