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

## Паттерны

### Быстрые действия

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

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

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

- Держите содержимое компактным.
- Предпочитайте списки действий или короткие сгруппированные команды содержимому с длинными абзацами.
- Контролируйте `open` и закрывайте поповер, когда выполняется действие: сам он не закроется.

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

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

```tsx
import { useState } from 'react';
import {
  Button,
  Popover,
  PopoverContent,
  PopoverTrigger,
  Text,
} from '@vega-ui/react';

const actions = ['Duplicate', 'Move to folder', 'Download as PDF', 'Archive'];

export default function MenuClosesOnSelect() {
  const [open, setOpen] = useState(false);
  const [last, setLast] = useState('');

  const run = (action: string) => {
    setLast(action);
    setOpen(false);
  };

  return (
    <div style={{ display: 'grid', justifyItems: 'center', gap: 'var(--spacing-6)', width: '100%' }}>
      <Popover placement='bottom-start' open={open} onChangeOpen={setOpen}>
        <PopoverTrigger asChild>
          <Button variant='secondary' appearance='ghost'>Brand guidelines.fig</Button>
        </PopoverTrigger>
        <PopoverContent style={{ padding: 'var(--spacing-2)' }}>
          <div role='group' aria-label='File actions' style={{ display: 'grid', minWidth: 200 }}>
            {actions.map((action) => (
              <Button
                key={action}
                variant='secondary'
                appearance='transparent'
                style={{ justifyContent: 'flex-start' }}
                onClick={() => run(action)}
              >
                {action}
              </Button>
            ))}
          </div>
        </PopoverContent>
      </Popover>
      <Text size={2} style={{ minHeight: '1lh', color: 'var(--label-secondary)' }}>{last ? `Last action: ${last}` : ''}</Text>
    </div>
  );
}
```

### Переименование на месте

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

- Маленькая форма должна оставаться рядом со своим триггером.

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

- Делайте валидацию и отправку простыми.

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

- Эффективно для небольших правок.
- Слишком мало места для длинных форм.

```tsx
import { useId, useState, type CSSProperties, type FormEvent } from 'react';
import {
  Button,
  Heading,
  Icon,
  IconButton,
  Label,
  Popover,
  PopoverContent,
  PopoverTrigger,
  TextField,
  TextFieldInput,
} from '@vega-ui/react';
import { XIcon } from '@vega-ui/icons';

export default function InlineRenameFlow() {
  const titleId = useId();
  const fieldId = useId();
  const [open, setOpen] = useState(false);
  const [name, setName] = useState('Q4 launch plan');

  const onSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setName(String(new FormData(event.currentTarget).get('name')).trim());
    setOpen(false);
  };

  return (
    <Popover role='dialog' placement='bottom-start' open={open} onChangeOpen={setOpen}>
      <PopoverTrigger asChild>
        <Button variant='secondary' appearance='ghost'>{name}</Button>
      </PopoverTrigger>
      <PopoverContent aria-labelledby={titleId}>
        <form onSubmit={onSubmit} style={{ display: 'grid', width: 260 }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--spacing-8)' }}>
            <Heading id={titleId} as='h3' size={5} fontWeight={600}>Rename document</Heading>
            <IconButton
              aria-label='Close'
              variant='secondary'
              appearance='ghost'
              style={{ flexShrink: 0, '--icon-button-size': '24px', '--icon-button-br-ratio': 0.5 } as CSSProperties}
              onClick={() => setOpen(false)}
            >
              <Icon size='xs'><XIcon strokeWidth={2.5} /></Icon>
            </IconButton>
          </div>
          <div style={{ display: 'grid', gap: 'var(--spacing-3)', marginBlockStart: 'var(--spacing-6)' }}>
            <Label htmlFor={fieldId}>Name</Label>
            <TextField size='sm'>
              <TextFieldInput id={fieldId} name='name' defaultValue={name} required pattern='.*\S.*' maxLength={80} autoFocus />
            </TextField>
          </div>
          <Button type='submit' size='sm' style={{ justifySelf: 'end', marginBlockStart: 'var(--spacing-8)' }}>Rename</Button>
        </form>
      </PopoverContent>
    </Popover>
  );
}
```

