NotificationCard
Карточка одного уведомления. Тип задаёт иконку статуса и цвет, состояние unread подсвечивает непрочитанное и шлёт onVisible при попадании во вьюпорт, опциональные слоты — лейбл, ссылка, дата, кнопки действий и меню.
Когда использовать
- Элемент ленты внутри
NotificationPanelContent. - Отдельная карточка статуса операции (деплой, бэкап, инцидент) в произвольном контейнере.
Анатомия
Appearance (default default)
Тип уведомления — задаёт иконку статуса и её цвет.
default— нейтральное событие (упоминание, информация).error— ошибка или инцидент.warning— предупреждение (лимит, деградация).success— успешное завершение операции.
Состояние unread (default false)
unread подсвечивает карточку как непрочитанную и показывает индикатор. Когда карточка попадает во вьюпорт на 80%, один раз срабатывает onVisible(id) — потребитель помечает её прочитанной.
Слоты
label— короткий лейбл перед заголовком (категория, ID).link— ссылка в подвале, рендерится через@cloud-ru/ds-link.date— дата/время в подвале.primaryButton— кнопка главного действия (Button view='tonal').secondaryButton— кнопка второстепенного действия (Button view='simple').actions— меню дополнительных действий (kebab-кнопка с droplist).onClick— делает карточку кликабельной (role='button', фокус с клавиатуры). Вложенные кнопки, ссылка и меню активируются независимо.
Установка
pnpm add @cloud-ru/ds-uikit-product-notification
import { NotificationCard } from '@cloud-ru/ds-uikit-product-notification'
Примеры использования
Базовая карточка
Backup
Резервная копия завершена
Бэкап БД prod-1 (412 ГБ) загружен в холодное хранилище s3://backups-prod/2026-05-27/
tsx
import { APPEARANCE, NotificationCard } from '@cloud-ru/ds-uikit-product-notification';
export function BasicCard() {
return (
<NotificationCard
id='backup-1729'
label='Backup'
appearance={APPEARANCE.Success}
title='Резервная копия завершена'
description='Бэкап БД prod-1 (412 ГБ) загружен в холодное хранилище s3://backups-prod/2026-05-27/'
date='сегодня · 14:32'
/>
);
}Непрочитанные с onVisible
Непрочитанное уведомление
Storage
Лимит дисковой квоты
Использовано 92% квоты на /var/data — осталось 38 ГБ из 480 ГБ.
Непрочитанное уведомление
Storage
Лимит дисковой квоты
Использовано 87% квоты на /var/log — осталось 62 ГБ из 480 ГБ.
tsx
import { APPEARANCE, NotificationCard } from '@cloud-ru/ds-uikit-product-notification';
import { useState } from 'react';
export function UnreadCard() {
const [readIds, setReadIds] = useState<Set<string>>(new Set());
const markRead = (id: string) => setReadIds(prev => (prev.has(id) ? prev : new Set(prev).add(id)));
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<NotificationCard
id='quota-var-data'
label='Storage'
appearance={APPEARANCE.Warning}
title='Лимит дисковой квоты'
description='Использовано 92% квоты на /var/data — осталось 38 ГБ из 480 ГБ.'
date='вчера · 19:04'
unread={!readIds.has('quota-var-data')}
onVisible={markRead}
/>
<NotificationCard
id='quota-var-log'
label='Storage'
appearance={APPEARANCE.Warning}
title='Лимит дисковой квоты'
description='Использовано 87% квоты на /var/log — осталось 62 ГБ из 480 ГБ.'
date='вчера · 17:50'
unread={!readIds.has('quota-var-log')}
onVisible={markRead}
/>
</div>
);
}Инцидент со ссылкой
Непрочитанное уведомление
INC-4821
Кластер k8s-prod-1 деградирован
3 из 5 нод недоступны последние 6 минут. Pod scheduling приостановлен.
tsx
import { APPEARANCE, NotificationCard } from '@cloud-ru/ds-uikit-product-notification';
export function IncidentCard() {
return (
<NotificationCard
id='inc-4821'
label='INC-4821'
appearance={APPEARANCE.Error}
title='Кластер k8s-prod-1 деградирован'
description='3 из 5 нод недоступны последние 6 минут. Pod scheduling приостановлен.'
date='сегодня · 03:14'
link={{ label: 'Открыть инцидент', href: '/incidents/INC-4821' }}
unread
/>
);
}Действия и состояние
api-gateway · v2.18.3
Деплой не прошёл health-check
Readiness probe вернул 503 на 4 из 6 подов. Traffic не переключён на новую версию.
tsx
import { APPEARANCE, NotificationCard } from '@cloud-ru/ds-uikit-product-notification';
import { useState } from 'react';
export function DeployFailureCard() {
const [status, setStatus] = useState<'failed' | 'retrying' | 'dismissed'>('failed');
if (status === 'dismissed') {
return null;
}
return (
<NotificationCard
id='deploy-9217'
label='api-gateway · v2.18.3'
appearance={APPEARANCE.Error}
title='Деплой не прошёл health-check'
description='Readiness probe вернул 503 на 4 из 6 подов. Traffic не переключён на новую версию.'
date='5 минут назад'
primaryButton={{
label: status === 'retrying' ? 'Запускаю…' : 'Повторить деплой',
loading: status === 'retrying',
onClick: () => {
setStatus('retrying');
// Если карточку закрыли во время повтора, по таймеру не возвращаем её в `failed`.
setTimeout(() => setStatus(prev => (prev === 'retrying' ? 'failed' : prev)), 1200);
},
}}
secondaryButton={{
label: 'Логи пода',
onClick: () => window.open('/logs/api-gateway/2.18.3', '_blank'),
}}
actions={[
{ content: { label: 'Отметить прочитанным' }, onClick: () => setStatus('dismissed') },
{ content: { label: 'Заглушить на 1 час' }, onClick: () => setStatus('dismissed') },
{ content: { label: 'Удалить' }, onClick: () => setStatus('dismissed') },
]}
/>
);
}Props
Types
Props
NotificationCardProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
actions | Action[] | — | no | Дополнительные действия у карточки |
appearance | "default" | "error" | "success" | "warning" | default | no | Тип уведомления |
className | string | — | no | CSS-класс |
data-test-id | string | — | no | |
date | string | — | yes | Дата уведомления |
description | ReactNode | — | no | Описание уведомления — вторичный текст под заголовком |
id | string | — | yes | Идентификатор уведомления |
label | string | — | no | Лейбл перед заголовком |
link | PickLinkProps<"a", "label" | "truncateVariant" | "insideText"> | — | no | Ссылка (рендерится как `<a href>` через `@cloud-ru/ds-link`) |
onClick | MouseEventHandler<HTMLDivElement> | — | no | Колбэк клика по карточке. Делает карточку кликабельной (`role='button'`, фокус с клавиатуры). Активация по карточке срабатывает только при фокусе на самой карточке — вложенные кнопки, ссылка и меню действий активируются независимо. |
onVisible | ((cardId: string) => void) | — | no | Колбэк при попадании карточки в область видимости на 80% |
primaryButton | NotificationButtonProps | — | no | Кнопка главного действия у карточки (рендерится как Button view='tonal') |
secondaryButton | NotificationButtonProps | — | no | Кнопка второстепенного действия у карточки (рендерится как Button view='simple') |
title | string | — | yes | Заголовок уведомления |
unread | boolean | — | no | Управление состоянием прочитано/не прочитано |