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

## Паттерны

### Компактное действие на панели инструментов

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

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

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

- Делайте подписи явными через `aria-label`.
- Оставляйте вариант только с иконкой для привычных действий.

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

- Экономит место.
- Менее явно, чем текстовые кнопки.

```tsx
import { Icon, IconButton, Text } from '@vega-ui/react';
import { EllipsisIcon, PencilIcon, Share2Icon } from '@vega-ui/icons';
import { useState } from 'react';

export default function BasicUsage() {
  const [lastAction, setLastAction] = useState('');

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-2)', width: '100%' }}>
      <header style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-2)' }}>
        <Text size={4} fontWeight={600} style={{ flex: 1, minWidth: 0 }}>Brand guidelines</Text>
        <IconButton type='button' size='sm' variant='secondary' appearance='transparent' aria-label='Rename document' onClick={() => setLastAction('Rename')}>
          <Icon><PencilIcon /></Icon>
        </IconButton>
        <IconButton type='button' size='sm' variant='secondary' appearance='transparent' aria-label='Share document' onClick={() => setLastAction('Share')}>
          <Icon><Share2Icon /></Icon>
        </IconButton>
        <IconButton type='button' size='sm' variant='secondary' appearance='transparent' aria-label='More actions' onClick={() => setLastAction('More')}>
          <Icon><EllipsisIcon /></Icon>
        </IconButton>
      </header>
      <Text size={2} style={{ color: 'var(--label-secondary)' }}>
        {lastAction === '' ? 'Updated 2 hours ago' : `Clicked: ${lastAction}`}
      </Text>
    </div>
  );
}
```

### Встроенное действие сброса

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

- Небольшой локальный контрол очищает или удаляет соседнее состояние.

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

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

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

- Очень компактно.
- Легко злоупотребить в формах без достаточного контекста.

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

export default function InlineClearAction() {
  const input = useRef<HTMLInputElement>(null);
  const [query, setQuery] = useState('quarterly report');
  const [submitted, setSubmitted] = useState('');

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

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

  return (
    <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%' }}>
      <Label htmlFor='file-search'>Search files</Label>
      <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)' }}>
        <TextField style={{ flex: 1, minWidth: 0, paddingInlineEnd: 'var(--spacing-5)' }}>
          <TextFieldInput
            ref={input}
            id='file-search'
            name='q'
            value={query}
            onChange={(event) => setQuery(event.target.value)}
          />
          {query !== '' && (
            <IconButton
              type='button'
              variant='secondary'
              appearance='ghost'
              aria-label='Clear search'
              onClick={clear}
              style={{ flexShrink: 0, '--icon-button-size': '24px', '--icon-button-br-ratio': 0.5 } as CSSProperties}
            >
              <Icon size='xs'><XIcon strokeWidth={2.5} /></Icon>
            </IconButton>
          )}
        </TextField>
        <IconButton type='submit' aria-label='Search'>
          <Icon><SearchIcon /></Icon>
        </IconButton>
      </div>
      <output style={{ color: 'var(--label-secondary)', overflowWrap: 'anywhere' }}>
        {submitted === '' ? 'Press Enter or the search button' : `Searched for “${submitted}”`}
      </output>
    </form>
  );
}
```

### Навигационная поверхность с иконкой

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

- Навигации нужен вид иконки-кнопки с сохранением семантики ссылки.

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

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

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

- Сильная визуальная согласованность.
- Легко использовать неправильно для неоднозначной навигации.

```tsx
import { Icon, IconButton } from '@vega-ui/react';
import { BookOpenIcon, LifeBuoyIcon, RssIcon } from '@vega-ui/icons';

const links = [
  { href: '/docs', label: 'Documentation', icon: <BookOpenIcon /> },
  { href: '/support', label: 'Help center', icon: <LifeBuoyIcon /> },
  { href: '/blog/rss.xml', label: 'Blog RSS feed', icon: <RssIcon /> },
];

export default function NavigationalIconSurface() {
  return (
    <nav aria-label='Resources'>
      <ul style={{ display: 'flex', gap: 'var(--spacing-4)', margin: 0, padding: 0, listStyle: 'none' }}>
        {links.map((link) => (
          <li key={link.href}>
            <IconButton asChild variant='secondary' appearance='ghost'>
              <a href={link.href} aria-label={link.label}>
                <Icon>{link.icon}</Icon>
              </a>
            </IconButton>
          </li>
        ))}
      </ul>
    </nav>
  );
}
```

## Сравнение

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

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

### `IconButton` или `Button`

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

- места мало
- действие распространённое и узнаваемое

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

- видимый текст делает действие понятнее

Главный компромисс: иконка-кнопка плотнее, а обычная кнопка понятнее.

### `IconButton` или `Link`

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

- взаимодействие — это действие

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

- взаимодействие в первую очередь ведёт на другую страницу

Главный компромисс: у иконки-кнопки семантика действия, а у ссылки — семантика навигации.

### `IconButton` или `ButtonBase`

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

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

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

- строится низкоуровневая собственная поверхность действия с иконкой

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

- Действие строится вокруг иконки и хорошо понятно.
- Места мало.
- Интерфейсу всё равно нужна поверхность действия, похожая на кнопку.
- действие должно оставаться компактным, не превращаясь в общий низкоуровневый примитив

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

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

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

### Смысл действия неясен

#### Симптом

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

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

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

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

- Скройте окружающий контекст и проверьте, остаётся ли действие очевидным.
- Проверьте, имеет ли та же иконка устойчивый смысл в других местах продукта.

#### Решение

- Перейдите на `Button`, если видимый текст делает действие понятнее.
- Оставляйте вариант только с иконкой для привычных действий.

### Нет доступного имени

#### Симптом

Кнопка визуально работает, но у неё нет понятной доступной подписи.

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

Не задан `aria-label` или другой поддерживаемый способ подписи.

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

- Изучите отрендеренную разметку.
- Проверьте инструментами доступности или выводом скринридера.

#### Решение

- Задайте явную доступную подпись.

### Полиморфный IconButton ведёт себя странно

#### Симптом

Внешний вид иконки-кнопки рендерится, но дочерний элемент не поддерживает нужную семантику или пропсы.

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

`asChild` использован с несовместимым дочерним элементом.

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

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

#### Решение

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