Hania Rani uploaded Esja (piano take 4).wav
Nils Frahm commented on Says, mix 7
Ólafur Arnalds approved Saman master
Kelly Lee Owens joined the Harpa live crew
Jon Hopkins merged Singularity stems into the main session
Nils Frahm renamed Spaces (live) to Spaces, Harpa
Arooj Aftab flagged clipping in Mohabbat, vocal bus
Hania Rani created the Reykjavík setlist
Björk removed a take from Hyperballad rehearsal
Ólafur Arnalds invited Kelly Lee Owens
An activity timeline: events grouped by day under sticky headers (Today, Yesterday, then dates written in the reader's locale), each on a vertical rail with a tinted icon node, the actor, what they did and what it happened to. Relative times (3 min ago) keep themselves current from one shared clock that pauses on hidden tabs, and render only after mount so the server and the browser never disagree. Details open under an event with a smooth height animation, new events slide in at the top, and a Load more button stays busy while its promise runs. Three tones, three sizes, controlled or uncontrolled details.
The import and the props worth knowing about, in one place.
import { MessageSquare, Upload } from "lucide-react";
import { ActivityFeed } from "@/components/beste/component/activity-feed";
<ActivityFeed
events={[
{
id: "1",
actor: { name: "Hania Rani" },
action: "uploaded",
target: "Esja (piano take 4).wav",
time: "2026-09-27T09:41:00Z",
icon: Upload,
tone: "info", // "neutral" | "info" | "success" | "warning" | "danger"
detail: "48 kHz, 24 bit, 6 min 12 s.",
},
{ id: "2", actor: { name: "Nils Frahm" }, action: "commented on", target: "Says, mix 7", time: Date.now() - 60_000, icon: MessageSquare },
]}
hasMore
onLoadMore={async () => console.log("fetch the next page")}
locale="en-GB"
tone="outline" // "muted" | "outline" | "ghost" (default)
className="max-h-96 overflow-y-auto" // a scroll area makes the day headers stick
/>Every one of these is in the component already. They are listed because a props table cannot mention a gesture, so nothing else on this page can tell you they exist.
| Tab | Move through the Show details buttons and Load more, in reading order. |
|---|---|
| Enter / Space on Show details | Open or close the event's details. |
Read from the component's own type, so this cannot drift from what it accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
events* | ActivityEvent[] | — | Newest first. New events added at the start animate in. |
expanded | string[] | — | Ids of the events whose details are open. Pair with `onExpandedChange` to control it. |
defaultExpanded | string[] | — | |
onExpandedChange | (expanded: string[]) => void | — | |
hasMore | boolean | — | Shows a "Load more" button while true. |
onLoadMore | () => void | Promise<void> | — | Called by "Load more"; a returned promise keeps the button busy until it settles. |
locale | string | — | Locale for day headers, times and relative times. @defaultValue "en-US" |
labels | Partial<typeof DEFAULT_LABELS> | — | Words used by the component, for translation. |
tone | "muted" | "outline" | "ghost" | "ghost" | |
size | "sm" | "default" | "lg" | "default" | |
aria-label | string | "Activity" | |
className | string | — |