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

## Паттерны

### Строка поиска

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

- полю нужно действие отправки или поиска в конце

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

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

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

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

```tsx
import type { CSSProperties, FormEvent } from 'react';
import { Button, Icon, IconButton, Text, TextField, TextFieldInput } from '@vega-ui/react';
import { SearchIcon, XIcon } from '@vega-ui/icons';
import { useRef, useState } from 'react';
import './examples.css';

export default function SearchWithClearAndSubmit() {
  const inputRef = useRef<HTMLInputElement>(null);
  const [query, setQuery] = useState('');
  const [searched, setSearched] = useState('');

  const onSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setSearched(query.trim());
  };

  const clear = () => {
    setQuery('');
    inputRef.current?.focus();
  };

  return (
    <form role='search' onSubmit={onSubmit} style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
      <TextField>
        <Icon size='sm' aria-hidden style={{ flexShrink: 0, marginInlineStart: 'var(--spacing-6)', color: 'var(--label-secondary)' }}>
          <SearchIcon />
        </Icon>
        <TextFieldInput
          ref={inputRef}
          className='searchInput'
          type='search'
          name='q'
          aria-label='Search invoices'
          placeholder='Invoice or customer'
          value={query}
          onChange={(event) => setQuery(event.currentTarget.value)}
        />
        {query !== '' && (
          <IconButton
            type='button'
            aria-label='Clear search'
            variant='secondary'
            appearance='ghost'
            onClick={clear}
            style={{ '--icon-button-size': '24px', '--icon-button-br-ratio': 0.5, flexShrink: 0 } as CSSProperties}
          >
            <Icon size='xs'><XIcon strokeWidth={2.5} /></Icon>
          </IconButton>
        )}
        <Button type='submit' size='sm' style={{ flexShrink: 0, marginInlineStart: 'var(--spacing-2)', marginInlineEnd: 'var(--spacing-3)' }}>Search</Button>
      </TextField>
      <Text size={2} role='status' style={{ color: 'var(--label-secondary)', overflowWrap: 'anywhere' }}>
        {searched === '' ? 'Showing all invoices' : `Results for “${searched}”`}
      </Text>
    </form>
  );
}
```

### Суффикс со счётчиком или статусом

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

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

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

- текст суффикса должен быть коротким
- не сужайте инпут слишком сильно

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

- мгновенная обратная связь
- может уменьшить полезную ширину ввода

```tsx
import { Label, Text, TextField, TextFieldInput } from '@vega-ui/react';
import { useId, useState } from 'react';

const MAX_LENGTH = 60;

export default function CharacterCounterSuffix() {
  const id = useId();
  const counterId = `${id}-counter`;
  const [title, setTitle] = useState('Q3 roadmap review');
  const remaining = MAX_LENGTH - title.length;

  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
      <Label htmlFor={id}>Meeting title</Label>
      <TextField>
        <TextFieldInput
          id={id}
          value={title}
          maxLength={MAX_LENGTH}
          aria-describedby={counterId}
          onChange={(event) => setTitle(event.currentTarget.value)}
        />
        <Text
          id={counterId}
          size={2}
          style={{ paddingInlineEnd: 'var(--spacing-6)', whiteSpace: 'nowrap', color: 'var(--label-secondary)', fontVariantNumeric: 'tabular-nums' }}
        >
          {remaining} left
        </Text>
      </TextField>
      <Text size={2} style={{ color: 'var(--label-secondary)', overflowWrap: 'anywhere' }}>
        Calendar preview: {title.trim() === '' ? 'Untitled meeting' : title.trim()}
      </Text>
    </div>
  );
}
```

### Префикс и суффикс

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

- фиксированный текст — часть значения: протокол, домен или единица измерения

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

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

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

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

```tsx
import { HelperText, Label, Text, TextField, TextFieldInput } from '@vega-ui/react';
import { useId, useState } from 'react';

const affixStyle = {
  flexShrink: 0,
  whiteSpace: 'nowrap',
  color: 'var(--label-secondary)',
} as const;

export default function SubdomainPrefixSuffix() {
  const id = useId();
  const hintId = `${id}-hint`;
  const [subdomain, setSubdomain] = useState('northwind-traders-international-logistics');

  const onChange = (value: string) => {
    setSubdomain(value.toLowerCase().replace(/[^a-z0-9-]/g, ''));
  };

  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
      <Label htmlFor={id}>Workspace address</Label>
      <TextField>
        <Text size={3} aria-hidden style={{ ...affixStyle, paddingInlineStart: 'var(--spacing-6)' }}>https://</Text>
        <TextFieldInput
          id={id}
          name='subdomain'
          value={subdomain}
          spellCheck={false}
          aria-describedby={hintId}
          onChange={(event) => onChange(event.currentTarget.value)}
          style={{ minWidth: 0, paddingInline: 'var(--spacing-1)' }}
        />
        <Text size={3} aria-hidden style={{ ...affixStyle, paddingInlineEnd: 'var(--spacing-6)' }}>.vega.app</Text>
      </TextField>
      <HelperText id={hintId} style={{ overflowWrap: 'anywhere' }}>
        Lowercase letters, digits and hyphens. Full address: https://{subdomain === '' ? '…' : subdomain}.vega.app
      </HelperText>
    </div>
  );
}
```

## Сравнение

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

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

### `TextField` и `TextArea`

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

- значение однострочное

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

- содержимое намеренно многострочное

### `TextField` и `PasswordField`

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

- вводится обычный текст

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

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

### `TextField` и `NumberField`

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

- значение — произвольный текст, даже если в нём есть цифры

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

- важны шаг, границы или числовые ограничения

### `TextField` и `PhoneField`

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

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

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

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

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

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

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

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

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

### Поле рендерится, но значение не попадает в отправку формы

#### Симптом

Обёртка видна, но в отправленной форме нет ожидаемого значения.

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

`name` или другие пропсы формы заданы на `TextField`, а не на `TextFieldInput`.

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

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

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

Задайте атрибуты формы на `TextFieldInput`.

### Стиль ошибки не отображается

#### Симптом

Инпут невалиден, но контур поля по-прежнему нейтральный.

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

Обёртка не получила `error`.

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

- изучите пропсы `TextField`
- убедитесь, что на обёртке есть `data-error='true'`

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

Задайте `error` на `TextField` и при необходимости сохраните семантику невалидности на инпуте.

### Содержимое префикса или суффикса мешает пользоваться полем

#### Симптом

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

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

Вокруг инпута добавлено слишком много элементов управления.

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

- временно уберите лишние дочерние элементы
- сравните взаимодействие с простым полем

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

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