{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "activity-feed", "title": "Activity Feed", "description": "A block for displaying a list of recent activities with rough styling.", "dependencies": [ "roughjs" ], "registryDependencies": [ "https://www.bydefaulthuman.fun/r/use-rough.json", "utils", "https://www.bydefaulthuman.fun/r/crumble-theme.json", "https://www.bydefaulthuman.fun/r/rough-lib.json", "https://www.bydefaulthuman.fun/r/avatar.json", "https://www.bydefaulthuman.fun/r/button.json", "https://www.bydefaulthuman.fun/r/badge.json", "https://www.bydefaulthuman.fun/r/card.json", "https://www.bydefaulthuman.fun/r/timeline.json" ], "files": [ { "path": "registry/new-york/blocks/ActivityFeedBlock.tsx", "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { Avatar } from \"@/components/crumble/ui/avatar\";\nimport { Badge } from \"@/components/crumble/ui/badge\";\nimport { Button } from \"@/components/crumble/ui/button\";\nimport { Card } from \"@/components/crumble/ui/card\";\nimport {\n Timeline,\n TimelineItem,\n type TimelineStatus,\n} from \"@/components/crumble/ui/timeline\";\n\ntype TagVariant = \"default\" | \"success\" | \"warning\" | \"destructive\" | \"outline\";\n\nexport interface FeedEvent {\n action: string;\n id: number | string;\n read?: boolean;\n tag: string;\n tagVariant?: TagVariant;\n target: string;\n time: string;\n userFallback: string;\n userName: string;\n}\n\nexport interface ActivityFeedBlockProps {\n animate?: boolean;\n animationStaggerMs?: number;\n events?: FeedEvent[];\n onEventClick?: (event: FeedEvent) => void;\n title?: string;\n}\n\nconst DEFAULT_EVENTS: FeedEvent[] = [\n {\n id: 1,\n userFallback: \"AC\",\n userName: \"Alice Chen\",\n action: \"merged pull request\",\n target: \"feat/rough-tooltip\",\n time: \"2 min ago\",\n tag: \"merge\",\n tagVariant: \"success\",\n read: false,\n },\n {\n id: 2,\n userFallback: \"BM\",\n userName: \"Bob Marsh\",\n action: \"opened issue\",\n target: \"Button hover flicker on Safari\",\n time: \"14 min ago\",\n tag: \"bug\",\n tagVariant: \"destructive\",\n read: false,\n },\n {\n id: 3,\n userFallback: \"CD\",\n userName: \"Carol Dana\",\n action: \"commented on\",\n target: \"StatCard trend arrow\",\n time: \"1 hr ago\",\n tag: \"comment\",\n tagVariant: \"outline\",\n read: true,\n },\n {\n id: 4,\n userFallback: \"DP\",\n userName: \"Dev Patel\",\n action: \"deployed\",\n target: \"production v0.4.2\",\n time: \"3 hr ago\",\n tag: \"deploy\",\n tagVariant: \"warning\",\n read: true,\n },\n];\n\nexport function ActivityFeedBlock({\n animate = true,\n animationStaggerMs = 700,\n events = DEFAULT_EVENTS,\n onEventClick,\n title = \"Activity Feed\",\n}: ActivityFeedBlockProps) {\n const [items, setItems] = useState(events);\n const [activeIndex, setActiveIndex] = useState(-1);\n const timersRef = useRef[]>([]);\n\n useEffect(() => {\n setItems(events);\n }, [events]);\n\n const runAnimation = () => {\n if (!animate) return;\n timersRef.current.forEach(clearTimeout);\n timersRef.current = [];\n setActiveIndex(-1);\n items.forEach((_, index) => {\n const timer = setTimeout(\n () => setActiveIndex(index),\n (index + 1) * animationStaggerMs,\n );\n timersRef.current.push(timer);\n });\n };\n\n useEffect(() => {\n runAnimation();\n return () => timersRef.current.forEach(clearTimeout);\n }, [animate, animationStaggerMs, items]);\n\n const getStatus = (index: number): TimelineStatus => {\n if (!animate) return \"complete\";\n if (index < activeIndex) return \"complete\";\n if (index === activeIndex) return \"active\";\n return \"pending\";\n };\n\n const unreadCount = items.filter((item) => !item.read).length;\n\n return (\n \n
\n
\n

{title}

\n {unreadCount > 0 ? {unreadCount} : null}\n
\n
\n {unreadCount > 0 ? (\n setItems((current) => current.map((item) => ({ ...item, read: true })))}\n >\n Mark all read\n \n ) : null}\n \n Live\n \n
\n
\n\n \n {items.map((event, index) => (\n onEventClick?.(event)}\n type=\"button\"\n >\n \n \n {event.userName}\n \n {event.action}\n \n {event.target}\n \n {!event.read ? (\n \n ) : null}\n \n }\n description={\n \n \n {event.tag}\n \n {event.time}\n \n }\n />\n ))}\n \n\n {animate ? (\n \n Replay\n \n ) : null}\n
\n );\n}\n", "type": "registry:ui", "target": "components/crumble/blocks/activity-feed.tsx" } ], "type": "registry:block" }