ActivityPanel
Activity feed panel component for displaying event streams
ActivityPanel
The ActivityPanel component displays a chronological feed of user and system activities. It's commonly used to show recent actions, updates, notifications, and events in your application.
Type Signature
interface ActivityPanelProps {
/** Panel title */
title?: string
/** Panel description */
description?: string
/** Activity items */
activities: Array<{
id: string
type: string
actor: string
action: string
target?: string
timestamp: string
metadata?: any
}>
/** Group activities by time */
groupByTime?: boolean
/** Show actor avatars */
showAvatars?: boolean
/** Activity click handler */
onActivityClick?: (activity: Activity) => void
/** Panel className */
className?: string
/** Collapsible panel */
collapsible?: boolean
/** Start collapsed */
defaultCollapsed?: boolean
}Usage
<ActivityPanel
title="Recent Activity"
activities={[
{
id: '1',
type: 'comment',
actor: 'John Doe',
action: 'commented on',
target: 'API Documentation',
timestamp: '2024-01-15T10:30:00Z',
},
{
id: '2',
type: 'update',
actor: 'Jane Smith',
action: 'updated',
target: 'User Settings',
timestamp: '2024-01-15T09:15:00Z',
},
{
id: '3',
type: 'create',
actor: 'System',
action: 'created',
target: 'New API Key',
timestamp: '2024-01-15T08:00:00Z',
},
]}
groupByTime={true}
showAvatars={true}
/>Props
title
The panel title displayed in the header.
<ActivityPanel title="Recent Activity" activities={activities} />description
Optional panel description or subtitle.
<ActivityPanel
title="Team Activity"
description="Latest updates from your team members"
activities={activities}
/>activities
Array of activity items to display. Each activity includes actor, action, target, and timestamp.
<ActivityPanel
activities={[
{
id: '1',
type: 'comment',
actor: 'John Doe',
action: 'commented on',
target: 'Issue #123',
timestamp: '2024-01-15T10:30:00Z',
metadata: {
avatar: 'https://...',
comment: 'Great work on this!',
},
},
{
id: '2',
type: 'update',
actor: 'Jane Smith',
action: 'updated',
target: 'Project Settings',
timestamp: '2024-01-15T09:15:00Z',
metadata: {
avatar: 'https://...',
changes: ['name', 'description'],
},
},
]}
/>groupByTime
Group activities by time periods (Today, Yesterday, This Week, etc.). Defaults to true.
<ActivityPanel
groupByTime={true}
activities={activities}
/>showAvatars
Display actor avatars next to activities. Defaults to true.
<ActivityPanel
showAvatars={true}
activities={activities}
/>onActivityClick
Callback when an activity item is clicked.
<ActivityPanel
activities={activities}
onActivityClick={(activity) => {
console.log('Clicked activity:', activity)
navigateToActivity(activity)
}}
/>Common Patterns
User Activity Feed
<ActivityPanel
title="Your Activity"
activities={[
{
id: '1',
type: 'login',
actor: 'You',
action: 'logged in',
timestamp: '2024-01-15T10:30:00Z',
},
{
id: '2',
type: 'update',
actor: 'You',
action: 'updated',
target: 'Profile Settings',
timestamp: '2024-01-15T09:00:00Z',
},
{
id: '3',
type: 'create',
actor: 'You',
action: 'created',
target: 'New Project',
timestamp: '2024-01-14T16:45:00Z',
},
]}
/>Team Activity Feed
<ActivityPanel
title="Team Activity"
showAvatars={true}
activities={[
{
id: '1',
type: 'commit',
actor: 'Alice Johnson',
action: 'pushed 3 commits to',
target: 'main branch',
timestamp: '2024-01-15T11:00:00Z',
metadata: {
avatar: 'https://...',
commits: 3,
},
},
{
id: '2',
type: 'review',
actor: 'Bob Williams',
action: 'reviewed',
target: 'Pull Request #42',
timestamp: '2024-01-15T10:30:00Z',
metadata: {
avatar: 'https://...',
status: 'approved',
},
},
{
id: '3',
type: 'issue',
actor: 'Carol Davis',
action: 'opened',
target: 'Issue #256',
timestamp: '2024-01-15T09:15:00Z',
metadata: {
avatar: 'https://...',
priority: 'high',
},
},
]}
/>System Events
<ActivityPanel
title="System Events"
activities={[
{
id: '1',
type: 'deploy',
actor: 'System',
action: 'deployed',
target: 'v2.1.0 to production',
timestamp: '2024-01-15T12:00:00Z',
metadata: {
status: 'success',
duration: '2m 34s',
},
},
{
id: '2',
type: 'backup',
actor: 'System',
action: 'completed backup of',
target: 'Database',
timestamp: '2024-01-15T06:00:00Z',
metadata: {
size: '2.4 GB',
},
},
{
id: '3',
type: 'alert',
actor: 'System',
action: 'sent alert:',
target: 'High CPU usage detected',
timestamp: '2024-01-15T03:30:00Z',
metadata: {
severity: 'warning',
},
},
]}
/>API Request Log
<ActivityPanel
title="Recent Requests"
showAvatars={false}
activities={[
{
id: '1',
type: 'api_request',
actor: 'API Key: pk_live_...',
action: 'POST',
target: '/api/v1/users',
timestamp: '2024-01-15T10:30:45Z',
metadata: {
status: 200,
duration: '45ms',
},
},
{
id: '2',
type: 'api_request',
actor: 'API Key: pk_live_...',
action: 'GET',
target: '/api/v1/users/123',
timestamp: '2024-01-15T10:30:42Z',
metadata: {
status: 200,
duration: '23ms',
},
},
]}
/>Time Grouping
When groupByTime is enabled, activities are grouped into time periods:
Today
Today
─────────────────────────
• John commented on Issue #123
2 hours ago
• Jane updated Project Settings
3 hours agoYesterday
Yesterday
─────────────────────────
• Bob pushed 3 commits
Yesterday at 4:30 PM
• Alice opened Pull Request #42
Yesterday at 2:15 PMThis Week
This Week
─────────────────────────
• Carol created New API Key
Monday at 9:00 AM
• System deployed v2.1.0
Sunday at 6:00 PMEarlier
Earlier
─────────────────────────
• Team backup completed
Jan 10
• User logged in
Jan 8Activity Types
Different activity types can have custom styling and icons:
const activityTypes = {
comment: { icon: 'MessageSquare', color: 'blue' },
update: { icon: 'Edit', color: 'green' },
create: { icon: 'Plus', color: 'purple' },
delete: { icon: 'Trash', color: 'red' },
login: { icon: 'LogIn', color: 'gray' },
logout: { icon: 'LogOut', color: 'gray' },
deploy: { icon: 'Rocket', color: 'green' },
error: { icon: 'AlertCircle', color: 'red' },
warning: { icon: 'AlertTriangle', color: 'yellow' },
}
<ActivityPanel
activities={activities}
renderActivity={(activity) => {
const config = activityTypes[activity.type]
return (
<ActivityItem
icon={config.icon}
color={config.color}
activity={activity}
/>
)
}}
/>Panel Context Usage
ActivityPanel can be used in different contexts:
Dashboard Widget
Embedded in dashboard:
<DashboardView>
<div className="grid grid-cols-3 gap-4">
<div className="col-span-2">
<MetricsPanel />
</div>
<div className="col-span-1">
<ActivityPanel
title="Recent Activity"
activities={recentActivities.slice(0, 10)}
/>
</div>
</div>
</DashboardView>Slideover Panel
Show full activity feed in slideover:
<Shell>
<DashboardView>
<Button onClick={() => setShowActivity(true)}>
View Activity
</Button>
</DashboardView>
<Slideover open={showActivity} onClose={() => setShowActivity(false)}>
<ActivityPanel
title="All Activity"
activities={allActivities}
groupByTime={true}
/>
</Slideover>
</Shell>Inline Panel
Side panel in layout:
<div className="flex h-screen">
<div className="flex-1">
<MainContent />
</div>
<div className="w-80 border-l">
<ActivityPanel
title="Activity Feed"
activities={activities}
/>
</div>
</div>Modal Dialog
Activity details in modal:
<Dialog open={showDetails} onClose={() => setShowDetails(false)}>
<ActivityPanel
title="Activity Details"
activities={selectedActivity ? [selectedActivity] : []}
/>
</Dialog>Real-Time Updates
Implement real-time activity updates:
function RealtimeActivityPanel() {
const [activities, setActivities] = useState([])
useEffect(() => {
// Subscribe to real-time updates
const unsubscribe = subscribeToActivities((newActivity) => {
setActivities((prev) => [newActivity, ...prev])
})
return () => unsubscribe()
}, [])
return (
<ActivityPanel
title="Live Activity"
activities={activities}
groupByTime={true}
/>
)
}Infinite Scroll
Load more activities on scroll:
function InfiniteActivityPanel() {
const [activities, setActivities] = useState([])
const [hasMore, setHasMore] = useState(true)
const loadMore = async () => {
const newActivities = await fetchActivities({
offset: activities.length,
limit: 20,
})
setActivities([...activities, ...newActivities])
setHasMore(newActivities.length === 20)
}
return (
<ActivityPanel
title="Activity Feed"
activities={activities}
onScroll={(e) => {
const { scrollTop, scrollHeight, clientHeight } = e.target
if (scrollHeight - scrollTop === clientHeight && hasMore) {
loadMore()
}
}}
/>
)
}Empty States
Handle cases with no activities:
<ActivityPanel
title="Activity"
activities={activities}
emptyState={
<div className="text-center py-8">
<p className="text-muted-foreground">No recent activity</p>
<p className="text-sm text-muted-foreground mt-2">
Activity will appear here when users take actions
</p>
</div>
}
/>Filtering Activities
Allow filtering by activity type:
function FilterableActivityPanel() {
const [filter, setFilter] = useState('all')
const [activities, setActivities] = useState([])
const filteredActivities = activities.filter(
(a) => filter === 'all' || a.type === filter
)
return (
<>
<Select value={filter} onValueChange={setFilter}>
<option value="all">All Activity</option>
<option value="comment">Comments</option>
<option value="update">Updates</option>
<option value="create">Created</option>
<option value="delete">Deleted</option>
</Select>
<ActivityPanel
title="Activity Feed"
activities={filteredActivities}
/>
</>
)
}Accessibility
ActivityPanel implements accessibility best practices:
- Semantic HTML - Uses
<article>,<time>elements - ARIA labels - Descriptive labels for screen readers
- Keyboard navigation - All interactive elements keyboard accessible
- Time formatting - Human-readable relative times with ISO fallback
<aside role="complementary" aria-labelledby="activity-panel-title">
<h2 id="activity-panel-title">Recent Activity</h2>
<ul role="list">
<li>
<article>
<p>
<strong>John Doe</strong> commented on <a href="#">Issue #123</a>
</p>
<time datetime="2024-01-15T10:30:00Z">2 hours ago</time>
</article>
</li>
</ul>
</aside>Best Practices
Limit Initial Items
Show a reasonable number of activities initially:
// Good - show 10 most recent
<ActivityPanel
activities={activities.slice(0, 10)}
/>
// Bad - showing too many can impact performance
<ActivityPanel
activities={activities} // 1000+ items
/>Use Relative Time
Display timestamps as relative time for recent activities:
const formatTime = (timestamp) => {
const now = new Date()
const then = new Date(timestamp)
const seconds = Math.floor((now - then) / 1000)
if (seconds < 60) return 'just now'
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`
return then.toLocaleDateString()
}Group Related Activities
Combine similar activities to reduce noise:
// Instead of:
// • John liked Post #1
// • John liked Post #2
// • John liked Post #3
// Show:
// • John liked 3 postsProvide Context
Include enough information to understand the activity:
// Good - clear context
{
actor: 'John Doe',
action: 'commented on',
target: 'Pull Request #42: Fix login bug',
}
// Bad - unclear context
{
actor: 'John',
action: 'commented',
target: '#42',
}Related Components
- DetailPanel - Detail side panel component
- SearchBox - Modal search widget
- ChatBox - Slideover chat widget
- CommandPalette - Keyboard commands widget