KQ-1317 · ACTIVITY/STREAM
Activity Stream
A live activity feed where new events gather behind an "N new" pill instead of shoving the list under the reader: the faces behind them land on the pill on recoil and the count rolls on snap. Pressing it scrolls the feed to its top on glide and releases them in a cascade while everything below glides down, each revealed line keeping a wash that fades over two seconds; switching the grouping between person, type and none glides every line to its new seat, and relative times tick on a timer set for the next boundary any line will cross. The list is a role="feed" of articles with one roving tab stop — Up and Down move between lines, Page keys between articles, Home and End to the ends, Enter opens — and the kind chips are toggle buttons.
Fieldline Ops
Livelive · up to date
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/activity-stream
Props
- rate
- number
- default 12
- How many events arrive per minute from incoming while live; 0 stops arrivals. Sets how fast the pill fills.
- group
- "actor" | "kind" | "none"
- default "actor"
- How the feed groups: consecutive events by one person, by one type, or one article per event. Changing it glides every line to its new seat.
- pill
- "avatars" | "count" | "off"
- default "avatars"
- How new events wait: behind a pill of faces and a rolling count, behind a plain count, or not at all — they slide in at the top as they land.
- events
- ActivityEvent[]
- default defaultActivityEvents
- The history, in any order: id, actor, kind, action, target, detail and at (Date or ms). Ids added after mount wait behind the pill like arrivals.
- incoming
- ActivityIncoming[]
- default defaultActivityIncoming
- Events still to come (no time yet), released in order at rate and stamped with the stream's clock; the list loops with fresh ids.
- actors
- ActivityActor[]
- default defaultActivityActors
- Who can act: id, name, an optional role shown in group headers and an optional tint (any CSS colour) for the avatar.
- kinds
- ActivityKind[]
- default defaultActivityKinds
- What can happen: id, label, plural and an optional 16px icon. Each kind present becomes a filter chip with its count.
- now
- number | Date
- default defaultActivityNow
- The time the stream starts from. Relative times are measured from it, never from the wall clock during render.
- live
- boolean
- default true
- The clock runs on from now in real time and incoming arrives. Off: the clock holds and nothing arrives.
- filter / defaultFilter
- string[]
- default []
- The kind ids shown; empty shows everything. Controlled or uncontrolled.
- onFilterChange
- (kinds: string[]) => void
- default —
- Fires from the chip that changed the filter, with the new kind ids.
- onReveal
- (events: ActivityEvent[]) => void
- default —
- The pill was pressed: fires with the events it brought into the feed, newest first.
- onArrive
- (event: ActivityEvent) => void
- default —
- An incoming event arrived, stamped with the stream's clock.
- onOpen
- (event: ActivityEvent) => void
- default —
- A line was pressed or opened with Enter.
- groupWindow
- number
- default 30
- Consecutive events further apart than this many minutes start a new group.
- collapseAfter
- number
- default 3
- Lines a group shows before a Show N more disclosure, whose height glides open to what its lines measure.
- height
- number
- default 560
- The panel's height in px; the feed scrolls inside it.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the history has arrived: loading shows a skeleton and marks the feed busy, error a retry.
- onRetry
- () => void
- default —
- Try again was pressed after the history failed to load.
- title
- string
- default "Activity"
- The panel's heading.
- label
- string
- default —
- The panel's accessible name. Defaults to the title.
- emptyLabel
- string
- default "No activity yet."
- What an empty feed says.
- formatTime
- (at: Date) => string
- default —
- The absolute time in a line's title. Defaults to a fixed, locale-free "30 Sep, 14:05 UTC".
- sound
- boolean
- default false
- Play a plip for filter chips and Show more, and a pop when the pill reveals. Arrivals and ticks are always silent.
- disabled
- boolean
- default false
- Shows the feed but refuses filters, reveals and opens; arrivals keep gathering.
- className
- string
- default —
- Classes for the outer panel.
| Prop | Type | Default | Description |
|---|---|---|---|
| rate | number | 12 | How many events arrive per minute from incoming while live; 0 stops arrivals. Sets how fast the pill fills. |
| group | "actor" | "kind" | "none" | "actor" | How the feed groups: consecutive events by one person, by one type, or one article per event. Changing it glides every line to its new seat. |
| pill | "avatars" | "count" | "off" | "avatars" | How new events wait: behind a pill of faces and a rolling count, behind a plain count, or not at all — they slide in at the top as they land. |
| events | ActivityEvent[] | defaultActivityEvents | The history, in any order: id, actor, kind, action, target, detail and at (Date or ms). Ids added after mount wait behind the pill like arrivals. |
| incoming | ActivityIncoming[] | defaultActivityIncoming | Events still to come (no time yet), released in order at rate and stamped with the stream's clock; the list loops with fresh ids. |
| actors | ActivityActor[] | defaultActivityActors | Who can act: id, name, an optional role shown in group headers and an optional tint (any CSS colour) for the avatar. |
| kinds | ActivityKind[] | defaultActivityKinds | What can happen: id, label, plural and an optional 16px icon. Each kind present becomes a filter chip with its count. |
| now | number | Date | defaultActivityNow | The time the stream starts from. Relative times are measured from it, never from the wall clock during render. |
| live | boolean | true | The clock runs on from now in real time and incoming arrives. Off: the clock holds and nothing arrives. |
| filter / defaultFilter | string[] | [] | The kind ids shown; empty shows everything. Controlled or uncontrolled. |
| onFilterChange | (kinds: string[]) => void | — | Fires from the chip that changed the filter, with the new kind ids. |
| onReveal | (events: ActivityEvent[]) => void | — | The pill was pressed: fires with the events it brought into the feed, newest first. |
| onArrive | (event: ActivityEvent) => void | — | An incoming event arrived, stamped with the stream's clock. |
| onOpen | (event: ActivityEvent) => void | — | A line was pressed or opened with Enter. |
| groupWindow | number | 30 | Consecutive events further apart than this many minutes start a new group. |
| collapseAfter | number | 3 | Lines a group shows before a Show N more disclosure, whose height glides open to what its lines measure. |
| height | number | 560 | The panel's height in px; the feed scrolls inside it. |
| status | "ready" | "loading" | "error" | "ready" | Whether the history has arrived: loading shows a skeleton and marks the feed busy, error a retry. |
| onRetry | () => void | — | Try again was pressed after the history failed to load. |
| title | string | "Activity" | The panel's heading. |
| label | string | — | The panel's accessible name. Defaults to the title. |
| emptyLabel | string | "No activity yet." | What an empty feed says. |
| formatTime | (at: Date) => string | — | The absolute time in a line's title. Defaults to a fixed, locale-free "30 Sep, 14:05 UTC". |
| sound | boolean | false | Play a plip for filter chips and Show more, and a pop when the pill reveals. Arrivals and ticks are always silent. |
| disabled | boolean | false | Shows the feed but refuses filters, reveals and opens; arrivals keep gathering. |
| className | string | — | Classes for the outer panel. |