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

## Паттерны

### Простая группа выбора

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

- Небольшой видимый набор взаимоисключающих вариантов находится в форме.

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

- Держите все связанные радиокнопки под одним `name`.
- Оборачивайте контрол и текст подписи вместе, чтобы кликабельной оставалась вся строка.

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

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

```tsx
import { Fieldset, FieldsetLegend, Radio } from '@vega-ui/react';

const methods = [
  { value: 'standard', label: 'Standard · 5–7 business days · Free' },
  { value: 'express', label: 'Express · 2–3 business days · $9.90' },
  { value: 'overnight', label: 'Overnight · next business day · $24.00' },
];

export default function LabeledRadioOption() {
  return (
    <Fieldset>
      <FieldsetLegend style={{ marginBlockEnd: 'var(--spacing-5)' }}>Shipping method</FieldsetLegend>
      <div style={{ display: 'grid', gap: 'var(--spacing-6)' }}>
        {methods.map((method) => (
          <label key={method.value} style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)' }}>
            <Radio name='shipping' value={method.value} defaultChecked={method.value === 'standard'} />
            {method.label}
          </label>
        ))}
      </div>
    </Fieldset>
  );
}
```

### Группа выбора в настройках

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

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

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

- Дайте группе окружающую подпись или контекст на уровне fieldset.
- Подписи вариантов должны описывать вариант, а не действие.

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

- Легко понять в формах.
- Менее компактно, чем `SegmentedControl`, когда все варианты должны читаться как соседние кнопки.

```tsx
import { Fieldset, FieldsetLegend, Radio, Text } from '@vega-ui/react';

const policies = [
  {
    value: 'anyone',
    label: 'Anyone with the link',
    description: 'People outside your organization can open shared documents without signing in.',
  },
  {
    value: 'organization',
    label: 'People in Acme Corp',
    description: 'Only signed-in members of your organization can open shared documents.',
  },
  {
    value: 'invited',
    label: 'Invited people only',
    description: 'Every document must be shared with each person by email. Existing links stop working.',
  },
];

export default function OptionsWithDescriptions() {
  return (
    <Fieldset style={{ width: '100%' }}>
      <FieldsetLegend style={{ marginBlockEnd: 'var(--spacing-5)' }}>Link sharing</FieldsetLegend>
      <div style={{ display: 'grid', gap: 'var(--spacing-8)' }}>
        {policies.map((policy) => (
          <div key={policy.value} style={{ display: 'flex', alignItems: 'flex-start', gap: 'var(--spacing-4)' }}>
            <Radio
              id={`sharing-${policy.value}`}
              name='sharing'
              value={policy.value}
              defaultChecked={policy.value === 'organization'}
              aria-describedby={`sharing-${policy.value}-description`}
              style={{ flexShrink: 0, marginBlockStart: 'var(--spacing-1)' }}
            />
            <div style={{ display: 'grid', gap: 'var(--spacing-1)' }}>
              <label htmlFor={`sharing-${policy.value}`}>{policy.label}</label>
              <Text id={`sharing-${policy.value}-description`} size={2} style={{ color: 'var(--label-secondary)' }}>
                {policy.description}
              </Text>
            </div>
          </div>
        ))}
      </div>
    </Fieldset>
  );
}
```

### Выбор радиокнопкой в оверлеях

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

- Оверлей запрашивает одно обязательное решение перед продолжением.

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

- Делайте список коротким и легко просматриваемым.
- Сопровождайте понятным текстом диалога или выдвижной панели, чтобы смысл группы был очевиден.

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

- Выбор остаётся явным.
- При большом числе вариантов может казаться многословным.

