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

## Паттерны

### Встроенное сообщение обратной связи

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

- Локальному сценарию нужна короткая видимая обратная связь.

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

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

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

- Сильная семантическая обратная связь.
- Плохо подходит для длинных объяснений.

```tsx
import { FormEvent, useState } from 'react';
import {
  Alert,
  AlertContent,
  AlertIcon,
  AlertMain,
  AlertTitle,
  Button,
  Checkbox,
  CheckboxCheckedIcon,
  CheckboxIndicator,
  CheckboxInput,
  Label,
  TextField,
  TextFieldInput,
} from '@vega-ui/react';

type Status = 'idle' | 'saving' | 'saved' | 'failed';

export default function SaveFeedback() {
  const [status, setStatus] = useState<Status>('idle');
  const [savedName, setSavedName] = useState('');

  const submit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    const name = String(data.get('workspace'));
    const offline = data.get('offline') === 'on';
    setStatus('saving');
    setTimeout(() => {
      if (offline) {
        setStatus('failed');
        return;
      }
      setSavedName(name);
      setStatus('saved');
    }, 800);
  };

  return (
    <form onSubmit={submit} style={{ display: 'grid', gap: 'var(--spacing-12)', width: '100%', maxWidth: 420 }}>
      <div style={{ display: 'grid', gap: 'var(--spacing-8)' }}>
        <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
          <Label htmlFor='workspace-name'>Workspace name</Label>
          <TextField>
            <TextFieldInput id='workspace-name' name='workspace' defaultValue='Northwind Studio' required />
          </TextField>
        </div>
        <Checkbox style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)' }}>
          <CheckboxInput name='offline' />
          <CheckboxIndicator>
            <CheckboxCheckedIcon />
          </CheckboxIndicator>
          Simulate a network error
        </Checkbox>
      </div>
      <Button type='submit' disabled={status === 'saving'} style={{ justifySelf: 'start' }}>
        {status === 'saving' ? 'Saving…' : 'Save'}
      </Button>
      <div style={{ display: 'grid' }}>
        <div role='status'>
          {status === 'saved' && (
            <Alert variant='success'>
              <AlertIcon />
              <AlertMain>
                <AlertContent>Workspace renamed to “{savedName}”.</AlertContent>
              </AlertMain>
            </Alert>
          )}
        </div>
        {status === 'failed' && (
          <Alert variant='error' role='alert'>
            <AlertIcon />
            <AlertMain>
              <AlertTitle>Changes not saved</AlertTitle>
              <AlertContent>We could not reach the server. Check your connection and save again.</AlertContent>
            </AlertMain>
          </Alert>
        )}
      </div>
    </form>
  );
}
```

### Предупреждение в настройках

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

- Странице или панели настроек нужно одно видимое предостережение или предварительное условие.

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

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

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

- Хорошая локальная заметность.
- Создаёт шум, если в каждом разделе по несколько алертов.

```tsx
import { useState } from 'react';
import { Alert, AlertContent, AlertIcon, AlertMain, AlertTitle, Button, Text } from '@vega-ui/react';

const record = 'vega-site-verification=8c1f0b6e2d9a4f7b93e5c2a1d0f4e6b8a7c3d9e1f2b4a6c8d0e2f4a6b8c0d2e4';

export default function DomainVerification() {
  const [message, setMessage] = useState('');

  const copy = async () => {
    await navigator.clipboard.writeText(record);
    setMessage('Copied to clipboard.');
  };

  const check = () => {
    setMessage('Checking DNS…');
    setTimeout(() => setMessage('Record not found yet. Try again in a few minutes.'), 1000);
  };

  return (
    <Alert variant='warning' style={{ width: '100%', maxWidth: 560 }}>
      <AlertIcon />
      <AlertMain style={{ minWidth: 0, gap: 'var(--spacing-4)' }}>
        <AlertTitle style={{ overflowWrap: 'anywhere' }}>Verify northwind-logistics-international.example.com</AlertTitle>
        <AlertContent>Add this TXT record at your DNS provider. Changes can take up to 48 hours to apply.</AlertContent>
        <Text size={1} asChild>
          <code style={{ display: 'block', fontFamily: 'var(--code-font-family)', wordBreak: 'break-all', padding: 'var(--spacing-4) var(--spacing-6)', borderRadius: 'var(--radius-2)', background: 'var(--fills-tertiary)', color: 'var(--label-primary)' }}>{record}</code>
        </Text>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-4)', alignItems: 'center', marginBlockStart: 'var(--spacing-2)' }}>
          <Button size='sm' onClick={copy}>Copy record</Button>
          <Button size='sm' variant='secondary' appearance='ghost' onClick={check}>Check again</Button>
          <Text size={2} role='status' style={{ color: 'var(--label-secondary)' }}>{message}</Text>
        </div>
      </AlertMain>
    </Alert>
  );
}
```

