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