@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
package/dist/index.js ADDED
@@ -0,0 +1,904 @@
1
+ // src/components/NotificationCenter.tsx
2
+ import { Button as Button2, Surface } from "@echothink-ui/core";
3
+
4
+ // src/components/NotificationItem.tsx
5
+ import { ActionGroup, Badge, Button } from "@echothink-ui/core";
6
+
7
+ // src/components/helpers.ts
8
+ function severityToCore(severity) {
9
+ if (severity === "error" || severity === "danger") return "danger";
10
+ if (severity === "success") return "success";
11
+ if (severity === "warning") return "warning";
12
+ return "info";
13
+ }
14
+ function dateGroupKey(value) {
15
+ const date = new Date(value);
16
+ if (!Number.isFinite(date.valueOf())) return value;
17
+ return date.toISOString().slice(0, 10);
18
+ }
19
+
20
+ // src/components/NotificationItem.tsx
21
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
22
+ function NotificationItem({
23
+ notification,
24
+ onRead,
25
+ onClick,
26
+ className,
27
+ title: _title,
28
+ subtitle: _subtitle,
29
+ description: _description,
30
+ eyebrow: _eyebrow,
31
+ density: _density,
32
+ status: _status,
33
+ severity: _severity,
34
+ loading: _loading,
35
+ empty: _empty,
36
+ error: _error,
37
+ items: _items,
38
+ actions: _actions,
39
+ metadata: _metadata,
40
+ footer: _footer,
41
+ ...props
42
+ }) {
43
+ const severity = severityToCore(notification.severity);
44
+ const mainContent = /* @__PURE__ */ jsxs(Fragment, { children: [
45
+ /* @__PURE__ */ jsx(Badge, { className: "eth-activity-notification-item__severity", severity, children: notification.severity }),
46
+ /* @__PURE__ */ jsxs("span", { className: "eth-activity-notification-item__content", children: [
47
+ /* @__PURE__ */ jsx("strong", { className: "eth-activity-notification-item__title", children: notification.title }),
48
+ notification.body ? /* @__PURE__ */ jsx("span", { className: "eth-activity-notification-item__body", children: notification.body }) : null
49
+ ] }),
50
+ /* @__PURE__ */ jsx(
51
+ "time",
52
+ {
53
+ className: "eth-activity-notification-item__time",
54
+ dateTime: dateTimeValue(notification.createdAt),
55
+ children: formatTimestamp(notification.createdAt)
56
+ }
57
+ )
58
+ ] });
59
+ const hasActions = Boolean(!notification.read && onRead || notification.actions?.length);
60
+ return /* @__PURE__ */ jsxs(
61
+ "article",
62
+ {
63
+ ...props,
64
+ className: [
65
+ "eth-activity-notification-item",
66
+ notification.read ? "eth-activity-notification-item--read" : void 0,
67
+ className
68
+ ].filter(Boolean).join(" "),
69
+ "data-eth-component": "NotificationItem",
70
+ "data-severity": severity,
71
+ children: [
72
+ onClick ? /* @__PURE__ */ jsx("button", { type: "button", className: "eth-activity-notification-item__main", onClick, children: mainContent }) : /* @__PURE__ */ jsx("div", { className: "eth-activity-notification-item__main", children: mainContent }),
73
+ hasActions ? /* @__PURE__ */ jsxs("div", { className: "eth-activity-notification-item__actions", children: [
74
+ !notification.read && onRead ? /* @__PURE__ */ jsx(Button, { type: "button", intent: "ghost", density: "compact", onClick: onRead, children: "Mark read" }) : null,
75
+ /* @__PURE__ */ jsx(ActionGroup, { actions: notification.actions })
76
+ ] }) : null
77
+ ]
78
+ }
79
+ );
80
+ }
81
+ function dateTimeValue(value) {
82
+ const date = new Date(value);
83
+ return Number.isFinite(date.valueOf()) ? date.toISOString() : void 0;
84
+ }
85
+ function formatTimestamp(value) {
86
+ const date = new Date(value);
87
+ if (!Number.isFinite(date.valueOf())) return value;
88
+ const iso = date.toISOString();
89
+ return `${iso.slice(11, 16)} UTC`;
90
+ }
91
+
92
+ // src/components/NotificationCenter.tsx
93
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
94
+ function NotificationCenter({
95
+ notifications,
96
+ unreadCount,
97
+ onMarkRead,
98
+ onMarkAllRead,
99
+ onDismiss,
100
+ title,
101
+ className,
102
+ role,
103
+ "aria-label": ariaLabel,
104
+ ...props
105
+ }) {
106
+ const groups = groupByDate(notifications);
107
+ const unreadTotal = unreadCount ?? notifications.filter((item) => !item.read).length;
108
+ const displayTitle = title ?? "Notifications";
109
+ const regionLabel = ariaLabel ?? (typeof displayTitle === "string" ? displayTitle : "Notification center");
110
+ return /* @__PURE__ */ jsxs2(
111
+ Surface,
112
+ {
113
+ ...props,
114
+ title: displayTitle,
115
+ subtitle: `${unreadTotal} unread`,
116
+ className: ["eth-activity-notification-center", className].filter(Boolean).join(" "),
117
+ "data-eth-component": "NotificationCenter",
118
+ role: role ?? "region",
119
+ "aria-label": regionLabel,
120
+ children: [
121
+ onMarkAllRead ? /* @__PURE__ */ jsx2("div", { className: "eth-activity-notification-center__toolbar", children: /* @__PURE__ */ jsx2(
122
+ Button2,
123
+ {
124
+ type: "button",
125
+ intent: "secondary",
126
+ density: "compact",
127
+ disabled: unreadTotal === 0,
128
+ onClick: onMarkAllRead,
129
+ children: "Mark all read"
130
+ }
131
+ ) }) : null,
132
+ notifications.length ? /* @__PURE__ */ jsx2("div", { className: "eth-activity-notification-center__groups", children: groups.map((group) => /* @__PURE__ */ jsxs2("section", { className: "eth-activity-notification-center__group", children: [
133
+ /* @__PURE__ */ jsx2("h3", { children: formatGroupLabel(group.date) }),
134
+ /* @__PURE__ */ jsx2("ol", { className: "eth-activity-notification-center__items", children: group.items.map((notification) => /* @__PURE__ */ jsxs2("li", { className: "eth-activity-notification-center__row", children: [
135
+ /* @__PURE__ */ jsx2(
136
+ NotificationItem,
137
+ {
138
+ notification,
139
+ onRead: onMarkRead && !notification.read ? () => onMarkRead(notification.id) : void 0
140
+ }
141
+ ),
142
+ onDismiss ? /* @__PURE__ */ jsx2(
143
+ Button2,
144
+ {
145
+ type: "button",
146
+ intent: "ghost",
147
+ density: "compact",
148
+ onClick: () => onDismiss(notification.id),
149
+ children: "Dismiss"
150
+ }
151
+ ) : null
152
+ ] }, notification.id)) })
153
+ ] }, group.date)) }) : /* @__PURE__ */ jsx2("p", { className: "eth-activity-notification-center__empty", children: "No notifications." })
154
+ ]
155
+ }
156
+ );
157
+ }
158
+ function groupByDate(notifications) {
159
+ const groups = /* @__PURE__ */ new Map();
160
+ for (const notification of notifications) {
161
+ const key = groupKey(notification.createdAt);
162
+ groups.set(key, [...groups.get(key) ?? [], notification]);
163
+ }
164
+ return Array.from(groups, ([date, items]) => ({ date, items }));
165
+ }
166
+ function groupKey(value) {
167
+ const date = new Date(value);
168
+ return Number.isFinite(date.valueOf()) ? date.toISOString().slice(0, 10) : "Recent";
169
+ }
170
+ function formatGroupLabel(value) {
171
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
172
+ return new Intl.DateTimeFormat(void 0, {
173
+ day: "numeric",
174
+ month: "short",
175
+ timeZone: "UTC",
176
+ weekday: "short",
177
+ year: "numeric"
178
+ }).format(/* @__PURE__ */ new Date(`${value}T00:00:00Z`));
179
+ }
180
+
181
+ // src/components/ActivityFeed.tsx
182
+ import { Badge as Badge2, Surface as Surface2 } from "@echothink-ui/core";
183
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
184
+ function ActivityFeed({ events, streaming, title, className, ...props }) {
185
+ const hasEvents = events.length > 0;
186
+ return /* @__PURE__ */ jsxs3(
187
+ Surface2,
188
+ {
189
+ ...props,
190
+ title: title ?? "Activity",
191
+ className: ["eth-activity-feed", className].filter(Boolean).join(" "),
192
+ "data-eth-component": "ActivityFeed",
193
+ children: [
194
+ streaming ? /* @__PURE__ */ jsx3("div", { className: "eth-activity-feed__status", "aria-label": "Streaming activity", children: /* @__PURE__ */ jsx3(Badge2, { severity: "success", children: "Streaming" }) }) : null,
195
+ hasEvents ? /* @__PURE__ */ jsx3("ol", { className: "eth-activity-feed__events", "aria-live": streaming ? "polite" : void 0, children: events.map((event) => /* @__PURE__ */ jsxs3(
196
+ "li",
197
+ {
198
+ className: `eth-activity-feed__event eth-activity-feed__event--${verbClass(event.verb)}`,
199
+ children: [
200
+ /* @__PURE__ */ jsx3("span", { className: "eth-activity-feed__icon", "aria-hidden": true, children: iconForVerb(event.verb) }),
201
+ /* @__PURE__ */ jsxs3("div", { className: "eth-activity-feed__body", children: [
202
+ /* @__PURE__ */ jsxs3("p", { className: "eth-activity-feed__summary", children: [
203
+ /* @__PURE__ */ jsx3("strong", { children: event.actor }),
204
+ " ",
205
+ event.verb,
206
+ " ",
207
+ /* @__PURE__ */ jsx3("strong", { children: event.objectLabel }),
208
+ event.targetLabel ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
209
+ " ",
210
+ "in ",
211
+ /* @__PURE__ */ jsx3("strong", { children: event.targetLabel })
212
+ ] }) : null
213
+ ] }),
214
+ event.details ? /* @__PURE__ */ jsx3("p", { className: "eth-activity-feed__details", children: event.details }) : null,
215
+ /* @__PURE__ */ jsx3("time", { className: "eth-activity-feed__time", dateTime: dateTimeValue2(event.createdAt), children: formatTimestamp2(event.createdAt) })
216
+ ] })
217
+ ]
218
+ },
219
+ event.id
220
+ )) }) : /* @__PURE__ */ jsx3("p", { className: "eth-activity-feed__empty", children: "No activity yet." })
221
+ ]
222
+ }
223
+ );
224
+ }
225
+ function iconForVerb(verb) {
226
+ const normalized = verb.toLowerCase();
227
+ if (normalized.includes("create") || normalized.includes("add")) return "+";
228
+ if (normalized.includes("delete") || normalized.includes("remove")) return "-";
229
+ if (normalized.includes("approve")) return "\u2713";
230
+ if (normalized.includes("fail") || normalized.includes("reject")) return "!";
231
+ return "\u2022";
232
+ }
233
+ function verbClass(verb) {
234
+ return verb.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "event";
235
+ }
236
+ function dateTimeValue2(value) {
237
+ const date = new Date(value);
238
+ return Number.isFinite(date.valueOf()) ? date.toISOString() : value;
239
+ }
240
+ function formatTimestamp2(value) {
241
+ const date = new Date(value);
242
+ if (!Number.isFinite(date.valueOf())) return value;
243
+ const iso = date.toISOString();
244
+ return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;
245
+ }
246
+
247
+ // src/components/ActivityTimeline.tsx
248
+ import { Badge as Badge3, Surface as Surface3 } from "@echothink-ui/core";
249
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
250
+ function ActivityTimeline({
251
+ events = [],
252
+ title,
253
+ subtitle,
254
+ className,
255
+ role,
256
+ "aria-label": ariaLabel,
257
+ ...props
258
+ }) {
259
+ const groups = groupEvents(events);
260
+ const eventCount = events.length;
261
+ return /* @__PURE__ */ jsx4(
262
+ Surface3,
263
+ {
264
+ ...props,
265
+ title: title ?? "Timeline",
266
+ subtitle: subtitle ?? summaryText(groups.length, eventCount),
267
+ className: ["eth-activity-timeline", className].filter(Boolean).join(" "),
268
+ "data-eth-component": "ActivityTimeline",
269
+ role: role ?? "region",
270
+ "aria-label": ariaLabel ?? "Activity timeline",
271
+ children: eventCount > 0 ? /* @__PURE__ */ jsx4("div", { className: "eth-activity-timeline__content", children: groups.map((group) => /* @__PURE__ */ jsxs4("section", { className: "eth-activity-timeline__group", children: [
272
+ /* @__PURE__ */ jsxs4("header", { className: "eth-activity-timeline__group-header", children: [
273
+ /* @__PURE__ */ jsx4("h3", { children: formatGroupLabel2(group.date) }),
274
+ /* @__PURE__ */ jsx4("span", { children: summaryText(0, group.events.length) })
275
+ ] }),
276
+ /* @__PURE__ */ jsx4("ol", { className: "eth-activity-timeline__events", children: group.events.map((event) => /* @__PURE__ */ jsxs4(
277
+ "li",
278
+ {
279
+ className: `eth-activity-timeline__event eth-activity-timeline__event--${verbClass2(event.verb)}`,
280
+ children: [
281
+ /* @__PURE__ */ jsx4("span", { className: "eth-activity-timeline__marker", "aria-hidden": true, children: iconForVerb2(event.verb) }),
282
+ /* @__PURE__ */ jsxs4("article", { className: "eth-activity-timeline__card", children: [
283
+ /* @__PURE__ */ jsxs4("header", { className: "eth-activity-timeline__event-header", children: [
284
+ /* @__PURE__ */ jsx4(Badge3, { severity: severityForVerb(event.verb), children: kindLabel(event.verb) }),
285
+ /* @__PURE__ */ jsx4("time", { dateTime: dateTimeValue3(event.createdAt), children: formatTimestamp3(event.createdAt) })
286
+ ] }),
287
+ /* @__PURE__ */ jsxs4("p", { className: "eth-activity-timeline__summary", children: [
288
+ /* @__PURE__ */ jsx4("strong", { children: event.actor }),
289
+ " ",
290
+ event.verb,
291
+ " ",
292
+ /* @__PURE__ */ jsx4("strong", { children: event.objectLabel }),
293
+ event.targetLabel ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
294
+ " ",
295
+ "in ",
296
+ /* @__PURE__ */ jsx4("strong", { children: event.targetLabel })
297
+ ] }) : null
298
+ ] }),
299
+ event.details ? /* @__PURE__ */ jsx4("p", { className: "eth-activity-timeline__details", children: event.details }) : null
300
+ ] })
301
+ ]
302
+ },
303
+ event.id
304
+ )) })
305
+ ] }, group.date)) }) : /* @__PURE__ */ jsx4("p", { className: "eth-activity-timeline__empty", children: "No activity yet." })
306
+ }
307
+ );
308
+ }
309
+ function groupEvents(events) {
310
+ const groups = /* @__PURE__ */ new Map();
311
+ const sortedEvents = events.map((event, index) => ({ event, index })).sort((a, b) => compareEvents(a, b)).map(({ event }) => event);
312
+ for (const event of sortedEvents) {
313
+ const key = dateGroupKey(event.createdAt);
314
+ groups.set(key, [...groups.get(key) ?? [], event]);
315
+ }
316
+ return Array.from(groups, ([date, groupedEvents]) => ({ date, events: groupedEvents }));
317
+ }
318
+ function compareEvents(a, b) {
319
+ const aTime = timestampValue(a.event.createdAt);
320
+ const bTime = timestampValue(b.event.createdAt);
321
+ if (aTime !== null && bTime !== null && aTime !== bTime) return bTime - aTime;
322
+ if (aTime !== null && bTime === null) return -1;
323
+ if (aTime === null && bTime !== null) return 1;
324
+ return a.index - b.index;
325
+ }
326
+ function timestampValue(value) {
327
+ const timestamp = new Date(value).getTime();
328
+ return Number.isFinite(timestamp) ? timestamp : null;
329
+ }
330
+ function dateTimeValue3(value) {
331
+ const date = new Date(value);
332
+ return Number.isFinite(date.valueOf()) ? date.toISOString() : value;
333
+ }
334
+ function formatGroupLabel2(value) {
335
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
336
+ return new Intl.DateTimeFormat(void 0, {
337
+ day: "numeric",
338
+ month: "short",
339
+ timeZone: "UTC",
340
+ weekday: "short",
341
+ year: "numeric"
342
+ }).format(/* @__PURE__ */ new Date(`${value}T00:00:00Z`));
343
+ }
344
+ function formatTimestamp3(value) {
345
+ const date = new Date(value);
346
+ if (!Number.isFinite(date.valueOf())) return value;
347
+ const iso = date.toISOString();
348
+ return `${iso.slice(11, 16)} UTC`;
349
+ }
350
+ function summaryText(groupCount, eventCount) {
351
+ const eventLabel = `${eventCount} ${eventCount === 1 ? "event" : "events"}`;
352
+ if (groupCount <= 0) return eventLabel;
353
+ return `${eventLabel} across ${groupCount} ${groupCount === 1 ? "day" : "days"}`;
354
+ }
355
+ function iconForVerb2(verb) {
356
+ const normalized = verb.toLowerCase();
357
+ if (normalized.includes("approve")) return "\u2713";
358
+ if (normalized.includes("draft") || normalized.includes("create") || normalized.includes("add")) {
359
+ return "+";
360
+ }
361
+ if (normalized.includes("comment")) return "\u2026";
362
+ if (normalized.includes("fail") || normalized.includes("reject")) return "!";
363
+ return "\u2022";
364
+ }
365
+ function kindLabel(verb) {
366
+ const normalized = verb.trim().toLowerCase();
367
+ if (normalized.includes("approve")) return "Approval";
368
+ if (normalized.includes("draft")) return "Draft";
369
+ if (normalized.includes("comment")) return "Comment";
370
+ if (normalized.includes("create") || normalized.includes("add")) return "Created";
371
+ if (normalized.includes("fail") || normalized.includes("reject")) return "Attention";
372
+ return "Update";
373
+ }
374
+ function severityForVerb(verb) {
375
+ const normalized = verb.toLowerCase();
376
+ if (normalized.includes("approve")) return "success";
377
+ if (normalized.includes("fail") || normalized.includes("reject")) return "danger";
378
+ if (normalized.includes("comment")) return "info";
379
+ return "neutral";
380
+ }
381
+ function verbClass2(verb) {
382
+ return verb.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "event";
383
+ }
384
+
385
+ // src/components/AlertBanner.tsx
386
+ import {
387
+ ActionGroup as ActionGroup2,
388
+ Button as Button3,
389
+ InlineNotification
390
+ } from "@echothink-ui/core";
391
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
392
+ function AlertBanner({
393
+ severity,
394
+ title,
395
+ description,
396
+ onDismiss,
397
+ actions,
398
+ className,
399
+ role,
400
+ ...props
401
+ }) {
402
+ const coreSeverity = severityToCore(severity);
403
+ const hasActions = Boolean(actions?.length || onDismiss);
404
+ const ariaLive = props["aria-live"] ?? (coreSeverity === "danger" ? "assertive" : "polite");
405
+ const dataEthComponent = props["data-eth-component"] ?? "AlertBanner";
406
+ return /* @__PURE__ */ jsxs5(
407
+ "section",
408
+ {
409
+ ...props,
410
+ className: [
411
+ "eth-activity-alert-banner",
412
+ `eth-activity-alert-banner--${coreSeverity}`,
413
+ className
414
+ ].filter(Boolean).join(" "),
415
+ "data-eth-component": dataEthComponent,
416
+ role: role ?? (coreSeverity === "danger" ? "alert" : "status"),
417
+ "aria-live": ariaLive,
418
+ children: [
419
+ /* @__PURE__ */ jsx5("div", { className: "eth-activity-alert-banner__notice", children: /* @__PURE__ */ jsx5(InlineNotification, { severity: coreSeverity, title, children: description }) }),
420
+ hasActions ? /* @__PURE__ */ jsxs5("div", { className: "eth-activity-alert-banner__actions", children: [
421
+ /* @__PURE__ */ jsx5(ActionGroup2, { actions }),
422
+ onDismiss ? /* @__PURE__ */ jsx5(Button3, { type: "button", intent: "ghost", density: "compact", onClick: onDismiss, children: "Dismiss" }) : null
423
+ ] }) : null
424
+ ]
425
+ }
426
+ );
427
+ }
428
+
429
+ // src/components/SystemStatusBanner.tsx
430
+ import { jsx as jsx6 } from "react/jsx-runtime";
431
+ function SystemStatusBanner({
432
+ status,
433
+ title,
434
+ description,
435
+ actions,
436
+ onDismiss,
437
+ className
438
+ }) {
439
+ return /* @__PURE__ */ jsx6(
440
+ AlertBanner,
441
+ {
442
+ severity: severityForStatus(status),
443
+ title: title ?? defaultTitleForStatus(status),
444
+ description,
445
+ actions,
446
+ onDismiss,
447
+ className: ["eth-activity-system-status-banner", className].filter(Boolean).join(" "),
448
+ "data-eth-component": "SystemStatusBanner"
449
+ }
450
+ );
451
+ }
452
+ function defaultTitleForStatus(status) {
453
+ if (status === "operational") return "System operational";
454
+ if (status === "degraded") return "System degraded";
455
+ if (status === "maintenance") return "Scheduled maintenance";
456
+ return "System unavailable";
457
+ }
458
+ function severityForStatus(status) {
459
+ if (status === "operational") return "success";
460
+ if (status === "degraded" || status === "maintenance") return "warning";
461
+ return "error";
462
+ }
463
+
464
+ // src/components/IncidentPanel.tsx
465
+ import { Badge as Badge4, Surface as Surface4 } from "@echothink-ui/core";
466
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
467
+ function IncidentPanel({ incidents, title, className, ...props }) {
468
+ const hasIncidents = incidents.length > 0;
469
+ return /* @__PURE__ */ jsx7(
470
+ Surface4,
471
+ {
472
+ ...props,
473
+ title: title ?? "Incidents",
474
+ className: ["eth-activity-incident-panel", className].filter(Boolean).join(" "),
475
+ "data-eth-component": "IncidentPanel",
476
+ children: hasIncidents ? /* @__PURE__ */ jsx7("div", { className: "eth-activity-incident-panel__list", role: "list", children: incidents.map((incident) => /* @__PURE__ */ jsxs6(
477
+ "article",
478
+ {
479
+ className: "eth-activity-incident-panel__incident",
480
+ "data-severity": incident.severity ? severityToCore(incident.severity) : void 0,
481
+ role: "listitem",
482
+ "aria-label": incident.title,
483
+ children: [
484
+ /* @__PURE__ */ jsxs6("header", { className: "eth-activity-incident-panel__incident-header", children: [
485
+ /* @__PURE__ */ jsxs6("div", { className: "eth-activity-incident-panel__summary", children: [
486
+ /* @__PURE__ */ jsx7("h3", { children: incident.title }),
487
+ incident.description ? /* @__PURE__ */ jsx7("p", { children: incident.description }) : null
488
+ ] }),
489
+ incident.severity ? /* @__PURE__ */ jsx7(
490
+ Badge4,
491
+ {
492
+ className: "eth-activity-incident-panel__severity",
493
+ severity: severityToCore(incident.severity),
494
+ children: formatIncidentLabel(incident.severity)
495
+ }
496
+ ) : null
497
+ ] }),
498
+ /* @__PURE__ */ jsxs6("dl", { className: "eth-activity-incident-panel__metadata", children: [
499
+ incident.status ? /* @__PURE__ */ jsxs6("div", { children: [
500
+ /* @__PURE__ */ jsx7("dt", { children: "Status" }),
501
+ /* @__PURE__ */ jsx7("dd", { children: /* @__PURE__ */ jsxs6(
502
+ "span",
503
+ {
504
+ className: "eth-activity-incident-panel__status",
505
+ "data-status": incident.status,
506
+ children: [
507
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true" }),
508
+ formatIncidentLabel(incident.status)
509
+ ]
510
+ }
511
+ ) })
512
+ ] }) : null,
513
+ incident.startedAt ? /* @__PURE__ */ jsxs6("div", { children: [
514
+ /* @__PURE__ */ jsx7("dt", { children: "Started" }),
515
+ /* @__PURE__ */ jsx7("dd", { children: /* @__PURE__ */ jsx7("time", { children: incident.startedAt }) })
516
+ ] }) : null
517
+ ] })
518
+ ]
519
+ },
520
+ incident.id
521
+ )) }) : /* @__PURE__ */ jsx7("p", { className: "eth-activity-incident-panel__empty", children: "No active incidents." })
522
+ }
523
+ );
524
+ }
525
+ function formatIncidentLabel(value) {
526
+ return value.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
527
+ }
528
+
529
+ // src/components/ChangelogPanel.tsx
530
+ import { Badge as Badge5, Surface as Surface5 } from "@echothink-ui/core";
531
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
532
+ function ChangelogPanel({
533
+ entries,
534
+ title,
535
+ subtitle,
536
+ className,
537
+ role,
538
+ "aria-label": ariaLabel,
539
+ ...props
540
+ }) {
541
+ return /* @__PURE__ */ jsx8(
542
+ Surface5,
543
+ {
544
+ ...props,
545
+ title: title ?? "Changelog",
546
+ subtitle: subtitle ?? changelogSummary(entries),
547
+ className: ["eth-activity-changelog", className].filter(Boolean).join(" "),
548
+ "data-eth-component": "ChangelogPanel",
549
+ role: role ?? "region",
550
+ "aria-label": ariaLabel ?? "Product changelog",
551
+ children: entries.length ? /* @__PURE__ */ jsx8("ol", { className: "eth-activity-changelog__releases", children: entries.map((entry) => /* @__PURE__ */ jsx8("li", { className: "eth-activity-changelog__release", children: /* @__PURE__ */ jsxs7(
552
+ "article",
553
+ {
554
+ className: "eth-activity-changelog__entry",
555
+ "aria-label": `Version ${entry.version}`,
556
+ children: [
557
+ /* @__PURE__ */ jsxs7("header", { className: "eth-activity-changelog__entry-header", children: [
558
+ /* @__PURE__ */ jsxs7("div", { children: [
559
+ /* @__PURE__ */ jsx8("h3", { children: entry.version }),
560
+ /* @__PURE__ */ jsx8("time", { dateTime: dateTimeValue4(entry.date), children: entry.date })
561
+ ] }),
562
+ /* @__PURE__ */ jsx8("span", { className: "eth-activity-changelog__entry-count", children: pluralize(entry.changes.length, "change") })
563
+ ] }),
564
+ /* @__PURE__ */ jsx8("ul", { className: "eth-activity-changelog__changes", children: entry.changes.map((change, index) => /* @__PURE__ */ jsxs7(
565
+ "li",
566
+ {
567
+ className: `eth-activity-changelog__change eth-activity-changelog__change--${change.type}`,
568
+ children: [
569
+ /* @__PURE__ */ jsx8(Badge5, { severity: severityForChange(change.type), children: changeLabel(change.type) }),
570
+ /* @__PURE__ */ jsx8("span", { children: change.summary })
571
+ ]
572
+ },
573
+ `${change.type}-${index}`
574
+ )) })
575
+ ]
576
+ }
577
+ ) }, entry.id)) }) : /* @__PURE__ */ jsx8("p", { className: "eth-activity-changelog__empty", children: "No release notes published yet." })
578
+ }
579
+ );
580
+ }
581
+ function severityForChange(type) {
582
+ if (type === "added") return "success";
583
+ if (type === "fixed") return "info";
584
+ if (type === "removed") return "danger";
585
+ return "warning";
586
+ }
587
+ function changeLabel(type) {
588
+ const labels = {
589
+ added: "Added",
590
+ changed: "Changed",
591
+ fixed: "Fixed",
592
+ removed: "Removed"
593
+ };
594
+ return labels[type];
595
+ }
596
+ function changelogSummary(entries) {
597
+ if (!entries.length) return "No releases published";
598
+ const changeCount = entries.reduce((count, entry) => count + entry.changes.length, 0);
599
+ return `${pluralize(entries.length, "release")} \xB7 ${pluralize(changeCount, "change")}`;
600
+ }
601
+ function pluralize(count, label) {
602
+ return `${count} ${label}${count === 1 ? "" : "s"}`;
603
+ }
604
+ function dateTimeValue4(value) {
605
+ const date = new Date(value);
606
+ return Number.isFinite(date.valueOf()) ? date.toISOString().slice(0, 10) : void 0;
607
+ }
608
+
609
+ // src/components/SubscriptionPreferences.tsx
610
+ import * as React from "react";
611
+ import { Surface as Surface6, Toggle } from "@echothink-ui/core";
612
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
613
+ function SubscriptionPreferences({
614
+ categories,
615
+ onToggle,
616
+ title,
617
+ className,
618
+ role,
619
+ "aria-label": ariaLabel,
620
+ ...props
621
+ }) {
622
+ const displayTitle = title ?? "Notification preferences";
623
+ const panelLabel = ariaLabel ?? (typeof displayTitle === "string" ? displayTitle : void 0);
624
+ return /* @__PURE__ */ jsx9(
625
+ Surface6,
626
+ {
627
+ ...props,
628
+ title: displayTitle,
629
+ role: role ?? (panelLabel ? "region" : void 0),
630
+ "aria-label": panelLabel,
631
+ className: ["eth-activity-subscription-preferences", className].filter(Boolean).join(" "),
632
+ "data-eth-component": "SubscriptionPreferences",
633
+ children: categories.length ? /* @__PURE__ */ jsx9("div", { className: "eth-activity-subscription-preferences__categories", children: categories.map((category) => /* @__PURE__ */ jsx9(SubscriptionCategoryGroup, { category, onToggle }, category.id)) }) : /* @__PURE__ */ jsx9("p", { className: "eth-activity-subscription-preferences__empty", role: "status", children: "No notification categories configured." })
634
+ }
635
+ );
636
+ }
637
+ function SubscriptionCategoryGroup({
638
+ category,
639
+ onToggle
640
+ }) {
641
+ const headingId = React.useId();
642
+ const descriptionId = category.description ? `${headingId}-description` : void 0;
643
+ return /* @__PURE__ */ jsxs8(
644
+ "section",
645
+ {
646
+ className: "eth-activity-subscription-preferences__category",
647
+ role: "group",
648
+ "aria-labelledby": headingId,
649
+ "aria-describedby": descriptionId,
650
+ children: [
651
+ /* @__PURE__ */ jsxs8("header", { className: "eth-activity-subscription-preferences__category-header", children: [
652
+ /* @__PURE__ */ jsx9("h3", { id: headingId, children: category.label }),
653
+ category.description ? /* @__PURE__ */ jsx9(
654
+ "p",
655
+ {
656
+ id: descriptionId,
657
+ className: "eth-activity-subscription-preferences__category-description",
658
+ children: category.description
659
+ }
660
+ ) : null
661
+ ] }),
662
+ category.channels.length ? /* @__PURE__ */ jsx9("div", { className: "eth-activity-subscription-preferences__channels", children: category.channels.map((channel) => /* @__PURE__ */ jsx9(
663
+ SubscriptionChannelRow,
664
+ {
665
+ category,
666
+ channel,
667
+ onToggle
668
+ },
669
+ channel.id
670
+ )) }) : /* @__PURE__ */ jsx9("p", { className: "eth-activity-subscription-preferences__channel-empty", children: "No channels configured." })
671
+ ]
672
+ }
673
+ );
674
+ }
675
+ function SubscriptionChannelRow({
676
+ category,
677
+ channel,
678
+ onToggle
679
+ }) {
680
+ const accessibleLabel = `${category.label} ${channel.label}`;
681
+ return /* @__PURE__ */ jsxs8(
682
+ "div",
683
+ {
684
+ className: [
685
+ "eth-activity-subscription-preferences__channel",
686
+ channel.disabled ? "eth-activity-subscription-preferences__channel--disabled" : void 0
687
+ ].filter(Boolean).join(" "),
688
+ children: [
689
+ /* @__PURE__ */ jsxs8("div", { className: "eth-activity-subscription-preferences__channel-copy", children: [
690
+ /* @__PURE__ */ jsx9("span", { className: "eth-activity-subscription-preferences__channel-label", children: channel.label }),
691
+ channel.description ? /* @__PURE__ */ jsx9("span", { className: "eth-activity-subscription-preferences__channel-description", children: channel.description }) : null
692
+ ] }),
693
+ /* @__PURE__ */ jsx9(
694
+ Toggle,
695
+ {
696
+ id: `eth-subscription-${safeId(category.id)}-${safeId(channel.id)}`,
697
+ label: accessibleLabel,
698
+ hideLabel: true,
699
+ density: "compact",
700
+ checked: channel.enabled,
701
+ disabled: channel.disabled,
702
+ onChange: (event) => onToggle?.(category.id, channel.id, event.currentTarget.checked)
703
+ }
704
+ )
705
+ ]
706
+ }
707
+ );
708
+ }
709
+ function safeId(value) {
710
+ return value.replace(/[^a-zA-Z0-9_-]+/g, "-") || "channel";
711
+ }
712
+
713
+ // src/components/MentionList.tsx
714
+ import { Button as Button4, Surface as Surface7 } from "@echothink-ui/core";
715
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
716
+ function MentionList({
717
+ mentions,
718
+ onOpen,
719
+ title,
720
+ subtitle,
721
+ className,
722
+ ...props
723
+ }) {
724
+ const hasMentions = mentions.length > 0;
725
+ const countLabel = hasMentions ? `${mentions.length} pending ${mentions.length === 1 ? "reference" : "references"}` : "No pending references";
726
+ return /* @__PURE__ */ jsx10(
727
+ Surface7,
728
+ {
729
+ ...props,
730
+ title: title ?? "Mentions",
731
+ subtitle: subtitle ?? countLabel,
732
+ className: ["eth-activity-mention-list", className].filter(Boolean).join(" "),
733
+ "data-eth-component": "MentionList",
734
+ children: hasMentions ? /* @__PURE__ */ jsx10("ul", { className: "eth-activity-mention-list__items", "aria-label": "Mention assignments", children: mentions.map((mention) => /* @__PURE__ */ jsxs9("li", { className: "eth-activity-mention-list__item", children: [
735
+ /* @__PURE__ */ jsx10("span", { className: "eth-activity-mention-list__avatar", "aria-hidden": "true", children: initialsForName(mention.from) }),
736
+ /* @__PURE__ */ jsxs9("div", { className: "eth-activity-mention-list__content", children: [
737
+ /* @__PURE__ */ jsxs9("div", { className: "eth-activity-mention-list__header", children: [
738
+ /* @__PURE__ */ jsx10("strong", { className: "eth-activity-mention-list__sender", children: mention.from }),
739
+ /* @__PURE__ */ jsx10("span", { className: "eth-activity-mention-list__reference", children: mention.messageRef })
740
+ ] }),
741
+ /* @__PURE__ */ jsx10("p", { className: "eth-activity-mention-list__excerpt", children: mention.excerpt }),
742
+ /* @__PURE__ */ jsx10(
743
+ "time",
744
+ {
745
+ className: "eth-activity-mention-list__time",
746
+ dateTime: dateTimeValue5(mention.createdAt),
747
+ children: mention.createdAt
748
+ }
749
+ )
750
+ ] }),
751
+ /* @__PURE__ */ jsx10(
752
+ Button4,
753
+ {
754
+ type: "button",
755
+ intent: "tertiary",
756
+ density: "compact",
757
+ disabled: !onOpen,
758
+ "aria-label": `Open mention ${mention.messageRef}`,
759
+ onClick: () => onOpen?.(mention.messageRef),
760
+ children: "Open"
761
+ }
762
+ )
763
+ ] }, mention.id)) }) : /* @__PURE__ */ jsx10("p", { className: "eth-activity-mention-list__empty", role: "status", children: "No mentions assigned to you." })
764
+ }
765
+ );
766
+ }
767
+ function initialsForName(value) {
768
+ const parts = value.trim().split(/\s+/).filter(Boolean);
769
+ if (parts.length === 0) return "?";
770
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
771
+ return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
772
+ }
773
+ function dateTimeValue5(value) {
774
+ const trimmed = value.trim();
775
+ if (/^\d{2}:\d{2}(:\d{2})?$/.test(trimmed)) return trimmed;
776
+ const date = new Date(trimmed);
777
+ return Number.isFinite(date.valueOf()) ? date.toISOString() : void 0;
778
+ }
779
+
780
+ // src/components/WatcherList.tsx
781
+ import {
782
+ Badge as Badge6,
783
+ Button as Button5,
784
+ IconButton,
785
+ Surface as Surface8
786
+ } from "@echothink-ui/core";
787
+ import { CloseIcon, PlusIcon } from "@echothink-ui/icons";
788
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
789
+ function WatcherList({
790
+ watchers,
791
+ onAdd,
792
+ onRemove,
793
+ title,
794
+ subtitle,
795
+ className,
796
+ role,
797
+ footer,
798
+ "aria-label": ariaLabel,
799
+ ...props
800
+ }) {
801
+ const heading = title ?? "Watchers";
802
+ const regionLabel = ariaLabel ?? (typeof heading === "string" ? heading : "Watchers");
803
+ const addAction = onAdd ? /* @__PURE__ */ jsx11(
804
+ Button5,
805
+ {
806
+ type: "button",
807
+ density: "compact",
808
+ intent: "tertiary",
809
+ icon: /* @__PURE__ */ jsx11(PlusIcon, { size: 16 }),
810
+ onClick: onAdd,
811
+ children: "Add watcher"
812
+ }
813
+ ) : null;
814
+ const surfaceFooter = addAction || footer ? /* @__PURE__ */ jsxs10("div", { className: "eth-activity-watcher-list__footer", children: [
815
+ addAction,
816
+ footer
817
+ ] }) : void 0;
818
+ return /* @__PURE__ */ jsx11(
819
+ Surface8,
820
+ {
821
+ ...props,
822
+ title: heading,
823
+ subtitle: subtitle ?? watcherSummary(watchers.length),
824
+ className: ["eth-activity-watcher-list", className].filter(Boolean).join(" "),
825
+ "data-eth-component": "WatcherList",
826
+ role: role ?? "region",
827
+ "aria-label": regionLabel,
828
+ footer: surfaceFooter,
829
+ children: watchers.length ? /* @__PURE__ */ jsx11("ul", { className: "eth-activity-watcher-list__items", "aria-label": "Watchers", children: watchers.map((watcher) => /* @__PURE__ */ jsxs10("li", { className: "eth-activity-watcher-list__item", children: [
830
+ /* @__PURE__ */ jsx11("span", { className: "eth-activity-watcher-list__avatar", "aria-hidden": "true", children: watcher.avatar ? /* @__PURE__ */ jsx11("img", { src: watcher.avatar, alt: "" }) : initialsForName2(watcher.label) }),
831
+ /* @__PURE__ */ jsxs10("span", { className: "eth-activity-watcher-list__identity", children: [
832
+ /* @__PURE__ */ jsx11("strong", { children: watcher.label }),
833
+ /* @__PURE__ */ jsx11("span", { children: watcherDetail(watcher) })
834
+ ] }),
835
+ /* @__PURE__ */ jsxs10("span", { className: "eth-activity-watcher-list__actions", children: [
836
+ /* @__PURE__ */ jsx11(Badge6, { severity: "neutral", children: watcherKindLabel(watcher.kind) }),
837
+ onRemove ? /* @__PURE__ */ jsx11(
838
+ IconButton,
839
+ {
840
+ type: "button",
841
+ density: "compact",
842
+ intent: "ghost",
843
+ label: `Remove ${watcher.label} from watchers`,
844
+ icon: /* @__PURE__ */ jsx11(CloseIcon, { size: 16 }),
845
+ onClick: () => onRemove(watcher.id)
846
+ }
847
+ ) : null
848
+ ] })
849
+ ] }, watcher.id)) }) : /* @__PURE__ */ jsx11("p", { className: "eth-activity-watcher-list__empty", role: "status", children: "No watchers have been added." })
850
+ }
851
+ );
852
+ }
853
+ function watcherSummary(count) {
854
+ if (count === 0) return "No watchers configured";
855
+ return `${count} ${count === 1 ? "watcher" : "watchers"} notified on updates`;
856
+ }
857
+ function watcherDetail(watcher) {
858
+ const details = [watcher.role, watcher.email].filter(Boolean);
859
+ return details.length ? details.join(" / ") : "Receives activity updates";
860
+ }
861
+ function watcherKindLabel(kind) {
862
+ if (kind === "group") return "Group";
863
+ if (kind === "service-account") return "Service account";
864
+ return "User";
865
+ }
866
+ function initialsForName2(value) {
867
+ const parts = value.trim().split(/\s+/).filter(Boolean);
868
+ if (!parts.length) return "?";
869
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
870
+ return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
871
+ }
872
+
873
+ // src/index.tsx
874
+ import { AuditLogTable } from "@echothink-ui/data";
875
+ var ActivityComponentNames = [
876
+ "NotificationCenter",
877
+ "NotificationItem",
878
+ "ActivityFeed",
879
+ "ActivityTimeline",
880
+ "AlertBanner",
881
+ "SystemStatusBanner",
882
+ "IncidentPanel",
883
+ "ChangelogPanel",
884
+ "SubscriptionPreferences",
885
+ "MentionList",
886
+ "WatcherList",
887
+ "AuditLogTable"
888
+ ];
889
+ export {
890
+ ActivityComponentNames,
891
+ ActivityFeed,
892
+ ActivityTimeline,
893
+ AlertBanner,
894
+ AuditLogTable,
895
+ ChangelogPanel,
896
+ IncidentPanel,
897
+ MentionList,
898
+ NotificationCenter,
899
+ NotificationItem,
900
+ SubscriptionPreferences,
901
+ SystemStatusBanner,
902
+ WatcherList
903
+ };
904
+ //# sourceMappingURL=index.js.map