### Закрываемая обратная связь

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

- Алерт можно убрать после того, как пользователь с ним ознакомился.

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

- Закрытием должно управлять состояние на стороне потребителя.
- Действие закрытия должно быть вторичным по отношению к самому сообщению.

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

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

```tsx
import { useRef, useState } from 'react';
import type { CSSProperties } from 'react';
import { Alert, AlertContent, AlertIcon, AlertMain, AlertTitle, Button, Heading, Icon, IconButton } from '@vega-ui/react';
import { XIcon } from '@vega-ui/icons';

export default function Dismissible() {
  const [visible, setVisible] = useState(true);
  const headingRef = useRef<HTMLHeadingElement>(null);

  const dismiss = () => {
    setVisible(false);
    headingRef.current?.focus();
  };

  return (
    <section aria-labelledby='billing-heading' style={{ display: 'grid', gap: 'var(--spacing-6)', width: '100%', maxWidth: 560 }}>
      <Heading ref={headingRef} id='billing-heading' as='h2' size={4} fontWeight={600} tabIndex={-1}>Billing</Heading>
      {visible && (
        <Alert variant='info'>
          <AlertIcon />
          <AlertMain>
            <AlertTitle>New invoice layout</AlertTitle>
            <AlertContent>Invoices now group usage by project. Old invoices keep their original layout.</AlertContent>
          </AlertMain>
          <IconButton
            aria-label='Dismiss'
            variant='secondary'
            appearance='ghost'
            onClick={dismiss}
            style={{ '--icon-button-size': '24px', '--icon-button-br-ratio': 0.5 } as CSSProperties}
          >
            <Icon size='xs'><XIcon strokeWidth={2.5} /></Icon>
          </IconButton>
        </Alert>
      )}
      {!visible && (
        <Button size='sm' variant='secondary' appearance='ghost' onClick={() => setVisible(true)} style={{ justifySelf: 'start' }}>
          Show the announcement again
        </Button>
      )}
    </section>
  );
}
```

## Сравнение

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

Используйте `Alert` для коротких семантических сообщений обратной связи.

### `Alert` и `Badge`

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

- интерфейсу нужно короткое сообщение с иерархией

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

- интерфейсу нужна лишь маленькая метка статуса

Главный компромисс: алерт — это паттерн сообщения, а бейдж — компактные метаданные.

### `Alert` и `Card`

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

- содержимое — это обратная связь или статус

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

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

Главный компромисс: алерт ориентирован на обратную связь, а карточка — это поверхность-контейнер.

### `Alert` и `Link`

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

- интерфейс сообщает о статусе или даёт обратную связь

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

- текст предназначен для навигации, а не для объяснения статуса

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

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

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

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

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

### Алерт слишком велик для сообщения

#### Симптом

Текст настолько короткий, что алерт выглядит визуально тяжёлым.

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

`Alert` используется там, где хватило бы меньшего `Badge` или встроенной текстовой подсказки.

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

- Проверьте, не является ли содержимое лишь маленькой меткой статуса.

#### Решение

- Перейдите на `Badge` или более простой встроенный текст, где это уместно.

### Алерт выполняет работу макета вместо обратной связи

#### Симптом

Компонент начинает вести себя как постоянный блок контента, а не как сообщение обратной связи.

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

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

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

- Уберите семантическое обрамление сообщения и посмотрите, не является ли поверхность просто контейнером макета.

#### Решение

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