Skip to content
All Taste components

Dashboard widgets

Activity feed card

A timeline of what happened in the workspace: payments, moves, bookings and comments.

icon & avatar rows · quoted comment · timeline rail

Activity

View all
  1. Ritika Shah paid invoice INV-2041 · ₹48,500
  2. AM
    Arjun Mehta moved Menu redesign to Review
  3. New booking from Meera Iyer for Fri 16 Oct, 11:00
  4. KR
    Kabir Rao commented on Q3 report
    Numbers match the bank statement. Good to send.
  5. You invited neha@saffronstudio.in to the team
import { CalendarDays, CreditCard, Mail } from "lucide-react";
import { cn } from "@/lib/utils";

export interface ActivityItem {
  id: string;
  /** An icon kind, or a person's initials for an avatar. */
  icon?: "payment" | "booking" | "invite";
  initials?: string;
  text: React.ReactNode;
  quote?: string;
  time: string;
}

const icons = { payment: CreditCard, booking: CalendarDays, invite: Mail };

const sample: ActivityItem[] = [
  { id: "1", icon: "payment", text: <><b className="font-medium">Ritika Shah</b> paid invoice INV-2041 · ₹48,500</>, time: "12 min ago" },
  { id: "2", initials: "AM", text: <><b className="font-medium">Arjun Mehta</b> moved <b className="font-medium">Menu redesign</b> to Review</>, time: "1 h ago" },
  { id: "3", icon: "booking", text: <>New booking from <b className="font-medium">Meera Iyer</b> for Fri 16 Oct, 11:00</>, time: "2 h ago" },
  { id: "4", initials: "KR", text: <><b className="font-medium">Kabir Rao</b> commented on <b className="font-medium">Q3 report</b></>, quote: "Numbers match the bank statement. Good to send.", time: "4 h ago" },
  { id: "5", icon: "invite", text: <>You invited <b className="font-medium">neha@saffronstudio.in</b> to the team</>, time: "Yesterday" },
];

export function ActivityFeed({ items = sample }: { items?: ActivityItem[] }) {
  return (
    <section className="w-full max-w-md rounded-lg border border-border bg-bg p-4">
      <div className="flex items-center justify-between">
        <h3 className="text-sm font-medium">Activity</h3>
        <a href="/activity" className="text-[12.5px] text-primary hover:underline">View all</a>
      </div>
      <ol className="mt-4">
        {items.map((item, i) => {
          const Icon = item.icon ? icons[item.icon] : null;
          return (
            <li key={item.id} className={cn("relative grid grid-cols-[28px_minmax(0,1fr)] gap-3 text-[13.5px]", i < items.length - 1 && "pb-[18px]")}>
              {i < items.length - 1 && <span className="absolute bottom-1.5 left-[13.5px] top-[34px] w-px bg-border" aria-hidden />}
              {Icon ? (
                <span className={cn("grid size-7 place-items-center rounded-full",
                  item.icon === "payment" ? "bg-success-soft text-success-fg" : "bg-surface-2 text-fg-muted")}>
                  <Icon className="size-3.5" aria-hidden />
                </span>
              ) : (
                <span className="grid size-7 place-items-center rounded-full bg-primary-soft text-[11px] font-medium text-primary-soft-fg">{item.initials}</span>
              )}
              <div>
                <p>{item.text}</p>
                {item.quote && <p className="mt-2 rounded bg-surface px-3 py-2 text-[13px] text-fg-muted">{item.quote}</p>}
                <time className="mt-0.5 block text-xs text-fg-subtle">{item.time}</time>
              </div>
            </li>
          );
        })}
      </ol>
    </section>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.