Интерфейсы и frontend
April 9

Компонент для номера телефона

Около месяца назад я писал пост про библиотеку imask, чтобы стандартизировать ввод номера телефона, с помощью использования маски. Тогда же я упомянул, что основным минусом, на мой взгляд, является то, что нужно написать много дополнительного кода, чтобы достичь желаемого результата.

Пару недель назад я как раз столкнулся с подобной задачей в рамках проекта на react и захотел попробовать реализовать это с помощью imask, но столкнулся с проблемой: нужно отображать список стран, с привязанным к ним кодом. В imask есть возможность решить это, но нет готового набора кодов номеров телефонов. Я вспомнил о решении, которое мои сотрудники находили. Попробовал его адаптировать и понял, что я особо не могу его кастомизировать (по дизайну требовалось выводить двухбуквенное название страны, а не флаг), несмотря на его описание, — "Advanced, highly customizable phone input component for Ant Design."

Поняв, что нахожусь в сложной ситуации, я пошёл искать библиотеки, которые предоставляют просто список телефонных кодов по странам, чтобы реализовать это с помощью imask и нашёл такое решение. По сути, это простая библиотечка, которая возвращает нам объект из пар ключ-значение в том формате, в котором нам необходимо. Казалось бы, всё замечательно. Можно брать динамически маски в imask, генерировать их с помощью этих самых кодов и идти спокойно пить чай. Но в этот момент я осознал, что у разных стран, буквально, могут быть разные маски для ввода номера телефона и это окончательно закопало мою прекрасную идею об использовании imask для решения этой задачи.

Пример с самого сайта imask, где наглядно видно, что маски вообще не подчиняются единым правилам и могут выглядеть абсолютно как угодно:

[
    {
      mask: '+00 {21} 0 000 0000',
      startsWith: '30',
      lazy: false,
      country: 'Greece'
    },
    {
      mask: '+0 000 000-00-00',
      startsWith: '7',
      lazy: false,
      country: 'Russia'
    },
    {
      mask: '+00-0000-000000',
      startsWith: '91',
      lazy: false,
      country: 'India'
    },
    {
      mask: '0000000000000',
      startsWith: '',
      country: 'unknown'
    }
]

Ресёрч пришлось продолжать. Я нашёл библиотеку, которая позволяла валидировать номер телефона относительно двухбуквенного кода страны, к которой этот номер относится. Кроме прочего, в этой библиотеке есть и возможность форматирования номера телефона, как раз под маску, принятую в конкретной стране. В описании библиотеки также упоминался react-компонент, который использует эту самую библиотеку. И на основе уже этого компонента, я собрал своё решение с использованием ant-design (разумеется, оно не идеальное, но достаточно хорошее, чтобы использовать в условном продакшне):

import PhoneInput from 'react-phone-number-input';
import PropTypes from 'prop-types';
import { getCountries } from 'react-phone-number-input';
import { Select as AntdSelect } from 'antd';

const phoneCountryLabels = () => {
    const countryLabels = {};

    getCountries().forEach((country) => (countryLabels[country] = country));

    return countryLabels;
};

const CountrySelect = ({ value, onChange, labels, variant = 'filled', ...rest }) => (
    <AntdSelect
        {...rest}
        value={value}
        showSearch
        optionFilterProp="label"
        onChange={onChange}
        variant={variant}
        size="large"
        style={{ '--ant-select-single-item-height-lg': '3rem' }}
    />
);

CountrySelect.propTypes = {
    value: PropTypes.string,
    onChange: PropTypes.func.isRequired,
    labels: PropTypes.objectOf(PropTypes.string).isRequired,
    variant: PropTypes.string,
};

const PhoneNumberInput = ({ onChange, country = 'US' }) => {
    return (
        <div className="base-phone-number">
            <PhoneInput
                onChange={onChange}
                defaultCountry={country}
                international
                limitMaxLength
                labels={phoneCountryLabels()}
                countrySelectComponent={CountrySelect}
                numberInputProps={{
                    className: 'ant-input ant-input-filled css-var-r1 ant-input-css-var base-input',
                }}
            />
        </div>
    );
};

PhoneNumberInput.propTypes = {
    onChange: PropTypes.func.isRequired,
    country: PropTypes.string,
};

export default PhoneNumberInput;

А как вы решали подобные задачи? Делитесь, будет интересно узнать.

P. S. как появится немного времени, планирую тоже самое адаптировать под vue-компонент и поделиться результатом