Activity Feed
The timeline activity feed displays a chronological list of events, actions, and transactions. It appears on dashboards and detail pages to provide context about what has happened.
Rina menyelesaikan transaksi #28491
+Rp 287.500
14:32
Budi memproses retur #R-0042
-Rp 54.000
14:15
Sistem menerima kiriman dari DC (48 item)
13:50
Dewi membuka shift pagi
08:00
Anatomy
Each feed item contains:
[Icon 32px] Actor Name performed action Rp +1.450.000
(colored Description or context text 14:32
circle)| Element | Style |
|---|---|
| Icon container | 32px circle, colored background (soft variant), icon in center (16px) |
| Actor name | 14px, font-semibold, text-1 |
| Action text | 14px, text-2, inline after actor |
| Amount (optional) | font-mono, 13px, font-semibold. Green (#2D7D46) for positive, red (#C4463A) for negative |
| Timestamp | font-mono, 12px, text-3, right-aligned |
| Connector line | 1px border vertical line connecting icons |
Event Types
| Event | Icon | Circle Color | Icon Color |
|---|---|---|---|
| Sale / Transaction | ShoppingCart | #E8F0EC | #2D6B4A |
| Payment received | CreditCard | #E8F0EC | #2D6B4A |
| Refund | RotateCcw | #FCEAE8 | #C4463A |
| Stock movement | Package | #DBEAFE | #2563EB |
| User action | User | #F3F1EC | #57534E |
| Approval | CheckCircle | #E8F0EC | #2D6B4A |
| Rejection | XCircle | #FCEAE8 | #C4463A |
| Alert / Warning | AlertTriangle | #FEF3C7 | #92400E |
| System | Settings | #F3F1EC | #57534E |
React Component
tsx
interface FeedItem {
id: string
icon: LucideIcon
iconBg: string
iconColor: string
actor: string
action: string
amount?: { value: number; prefix?: string }
timestamp: string
}
export function ActivityFeed({ items }: { items: FeedItem[] }) {
return (
<div className="relative">
{items.map((item, i) => (
<div key={item.id} className="flex gap-3 pb-4 relative">
{/* Connector line */}
{i < items.length - 1 && (
<div className="absolute left-4 top-8 bottom-0 w-px bg-border" />
)}
{/* Icon */}
<div
className="w-8 h-8 rounded-full flex items-center justify-center shrink-0 relative z-10"
style={{ backgroundColor: item.iconBg }}
>
<item.icon className="w-4 h-4" style={{ color: item.iconColor }} />
</div>
{/* Content */}
<div className="flex-1 min-w-0 pt-0.5">
<p className="text-sm">
<span className="font-semibold text-1">{item.actor}</span>
{' '}
<span className="text-2">{item.action}</span>
</p>
</div>
{/* Right side */}
<div className="text-right shrink-0">
{item.amount && (
<p className={cn(
'font-mono text-[13px] font-semibold',
item.amount.value >= 0 ? 'text-success' : 'text-danger'
)}>
{item.amount.value >= 0 ? '+' : ''}
{item.amount.prefix ?? 'Rp '}
{Math.abs(item.amount.value).toLocaleString('id-ID')}
</p>
)}
<p className="font-mono text-xs text-3">{item.timestamp}</p>
</div>
</div>
))}
</div>
)
}Usage
| App | Context |
|---|---|
| ho-finance | Store activity feed on store detail page |
| store-admin | Dashboard recent activity |
| stock-app-web | Stock movement history |
Grouping
When showing a full day's activity, group by date with a date separator:
--- Sabtu, 22 Maret 2026 ---
[feed items...]
--- Jumat, 21 Maret 2026 ---
[feed items...]Date separator: text-3, 12px, font-mono, centered, with border lines on either side.
Loading & Empty States
- Loading: Skeleton pulse on 3 placeholder rows
- Empty: Isometric illustration + "Belum ada aktivitas" message
Don't
- Don't show more than 20 items without pagination or "Muat lebih banyak" button
- Don't mix different time zones --- always use the store's local time
- Don't use the activity feed for real-time streaming --- poll every 30 seconds or use WebSocket with throttle