InputPhone
For API documentation, refer to the components used in the InputPhone pattern:
Unknown country and number format
The input is pre-filled with the value: +.
import { Flex } from '@semcore/ui/base-components';
import { ButtonLink } from '@semcore/ui/button';
import CloseM from '@semcore/ui/icon/Close/m';
import Input from '@semcore/ui/input';
import { Hint } from '@semcore/ui/tooltip';
import { Text } from '@semcore/ui/typography';
import React from 'react';
const Demo = () => {
const [value, setValue] = React.useState('+');
return (
<Flex direction='column'>
<Text tag='label' htmlFor='basic-example' size={200}>
Phone number
</Text>
<Input w={180} mt={2}>
<Input.Value
id='basic-example'
type='tel'
autoComplete='tel'
value={value}
onChange={(v) => setValue(v)}
/>
{value.length > 1 && (
<Input.Addon>
<ButtonLink
use='secondary'
addonLeft={CloseM}
title='Clear'
onClick={() => setValue('+')}
/>
</Input.Addon>
)}
</Input>
</Flex>
);
};
export default Demo;
Known country and unknown number format
The input has a preset value: "{country code}". However, if it's possible to enter phone numbers from multiple countries, a country select option should be provided instead of a static flag.
TIP
Some countries may have multiple formats for phone numbers. Therefore, it's sometimes safer to remove the format mask to avoid restricting user input.
import { Flex } from '@semcore/ui/base-components';
import { ButtonLink } from '@semcore/ui/button';
import Flag, { iso2Name } from '@semcore/ui/flags';
import CloseM from '@semcore/ui/icon/Close/m';
import Input from '@semcore/ui/input';
import { Text } from '@semcore/ui/typography';
import React from 'react';
const Demo = () => {
const [value, setValue] = React.useState('1');
return (
<Flex direction='column'>
<Text tag='label' size={200} htmlFor='phone-number' id='phone-label'>
Phone number
</Text>
<Input w={180} mt={2}>
<Input.Addon>
<Flag iso2='US' role='img' aria-label={iso2Name['US']} id='country-flag' />
</Input.Addon>
<Input.Value
value={value}
onChange={(v) => setValue(v)}
id='phone-number'
type='tel'
autoComplete='tel'
aria-labelledby='phone-label country-flag'
/>
{Number.parseInt(value, 10) > 2 && (
<Input.Addon>
<ButtonLink
use='secondary'
addonLeft={CloseM}
title='Clear'
onClick={() => setValue('1')}
/>
</Input.Addon>
)}
</Input>
</Flex>
);
};
export default Demo;
Known country and number format
WARNING
InputMask is deprecated and will be removed in the next major release.
The input includes the country code as a preset value: "+ {country code}". Additionally, it has the required format mask.
This option is suitable when collecting phone numbers from users in one or a limited number of countries.
TIP
Make sure to verify the available valid phone number formats for the specific country you are targeting.
import { Box, Flex } from '@semcore/ui/base-components';
import { ButtonLink } from '@semcore/ui/button';
import Flag from '@semcore/ui/flags';
import CloseM from '@semcore/ui/icon/Close/m';
import Input from '@semcore/ui/input';
import InputMask from '@semcore/ui/input-mask';
import Select from '@semcore/ui/select';
import { Text } from '@semcore/ui/typography';
import React from 'react';
const Demo = () => {
const inputRef = React.useRef<HTMLInputElement>(null);
const [country, setCountry] = React.useState<keyof typeof countries>('DE');
const prefix = countries[country].prefix;
const [phoneNumber, setPhoneNumber] = React.useState(prefix);
const [phoneMask, setPhoneMask] = React.useState(`${prefix} (___)___-____`);
const positionAfterFirstBracket = prefix.length + 2;
const handleChange = (value: string, e: React.SyntheticEvent<HTMLInputElement>) => {
setPhoneNumber(value);
if (value === prefix) {
inputRef.current?.setSelectionRange(positionAfterFirstBracket, positionAfterFirstBracket);
}
if (e.currentTarget.selectionStart === 0) {
e.currentTarget.setSelectionRange(positionAfterFirstBracket, positionAfterFirstBracket);
}
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Backspace' || e.key === 'ArrowLeft') {
if (e.currentTarget.value === prefix) {
e.preventDefault();
inputRef.current?.setSelectionRange(positionAfterFirstBracket, positionAfterFirstBracket);
return false;
}
const selectionStart = inputRef.current?.selectionStart ?? 0;
const selectionEnd = inputRef.current?.selectionEnd ?? 0;
if (selectionStart <= positionAfterFirstBracket) {
if (selectionStart === selectionEnd) {
e.preventDefault();
return false;
} else {
setTimeout(() => {
inputRef.current?.setSelectionRange(
positionAfterFirstBracket,
positionAfterFirstBracket,
);
}, 0);
return false;
}
}
}
if (e.key === 'ArrowUp' || (e.key === 'ArrowLeft' && e.metaKey) || e.key === 'Home') {
e.preventDefault();
inputRef.current?.setSelectionRange(positionAfterFirstBracket, positionAfterFirstBracket);
}
};
const handleClick = (e: React.SyntheticEvent<HTMLInputElement>) => {
if (
e.currentTarget instanceof HTMLInputElement &&
(e.currentTarget.selectionStart ?? 0) <= positionAfterFirstBracket
) {
e.preventDefault();
inputRef.current?.setSelectionRange(positionAfterFirstBracket, positionAfterFirstBracket);
}
};
return (
<Flex direction='column'>
<Text tag='label' htmlFor='phone-number-with-country-select' size={200}>
Phone number
</Text>
<Box mt={2}>
<Select
value={country}
onChange={(newCountry: keyof typeof countries) => {
setCountry(newCountry);
const prefix = countries[newCountry].prefix;
setPhoneNumber(prefix);
setPhoneMask(`${prefix} (___)___-____`);
setTimeout(() => {
inputRef?.current?.focus();
}, 1);
}}
>
<Select.Trigger aria-label='Country code' neighborLocation='right'>
<Select.Trigger.Addon mx={0}>
<Flag role='img' iso2={country} aria-label={countries[country].name} />
</Select.Trigger.Addon>
</Select.Trigger>
<Select.Menu>
{Object.keys(countries).map((country) => {
const countryKey = country as keyof typeof countries;
return (
<Select.Option key={countryKey} value={countryKey}>
<Text size={200} mr={2} aria-hidden='true'>
<Flag iso2={countryKey} />
</Text>
<Text size={200} mr={2}>
{countries[countryKey].name}
</Text>
<Text size={200} color='text-secondary'>
{countries[countryKey].prefix}
</Text>
</Select.Option>
);
})}
</Select.Menu>
</Select>
<InputMask w={210} neighborLocation='left'>
<InputMask.Value
id='phone-number-with-country-select'
ref={inputRef}
value={phoneNumber}
onChange={handleChange}
aliases={{ _: /\d/ }}
mask={phoneMask}
type='tel'
autoComplete='tel'
title='10 digits, without country code'
onKeyDown={handleKeyDown}
onClick={handleClick}
/>
{phoneNumber !== prefix && (
<Input.Addon>
<ButtonLink
use='secondary'
addonLeft={CloseM}
title='Clear'
onClick={() => setPhoneNumber(prefix)}
/>
</Input.Addon>
)}
</InputMask>
</Box>
</Flex>
);
};
const countries = {
DE: { name: 'Germany', prefix: '+49' },
IT: { name: 'Italy', prefix: '+39' },
NL: { name: 'Netherlands', prefix: '+31' },
GB: { name: 'United Kingdom', prefix: '+44' },
};
export default Demo;