@echothink-ui/identity 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 (48) hide show
  1. package/README.md +5 -0
  2. package/dist/components/AccessReviewPanel.d.ts +9 -0
  3. package/dist/components/AccountMenu.d.ts +8 -0
  4. package/dist/components/ApprovalPolicyEditor.d.ts +8 -0
  5. package/dist/components/AuditTrail.d.ts +6 -0
  6. package/dist/components/GroupPicker.d.ts +12 -0
  7. package/dist/components/IdentityCard.d.ts +14 -0
  8. package/dist/components/InviteUserPanel.d.ts +14 -0
  9. package/dist/components/OrganizationSwitcher.d.ts +9 -0
  10. package/dist/components/PermissionMatrix.d.ts +10 -0
  11. package/dist/components/PolicyRuleViewer.d.ts +6 -0
  12. package/dist/components/RoleBadge.d.ts +6 -0
  13. package/dist/components/SessionStatus.d.ts +9 -0
  14. package/dist/components/TeamList.d.ts +13 -0
  15. package/dist/components/UserPicker.d.ts +12 -0
  16. package/dist/components/types.d.ts +77 -0
  17. package/dist/index.cjs +1629 -0
  18. package/dist/index.cjs.map +1 -0
  19. package/dist/index.css +2238 -0
  20. package/dist/index.css.map +1 -0
  21. package/dist/index.d.ts +18 -0
  22. package/dist/index.js +1610 -0
  23. package/dist/index.js.map +1 -0
  24. package/package.json +43 -0
  25. package/src/components/AccessReviewPanel.tsx +169 -0
  26. package/src/components/AccountMenu.tsx +144 -0
  27. package/src/components/ApprovalPolicyEditor.tsx +131 -0
  28. package/src/components/AuditTrail.tsx +105 -0
  29. package/src/components/GroupPicker.tsx +175 -0
  30. package/src/components/IdentityCard.tsx +78 -0
  31. package/src/components/InviteUserPanel.tsx +162 -0
  32. package/src/components/OrganizationSwitcher.test.tsx +59 -0
  33. package/src/components/OrganizationSwitcher.tsx +161 -0
  34. package/src/components/PermissionMatrix.test.tsx +96 -0
  35. package/src/components/PermissionMatrix.tsx +271 -0
  36. package/src/components/PolicyRuleViewer.test.tsx +29 -0
  37. package/src/components/PolicyRuleViewer.tsx +78 -0
  38. package/src/components/RoleBadge.test.tsx +35 -0
  39. package/src/components/RoleBadge.tsx +38 -0
  40. package/src/components/SessionStatus.test.tsx +40 -0
  41. package/src/components/SessionStatus.tsx +194 -0
  42. package/src/components/TeamList.test.tsx +48 -0
  43. package/src/components/TeamList.tsx +98 -0
  44. package/src/components/UserPicker.test.tsx +52 -0
  45. package/src/components/UserPicker.tsx +174 -0
  46. package/src/components/types.ts +89 -0
  47. package/src/index.tsx +35 -0
  48. package/src/styles.css +2578 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1629 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.tsx
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AccessReviewPanel: () => AccessReviewPanel,
34
+ AccountMenu: () => AccountMenu,
35
+ ApprovalPolicyEditor: () => ApprovalPolicyEditor,
36
+ AuditTrail: () => AuditTrail,
37
+ GroupPicker: () => GroupPicker,
38
+ IdentityCard: () => IdentityCard,
39
+ IdentityComponentNames: () => IdentityComponentNames,
40
+ InviteUserPanel: () => InviteUserPanel,
41
+ OrganizationSwitcher: () => OrganizationSwitcher,
42
+ PermissionMatrix: () => PermissionMatrix,
43
+ PolicyRuleViewer: () => PolicyRuleViewer,
44
+ RoleBadge: () => RoleBadge,
45
+ SessionStatus: () => SessionStatus,
46
+ TeamList: () => TeamList,
47
+ UserPicker: () => UserPicker
48
+ });
49
+ module.exports = __toCommonJS(index_exports);
50
+
51
+ // src/components/IdentityCard.tsx
52
+ var import_core2 = require("@echothink-ui/core");
53
+
54
+ // src/components/RoleBadge.tsx
55
+ var import_core = require("@echothink-ui/core");
56
+ var import_jsx_runtime = require("react/jsx-runtime");
57
+ function RoleBadge({
58
+ role,
59
+ tier = "custom",
60
+ className,
61
+ "aria-label": ariaLabel,
62
+ "aria-labelledby": ariaLabelledBy,
63
+ title,
64
+ ...props
65
+ }) {
66
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
67
+ "span",
68
+ {
69
+ ...props,
70
+ "aria-label": ariaLabel ?? (ariaLabelledBy ? void 0 : `Role: ${role}`),
71
+ "aria-labelledby": ariaLabelledBy,
72
+ className: ["eth-identity-role-badge", className].filter(Boolean).join(" "),
73
+ "data-eth-component": "RoleBadge",
74
+ "data-tier": tier,
75
+ title: title ?? role,
76
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.Badge, { severity: severityForTier(tier), children: role })
77
+ }
78
+ );
79
+ }
80
+ function severityForTier(tier) {
81
+ if (tier === "admin") return "danger";
82
+ if (tier === "editor") return "warning";
83
+ if (tier === "viewer") return "info";
84
+ return "neutral";
85
+ }
86
+
87
+ // src/components/IdentityCard.tsx
88
+ var import_jsx_runtime2 = require("react/jsx-runtime");
89
+ function IdentityCard({ identity, actions, className, ...props }) {
90
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
91
+ import_core2.Surface,
92
+ {
93
+ ...props,
94
+ className: ["eth-identity-card", `eth-identity-card--${identity.kind}`, className].filter(Boolean).join(" "),
95
+ "data-eth-component": "IdentityCard",
96
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "eth-identity-card__body", children: [
97
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Avatar, { label: identity.label, src: identity.avatar }),
98
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "eth-identity-card__main", children: [
99
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: identity.label }),
100
+ identity.email ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: identity.email }) : null,
101
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "eth-identity-card__meta", children: [
102
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_core2.Badge, { severity: "neutral", children: formatIdentityKind(identity.kind) }),
103
+ identity.role ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RoleBadge, { role: identity.role }) : null,
104
+ identity.status ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_core2.StatusDot, { status: identity.status, label: formatIdentityStatus(identity.status) }) : null
105
+ ] })
106
+ ] }),
107
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_core2.ActionGroup, { actions })
108
+ ] })
109
+ }
110
+ );
111
+ }
112
+ function Avatar({ label, src }) {
113
+ if (src) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { className: "eth-identity-card__avatar", src, alt: "", "aria-hidden": "true" });
114
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "eth-identity-card__avatar", "aria-hidden": "true", children: initials(label) });
115
+ }
116
+ function initials(label) {
117
+ return label.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase()).join("");
118
+ }
119
+ function formatIdentityKind(kind) {
120
+ if (kind === "service-account") return "service account";
121
+ return kind;
122
+ }
123
+ function formatIdentityStatus(status) {
124
+ return status.replace(/-/g, " ");
125
+ }
126
+
127
+ // src/components/UserPicker.tsx
128
+ var React = __toESM(require("react"), 1);
129
+ var import_core3 = require("@echothink-ui/core");
130
+ var import_jsx_runtime3 = require("react/jsx-runtime");
131
+ function UserPicker({
132
+ value,
133
+ onChange,
134
+ onSearch,
135
+ suggestions = [],
136
+ multiple = true,
137
+ defaultOpen = false,
138
+ disabled = false,
139
+ loading = false,
140
+ className
141
+ }) {
142
+ const [query, setQuery] = React.useState("");
143
+ const [open, setOpen] = React.useState(defaultOpen);
144
+ const searchRef = React.useRef(null);
145
+ const selected = value.map(
146
+ (id) => suggestions.find((item) => item.id === id) ?? { id, label: id }
147
+ );
148
+ const normalizedQuery = query.trim().toLocaleLowerCase();
149
+ const visibleSuggestions = normalizedQuery ? suggestions.filter(
150
+ (suggestion) => [suggestion.label, suggestion.email, suggestion.role, suggestion.kind].filter((field) => typeof field === "string").some((field) => field.toLocaleLowerCase().includes(normalizedQuery))
151
+ ) : suggestions;
152
+ const selectedLabel = selected.length ? `${selected.length} selected` : multiple ? "No users selected" : "No user selected";
153
+ const triggerLabel = multiple ? "Add users" : selected.length ? "Change user" : "Choose user";
154
+ const toggle = (id) => {
155
+ if (disabled || loading) return;
156
+ if (!multiple) {
157
+ onChange?.([id]);
158
+ setOpen(false);
159
+ return;
160
+ }
161
+ onChange?.(value.includes(id) ? value.filter((item) => item !== id) : [...value, id]);
162
+ };
163
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
164
+ "div",
165
+ {
166
+ className: ["eth-identity-user-picker", className].filter(Boolean).join(" "),
167
+ "data-eth-component": "UserPicker",
168
+ "data-disabled": disabled || loading ? "true" : void 0,
169
+ "data-open": open ? "true" : void 0,
170
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "eth-identity-user-picker__control", children: [
171
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "eth-identity-user-picker__chips", "aria-label": "Selected users", children: selected.length ? selected.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
172
+ import_core3.Tag,
173
+ {
174
+ removable: !disabled && !loading,
175
+ onRemove: () => onChange?.(value.filter((id) => id !== item.id)),
176
+ children: item.label
177
+ },
178
+ item.id
179
+ )) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "eth-identity-user-picker__placeholder", children: multiple ? "Select users" : "Select a user" }) }),
180
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
181
+ "details",
182
+ {
183
+ open,
184
+ className: "eth-popover eth-identity-user-picker__popover",
185
+ onToggle: (event) => {
186
+ const nextOpen = event.currentTarget.open;
187
+ setOpen(nextOpen);
188
+ if (nextOpen) window.setTimeout(() => searchRef.current?.focus(), 0);
189
+ },
190
+ children: [
191
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
192
+ "summary",
193
+ {
194
+ "aria-label": `${triggerLabel}. ${selectedLabel}`,
195
+ "aria-disabled": disabled || loading ? true : void 0,
196
+ tabIndex: disabled || loading ? -1 : void 0,
197
+ onClick: (event) => {
198
+ if (disabled || loading) event.preventDefault();
199
+ },
200
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "eth-identity-user-picker__trigger", children: [
201
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: triggerLabel }),
202
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "eth-identity-user-picker__count", children: selectedLabel }),
203
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "eth-identity-user-picker__chevron", "aria-hidden": "true" })
204
+ ] })
205
+ }
206
+ ),
207
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "eth-popover__content eth-identity-user-picker__content", children: [
208
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
209
+ import_core3.TextInput,
210
+ {
211
+ ref: searchRef,
212
+ type: "search",
213
+ labelText: "Search users",
214
+ hideLabel: true,
215
+ placeholder: "Search users",
216
+ value: query,
217
+ disabled: disabled || loading,
218
+ onChange: (event) => {
219
+ setQuery(event.currentTarget.value);
220
+ onSearch?.(event.currentTarget.value);
221
+ }
222
+ }
223
+ ),
224
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
225
+ "div",
226
+ {
227
+ className: "eth-identity-user-picker__suggestions",
228
+ role: "listbox",
229
+ "aria-label": "User suggestions",
230
+ "aria-multiselectable": multiple ? true : void 0,
231
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "eth-identity-user-picker__empty", role: "status", children: "Loading users..." }) : visibleSuggestions.length ? visibleSuggestions.map((suggestion) => {
232
+ const isSelected = value.includes(suggestion.id);
233
+ const meta = suggestion.email ?? suggestion.role ?? (suggestion.kind === "service-account" ? "Service account" : void 0);
234
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
235
+ "button",
236
+ {
237
+ type: "button",
238
+ className: "eth-identity-user-picker__option",
239
+ role: "option",
240
+ "aria-selected": isSelected,
241
+ disabled: disabled || loading,
242
+ onClick: () => toggle(suggestion.id),
243
+ children: [
244
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "eth-identity-user-picker__option-main", children: [
245
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "eth-identity-user-picker__option-label", children: suggestion.label }),
246
+ meta ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "eth-identity-user-picker__option-meta", children: meta }) : null
247
+ ] }),
248
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "eth-identity-user-picker__option-check", "aria-hidden": "true" })
249
+ ]
250
+ },
251
+ suggestion.id
252
+ );
253
+ }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "eth-identity-user-picker__empty", role: "status", children: query ? "No matching users" : "No users available" })
254
+ }
255
+ )
256
+ ] })
257
+ ]
258
+ }
259
+ )
260
+ ] })
261
+ }
262
+ );
263
+ }
264
+
265
+ // src/components/GroupPicker.tsx
266
+ var React2 = __toESM(require("react"), 1);
267
+ var import_core4 = require("@echothink-ui/core");
268
+ var import_jsx_runtime4 = require("react/jsx-runtime");
269
+ function GroupPicker({
270
+ value,
271
+ onChange,
272
+ onSearch,
273
+ suggestions = [],
274
+ multiple = true,
275
+ defaultOpen = false,
276
+ disabled = false,
277
+ loading = false,
278
+ className
279
+ }) {
280
+ const [query, setQuery] = React2.useState("");
281
+ const [open, setOpen] = React2.useState(defaultOpen);
282
+ const searchRef = React2.useRef(null);
283
+ const selected = value.map(
284
+ (id) => suggestions.find((item) => item.id === id) ?? { id, label: id }
285
+ );
286
+ const normalizedQuery = query.trim().toLocaleLowerCase();
287
+ const visibleSuggestions = normalizedQuery ? suggestions.filter(
288
+ (suggestion) => [suggestion.label, suggestion.email, suggestion.role, suggestion.kind].filter((field) => typeof field === "string").some((field) => field.toLocaleLowerCase().includes(normalizedQuery))
289
+ ) : suggestions;
290
+ const selectedLabel = selected.length ? `${selected.length} selected` : multiple ? "No groups selected" : "No group selected";
291
+ const triggerLabel = multiple ? "Add groups" : selected.length ? "Change group" : "Choose group";
292
+ const toggle = (id) => {
293
+ if (disabled || loading) return;
294
+ if (!multiple) {
295
+ onChange?.([id]);
296
+ setOpen(false);
297
+ return;
298
+ }
299
+ onChange?.(value.includes(id) ? value.filter((item) => item !== id) : [...value, id]);
300
+ };
301
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
302
+ "div",
303
+ {
304
+ className: ["eth-identity-group-picker", className].filter(Boolean).join(" "),
305
+ "data-eth-component": "GroupPicker",
306
+ "data-disabled": disabled || loading ? "true" : void 0,
307
+ "data-open": open ? "true" : void 0,
308
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "eth-identity-group-picker__control", children: [
309
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "eth-identity-group-picker__chips", "aria-label": "Selected groups", children: selected.length ? selected.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
310
+ import_core4.Tag,
311
+ {
312
+ removable: !disabled && !loading,
313
+ onRemove: () => onChange?.(value.filter((id) => id !== item.id)),
314
+ children: item.label
315
+ },
316
+ item.id
317
+ )) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "eth-identity-group-picker__placeholder", children: multiple ? "Select groups" : "Select a group" }) }),
318
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
319
+ "details",
320
+ {
321
+ open,
322
+ className: "eth-popover eth-identity-group-picker__popover",
323
+ onToggle: (event) => {
324
+ const nextOpen = event.currentTarget.open;
325
+ setOpen(nextOpen);
326
+ if (nextOpen) window.setTimeout(() => searchRef.current?.focus(), 0);
327
+ },
328
+ children: [
329
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
330
+ "summary",
331
+ {
332
+ "aria-label": `${triggerLabel}. ${selectedLabel}`,
333
+ "aria-disabled": disabled || loading ? true : void 0,
334
+ onClick: (event) => {
335
+ if (disabled || loading) event.preventDefault();
336
+ },
337
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "eth-identity-group-picker__trigger", children: [
338
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: triggerLabel }),
339
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "eth-identity-group-picker__count", children: selectedLabel }),
340
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "eth-identity-group-picker__chevron", "aria-hidden": "true" })
341
+ ] })
342
+ }
343
+ ),
344
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "eth-popover__content eth-identity-group-picker__content", children: [
345
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
346
+ import_core4.TextInput,
347
+ {
348
+ ref: searchRef,
349
+ type: "search",
350
+ placeholder: "Search groups",
351
+ value: query,
352
+ disabled: disabled || loading,
353
+ onChange: (event) => {
354
+ setQuery(event.currentTarget.value);
355
+ onSearch?.(event.currentTarget.value);
356
+ }
357
+ }
358
+ ),
359
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
360
+ "div",
361
+ {
362
+ className: "eth-identity-group-picker__suggestions",
363
+ role: "listbox",
364
+ "aria-label": "Group suggestions",
365
+ "aria-multiselectable": multiple ? true : void 0,
366
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "eth-identity-group-picker__empty", role: "status", children: "Loading groups..." }) : visibleSuggestions.length ? visibleSuggestions.map((suggestion) => {
367
+ const isSelected = value.includes(suggestion.id);
368
+ const meta = suggestion.role ?? suggestion.email ?? (suggestion.kind === "group" ? "Group" : void 0);
369
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
370
+ "button",
371
+ {
372
+ type: "button",
373
+ className: "eth-identity-group-picker__option",
374
+ role: "option",
375
+ "aria-selected": isSelected,
376
+ onClick: () => toggle(suggestion.id),
377
+ children: [
378
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "eth-identity-group-picker__option-main", children: [
379
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "eth-identity-group-picker__option-label", children: suggestion.label }),
380
+ meta ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "eth-identity-group-picker__option-meta", children: meta }) : null
381
+ ] }),
382
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
383
+ "span",
384
+ {
385
+ className: "eth-identity-group-picker__option-check",
386
+ "aria-hidden": "true"
387
+ }
388
+ )
389
+ ]
390
+ },
391
+ suggestion.id
392
+ );
393
+ }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "eth-identity-group-picker__empty", role: "status", children: query ? "No matching groups" : "No groups available" })
394
+ }
395
+ )
396
+ ] })
397
+ ]
398
+ }
399
+ )
400
+ ] })
401
+ }
402
+ );
403
+ }
404
+
405
+ // src/components/TeamList.tsx
406
+ var import_core5 = require("@echothink-ui/core");
407
+ var import_jsx_runtime5 = require("react/jsx-runtime");
408
+ function TeamList({ teams, onSelect, title, className, ...props }) {
409
+ const listLabel = typeof title === "string" && title.trim() ? title : "Teams";
410
+ const totalMembers = teams.reduce((total, team) => total + team.memberCount, 0);
411
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
412
+ import_core5.Surface,
413
+ {
414
+ ...props,
415
+ title,
416
+ className: ["eth-identity-team-list", className].filter(Boolean).join(" "),
417
+ "data-eth-component": "TeamList",
418
+ children: teams.length ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
419
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("dl", { className: "eth-identity-team-list__summary", "aria-label": "Team summary", children: [
420
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
421
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: "Teams" }),
422
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { children: teams.length })
423
+ ] }),
424
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
425
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: "Members" }),
426
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { children: totalMembers })
427
+ ] })
428
+ ] }),
429
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "eth-identity-team-list__items", role: "list", "aria-label": listLabel, children: teams.map((team) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("article", { className: "eth-identity-team-list__item", role: "listitem", children: [
430
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "eth-identity-team-list__avatar", "aria-hidden": "true", children: initials2(team.label) }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "eth-identity-team-list__main", children: [
432
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "eth-identity-team-list__heading", children: [
433
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { children: team.label }),
434
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_core5.Badge, { severity: "neutral", children: formatMemberCount(team.memberCount) })
435
+ ] }),
436
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
437
+ "dl",
438
+ {
439
+ className: "eth-identity-team-list__meta",
440
+ "aria-label": `${team.label} membership summary`,
441
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
442
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: "Lead" }),
443
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { children: team.lead?.label ?? "Unassigned" })
444
+ ] })
445
+ }
446
+ )
447
+ ] }),
448
+ onSelect ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
449
+ import_core5.Button,
450
+ {
451
+ type: "button",
452
+ intent: "secondary",
453
+ density: "compact",
454
+ "aria-label": `Open ${team.label} team`,
455
+ onClick: () => onSelect(team.id),
456
+ children: "Open"
457
+ }
458
+ ) : null
459
+ ] }, team.id)) })
460
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "eth-identity-team-list__empty", role: "status", children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { children: "No teams available" }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "Add teams to see membership summaries." })
463
+ ] })
464
+ }
465
+ );
466
+ }
467
+ function formatMemberCount(count) {
468
+ return `${count} ${count === 1 ? "member" : "members"}`;
469
+ }
470
+ function initials2(label) {
471
+ const value = label.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase()).join("");
472
+ return value || "TM";
473
+ }
474
+
475
+ // src/components/PermissionMatrix.tsx
476
+ var import_core6 = require("@echothink-ui/core");
477
+ var import_jsx_runtime6 = require("react/jsx-runtime");
478
+ function PermissionMatrix({
479
+ subjects,
480
+ actions,
481
+ assignments,
482
+ onChange,
483
+ mode = "read",
484
+ title,
485
+ description,
486
+ metadata,
487
+ className,
488
+ "aria-label": ariaLabel,
489
+ ...props
490
+ }) {
491
+ const groups = groupActions(actions);
492
+ const permissionCounts = countPermissionValues(subjects, actions, assignments);
493
+ const resourceCount = countResources(actions);
494
+ const tableLabel = ariaLabel ?? matrixLabelForTitle(title);
495
+ const matrixMetadata = [
496
+ { label: "Subjects", value: countLabel(subjects.length, "subject") },
497
+ { label: "Actions", value: countLabel(actions.length, "action") },
498
+ { label: "Resources", value: countLabel(resourceCount, "resource") },
499
+ ...metadata ?? []
500
+ ];
501
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
502
+ import_core6.Surface,
503
+ {
504
+ ...props,
505
+ title,
506
+ description,
507
+ metadata: matrixMetadata,
508
+ className: ["eth-identity-permission-matrix", className].filter(Boolean).join(" "),
509
+ "data-eth-component": "PermissionMatrix",
510
+ children: subjects.length > 0 && actions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
511
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "eth-identity-permission-matrix__summary", "aria-label": "Permission summary", children: [
512
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SummaryItem, { value: permissionCounts.allow, label: "Allowed", tone: "allow" }),
513
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SummaryItem, { value: permissionCounts.deny, label: "Denied", tone: "deny" }),
514
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SummaryItem, { value: permissionCounts.inherit, label: "Inherited", tone: "inherit" }),
515
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "eth-identity-permission-matrix__mode", children: mode === "edit" ? "Editable" : "Read only" })
516
+ ] }),
517
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "eth-identity-permission-matrix__table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("table", { "aria-label": tableLabel, className: "eth-identity-permission-matrix__table", children: [
518
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("caption", { className: "eth-identity-permission-matrix__caption", children: "Permission assignments across subjects, resources, scopes, and actions." }),
519
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("thead", { children: [
520
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("tr", { children: [
521
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
522
+ "th",
523
+ {
524
+ scope: "col",
525
+ rowSpan: 2,
526
+ className: [
527
+ "eth-identity-permission-matrix__subject-col",
528
+ "eth-identity-permission-matrix__subject-heading"
529
+ ].join(" "),
530
+ children: "Subject"
531
+ }
532
+ ),
533
+ groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { scope: "colgroup", colSpan: group.actions.length, children: group.label }, group.label))
534
+ ] }),
535
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { children: groups.flatMap(
536
+ (group) => group.actions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("th", { scope: "col", children: [
537
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "eth-identity-permission-matrix__action-label", children: action.label }),
538
+ action.resource || action.scope ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "eth-identity-permission-matrix__action-meta", children: [
539
+ action.resource ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: action.resource }) : null,
540
+ action.scope ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: action.scope }) : null
541
+ ] }) : null
542
+ ] }, action.id))
543
+ ) })
544
+ ] }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tbody", { children: subjects.map((subject) => {
546
+ const subjectMeta = formatSubjectMeta(subject);
547
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("tr", { children: [
548
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("th", { scope: "row", className: "eth-identity-permission-matrix__subject", children: [
549
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "eth-identity-permission-matrix__subject-label", children: subject.label }),
550
+ subjectMeta ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("small", { children: subjectMeta }) : null
551
+ ] }),
552
+ actions.map((action) => {
553
+ const value = assignments[subject.id]?.[action.id] ?? "inherit";
554
+ const valueLabel = labelForPermission(value);
555
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
556
+ "td",
557
+ {
558
+ className: "eth-identity-permission-matrix__cell",
559
+ "data-permission": value,
560
+ "data-permission-value": value,
561
+ title: `${subject.label} / ${action.label}: ${valueLabel}`,
562
+ children: mode === "edit" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
563
+ import_core6.Select,
564
+ {
565
+ "aria-label": `${subject.label} ${action.label} permission`,
566
+ className: [
567
+ "eth-identity-permission-matrix__select",
568
+ `eth-identity-permission-matrix__select--${value}`
569
+ ].join(" "),
570
+ density: "compact",
571
+ disabled: !onChange,
572
+ value,
573
+ options: permissionOptions,
574
+ onChange: (event) => onChange?.(
575
+ subject.id,
576
+ action.id,
577
+ event.currentTarget.value
578
+ )
579
+ }
580
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
581
+ import_core6.Badge,
582
+ {
583
+ severity: severityForPermission(value),
584
+ "aria-label": `${subject.label} ${action.label}: ${valueLabel}`,
585
+ className: [
586
+ "eth-identity-permission-matrix__badge",
587
+ `eth-identity-permission-matrix__badge--${value}`
588
+ ].join(" "),
589
+ children: [
590
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
591
+ "span",
592
+ {
593
+ className: "eth-identity-permission-matrix__status-mark",
594
+ "aria-hidden": "true"
595
+ }
596
+ ),
597
+ valueLabel
598
+ ]
599
+ }
600
+ )
601
+ },
602
+ action.id
603
+ );
604
+ })
605
+ ] }, subject.id);
606
+ }) })
607
+ ] }) })
608
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
609
+ import_core6.EmptyState,
610
+ {
611
+ title: "No permissions configured",
612
+ description: "Add at least one subject and one action to render the permission matrix."
613
+ }
614
+ )
615
+ }
616
+ );
617
+ }
618
+ function SummaryItem({
619
+ value,
620
+ label,
621
+ tone
622
+ }) {
623
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
624
+ "span",
625
+ {
626
+ className: `eth-identity-permission-matrix__summary-item eth-identity-permission-matrix__summary-item--${tone}`,
627
+ children: [
628
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: value }),
629
+ label
630
+ ]
631
+ }
632
+ );
633
+ }
634
+ var permissionOptions = [
635
+ { value: "inherit", label: "Inherit" },
636
+ { value: "allow", label: "Allow" },
637
+ { value: "deny", label: "Deny" }
638
+ ];
639
+ function groupActions(actions) {
640
+ const labels = Array.from(new Set(actions.map((action) => action.group ?? "Permissions")));
641
+ return labels.map((label) => ({
642
+ label,
643
+ actions: actions.filter((action) => (action.group ?? "Permissions") === label)
644
+ }));
645
+ }
646
+ function severityForPermission(value) {
647
+ if (value === "allow") return "success";
648
+ if (value === "deny") return "danger";
649
+ return "neutral";
650
+ }
651
+ function labelForPermission(value) {
652
+ return permissionOptions.find((option) => option.value === value)?.label ?? value;
653
+ }
654
+ function countPermissionValues(subjects, actions, assignments) {
655
+ return subjects.reduce(
656
+ (counts, subject) => {
657
+ actions.forEach((action) => {
658
+ const value = assignments[subject.id]?.[action.id] ?? "inherit";
659
+ counts[value] += 1;
660
+ });
661
+ return counts;
662
+ },
663
+ { allow: 0, deny: 0, inherit: 0 }
664
+ );
665
+ }
666
+ function countResources(actions) {
667
+ return new Set(actions.map((action) => action.resource ?? action.group ?? action.label)).size;
668
+ }
669
+ function countLabel(value, noun) {
670
+ return `${value} ${value === 1 ? noun : `${noun}s`}`;
671
+ }
672
+ function formatSubjectKind(kind) {
673
+ return kind.replace(/-/g, " ");
674
+ }
675
+ function formatSubjectMeta(subject) {
676
+ return [formatSubjectKind(subject.kind), subject.role, subject.description].filter(Boolean).join(" / ");
677
+ }
678
+ function nodeToText(node, fallback) {
679
+ if (typeof node === "string") return node;
680
+ if (typeof node === "number") return String(node);
681
+ return fallback;
682
+ }
683
+ function matrixLabelForTitle(title) {
684
+ const label = nodeToText(title, "Permission");
685
+ return label.toLowerCase().includes("matrix") ? label : `${label} matrix`;
686
+ }
687
+
688
+ // src/components/AccessReviewPanel.tsx
689
+ var import_core7 = require("@echothink-ui/core");
690
+ var import_jsx_runtime7 = require("react/jsx-runtime");
691
+ function AccessReviewPanel({
692
+ subjects,
693
+ findings,
694
+ onApprove,
695
+ onRevoke,
696
+ title,
697
+ className,
698
+ ...props
699
+ }) {
700
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
701
+ import_core7.Surface,
702
+ {
703
+ ...props,
704
+ title,
705
+ className: ["eth-identity-access-review", className].filter(Boolean).join(" "),
706
+ "data-eth-component": "AccessReviewPanel",
707
+ children: [
708
+ subjects.map((subject) => {
709
+ const subjectFindings = findings.filter((finding) => finding.subjectId === subject.id);
710
+ const subjectMeta = [subject.kind, subject.role].filter(Boolean).join(" / ");
711
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { className: "eth-identity-access-review__subject", children: [
712
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("header", { className: "eth-identity-access-review__subject-header", children: [
713
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "eth-identity-access-review__identity", children: [
714
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "eth-identity-access-review__avatar", "aria-hidden": "true", children: initialsForSubject(subject.label) }),
715
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "eth-identity-access-review__identity-main", children: [
716
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { children: subject.label }),
717
+ subject.email ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { children: subject.email }) : null,
718
+ subjectMeta ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("small", { children: subjectMeta }) : null
719
+ ] })
720
+ ] }),
721
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
722
+ "div",
723
+ {
724
+ className: "eth-identity-access-review__findings",
725
+ "aria-label": `${subject.label} access findings`,
726
+ children: subjectFindings.length ? subjectFindings.map((finding) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_core7.Badge, { severity: severityForFinding(finding.severity), children: finding.reason }, finding.id)) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_core7.Badge, { severity: "neutral", children: "No findings" })
727
+ }
728
+ )
729
+ ] }),
730
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "eth-identity-access-review__table-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { className: "eth-identity-access-review__table", children: [
731
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
732
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { scope: "col", children: "Resource" }),
733
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { scope: "col", children: "Permissions" }),
734
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { scope: "col", children: "Last used" }),
735
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { scope: "col", children: "Decision" })
736
+ ] }) }),
737
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: subject.accessTo.length ? subject.accessTo.map((resource) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
738
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { scope: "row", children: resource.resourceLabel }),
739
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { children: resource.permissions.length ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "eth-identity-access-review__permissions", children: resource.permissions.map((permission) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_core7.Badge, { severity: "neutral", children: permission }, permission)) }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "eth-identity-access-review__muted", children: "No permissions" }) }),
740
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
741
+ "span",
742
+ {
743
+ className: resource.lastUsedAt ? void 0 : "eth-identity-access-review__muted",
744
+ children: resource.lastUsedAt ?? "Never"
745
+ }
746
+ ) }),
747
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "eth-identity-access-review__decision-actions", children: [
748
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
749
+ import_core7.Button,
750
+ {
751
+ type: "button",
752
+ intent: "success",
753
+ density: "compact",
754
+ disabled: !onApprove,
755
+ "aria-label": `Approve ${subject.label} access to ${resource.resourceLabel}`,
756
+ onClick: () => onApprove?.(subject.id, resource.resourceId),
757
+ children: "Approve"
758
+ }
759
+ ),
760
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
761
+ import_core7.Button,
762
+ {
763
+ type: "button",
764
+ intent: "danger",
765
+ density: "compact",
766
+ disabled: !onRevoke,
767
+ "aria-label": `Revoke ${subject.label} access to ${resource.resourceLabel}`,
768
+ onClick: () => onRevoke?.(subject.id, resource.resourceId),
769
+ children: "Revoke"
770
+ }
771
+ )
772
+ ] }) })
773
+ ] }, resource.resourceId)) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { colSpan: 4, className: "eth-identity-access-review__empty-cell", children: "No resource access assigned." }) }) })
774
+ ] }) })
775
+ ] }, subject.id);
776
+ }),
777
+ !subjects.length ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
778
+ import_core7.EmptyState,
779
+ {
780
+ title: "No access to review",
781
+ description: "Subjects with assigned resources will appear here when a review is ready."
782
+ }
783
+ ) : null
784
+ ]
785
+ }
786
+ );
787
+ }
788
+ function initialsForSubject(label) {
789
+ return label.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("") || "ID";
790
+ }
791
+ function severityForFinding(severity) {
792
+ if (severity === "critical" || severity === "error") return "danger";
793
+ if (severity === "warning") return "warning";
794
+ return "info";
795
+ }
796
+
797
+ // src/components/InviteUserPanel.tsx
798
+ var React3 = __toESM(require("react"), 1);
799
+ var import_core8 = require("@echothink-ui/core");
800
+ var import_jsx_runtime8 = require("react/jsx-runtime");
801
+ function isValidInviteEmail(email) {
802
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
803
+ }
804
+ function InviteUserPanel({
805
+ roles,
806
+ onInvite,
807
+ title = "Invite user",
808
+ description = "Send a workspace invitation and assign the recipient's initial access role.",
809
+ defaultEmail = "",
810
+ defaultRole,
811
+ inviteLabel = "Send invite",
812
+ className,
813
+ ...props
814
+ }) {
815
+ const options = React3.useMemo(
816
+ () => roles.map(
817
+ (role2) => typeof role2 === "string" ? { value: role2, label: role2 } : { value: role2.id, label: role2.label }
818
+ ),
819
+ [roles]
820
+ );
821
+ const preferredRole = options.some((option) => option.value === defaultRole) ? defaultRole : options[0]?.value;
822
+ const [email, setEmail] = React3.useState(defaultEmail);
823
+ const [emailTouched, setEmailTouched] = React3.useState(false);
824
+ const [role, setRole] = React3.useState(preferredRole ?? "");
825
+ const [roleTouched, setRoleTouched] = React3.useState(false);
826
+ const baseId = React3.useId();
827
+ const emailId = `${baseId}-email`;
828
+ const roleId = `${baseId}-role`;
829
+ const trimmedEmail = email.trim();
830
+ const emailIsValid = isValidInviteEmail(trimmedEmail);
831
+ const selectedRole = options.find((option) => option.value === role);
832
+ const emailError = emailTouched ? trimmedEmail ? emailIsValid ? void 0 : "Enter a valid email address." : "Email is required." : void 0;
833
+ const roleError = roleTouched && !role ? "Select a role." : void 0;
834
+ const canInvite = Boolean(onInvite && emailIsValid && role);
835
+ const inviteStatus = onInvite ? canInvite ? "Ready to send" : "Needs required fields" : "Unavailable";
836
+ React3.useEffect(() => {
837
+ if (!options.some((option) => option.value === role)) {
838
+ setRole(preferredRole ?? "");
839
+ }
840
+ }, [options, preferredRole, role]);
841
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
842
+ import_core8.Surface,
843
+ {
844
+ ...props,
845
+ title,
846
+ description,
847
+ className: ["eth-identity-invite-user", className].filter(Boolean).join(" "),
848
+ "data-eth-component": "InviteUserPanel",
849
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
850
+ "form",
851
+ {
852
+ noValidate: true,
853
+ onSubmit: (event) => {
854
+ event.preventDefault();
855
+ setEmailTouched(true);
856
+ setRoleTouched(true);
857
+ if (emailIsValid && role) onInvite?.(trimmedEmail, role);
858
+ },
859
+ children: [
860
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "eth-identity-invite-user__layout", children: [
861
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "eth-identity-invite-user__fields", children: [
862
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
863
+ import_core8.FormField,
864
+ {
865
+ id: emailId,
866
+ label: "Email",
867
+ helperText: "Use the recipient's managed workspace email address.",
868
+ error: emailError,
869
+ required: true,
870
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
871
+ import_core8.TextInput,
872
+ {
873
+ id: emailId,
874
+ type: "email",
875
+ value: email,
876
+ placeholder: "name@company.com",
877
+ autoComplete: "email",
878
+ onBlur: () => setEmailTouched(true),
879
+ onChange: (event) => setEmail(event.currentTarget.value)
880
+ }
881
+ )
882
+ }
883
+ ),
884
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
885
+ import_core8.FormField,
886
+ {
887
+ id: roleId,
888
+ label: "Role",
889
+ helperText: "Choose the initial permission set for this invite.",
890
+ error: roleError,
891
+ required: true,
892
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
893
+ import_core8.Select,
894
+ {
895
+ id: roleId,
896
+ value: role,
897
+ options,
898
+ disabled: !options.length,
899
+ onBlur: () => setRoleTouched(true),
900
+ onChange: (event) => {
901
+ setRoleTouched(true);
902
+ setRole(event.currentTarget.value);
903
+ }
904
+ }
905
+ )
906
+ }
907
+ )
908
+ ] }),
909
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "eth-identity-invite-user__summary", "aria-live": "polite", children: [
910
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "eth-identity-invite-user__summary-label", children: "Invitation summary" }),
911
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("dl", { children: [
912
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
913
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dt", { children: "Recipient" }),
914
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dd", { children: trimmedEmail || "Waiting for email" })
915
+ ] }),
916
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
917
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dt", { children: "Initial role" }),
918
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dd", { children: selectedRole?.label ?? "No role selected" })
919
+ ] }),
920
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
921
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dt", { children: "Status" }),
922
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("dd", { children: inviteStatus })
923
+ ] })
924
+ ] })
925
+ ] })
926
+ ] }),
927
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "eth-identity-invite-user__actions", children: [
928
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_core8.Button, { type: "submit", disabled: !canInvite, children: inviteLabel }),
929
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: "Access can be reviewed after the recipient accepts the invitation." })
930
+ ] })
931
+ ]
932
+ }
933
+ )
934
+ }
935
+ );
936
+ }
937
+
938
+ // src/components/OrganizationSwitcher.tsx
939
+ var React4 = __toESM(require("react"), 1);
940
+ var import_core9 = require("@echothink-ui/core");
941
+ var import_jsx_runtime9 = require("react/jsx-runtime");
942
+ function OrganizationSwitcher({
943
+ organizations,
944
+ activeId,
945
+ defaultOpen = false,
946
+ onSwitch,
947
+ className
948
+ }) {
949
+ const [open, setOpen] = React4.useState(defaultOpen);
950
+ const [selectedId, setSelectedId] = React4.useState(activeId ?? organizations[0]?.id);
951
+ const detailsRef = React4.useRef(null);
952
+ const listRef = React4.useRef(null);
953
+ const active = organizations.find((organization) => organization.id === (activeId ?? selectedId)) ?? organizations[0];
954
+ const triggerLabel = active ? `Switch organization, current organization ${active.label}` : "Switch organization";
955
+ React4.useEffect(() => {
956
+ if (activeId !== void 0) setSelectedId(activeId);
957
+ }, [activeId]);
958
+ React4.useEffect(() => {
959
+ if (!organizations.some((organization) => organization.id === selectedId)) {
960
+ setSelectedId(organizations[0]?.id);
961
+ }
962
+ }, [organizations, selectedId]);
963
+ const closeMenu = React4.useCallback(() => {
964
+ setOpen(false);
965
+ detailsRef.current?.querySelector("summary")?.focus();
966
+ }, []);
967
+ const selectOrganization = (organization) => {
968
+ if (activeId === void 0) setSelectedId(organization.id);
969
+ onSwitch?.(organization.id);
970
+ closeMenu();
971
+ };
972
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
973
+ "div",
974
+ {
975
+ className: ["eth-identity-organization-switcher", className].filter(Boolean).join(" "),
976
+ "data-eth-component": "OrganizationSwitcher",
977
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
978
+ "details",
979
+ {
980
+ ref: detailsRef,
981
+ className: "eth-popover",
982
+ open,
983
+ onToggle: (event) => {
984
+ const nextOpen = event.currentTarget.open;
985
+ setOpen(nextOpen);
986
+ if (nextOpen) window.setTimeout(() => listRef.current?.focus(), 0);
987
+ },
988
+ children: [
989
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("summary", { "aria-haspopup": "menu", "aria-expanded": open, "aria-label": triggerLabel, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "eth-identity-organization-switcher__trigger", children: [
990
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "eth-identity-organization-switcher__avatar", "aria-hidden": "true", children: active ? initials3(active.label) : "ORG" }),
991
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "eth-identity-organization-switcher__copy", children: [
992
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "eth-identity-organization-switcher__label", children: active?.label ?? "Select organization" }),
993
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "eth-identity-organization-switcher__meta", children: active?.description ?? `${organizations.length} organizations` })
994
+ ] }),
995
+ active?.status ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_core9.StatusDot, { status: active.status, label: formatStatus(active.status) }) : null,
996
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "eth-identity-organization-switcher__chevron", "aria-hidden": "true" })
997
+ ] }) }),
998
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
999
+ "div",
1000
+ {
1001
+ ref: listRef,
1002
+ tabIndex: -1,
1003
+ className: "eth-identity-organization-switcher__list",
1004
+ role: "menu",
1005
+ "aria-label": "Organizations",
1006
+ onKeyDown: (event) => {
1007
+ if (event.key === "Escape") closeMenu();
1008
+ },
1009
+ children: organizations.length ? organizations.map((organization) => {
1010
+ const selected = organization.id === active?.id;
1011
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1012
+ "button",
1013
+ {
1014
+ type: "button",
1015
+ className: "eth-identity-organization-switcher__option",
1016
+ role: "menuitemradio",
1017
+ "aria-checked": selected,
1018
+ "aria-current": selected ? "true" : void 0,
1019
+ onClick: () => selectOrganization(organization),
1020
+ children: [
1021
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "eth-identity-organization-switcher__option-main", children: [
1022
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "eth-identity-organization-switcher__option-label", children: organization.label }),
1023
+ organization.description ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "eth-identity-organization-switcher__option-meta", children: organization.description }) : null
1024
+ ] }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "eth-identity-organization-switcher__option-state", children: [
1026
+ organization.status ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1027
+ import_core9.StatusDot,
1028
+ {
1029
+ status: organization.status,
1030
+ label: formatStatus(organization.status)
1031
+ }
1032
+ ) : null,
1033
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1034
+ "span",
1035
+ {
1036
+ className: "eth-identity-organization-switcher__option-check",
1037
+ "aria-hidden": "true"
1038
+ }
1039
+ )
1040
+ ] })
1041
+ ]
1042
+ },
1043
+ organization.id
1044
+ );
1045
+ }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "eth-identity-organization-switcher__empty", role: "none", children: "No organizations available" })
1046
+ }
1047
+ )
1048
+ ]
1049
+ }
1050
+ )
1051
+ }
1052
+ );
1053
+ }
1054
+ function initials3(label) {
1055
+ const value = label.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase()).join("");
1056
+ return value || "ORG";
1057
+ }
1058
+ function formatStatus(status) {
1059
+ return status.split("-").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
1060
+ }
1061
+
1062
+ // src/components/AccountMenu.tsx
1063
+ var React5 = __toESM(require("react"), 1);
1064
+ var import_core10 = require("@echothink-ui/core");
1065
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1066
+ function AccountMenu({ user, actions, defaultOpen = false, className }) {
1067
+ const [open, setOpen] = React5.useState(defaultOpen);
1068
+ const detailsRef = React5.useRef(null);
1069
+ const contentRef = React5.useRef(null);
1070
+ const userDescriptor = user.email ?? user.role ?? user.kind;
1071
+ const closeMenu = React5.useCallback(() => {
1072
+ setOpen(false);
1073
+ detailsRef.current?.querySelector("summary")?.focus();
1074
+ }, []);
1075
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1076
+ "div",
1077
+ {
1078
+ className: ["eth-identity-account-menu", className].filter(Boolean).join(" "),
1079
+ "data-eth-component": "AccountMenu",
1080
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1081
+ "details",
1082
+ {
1083
+ ref: detailsRef,
1084
+ className: "eth-popover",
1085
+ open,
1086
+ onToggle: (event) => {
1087
+ const nextOpen = event.currentTarget.open;
1088
+ setOpen(nextOpen);
1089
+ if (nextOpen) window.setTimeout(() => contentRef.current?.focus(), 0);
1090
+ },
1091
+ children: [
1092
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1093
+ "summary",
1094
+ {
1095
+ "aria-haspopup": "menu",
1096
+ "aria-expanded": open,
1097
+ "aria-label": `Account menu for ${user.label}`,
1098
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "eth-identity-account-menu__trigger", children: [
1099
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Avatar2, { label: user.label, src: user.avatar }),
1100
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "eth-identity-account-menu__trigger-copy", children: [
1101
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "eth-identity-account-menu__trigger-label", children: user.label }),
1102
+ userDescriptor ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "eth-identity-account-menu__trigger-meta", children: userDescriptor }) : null
1103
+ ] }),
1104
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "eth-identity-account-menu__chevron", "aria-hidden": "true" })
1105
+ ] })
1106
+ }
1107
+ ),
1108
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1109
+ "div",
1110
+ {
1111
+ ref: contentRef,
1112
+ tabIndex: -1,
1113
+ className: "eth-identity-account-menu__content",
1114
+ onKeyDown: (event) => {
1115
+ if (event.key === "Escape") closeMenu();
1116
+ },
1117
+ children: [
1118
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "eth-identity-account-menu__profile", children: [
1119
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Avatar2, { label: user.label, src: user.avatar }),
1120
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "eth-identity-account-menu__profile-copy", children: [
1121
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: user.label }),
1122
+ user.email ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: user.email }) : null,
1123
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "eth-identity-account-menu__meta", children: [
1124
+ user.role ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(RoleBadge, { role: user.role }) : null,
1125
+ user.status ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_core10.StatusDot, { status: user.status, label: user.status }) : null
1126
+ ] })
1127
+ ] })
1128
+ ] }),
1129
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1130
+ "div",
1131
+ {
1132
+ className: "eth-identity-account-menu__actions",
1133
+ role: "menu",
1134
+ "aria-label": `Actions for ${user.label}`,
1135
+ children: actions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AccountMenuAction, { action, onClose: closeMenu }, action.id))
1136
+ }
1137
+ )
1138
+ ]
1139
+ }
1140
+ )
1141
+ ]
1142
+ }
1143
+ )
1144
+ }
1145
+ );
1146
+ }
1147
+ function AccountMenuAction({ action, onClose }) {
1148
+ const className = [
1149
+ "eth-identity-account-menu__action",
1150
+ action.intent === "danger" ? "eth-identity-account-menu__action--danger" : null
1151
+ ].filter(Boolean).join(" ");
1152
+ if (action.href && !action.disabled) {
1153
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1154
+ "a",
1155
+ {
1156
+ className,
1157
+ href: action.href,
1158
+ role: "menuitem",
1159
+ onClick: () => {
1160
+ action.onSelect?.();
1161
+ onClose();
1162
+ },
1163
+ children: action.label
1164
+ }
1165
+ );
1166
+ }
1167
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1168
+ "button",
1169
+ {
1170
+ className,
1171
+ type: "button",
1172
+ disabled: action.disabled,
1173
+ role: "menuitem",
1174
+ onClick: () => {
1175
+ action.onSelect?.();
1176
+ onClose();
1177
+ },
1178
+ children: action.label
1179
+ }
1180
+ );
1181
+ }
1182
+ function Avatar2({ label, src }) {
1183
+ if (src) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("img", { className: "eth-identity-account-menu__avatar", src, alt: "" });
1184
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "eth-identity-account-menu__avatar", "aria-hidden": "true", children: initials4(label) });
1185
+ }
1186
+ function initials4(label) {
1187
+ return label.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase()).join("");
1188
+ }
1189
+
1190
+ // src/components/SessionStatus.tsx
1191
+ var import_core11 = require("@echothink-ui/core");
1192
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1193
+ var EXPIRING_SOON_MS = 24 * 60 * 60 * 1e3;
1194
+ function SessionStatus({
1195
+ session,
1196
+ title,
1197
+ description,
1198
+ className,
1199
+ severity,
1200
+ status,
1201
+ ...props
1202
+ }) {
1203
+ const expires = new Date(session.expiresAt);
1204
+ const hasValidExpiration = Number.isFinite(expires.valueOf());
1205
+ const expiresAt = hasValidExpiration ? expires.getTime() : null;
1206
+ const now = Date.now();
1207
+ const expired = expiresAt !== null && expiresAt < now;
1208
+ const expiringSoon = expiresAt !== null && !expired && expiresAt - now <= EXPIRING_SOON_MS;
1209
+ const needsReview = !session.mfaEnabled || expiringSoon || !hasValidExpiration;
1210
+ const tone = expired ? "danger" : needsReview ? "warning" : "success";
1211
+ const resolvedStatus = status ?? (expired ? "stale" : needsReview ? "warning" : "active");
1212
+ const sessionState = expired ? "Expired" : expiringSoon ? "Expiring soon" : "Active";
1213
+ const summaryTitle = summaryTitleForSession({ expired, needsReview });
1214
+ const summaryDetail = summaryForSession({
1215
+ expired,
1216
+ expiringSoon,
1217
+ hasValidExpiration,
1218
+ mfaEnabled: session.mfaEnabled
1219
+ });
1220
+ const summaryClassName = [
1221
+ "eth-identity-session-status__summary",
1222
+ `eth-identity-session-status__summary--${tone}`
1223
+ ].join(" ");
1224
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1225
+ import_core11.Surface,
1226
+ {
1227
+ ...props,
1228
+ title: title === void 0 ? "Session security" : title,
1229
+ description: description === void 0 ? "Authentication freshness and recent access signal." : description,
1230
+ severity,
1231
+ status: resolvedStatus,
1232
+ className: ["eth-identity-session-status", className].filter(Boolean).join(" "),
1233
+ "data-eth-component": "SessionStatus",
1234
+ children: [
1235
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: summaryClassName, children: [
1236
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "eth-identity-session-status__summary-mark", "aria-hidden": "true" }),
1237
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "eth-identity-session-status__summary-copy", children: [
1238
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { children: summaryTitle }),
1239
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: summaryDetail })
1240
+ ] })
1241
+ ] }),
1242
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("dl", { "aria-label": "Session details", children: [
1243
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
1244
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dt", { children: "Session" }),
1245
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("dd", { className: "eth-identity-session-status__value", children: [
1246
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "eth-identity-session-status__value-main", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_core11.Badge, { severity: tone, children: sessionState }) }),
1247
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "eth-identity-session-status__detail", children: expired ? "Credentials are no longer valid." : "Authenticated session lifecycle." })
1248
+ ] })
1249
+ ] }),
1250
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
1251
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dt", { children: "Expires" }),
1252
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("dd", { className: "eth-identity-session-status__value", children: [
1253
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "eth-identity-session-status__value-main", children: hasValidExpiration ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1254
+ "time",
1255
+ {
1256
+ "aria-label": "Session expiration",
1257
+ dateTime: session.expiresAt,
1258
+ title: session.expiresAt,
1259
+ children: formatSessionDate(expires)
1260
+ }
1261
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Unknown" }) }),
1262
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "eth-identity-session-status__detail", children: expirationDetail({ expired, expiringSoon, hasValidExpiration }) })
1263
+ ] })
1264
+ ] }),
1265
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
1266
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dt", { children: "MFA" }),
1267
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("dd", { className: "eth-identity-session-status__value", children: [
1268
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "eth-identity-session-status__value-main", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_core11.Badge, { severity: session.mfaEnabled ? "success" : "warning", children: session.mfaEnabled ? "Enabled" : "Disabled" }) }),
1269
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "eth-identity-session-status__detail", children: session.mfaEnabled ? "Second factor challenge is enforced." : "No second factor is enforced for this session." })
1270
+ ] })
1271
+ ] }),
1272
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
1273
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("dt", { children: "Last IP" }),
1274
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("dd", { className: "eth-identity-session-status__value", children: [
1275
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "eth-identity-session-status__value-main", children: session.lastIp ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("code", { className: "eth-identity-session-status__code", children: session.lastIp }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "eth-identity-session-status__muted", children: "Not recorded" }) }),
1276
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "eth-identity-session-status__detail", children: "Latest network signal captured for the session." })
1277
+ ] })
1278
+ ] })
1279
+ ] })
1280
+ ]
1281
+ }
1282
+ );
1283
+ }
1284
+ function formatSessionDate(value) {
1285
+ return new Intl.DateTimeFormat(void 0, {
1286
+ day: "2-digit",
1287
+ hour: "2-digit",
1288
+ minute: "2-digit",
1289
+ month: "short",
1290
+ timeZoneName: "short",
1291
+ year: "numeric"
1292
+ }).format(value);
1293
+ }
1294
+ function summaryTitleForSession({
1295
+ expired,
1296
+ needsReview
1297
+ }) {
1298
+ if (expired) return "Session expired";
1299
+ if (needsReview) return "Review required";
1300
+ return "Session active";
1301
+ }
1302
+ function summaryForSession({
1303
+ expired,
1304
+ expiringSoon,
1305
+ hasValidExpiration,
1306
+ mfaEnabled
1307
+ }) {
1308
+ if (!hasValidExpiration) return "Expiration could not be verified from the provided timestamp.";
1309
+ if (expired) return "Access should be refreshed or revoked before more work continues.";
1310
+ if (!mfaEnabled) return "MFA is disabled, so this session needs security review.";
1311
+ if (expiringSoon) return "The session is valid but expires within the next 24 hours.";
1312
+ return "MFA is enabled and the session remains within its validity window.";
1313
+ }
1314
+ function expirationDetail({
1315
+ expired,
1316
+ expiringSoon,
1317
+ hasValidExpiration
1318
+ }) {
1319
+ if (!hasValidExpiration) return "Timestamp could not be parsed.";
1320
+ if (expired) return "Past the identity provider expiry.";
1321
+ if (expiringSoon) return "Expires within 24 hours.";
1322
+ return "Valid until scheduled expiry.";
1323
+ }
1324
+
1325
+ // src/components/AuditTrail.tsx
1326
+ var import_core12 = require("@echothink-ui/core");
1327
+ var import_data = require("@echothink-ui/data");
1328
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1329
+ function AuditTrail({
1330
+ entries,
1331
+ title = "Audit trail",
1332
+ subtitle,
1333
+ description = "Identity and access audit history ordered by latest event.",
1334
+ density = "compact",
1335
+ metadata,
1336
+ className,
1337
+ role,
1338
+ "aria-label": ariaLabel,
1339
+ ...props
1340
+ }) {
1341
+ const rows = entries.map((entry) => ({ ...entry }));
1342
+ const successCount = entries.filter((entry) => entry.outcome === "success").length;
1343
+ const failureCount = entries.length - successCount;
1344
+ const resolvedSubtitle = subtitle ?? `${entries.length} ${entries.length === 1 ? "event" : "events"}`;
1345
+ const resolvedMetadata = metadata ?? [
1346
+ { label: "Events", value: entries.length },
1347
+ { label: "Successful", value: successCount },
1348
+ { label: "Failures", value: failureCount }
1349
+ ];
1350
+ const columns = [
1351
+ {
1352
+ key: "timestamp",
1353
+ header: "Time",
1354
+ width: "8.5rem",
1355
+ render: (row) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("time", { className: "eth-identity-audit-trail__time", children: row.timestamp })
1356
+ },
1357
+ {
1358
+ key: "actor",
1359
+ header: "Actor",
1360
+ width: "9rem",
1361
+ render: (row) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "eth-identity-audit-trail__actor", children: row.actor })
1362
+ },
1363
+ {
1364
+ key: "action",
1365
+ header: "Action",
1366
+ width: "11rem",
1367
+ render: (row) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "eth-identity-audit-trail__action", children: row.action })
1368
+ },
1369
+ {
1370
+ key: "resource",
1371
+ header: "Resource",
1372
+ render: (row) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "eth-identity-audit-trail__resource", children: row.resource })
1373
+ },
1374
+ {
1375
+ key: "outcome",
1376
+ header: "Outcome",
1377
+ width: "8rem",
1378
+ render: (row) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "eth-identity-audit-trail__outcome", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_core12.Badge, { severity: outcomeSeverity(row.outcome), children: outcomeLabel(row.outcome) }) })
1379
+ }
1380
+ ];
1381
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1382
+ import_core12.Surface,
1383
+ {
1384
+ ...props,
1385
+ title,
1386
+ subtitle: resolvedSubtitle,
1387
+ description,
1388
+ density,
1389
+ metadata: resolvedMetadata,
1390
+ className: ["eth-identity-audit-trail", className].filter(Boolean).join(" "),
1391
+ "data-eth-component": "AuditTrail",
1392
+ role: role ?? "region",
1393
+ "aria-label": ariaLabel ?? (typeof title === "string" ? title : "Audit trail"),
1394
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1395
+ import_data.DataTable,
1396
+ {
1397
+ rows,
1398
+ columns,
1399
+ density,
1400
+ emptyState: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1401
+ import_core12.EmptyState,
1402
+ {
1403
+ title: "No audit events",
1404
+ description: "Identity and access events will appear here when they are recorded."
1405
+ }
1406
+ )
1407
+ }
1408
+ )
1409
+ }
1410
+ );
1411
+ }
1412
+ function outcomeSeverity(outcome) {
1413
+ return outcome === "success" ? "success" : "danger";
1414
+ }
1415
+ function outcomeLabel(outcome) {
1416
+ return outcome === "success" ? "Success" : "Failure";
1417
+ }
1418
+
1419
+ // src/components/PolicyRuleViewer.tsx
1420
+ var import_core13 = require("@echothink-ui/core");
1421
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1422
+ function effectLabel(effect) {
1423
+ return effect === "allow" ? "Allow" : "Deny";
1424
+ }
1425
+ function renderRuleValues(values, label) {
1426
+ if (!values.length) {
1427
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "eth-identity-policy-rule-viewer__empty", children: "Not specified" });
1428
+ }
1429
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("ul", { className: "eth-identity-policy-rule-viewer__values", "aria-label": `${label} list`, children: values.map((value) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { children: value }) }, value)) });
1430
+ }
1431
+ function PolicyRuleViewer({ rule, title, className, ...props }) {
1432
+ const effect = effectLabel(rule.effect);
1433
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1434
+ import_core13.Surface,
1435
+ {
1436
+ ...props,
1437
+ title: title ?? `Policy rule ${rule.id}`,
1438
+ className: ["eth-identity-policy-rule-viewer", className].filter(Boolean).join(" "),
1439
+ "data-eth-component": "PolicyRuleViewer",
1440
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("dl", { className: "eth-identity-policy-rule-viewer__facet-list", children: [
1441
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1442
+ "div",
1443
+ {
1444
+ className: "eth-identity-policy-rule-viewer__facet eth-identity-policy-rule-viewer__facet--effect",
1445
+ children: [
1446
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dt", { children: "Effect" }),
1447
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dd", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1448
+ import_core13.Badge,
1449
+ {
1450
+ "aria-label": `Rule effect: ${effect}`,
1451
+ className: "eth-identity-policy-rule-viewer__effect",
1452
+ severity: rule.effect === "allow" ? "success" : "danger",
1453
+ children: effect
1454
+ }
1455
+ ) })
1456
+ ]
1457
+ }
1458
+ ),
1459
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1460
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dt", { children: "Subjects" }),
1461
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dd", { children: renderRuleValues(rule.subjects, "Subjects") })
1462
+ ] }),
1463
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1464
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dt", { children: "Actions" }),
1465
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dd", { children: renderRuleValues(rule.actions, "Actions") })
1466
+ ] }),
1467
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1468
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dt", { children: "Resources" }),
1469
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dd", { children: renderRuleValues(rule.resources, "Resources") })
1470
+ ] }),
1471
+ rule.conditions ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1472
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dt", { children: "Conditions" }),
1473
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("dd", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { className: "eth-identity-policy-rule-viewer__condition", children: rule.conditions }) })
1474
+ ] }) : null
1475
+ ] })
1476
+ }
1477
+ );
1478
+ }
1479
+
1480
+ // src/components/ApprovalPolicyEditor.tsx
1481
+ var import_react = require("react");
1482
+ var import_core14 = require("@echothink-ui/core");
1483
+ var import_forms = require("@echothink-ui/forms");
1484
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1485
+ function ApprovalPolicyEditor({
1486
+ policy,
1487
+ schema,
1488
+ onChange,
1489
+ title,
1490
+ description,
1491
+ actions,
1492
+ className,
1493
+ ...props
1494
+ }) {
1495
+ const generatedId = (0, import_react.useId)().replace(/[^a-zA-Z0-9_-]/g, "");
1496
+ const policyDomId = `eth-identity-policy-${String(policy.id ?? generatedId).replace(/[^a-zA-Z0-9_-]/g, "-")}`;
1497
+ const variables = schema.variables ?? Object.keys(schema.properties ?? {});
1498
+ const rules = policy.rules ?? [];
1499
+ const approvers = policy.approvers ?? [];
1500
+ const requiredApprovals = policy.requiredApprovals ?? 1;
1501
+ const update = (patch) => onChange?.({ ...policy, ...patch });
1502
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1503
+ import_core14.Surface,
1504
+ {
1505
+ ...props,
1506
+ title: title ?? "Approval policy",
1507
+ description: description ?? schema.description ?? "Configure approval thresholds, reviewers, and workflow rules for governed actions.",
1508
+ actions: actions ?? policy.actions,
1509
+ className: ["eth-identity-approval-policy-editor", className].filter(Boolean).join(" "),
1510
+ "data-eth-component": "ApprovalPolicyEditor",
1511
+ children: [
1512
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "eth-identity-approval-policy-editor__layout", children: [
1513
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "eth-identity-approval-policy-editor__fields", children: [
1514
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_core14.FormField, { id: `${policyDomId}-name`, label: "Policy name", required: true, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1515
+ import_core14.TextInput,
1516
+ {
1517
+ id: `${policyDomId}-name`,
1518
+ value: policy.name ?? "",
1519
+ onChange: (event) => update({ name: event.currentTarget.value })
1520
+ }
1521
+ ) }),
1522
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_core14.FormField, { id: `${policyDomId}-description`, label: "Description", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1523
+ import_core14.Textarea,
1524
+ {
1525
+ id: `${policyDomId}-description`,
1526
+ value: policy.description ?? "",
1527
+ onChange: (event) => update({ description: event.currentTarget.value })
1528
+ }
1529
+ ) }),
1530
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1531
+ import_core14.FormField,
1532
+ {
1533
+ id: `${policyDomId}-required`,
1534
+ label: "Required approvals",
1535
+ helperText: "Minimum number of reviewers who must approve before the workflow can continue.",
1536
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1537
+ import_core14.NumberInput,
1538
+ {
1539
+ id: `${policyDomId}-required`,
1540
+ min: 1,
1541
+ value: requiredApprovals,
1542
+ onChange: (event) => update({ requiredApprovals: Number(event.currentTarget.value) })
1543
+ }
1544
+ )
1545
+ }
1546
+ )
1547
+ ] }),
1548
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1549
+ "aside",
1550
+ {
1551
+ className: "eth-identity-approval-policy-editor__summary",
1552
+ "aria-label": "Approval policy summary",
1553
+ children: [
1554
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "eth-identity-approval-policy-editor__summary-header", children: [
1555
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { children: "Workflow gates" }),
1556
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_core14.Badge, { severity: rules.length ? "success" : "warning", children: rules.length ? "Configured" : "Needs rules" })
1557
+ ] }),
1558
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("dl", { className: "eth-identity-approval-policy-editor__summary-grid", children: [
1559
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
1560
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dt", { children: "Required approvals" }),
1561
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dd", { children: requiredApprovals })
1562
+ ] }),
1563
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
1564
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dt", { children: "Rules" }),
1565
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dd", { children: rules.length })
1566
+ ] }),
1567
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
1568
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dt", { children: "Approvers" }),
1569
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("dd", { children: approvers.length ? approvers.length : "Unassigned" })
1570
+ ] })
1571
+ ] }),
1572
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "eth-identity-approval-policy-editor__approvers", children: [
1573
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: "Reviewer pool" }),
1574
+ approvers.length ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "eth-identity-approval-policy-editor__approver-list", children: approvers.map((approver) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_core14.Tag, { children: approver.label }, approver.id)) }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { children: "No approvers assigned to this policy." })
1575
+ ] })
1576
+ ]
1577
+ }
1578
+ )
1579
+ ] }),
1580
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1581
+ import_forms.RuleBuilder,
1582
+ {
1583
+ className: "eth-identity-approval-policy-editor__rules",
1584
+ rules,
1585
+ variables,
1586
+ onChange: (nextRules) => update({ rules: nextRules })
1587
+ }
1588
+ )
1589
+ ]
1590
+ }
1591
+ );
1592
+ }
1593
+
1594
+ // src/index.tsx
1595
+ var IdentityComponentNames = [
1596
+ "IdentityCard",
1597
+ "UserPicker",
1598
+ "GroupPicker",
1599
+ "TeamList",
1600
+ "RoleBadge",
1601
+ "PermissionMatrix",
1602
+ "AccessReviewPanel",
1603
+ "InviteUserPanel",
1604
+ "OrganizationSwitcher",
1605
+ "AccountMenu",
1606
+ "SessionStatus",
1607
+ "AuditTrail",
1608
+ "PolicyRuleViewer",
1609
+ "ApprovalPolicyEditor"
1610
+ ];
1611
+ // Annotate the CommonJS export names for ESM import in node:
1612
+ 0 && (module.exports = {
1613
+ AccessReviewPanel,
1614
+ AccountMenu,
1615
+ ApprovalPolicyEditor,
1616
+ AuditTrail,
1617
+ GroupPicker,
1618
+ IdentityCard,
1619
+ IdentityComponentNames,
1620
+ InviteUserPanel,
1621
+ OrganizationSwitcher,
1622
+ PermissionMatrix,
1623
+ PolicyRuleViewer,
1624
+ RoleBadge,
1625
+ SessionStatus,
1626
+ TeamList,
1627
+ UserPicker
1628
+ });
1629
+ //# sourceMappingURL=index.cjs.map