@echothink-ui/activity 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +5 -0
  2. package/dist/components/ActivityFeed.d.ts +7 -0
  3. package/dist/components/ActivityTimeline.d.ts +6 -0
  4. package/dist/components/AlertBanner.d.ts +10 -0
  5. package/dist/components/ChangelogPanel.d.ts +6 -0
  6. package/dist/components/IncidentPanel.d.ts +6 -0
  7. package/dist/components/MentionList.d.ts +7 -0
  8. package/dist/components/NotificationCenter.d.ts +10 -0
  9. package/dist/components/NotificationItem.d.ts +8 -0
  10. package/dist/components/SubscriptionPreferences.d.ts +7 -0
  11. package/dist/components/SystemStatusBanner.d.ts +9 -0
  12. package/dist/components/WatcherList.d.ts +8 -0
  13. package/dist/components/helpers.d.ts +4 -0
  14. package/dist/components/types.d.ts +65 -0
  15. package/dist/index.cjs +944 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.css +711 -0
  18. package/dist/index.css.map +1 -0
  19. package/dist/index.d.ts +16 -0
  20. package/dist/index.js +904 -0
  21. package/dist/index.js.map +1 -0
  22. package/package.json +43 -0
  23. package/src/components/ActivityFeed.tsx +83 -0
  24. package/src/components/ActivityTimeline.tsx +178 -0
  25. package/src/components/AlertBanner.tsx +69 -0
  26. package/src/components/ChangelogPanel.tsx +100 -0
  27. package/src/components/IncidentPanel.tsx +82 -0
  28. package/src/components/MentionList.tsx +85 -0
  29. package/src/components/NotificationCenter.tsx +117 -0
  30. package/src/components/NotificationItem.tsx +99 -0
  31. package/src/components/SubscriptionPreferences.test.tsx +64 -0
  32. package/src/components/SubscriptionPreferences.tsx +140 -0
  33. package/src/components/SystemStatusBanner.tsx +46 -0
  34. package/src/components/WatcherList.test.tsx +50 -0
  35. package/src/components/WatcherList.tsx +122 -0
  36. package/src/components/helpers.ts +15 -0
  37. package/src/components/types.ts +71 -0
  38. package/src/index.tsx +31 -0
  39. package/src/styles.css +854 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/NotificationCenter.tsx","../src/components/NotificationItem.tsx","../src/components/helpers.ts","../src/components/ActivityFeed.tsx","../src/components/ActivityTimeline.tsx","../src/components/AlertBanner.tsx","../src/components/SystemStatusBanner.tsx","../src/components/IncidentPanel.tsx","../src/components/ChangelogPanel.tsx","../src/components/SubscriptionPreferences.tsx","../src/components/MentionList.tsx","../src/components/WatcherList.tsx","../src/index.tsx"],"sourcesContent":["import { Button, Surface, type SurfaceComponentProps } from \"@echothink-ui/core\";\nimport { NotificationItem as NotificationRow } from \"./NotificationItem\";\nimport type { NotificationItemData } from \"./types\";\n\nexport interface NotificationCenterProps extends Omit<SurfaceComponentProps, \"children\"> {\n notifications: NotificationItemData[];\n unreadCount?: number;\n onMarkRead?: (id: string) => void;\n onMarkAllRead?: () => void;\n onDismiss?: (id: string) => void;\n}\n\nexport function NotificationCenter({\n notifications,\n unreadCount,\n onMarkRead,\n onMarkAllRead,\n onDismiss,\n title,\n className,\n role,\n \"aria-label\": ariaLabel,\n ...props\n}: NotificationCenterProps) {\n const groups = groupByDate(notifications);\n const unreadTotal = unreadCount ?? notifications.filter((item) => !item.read).length;\n const displayTitle = title ?? \"Notifications\";\n const regionLabel =\n ariaLabel ?? (typeof displayTitle === \"string\" ? displayTitle : \"Notification center\");\n\n return (\n <Surface\n {...props}\n title={displayTitle}\n subtitle={`${unreadTotal} unread`}\n className={[\"eth-activity-notification-center\", className].filter(Boolean).join(\" \")}\n data-eth-component=\"NotificationCenter\"\n role={role ?? \"region\"}\n aria-label={regionLabel}\n >\n {onMarkAllRead ? (\n <div className=\"eth-activity-notification-center__toolbar\">\n <Button\n type=\"button\"\n intent=\"secondary\"\n density=\"compact\"\n disabled={unreadTotal === 0}\n onClick={onMarkAllRead}\n >\n Mark all read\n </Button>\n </div>\n ) : null}\n {notifications.length ? (\n <div className=\"eth-activity-notification-center__groups\">\n {groups.map((group) => (\n <section key={group.date} className=\"eth-activity-notification-center__group\">\n <h3>{formatGroupLabel(group.date)}</h3>\n <ol className=\"eth-activity-notification-center__items\">\n {group.items.map((notification) => (\n <li key={notification.id} className=\"eth-activity-notification-center__row\">\n <NotificationRow\n notification={notification}\n onRead={\n onMarkRead && !notification.read\n ? () => onMarkRead(notification.id)\n : undefined\n }\n />\n {onDismiss ? (\n <Button\n type=\"button\"\n intent=\"ghost\"\n density=\"compact\"\n onClick={() => onDismiss(notification.id)}\n >\n Dismiss\n </Button>\n ) : null}\n </li>\n ))}\n </ol>\n </section>\n ))}\n </div>\n ) : (\n <p className=\"eth-activity-notification-center__empty\">No notifications.</p>\n )}\n </Surface>\n );\n}\n\nfunction groupByDate(notifications: NotificationItemData[]) {\n const groups = new Map<string, NotificationItemData[]>();\n for (const notification of notifications) {\n const key = groupKey(notification.createdAt);\n groups.set(key, [...(groups.get(key) ?? []), notification]);\n }\n return Array.from(groups, ([date, items]) => ({ date, items }));\n}\n\nfunction groupKey(value: string) {\n const date = new Date(value);\n return Number.isFinite(date.valueOf()) ? date.toISOString().slice(0, 10) : \"Recent\";\n}\n\nfunction formatGroupLabel(value: string) {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n\n return new Intl.DateTimeFormat(undefined, {\n day: \"numeric\",\n month: \"short\",\n timeZone: \"UTC\",\n weekday: \"short\",\n year: \"numeric\"\n }).format(new Date(`${value}T00:00:00Z`));\n}\n","import { ActionGroup, Badge, Button, type SurfaceComponentProps } from \"@echothink-ui/core\";\nimport { severityToCore } from \"./helpers\";\nimport type { NotificationItemData } from \"./types\";\n\nexport interface NotificationItemProps extends Omit<SurfaceComponentProps, \"children\"> {\n notification: NotificationItemData;\n onRead?: () => void;\n onClick?: () => void;\n}\n\nexport function NotificationItem({\n notification,\n onRead,\n onClick,\n className,\n title: _title,\n subtitle: _subtitle,\n description: _description,\n eyebrow: _eyebrow,\n density: _density,\n status: _status,\n severity: _severity,\n loading: _loading,\n empty: _empty,\n error: _error,\n items: _items,\n actions: _actions,\n metadata: _metadata,\n footer: _footer,\n ...props\n}: NotificationItemProps) {\n const severity = severityToCore(notification.severity);\n const mainContent = (\n <>\n <Badge className=\"eth-activity-notification-item__severity\" severity={severity}>\n {notification.severity}\n </Badge>\n <span className=\"eth-activity-notification-item__content\">\n <strong className=\"eth-activity-notification-item__title\">{notification.title}</strong>\n {notification.body ? (\n <span className=\"eth-activity-notification-item__body\">{notification.body}</span>\n ) : null}\n </span>\n <time\n className=\"eth-activity-notification-item__time\"\n dateTime={dateTimeValue(notification.createdAt)}\n >\n {formatTimestamp(notification.createdAt)}\n </time>\n </>\n );\n const hasActions = Boolean((!notification.read && onRead) || notification.actions?.length);\n\n return (\n <article\n {...props}\n className={[\n \"eth-activity-notification-item\",\n notification.read ? \"eth-activity-notification-item--read\" : undefined,\n className\n ]\n .filter(Boolean)\n .join(\" \")}\n data-eth-component=\"NotificationItem\"\n data-severity={severity}\n >\n {onClick ? (\n <button type=\"button\" className=\"eth-activity-notification-item__main\" onClick={onClick}>\n {mainContent}\n </button>\n ) : (\n <div className=\"eth-activity-notification-item__main\">{mainContent}</div>\n )}\n {hasActions ? (\n <div className=\"eth-activity-notification-item__actions\">\n {!notification.read && onRead ? (\n <Button type=\"button\" intent=\"ghost\" density=\"compact\" onClick={onRead}>\n Mark read\n </Button>\n ) : null}\n <ActionGroup actions={notification.actions} />\n </div>\n ) : null}\n </article>\n );\n}\n\nfunction dateTimeValue(value: string) {\n const date = new Date(value);\n return Number.isFinite(date.valueOf()) ? date.toISOString() : undefined;\n}\n\nfunction formatTimestamp(value: string) {\n const date = new Date(value);\n if (!Number.isFinite(date.valueOf())) return value;\n\n const iso = date.toISOString();\n return `${iso.slice(11, 16)} UTC`;\n}\n","import type { EthSeverity } from \"@echothink-ui/core\";\nimport type { ActivitySeverity } from \"./types\";\n\nexport function severityToCore(severity: ActivitySeverity): EthSeverity {\n if (severity === \"error\" || severity === \"danger\") return \"danger\";\n if (severity === \"success\") return \"success\";\n if (severity === \"warning\") return \"warning\";\n return \"info\";\n}\n\nexport function dateGroupKey(value: string) {\n const date = new Date(value);\n if (!Number.isFinite(date.valueOf())) return value;\n return date.toISOString().slice(0, 10);\n}\n","import { Badge, Surface, type SurfaceComponentProps } from \"@echothink-ui/core\";\nimport type { ActivityEvent } from \"./types\";\n\nexport interface ActivityFeedProps extends Omit<SurfaceComponentProps, \"children\"> {\n events: ActivityEvent[];\n streaming?: boolean;\n}\n\nexport function ActivityFeed({ events, streaming, title, className, ...props }: ActivityFeedProps) {\n const hasEvents = events.length > 0;\n\n return (\n <Surface\n {...props}\n title={title ?? \"Activity\"}\n className={[\"eth-activity-feed\", className].filter(Boolean).join(\" \")}\n data-eth-component=\"ActivityFeed\"\n >\n {streaming ? (\n <div className=\"eth-activity-feed__status\" aria-label=\"Streaming activity\">\n <Badge severity=\"success\">Streaming</Badge>\n </div>\n ) : null}\n {hasEvents ? (\n <ol className=\"eth-activity-feed__events\" aria-live={streaming ? \"polite\" : undefined}>\n {events.map((event) => (\n <li\n key={event.id}\n className={`eth-activity-feed__event eth-activity-feed__event--${verbClass(event.verb)}`}\n >\n <span className=\"eth-activity-feed__icon\" aria-hidden>\n {iconForVerb(event.verb)}\n </span>\n <div className=\"eth-activity-feed__body\">\n <p className=\"eth-activity-feed__summary\">\n <strong>{event.actor}</strong> {event.verb} <strong>{event.objectLabel}</strong>\n {event.targetLabel ? (\n <>\n {\" \"}\n in <strong>{event.targetLabel}</strong>\n </>\n ) : null}\n </p>\n {event.details ? <p className=\"eth-activity-feed__details\">{event.details}</p> : null}\n <time className=\"eth-activity-feed__time\" dateTime={dateTimeValue(event.createdAt)}>\n {formatTimestamp(event.createdAt)}\n </time>\n </div>\n </li>\n ))}\n </ol>\n ) : (\n <p className=\"eth-activity-feed__empty\">No activity yet.</p>\n )}\n </Surface>\n );\n}\n\nfunction iconForVerb(verb: string) {\n const normalized = verb.toLowerCase();\n if (normalized.includes(\"create\") || normalized.includes(\"add\")) return \"+\";\n if (normalized.includes(\"delete\") || normalized.includes(\"remove\")) return \"-\";\n if (normalized.includes(\"approve\")) return \"✓\";\n if (normalized.includes(\"fail\") || normalized.includes(\"reject\")) return \"!\";\n return \"•\";\n}\n\nfunction verbClass(verb: string) {\n return verb.toLowerCase().replace(/[^a-z0-9]+/g, \"-\") || \"event\";\n}\n\nfunction dateTimeValue(value: string) {\n const date = new Date(value);\n return Number.isFinite(date.valueOf()) ? date.toISOString() : value;\n}\n\nfunction formatTimestamp(value: string) {\n const date = new Date(value);\n if (!Number.isFinite(date.valueOf())) return value;\n\n const iso = date.toISOString();\n return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;\n}\n","import { Badge, Surface, type EthSeverity, type SurfaceComponentProps } from \"@echothink-ui/core\";\nimport { dateGroupKey } from \"./helpers\";\nimport type { ActivityEvent } from \"./types\";\n\nexport interface ActivityTimelineProps extends Omit<SurfaceComponentProps, \"children\"> {\n events: ActivityEvent[];\n}\n\nexport function ActivityTimeline({\n events = [],\n title,\n subtitle,\n className,\n role,\n \"aria-label\": ariaLabel,\n ...props\n}: ActivityTimelineProps) {\n const groups = groupEvents(events);\n const eventCount = events.length;\n\n return (\n <Surface\n {...props}\n title={title ?? \"Timeline\"}\n subtitle={subtitle ?? summaryText(groups.length, eventCount)}\n className={[\"eth-activity-timeline\", className].filter(Boolean).join(\" \")}\n data-eth-component=\"ActivityTimeline\"\n role={role ?? \"region\"}\n aria-label={ariaLabel ?? \"Activity timeline\"}\n >\n {eventCount > 0 ? (\n <div className=\"eth-activity-timeline__content\">\n {groups.map((group) => (\n <section key={group.date} className=\"eth-activity-timeline__group\">\n <header className=\"eth-activity-timeline__group-header\">\n <h3>{formatGroupLabel(group.date)}</h3>\n <span>{summaryText(0, group.events.length)}</span>\n </header>\n <ol className=\"eth-activity-timeline__events\">\n {group.events.map((event) => (\n <li\n key={event.id}\n className={`eth-activity-timeline__event eth-activity-timeline__event--${verbClass(event.verb)}`}\n >\n <span className=\"eth-activity-timeline__marker\" aria-hidden>\n {iconForVerb(event.verb)}\n </span>\n <article className=\"eth-activity-timeline__card\">\n <header className=\"eth-activity-timeline__event-header\">\n <Badge severity={severityForVerb(event.verb)}>\n {kindLabel(event.verb)}\n </Badge>\n <time dateTime={dateTimeValue(event.createdAt)}>\n {formatTimestamp(event.createdAt)}\n </time>\n </header>\n <p className=\"eth-activity-timeline__summary\">\n <strong>{event.actor}</strong> {event.verb}{\" \"}\n <strong>{event.objectLabel}</strong>\n {event.targetLabel ? (\n <>\n {\" \"}\n in <strong>{event.targetLabel}</strong>\n </>\n ) : null}\n </p>\n {event.details ? (\n <p className=\"eth-activity-timeline__details\">{event.details}</p>\n ) : null}\n </article>\n </li>\n ))}\n </ol>\n </section>\n ))}\n </div>\n ) : (\n <p className=\"eth-activity-timeline__empty\">No activity yet.</p>\n )}\n </Surface>\n );\n}\n\nfunction groupEvents(events: ActivityEvent[]) {\n const groups = new Map<string, ActivityEvent[]>();\n const sortedEvents = events\n .map((event, index) => ({ event, index }))\n .sort((a, b) => compareEvents(a, b))\n .map(({ event }) => event);\n\n for (const event of sortedEvents) {\n const key = dateGroupKey(event.createdAt);\n groups.set(key, [...(groups.get(key) ?? []), event]);\n }\n return Array.from(groups, ([date, groupedEvents]) => ({ date, events: groupedEvents }));\n}\n\nfunction compareEvents(\n a: { event: ActivityEvent; index: number },\n b: { event: ActivityEvent; index: number }\n) {\n const aTime = timestampValue(a.event.createdAt);\n const bTime = timestampValue(b.event.createdAt);\n\n if (aTime !== null && bTime !== null && aTime !== bTime) return bTime - aTime;\n if (aTime !== null && bTime === null) return -1;\n if (aTime === null && bTime !== null) return 1;\n return a.index - b.index;\n}\n\nfunction timestampValue(value: string) {\n const timestamp = new Date(value).getTime();\n return Number.isFinite(timestamp) ? timestamp : null;\n}\n\nfunction dateTimeValue(value: string) {\n const date = new Date(value);\n return Number.isFinite(date.valueOf()) ? date.toISOString() : value;\n}\n\nfunction formatGroupLabel(value: string) {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n\n return new Intl.DateTimeFormat(undefined, {\n day: \"numeric\",\n month: \"short\",\n timeZone: \"UTC\",\n weekday: \"short\",\n year: \"numeric\"\n }).format(new Date(`${value}T00:00:00Z`));\n}\n\nfunction formatTimestamp(value: string) {\n const date = new Date(value);\n if (!Number.isFinite(date.valueOf())) return value;\n\n const iso = date.toISOString();\n return `${iso.slice(11, 16)} UTC`;\n}\n\nfunction summaryText(groupCount: number, eventCount: number) {\n const eventLabel = `${eventCount} ${eventCount === 1 ? \"event\" : \"events\"}`;\n if (groupCount <= 0) return eventLabel;\n return `${eventLabel} across ${groupCount} ${groupCount === 1 ? \"day\" : \"days\"}`;\n}\n\nfunction iconForVerb(verb: string) {\n const normalized = verb.toLowerCase();\n if (normalized.includes(\"approve\")) return \"✓\";\n if (normalized.includes(\"draft\") || normalized.includes(\"create\") || normalized.includes(\"add\")) {\n return \"+\";\n }\n if (normalized.includes(\"comment\")) return \"…\";\n if (normalized.includes(\"fail\") || normalized.includes(\"reject\")) return \"!\";\n return \"•\";\n}\n\nfunction kindLabel(verb: string) {\n const normalized = verb.trim().toLowerCase();\n if (normalized.includes(\"approve\")) return \"Approval\";\n if (normalized.includes(\"draft\")) return \"Draft\";\n if (normalized.includes(\"comment\")) return \"Comment\";\n if (normalized.includes(\"create\") || normalized.includes(\"add\")) return \"Created\";\n if (normalized.includes(\"fail\") || normalized.includes(\"reject\")) return \"Attention\";\n return \"Update\";\n}\n\nfunction severityForVerb(verb: string): EthSeverity {\n const normalized = verb.toLowerCase();\n if (normalized.includes(\"approve\")) return \"success\";\n if (normalized.includes(\"fail\") || normalized.includes(\"reject\")) return \"danger\";\n if (normalized.includes(\"comment\")) return \"info\";\n return \"neutral\";\n}\n\nfunction verbClass(verb: string) {\n return verb.toLowerCase().replace(/[^a-z0-9]+/g, \"-\") || \"event\";\n}\n","import {\n ActionGroup,\n Button,\n InlineNotification,\n type EthAction,\n type SurfaceComponentProps\n} from \"@echothink-ui/core\";\nimport { severityToCore } from \"./helpers\";\nimport type { ActivitySeverity } from \"./types\";\n\nexport interface AlertBannerProps extends Omit<\n SurfaceComponentProps,\n \"children\" | \"actions\" | \"severity\"\n> {\n severity: ActivitySeverity;\n title: string;\n description?: string;\n onDismiss?: () => void;\n actions?: EthAction[];\n}\n\nexport function AlertBanner({\n severity,\n title,\n description,\n onDismiss,\n actions,\n className,\n role,\n ...props\n}: AlertBannerProps) {\n const coreSeverity = severityToCore(severity);\n const hasActions = Boolean(actions?.length || onDismiss);\n const ariaLive = props[\"aria-live\"] ?? (coreSeverity === \"danger\" ? \"assertive\" : \"polite\");\n const dataEthComponent =\n (props as { \"data-eth-component\"?: string })[\"data-eth-component\"] ?? \"AlertBanner\";\n\n return (\n <section\n {...props}\n className={[\n \"eth-activity-alert-banner\",\n `eth-activity-alert-banner--${coreSeverity}`,\n className\n ]\n .filter(Boolean)\n .join(\" \")}\n data-eth-component={dataEthComponent}\n role={role ?? (coreSeverity === \"danger\" ? \"alert\" : \"status\")}\n aria-live={ariaLive}\n >\n <div className=\"eth-activity-alert-banner__notice\">\n <InlineNotification severity={coreSeverity} title={title}>\n {description}\n </InlineNotification>\n </div>\n {hasActions ? (\n <div className=\"eth-activity-alert-banner__actions\">\n <ActionGroup actions={actions} />\n {onDismiss ? (\n <Button type=\"button\" intent=\"ghost\" density=\"compact\" onClick={onDismiss}>\n Dismiss\n </Button>\n ) : null}\n </div>\n ) : null}\n </section>\n );\n}\n","import type { EthAction, SurfaceComponentProps } from \"@echothink-ui/core\";\nimport { AlertBanner } from \"./AlertBanner\";\nimport type { ActivitySeverity } from \"./types\";\n\nexport interface SystemStatusBannerProps\n extends Omit<SurfaceComponentProps, \"children\" | \"actions\" | \"status\"> {\n status: \"operational\" | \"degraded\" | \"down\" | \"maintenance\";\n title?: string;\n description?: string;\n actions?: EthAction[];\n onDismiss?: () => void;\n}\n\nexport function SystemStatusBanner({\n status,\n title,\n description,\n actions,\n onDismiss,\n className\n}: SystemStatusBannerProps) {\n return (\n <AlertBanner\n severity={severityForStatus(status)}\n title={title ?? defaultTitleForStatus(status)}\n description={description}\n actions={actions}\n onDismiss={onDismiss}\n className={[\"eth-activity-system-status-banner\", className].filter(Boolean).join(\" \")}\n data-eth-component=\"SystemStatusBanner\"\n />\n );\n}\n\nfunction defaultTitleForStatus(status: SystemStatusBannerProps[\"status\"]) {\n if (status === \"operational\") return \"System operational\";\n if (status === \"degraded\") return \"System degraded\";\n if (status === \"maintenance\") return \"Scheduled maintenance\";\n return \"System unavailable\";\n}\n\nfunction severityForStatus(status: SystemStatusBannerProps[\"status\"]): ActivitySeverity {\n if (status === \"operational\") return \"success\";\n if (status === \"degraded\" || status === \"maintenance\") return \"warning\";\n return \"error\";\n}\n","import { Badge, Surface, type SurfaceComponentProps } from \"@echothink-ui/core\";\nimport { severityToCore } from \"./helpers\";\nimport type { ActivityIncident, ActivitySeverity } from \"./types\";\n\nexport interface IncidentPanelProps extends Omit<SurfaceComponentProps, \"children\"> {\n incidents: ActivityIncident[];\n}\n\nexport function IncidentPanel({ incidents, title, className, ...props }: IncidentPanelProps) {\n const hasIncidents = incidents.length > 0;\n\n return (\n <Surface\n {...props}\n title={title ?? \"Incidents\"}\n className={[\"eth-activity-incident-panel\", className].filter(Boolean).join(\" \")}\n data-eth-component=\"IncidentPanel\"\n >\n {hasIncidents ? (\n <div className=\"eth-activity-incident-panel__list\" role=\"list\">\n {incidents.map((incident) => (\n <article\n key={incident.id}\n className=\"eth-activity-incident-panel__incident\"\n data-severity={incident.severity ? severityToCore(incident.severity) : undefined}\n role=\"listitem\"\n aria-label={incident.title}\n >\n <header className=\"eth-activity-incident-panel__incident-header\">\n <div className=\"eth-activity-incident-panel__summary\">\n <h3>{incident.title}</h3>\n {incident.description ? <p>{incident.description}</p> : null}\n </div>\n {incident.severity ? (\n <Badge\n className=\"eth-activity-incident-panel__severity\"\n severity={severityToCore(incident.severity)}\n >\n {formatIncidentLabel(incident.severity)}\n </Badge>\n ) : null}\n </header>\n <dl className=\"eth-activity-incident-panel__metadata\">\n {incident.status ? (\n <div>\n <dt>Status</dt>\n <dd>\n <span\n className=\"eth-activity-incident-panel__status\"\n data-status={incident.status}\n >\n <span aria-hidden=\"true\" />\n {formatIncidentLabel(incident.status)}\n </span>\n </dd>\n </div>\n ) : null}\n {incident.startedAt ? (\n <div>\n <dt>Started</dt>\n <dd>\n <time>{incident.startedAt}</time>\n </dd>\n </div>\n ) : null}\n </dl>\n </article>\n ))}\n </div>\n ) : (\n <p className=\"eth-activity-incident-panel__empty\">No active incidents.</p>\n )}\n </Surface>\n );\n}\n\nfunction formatIncidentLabel(value: NonNullable<ActivityIncident[\"status\"]> | ActivitySeverity) {\n return value\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\" \");\n}\n","import { Badge, Surface, type EthSeverity, type SurfaceComponentProps } from \"@echothink-ui/core\";\nimport type { ChangelogEntry } from \"./types\";\n\nexport interface ChangelogPanelProps extends Omit<SurfaceComponentProps, \"children\"> {\n entries: ChangelogEntry[];\n}\n\nexport function ChangelogPanel({\n entries,\n title,\n subtitle,\n className,\n role,\n \"aria-label\": ariaLabel,\n ...props\n}: ChangelogPanelProps) {\n return (\n <Surface\n {...props}\n title={title ?? \"Changelog\"}\n subtitle={subtitle ?? changelogSummary(entries)}\n className={[\"eth-activity-changelog\", className].filter(Boolean).join(\" \")}\n data-eth-component=\"ChangelogPanel\"\n role={role ?? \"region\"}\n aria-label={ariaLabel ?? \"Product changelog\"}\n >\n {entries.length ? (\n <ol className=\"eth-activity-changelog__releases\">\n {entries.map((entry) => (\n <li key={entry.id} className=\"eth-activity-changelog__release\">\n <article\n className=\"eth-activity-changelog__entry\"\n aria-label={`Version ${entry.version}`}\n >\n <header className=\"eth-activity-changelog__entry-header\">\n <div>\n <h3>{entry.version}</h3>\n <time dateTime={dateTimeValue(entry.date)}>{entry.date}</time>\n </div>\n <span className=\"eth-activity-changelog__entry-count\">\n {pluralize(entry.changes.length, \"change\")}\n </span>\n </header>\n <ul className=\"eth-activity-changelog__changes\">\n {entry.changes.map((change, index) => (\n <li\n key={`${change.type}-${index}`}\n className={`eth-activity-changelog__change eth-activity-changelog__change--${change.type}`}\n >\n <Badge severity={severityForChange(change.type)}>\n {changeLabel(change.type)}\n </Badge>\n <span>{change.summary}</span>\n </li>\n ))}\n </ul>\n </article>\n </li>\n ))}\n </ol>\n ) : (\n <p className=\"eth-activity-changelog__empty\">No release notes published yet.</p>\n )}\n </Surface>\n );\n}\n\nfunction severityForChange(type: ChangelogEntry[\"changes\"][number][\"type\"]): EthSeverity {\n if (type === \"added\") return \"success\";\n if (type === \"fixed\") return \"info\";\n if (type === \"removed\") return \"danger\";\n return \"warning\";\n}\n\nfunction changeLabel(type: ChangelogEntry[\"changes\"][number][\"type\"]) {\n const labels = {\n added: \"Added\",\n changed: \"Changed\",\n fixed: \"Fixed\",\n removed: \"Removed\"\n } satisfies Record<ChangelogEntry[\"changes\"][number][\"type\"], string>;\n\n return labels[type];\n}\n\nfunction changelogSummary(entries: ChangelogEntry[]) {\n if (!entries.length) return \"No releases published\";\n\n const changeCount = entries.reduce((count, entry) => count + entry.changes.length, 0);\n return `${pluralize(entries.length, \"release\")} · ${pluralize(changeCount, \"change\")}`;\n}\n\nfunction pluralize(count: number, label: string) {\n return `${count} ${label}${count === 1 ? \"\" : \"s\"}`;\n}\n\nfunction dateTimeValue(value: string) {\n const date = new Date(value);\n return Number.isFinite(date.valueOf()) ? date.toISOString().slice(0, 10) : undefined;\n}\n","import * as React from \"react\";\nimport { Surface, Toggle, type SurfaceComponentProps } from \"@echothink-ui/core\";\nimport type { SubscriptionCategory, SubscriptionChannel } from \"./types\";\n\nexport interface SubscriptionPreferencesProps\n extends Omit<SurfaceComponentProps, \"children\" | \"onToggle\"> {\n categories: SubscriptionCategory[];\n onToggle?: (categoryId: string, channelId: string, enabled: boolean) => void;\n}\n\nexport function SubscriptionPreferences({\n categories,\n onToggle,\n title,\n className,\n role,\n \"aria-label\": ariaLabel,\n ...props\n}: SubscriptionPreferencesProps) {\n const displayTitle = title ?? \"Notification preferences\";\n const panelLabel = ariaLabel ?? (typeof displayTitle === \"string\" ? displayTitle : undefined);\n\n return (\n <Surface\n {...props}\n title={displayTitle}\n role={role ?? (panelLabel ? \"region\" : undefined)}\n aria-label={panelLabel}\n className={[\"eth-activity-subscription-preferences\", className].filter(Boolean).join(\" \")}\n data-eth-component=\"SubscriptionPreferences\"\n >\n {categories.length ? (\n <div className=\"eth-activity-subscription-preferences__categories\">\n {categories.map((category) => (\n <SubscriptionCategoryGroup key={category.id} category={category} onToggle={onToggle} />\n ))}\n </div>\n ) : (\n <p className=\"eth-activity-subscription-preferences__empty\" role=\"status\">\n No notification categories configured.\n </p>\n )}\n </Surface>\n );\n}\n\nfunction SubscriptionCategoryGroup({\n category,\n onToggle\n}: {\n category: SubscriptionCategory;\n onToggle?: SubscriptionPreferencesProps[\"onToggle\"];\n}) {\n const headingId = React.useId();\n const descriptionId = category.description ? `${headingId}-description` : undefined;\n\n return (\n <section\n className=\"eth-activity-subscription-preferences__category\"\n role=\"group\"\n aria-labelledby={headingId}\n aria-describedby={descriptionId}\n >\n <header className=\"eth-activity-subscription-preferences__category-header\">\n <h3 id={headingId}>{category.label}</h3>\n {category.description ? (\n <p\n id={descriptionId}\n className=\"eth-activity-subscription-preferences__category-description\"\n >\n {category.description}\n </p>\n ) : null}\n </header>\n {category.channels.length ? (\n <div className=\"eth-activity-subscription-preferences__channels\">\n {category.channels.map((channel) => (\n <SubscriptionChannelRow\n key={channel.id}\n category={category}\n channel={channel}\n onToggle={onToggle}\n />\n ))}\n </div>\n ) : (\n <p className=\"eth-activity-subscription-preferences__channel-empty\">\n No channels configured.\n </p>\n )}\n </section>\n );\n}\n\nfunction SubscriptionChannelRow({\n category,\n channel,\n onToggle\n}: {\n category: SubscriptionCategory;\n channel: SubscriptionChannel;\n onToggle?: SubscriptionPreferencesProps[\"onToggle\"];\n}) {\n const accessibleLabel = `${category.label} ${channel.label}`;\n\n return (\n <div\n className={[\n \"eth-activity-subscription-preferences__channel\",\n channel.disabled ? \"eth-activity-subscription-preferences__channel--disabled\" : undefined\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n <div className=\"eth-activity-subscription-preferences__channel-copy\">\n <span className=\"eth-activity-subscription-preferences__channel-label\">\n {channel.label}\n </span>\n {channel.description ? (\n <span className=\"eth-activity-subscription-preferences__channel-description\">\n {channel.description}\n </span>\n ) : null}\n </div>\n <Toggle\n id={`eth-subscription-${safeId(category.id)}-${safeId(channel.id)}`}\n label={accessibleLabel}\n hideLabel\n density=\"compact\"\n checked={channel.enabled}\n disabled={channel.disabled}\n onChange={(event) => onToggle?.(category.id, channel.id, event.currentTarget.checked)}\n />\n </div>\n );\n}\n\nfunction safeId(value: string) {\n return value.replace(/[^a-zA-Z0-9_-]+/g, \"-\") || \"channel\";\n}\n","import { Button, Surface, type SurfaceComponentProps } from \"@echothink-ui/core\";\nimport type { Mention } from \"./types\";\n\nexport interface MentionListProps extends Omit<SurfaceComponentProps, \"children\"> {\n mentions: Mention[];\n onOpen?: (messageRef: string) => void;\n}\n\nexport function MentionList({\n mentions,\n onOpen,\n title,\n subtitle,\n className,\n ...props\n}: MentionListProps) {\n const hasMentions = mentions.length > 0;\n const countLabel = hasMentions\n ? `${mentions.length} pending ${mentions.length === 1 ? \"reference\" : \"references\"}`\n : \"No pending references\";\n\n return (\n <Surface\n {...props}\n title={title ?? \"Mentions\"}\n subtitle={subtitle ?? countLabel}\n className={[\"eth-activity-mention-list\", className].filter(Boolean).join(\" \")}\n data-eth-component=\"MentionList\"\n >\n {hasMentions ? (\n <ul className=\"eth-activity-mention-list__items\" aria-label=\"Mention assignments\">\n {mentions.map((mention) => (\n <li key={mention.id} className=\"eth-activity-mention-list__item\">\n <span className=\"eth-activity-mention-list__avatar\" aria-hidden=\"true\">\n {initialsForName(mention.from)}\n </span>\n <div className=\"eth-activity-mention-list__content\">\n <div className=\"eth-activity-mention-list__header\">\n <strong className=\"eth-activity-mention-list__sender\">{mention.from}</strong>\n <span className=\"eth-activity-mention-list__reference\">{mention.messageRef}</span>\n </div>\n <p className=\"eth-activity-mention-list__excerpt\">{mention.excerpt}</p>\n <time\n className=\"eth-activity-mention-list__time\"\n dateTime={dateTimeValue(mention.createdAt)}\n >\n {mention.createdAt}\n </time>\n </div>\n <Button\n type=\"button\"\n intent=\"tertiary\"\n density=\"compact\"\n disabled={!onOpen}\n aria-label={`Open mention ${mention.messageRef}`}\n onClick={() => onOpen?.(mention.messageRef)}\n >\n Open\n </Button>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"eth-activity-mention-list__empty\" role=\"status\">\n No mentions assigned to you.\n </p>\n )}\n </Surface>\n );\n}\n\nfunction initialsForName(value: string) {\n const parts = value.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"?\";\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();\n}\n\nfunction dateTimeValue(value: string) {\n const trimmed = value.trim();\n if (/^\\d{2}:\\d{2}(:\\d{2})?$/.test(trimmed)) return trimmed;\n\n const date = new Date(trimmed);\n return Number.isFinite(date.valueOf()) ? date.toISOString() : undefined;\n}\n","import {\n Badge,\n Button,\n IconButton,\n Surface,\n type SurfaceComponentProps\n} from \"@echothink-ui/core\";\nimport { CloseIcon, PlusIcon } from \"@echothink-ui/icons\";\nimport type { IdentityRef } from \"./types\";\n\nexport interface WatcherListProps extends Omit<SurfaceComponentProps, \"children\"> {\n watchers: IdentityRef[];\n onAdd?: () => void;\n onRemove?: (id: string) => void;\n}\n\nexport function WatcherList({\n watchers,\n onAdd,\n onRemove,\n title,\n subtitle,\n className,\n role,\n footer,\n \"aria-label\": ariaLabel,\n ...props\n}: WatcherListProps) {\n const heading = title ?? \"Watchers\";\n const regionLabel = ariaLabel ?? (typeof heading === \"string\" ? heading : \"Watchers\");\n const addAction = onAdd ? (\n <Button\n type=\"button\"\n density=\"compact\"\n intent=\"tertiary\"\n icon={<PlusIcon size={16} />}\n onClick={onAdd}\n >\n Add watcher\n </Button>\n ) : null;\n const surfaceFooter =\n addAction || footer ? (\n <div className=\"eth-activity-watcher-list__footer\">\n {addAction}\n {footer}\n </div>\n ) : undefined;\n\n return (\n <Surface\n {...props}\n title={heading}\n subtitle={subtitle ?? watcherSummary(watchers.length)}\n className={[\"eth-activity-watcher-list\", className].filter(Boolean).join(\" \")}\n data-eth-component=\"WatcherList\"\n role={role ?? \"region\"}\n aria-label={regionLabel}\n footer={surfaceFooter}\n >\n {watchers.length ? (\n <ul className=\"eth-activity-watcher-list__items\" aria-label=\"Watchers\">\n {watchers.map((watcher) => (\n <li key={watcher.id} className=\"eth-activity-watcher-list__item\">\n <span className=\"eth-activity-watcher-list__avatar\" aria-hidden=\"true\">\n {watcher.avatar ? (\n <img src={watcher.avatar} alt=\"\" />\n ) : (\n initialsForName(watcher.label)\n )}\n </span>\n <span className=\"eth-activity-watcher-list__identity\">\n <strong>{watcher.label}</strong>\n <span>{watcherDetail(watcher)}</span>\n </span>\n <span className=\"eth-activity-watcher-list__actions\">\n <Badge severity=\"neutral\">{watcherKindLabel(watcher.kind)}</Badge>\n {onRemove ? (\n <IconButton\n type=\"button\"\n density=\"compact\"\n intent=\"ghost\"\n label={`Remove ${watcher.label} from watchers`}\n icon={<CloseIcon size={16} />}\n onClick={() => onRemove(watcher.id)}\n />\n ) : null}\n </span>\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"eth-activity-watcher-list__empty\" role=\"status\">\n No watchers have been added.\n </p>\n )}\n </Surface>\n );\n}\n\nfunction watcherSummary(count: number) {\n if (count === 0) return \"No watchers configured\";\n return `${count} ${count === 1 ? \"watcher\" : \"watchers\"} notified on updates`;\n}\n\nfunction watcherDetail(watcher: IdentityRef) {\n const details = [watcher.role, watcher.email].filter(Boolean);\n return details.length ? details.join(\" / \") : \"Receives activity updates\";\n}\n\nfunction watcherKindLabel(kind: IdentityRef[\"kind\"]) {\n if (kind === \"group\") return \"Group\";\n if (kind === \"service-account\") return \"Service account\";\n return \"User\";\n}\n\nfunction initialsForName(value: string) {\n const parts = value.trim().split(/\\s+/).filter(Boolean);\n if (!parts.length) return \"?\";\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();\n}\n","import \"./styles.css\";\n\nexport * from \"./components/types\";\nexport * from \"./components/NotificationCenter\";\nexport * from \"./components/NotificationItem\";\nexport * from \"./components/ActivityFeed\";\nexport * from \"./components/ActivityTimeline\";\nexport * from \"./components/AlertBanner\";\nexport * from \"./components/SystemStatusBanner\";\nexport * from \"./components/IncidentPanel\";\nexport * from \"./components/ChangelogPanel\";\nexport * from \"./components/SubscriptionPreferences\";\nexport * from \"./components/MentionList\";\nexport * from \"./components/WatcherList\";\nexport { AuditLogTable, type AuditExportFormat, type AuditLogTableProps } from \"@echothink-ui/data\";\n\nexport const ActivityComponentNames = [\n \"NotificationCenter\",\n \"NotificationItem\",\n \"ActivityFeed\",\n \"ActivityTimeline\",\n \"AlertBanner\",\n \"SystemStatusBanner\",\n \"IncidentPanel\",\n \"ChangelogPanel\",\n \"SubscriptionPreferences\",\n \"MentionList\",\n \"WatcherList\",\n \"AuditLogTable\"\n] as const;\nexport type ActivityComponentName = (typeof ActivityComponentNames)[number];\n"],"mappings":"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","names":["Button","jsx","jsxs","Button","Badge","Surface","Fragment","jsx","jsxs","dateTimeValue","formatTimestamp","Badge","Surface","Fragment","jsx","jsxs","Surface","formatGroupLabel","verbClass","iconForVerb","Badge","dateTimeValue","formatTimestamp","ActionGroup","Button","jsx","jsxs","ActionGroup","Button","jsx","Badge","Surface","jsx","jsxs","Surface","Badge","Badge","Surface","jsx","jsxs","dateTimeValue","Surface","jsx","jsxs","Button","Surface","jsx","jsxs","dateTimeValue","Badge","Button","Surface","jsx","jsxs","initialsForName"]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@echothink-ui/activity",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "sideEffects": [
