useMenuItem для пунктов меню

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


Подключение и базовое использование

Для работы с useMenuItem необходимы React, React Aria, а также React Stately для управления состоянием меню:

import { useMenuItem } from '@react-aria/menu';
import { useMenuState } from '@react-stately/menu';
import { useFocusRing } from '@react-aria/focus';
import { useRef } from 'react';

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

function MenuItem({ item, state }) {
  const ref = useRef();
  const { menuItemProps, isFocused, isSelected } = useMenuItem(
    { key: item.key },
    state,
    ref
  );
  
  return (
    <div
      {...menuItemProps}
      ref={ref}
      style={{
        background: isFocused ? 'lightblue' : 'transparent',
        fontWeight: isSelected ? 'bold' : 'normal',
        padding: '4px 8px',
        cursor: 'pointer'
      }}
    >
      {item.name}
    </div>
  );
}

Разбор ключевых моментов:

  • menuItemProps — содержит все необходимые свойства для корректного взаимодействия элемента меню с клавиатурой, мышью и ассистивными технологиями. Их нужно распространять на корневой элемент пункта меню.
  • isFocused — указывает, что пункт меню находится в фокусе, что важно для визуальной подсветки.
  • isSelected — отображает, выбран ли пункт меню (для радио или чекбоксов внутри меню).

Параметры хука

useMenuItem принимает три аргумента:

  1. props — объект с настройками пункта меню:

    • key — уникальный идентификатор пункта меню (обязательный).
    • isDisabled — отключает пункт меню.
    • isSelected — задает состояние выбранности пункта.
    • onAction — callback, вызываемый при активации пункта.
  2. state — состояние меню, созданное с помощью useMenuState. Отвечает за управление активным пунктом, выбранными элементами и закрытием меню.

  3. ref — ссылка на DOM-элемент пункта меню. Используется для управления фокусом и взаимодействия с клавиатурой.

Пример передачи этих параметров:

const { menuItemProps, isFocused, isSelected } = useMenuItem(
  {
    key: 'save',
    isDisabled: false,
    onAction: () => console.log('Сохранение выполнено')
  },
  menuState,
  itemRef
);

Взаимодействие с клавиатурой

useMenuItem автоматически обрабатывает:

  • ArrowUp / ArrowDown — переход между пунктами меню.
  • Enter / Space — активация выбранного пункта.
  • Escape — закрытие меню.
  • Home / End — переход к первому или последнему пункту.

Для корректной работы необходимо, чтобы контейнер меню использовал useMenu из React Aria, а состояние управлялось через useMenuState.

import { useMenu } from '@react-aria/menu';

function Menu({ state }) {
  const ref = useRef();
  const { menuProps } = useMenu({}, state, ref);
  
  return (
    <ul {...menuProps} ref={ref}>
      {state.collection.map(item => (
        <MenuItem key={item.key} item={item} state={state} />
      ))}
    </ul>
  );
}

Доступность и семантика

useMenuItem обеспечивает правильные атрибуты для ассистивных технологий:

  • role="menuitem" — основной атрибут для элементов меню.
  • aria-disabled — если элемент отключен.
  • aria-selected — если пункт выбран.
  • tabIndex — управление фокусом при навигации.

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

const { isFocusVisible, focusProps } = useFocusRing();

<div
  {...menuItemProps}
  {...focusProps}
  ref={ref}
  style={{
    outline: isFocusVisible ? '2px solid blue' : 'none'
  }}
>
  {item.name}
</div>

Работа с подменю

Для вложенных меню useMenuItem поддерживает hasSubmenu и автоматически добавляет aria-haspopup и aria-expanded. Это позволяет создавать многоуровневые навигационные структуры без ручного управления семантикой.

<MenuItem
  key="file"
  item={{ key: 'file', name: 'Файл' }}
  state={menuState}
  hasSubmenu={true}
/>

Советы по оптимизации

  • Всегда использовать уникальный key для каждого пункта меню.
  • Использовать useRef для элементов меню, чтобы React Aria мог управлять фокусом.
  • При динамическом изменении меню обновлять состояние через useMenuState.
  • Для больших меню использовать виртуализацию элементов, чтобы улучшить производительность, сохраняя все возможности хука.

useMenuItem превращает обычный <div> или <li> в полностью доступный, интерактивный пункт меню, автоматически управляя фокусом, состояниями и навигацией, что делает интерфейсы с меню безопасными и удобными для всех пользователей.