ModalProvider и контекст

React Aria предоставляет мощные инструменты для работы с доступностью (accessibility) компонентов в React-приложениях. Одним из ключевых элементов для управления модальными окнами является ModalProvider. Он используется для координации поведения всех модальных элементов и обеспечения корректного взаимодействия с пользователем, включая фокус и клавиатурную навигацию.


ModalProvider и его роль

ModalProvider создаёт контекст, который обеспечивает глобальное управление состоянием модальных окон в приложении. Основные функции включают:

  • Управление фокусом: при открытии модального окна фокус автоматически переводится на первый интерактивный элемент внутри модального контента, предотвращая взаимодействие с остальными элементами страницы.
  • Блокировка фона: отключает возможность взаимодействия с контентом вне модального окна.
  • Множественные модальные окна: корректно управляет стеком модальных окон, обеспечивая правильный возврат фокуса при закрытии верхнего модального окна.

Пример базовой обёртки:

import { ModalProvider } from "@react-aria/overlays";

function App() {
  return (
    <ModalProvider>
      <MainComponent />
    </ModalProvider>
  );
}

Все модальные компоненты внутри ModalProvider автоматически используют созданный контекст для управления фокусом и aria-атрибутами.


Контекст модальных окон

ModalProvider создаёт внутренний React Context, который можно использовать для доступа к состоянию модальных окон:

  • isTopModal(ref) – проверяет, является ли текущее модальное окно верхним в стеке.
  • addModal(ref) и removeModal(ref) – регистрируют и снимают модальные окна со стека.

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

Пример проверки верхнего модального окна:

import { useModalProvider } from "@react-aria/overlays";

function MyModal({ ref }) {
  let { isTopModal } = useModalProvider();
  
  useEffect(() => {
    if (isTopModal(ref)) {
      console.log("Это верхнее модальное окно");
    }
  }, [ref, isTopModal]);
  
  return <div ref={ref}>Контент модального окна</div>;
}

Управление фокусом

Одной из важнейших задач ModalProvider является управление фокусом:

  1. При открытии модального окна фокус перемещается на первый доступный интерактивный элемент.
  2. При закрытии фокус возвращается на элемент, с которого было вызвано модальное окно.
  3. Все элементы вне модального окна получают aria-hidden и становятся недоступными для клавиатуры.

Используется комбинация useModal и useOverlay из @react-aria/overlays:

import { useOverlay, usePreventScroll } from "@react-aria/overlays";

function Modal({ isOpen, onClose, children }) {
  let ref = useRef();
  usePreventScroll();
  
  let { overlayProps } = useOverlay({ isOpen, onClose, isDismissable: true }, ref);

  return (
    isOpen && (
      <div {...overlayProps} ref={ref} role="dialog" aria-modal="true">
        {children}
      </div>
    )
  );
}

usePreventScroll автоматически блокирует прокрутку фона при открытом модальном окне, а useOverlay обеспечивает обработку клавиши Escape и клика вне окна для закрытия.


Стек модальных окон

Когда в приложении могут быть открыты несколько модальных окон одновременно, ModalProvider управляет стеком окон:

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

Пример реализации стека:

import { ModalProvider } from "@react-aria/overlays";

function App() {
  return (
    <ModalProvider>
      <ModalA />
      <ModalB />
    </ModalProvider>
  );
}

Здесь ModalB будет верхним окном, пока оно открыто, и только оно сможет обрабатывать события Escape и клики вне окна.


Интеграция с другими контекстами

ModalProvider хорошо комбинируется с другими контекстами, например FocusScope из @react-aria/focus:

  • FocusScope изолирует фокус внутри модального окна.
  • ModalProvider управляет глобальным стеком модальных окон.
  • Вместе они обеспечивают полностью доступный пользовательский опыт без конфликтов фокуса.

Пример использования FocusScope:

import { FocusScope } from "@react-aria/focus";

function ModalContent() {
  return (
    <FocusScope contain restoreFocus autoFocus>
      <button>Закрыть</button>
      <input placeholder="Введите текст" />
    </FocusScope>
  );
}

contain гарантирует, что фокус не выйдет за пределы модального окна, а restoreFocus вернёт его на элемент вызова после закрытия.


Ключевые моменты работы с ModalProvider

  • Всегда оборачивать корневой компонент приложения или раздел с модальными окнами в ModalProvider.
  • Для каждого модального окна использовать useOverlay и FocusScope для управления доступностью.
  • Стек модальных окон автоматически управляется, но можно получать информацию о верхнем модальном окне через контекст.
  • ModalProvider интегрируется с usePreventScroll, предотвращая прокрутку фонового контента.

Эта комбинация обеспечивает полностью доступные, безопасные и корректно работающие модальные окна в сложных React-приложениях.