﻿---
description: Компонент реализующий модальную карточку.
---

<Overview group="modals">

# ModalCard [tag:component]

Компонент реализующий модальную карточку. Позволяет показать небольшой дополнительный контент,
удерживая пользователя в текущем контексте страницы/приложения.

</Overview>

<Playground hide>

```jsx
<PlatformProvider value="vkcom">
  <ModalCard
    open
    onClose={() => {}}
    icon={<Icon56NotificationOutline />}
    disableModalOverlay
    title="Приложение запрашивает разрешение на отправку Вам уведомлений"
    actions={
      <React.Fragment>
        <Spacing size={16} />
        <ButtonGroup gap="m" stretched>
          <Button key="deny" size="l" mode="secondary" stretched>
            Запретить
          </Button>
          <Button key="allow" size="l" mode="primary" stretched>
            Разрешить
          </Button>
        </ButtonGroup>
      </React.Fragment>
    }
  />
</PlatformProvider>
```

</Playground>

{/* @example-description: Базовая `ModalCard` с управлением через `open/onClose` и действиями подтверждения. */}
<Playground>

```jsx
const [open, setOpen] = React.useState(false);

return (
  <>
    <Button onClick={() => setOpen(true)}>Открыть</Button>
    <ModalCard
      open={open}
      onClose={() => setOpen(false)}
      icon={<Icon56NotificationOutline />}
      title="Приложение запрашивает разрешение на отправку Вам уведомлений"
      actions={
        <React.Fragment>
          <Spacing size={16} />
          <ButtonGroup gap="m" stretched>
            <Button key="deny" size="l" mode="secondary" stretched onClick={() => setOpen(false)}>
              Запретить
            </Button>
            <Button key="allow" size="l" mode="primary" stretched onClick={() => setOpen(false)}>
              Разрешить
            </Button>
          </ButtonGroup>
        </React.Fragment>
      }
    />
  </>
);
```

</Playground>

## Область применения

- показ уведомления (например, о новом функционале);
- отображение формы ввода (например, ввод СМС-кода).

## Анатомия

Вёрстку и параметры `ModalCard` наследует от [ModalCardBase](/components/modal-card-base). Также используется компоновка из следующих внутренних
компонентов:

- `ModalOutlet` – обёртка с фиксированной позицией для перекрытия других элементов в **DOM**;
- `ModalOverlay` – интерактивный задний фон;
- `ModalCardInternal` – инкапсулирует в себе всю логику поведения.

## Поведение

Параметр `open` задает состояние показа/скрытия `ModalCard`. По умолчанию компонент скрыт.

При состоянии `open={false}` компонент размонтируется из DOM. Чтобы отключить это поведение, необходимо задать параметр
`keepMounted`.

Смена состояния сопровождается анимацией содержимого `ModalCard`, а также `ModalOverlay`. За сменой состояния можно следить
через события:

- `onOpen`
- `onOpened`
- `onClose` – передаёт в аргумент причину закрытия (см. описание в таблице с API)
- `onClosed`

Отличие `onOpened`/`onClosed` от `onOpen`/`onClose` в том, что они вызываются после завершения анимации.

Далее поведение зависит от разрешения экрана.

### Настольный режим

При разрешении `>= 768px` компонент `ModalCard` имеет вид **диалогового окна**.

При `open={true}`:

- навигация через `Tab` и `Shift` + `Tab` будет зациклена на содержимом `ModalCard`.

По умолчанию, пользователь может закрыть **диалоговое окно** с помощью:

- нажатия на кнопку закрытия, что вызовет событие `onClose` с аргументом `click-close-button`;
- нажатия на `ModalOverlay`, что вызовет событие `onClose` с аргументом `click-overlay`;
- нажатия на `Esc`, что вызовет событие `onClose` с аргументом `escape-key`.

### Мобильный режим

При разрешении `<= 767px` компонент `ModalCard` имеет вид панели, выдвигающейся снизу вверх (далее **bottom card**).

При `open={true}` навигация через `Tab` и `Shift` + `Tab` будет зациклена на содержимом `ModalCard` (допустимо, т.к. к устройству
может быть подключена клавиатура).

> При открытии модального окна поверх другого модального окна может возникнуть конфликт в управлении фокусом.
> По умолчанию каждое модальное окно пытается удерживать фокус внутри себя, что может привести к некорректной работе при наличии нескольких открытых модальных окон.
>
> Чтобы избежать этой проблемы, для первого (нижнего) модального окна необходимо отключить захват фокуса с помощью свойства disableFocusTrap.
> Это позволит корректно управлять фокусом во втором (верхнем) модальном окне.

