hs-uix 1.6.4 → 1.7.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.
package/dist/feed.js ADDED
@@ -0,0 +1,939 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/feed.js
30
+ var feed_exports = {};
31
+ __export(feed_exports, {
32
+ Feed: () => Feed
33
+ });
34
+ module.exports = __toCommonJS(feed_exports);
35
+
36
+ // packages/feed/src/Feed.jsx
37
+ var import_react2 = __toESM(require("react"));
38
+ var import_ui_extensions2 = require("@hubspot/ui-extensions");
39
+
40
+ // src/common-components/AvatarStack.js
41
+ var import_react = __toESM(require("react"));
42
+ var import_ui_extensions = require("@hubspot/ui-extensions");
43
+
44
+ // src/common-components/svgDefaults.js
45
+ var HS_FONT_FAMILY = '"Lexend Deca", Helvetica, Arial, sans-serif';
46
+ var HS_TEXT_COLOR = "#33475b";
47
+ var HS_NEUTRAL_CHIP = "#CBD6E2";
48
+
49
+ // src/common-components/AvatarStack.js
50
+ var DEFAULT_COLORS = [
51
+ "#0091ae",
52
+ "#8B0000",
53
+ "#ff5c35",
54
+ "#00bda5",
55
+ "#fdcc00",
56
+ "#516f90",
57
+ "#003366",
58
+ "#8e7cc3"
59
+ ];
60
+ var SIZE_TOKENS = {
61
+ xs: 16,
62
+ "extra-small": 16,
63
+ sm: 20,
64
+ "small": 20,
65
+ md: 24,
66
+ "med": 24,
67
+ "medium": 24,
68
+ lg: 32,
69
+ "large": 32,
70
+ xl: 40,
71
+ "extra-large": 40
72
+ };
73
+ var resolveSize = (size) => {
74
+ if (typeof size === "number") return size;
75
+ if (typeof size === "string" && SIZE_TOKENS[size] != null) return SIZE_TOKENS[size];
76
+ return 24;
77
+ };
78
+ var isImageUri = (s) => typeof s === "string" && /^(https?:|data:image\/)/i.test(s);
79
+ var escapeXmlAttr = (s) => String(s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
80
+ var pickColor = (key, palette, index) => {
81
+ if (!key) return palette[index % palette.length];
82
+ const code = String(key).charCodeAt(0) || 0;
83
+ return palette[(code + index) % palette.length];
84
+ };
85
+ var normalizeEntry = (entry) => {
86
+ if (entry == null) return null;
87
+ if (typeof entry === "string") {
88
+ if (entry.length === 0) return null;
89
+ if (isImageUri(entry)) return { src: entry };
90
+ return { letter: entry.slice(0, 2).toUpperCase() };
91
+ }
92
+ if (typeof entry === "object") {
93
+ if (entry.src) return { src: entry.src, letter: entry.letter };
94
+ if (entry.letter) return { letter: String(entry.letter).slice(0, 2).toUpperCase(), color: entry.color };
95
+ }
96
+ return null;
97
+ };
98
+ var makeAvatarStackDataUri = (rawEntries, opts = {}) => {
99
+ const {
100
+ size: sizeProp = "medium",
101
+ step: stepProp,
102
+ overlap: overlapProp,
103
+ maxVisible = 4,
104
+ colors = DEFAULT_COLORS,
105
+ overflowBg = HS_NEUTRAL_CHIP,
106
+ overflowColor = HS_TEXT_COLOR,
107
+ fontFamily = HS_FONT_FAMILY
108
+ } = opts;
109
+ const size = resolveSize(sizeProp);
110
+ let step;
111
+ if (stepProp != null) {
112
+ step = stepProp;
113
+ } else if (overlapProp != null) {
114
+ const clampedOverlap = Math.max(0, Math.min(size - 1, overlapProp));
115
+ step = size - clampedOverlap;
116
+ } else {
117
+ step = Math.round(size * 0.65);
118
+ }
119
+ const entries = (rawEntries || []).map(normalizeEntry).filter(Boolean);
120
+ if (entries.length === 0) return null;
121
+ const visible = entries.slice(0, maxVisible);
122
+ const overflowCount = entries.length - visible.length;
123
+ const slots = overflowCount > 0 ? [...entries.slice(0, maxVisible - 1), { overflow: overflowCount }] : visible;
124
+ const count = slots.length;
125
+ const r = size / 2;
126
+ const haloR = r + 1;
127
+ const width = size + (count - 1) * step;
128
+ const height = size;
129
+ const defs = `<defs><clipPath id="hsuixAvatarClip"><circle cx="${r}" cy="${r}" r="${r}"/></clipPath></defs>`;
130
+ const fontFamilyAttr = fontFamily.replace(/"/g, "&quot;");
131
+ const pieces = slots.map((slot, i) => {
132
+ const cx = r + i * step;
133
+ const tx = i * step;
134
+ const halo = i > 0 ? `<circle cx="${cx}" cy="${r}" r="${haloR}" fill="#ffffff" />` : "";
135
+ if (slot.overflow) {
136
+ return halo + `<circle cx="${cx}" cy="${r}" r="${r}" fill="${overflowBg}" /><text x="${cx}" y="${r + 1}" text-anchor="middle" dominant-baseline="central" font-family="${fontFamilyAttr}" font-size="${Math.round(size * 0.42)}" font-weight="700" fill="${overflowColor}">+${slot.overflow}</text>`;
137
+ }
138
+ if (slot.src) {
139
+ return halo + `<g transform="translate(${tx}, 0)"><image href="${escapeXmlAttr(slot.src)}" x="0" y="0" width="${size}" height="${size}" preserveAspectRatio="xMidYMid slice" clip-path="url(#hsuixAvatarClip)" /></g>`;
140
+ }
141
+ const letter = slot.letter || "?";
142
+ const bgColor = slot.color || pickColor(letter, colors, i);
143
+ return halo + `<circle cx="${cx}" cy="${r}" r="${r}" fill="${bgColor}" /><text x="${cx}" y="${r + 1}" text-anchor="middle" dominant-baseline="central" font-family="${fontFamilyAttr}" font-size="${Math.round(size * 0.46)}" font-weight="700" fill="#ffffff">${escapeXmlAttr(letter)}</text>`;
144
+ });
145
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">` + defs + pieces.join("") + `</svg>`;
146
+ return {
147
+ src: `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`,
148
+ width,
149
+ height,
150
+ count
151
+ };
152
+ };
153
+ var AvatarStack = ({
154
+ items,
155
+ size,
156
+ overlap,
157
+ step,
158
+ maxVisible,
159
+ colors,
160
+ overflowBg,
161
+ overflowColor,
162
+ fontFamily,
163
+ alt
164
+ }) => {
165
+ const stack = makeAvatarStackDataUri(items, {
166
+ size,
167
+ overlap,
168
+ step,
169
+ maxVisible,
170
+ colors,
171
+ overflowBg,
172
+ overflowColor,
173
+ fontFamily
174
+ });
175
+ if (!stack) return null;
176
+ return import_react.default.createElement(import_ui_extensions.Image, {
177
+ src: stack.src,
178
+ width: stack.width,
179
+ height: stack.height,
180
+ alt: alt ?? `${items.length} associated records`
181
+ });
182
+ };
183
+
184
+ // packages/feed/src/Feed.jsx
185
+ var DEFAULT_LABELS = {
186
+ search: "Search activity...",
187
+ sort: "Sort",
188
+ all: "All",
189
+ clearAll: "Clear all",
190
+ dateFrom: "From",
191
+ dateTo: "To",
192
+ loading: "Loading feed...",
193
+ loadingMessage: "This should only take a moment.",
194
+ loadingMore: "Loading...",
195
+ loadMore: "View more",
196
+ collapseAll: "Collapse all",
197
+ expandAll: "Expand all",
198
+ emptyTitle: "No activity yet",
199
+ emptyMessage: "New activity will appear here.",
200
+ errorTitle: "Something went wrong.",
201
+ errorMessage: "An error occurred while loading the feed.",
202
+ itemCount: (shown, total, label) => shown === total ? `${total} ${label}` : `${shown} of ${total} ${label}`,
203
+ allItems: "All"
204
+ };
205
+ var DEFAULT_RECORD_LABEL = { singular: "item", plural: "items" };
206
+ var DEFAULT_SEARCH_FIELDS = ["title", "subject", "body", "description", "content", "preview", "type", "typeLabel", "actorName", "author"];
207
+ var DEFAULT_PAGE_SIZE = 5;
208
+ var hasValue = (value) => value != null && value !== false && value !== "";
209
+ var getItemKey = (item, index, getKey) => {
210
+ if (typeof getKey === "function") return getKey(item, index);
211
+ return (item == null ? void 0 : item.id) ?? (item == null ? void 0 : item.key) ?? `feed-item-${index}`;
212
+ };
213
+ var getValue = (item, field) => {
214
+ if (typeof field === "function") return field(item);
215
+ if (typeof field === "string") return item == null ? void 0 : item[field];
216
+ return void 0;
217
+ };
218
+ var normalizeText = (value) => String(value ?? "").toLowerCase();
219
+ var pickTimestamp = (item) => (item == null ? void 0 : item.timestamp) ?? (item == null ? void 0 : item.time) ?? (item == null ? void 0 : item.date) ?? (item == null ? void 0 : item.createdAt) ?? (item == null ? void 0 : item.dateLabel);
220
+ var pickActor = (item) => (item == null ? void 0 : item.actor) ?? (item == null ? void 0 : item.actorName) ?? (item == null ? void 0 : item.author);
221
+ var pickActorName = (actor) => {
222
+ if (!actor || typeof actor !== "object") return actor;
223
+ return actor.name ?? actor.label ?? actor.email;
224
+ };
225
+ var pickActorAvatar = (actor) => {
226
+ if (!actor || typeof actor !== "object") return null;
227
+ return actor.avatar ?? actor.avatarUrl ?? actor.src ?? actor.initials ?? actor.name;
228
+ };
229
+ var pickBody = (item) => (item == null ? void 0 : item.body) ?? (item == null ? void 0 : item.description) ?? (item == null ? void 0 : item.content) ?? (item == null ? void 0 : item.preview) ?? (item == null ? void 0 : item.notePreview);
230
+ var pickHeaderActions = (item) => item == null ? void 0 : item.headerActions;
231
+ var itemHasExpandableContent = (item, fields) => {
232
+ if ((item == null ? void 0 : item.collapsible) === false) return false;
233
+ if ((item == null ? void 0 : item.collapsible) === true) return true;
234
+ if (hasValue(pickBody(item))) return true;
235
+ if (hasValue(pickActor(item))) return true;
236
+ if (hasValue(item == null ? void 0 : item.actions)) return true;
237
+ if (hasValue(item == null ? void 0 : item.footer)) return true;
238
+ if (hasValue(item == null ? void 0 : item.meta) || hasValue(item == null ? void 0 : item.metadata)) return true;
239
+ if (Array.isArray(fields)) {
240
+ if (fields.some((f) => ["body", "footer"].includes(f.placement ?? "body"))) return true;
241
+ }
242
+ return false;
243
+ };
244
+ var pickMeta = (item) => (item == null ? void 0 : item.meta) ?? (item == null ? void 0 : item.metadata);
245
+ var pickType = (item) => (item == null ? void 0 : item.typeLabel) ?? (item == null ? void 0 : item.type);
246
+ var pickStatus = (item) => (item == null ? void 0 : item.statusLabel) ?? (item == null ? void 0 : item.status) ?? (item == null ? void 0 : item.outcome) ?? (item == null ? void 0 : item.severity);
247
+ var pickStatusVariant = (item) => (item == null ? void 0 : item.statusVariant) ?? (item == null ? void 0 : item.outcomeVariant) ?? (item == null ? void 0 : item.severityVariant) ?? "default";
248
+ var toDate = (value) => {
249
+ if (!value) return null;
250
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
251
+ if (typeof value === "object" && value.year != null && value.month != null && value.date != null) {
252
+ return new Date(value.year, value.month, value.date);
253
+ }
254
+ const parsed = new Date(value);
255
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
256
+ };
257
+ var compareValues = (a, b) => {
258
+ const aDate = toDate(a);
259
+ const bDate = toDate(b);
260
+ if (aDate && bDate) return aDate.getTime() - bDate.getTime();
261
+ if (typeof a === "number" && typeof b === "number") return a - b;
262
+ return String(a ?? "").localeCompare(String(b ?? ""));
263
+ };
264
+ var formatDateGroup = (value) => {
265
+ if (!value) return "No date";
266
+ const date = toDate(value);
267
+ if (!date) return String(value);
268
+ const today = /* @__PURE__ */ new Date();
269
+ const yesterday = /* @__PURE__ */ new Date();
270
+ yesterday.setDate(today.getDate() - 1);
271
+ const sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
272
+ if (sameDay(date, today)) return "Today";
273
+ if (sameDay(date, yesterday)) return "Yesterday";
274
+ return date.toLocaleDateString(void 0, {
275
+ month: "short",
276
+ day: "numeric",
277
+ year: date.getFullYear() === today.getFullYear() ? void 0 : "numeric"
278
+ });
279
+ };
280
+ var formatTimestamp = (value) => {
281
+ if (!value) return value;
282
+ const date = toDate(value);
283
+ if (!date) return value;
284
+ return date.toLocaleString(void 0, {
285
+ month: "short",
286
+ day: "numeric",
287
+ year: date.getFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? void 0 : "numeric",
288
+ hour: "numeric",
289
+ minute: "2-digit"
290
+ });
291
+ };
292
+ var renderMaybe = (renderer, item, index) => {
293
+ if (typeof renderer !== "function") return null;
294
+ return renderer(item, index);
295
+ };
296
+ var isEmptyFilterValue = (value) => {
297
+ if (value == null || value === "" || value === "all") return true;
298
+ if (Array.isArray(value)) return value.length === 0;
299
+ if (typeof value === "object") return !value.from && !value.to;
300
+ return false;
301
+ };
302
+ var getFilterValue = (filterValues, filter) => {
303
+ if (!filterValues) return filter.defaultValue;
304
+ const value = filterValues[filter.name];
305
+ return value === void 0 ? filter.defaultValue : value;
306
+ };
307
+ var getRecordLabel = (recordLabel, count) => {
308
+ const label = { ...DEFAULT_RECORD_LABEL, ...recordLabel || {} };
309
+ return count === 1 ? label.singular : label.plural;
310
+ };
311
+ var FeedIcon = ({ name, size = "sm" }) => {
312
+ if (!name) return null;
313
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name, size, purpose: "decorative" });
314
+ };
315
+ var FeedActorAvatar = ({ item, avatarSize }) => {
316
+ const actor = pickActor(item);
317
+ const avatar = (item == null ? void 0 : item.avatar) ?? pickActorAvatar(actor);
318
+ if (!hasValue(avatar)) return null;
319
+ return /* @__PURE__ */ import_react2.default.createElement(
320
+ AvatarStack,
321
+ {
322
+ items: [avatar],
323
+ size: item.avatarSize ?? avatarSize,
324
+ maxVisible: 1,
325
+ alt: item.avatarAlt ?? pickActorName(actor) ?? item.title ?? "Feed item avatar"
326
+ }
327
+ );
328
+ };
329
+ var FeedTypeIcon = ({ item, iconSize }) => {
330
+ if (hasValue(item == null ? void 0 : item.icon) && typeof item.icon !== "string") return item.icon;
331
+ const iconName = typeof (item == null ? void 0 : item.icon) === "string" ? item.icon : item == null ? void 0 : item.iconName;
332
+ if (!hasValue(iconName)) return null;
333
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: iconName, size: iconSize, purpose: "decorative" });
334
+ };
335
+ var FeedActions = ({ actions }) => {
336
+ if (!Array.isArray(actions) || actions.length === 0) return actions || null;
337
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.ButtonRow, { dropDownButtonOptions: { text: "More", size: "small", variant: "transparent" } }, actions.filter(Boolean).slice(0, 3).map((action, index) => /* @__PURE__ */ import_react2.default.createElement(
338
+ import_ui_extensions2.Button,
339
+ {
340
+ key: action.key ?? action.label ?? `action-${index}`,
341
+ size: action.size ?? "small",
342
+ variant: action.variant ?? "transparent",
343
+ disabled: action.disabled,
344
+ href: action.href,
345
+ onClick: action.onClick
346
+ },
347
+ action.icon ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: action.icon, size: action.iconSize ?? "sm", purpose: "decorative" }) : null,
348
+ action.label
349
+ )));
350
+ };
351
+ var FeedField = ({ field, item, index }) => {
352
+ if (!field) return null;
353
+ if (field.visible && !field.visible(item)) return null;
354
+ const value = getValue(item, field.field);
355
+ const rendered = field.render ? field.render(value, item, index) : value;
356
+ if (!hasValue(rendered)) return null;
357
+ if (field.href) {
358
+ const href = typeof field.href === "function" ? field.href(item) : field.href;
359
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { href }, rendered);
360
+ }
361
+ if (field.type === "status") {
362
+ const variant = typeof field.variant === "function" ? field.variant(value, item) : field.variant;
363
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.StatusTag, { variant: variant ?? "default" }, rendered);
364
+ }
365
+ if (field.type === "tag") {
366
+ const variant = typeof field.variant === "function" ? field.variant(value, item) : field.variant;
367
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tag, { variant: variant ?? "default" }, rendered);
368
+ }
369
+ if (field.label) {
370
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.DescriptionListItem, { label: field.label }, rendered);
371
+ }
372
+ return rendered;
373
+ };
374
+ var fieldsForPlacement = (fields, placement) => Array.isArray(fields) ? fields.filter((field) => (field.placement ?? "body") === placement) : [];
375
+ var renderPlacedFields = ({ fields, placement, item, index, inline = false }) => {
376
+ const placed = fieldsForPlacement(fields, placement);
377
+ if (placed.length === 0) return null;
378
+ const nodes = placed.map((field, fieldIndex) => /* @__PURE__ */ import_react2.default.createElement(
379
+ FeedField,
380
+ {
381
+ key: field.key ?? field.field ?? `${placement}-${fieldIndex}`,
382
+ field,
383
+ item,
384
+ index
385
+ }
386
+ )).filter(Boolean);
387
+ if (nodes.length === 0) return null;
388
+ if (inline) return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { gap: "xs", align: "center" }, nodes);
389
+ if (placed.some((field) => field.label)) return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.DescriptionList, { direction: "row" }, nodes);
390
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, nodes);
391
+ };
392
+ var renderHeaderActions = (headerActions) => {
393
+ if (!hasValue(headerActions)) return null;
394
+ if (!Array.isArray(headerActions)) return headerActions;
395
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { gap: "sm", align: "center" }, headerActions.filter(Boolean).map((action, index) => /* @__PURE__ */ import_react2.default.createElement(
396
+ import_ui_extensions2.Link,
397
+ {
398
+ key: action.key ?? action.label ?? `header-action-${index}`,
399
+ href: action.href,
400
+ onClick: action.onClick
401
+ },
402
+ action.label
403
+ )));
404
+ };
405
+ var DefaultFeedItem = ({
406
+ item,
407
+ index,
408
+ fields,
409
+ compact,
410
+ avatarSize,
411
+ iconSize,
412
+ collapsible,
413
+ expanded,
414
+ onToggleExpanded,
415
+ renderActor,
416
+ renderTimestamp,
417
+ renderMeta,
418
+ renderActions,
419
+ renderFooter
420
+ }) => {
421
+ const rawActor = pickActor(item);
422
+ const actor = renderMaybe(renderActor, item, index) ?? pickActorName(rawActor);
423
+ const timestamp = renderMaybe(renderTimestamp, item, index) ?? pickTimestamp(item);
424
+ const meta = renderMaybe(renderMeta, item, index) ?? pickMeta(item);
425
+ const type = pickType(item);
426
+ const status = pickStatus(item);
427
+ const statusVariant = pickStatusVariant(item);
428
+ const actions = renderMaybe(renderActions, item, index) ?? (item == null ? void 0 : item.actions);
429
+ const headerActions = pickHeaderActions(item);
430
+ const footer = renderMaybe(renderFooter, item, index) ?? (item == null ? void 0 : item.footer);
431
+ const body = pickBody(item);
432
+ const avatar = /* @__PURE__ */ import_react2.default.createElement(FeedActorAvatar, { item, avatarSize });
433
+ const typeIcon = /* @__PURE__ */ import_react2.default.createElement(FeedTypeIcon, { item, iconSize });
434
+ const hasAvatarNode = hasValue(item == null ? void 0 : item.avatar) || hasValue(pickActorAvatar(rawActor));
435
+ const titleFields = fieldsForPlacement(fields, "title");
436
+ const titleField = titleFields.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(FeedField, { field: titleFields[0], item, index }) : null;
437
+ const subtitleFields = renderPlacedFields({ fields, placement: "subtitle", item, index, inline: true });
438
+ const metaFields = renderPlacedFields({ fields, placement: "meta", item, index, inline: true });
439
+ const bodyFields = renderPlacedFields({ fields, placement: "body", item, index });
440
+ const footerFields = renderPlacedFields({ fields, placement: "footer", item, index, inline: true });
441
+ const titleContent = titleField ?? (item == null ? void 0 : item.title) ?? (item == null ? void 0 : item.subject);
442
+ const title = hasValue(item == null ? void 0 : item.href) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { href: item.href }, titleContent) : titleContent;
443
+ const headerLeft = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, collapsible ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { onClick: onToggleExpanded }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: expanded ? "downCarat" : "right", size: "sm", purpose: "decorative" })) : null, typeIcon, hasValue(title) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" }, truncate: true }, title));
444
+ const headerRight = hasValue(headerActions) || hasValue(timestamp) || hasValue(type) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { gap: "sm", align: "center" }, renderHeaderActions(headerActions), hasValue(type) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, type), hasValue(timestamp) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, formatTimestamp(timestamp))) : null;
445
+ const showBody = !collapsible || expanded;
446
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: compact ? "xs" : "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "between", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, headerLeft), headerRight), showBody ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: compact ? "xs" : "sm" }, (hasAvatarNode || hasValue(actor) || hasValue(subtitleFields) || hasValue(status)) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs", wrap: "wrap" }, hasAvatarNode ? avatar : null, hasValue(actor) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, actor), subtitleFields, hasValue(status) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.StatusTag, { variant: statusVariant, hollow: true }, status)), hasValue(body) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, body), bodyFields, Array.isArray(meta) ? meta.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.List, { variant: "inline-divided" }, meta) : metaFields : hasValue(meta) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { gap: "xs" }, meta) : metaFields ? metaFields : null, hasValue(actions) && /* @__PURE__ */ import_react2.default.createElement(FeedActions, { actions }), (hasValue(footer) || hasValue(footerFields)) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { gap: "xs", align: "center" }, footerFields, footer)) : null);
447
+ };
448
+ var applyTab = (items, activeTab, tabField) => {
449
+ if (!activeTab || activeTab === "all") return items;
450
+ return items.filter((item) => String(getValue(item, tabField)) === String(activeTab));
451
+ };
452
+ var normalizeTabs = (tabs, items, tabField, labels) => {
453
+ if (Array.isArray(tabs) && tabs.length > 0) return tabs;
454
+ const values = [];
455
+ items.forEach((item) => {
456
+ const value = getValue(item, tabField);
457
+ if (value != null && value !== "" && !values.some((v) => String(v) === String(value))) values.push(value);
458
+ });
459
+ return [
460
+ { label: labels.allItems, value: "all" },
461
+ ...values.map((value) => ({ label: String(value), value }))
462
+ ];
463
+ };
464
+ var applySearch = (items, search, searchFields) => {
465
+ const term = normalizeText(search).trim();
466
+ if (!term) return items;
467
+ const fields = Array.isArray(searchFields) && searchFields.length > 0 ? searchFields : DEFAULT_SEARCH_FIELDS;
468
+ return items.filter((item) => fields.some((field) => {
469
+ const value = field === "actor" || field === "author" ? pickActorName(getValue(item, field)) : getValue(item, field);
470
+ return normalizeText(value).includes(term);
471
+ }));
472
+ };
473
+ var applyFilters = (items, filters, filterValues) => {
474
+ if (!Array.isArray(filters) || filters.length === 0) return items;
475
+ return items.filter((item) => filters.every((filter) => {
476
+ const value = getFilterValue(filterValues, filter);
477
+ if (isEmptyFilterValue(value)) return true;
478
+ if (typeof filter.filterFn === "function") return filter.filterFn(item, value);
479
+ const itemValue = getValue(item, filter.field ?? filter.name);
480
+ if (filter.type === "multiselect") {
481
+ const selected = Array.isArray(value) ? value : [value];
482
+ return selected.map(String).includes(String(itemValue));
483
+ }
484
+ if (filter.type === "dateRange") {
485
+ const itemDate = toDate(itemValue);
486
+ if (!itemDate) return false;
487
+ const from = toDate(value.from);
488
+ const to = toDate(value.to);
489
+ if (from && itemDate < from) return false;
490
+ if (to && itemDate > to) return false;
491
+ return true;
492
+ }
493
+ return String(itemValue) === String(value);
494
+ }));
495
+ };
496
+ var applySort = (items, sortValue, sortOptions) => {
497
+ if (!sortValue || !Array.isArray(sortOptions) || sortOptions.length === 0) return items;
498
+ const option = sortOptions.find((opt) => opt.value === sortValue);
499
+ if (!option) return items;
500
+ const sorted = [...items];
501
+ sorted.sort((a, b) => {
502
+ if (typeof option.comparator === "function") return option.comparator(a, b);
503
+ const direction = option.direction === "desc" || option.direction === "descending" ? -1 : 1;
504
+ return compareValues(getValue(a, option.field), getValue(b, option.field)) * direction;
505
+ });
506
+ return sorted;
507
+ };
508
+ var buildGroups = ({ items, groupBy, groupByDate, getGroupLabel }) => {
509
+ if (!groupBy && !groupByDate) return [{ key: "__all__", label: null, items }];
510
+ const groups = [];
511
+ const groupMap = /* @__PURE__ */ new Map();
512
+ items.forEach((item) => {
513
+ const rawKey = groupByDate ? formatDateGroup(pickTimestamp(item)) : getValue(item, groupBy);
514
+ const key = rawKey == null ? "__empty__" : String(rawKey);
515
+ const label = typeof getGroupLabel === "function" ? getGroupLabel(rawKey, item) : key === "__empty__" ? "Other" : key;
516
+ if (!groupMap.has(key)) {
517
+ const group = { key, label, items: [] };
518
+ groupMap.set(key, group);
519
+ groups.push(group);
520
+ }
521
+ groupMap.get(key).items.push(item);
522
+ });
523
+ return groups;
524
+ };
525
+ var FeedToolbar = ({
526
+ labels,
527
+ search,
528
+ onSearch,
529
+ showSearch,
530
+ searchPlaceholder,
531
+ filters,
532
+ filterValues,
533
+ onFilter,
534
+ filterInlineLimit,
535
+ sortOptions,
536
+ sort,
537
+ onSort,
538
+ showItemCount,
539
+ itemCountLabel
540
+ }) => {
541
+ const [showMoreFilters, setShowMoreFilters] = (0, import_react2.useState)(false);
542
+ const inlineFilters = (filters || []).slice(0, filterInlineLimit);
543
+ const overflowFilters = (filters || []).slice(filterInlineLimit);
544
+ const renderFilterControl = (filter) => {
545
+ const value = getFilterValue(filterValues, filter);
546
+ if (filter.type === "multiselect") {
547
+ return /* @__PURE__ */ import_react2.default.createElement(
548
+ import_ui_extensions2.MultiSelect,
549
+ {
550
+ key: filter.name,
551
+ name: `feed-filter-${filter.name}`,
552
+ label: "",
553
+ placeholder: filter.placeholder || filter.label || labels.all,
554
+ value: Array.isArray(value) ? value : [],
555
+ options: filter.options || [],
556
+ onChange: (next) => onFilter(filter.name, next)
557
+ }
558
+ );
559
+ }
560
+ if (filter.type === "dateRange") {
561
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: filter.name, direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(
562
+ import_ui_extensions2.DateInput,
563
+ {
564
+ label: "",
565
+ placeholder: filter.fromLabel ?? labels.dateFrom,
566
+ name: `feed-filter-${filter.name}-from`,
567
+ format: "medium",
568
+ value: (value == null ? void 0 : value.from) ?? null,
569
+ onChange: (next) => onFilter(filter.name, { ...value || {}, from: next })
570
+ }
571
+ ), /* @__PURE__ */ import_react2.default.createElement(FeedIcon, { name: "dataSync", size: "sm" }), /* @__PURE__ */ import_react2.default.createElement(
572
+ import_ui_extensions2.DateInput,
573
+ {
574
+ label: "",
575
+ placeholder: filter.toLabel ?? labels.dateTo,
576
+ name: `feed-filter-${filter.name}-to`,
577
+ format: "medium",
578
+ value: (value == null ? void 0 : value.to) ?? null,
579
+ onChange: (next) => onFilter(filter.name, { ...value || {}, to: next })
580
+ }
581
+ ));
582
+ }
583
+ const options = filter.includeAll === false ? filter.options || [] : [{ label: filter.allLabel ?? filter.placeholder ?? filter.label ?? labels.all, value: "all" }, ...filter.options || []];
584
+ return /* @__PURE__ */ import_react2.default.createElement(
585
+ import_ui_extensions2.Select,
586
+ {
587
+ key: filter.name,
588
+ name: `feed-filter-${filter.name}`,
589
+ variant: "transparent",
590
+ placeholder: filter.placeholder || filter.label || labels.all,
591
+ value: value ?? "all",
592
+ options,
593
+ onChange: (next) => onFilter(filter.name, next)
594
+ }
595
+ );
596
+ };
597
+ const hasLeftControls = showSearch || inlineFilters.length > 0 || overflowFilters.length > 0;
598
+ const hasRightControls = Array.isArray(sortOptions) && sortOptions.length > 0 || showItemCount;
599
+ if (!hasLeftControls && !hasRightControls) return null;
600
+ return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 3 }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "sm" }, hasLeftControls ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch ? /* @__PURE__ */ import_react2.default.createElement(
601
+ import_ui_extensions2.SearchInput,
602
+ {
603
+ name: "feed-search",
604
+ placeholder: searchPlaceholder ?? labels.search,
605
+ value: search,
606
+ clearable: true,
607
+ onChange: onSearch
608
+ }
609
+ ) : null, inlineFilters.map(renderFilterControl), overflowFilters.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(
610
+ import_ui_extensions2.Button,
611
+ {
612
+ variant: "transparent",
613
+ size: "sm",
614
+ onClick: () => setShowMoreFilters((prev) => !prev)
615
+ },
616
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "filter", size: "sm", purpose: "decorative" }),
617
+ "Filters"
618
+ ) : null) : null, showMoreFilters && overflowFilters.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, overflowFilters.map(renderFilterControl)) : null)), hasRightControls ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1, alignSelf: "start" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", justify: "end", wrap: "wrap" }, Array.isArray(sortOptions) && sortOptions.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(
619
+ import_ui_extensions2.Select,
620
+ {
621
+ name: "feed-sort",
622
+ value: sort ?? "",
623
+ variant: "transparent",
624
+ placeholder: labels.sort,
625
+ options: [{ label: labels.sort, value: "" }, ...sortOptions.map((opt) => ({ label: opt.label, value: opt.value }))],
626
+ onChange: onSort
627
+ }
628
+ ) : null, showItemCount ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, itemCountLabel) : null)) : null);
629
+ };
630
+ var Feed = ({
631
+ items = [],
632
+ fields,
633
+ title,
634
+ description,
635
+ actions,
636
+ children,
637
+ loading = false,
638
+ error = null,
639
+ labels: labelOverrides,
640
+ renderItem,
641
+ renderActor,
642
+ renderTimestamp,
643
+ renderMeta,
644
+ renderActions,
645
+ renderFooter,
646
+ renderToolbar,
647
+ renderEmptyState,
648
+ renderLoadingState,
649
+ renderErrorState,
650
+ getKey,
651
+ groupBy,
652
+ groupByDate = false,
653
+ getGroupLabel,
654
+ bordered = true,
655
+ container = bordered ? "tile" : "none",
656
+ showDividers = true,
657
+ itemContainer = "tile",
658
+ compact = false,
659
+ gap = "sm",
660
+ avatarSize = "small",
661
+ iconSize = "sm",
662
+ tabs,
663
+ showTabs,
664
+ tabField = "type",
665
+ tabVariant = "default",
666
+ tabValue,
667
+ defaultTab = "all",
668
+ onTabChange,
669
+ searchFields = DEFAULT_SEARCH_FIELDS,
670
+ showSearch,
671
+ searchPlaceholder,
672
+ searchValue,
673
+ onSearchChange,
674
+ filters = [],
675
+ filterValues,
676
+ defaultFilterValues = {},
677
+ onFilterChange,
678
+ sortOptions = [],
679
+ sort,
680
+ defaultSort = null,
681
+ onSortChange,
682
+ onParamsChange,
683
+ serverSide = false,
684
+ showToolbar = true,
685
+ showItemCount = true,
686
+ itemCountText,
687
+ recordLabel,
688
+ filterInlineLimit = 1,
689
+ pageSize = DEFAULT_PAGE_SIZE,
690
+ initialItemCount,
691
+ maxItems,
692
+ hasMore = false,
693
+ loadingMore = false,
694
+ onLoadMore,
695
+ collapsible = "auto",
696
+ defaultCollapsed = "none",
697
+ collapsedIds,
698
+ onCollapsedIdsChange,
699
+ showCollapseToggle = true
700
+ }) => {
701
+ const labels = { ...DEFAULT_LABELS, ...labelOverrides || {} };
702
+ const safeItems = Array.isArray(items) ? items : [];
703
+ const [internalTab, setInternalTab] = (0, import_react2.useState)(tabValue ?? defaultTab ?? "all");
704
+ const [internalSearch, setInternalSearch] = (0, import_react2.useState)(searchValue ?? "");
705
+ const [internalFilters, setInternalFilters] = (0, import_react2.useState)(filterValues ?? defaultFilterValues);
706
+ const [internalSort, setInternalSort] = (0, import_react2.useState)(sort ?? defaultSort ?? null);
707
+ const [visibleCount, setVisibleCount] = (0, import_react2.useState)(initialItemCount ?? pageSize);
708
+ const computeInitialCollapsed = () => {
709
+ if (Array.isArray(collapsedIds)) return collapsedIds;
710
+ if (Array.isArray(defaultCollapsed)) return defaultCollapsed;
711
+ if (defaultCollapsed === "all") return safeItems.map((item, idx) => getItemKey(item, idx, getKey));
712
+ return [];
713
+ };
714
+ const [internalCollapsedIds, setInternalCollapsedIds] = (0, import_react2.useState)(computeInitialCollapsed);
715
+ const activeTab = tabValue !== void 0 ? tabValue : internalTab;
716
+ const activeSearch = searchValue !== void 0 ? searchValue : internalSearch;
717
+ const activeFilters = filterValues !== void 0 ? filterValues : internalFilters;
718
+ const activeSort = sort !== void 0 ? sort : internalSort;
719
+ const activeCollapsedIds = Array.isArray(collapsedIds) ? collapsedIds : internalCollapsedIds;
720
+ const resolvedShowSearch = showSearch ?? (Array.isArray(searchFields) && searchFields.length > 0);
721
+ const resolvedMaxItems = maxItems ?? visibleCount;
722
+ (0, import_react2.useEffect)(() => {
723
+ if (tabValue !== void 0) setInternalTab(tabValue);
724
+ }, [tabValue]);
725
+ (0, import_react2.useEffect)(() => {
726
+ if (searchValue !== void 0) setInternalSearch(searchValue);
727
+ }, [searchValue]);
728
+ (0, import_react2.useEffect)(() => {
729
+ if (filterValues !== void 0) setInternalFilters(filterValues);
730
+ }, [filterValues]);
731
+ (0, import_react2.useEffect)(() => {
732
+ if (sort !== void 0) setInternalSort(sort);
733
+ }, [sort]);
734
+ (0, import_react2.useEffect)(() => {
735
+ if (Array.isArray(collapsedIds)) setInternalCollapsedIds(collapsedIds);
736
+ }, [collapsedIds]);
737
+ const emitParamsChange = (next) => {
738
+ if (typeof onParamsChange === "function") {
739
+ onParamsChange({ tab: activeTab, search: activeSearch, filters: activeFilters, sort: activeSort, ...next });
740
+ }
741
+ };
742
+ const handleTabChange = (next) => {
743
+ if (tabValue === void 0) setInternalTab(next);
744
+ setVisibleCount(initialItemCount ?? pageSize);
745
+ onTabChange == null ? void 0 : onTabChange(next);
746
+ emitParamsChange({ tab: next });
747
+ };
748
+ const handleSearchChange = (next) => {
749
+ if (searchValue === void 0) setInternalSearch(next);
750
+ setVisibleCount(initialItemCount ?? pageSize);
751
+ onSearchChange == null ? void 0 : onSearchChange(next);
752
+ emitParamsChange({ search: next });
753
+ };
754
+ const handleFilterChange = (name, value) => {
755
+ const nextFilters = { ...activeFilters || {}, [name]: value };
756
+ if (filterValues === void 0) setInternalFilters(nextFilters);
757
+ setVisibleCount(initialItemCount ?? pageSize);
758
+ onFilterChange == null ? void 0 : onFilterChange(nextFilters);
759
+ emitParamsChange({ filters: nextFilters });
760
+ };
761
+ const handleSortChange = (next) => {
762
+ const nextSort = next || null;
763
+ if (sort === void 0) setInternalSort(nextSort);
764
+ setVisibleCount(initialItemCount ?? pageSize);
765
+ onSortChange == null ? void 0 : onSortChange(nextSort);
766
+ emitParamsChange({ sort: nextSort });
767
+ };
768
+ const setCollapsedIds = (next) => {
769
+ if (collapsedIds === void 0) setInternalCollapsedIds(next);
770
+ onCollapsedIdsChange == null ? void 0 : onCollapsedIdsChange(next);
771
+ };
772
+ const toggleItemExpanded = (key) => {
773
+ const isCollapsed = activeCollapsedIds.includes(key);
774
+ setCollapsedIds(isCollapsed ? activeCollapsedIds.filter((id) => id !== key) : [...activeCollapsedIds, key]);
775
+ };
776
+ const handleCollapseAll = () => {
777
+ const collapsibleKeys = visibleItems.map((item, idx) => ({ item, idx })).filter(({ item }) => itemHasExpandableContent(item, fields)).map(({ item, idx }) => getItemKey(item, idx, getKey));
778
+ setCollapsedIds(collapsibleKeys);
779
+ };
780
+ const handleExpandAll = () => setCollapsedIds([]);
781
+ const processedItems = (0, import_react2.useMemo)(() => {
782
+ if (serverSide) return safeItems;
783
+ const tabbed = applyTab(safeItems, activeTab, tabField);
784
+ const searched = applySearch(tabbed, activeSearch, searchFields);
785
+ const filtered = applyFilters(searched, filters, activeFilters);
786
+ return applySort(filtered, activeSort, sortOptions);
787
+ }, [safeItems, activeTab, tabField, activeSearch, searchFields, filters, activeFilters, activeSort, sortOptions, serverSide]);
788
+ const visibleItems = (0, import_react2.useMemo)(
789
+ () => processedItems.slice(0, Math.max(0, resolvedMaxItems)),
790
+ [processedItems, resolvedMaxItems]
791
+ );
792
+ const groups = (0, import_react2.useMemo)(
793
+ () => buildGroups({ items: visibleItems, groupBy, groupByDate, getGroupLabel }),
794
+ [visibleItems, groupBy, groupByDate, getGroupLabel]
795
+ );
796
+ const totalCount = processedItems.length;
797
+ const itemCountLabel = typeof itemCountText === "function" ? itemCountText(visibleItems.length, totalCount) : labels.itemCount(visibleItems.length, totalCount, getRecordLabel(recordLabel, totalCount));
798
+ const canViewMore = visibleItems.length < processedItems.length;
799
+ const shouldShowExternalLoadMore = hasMore && onLoadMore;
800
+ const normalizedTabs = (0, import_react2.useMemo)(
801
+ () => normalizeTabs(tabs, safeItems, tabField, labels),
802
+ [tabs, safeItems, tabField, labels]
803
+ );
804
+ const resolvedShowTabs = showTabs ?? normalizedTabs.length > 1;
805
+ const toolbarNode = renderToolbar ? renderToolbar({
806
+ tab: activeTab,
807
+ tabs: normalizedTabs,
808
+ search: activeSearch,
809
+ filters: activeFilters,
810
+ sort: activeSort,
811
+ totalCount,
812
+ visibleCount: visibleItems.length,
813
+ onTabChange: handleTabChange,
814
+ onSearchChange: handleSearchChange,
815
+ onFilterChange: handleFilterChange,
816
+ onSortChange: handleSortChange
817
+ }) : showToolbar && !loading && !error ? /* @__PURE__ */ import_react2.default.createElement(
818
+ FeedToolbar,
819
+ {
820
+ labels,
821
+ search: activeSearch,
822
+ onSearch: handleSearchChange,
823
+ showSearch: resolvedShowSearch,
824
+ searchPlaceholder,
825
+ filters,
826
+ filterValues: activeFilters,
827
+ onFilter: handleFilterChange,
828
+ filterInlineLimit,
829
+ sortOptions,
830
+ sort: activeSort,
831
+ onSort: handleSortChange,
832
+ showItemCount,
833
+ itemCountLabel
834
+ }
835
+ ) : null;
836
+ const content = [];
837
+ const collapsibleVisibleItems = visibleItems.filter((item) => itemHasExpandableContent(item, fields));
838
+ const allCollapsed = collapsibleVisibleItems.length > 0 && collapsibleVisibleItems.every((item, idx) => {
839
+ const i = visibleItems.indexOf(item);
840
+ return activeCollapsedIds.includes(getItemKey(item, i >= 0 ? i : idx, getKey));
841
+ });
842
+ const collapseToggle = showCollapseToggle && collapsibleVisibleItems.length > 1 && !loading && !error ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { onClick: allCollapsed ? handleExpandAll : handleCollapseAll }, allCollapsed ? labels.expandAll : labels.collapseAll) : null;
843
+ if (hasValue(title) || hasValue(description) || hasValue(actions) || hasValue(children) || collapseToggle) {
844
+ const headerBody = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, hasValue(title) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, title), hasValue(description) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, description), children);
845
+ const headerRight = hasValue(actions) || collapseToggle ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { gap: "sm", align: "center" }, actions, collapseToggle) : null;
846
+ content.push(
847
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: "header", direction: "row", justify: "between", align: "start", gap: "sm" }, headerBody, headerRight)
848
+ );
849
+ }
850
+ const bodyContent = [];
851
+ if (toolbarNode) bodyContent.push(/* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key: "toolbar" }, toolbarNode));
852
+ if (loading) {
853
+ bodyContent.push(
854
+ renderLoadingState ? renderLoadingState({ label: labels.loading }) : (
855
+ // Same EmptyState layout as the empty state (just the "building" image +
856
+ // a loading message) so loading and empty match with no layout shift.
857
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { key: "loading" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.EmptyState, { title: labels.loading, imageName: "building", layout: "vertical" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, labels.loadingMessage))))
858
+ )
859
+ );
860
+ } else if (error) {
861
+ bodyContent.push(
862
+ renderErrorState ? renderErrorState({
863
+ error,
864
+ title: typeof error === "string" ? error : labels.errorTitle,
865
+ message: labels.errorMessage
866
+ }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { key: "error", variant: "danger", title: typeof error === "string" ? error : labels.errorTitle }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, labels.errorMessage))
867
+ );
868
+ } else if (processedItems.length === 0) {
869
+ bodyContent.push(
870
+ renderEmptyState ? renderEmptyState({ title: labels.emptyTitle, message: labels.emptyMessage }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { key: "empty" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, labels.emptyMessage))))
871
+ );
872
+ } else {
873
+ bodyContent.push(
874
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: "items", direction: "column", gap: compact ? "xs" : gap }, groups.map((group) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: group.key, direction: "column", gap: compact ? "xs" : gap }, hasValue(group.label) && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, group.label), group.items.map((item, index) => {
875
+ const globalIndex = safeItems.indexOf(item);
876
+ const itemIndex = globalIndex >= 0 ? globalIndex : index;
877
+ const key = getItemKey(item, itemIndex, getKey);
878
+ const node = renderItem ? renderItem(item, itemIndex) : /* @__PURE__ */ import_react2.default.createElement(
879
+ DefaultFeedItem,
880
+ {
881
+ item,
882
+ index: itemIndex,
883
+ fields,
884
+ compact,
885
+ avatarSize,
886
+ iconSize,
887
+ collapsible: collapsible !== false && itemHasExpandableContent(item, fields),
888
+ expanded: !activeCollapsedIds.includes(key),
889
+ onToggleExpanded: () => toggleItemExpanded(key),
890
+ renderActor,
891
+ renderTimestamp,
892
+ renderMeta,
893
+ renderActions,
894
+ renderFooter
895
+ }
896
+ );
897
+ const wrappedNode = itemContainer === "card" || itemContainer === "tile" ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { compact: true }, node) : node;
898
+ return /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key }, wrappedNode, showDividers && itemContainer === "none" && index < group.items.length - 1 && /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null));
899
+ }))))
900
+ );
901
+ if (canViewMore || shouldShowExternalLoadMore) {
902
+ bodyContent.push(
903
+ /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: "load-more", direction: "row", justify: "center" }, /* @__PURE__ */ import_react2.default.createElement(
904
+ import_ui_extensions2.Button,
905
+ {
906
+ onClick: shouldShowExternalLoadMore ? onLoadMore : () => setVisibleCount((count) => count + pageSize),
907
+ disabled: loadingMore,
908
+ variant: "transparent",
909
+ size: "sm"
910
+ },
911
+ loadingMore ? labels.loadingMore : labels.loadMore
912
+ ))
913
+ );
914
+ }
915
+ }
916
+ if (resolvedShowTabs && !loading && !error) {
917
+ content.push(
918
+ /* @__PURE__ */ import_react2.default.createElement(
919
+ import_ui_extensions2.Tabs,
920
+ {
921
+ key: "tabs",
922
+ selected: activeTab ?? "all",
923
+ onSelectedChange: handleTabChange,
924
+ variant: tabVariant
925
+ },
926
+ normalizedTabs.map((tab) => /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tab, { key: tab.value, tabId: tab.value, title: String(tab.label), disabled: tab.disabled, tooltip: tab.tooltip }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: compact ? "xs" : gap }, String(tab.value) === String(activeTab ?? "all") ? bodyContent : null)))
927
+ )
928
+ );
929
+ } else {
930
+ content.push(...bodyContent);
931
+ }
932
+ const feed = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: compact ? "xs" : gap }, content);
933
+ if (container === "card" || container === "tile") return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { compact: true }, feed);
934
+ return feed;
935
+ };
936
+ // Annotate the CommonJS export names for ESM import in node:
937
+ 0 && (module.exports = {
938
+ Feed
939
+ });