Shilp Sutra
composed · Composed pattern

ActivityFeed

@devalok/shilp-sutra/composed/activity-feedView in Storybook

Preview

AK
Aisha Kapoor shipped the Orbit redesign
BC
Ben Carter merged ds-link-button into main
CW
Chen Wei
DA
Diego Alvarez joined the workspace

Variants

compact
Aisha Kapoor shipped the Orbit redesign
Ben Carter merged ds-link-button into main
Chen Wei
Diego Alvarez joined the workspace
empty state
No activity yet.

Reference

  • Import: @devalok/shilp-sutra/composed/activity-feed
  • Server-safe: No
  • Category: composed

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 renderItem to use your own primitives per row.
  • renderItem is the composition hook — return your own JSX or undefined to 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-drivenhasMore + onLoadMore for server-side, or maxInitialItems + "Show all" toggle for client-side truncation.

Gotchas

  • items is required — passing an empty array renders the emptyState content
  • color on each item controls the timeline dot color
  • actor.image is optional — falls back to initials from actor.name
  • maxInitialItems truncates with a "Show all (N)" toggle button
  • maxInitialItems applies to the flat list BEFORE grouping — items are sliced first, then grouped
  • Empty time groups are automatically skipped
  • renderItem receives the item and index; return undefined to use the default ActivityEntry rendering
  • Custom renderItem content is wrapped in the same dot + layout container as default entries for consistent vertical rhythm

Changes

v0.29.0

  • Added renderItem prop — custom render function per item; return ReactNode for custom rendering, return undefined to fall back to default ActivityEntry
  • Added Internal CustomEntry wrapper 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 groupLabels prop for custom group label text
  • Added groupItemsByTime() exported pure utility function

v0.18.0

  • Fixed bg-accent-9 changed to bg-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