### Привязанное меню пользователя

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

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

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

- Держите содержимое узким и легко просматриваемым.
- Не превращайте меню в полноценный экран настроек.

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

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

```tsx
import {
  Button,
  Popover,
  PopoverContent,
  PopoverTrigger,
  Separator,
  Text,
} from '@vega-ui/react';

export default function AnchoredUserMenu() {
  return (
    <Popover role='dialog' placement='bottom-end'>
      <PopoverTrigger asChild>
        <Button variant='secondary' appearance='ghost'>Maria Chen</Button>
      </PopoverTrigger>
      <PopoverContent aria-label='Account' style={{ padding: 'var(--spacing-2)' }}>
        <div style={{ display: 'grid', gap: 'var(--spacing-1)', minWidth: 220 }}>
          <div style={{ display: 'grid', paddingInline: 'var(--spacing-6)', paddingBlock: 'var(--spacing-4)' }}>
            <Text size={3} fontWeight={600}>Maria Chen</Text>
            <Text size={2} style={{ color: 'var(--label-secondary)' }}>maria@northwind.dev</Text>
          </div>
          <Separator />
          <Button asChild variant='secondary' appearance='transparent' style={{ justifyContent: 'flex-start' }}>
            <a href='#profile'>Profile</a>
          </Button>
          <Button asChild variant='secondary' appearance='transparent' style={{ justifyContent: 'flex-start' }}>
            <a href='#billing'>Billing</a>
          </Button>
          <Separator />
          <Button variant='secondary' appearance='transparent' style={{ justifyContent: 'flex-start' }}>Sign out</Button>
        </div>
      </PopoverContent>
    </Popover>
  );
}
```

## Сравнение

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

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

### `Popover` или `Tooltip`

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

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

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

- содержимое — исключительно дополнительная информация

### `Popover` или `Dialog`

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

- взаимодействие немодальное и лёгкое
- пользователь должен сохранять контекст страницы и не попадать в модальный сценарий с ловушкой фокуса

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

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

### `Popover` или `Drawer`

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

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

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

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

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

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

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

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

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

### Поповер кажется слишком тяжёлым

#### Симптом

Технически содержимое работает, но оверлей кажется слишком большим или модальным.

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

Задаче место в `Dialog` или `Drawer`.

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

- Оцените объём содержимого и критичность задачи.

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

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

### Расположение ломается в прокручиваемых контейнерах

#### Симптом

Плавающий слой выглядит оторванным от триггера.

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

Не протестированы реальный контейнер или стек оверлеев.

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

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

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

- Повторно протестируйте точную структуру контейнеров из продакшена.
- `PopoverContent` рендерится в верхнем слое, поэтому `overflow` и `z-index` у предков не обрезают и не перекрывают его; ищите проблему в расположении.

### Вложенные поповеры сбивают с толку

#### Симптом

С поповерами внутри поповеров состояние открытия и порядок фокуса становятся неясными.

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

Вложенные плавающие слои используются для задачи, которой нужен более строгий паттерн.

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

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

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

- Упростите сценарий или переключитесь на более крупный паттерн оверлея.

### Смысл триггера непонятен без поповера

#### Симптом

Пользователи понимают триггер только после открытия поповера.

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

Триггер опирается на плавающее содержимое как на основную подпись или объяснение.

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

- Скройте поповер и проверьте, понятен ли триггер сам по себе.
- Протестируйте сценарии с клавиатурой и скринридером.

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

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

### Поповер не открывается с собственного триггера

#### Симптом

Клик по собственному дочернему элементу `PopoverTrigger asChild` ничего не делает.

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

Дочерний элемент не рендерит `<button>`, поэтому нативный `popovertarget` не действует.

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

- Посмотрите элемент триггера и убедитесь, что это `<button>` с `popovertarget`.

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

- Используйте в качестве дочернего элемента `asChild` компонент `Button` или другой компонент, который рендерит `<button>`.
