MainMenu
MainMenu — drawer с каталогом сервисов: сегменты, fuzzy-поиск, избранное, DnD групп и карточек.
- Desktop — resizable
@cloud-ru/ds-drawerслева: левая колонка (избранное, настройки), правая — сегменты и сетка карточек. - Mobile — fullscreen bottom sheet (
MobileDrawerCustom,position='bottom'): sticky header с логотипом, scroll-body, поиск и каталог.
Поверхность выбирается через AdaptiveProvider (layoutType).
Когда использовать
- Навигация по каталогу облачных сервисов из шапки консоли (новая продуктовая навигация).
- Сценарии с избранным, недавними сервисами и настройками отображения карточек.
- Mobile-меню для микрофронта на legacy
HeaderLayout:MenuMobileподключается черезcustomMobileMenuу@cloud-ru/ds-uikit-product-header-legacy/ MainMenu.
Когда не нужен:
- Legacy drawer по Figma
navigationOldDrawer/navigationOldDrawerMobile:- используйте
@cloud-ru/ds-uikit-product-header-legacy.
- используйте
Анатомия
Trigger и drawer
- trigger —
HeaderButtonсMainMenuSVGи tooltip «Сервисы». - desktop drawer — resizable
DrawerCustom, слева; drag-handle для ширины. - mobile drawer — fullscreen bottom sheet; swipe отключён (
swipeEnabled={false}).
Левая колонка (desktop)
leftTop— слот над избранным (например, селектор платформы).Favorites— избранные сервисы с DnD (MainMenuDndContext).settingItems— административные пункты (mapInnerLinksToListItems).leftBottom— дополнительный слот внизу колонки.
Правая колонка (desktop)
rightTop— баннеры.Search— fuzzy-поиск по каталогу.segments— сегменты сSegmentControl(если сегментов > 1).- Сетка карточек
CardServiceSmallпо группам в активном сегменте.
Mobile body
MenuHeaderBrand+ divider — sticky header с кнопкой закрытия.leftTop,Search,rightTop,Content— в scroll-body.settingItems/leftBottom— нижняя зона (MenuBottom).- Избранное без drag&drop (desktop-only DnD).
open / setOpen
Controlled-открытие drawer. Без open состояние держит сам компонент.
MenuMobile
MenuMobile экспортируется из @cloud-ru/ds-uikit-product-header отдельно от MainMenu — тот же API (MainMenuProps), но без кнопки-триггера. Используется:
- внутри
MainMenuна mobile-поверхности; - в legacy-интеграции:
customMobileMenuу@cloud-ru/ds-uikit-product-header-legacy/MainMenu.
import { MainMenu, MenuMobile } from '@cloud-ru/ds-uikit-product-header';
Types
Props
MainMenuProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
activeSegmentId | string | — | no | Активный сегмент правой панели (значение SegmentControl, см. {@link MainMenuSegment.id}). Не передано — неуправляемое состояние (дефолт — первый сегмент с видимыми карточками). |
defaultWidth | number | — | no | Ширина дровера, с которой открывается меню (desktop only) |
disabled | boolean | — | no | Кнопка открытия меню недоступна для клика. |
draggerTooltip | string | — | no | Текст подсказки для драггера (desktop only) |
favorite | FavoriteProps | — | no | Избранное. Без пропа группа-предок карточек драга из избранного не активируется. Избранное. Без пропа секция «Избранное» не отображается. |
leftBottom | ReactNode | — | no | Слот под пунктами настроек в левой колонке (desktop) / в конце списка (mobile). |
leftTop | ReactNode | — | no | Слот над поиском в левой колонке (desktop) / над поиском в списке (mobile). |
loading | boolean | — | no | Флаг загрузки данных |
logo | ReactNode | — | no | Логотип в шапке меню. |
onActiveSegmentChange | ((segmentId: string) => void) | — | no | Колбэк смены активного сегмента правой панели. |
onSegmentExpandedChange | ((segmentId: string, expandedGroupIds: string[]) => void) | — | no | Колбэк при изменении набора раскрытых групп сегмента (без id синтетической группы избранного). |
onSegmentOrderChange | ((segmentId: string, orderedGroupIds: string[]) => void) | — | no | Колбэк после DnD групп в сегменте (без id синтетической группы избранного). |
onSegmentServiceClick | ((service: InnerLink, e?: MouseEvent<HTMLElement, MouseEvent>) => void) | — | no | Колбэк клика по карточке сервиса в сегменте. |
onWidthChangeEnd | ((width: number) => void) | — | no | Вызывается при окончании изменения ширины дровера (desktop only) |
open | boolean | false | no | Открыто ли меню. Не передано — состояние открытия неуправляемое (меню само переключает себя по клику на кнопку). |
platformGroups | LinksGroup[] | — | no | Платформенные группы (например «Облачные продукты», «Другие продукты»). Без поиска в сетке карточек **не отображаются**. С поиском: попадают в результаты при совпадении; порядок — после совпадений из сегментов без `pinBottomOnSearch`, перед сегментами с `pinBottomOnSearch`. Обычно `favoritesEnabled: false`; карточки могут быть без `icon` (Avatar по `label`). |
preferences | MainMenuPreferencesProps | — | no | Настройки меню (модалка по кнопке в тулбаре): описания карточек, цвета групп. Не передано — кнопка настроек в тулбаре не отображается. |
rightTop | ReactNode | — | no | Слот над тулбаром правой колонки (например, баннеры) |
search | SearchProps | — | no | Поиск по сервисам. Без пропа поисковая строка не отображается. |
searchGroups | LinksGroup[] | — | no | Результаты поиска (уже смерженные); в обычном режиме не используются. |
segmentPrefs | MainMenuSegmentPrefs[] | — | no | Пользовательские prefs сегментов (порядок / раскрытие групп). Нет записи для сегмента или omit `order` / `expanded` → uncontrolled для этого поля. |
segments | MainMenuSegment[] | — | no | Сегменты правой панели (сетка ка рточек) — только каталог. При поиске: совпадения из сегментов без `pinBottomOnSearch` → `platformGroups` → сегменты с `pinBottomOnSearch`. Если один и тот же {@link InnerLink.id} совпал сразу в нескольких сегментах — остаётся только первое по этому приоритету вхождение, остальные (и опустевшие после этого группы) не показываются. При `segments.length > 1` показывается SegmentControl (скрывается во время поиска). Порядок и раскрытие групп — через `segmentPrefs` и колбэки ниже. |
setOpen | ((open: boolean) => void) | — | no | Колбэк открытия/закрытия меню. |
settingItems | MainMenuSettingsItem[] | — | no | Пункты левой колонки (desktop) / нижней части списка (mobile). Плоский список (`dividerBefore` для разделителей). Не связан с сегментами правой панели и не меняется при сортировке групп в сегментах. |
Types
MainMenuProps
FavoriteProps
InnerLink
LinksGroup
MainMenuPreferencesProps
MainMenuSegment
MainMenuSegmentPrefs
MainMenuSettingsItem
SearchProps
Установка
pnpm add @cloud-ru/ds-uikit-product-header
import { MainMenu, MenuMobile } from '@cloud-ru/ds-uikit-product-header';
import '@cloud-ru/ds-uikit-product-header/style.css';
Примеры использования
Базовое меню
tsx
import { ViewTileSVG } from '@cloud-ru/ds-icons/interface/product';
import { MainMenu } from '@cloud-ru/ds-uikit-product-header';
import { SERVICE_GROUPS } from '../../../stories/demoData';
export function Basic() {
return (
<MainMenu
segments={[
{
id: 'allServices',
label: 'Все сервисы',
icon: <ViewTileSVG size={24} />,
items: SERVICE_GROUPS.slice(0, 2),
},
]}
data-test-id='header-main-menu-basic'
/>
);
}С поиском
tsx
import { ViewTileSVG } from '@cloud-ru/ds-icons/interface/product';
import { MainMenu } from '@cloud-ru/ds-uikit-product-header';
import { useState } from 'react';
import { SERVICE_GROUPS } from '../../../stories/demoData';
export function WithSearch() {
const [searchValue, setSearchValue] = useState('');
return (
<MainMenu
segments={[
{
id: 'allServices',
label: 'Все сервисы',
icon: <ViewTileSVG size={24} />,
items: SERVICE_GROUPS.slice(0, 3),
},
]}
search={{ value: searchValue, onChange: setSearchValue }}
data-test-id='header-main-menu-search'
/>
);
}С избранным
tsx
import { ViewTileSVG } from '@cloud-ru/ds-icons/interface/product';
import { MainMenu } from '@cloud-ru/ds-uikit-product-header';
import { useState } from 'react';
import { SERVICE_GROUPS } from '../../../stories/demoData';
export function WithFavorites() {
const [favoriteIds, setFavoriteIds] = useState<string[]>(['inference']);
return (
<MainMenu
segments={[
{
id: 'allServices',
label: 'Все сервисы',
icon: <ViewTileSVG size={24} />,
items: SERVICE_GROUPS.slice(0, 2),
},
]}
favorite={{
value: favoriteIds,
onChange: productId => (addingValue: boolean) => {
setFavoriteIds(prev => (addingValue ? [...prev, productId] : prev.filter(id => id !== productId)));
},
}}
data-test-id='header-main-menu-favorites'
/>
);
}Интеграция с legacy MainMenu
import { useState } from 'react';
import { MainMenu as LegacyMainMenu } from '@cloud-ru/ds-uikit-product-header-legacy';
import { MenuMobile } from '@cloud-ru/ds-uikit-product-header';
function LocalMainMenu() {
const [open, setOpen] = useState(false);
return (
<LegacyMainMenu
open={open}
setOpen={setOpen}
serviceGroups={legacyGroups}
customMobileMenu={
<MenuMobile
open={open}
setOpen={setOpen}
segments={headerSegments}
/>
}
/>
);
}
Props
Types
Props
MainMenuProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
activeSegmentId | string | — | no | Активный сегмент правой панели (значение SegmentControl, см. {@link MainMenuSegment.id}). Не передано — неуправляемое состояние (дефолт — первый сегмент с видимыми карточками). |
defaultWidth | number | — | no | Ширина дровера, с которой открывается меню (desktop only) |
disabled | boolean | — | no | Кнопка открытия меню недоступна для клика. |
draggerTooltip | string | — | no | Текст подсказки для драггера (desktop only) |
favorite | FavoriteProps | — | no | Избранное. Без пропа группа-предок карточек драга из избранного не активируется. Избранное. Без пропа секция «Избранное» не отображается. |
leftBottom | ReactNode | — | no | Слот под пунктами настроек в левой колонке (desktop) / в конце списка (mobile). |
leftTop | ReactNode | — | no | Слот над поиском в левой колонке (desktop) / над поиском в списке (mobile). |
loading | boolean | — | no | Флаг загрузки данных |
logo | ReactNode | — | no | Логотип в шапке меню. |
onActiveSegmentChange | ((segmentId: string) => void) | — | no | Колбэк смены активного сегмента правой панели. |
onSegmentExpandedChange | ((segmentId: string, expandedGroupIds: string[]) => void) | — | no | Колбэк при изменении набора раскрытых групп сегмента (без id синтетической группы избранного). |
onSegmentOrderChange | ((segmentId: string, orderedGroupIds: string[]) => void) | — | no | Колбэк после DnD групп в сегменте (без id синтетической группы избранного). |
onSegmentServiceClick | ((service: InnerLink, e?: MouseEvent<HTMLElement, MouseEvent>) => void) | — | no | Колбэк клика по карточке сервиса в сегменте. |
onWidthChangeEnd | ((width: number) => void) | — | no | Вызывается при окончании изменения ширины дровера (desktop only) |
open | boolean | — | no | Открыто ли меню. Не передано — состояние открытия неуправляемое (меню само переключает себя по клику на кнопку). |
platformGroups | LinksGroup[] | — | no | Платформенные группы (например «Облачные продукты», «Другие продукты»). Без поиска в сетке карточек **не отображаются**. С поиском: попадают в результаты при совпадении; порядок — после совпадений из сегментов без `pinBottomOnSearch`, перед сегментами с `pinBottomOnSearch`. Обычно `favoritesEnabled: false`; карточки могут быть без `icon` (Avatar по `label`). |
preferences | MainMenuPreferencesProps | — | no | Настройки меню (модалка по кнопке в тулбаре): описания карточек, цвета групп. Не передано — кнопка настроек в тулбаре не отображается. |
rightTop | ReactNode | — | no | Слот над тулбаром правой колонки (например, баннеры) |
search | SearchProps | — | no | Поиск по сервисам. Без пропа поисковая строка не отображается. |
searchGroups | LinksGroup[] | — | no | Результаты поиска (уже смерженные); в обычном режиме не используются. |
segmentPrefs | MainMenuSegmentPrefs[] | — | no | Пользовательские prefs сегментов (порядок / раскрытие групп). Нет записи для сегмента или omit `order` / `expanded` → uncontrolled для этого поля. |
segments | MainMenuSegment[] | — | no | Сегменты правой панели (сетка карточек) — то лько каталог. При поиске: совпадения из сегментов без `pinBottomOnSearch` → `platformGroups` → сегменты с `pinBottomOnSearch`. Если один и тот же {@link InnerLink.id} совпал сразу в нескольких сегментах — остаётся только первое по этому приоритету вхождение, остальные (и опустевшие после этого группы) не показываются. При `segments.length > 1` показывается SegmentControl (скрывается во время поиска). Порядок и раскрытие групп — через `segmentPrefs` и колбэки ниже. |
setOpen | ((open: boolean) => void) | — | no | Колбэк открытия/закрытия меню. |
settingItems | MainMenuSettingsItem[] | — | no | Пункты левой колонки (desktop) / нижней части списка (mobile). Плоский список (`dividerBefore` для разделителей). Не связан с сегментами правой панели и не меняется при сортировке групп в сегментах. |
Types
MainMenuProps
FavoriteProps
InnerLink
LinksGroup
MainMenuPreferencesProps
MainMenuSegment
MainMenuSegmentPrefs
MainMenuSettingsItem
SearchProps
Адаптивность
| Проп / поведение | Desktop | Mobile |
|---|---|---|
| Drawer | Resizable слева | Fullscreen bottom sheet |
favorite DnD | Да | Нет |
segments / SegmentControl | Да | Да (скрывается при поиске) |
preferences | Модалка настроек в тулбаре | То же |
Storybook
Figma
Смотри также
@cloud-ru/ds-uikit-product-header-legacy/ MainMenu — legacy drawer и пропcustomMobileMenu.@cloud-ru/ds-uikit-product-card-predefined— карточки сервисов в сетке.