Множественные меню

Radix UI предоставляет гибкую архитектуру для создания интерфейсных компонентов, в том числе меню. Множественные меню позволяют объединять несколько уровней вложенности или несколько связанных контекстных меню, сохраняя согласованное управление состоянием и взаимодействиями пользователя.


Структура компонентов

Основной строительный блок меню в Radix UI — это Menu, который состоит из следующих элементов:

  • Menu.Root — корневой контейнер меню, управляющий состоянием открытия и закрытия.
  • Menu.Trigger — элемент, который инициирует открытие меню (например, кнопка).
  • Menu.Content — контейнер для элементов меню.
  • Menu.Item — отдельный элемент меню.
  • Menu.Sub / Menu.SubTrigger / Menu.SubContent — элементы для вложенных подменю.
  • Menu.Separator — визуальный разделитель элементов меню.
  • Menu.Label — заголовок или подпись для группы элементов меню.

Для множественных меню особенно важны Menu.Sub и Menu.SubTrigger, так как они позволяют строить древовидную структуру.


Пример множественного меню

import * as Menu from '@radix-ui/react-menu';

function MultiLevelMenu() {
  return (
    <Menu.Root>
      <Menu.Trigger>Открыть меню</Menu.Trigger>
      <Menu.Content>
        <Menu.Item>Пункт 1</Menu.Item>
        <Menu.Sub>
          <Menu.SubTrigger>Пункт 2 (с подменю)</Menu.SubTrigger>
          <Menu.SubContent>
            <Menu.Item>Подпункт 2.1</Menu.Item>
            <Menu.Item>Подпункт 2.2</Menu.Item>
            <Menu.Sub>
              <Menu.SubTrigger>Подменю 2.3</Menu.SubTrigger>
              <Menu.SubContent>
                <Menu.Item>Подпункт 2.3.1</Menu.Item>
              </Menu.SubContent>
            </Menu.Sub>
          </Menu.SubContent>
        </Menu.Sub>
        <Menu.Item>Пункт 3</Menu.Item>
      </Menu.Content>
    </Menu.Root>
  );
}

В этом примере реализовано три уровня меню: основной, вложенный и дополнительное подменю внутри вложенного. Menu.Sub автоматически управляет позиционированием и открытием вложенного меню при наведении или фокусе.


Управление состоянием

Radix UI использует контролируемый и неконтролируемый подход к состоянию компонентов:

  • Неконтролируемый режимMenu.Root сам управляет открытием и закрытием, на основе взаимодействия с Menu.Trigger.
  • Контролируемый режим — с помощью свойства open и обработчика onOpenChange можно синхронизировать состояние меню с внешним состоянием приложения.
<Menu.Root open={isOpen} onOpenCha nge={setIsOpen}>
  <Menu.Trigger>Меню</Menu.Trigger>
  <Menu.Content>
    <Menu.Item onSel ect={() => console.log("Выбрано")}>Пункт</Menu.Item>
  </Menu.Content>
</Menu.Root>

Контролируемый режим особенно полезен, когда несколько меню должны взаимодействовать друг с другом: например, закрывать все подменю при закрытии основного меню.


Позиционирование и анимации

  • align и side — позволяют задать расположение подменю относительно триггера (start, center, end для выравнивания и top, bottom, left, right для стороны).
  • sideOffset — отступ подменю от триггера.
  • Анимации — через CSS можно задать плавное появление и скрытие меню. Radix добавляет классы состояния, такие как data-state="open" и data-state="closed", для управления анимациями через @keyframes или transition.
[data-state="open"] {
  animation: fadeIn 200ms ease-out;
}

[data-state="closed"] {
  animation: fadeOut 150ms ease-in;
}

Работа с клавиатурой и доступность

Radix UI автоматически обеспечивает:

  • Навигацию стрелками вверх/вниз между элементами.
  • Открытие подменю через стрелку вправо и закрытие через стрелку влево.
  • Закрытие меню по Escape.
  • Поддержку фокуса и скринридеров с использованием WAI-ARIA атрибутов.

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


Динамические меню

Меню может строиться на основе данных:

const menuItems = [
  { label: "Файл" },
  { 
    label: "Правка", 
    subItems: [
      { label: "Копировать" },
      { label: "Вставить" },
    ]
  },
  { label: "Вид" }
];

function DynamicMenu() {
  return (
    <Menu.Root>
      <Menu.Trigger>Меню</Menu.Trigger>
      <Menu.Content>
        {menuItems.map((item, idx) => item.subItems ? (
          <Menu.Sub key={idx}>
            <Menu.SubTrigger>{item.label}</Menu.SubTrigger>
            <Menu.SubContent>
              {item.subItems.map((sub, subIdx) => (
                <Menu.Item key={subIdx}>{sub.label}</Menu.Item>
              ))}
            </Menu.SubContent>
          </Menu.Sub>
        ) : (
          <Menu.Item key={idx}>{item.label}</Menu.Item>
        ))}
      </Menu.Content>
    </Menu.Root>
  );
}

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


Важные моменты при множественных меню

  • Закрытие всех подменю при закрытии корневого — необходимо использовать контролируемое состояние или слушатели событий onOpenChange.
  • Избегать наложения меню — с помощью sideOffset и align можно управлять положением подменю, чтобы они не выходили за пределы окна.
  • Композиция с другими компонентами Radix — меню можно использовать совместно с Popover или Dialog, если нужно более сложное поведение.

Множественные меню в Radix UI обеспечивают гибкость, масштабируемость и высокую доступность интерфейсов, сохраняя простоту управления состоянием и поведением. Их архитектура позволяет строить глубоко вложенные структуры с контролируемой логикой и адаптивным позиционированием.