7
+ "**/*.css"
8
+ ],
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ },
18
+ "./styles.css": "./src/styles.css"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src",
23
+ "README.md"
24
+ ],
25
+ "peerDependencies": {
26
+ "react": ">=18.3.0",
27
+ "react-dom": ">=18.3.0"
28
+ },
29
+ "dependencies": {
30
+ "@echothink-ui/core": "0.2.0",
31
+ "@echothink-ui/icons": "0.2.0",
32
+ "@echothink-ui/data": "0.2.0"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup src/index.tsx --format esm,cjs --sourcemap --clean --external react --external react-dom && tsc -p tsconfig.json --declaration --emitDeclarationOnly --noEmit false --outDir dist",
39
+ "typecheck": "tsc -p tsconfig.json --noEmit",
40
+ "test": "vitest run --config ../../vitest.config.ts --passWithNoTests",
41
+ "lint": "eslint src"
42
+ }
43
+ }
@@ -0,0 +1,83 @@
1
+ import { Badge, Surface, type SurfaceComponentProps } from "@echothink-ui/core";
2
+ import type { ActivityEvent } from "./types";
3
+
4
+ export interface ActivityFeedProps extends Omit<SurfaceComponentProps, "children"> {
5
+ events: ActivityEvent[];
6
+ streaming?: boolean;
7
+ }
8
+
9
+ export function ActivityFeed({ events, streaming, title, className, ...props }: ActivityFeedProps) {
10
+ const hasEvents = events.length > 0;
11
+
12
+ return (
13
+ <Surface
14
+ {...props}
15
+ title={title ?? "Activity"}
16
+ className={["eth-activity-feed", className].filter(Boolean).join(" ")}
17
+ data-eth-component="ActivityFeed"
18
+ >
19
+ {streaming ? (
20
+ <div className="eth-activity-feed__status" aria-label="Streaming activity">
21
+ <Badge severity="success">Streaming</Badge>
22
+ </div>
23
+ ) : null}
24
+ {hasEvents ? (
25
+ <ol className="eth-activity-feed__events" aria-live={streaming ? "polite" : undefined}>
26
+ {events.map((event) => (
27
+ <li
28
+ key={event.id}
29
+ className={`eth-activity-feed__event eth-activity-feed__event--${verbClass(event.verb)}`}
30
+ >
31
+ <span className="eth-activity-feed__icon" aria-hidden>
32
+ {iconForVerb(event.verb)}
33
+ </span>
34
+ <div className="eth-activity-feed__body">
35
+ <p className="eth-activity-feed__summary">
36
+ <strong>{event.actor}</strong> {event.verb} <strong>{event.objectLabel}</strong>
37
+ {event.targetLabel ? (
38
+ <>
39
+ {" "}
40
+ in <strong>{event.targetLabel}</strong>
41
+ </>
42
+ ) : null}
43
+ </p>
44
+ {event.details ? <p className="eth-activity-feed__details">{event.details}</p> : null}
45
+ <time className="eth-activity-feed__time" dateTime={dateTimeValue(event.createdAt)}>
46
+ {formatTimestamp(event.createdAt)}
47
+ </time>
48
+ </div>
49
+ </li>
50
+ ))}
51
+ </ol>
52
+ ) : (
53
+ <p className="eth-activity-feed__empty">No activity yet.</p>
54
+ )}
55
+ </Surface>
56
+ );
57
+ }
58
+
59
+ function iconForVerb(verb: string) {
60
+ const normalized = verb.toLowerCase();
61
+ if (normalized.includes("create") || normalized.includes("add")) return "+";
62
+ if (normalized.includes("delete") || normalized.includes("remove")) return "-";
63
+ if (normalized.includes("approve")) return "✓";
64
+ if (normalized.includes("fail") || normalized.includes("reject")) return "!";
65
+ return "•";
66
+ }
67
+
68
+ function verbClass(verb: string) {
69
+ return verb.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "event";
70
+ }
71
+
72
+ function dateTimeValue(value: string) {
73
+ const date = new Date(value);
74
+ return Number.isFinite(date.valueOf()) ? date.toISOString() : value;
75
+ }
76
+
77
+ function formatTimestamp(value: string) {
78
+ const date = new Date(value);
79
+ if (!Number.isFinite(date.valueOf())) return value;
80
+
81
+ const iso = date.toISOString();
82
+ return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;
83
+ }
@@ -0,0 +1,178 @@
1
+ import { Badge, Surface, type EthSeverity, type SurfaceComponentProps } from "@echothink-ui/core";
2
+ import { dateGroupKey } from "./helpers";
3
+ import type { ActivityEvent } from "./types";
4
+
5
+ export interface ActivityTimelineProps extends Omit<SurfaceComponentProps, "children"> {
6
+ events: ActivityEvent[];
7
+ }
8
+
9
+ export function ActivityTimeline({
10
+ events = [],
11
+ title,
12
+ subtitle,
13
+ className,
14
+ role,
15
+ "aria-label": ariaLabel,
16
+ ...props
17
+ }: ActivityTimelineProps) {
18
+ const groups = groupEvents(events);
19
+ const eventCount = events.length;
20
+
21
+ return (
22
+ <Surface
23
+ {...props}
24
+ title={title ?? "Timeline"}
25
+ subtitle={subtitle ?? summaryText(groups.length, eventCount)}
26
+ className={["eth-activity-timeline", className].filter(Boolean).join(" ")}
27
+ data-eth-component="ActivityTimeline"
28
+ role={role ?? "region"}
29
+ aria-label={ariaLabel ?? "Activity timeline"}
30
+ >
31
+ {eventCount > 0 ? (
32
+ <div className="eth-activity-timeline__content">
33
+ {groups.map((group) => (
34
+ <section key={group.date} className="eth-activity-timeline__group">
35
+ <header className="eth-activity-timeline__group-header">
36
+ <h3>{formatGroupLabel(group.date)}</h3>
37
+ <span>{summaryText(0, group.events.length)}</span>
38
+ </header>
39
+ <ol className="eth-activity-timeline__events">
40
+ {group.events.map((event) => (
41
+ <li
42
+ key={event.id}
43
+ className={`eth-activity-timeline__event eth-activity-timeline__event--${verbClass(event.verb)}`}
44
+ >
45
+ <span className="eth-activity-timeline__marker" aria-hidden>
46
+ {iconForVerb(event.verb)}
47
+ </span>
48
+ <article className="eth-activity-timeline__card">
49
+ <header className="eth-activity-timeline__event-header">
50
+ <Badge severity={severityForVerb(event.verb)}>
51
+ {kindLabel(event.verb)}
52
+ </Badge>
53
+ <time dateTime={dateTimeValue(event.createdAt)}>
54
+ {formatTimestamp(event.createdAt)}
55
+ </time>
56
+ </header>
57
+ <p className="eth-activity-timeline__summary">
58
+ <strong>{event.actor}</strong> {event.verb}{" "}
59
+ <strong>{event.objectLabel}</strong>
60
+ {event.targetLabel ? (
61
+ <>
62
+ {" "}
63
+ in <strong>{event.targetLabel}</strong>
64
+ </>
65
+ ) : null}
66
+ </p>
67
+ {event.details ? (
68
+ <p className="eth-activity-timeline__details">{event.details}</p>
69
+ ) : null}
70
+ </article>
71
+ </li>
72
+ ))}
73
+ </ol>
74
+ </section>
75
+ ))}
76
+ </div>
77
+ ) : (
78
+ <p className="eth-activity-timeline__empty">No activity yet.</p>
79
+ )}
80
+ </Surface>
81
+ );
82
+ }
83
+
84
+ function groupEvents(events: ActivityEvent[]) {
85
+ const groups = new Map<string, ActivityEvent[]>();
86
+ const sortedEvents = events
87
+ .map((event, index) => ({ event, index }))
88
+ .sort((a, b) => compareEvents(a, b))
89
+ .map(({ event }) => event);
90
+
91
+ for (const event of sortedEvents) {
92
+ const key = dateGroupKey(event.createdAt);
93
+ groups.set(key, [...(groups.get(key) ?? []), event]);
94
+ }
95
+ return Array.from(groups, ([date, groupedEvents]) => ({ date, events: groupedEvents }));
96
+ }
97
+
98
+ function compareEvents(
99
+ a: { event: ActivityEvent; index: number },
100
+ b: { event: ActivityEvent; index: number }
101
+ ) {
102
+ const aTime = timestampValue(a.event.createdAt);
103
+ const bTime = timestampValue(b.event.createdAt);
104
+
105
+ if (aTime !== null && bTime !== null && aTime !== bTime) return bTime - aTime;
106
+ if (aTime !== null && bTime === null) return -1;
107
+ if (aTime === null && bTime !== null) return 1;
108
+ return a.index - b.index;
109
+ }
110
+
111
+ function timestampValue(value: string) {
112
+ const timestamp = new Date(value).getTime();
113
+ return Number.isFinite(timestamp) ? timestamp : null;
114
+ }
115
+
116
+ function dateTimeValue(value: string) {
117
+ const date = new Date(value);
118
+ return Number.isFinite(date.valueOf()) ? date.toISOString() : value;
119
+ }
120
+
121
+ function formatGroupLabel(value: string) {
122
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
123
+
124
+ return new Intl.DateTimeFormat(undefined, {
125
+ day: "numeric",
126
+ month: "short",
127
+ timeZone: "UTC",
128
+ weekday: "short",
129
+ year: "numeric"
130
+ }).format(new Date(`${value}T00:00:00Z`));
131
+ }
132
+
133
+ function formatTimestamp(value: string) {
134
+ const date = new Date(value);
135
+ if (!Number.isFinite(date.valueOf())) return value;
136
+
137
+ const iso = date.toISOString();
138
+ return `${iso.slice(11, 16)} UTC`;
139
+ }
140
+
141
+ function summaryText(groupCount: number, eventCount: number) {
142
+ const eventLabel = `${eventCount} ${eventCount === 1 ? "event" : "events"}`;
143
+ if (groupCount <= 0) return eventLabel;
144
+ return `${eventLabel} across ${groupCount} ${groupCount === 1 ? "day" : "days"}`;
145
+ }
146
+
147
+ function iconForVerb(verb: string) {
148
+ const normalized = verb.toLowerCase();
149
+ if (normalized.includes("approve")) return "✓";
150
+ if (normalized.includes("draft") || normalized.includes("create") || normalized.includes("add")) {
151
+ return "+";
152
+ }
153
+ if (normalized.includes("comment")) return "…";
154
+ if (normalized.includes("fail") || normalized.includes("reject")) return "!";
155
+ return "•";
156
+ }
157
+
158
+ function kindLabel(verb: string) {
159
+ const normalized = verb.trim().toLowerCase();
160
+ if (normalized.includes("approve")) return "Approval";
161
+ if (normalized.includes("draft")) return "Draft";
162
+ if (normalized.includes("comment")) return "Comment";
163
+ if (normalized.includes("create") || normalized.includes("add")) return "Created";
164
+ if (normalized.includes("fail") || normalized.includes("reject")) return "Attention";
165
+ return "Update";
166
+ }
167
+
168
+ function severityForVerb(verb: string): EthSeverity {
169
+ const normalized = verb.toLowerCase();
170
+ if (normalized.includes("approve")) return "success";
171
+ if (normalized.includes("fail") || normalized.includes("reject")) return "danger";
172
+ if (normalized.includes("comment")) return "info";
173
+ return "neutral";
174
+ }
175
+
176
+ function verbClass(verb: string) {
177
+ return verb.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "event";
178
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ ActionGroup,
3
+ Button,
4
+ InlineNotification,
5
+ type EthAction,
6
+ type SurfaceComponentProps
7
+ } from "@echothink-ui/core";
8
+ import { severityToCore } from "./helpers";
9
+ import type { ActivitySeverity } from "./types";
10
+
11
+ export interface AlertBannerProps extends Omit<
12
+ SurfaceComponentProps,
13
+ "children" | "actions" | "severity"
14
+ > {
15
+ severity: ActivitySeverity;
16
+ title: string;
17
+ description?: string;
18
+ onDismiss?: () => void;
19
+ actions?: EthAction[];
20
+ }
21
+
22
+ export function AlertBanner({
23
+ severity,
24
+ title,
25
+ description,
26
+ onDismiss,
27
+ actions,
28
+ className,
29
+ role,
30
+ ...props
31
+ }: AlertBannerProps) {
32
+ const coreSeverity = severityToCore(severity);
33
+ const hasActions = Boolean(actions?.length || onDismiss);
34
+ const ariaLive = props["aria-live"] ?? (coreSeverity === "danger" ? "assertive" : "polite");
35
+ const dataEthComponent =
36
+ (props as { "data-eth-component"?: string })["data-eth-component"] ?? "AlertBanner";
37
+
38
+ return (
39
+ <section
40
+ {...props}
41
+ className={[
42
+ "eth-activity-alert-banner",
43
+ `eth-activity-alert-banner--${coreSeverity}`,
44
+ className
45
+ ]
46
+ .filter(Boolean)
47
+ .join(" ")}
48
+ data-eth-component={dataEthComponent}
49
+ role={role ?? (coreSeverity === "danger" ? "alert" : "status")}
50
+ aria-live={ariaLive}
51
+ >
52
+ <div className="eth-activity-alert-banner__notice">
53
+ <InlineNotification severity={coreSeverity} title={title}>
54
+ {description}
55
+ </InlineNotification>
56
+ </div>
57
+ {hasActions ? (
58
+ <div className="eth-activity-alert-banner__actions">
59
+ <ActionGroup actions={actions} />
60
+ {onDismiss ? (
61
+ <Button type="button" intent="ghost" density="compact" onClick={onDismiss}>
62
+ Dismiss
63
+ </Button>
64
+ ) : null}
65
+ </div>
66
+ ) : null}
67
+ </section>
68
+ );
69
+ }
@@ -0,0 +1,100 @@
1
+ import { Badge, Surface, type EthSeverity, type SurfaceComponentProps } from "@echothink-ui/core";
2
+ import type { ChangelogEntry } from "./types";
3
+
4
+ export interface ChangelogPanelProps extends Omit<SurfaceComponentProps, "children"> {
5
+ entries: ChangelogEntry[];
6
+ }
7
+
8
+ export function ChangelogPanel({
9
+ entries,
10
+ title,
11
+ subtitle,
12
+ className,
13
+ role,
14
+ "aria-label": ariaLabel,
15
+ ...props
16
+ }: ChangelogPanelProps) {
17
+ return (
18
+ <Surface
19
+ {...props}
20
+ title={title ?? "Changelog"}
21
+ subtitle={subtitle ?? changelogSummary(entries)}
22
+ className={["eth-activity-changelog", className].filter(Boolean).join(" ")}
23
+ data-eth-component="ChangelogPanel"
24
+ role={role ?? "region"}
25
+ aria-label={ariaLabel ?? "Product changelog"}
26
+ >
27
+ {entries.length ? (
28
+ <ol className="eth-activity-changelog__releases">
29
+ {entries.map((entry) => (
30
+ <li key={entry.id} className="eth-activity-changelog__release">
31
+ <article
32
+ className="eth-activity-changelog__entry"
33
+ aria-label={`Version ${entry.version}`}
34
+ >
35
+ <header className="eth-activity-changelog__entry-header">
36
+ <div>
37
+ <h3>{entry.version}</h3>
38
+ <time dateTime={dateTimeValue(entry.date)}>{entry.date}</time>
39
+ </div>
40
+ <span className="eth-activity-changelog__entry-count">
41
+ {pluralize(entry.changes.length, "change")}
42
+ </span>
43
+ </header>
44
+ <ul className="eth-activity-changelog__changes">
45
+ {entry.changes.map((change, index) => (
46
+ <li
47
+ key={`${change.type}-${index}`}
48
+ className={`eth-activity-changelog__change eth-activity-changelog__change--${change.type}`}
49
+ >
50
+ <Badge severity={severityForChange(change.type)}>
51
+ {changeLabel(change.type)}
52
+ </Badge>
53
+ <span>{change.summary}</span>
54
+ </li>
55
+ ))}
56
+ </ul>
57
+ </article>
58
+ </li>
59
+ ))}
60
+ </ol>
61
+ ) : (
62
+ <p className="eth-activity-changelog__empty">No release notes published yet.</p>
63
+ )}
64
+ </Surface>
65
+ );
66
+ }
67
+
68
+ function severityForChange(type: ChangelogEntry["changes"][number]["type"]): EthSeverity {
69
+ if (type === "added") return "success";
70
+ if (type === "fixed") return "info";
71
+ if (type === "removed") return "danger";
72
+ return "warning";
73
+ }
74
+
75
+ function changeLabel(type: ChangelogEntry["changes"][number]["type"]) {
76
+ const labels = {
77
+ added: "Added",
78
+ changed: "Changed",
79
+ fixed: "Fixed",
80
+ removed: "Removed"
81
+ } satisfies Record<ChangelogEntry["changes"][number]["type"], string>;
82
+
83
+ return labels[type];
84
+ }
85
+
86
+ function changelogSummary(entries: ChangelogEntry[]) {
87
+ if (!entries.length) return "No releases published";
88
+
89
+ const changeCount = entries.reduce((count, entry) => count + entry.changes.length, 0);
90
+ return `${pluralize(entries.length, "release")} · ${pluralize(changeCount, "change")}`;
91
+ }
92
+
93
+ function pluralize(count: number, label: string) {
94
+ return `${count} ${label}${count === 1 ? "" : "s"}`;
95
+ }
96
+
97
+ function dateTimeValue(value: string) {
98
+ const date = new Date(value);
99
+ return Number.isFinite(date.valueOf()) ? date.toISOString().slice(0, 10) : undefined;
100
+ }