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

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

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

Структура подменю

Подменю строится по иерархии:

  • DropdownMenu.Root – корневой компонент, обеспечивающий управление состоянием.
  • DropdownMenu.Trigger – кнопка или другой элемент, открывающий меню.
  • DropdownMenu.Content – контейнер, в котором располагаются элементы меню.
  • DropdownMenu.Item – отдельный пункт меню.
  • DropdownMenu.Sub – обёртка для подменю, если оно вложенное.
  • DropdownMenu.SubTrigger – элемент, открывающий подменю.
  • DropdownMenu.SubContent – контент подменю.

Простейший пример подменю выглядит так:

<DropdownMenu.Root>
  <DropdownMenu.Trigger>Файл</DropdownMenu.Trigger>
  <DropdownMenu.Content>
    <DropdownMenu.Item>Создать</DropdownMenu.Item>
    <DropdownMenu.Item>Открыть</DropdownMenu.Item>
    <DropdownMenu.Sub>
      <DropdownMenu.SubTrigger>Экспорт</DropdownMenu.SubTrigger>
      <DropdownMenu.SubContent>
        <DropdownMenu.Item>PDF</DropdownMenu.Item>
        <DropdownMenu.Item>SVG</DropdownMenu.Item>
      </DropdownMenu.SubContent>
    </DropdownMenu.Sub>
  </DropdownMenu.Content>
</DropdownMenu.Root>

Здесь DropdownMenu.Sub позволяет вложить подменю внутрь основного меню. Важно правильно использовать SubTrigger и SubContent, чтобы обеспечить корректное отображение вложенной структуры и правильную работу с клавиатурой.


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

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

const [open, setOpen] = useState(false);

<DropdownMenu.Root open={open} onOpenCha nge={setOpen}>
  <DropdownMenu.Trigger>Меню</DropdownMenu.Trigger>
  <DropdownMenu.Content>
    <DropdownMenu.Item>Пункт 1</DropdownMenu.Item>
    <DropdownMenu.Sub>
      <DropdownMenu.SubTrigger>Подменю</DropdownMenu.SubTrigger>
      <DropdownMenu.SubContent>
        <DropdownMenu.Item>Подпункт A</DropdownMenu.Item>
      </DropdownMenu.SubContent>
    </DropdownMenu.Sub>
  </DropdownMenu.Content>
</DropdownMenu.Root>

Такой подход позволяет интегрировать подменю с внешними состояниями приложения, например, с Redux или Zustand.


Настройка позиционирования подменю

DropdownMenu.SubContent поддерживает автоматическое позиционирование относительно родительского пункта через пропсы side, align и sideOffset:

  • side – указывает сторону появления (top, bottom, left, right).
  • align – выравнивание (start, center, end).
  • sideOffset – смещение от родителя в пикселях.

Пример:

<DropdownMenu.SubContent side="right" align="start" sideOffset={5}>
  <DropdownMenu.Item>Подпункт 1</DropdownMenu.Item>
  <DropdownMenu.Item>Подпункт 2</DropdownMenu.Item>
</DropdownMenu.SubContent>

Это позволяет точно управлять внешним видом подменю и предотвращает наложение на другие элементы интерфейса.


Обеспечение доступности

Radix UI автоматически добавляет ARIA-атрибуты для подменю, но есть нюансы при кастомизации:

  • Каждый SubTrigger получает aria-haspopup="menu" и управляет aria-expanded.
  • SubContent получает роль menu и связывается с SubTrigger через aria-labelledby.
  • При вложенных меню навигация с клавиатуры (стрелки, Escape) работает без дополнительного кода.

Важно не нарушать DOM-иерархию, иначе навигация клавиатурой может перестать работать корректно.


Анимация появления подменю

Radix UI не содержит встроенных анимаций, но интеграция с CSS или библиотекой framer-motion проста. Пример с CSS:

.subContent {
  opacity: 0;
  transform: translateX(-10px);
  transition: opacity 0.2s, transform 0.2s;
}

.subContent[data-state='open'] {
  opacity: 1;
  transform: translateX(0);
}
<DropdownMenu.SubContent className="subContent">
  <DropdownMenu.Item>Подпункт с анимацией</DropdownMenu.Item>
</DropdownMenu.SubContent>

Использование data-state='open' позволяет отслеживать состояние подменю и плавно анимировать его появление и исчезновение.


Стилизация подменю

Radix UI предоставляет полностью контролируемый внешний вид, поэтому элементы можно стилизовать через класс или CSS-in-JS:

<DropdownMenu.Item className="menuItem">
  Настройки
</DropdownMenu.Item>
.menuItem {
  padding: 8px 16px;
  cursor: pointer;
  border-radius: 4px;
}

.menuItem[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.menuItem[data-highlighted] {
  background-color: #e2e8f0;
}

Использование data-disabled и data-highlighted позволяет реагировать на состояние элемента без вмешательства в JS-код.


Вложенные подменю произвольной глубины

Radix UI поддерживает неограниченную вложенность подменю. Каждое новое подменю создаётся через Sub и правильно управляет фокусом, если структура соблюдена:

<DropdownMenu.Sub>
  <DropdownMenu.SubTrigger>Экспорт</DropdownMenu.SubTrigger>
  <DropdownMenu.SubContent>
    <DropdownMenu.Sub>
      <DropdownMenu.SubTrigger>Форматы</DropdownMenu.SubTrigger>
      <DropdownMenu.SubContent>
        <DropdownMenu.Item>PDF</DropdownMenu.Item>
        <DropdownMenu.Item>SVG</DropdownMenu.Item>
      </DropdownMenu.SubContent>
    </DropdownMenu.Sub>
    <DropdownMenu.Item>HTML</DropdownMenu.Item>
  </DropdownMenu.SubContent>
</DropdownMenu.Sub>

Соблюдение иерархии и правильное использование SubTrigger и SubContent обеспечивает стабильное поведение и доступность даже при сложной структуре меню.