```tsx
import { CSSProperties, FormEvent, useState } from 'react';
import {
  Button,
  Dialog,
  DialogClose,
  DialogContent,
  DialogTrigger,
  Fieldset,
  FieldsetLegend,
  Heading,
  Icon,
  IconButton,
  Radio,
  Text,
} from '@vega-ui/react';
import { XIcon } from '@vega-ui/icons';

const environments = [
  { value: 'preview', label: 'Preview' },
  { value: 'staging', label: 'Staging' },
  { value: 'production', label: 'Production' },
];

export default function RadioChoiceInOverlays() {
  const [environment, setEnvironment] = useState('staging');

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    const data = new FormData(event.currentTarget);
    setEnvironment(String(data.get('environment')));
  };

  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--spacing-6)', width: '100%' }}>
      <Text size={3}>Deploy target: <strong>{environment}</strong></Text>
      <Dialog>
        <DialogTrigger asChild>
          <Button variant='secondary' appearance='ghost'>Change</Button>
        </DialogTrigger>
        <DialogContent aria-labelledby='deploy-target-title'>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--spacing-8)' }}>
            <Heading id='deploy-target-title' as='h2' size={4} fontWeight={600}>Deploy target</Heading>
            <DialogClose asChild>
              <IconButton
                aria-label='Close'
                variant='secondary'
                appearance='ghost'
                style={{ flexShrink: 0, '--icon-button-size': '24px', '--icon-button-br-ratio': 0.5 } as CSSProperties}
              >
                <Icon size='xs'><XIcon strokeWidth={2.5} /></Icon>
              </IconButton>
            </DialogClose>
          </div>
          <form method='dialog' onSubmit={handleSubmit} style={{ display: 'grid', gap: 'var(--spacing-12)', marginBlockStart: 'var(--spacing-8)' }}>
            <Fieldset>
              <FieldsetLegend style={{ marginBlockEnd: 'var(--spacing-5)' }}>Environment</FieldsetLegend>
              <div style={{ display: 'grid', gap: 'var(--spacing-6)' }}>
                {environments.map((item) => (
                  <label key={item.value} style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)' }}>
                    <Radio name='environment' value={item.value} defaultChecked={item.value === environment} />
                    {item.label}
                  </label>
                ))}
              </div>
            </Fieldset>
            <Button type='submit'>Save</Button>
          </form>
        </DialogContent>
      </Dialog>
    </div>
  );
}
```

## Сравнение

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

Используйте `Radio`, когда из небольшого видимого набора нужно выбрать ровно один вариант.

### `Radio` или `Checkbox`

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

- должен быть выбран ровно один вариант

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

- варианты можно выбирать независимо или в сочетании

Главный компромисс: радиокнопки обеспечивают взаимоисключаемость, а чекбоксы поддерживают независимый выбор.

### `Radio` или `Switch`

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

- пользователь выбирает один вариант из набора

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

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

Главный компромисс: радиокнопки сравнивают альтернативы, а переключатели включают или выключают одну настройку.

### `Radio` или `SegmentedControl`

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

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

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

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

Главный компромисс: радиокнопки проще и ближе к форме, а сегментированный контрол визуально нагляднее.

### `Radio` или `CheckboxCard`

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

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

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

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

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

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

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

- Варианты можно выбирать независимо.
- Контрол — одна бинарная настройка, применяемая сразу.
- Функции нужен более плотный, всегда видимый сегментированный UI.

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

### Можно отметить несколько радиокнопок сразу

#### Симптом

Выбранными выглядят несколько вариантов, хотя интерфейс должен разрешать только один.

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

У радиокнопок нет общего нативного `name`.

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

- Изучите отрендеренный атрибут `name`.
- Проверьте, используют ли все варианты группы одно и то же значение.

#### Решение

- Задайте всем связанным радиокнопкам одинаковый `name`.
- Разделяйте несвязанные группы разными именами.

### Кликабельным кажется только крошечный контрол

#### Симптом

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

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

Видимый текст не обёрнут в ту же кликабельную структуру `label`.

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

- Наведите курсор и кликните по тексту рядом с контролом.
- Убедитесь, что `label` оборачивает и радиокнопку, и текст.

#### Решение

- Используйте оборачивающий `label` для контрола и его текста.
- По возможности делайте кликабельной всю строку.

### Radio использован не для той модели выбора

#### Симптом

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

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

`Radio` выбран там, где модели продукта лучше соответствует `Checkbox` или `Switch`.

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

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

#### Решение

- Используйте `Checkbox` для независимых вариантов.
- Используйте `Switch` для одной бинарной настройки, применяемой сразу.