По умолчанию, пользователь может закрыть **bottom card** с помощью:

- нажатия на `ModalOverlay`, что вызовет событие `onClose` с аргументом `click-overlay`;
- нажатия на `Esc`, что вызовет событие `onClose` с аргументом `escape-key` (допустимо, т.к. к устройству может быть подключена
  клавиатура);
- определенное взаимодействие через тач или указатель мыши, что вызовет событие `onClose` с аргументом `swipe-down`
  (см. **Взаимодействие через тач или указатель мыши**)

#### Взаимодействие через тач или указатель мыши

> `<ConfigProvider platform="vkcom" />`
>
> При `platform === 'vkcom` описанный в этом разделе функционал полностью игнорируется, т.к. по историческим причинам
> `platform="vkcom"` форсирует условие, что сейчас используется десктопный режим. Высота **bottom card** будет определяться за счёт
> контента.

Перетаскивание **bottom card** изменяет прозрачность `ModalOverlay` – при перетаскивание вниз фон становится светлее.

Пользователь через перетаскивание может вызвать закрытие **bottom card** по следующим условиям:

- `onClose('swipe-down')` вызовется сразу же если **bottom card** достиг высоты `0`, независимо закончил ли пользователь
  взаимодействие с ним или нет;
- `onClose('swipe-down')` вызовется если после окончания взаимодействия с **bottom card** его высота будет меньше половины контента;
- `onClose('swipe-down')` вызовется при сильном смахивании вниз;

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

- есть взаимодействие с горизонтальной полосой прокрутки;
- есть взаимодействие с текстовым полем;
- есть взаимодействие с выделенным текстом;
- есть взаимодействие с элементом вызвавшим `event.stopPropagation()` на `onTouchStart` или `onMouseDown`;
- есть взаимодействие с элементом с **data**-атрибутом `data-vkui-block-sheet-behavior`;

## Хук `useModalManager`

Для более удобного управления модальными окнами вы можете использовать менеджер в виде хука [`useModalManager`](/components/use-modal-manager)

## Доступность (a11y)

`ModalCard` является модальным окном (`role="dialog"`), а значит у него обязательно должно быть имя — его краткое название. Благодаря этому пользователи вспомогательных технологий знают, что это за элемент и какое у него содержимое.

Задать имя можно с помощью следующих способов:

- используя свойство `title`. Компонент в котором отрендерится `title` будет связан с модальным окном через аттрибут `aria-labelledby`;
- используя свойство `aria-label`;
- используя свойство `aria-labelledby`;
- используя внутри компонент [ModalPageHeader](/components/modal-page-header). Этот компонент сам свяжется с `ModalCard` через контекст c помощью `aria-labelledby`.

## FAQ

### Как правильно применять параметр `autoFocus`?

К сожалению, из-за особенностей **React**, `autoFocus` ломает CSS анимацию.

Чтобы исправить проблему, следует отказаться от `autoFocus` и выставлять фокус вручную при событии `onOpened`.

{/* @example-description: Ручная установка фокуса в `ModalCard` после открытия через обработчик `onOpened`. */}
<Playground>

```jsx
const [open, setOpen] = React.useState(false);

const inputRef = React.useRef(null);

const handleOpen = React.useCallback(() => {
  if (inputRef.current) {
    inputRef.current.focus();
  }
}, []);

return (
  <>
    <Button onClick={() => setOpen(true)}>Открыть</Button>
    <ModalCard
      id="modal-with-auto-focus"
      title="Введите ПИН-код"
      open={open}
      onOpened={handleOpen}
      onClose={() => setOpen(false)}
    >
      <Spacing size="xl" />
      <Flex direction="row" gap={24} noWrap>
        <Input type="number" min={0} max={9} pattern="[0-9]{2}" getRootRef={inputRef} />
        <Input type="number" min={0} max={9} pattern="[0-9]{2}" />
        <Input type="number" min={0} max={9} pattern="[0-9]{2}" />
        <Input type="number" min={0} max={9} pattern="[0-9]{2}" />
      </Flex>
    </ModalCard>
  </>
);
```

</Playground>

## Свойства и методы [#api]

