# PhoneField: Руководства

## Паттерны

### Контактная форма с учётом страны

Когда использовать:

- продукт хранит международные номера телефонов

Заметки о композиции:

- держите выбор страны видимым
- нормализуйте значения при отправке
- показывайте в триггере компактное значение (здесь флаг), а полное название страны — в списке

Компромиссы:

- лучшая корректность для международных номеров
- более сложное поведение формы, чем у обычного текстового поля

```tsx
import {
  Label,
  PhoneField,
  PhoneFieldInput,
  PhoneFieldSelect,
  PhoneFieldSelectCombobox,
  PhoneFieldSelectHiddenSelect,
  PhoneFieldSelectIcon,
  PhoneFieldSelectListbox,
  PhoneFieldSelectOption,
  PhoneFieldSelectValue,
} from '@vega-ui/react';
import { FlagDE, FlagFR, FlagGBUKM, FlagIN, FlagJP, FlagUS } from '@vega-ui/icons';
import { useId, useState } from 'react';

const countries = [
  { code: 'US', name: 'United States', callingCode: '+1', Flag: FlagUS },
  { code: 'GB', name: 'United Kingdom', callingCode: '+44', Flag: FlagGBUKM },
  { code: 'DE', name: 'Germany', callingCode: '+49', Flag: FlagDE },
  { code: 'FR', name: 'France', callingCode: '+33', Flag: FlagFR },
  { code: 'IN', name: 'India', callingCode: '+91', Flag: FlagIN },
  { code: 'JP', name: 'Japan', callingCode: '+81', Flag: FlagJP },
] as const;

export default function SelectWithCountryOptionsAndIcons() {
  const id = useId();
  const [country, setCountry] = useState<(typeof countries)[number]>(countries[1]);

  const onChangeCode = (code: string) => {
    const found = countries.find((item) => item.code === code);
    if (found) setCountry(found);
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 340 }}>
      <Label htmlFor={id}>Phone number</Label>
      <PhoneField code={country.code} onChangeCode={onChangeCode}>
        <PhoneFieldSelect>
          <PhoneFieldSelectHiddenSelect name='country' />
          <PhoneFieldSelectCombobox aria-label={`Country: ${country.name}`}>
            <PhoneFieldSelectValue>
              <country.Flag width={20} height={15} aria-hidden='true' />
            </PhoneFieldSelectValue>
            <PhoneFieldSelectIcon />
          </PhoneFieldSelectCombobox>
          <PhoneFieldSelectListbox>
            {countries.map(({ code, name, callingCode, Flag }) => (
              <PhoneFieldSelectOption key={code} value={code} label={name}>
                <span style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-6)' }}>
                  <Flag width={20} height={15} aria-hidden='true' />
                  {name}
                </span>
                <span style={{ opacity: 0.7 }}>{callingCode}</span>
              </PhoneFieldSelectOption>
            ))}
          </PhoneFieldSelectListbox>
        </PhoneFieldSelect>
        <PhoneFieldInput id={id} name='phone' autoComplete='tel' />
      </PhoneField>
    </div>
  );
}
```

### Ввод со строгой маской

Когда использовать:

- формат ввода должен быть жёстко ограничен

Заметки о композиции:

- `strictMask` включён по умолчанию: код страны из `code` зафиксирован, и принимаются только его цифры
- перепроверьте перемещение каретки и вставку

Компромиссы:

- более чистый отформатированный ввод
- менее снисходительное редактирование

```tsx
import { Label, PhoneField, PhoneFieldInput } from '@vega-ui/react';
import { useId } from 'react';

export default function SimpleIsoBasedPhoneMask() {
  const id = useId();

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 280 }}>
      <Label htmlFor={id}>Mobile number</Label>
      <PhoneField code='US'>
        <PhoneFieldInput id={id} name='phone' autoComplete='tel' placeholder='+1 201 555-0123' />
      </PhoneField>
    </div>
  );
}
```

### Терпимая вставка

Когда использовать:

- пользователи часто вставляют номера телефонов из внешних систем

Заметки о композиции:

- используйте `strictMask={false}`
- вставленный номер с другим кодом страны переключает страну, и `onChangeCode` сообщает об этом
- нормализуйте на границе функции

Компромиссы:

- лучшая терпимость к вставке
- более слабые гарантии форматирования внутри поля

