NotificationPanel
Адаптивная обёртка NotificationPanelContent. На desktop открывает панель в drawer, на mobile — в bottom-sheet. Выбор раскладки — по layoutType (как у Widget / Toolbar).
Контент передаётся пропом content как элемент <NotificationPanelContent />, а не как пропсы.
Когда использовать
- Лента уведомлений, открываемая из иконки в шапке или тулбаре.
- Один и тот же контент панели на desktop и mobile без дублирования разметки.
Анатомия
Layout type (default desktop)
Тип раскладки определяет обёртку:
desktop— drawer (FigmanotificationDrawer).mobile— bottom-sheet (FigmanotificationBottomSheet).
Position (default right)
Сторона, с которой выезжает drawer. Только для desktop.
Width (default s)
Ширина drawer. Только для desktop.
Установка
pnpm add @cloud-ru/ds-uikit-product-notification
import { NotificationPanelContent, NotificationPanel } from '@cloud-ru/ds-uikit-product-notification'
Примеры использования
Открытие из кнопки в шапке
tsx
import { Button } from '@cloud-ru/ds-button';
import {
APPEARANCE,
NotificationCard,
NotificationPanel,
NotificationPanelContent,
} from '@cloud-ru/ds-uikit-product-notification';
import { useMemo, useState } from 'react';
export function PanelScenario() {
const [open, setOpen] = useState(false);
const [readIds, setReadIds] = useState<Set<string>>(new Set());
const unreadCount = useMemo(() => ['inc-4821', 'deploy-9217'].filter(id => !readIds.has(id)).length, [readIds]);
const markRead = (id: string) => setReadIds(prev => (prev.has(id) ? prev : new Set(prev).add(id)));
return (
<div
style={{
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
padding: '12px 16px',
minHeight: 56,
}}
>
<Button
label={unreadCount > 0 ? `Уведомления · ${unreadCount}` : 'Уведомления'}
view='outline'
appearance='neutral'
onClick={() => setOpen(true)}
/>
<NotificationPanel
open={open}
onClose={() => setOpen(false)}
content={
<NotificationPanelContent
title='Уведомления'
readAllButton={{
label: 'Прочитать всё',
onClick: () => setReadIds(new Set(['inc-4821', 'deploy-9217'])),
}}
content={
<>
<NotificationCard
id='inc-4821'
label='INC-4821'
appearance={APPEARANCE.Error}
title='Кластер k8s-prod-1 деградирован'
description='3 из 5 нод недоступны последние 6 минут.'
date='03:14'
link={{ label: 'Открыть инцидент', href: '/incidents/INC-4821' }}
unread={!readIds.has('inc-4821')}
onVisible={markRead}
/>
<NotificationCard
id='deploy-9217'
label='api-gateway · v2.18.3'
appearance={APPEARANCE.Error}
title='Деплой не прошёл health-check'
description='Readiness probe вернул 503 на 4 из 6 подов.'
date='02:50'
primaryButton={{ label: 'Повторить', onClick: () => markRead('deploy-9217') }}
secondaryButton={{
label: 'Логи',
onClick: () => window.open('/logs/api-gateway/2.18.3', '_blank'),
}}
unread={!readIds.has('deploy-9217')}
onVisible={markRead}
/>
<NotificationCard
id='backup-1729'
label='Backup'
appearance={APPEARANCE.Success}
title='Резервная копия завершена'
description='prod-1 (412 ГБ) → s3://backups-prod/2026-05-26/'
date='вчера · 14:32'
/>
</>
}
/>
}
/>
</div>
);
}Props
Types
Props
NotificationPanelProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
className | string | — | no | CSS-класс для элемента с контентом |
closeOnPopstate | boolean | — | no | Закрывать дровер при перемещении по истории браузера |
container | string | HTMLElement | — | no | Контейнер в котором будет рендерится Drawer. По-умолчанию - body |
content | ReactElement<NotificationPanelContentProps, typeof NotificationPanelContent> | — | yes | Контент панели (`NotificationPanelContent`), отображаемый внутри обёртки |
data-test-id | string | — | no | |
onClose | () => void | — | yes | Колбэк закрытия |
open | boolean | — | yes | Управление состоянием показан/не показан. |
position | "bottom" | "left" | "right" | "top" | right | no | Расположение |
rootClassName | string | — | no | CSS-класс для корневого элемента |
showBlackout | boolean | true | no | Отображение темной подложки |
width | string | number | s | no | Ширина (только при position: "left" | "right") |