Закрытие меню

В React Aria меню управляется через контролируемое состояние, которое отделено от визуального отображения компонентов. Основная идея — использовать useMenuTriggerState для управления открытием и закрытием меню, что позволяет интегрировать клавиатурные и мышиные события без необходимости ручного отслеживания каждого события.

import { useMenuTriggerState } from "@react-stately/menu";

let state = useMenuTriggerState({});

Ключевые свойства состояния:

  • isOpen — булево значение, указывающее, открыто меню или закрыто.
  • open() — функция для открытия меню.
  • close() — функция для закрытия меню.
  • toggle() — переключение состояния меню.

Это состояние легко комбинируется с обработчиками событий на кнопке-триггере меню.


Обработка закрытия через кнопку и внешние события

Для стандартной кнопки-триггера используется хук useMenuTrigger из React Aria. Он предоставляет необходимые атрибуты для кнопки и связанного меню:

import { useMenuTrigger } from "@react-aria/menu";

let { menuTriggerProps, menuProps } = useMenuTrigger({ type: 'menu' }, state);

Особенности работы с закрытием:

  • Клик по кнопке-триггеру повторно закрывает меню.
  • Клик вне меню автоматически закрывает его благодаря встроенной обработке фокуса и useOverlay.
  • Esc-клавиша закрывает меню независимо от выбранного пункта.

Для корректной работы важно передавать menuProps на компонент меню, чтобы React Aria мог корректно обработать события закрытия.


Использование useOverlay для управления закрытием

useOverlay обеспечивает универсальное управление закрытием модальных элементов, включая меню. При интеграции с меню это позволяет реагировать на:

  • Escape для закрытия.
  • Клик за пределами меню.
  • Потерю фокуса.

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

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

let { overlayProps } = useOverlay(
  {
    onClose: state.close,
    isOpen: state.isOpen,
    isDismissable: true
  },
  menuRef
);

overlayProps следует передавать на корневой элемент меню для корректной обработки закрытия.


Управление закрытием при выборе элемента

При выборе пункта меню часто требуется автоматически закрывать меню. React Aria обеспечивает это через вызов state.close() внутри обработчика выбора:

function MenuItem({ item }) {
  let onSel ect = () => {
    console.log(`Выбран элемент: ${item.name}`);
    state.close();
  };

  return <div onCl ick={onSelect}>{item.name}</div>;
}

Для комплексных меню с вложенными подменю закрытие может быть частичным — закрываются только дочерние подменю, в то время как родительское остаётся открытым.


Поддержка клавиатуры для закрытия меню

React Aria автоматически обеспечивает закрытие меню через клавиши:

  • Escape — закрывает текущее меню или подменю.
  • Tab / Shift+Tab — перемещение фокуса с закрытием меню при выходе из зоны меню.
  • ArrowLeft / ArrowRight — для вложенных подменю, где закрытие подменю происходит при навигации назад.

Ключевое преимущество React Aria — это автоматическое связывание этих действий с состоянием меню через useMenuTriggerState, без необходимости писать отдельную логику для каждого события клавиатуры.


Пример полной интеграции закрытия меню

import React, { useRef } from "react";
import { useMenuTriggerState } from "@react-stately/menu";
import { useMenuTrigger, useMenu } from "@react-aria/menu";
import { useOverlay } from "@react-aria/overlays";

function DropdownMenu({ items }) {
  let state = useMenuTriggerState({});
  let triggerRef = useRef();
  let menuRef = useRef();

  let { menuTriggerProps, menuProps } = useMenuTrigger({ type: "menu" }, state);
  let { overlayProps } = useOverlay(
    { onClose: state.close, isOpen: state.isOpen, isDismissable: true },
    menuRef
  );

  return (
    <>
      <button {...menuTriggerProps} ref={triggerRef}>Меню</button>
      {state.isOpen && (
        <ul {...menuProps} {...overlayProps} ref={menuRef}>
          {items.map(item => (
            <li key={item.key} onCl ick={() => state.close()}>
              {item.name}
            </li>
          ))}
        </ul>
      )}
    </>
  );
}

Особенности:

  • Состояние открытия/закрытия полностью контролируется state.
  • Закрытие при выборе элемента и при клике вне меню обрабатывается единым механизмом через useOverlay.
  • Поддержка клавиатуры и фокуса встроена автоматически.

Советы по стабильному закрытию меню

  • Использовать isDismissable: true в useOverlay для корректного закрытия при кликах вне меню.
  • Вложенные меню закрывать только дочерние подменю при выборе, чтобы пользователь не терял контекст.
  • Для динамически рендеримых элементов меню обновлять menuProps и overlayProps после изменения списка элементов.
  • Не вручную управлять видимостью меню через CSS display; использовать state.isOpen для рендеринга и скрытия компонентов.

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