composed · Composed pattern
ActivityFeed
@devalok/shilp-sutra/composed/activity-feedView in Storybook Hand-curated previews ship in rolling waves. See it live in Storybook →
Reference
Props
items: ActivityItem[] (REQUIRED) — { id, actor?: { name, image? }, action: string|ReactNode, timestamp: Date|string, icon?, color?: 'default'|'success'|'warning'|'error'|'info', detail?: ReactNode }
onLoadMore?: () => void — "Load more" button callback
loading: boolean — skeleton shimmer
hasMore?: boolean — shows "Load more" button
emptyState?: ReactNode — empty state content
compact: boolean — tighter spacing, no avatars, smaller text
maxInitialItems: number — truncate with "Show all (N)" toggle
groupBy?: 'time' | 'none' — group items by time buckets (today, yesterday, this week, older)
groupLabels?: GroupLabels — custom labels for time groups: { today?, yesterday?, thisWeek?, older? }
renderItem?: (item: ActivityItem, index: number) => ReactNode | undefined — custom renderer per item; return undefined to fall back to default ActivityEntry
Defaults
loading=false, compact=false, hasMore=false, groupBy='none'
Example
<ActivityFeed
items={[
{ id: '1', actor: { name: 'Alice' }, action: 'completed task', timestamp: new Date(), color: 'success' },
{ id: '2', action: 'System backup completed', timestamp: new Date(), detail: <pre>Details...</pre> },
]}
hasMore
onLoadMore={() => fetchMore()}
compact
/>
Exported Utilities
groupItemsByTime(items: ActivityItem[], labels?: GroupLabels) — pure function that buckets items into time groups; returns { label: string, items: ActivityItem[] }[]
Composability
- Built from ui primitives: Avatar (actor), Button (Load more), Skeleton (loading), Text (body). Override via
renderItemto use your own primitives per row. - renderItem is the composition hook — return your own JSX or
undefinedto fall back to default ActivityEntry. Timeline dot + layout wrapper stay consistent. - groupBy="time" wraps items in Today/Yesterday/This Week/Older buckets;
groupItemsByTime()export reusable for custom renderers. - Pagination is consumer-driven —
hasMore+onLoadMorefor server-side, ormaxInitialItems+ "Show all" toggle for client-side truncation.
Gotchas
itemsis required — passing an empty array renders theemptyStatecontentcoloron each item controls the timeline dot coloractor.imageis optional — falls back to initials fromactor.namemaxInitialItemstruncates with a "Show all (N)" toggle buttonmaxInitialItemsapplies to the flat list BEFORE grouping — items are sliced first, then grouped- Empty time groups are automatically skipped
renderItemreceives the item and index; returnundefinedto use the default ActivityEntry rendering- Custom
renderItemcontent is wrapped in the same dot + layout container as default entries for consistent vertical rhythm
Changes
v0.29.0
- Added
renderItemprop — custom render function per item; return ReactNode for custom rendering, returnundefinedto fall back to default ActivityEntry - Added Internal
CustomEntrywrapper that keeps dot + layout consistent with default entries
v0.20.0
- Added
groupBy="time"prop — groups items into Today, Yesterday, This Week, Older with section headers - Added
groupLabelsprop for custom group label text - Added
groupItemsByTime()exported pure utility function
v0.18.0
- Fixed
bg-accent-9changed tobg-info-9(info color, not accent)
v0.16.0
- Added Initial release — vertical timeline with colored dots, actor avatars, expandable detail, compact mode, load more, maxInitialItems truncation