Activity feed
A chronological “who did what, when” list: an avatar per entry, the
actor’s name in the sentence, and a muted timestamp underneath. A thin
vertical line connects the avatars into a timeline, drawn only between
entries so it stops at the last one. The action is a ReactNode, so
you can bold or link the object that was acted on.
'use client';
import type { ReactNode } from 'react';import { Avatar, Flex, Stack, Text } from '@helix-ui/core';
export interface ActivityItem { id: string; actor: { name: string; initials: string; avatarUrl?: string }; action: ReactNode; timestamp: string;}
export function ActivityFeed({ items }: { items: ActivityItem[] }) { return ( <Stack gap={0}> {items.map((item, i) => ( <Flex key={item.id} gap={3} align="start"> <Stack align="center" gap={2} style={{ alignSelf: 'stretch' }}> <Avatar size="sm" fallback={item.actor.initials} src={item.actor.avatarUrl} alt={item.actor.name} /> {i < items.length - 1 ? ( <div style={{ flex: 1, width: 2, minHeight: 20, background: 'var(--helix-ui-color-border-default)', }} aria-hidden /> ) : null} </Stack>
<Stack gap={1} style={{ paddingBottom: 'var(--helix-ui-space-5)' }}> <Text size="sm"> <Text as="span" weight="semibold"> {item.actor.name} </Text>{' '} {item.action} </Text> <Text size="xs" tone="muted"> {item.timestamp} </Text> </Stack> </Flex> ))} </Stack> );}Usage
<ActivityFeed items={[ { id: '1', actor: { name: 'Sara Tang', initials: 'ST' }, action: ( <> merged{' '} <Text as="span" weight="medium"> #482 </Text>{' '} into main </> ), timestamp: '2 minutes ago', }, { id: '2', actor: { name: 'Diego Ruiz', initials: 'DR' }, action: 'commented on the release checklist', timestamp: '18 minutes ago', }, { id: '3', actor: { name: 'Amara Okafor', initials: 'AO' }, action: 'closed the milestone', timestamp: '1 hour ago', }, ]}/>Notes
- Timestamps are pre-formatted strings — format them upstream (relative or absolute) so this block stays presentational.
- For long feeds, paginate or cap the list; a timeline is for the recent past, not an audit log.