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

## Паттерны

### Подтверждение одноразовым кодом

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

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

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

- длина должна быть фиксированной
- задайте `autoComplete='one-time-code'` на `PinFieldInput`, чтобы браузер мог подставить код
- размещайте поясняющий текст или текст ошибки рядом

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

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

```tsx
import { Button, Label, PinField, PinFieldInput, PinFieldSlot, Text } from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';

export default function Example6DigitOtp() {
  const id = useId();
  const [submitted, setSubmitted] = useState('');

  const onSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setSubmitted(String(new FormData(event.currentTarget).get('otp')));
  };

  return (
    <form
      onSubmit={onSubmit}
      onReset={() => setSubmitted('')}
      style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 320 }}
    >
      <Label htmlFor={id}>Code from the SMS sent to +1 ••• ••• 0132</Label>
      <PinField maxLength={6}>
        <PinFieldInput id={id} name='otp' autoComplete='one-time-code' required pattern='\d{6}' />
        <PinFieldSlot index={0} />
        <PinFieldSlot index={1} />
        <PinFieldSlot index={2} />
        <PinFieldSlot index={3} />
        <PinFieldSlot index={4} />
        <PinFieldSlot index={5} />
      </PinField>
      <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 'var(--spacing-4)', marginBlockStart: 'var(--spacing-9)' }}>
        <Button type='reset' variant='secondary' appearance='ghost'>Clear</Button>
        <Button type='submit'>Verify</Button>
      </div>
      {submitted && (
        <Text size={2} role='status' style={{ color: 'var(--label-secondary)', fontVariantNumeric: 'tabular-nums', marginBlockStart: 'var(--spacing-4)' }}>
          Submitted otp={submitted}
        </Text>
      )}
    </form>
  );
}
```

### Сгруппированный устаревший код

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

- код визуально разбит на группы в продукте или во внешнем источнике, откуда пользователи его копируют

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

- используйте `PinFieldSeparator` только для визуальной группировки
- сохраняйте согласованную длину ввода

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

- повторяет реальное форматирование кода
- добавляет ещё одно решение по раскладке

```tsx
import { Label, PinField, PinFieldInput, PinFieldSeparator, PinFieldSlot } from '@vega-ui/react';
import { useId } from 'react';

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

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 320 }}>
      <Label htmlFor={id}>Code from your authenticator app</Label>
      <PinField maxLength={6}>
        <PinFieldInput id={id} name='totp' autoComplete='one-time-code' />
        <PinFieldSlot index={0} />
        <PinFieldSlot index={1} />
        <PinFieldSlot index={2} />
        <PinFieldSeparator />
        <PinFieldSlot index={3} />
        <PinFieldSlot index={4} />
        <PinFieldSlot index={5} />
      </PinField>
    </div>
  );
}
```

### Буквенно-цифровой код активации

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

- код принимает буквы и цифры

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

- передайте подходящую маску
- нормализуйте регистр в `onChangeValue`, если бэкенд ждёт заглавные буквы
- согласуйте маску на фронтенде с валидацией на бэкенде

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

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

```tsx
import { Label, PinField, PinFieldInput, PinFieldSlot, Text } from '@vega-ui/react';
import { useId, useState } from 'react';

export default function AlphanumericActivationCode() {
  const id = useId();
  const [code, setCode] = useState('');

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 320 }}>
      <Label htmlFor={id}>Activation code from the box</Label>
      <PinField
        mask={/^[A-Za-z0-9]+$/}
        maxLength={5}
        value={code}
        onChangeValue={(next) => setCode(next.toUpperCase())}
      >
        <PinFieldInput id={id} inputMode='text' autoCapitalize='characters' autoComplete='off' />
        <PinFieldSlot index={0} />
        <PinFieldSlot index={1} />
        <PinFieldSlot index={2} />
        <PinFieldSlot index={3} />
        <PinFieldSlot index={4} />
      </PinField>
      <Text size={2} style={{ color: 'var(--label-secondary)', fontVariantNumeric: 'tabular-nums' }}>
        {code.length === 5 ? `Ready to activate ${code}.` : `${5 - code.length} characters left.`}
      </Text>
    </div>
  );
}
```

## Сравнение

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

Используйте `PinField` для коротких кодов фиксированной длины, которым помогает сегментированное отображение. Используйте `PasswordField` для секретных строк и `TextField` для обычного текста.

### `PinField` и `PasswordField`

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

- значение — одноразовый код (OTP) или короткий код подтверждения

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

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

### `PinField` и `TextField`

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

- сегментация упрощает чтение и ввод

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

- поле — обычный свободный ввод

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

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

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

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

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

### Ввод не обновляет видимые слоты

#### Симптом

Сегментированный интерфейс рендерится, но введённые символы не появляются в слотах.

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

В композиции нет `PinFieldInput`.

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

- изучите отрендеренную композицию
- убедитесь, что `PinFieldInput` находится внутри корня

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

Добавьте `PinFieldInput` внутрь `PinField`.

### Порядок слотов кажется неправильным или каретка движется странно

#### Симптом

Стрелки или ввод словно попадают не в тот слот.

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

Индексы слотов отрендерены не по возрастанию.

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

- изучите последовательность слотов
- сравните её с `maxLength`

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

Рендерите элементы `PinFieldSlot` по возрастанию индекса и согласуйте их с задуманной длиной кода.

### Бэкенд отклоняет коды, которые в интерфейсе выглядят корректно

#### Симптом

Пользователи могут ввести код, но отправка не проходит из-за несоответствия формата.

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

`mask` на фронтенде не соответствует ожиданиям бэкенда.

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

- сравните текущую маску с форматом, который принимает бэкенд

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

Обновите маску и плейсхолдеры так, чтобы они отражали реальный допустимый формат кода.