```tsx
import {
  Label,
  PhoneField,
  PhoneFieldInput,
  PhoneFieldSelect,
  PhoneFieldSelectCombobox,
  PhoneFieldSelectIcon,
  PhoneFieldSelectListbox,
  PhoneFieldSelectOption,
  PhoneFieldSelectValue,
  Text,
} from '@vega-ui/react';
import { useId, useState } from 'react';

export default function NonStrictMaskForLenientPasteBehavior() {
  const id = useId();
  const [country, setCountry] = useState('US');

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 340 }}>
      <Label htmlFor={id}>Customer phone</Label>
      <PhoneField defaultCode='US' strictMask={false} onChangeCode={setCountry}>
        <PhoneFieldSelect>
          <PhoneFieldSelectCombobox aria-label='Country'>
            <PhoneFieldSelectValue />
            <PhoneFieldSelectIcon />
          </PhoneFieldSelectCombobox>
          <PhoneFieldSelectListbox>
            <PhoneFieldSelectOption value='US'>US</PhoneFieldSelectOption>
            <PhoneFieldSelectOption value='GB'>GB</PhoneFieldSelectOption>
            <PhoneFieldSelectOption value='DE'>DE</PhoneFieldSelectOption>
            <PhoneFieldSelectOption value='FR'>FR</PhoneFieldSelectOption>
            <PhoneFieldSelectOption value='JP'>JP</PhoneFieldSelectOption>
          </PhoneFieldSelectListbox>
        </PhoneFieldSelect>
        <PhoneFieldInput id={id} autoComplete='tel' />
      </PhoneField>
      <Text size={2} style={{ color: 'var(--label-secondary)', marginBlockStart: 'var(--spacing-2)' }}>
        Paste +33 6 12 34 56 78 or +81 90-1234-5678: the country switches to match. Detected: {country}
      </Text>
    </div>
  );
}
```

## Сравнение

### Быстрое правило выбора

Выбирайте `PhoneField`, когда значение телефона и выбор страны должны вести себя как одно согласованное поле.

### `PhoneField` или `TextField`

Используйте `PhoneField`, когда:

- важно форматирование телефона с учётом страны
- интерфейсу нужна маска или выбор страны

Используйте `TextField`, когда:

- значение — просто текстовый ввод

### `PhoneField` или `Select` + `TextField`

Используйте `PhoneField`, когда:

- выбор страны и форматирование телефона должны оставаться связанными
- во всём приложении нужно переиспользовать один канонический контракт ввода телефона

Используйте отдельные элементы, когда:

- продукт намеренно разделяет выбор страны и ввод телефона
- маска, валидация и смена страны не должны разделять один жизненный цикл поля

### Выбирайте этот компонент, когда

- важно поведение маски
- выбор страны меняет валидацию или форматирование
- продукт рассматривает ввод телефона как одно семейство поля

### Не выбирайте этот компонент, когда

- форматирование телефона не важно
- достаточно обычного текстового поля
- выбор страны должен быть полностью независим от поля ввода

## Решение проблем

### Смена страны неправильно переформатирует номер

#### Симптом

После смены выбранной страны видимый номер остаётся в невалидном или устаревшем формате.

#### Вероятная причина

Состояние страны и логика валидации не синхронизированы в родительском сценарии.

#### Как проверить

- переключите страну при уже введённом значении
- сравните поведение отображаемого и сохраняемого значения

#### Исправление

- рассматривайте страну и значение как один сценарий интеграции
- нормализуйте значение после смены страны

### Вставка кажется слишком строгой

#### Симптом

Пользователь вставляет номер, а поле неожиданно отклоняет или искажает ввод.

#### Вероятная причина

`strictMask` слишком строг для реального источника ввода.

#### Как проверить

- протестируйте вставку на реалистичных строках с номерами

#### Исправление

- рассмотрите `strictMask={false}` для терпимых сценариев
- нормализуйте каноническое значение после ввода

### Выбор страны выглядит правильно, но семантика формы слабая

#### Симптом

Визуально интерфейс работает, но выбранная страна не попадает в обычные сценарии формы.

#### Вероятная причина

Отсутствует `PhoneFieldSelectHiddenSelect`.

#### Как проверить

- изучите поведение нативной отправки формы

#### Исправление

- отрендерите `PhoneFieldSelectHiddenSelect` внутри композиции select
