NotificationMenu
A bell-anchored dropdown listing notifications — each row pairs a StatusPip severity with inline acknowledge / archive / delete actions that never close the menu.
Import
NotificationMenu is a registry composite — you own the source after installing:
npx ploxum add notification-menuimport { NotificationMenu, type NotificationItem } from "@/components/notification-menu";Depends on @roger-emerson/ploxum-icons (Archive, Check, Trash2), @roger-emerson/ploxum-primitives (DropdownMenu, EmptyState, StatusPip) and @roger-emerson/ploxum-utils (cn).
Usage
const notifications: NotificationItem[] = [
{
id: "n1",
severity: "critical",
title: "Ticketing API p99 breached 500ms",
source: "Ingress",
timestamp: "12:04",
read: false,
},
{
id: "n2",
severity: "warning",
title: "Camera mesh down to 47/48",
source: "Cameras",
timestamp: "11:58",
read: true,
},
];
<NotificationMenu
notifications={notifications}
onAcknowledge={(id) => ack(id)}
onArchive={(id) => archive(id)}
onDelete={(id) => remove(id)}
onMarkAllRead={() => markAllRead()}
trigger={
<button aria-label="Notifications" type="button">
<BellIcon />
</button>
}
/>;The unread count is derived from notifications.filter((n) => !n.read) and shown as an amber pill in the header. Unread rows carry an amber left border + tint wash. With an empty list the menu renders an EmptyState. A row action is omitted entirely when its handler prop is absent.
Props
NotificationMenu
| Prop | Type | Default | Description |
|---|---|---|---|
notifications | NotificationItem[] | — | Rows, in display order. |
trigger | ReactNode | — | Caller-owned bell + badge, passed to DropdownMenu.Trigger asChild. Required. |
onAcknowledge | (id: string) => void | — | Renders the acknowledge (check) action when present. |
onArchive | (id: string) => void | — | Renders the archive action when present. |
onDelete | (id: string) => void | — | Renders the delete (danger) action when present. |
onMarkAllRead | () => void | — | Renders the header "Mark all read" button when present and there are unread rows. |
width | number | 360 | Content width in px. |
NotificationItem
| Field | Type | Description |
|---|---|---|
id | string | Stable key; passed to every row action handler. |
severity | StatusPipStatus | "live" | "ok" | "warning" | "critical" | "offline" | "unknown". Renders a leading StatusPip with a mapped label. |
title | string | Headline, truncates on overflow. |
source | string | Origin subsystem, shown in the meta line. |
timestamp | string | Pre-formatted time, rendered tabular-nums in the meta line. |
read | boolean? | Unread (false/omitted) rows get the amber border + tint and bold title; counted toward the unread badge. |
Design rules
- Rows are bespoke
<div>s, notDropdownMenu.Item. This keeps the inline acknowledge / archive / delete buttons from selecting the row or closing the menu — each actionstopPropagations and calls its handler with the rowid. - Critical and other high-severity rows render motionless. Only the dropdown Content open/close is animated (the sanctioned palette-style flourish); rows never animate on state change (P1 rule).
- Delete is the danger action —
Trash2icon, danger-tinted hover. It sits last in the action cluster and is icon-only, distinct from the neutral acknowledge/archive actions. - Status is StatusPip shape + color, never hue alone; the pip carries the mapped severity label (Live / Operational / Warning / Critical / Offline / Unknown).
