EventStream
Live activity feed with follow mode — newest events pin to the bottom of a scrollable stream, and scrolling up releases the pin.
Import
EventStream is a registry composite — you own the source after installing:
npx ploxum add event-streamimport { EventStream, type EventStreamItem } from "@/components/event-stream";Depends on @roger-emerson/ploxum-primitives (Card, StatusPip, Badge, Switch, EmptyState) and @roger-emerson/ploxum-icons (Radio, for the default empty state).
Usage
const events: EventStreamItem[] = [
{
id: "evt-01",
severity: "ok",
title: "Gate A throughput nominal",
detail: "1,204 scans in last 5 min",
source: "Ingress",
timestamp: "19:42:07",
},
{
id: "evt-02",
severity: "live",
title: "Broadcast feed 2 went live",
source: "Media",
timestamp: "19:43:12",
},
{
id: "evt-03",
severity: "warning",
title: "Mezz 202 density trending up",
detail: "78% of threshold",
source: "Crowd",
timestamp: "19:44:01",
},
{
id: "evt-04",
severity: "critical",
title: "Floor 103 capacity threshold reached",
source: "Crowd",
timestamp: "19:45:30",
},
];
<EventStream
events={events}
maxHeight={360}
onSelect={(id) => openAlertDetail(id)}
/>;Follow defaults to on: the scroller stays pinned to the newest event as items append. Scrolling more than 24px off the bottom turns follow off (reported via onFollowChange); hovering pauses the auto-scroll without changing state. Pass follow to run it controlled.
Props
EventStream
| Prop | Type | Default | Description |
|---|---|---|---|
events | EventStreamItem[] | — | Events to render, oldest first. Newest renders at the bottom. |
follow | boolean | true | Pin the stream to the newest event. Controlled when provided. |
onFollowChange | (follow: boolean) => void | — | Fires when the user scrolls out of (or toggles back into) follow mode. |
maxHeight | number | 320 | Scroller height cap in px. |
fill | boolean | false | Fill the parent's bounded height and scroll internally instead of capping at maxHeight. |
glowOnArrival | boolean | false | Pulse a brief amber glow on each newly-arrived row; fades ~2s. Critical rows and reduced-motion never glow. |
onSelect | (id: string) => void | — | Row click handler. |
emptyState | ReactNode | built-in EmptyState | Slot rendered when there are no events. |
className | string | — | Extra classes on the root Card. |
ref | Ref<HTMLDivElement> | — | Forwarded to the root Card. |
EventStreamItem
| Field | Type | Description |
|---|---|---|
id | string | Stable key. |
severity | StatusPipStatus | "live" | "ok" | "warning" | "critical" | "offline" | "unknown" — rendered as a StatusPip (shape + color). |
title | string | Headline, truncates on overflow. |
detail | string? | Secondary line below the title. |
source | string? | Origin system, rendered as a Badge. |
timestamp | string | Date | Pre-formatted string passes through; a Date formats as HH:MM:SS. Always monospace + tabular numerals. |
Design rules
- Critical rows render motionless. The 150ms fade-in entrance is skipped for
severity: "critical"(P1 rule: instant, no animation) and underprefers-reduced-motion. - Severity is conveyed by StatusPip shape + color, never hue alone.
- Arrival glow respects the same gates.
glowOnArrivalpulses an amber halo on new rows but skipsseverity: "critical"(P1 rule: motionless) andprefers-reduced-motion.fillrequires a parent with a bounded height — without one the scroller has nothing to fill against. - The scroller carries
role="log"so assistive tech announces appended events without focus theft. - Follow-mode release is the user's signal that they're reading history — never re-pin programmatically except through the explicit toggle or
followprop.
