Activity Feed

 

  1. Hania Rani uploaded Esja (piano take 4).wav

    48 kHz, 24 bit, 6 min 12 s. Replaces take 3 in the album session.
  2. Nils Frahm commented on Says, mix 7

    The pad comes in a bar early after the second chorus. Can we nudge it?
  3. Ólafur Arnalds approved Saman master

 

  1. Kelly Lee Owens joined the Harpa live crew

  2. Jon Hopkins merged Singularity stems into the main session

  3. Nils Frahm renamed Spaces (live) to Spaces, Harpa

 

  1. Arooj Aftab flagged clipping in Mohabbat, vocal bus

    Peaks at +0.4 dB between 2:31 and 2:34. The limiter on the bus was bypassed.
  2. Hania Rani created the Reykjavík setlist

  3. Björk removed a take from Hyperballad rehearsal

  4. Ólafur Arnalds invited Kelly Lee Owens

About this component

Activity Feed

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.

Usage

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
/>

Keyboard and gestures

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.

TabMove through the Show details buttons and Load more, in reading order.
Enter / Space on Show detailsOpen or close the event's details.

Props

Read from the component's own type, so this cannot drift from what it accepts.

PropTypeDefaultDescription
events*ActivityEvent[]—Newest first. New events added at the start animate in.
expandedstring[]—Ids of the events whose details are open. Pair with `onExpandedChange` to control it.
defaultExpandedstring[]—
onExpandedChange(expanded: string[]) => void—
hasMoreboolean—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.
localestring—Locale for day headers, times and relative times. @defaultValue "en-US"
labelsPartial<typeof DEFAULT_LABELS>—Words used by the component, for translation.
tone"muted" | "outline" | "ghost""ghost"
size"sm" | "default" | "lg""default"
aria-labelstring"Activity"
classNamestring—