| Свойство | Тип | По умолчанию | Описание |
| --- | --- | --- | --- |
| `actions` | `ReactNode` | `-` | Кнопки-действия. Принимает [`Button`](https://vkui.io/components/button) с параметрами:  - `size="l" mode="primary" stretched` - `size="l" mode="secondary" stretched`.  Для набора кнопок используйте [`ButtonGroup`](https://vkui.io/components/button-group) с параметрами:  - `gap="s" mode="horizontal" stretched` - `gap="m" mode="vertical" stretched`. |
| `description` | `ReactNode` | `-` | Описание. |
| `descriptionComponent` | `ElementType<any, keyof IntrinsicElements>` | `-` | Позволяет поменять тег используемый для описания. |
| `disableCloseAnimation` | `boolean` | `-` | Отключает анимацию закрытия модалки. |
| `disableFocusTrap` | `boolean` | `-` | Позволяет отключить захват фокуса.  Нужно использовать, когда поверх одной модалки открывается другая, чтобы два `FocusTrap` не конфликтовали. |
| `disableModalOverlay` | `boolean` | `-` | Отключает отображение и взаимодействие с фоном модалки. |
| `disableOpenAnimation` | `boolean` | `-` | Отключает анимацию открытия модалки. |
| `dismissButtonMode` | `"none" \| "inside" \| "outside"` | `-` | Расположение кнопки закрытия (внутри и вне `popout'a`).  Доступно только в `compact`-режиме.  На `iOS` в `regular`-режиме всегда включен `inside`.  ⚠️ ВНИМАНИЕ: использование `none` скрывает крестик, это негативно сказывается на пользовательском опыте. |
| `dismissLabel` | `string` | `-` | Текст кнопки закрытия. Делает ее доступной для ассистивных технологий. |
| `getRootRef` | `Ref<HTMLDivElement>` | `-` |  |
| `icon` | `ReactNode` | `-` | Иконка.  Может быть компонентом иконки, например, `<Icon56MoneyTransferOutline />`, или `<Avatar size={72} src="" />`. |
| `id` | `string` | `-` |  |
| `keepMounted` | `boolean` | `false` | Сохранять ли компонент в DOM при `open={false}`. |
| `modalDismissButtonTestId` | `string` | `-` | Передает атрибут `data-testid` для кнопки закрытия. |
| `modalOverlayTestId` | `string` | `-` | `data-testid` для оверлея. |
| `nav` | `string` | `-` | Уникальный идентификатор навигационного элемента (вместо id) |
| `noFocusToDialog` | `boolean` | `-` | Отключает фокус на интерактивный элемент после открытия модалки.  > Важно установить фокус после открытия в любое место модалки используя событие `onOpened`, иначе не будет работать закрытие по нажатию `Esc`. |
| `onClose` | `((reason: ModalCardCloseReason, event?: UIEvent<HTMLElement, UIEvent>) => void) \| undefined` | `-` | Будет вызвано при начале закрытия модалки. |
| `onClosed` | `VoidFunction` | `-` | Будет вызвано при окончательном закрытии модалки. |
| `onOpen` | `VoidFunction` | `-` | Будет вызвано при начале открытия модалки. |
| `onOpened` | `VoidFunction` | `-` | Будет вызвано при окончательном открытии модалки. |
| `open` | `boolean` | `false` | Состояние видимости. |
| `outsideButtons` | `ReactNode` | `-` | Управляющие элементы под кнопкой закрытия.  Доступно только в `compact`-режиме. Рекомендуется размещать иконки размера 20, обернутые в ModalOutsideButton. |
| `preventClose` | `boolean` | `-` | Позволяет отключить возможность закрытия модальной страницы (смахивание, клавиша `ESC`, нажатие на подложку).  ⚠️ ВНИМАНИЕ: использование этой опции негативно сказывается на пользовательском опыте. |
| `restoreFocus` | `boolean \| (() => boolean \| HTMLElement)` | `true` | Управление поведением возврата фокуса при закрытии всплывающего окна. |
| `size` | `number` | `-` | Задаёт контенту максимальную ширину для десктопной версии. |
| `title` | `ReactNode` | `-` | Заголовок карточки. |
| `titleComponent` | `ElementType<any, keyof IntrinsicElements>` | `-` | Позволяет поменять тег используемый для заголовка. |
| `titleId` | `string` | `-` | Позволяет задать id для заголовка. Используется, чтобы связать модальное окно и title через aria-labelledby, тем самым задав модальному окну имя через title. |

