@dloizides/ui-access-admin 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
2
- import { StyleSheet, View, Text, Linking } from 'react-native';
2
+ import { StyleSheet, View, Text, useWindowDimensions, Linking, ScrollView } from 'react-native';
3
3
  import { Button } from '@dloizides/ui-buttons';
4
4
  import { useUi, ToastHost, createToastBus } from '@dloizides/ui-feedback';
5
- import { copyText, ModalShell, ModalDropdown } from '@dloizides/ui-layout';
5
+ import { copyText, LAYOUT_COLLAPSE_BREAKPOINT, ModalShell, ModalDropdown, DropdownVariant } from '@dloizides/ui-layout';
6
6
  import { jsxs, jsx } from 'react/jsx-runtime';
7
7
  import { SelectControl, FormField, FormSwitch, Field } from '@dloizides/ui-forms';
8
8
  import { FilterBar, DataTable } from '@dloizides/ui-tables';
@@ -167,12 +167,25 @@ var ACCESS_ADMIN_I18N = {
167
167
  usersUpdateSuccess: "accessAdmin.users.updateSuccess",
168
168
  usersUpdateFailed: "accessAdmin.users.updateFailed",
169
169
  usersResetSuccess: "accessAdmin.users.resetSuccess",
170
- usersResetFailed: "accessAdmin.users.resetFailed"
170
+ usersResetFailed: "accessAdmin.users.resetFailed",
171
+ usersFieldBlockedReason: "accessAdmin.users.fieldBlockedReason",
172
+ usersBlockedReasonPlaceholder: "accessAdmin.users.blockedReasonPlaceholder",
173
+ usersBlockedReasonHint: "accessAdmin.users.blockedReasonHint",
174
+ permissionsHeading: "accessAdmin.permissions.heading",
175
+ permissionsNote: "accessAdmin.permissions.note",
176
+ permissionsColPermission: "accessAdmin.permissions.colPermission",
177
+ permissionsGranted: "accessAdmin.permissions.granted",
178
+ permissionsNotGranted: "accessAdmin.permissions.notGranted",
179
+ permissionsCellLabel: "accessAdmin.permissions.cellLabel",
180
+ permissionsReadOnlyHint: "accessAdmin.permissions.readOnlyHint",
181
+ permissionsRoleCount: "accessAdmin.permissions.roleCount",
182
+ permissionsEmpty: "accessAdmin.permissions.empty"
171
183
  };
172
184
  var ACCESS_ADMIN_TEST_IDS = {
173
185
  linkPanel: "access-link-panel",
174
186
  accountPanel: "account-link-panel",
175
- usersPanel: "user-management-panel"
187
+ usersPanel: "user-management-panel",
188
+ permissionMatrix: "permission-matrix"
176
189
  };
177
190
 
178
191
  // src/expiry/expiryPolicy.ts
@@ -317,7 +330,7 @@ var AccessLinkPanel = ({
317
330
  }) => {
318
331
  const { t } = useUi();
319
332
  const palette = useAccessAdminPalette();
320
- const styles = useMemo(() => buildStyles(palette), [palette]);
333
+ const styles2 = useMemo(() => buildStyles(palette), [palette]);
321
334
  const state = useCapabilityLinkMint({
322
335
  subject,
323
336
  policy,
@@ -345,16 +358,16 @@ var AccessLinkPanel = ({
345
358
  onResult(t(ACCESS_ADMIN_I18N.copyFailed, subject.displayName), "error" /* Error */);
346
359
  });
347
360
  };
348
- return /* @__PURE__ */ jsxs(View, { style: styles.panel, testID: testIDPrefix, children: [
349
- /* @__PURE__ */ jsx(Text, { style: styles.heading, children: t(ACCESS_ADMIN_I18N.panelHeading, subject.displayName) }),
350
- /* @__PURE__ */ jsx(Text, { style: styles.note, children: t(ACCESS_ADMIN_I18N.panelNote) }),
351
- /* @__PURE__ */ jsx(Text, { style: styles.warning, testID: `${testIDPrefix}-capability-warning`, children: t(ACCESS_ADMIN_I18N.capabilityWarning) }),
352
- /* @__PURE__ */ jsx(Text, { style: styles.note, testID: `${testIDPrefix}-expiry-preview`, children: t(ACCESS_ADMIN_I18N.expiryPreview, expiryDate) }),
353
- state.minted !== null ? /* @__PURE__ */ jsxs(View, { style: styles.revealBox, testID: `${testIDPrefix}-minted`, children: [
354
- /* @__PURE__ */ jsx(Text, { style: styles.revealTitle, children: t(ACCESS_ADMIN_I18N.mintedTitle, subject.displayName) }),
355
- /* @__PURE__ */ jsx(Text, { style: styles.note, children: t(ACCESS_ADMIN_I18N.mintedHint) }),
356
- /* @__PURE__ */ jsx(Text, { selectable: true, style: styles.url, testID: `${testIDPrefix}-minted-url`, children: mintedUrl }),
357
- /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
361
+ return /* @__PURE__ */ jsxs(View, { style: styles2.panel, testID: testIDPrefix, children: [
362
+ /* @__PURE__ */ jsx(Text, { style: styles2.heading, children: t(ACCESS_ADMIN_I18N.panelHeading, subject.displayName) }),
363
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, children: t(ACCESS_ADMIN_I18N.panelNote) }),
364
+ /* @__PURE__ */ jsx(Text, { style: styles2.warning, testID: `${testIDPrefix}-capability-warning`, children: t(ACCESS_ADMIN_I18N.capabilityWarning) }),
365
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, testID: `${testIDPrefix}-expiry-preview`, children: t(ACCESS_ADMIN_I18N.expiryPreview, expiryDate) }),
366
+ state.minted !== null ? /* @__PURE__ */ jsxs(View, { style: styles2.revealBox, testID: `${testIDPrefix}-minted`, children: [
367
+ /* @__PURE__ */ jsx(Text, { style: styles2.revealTitle, children: t(ACCESS_ADMIN_I18N.mintedTitle, subject.displayName) }),
368
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, children: t(ACCESS_ADMIN_I18N.mintedHint) }),
369
+ /* @__PURE__ */ jsx(Text, { selectable: true, style: styles2.url, testID: `${testIDPrefix}-minted-url`, children: mintedUrl }),
370
+ /* @__PURE__ */ jsxs(View, { style: styles2.actions, children: [
358
371
  /* @__PURE__ */ jsx(
359
372
  Button,
360
373
  {
@@ -388,13 +401,13 @@ var AccessLinkPanel = ({
388
401
  }
389
402
  )
390
403
  ] }),
391
- /* @__PURE__ */ jsx(Text, { style: styles.note, children: t(ACCESS_ADMIN_I18N.emailNote) })
404
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, children: t(ACCESS_ADMIN_I18N.emailNote) })
392
405
  ] }) : null,
393
- linksFailed ? /* @__PURE__ */ jsx(Text, { style: styles.warning, testID: `${testIDPrefix}-load-error`, children: t(ACCESS_ADMIN_I18N.liveLoadError) }) : null,
394
- !linksFailed && liveLinks.length > 0 ? /* @__PURE__ */ jsxs(View, { style: styles.liveBox, children: [
395
- /* @__PURE__ */ jsx(Text, { style: styles.subheading, children: t(ACCESS_ADMIN_I18N.liveHeading, String(liveLinks.length)) }),
396
- liveLinks.map((link) => /* @__PURE__ */ jsxs(View, { style: styles.liveRow, children: [
397
- /* @__PURE__ */ jsx(Text, { style: styles.liveMeta, children: t(
406
+ linksFailed ? /* @__PURE__ */ jsx(Text, { style: styles2.warning, testID: `${testIDPrefix}-load-error`, children: t(ACCESS_ADMIN_I18N.liveLoadError) }) : null,
407
+ !linksFailed && liveLinks.length > 0 ? /* @__PURE__ */ jsxs(View, { style: styles2.liveBox, children: [
408
+ /* @__PURE__ */ jsx(Text, { style: styles2.subheading, children: t(ACCESS_ADMIN_I18N.liveHeading, String(liveLinks.length)) }),
409
+ liveLinks.map((link) => /* @__PURE__ */ jsxs(View, { style: styles2.liveRow, children: [
410
+ /* @__PURE__ */ jsx(Text, { style: styles2.liveMeta, children: t(
398
411
  ACCESS_ADMIN_I18N.liveMeta,
399
412
  formatLinkDate(link.createdAt, ""),
400
413
  formatLinkDate(link.expiresAt, t(ACCESS_ADMIN_I18N.expiresNever))
@@ -402,8 +415,8 @@ var AccessLinkPanel = ({
402
415
  renderLinkActions === void 0 ? null : renderLinkActions(link)
403
416
  ] }, link.id))
404
417
  ] }) : null,
405
- !linksFailed && liveLinks.length === 0 ? /* @__PURE__ */ jsx(Text, { style: styles.note, testID: `${testIDPrefix}-no-live`, children: t(ACCESS_ADMIN_I18N.noLive) }) : null,
406
- /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
418
+ !linksFailed && liveLinks.length === 0 ? /* @__PURE__ */ jsx(Text, { style: styles2.note, testID: `${testIDPrefix}-no-live`, children: t(ACCESS_ADMIN_I18N.noLive) }) : null,
419
+ /* @__PURE__ */ jsxs(View, { style: styles2.actions, children: [
407
420
  /* @__PURE__ */ jsx(
408
421
  Button,
409
422
  {
@@ -549,7 +562,7 @@ var AccountLinkPanel = ({
549
562
  }) => {
550
563
  const { t } = useUi();
551
564
  const palette = useAccessAdminPalette();
552
- const styles = useMemo(() => buildStyles2(palette), [palette]);
565
+ const styles2 = useMemo(() => buildStyles2(palette), [palette]);
553
566
  const state = useSubjectAccountLink({
554
567
  subject,
555
568
  boundAccountId,
@@ -564,9 +577,9 @@ var AccountLinkPanel = ({
564
577
  [accounts, bindings, subject, unnamedLabel]
565
578
  );
566
579
  const selectedLabel = options.find((option) => option.value === state.selectedAccountId)?.label ?? "";
567
- return /* @__PURE__ */ jsxs(View, { style: styles.panel, testID: testIDPrefix, children: [
568
- /* @__PURE__ */ jsx(Text, { style: styles.heading, children: t(ACCESS_ADMIN_I18N.accountPanelHeading, subject.displayName) }),
569
- /* @__PURE__ */ jsx(Text, { style: styles.note, children: t(ACCESS_ADMIN_I18N.accountPanelNote) }),
580
+ return /* @__PURE__ */ jsxs(View, { style: styles2.panel, testID: testIDPrefix, children: [
581
+ /* @__PURE__ */ jsx(Text, { style: styles2.heading, children: t(ACCESS_ADMIN_I18N.accountPanelHeading, subject.displayName) }),
582
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, children: t(ACCESS_ADMIN_I18N.accountPanelNote) }),
570
583
  options.length > 0 ? /* @__PURE__ */ jsx(
571
584
  SelectControl,
572
585
  {
@@ -579,8 +592,8 @@ var AccountLinkPanel = ({
579
592
  testID: `${testIDPrefix}-picker`,
580
593
  value: state.selectedAccountId ?? ""
581
594
  }
582
- ) : /* @__PURE__ */ jsx(Text, { style: styles.note, testID: `${testIDPrefix}-no-accounts`, children: t(ACCESS_ADMIN_I18N.accountNone) }),
583
- /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
595
+ ) : /* @__PURE__ */ jsx(Text, { style: styles2.note, testID: `${testIDPrefix}-no-accounts`, children: t(ACCESS_ADMIN_I18N.accountNone) }),
596
+ /* @__PURE__ */ jsxs(View, { style: styles2.actions, children: [
584
597
  /* @__PURE__ */ jsx(
585
598
  Button,
586
599
  {
@@ -673,6 +686,18 @@ function roleOptions(assignableRoles) {
673
686
  function tenantValueToId(value) {
674
687
  return value === NO_TENANT_VALUE ? null : value;
675
688
  }
689
+ function isBlocking(wasActive, active) {
690
+ return wasActive && !active;
691
+ }
692
+ function isBlockReasonMissing(wasActive, active, blockedReason) {
693
+ return isBlocking(wasActive, active) && blockedReason.trim() === "";
694
+ }
695
+ function buildEditPatch(input) {
696
+ const patch = { displayName: input.displayName.trim(), role: input.role, active: input.active };
697
+ if (isBlocking(input.wasActive, input.active)) patch.blockedReason = input.blockedReason.trim();
698
+ if (input.tenantId !== void 0) patch.tenantId = input.tenantId;
699
+ return patch;
700
+ }
676
701
  var ACTIONS_GAP = 8;
677
702
  function buildUserColumns({
678
703
  t,
@@ -763,7 +788,8 @@ var PickerField = ({
763
788
  onChange,
764
789
  options,
765
790
  testID,
766
- value
791
+ value,
792
+ variant: DropdownVariant.Modal
767
793
  }
768
794
  ) });
769
795
  };
@@ -910,6 +936,7 @@ var EditUserModal = ({
910
936
  const [role, setRole] = useState("");
911
937
  const [tenantValue, setTenantValue] = useState(NO_TENANT_VALUE);
912
938
  const [active, setActive] = useState(true);
939
+ const [blockedReason, setBlockedReason] = useState("");
913
940
  const [submitting, setSubmitting] = useState(false);
914
941
  const userId = user?.id;
915
942
  useEffect(() => {
@@ -918,28 +945,36 @@ var EditUserModal = ({
918
945
  setRole(user.roles[0] ?? "");
919
946
  setTenantValue(user.tenantId ?? NO_TENANT_VALUE);
920
947
  setActive(user.active);
948
+ setBlockedReason(user.blockedReason ?? "");
921
949
  setSubmitting(false);
922
950
  }, [visible, userId, user]);
923
- const canSubmit = !submitting && displayName.trim() !== "" && role !== "";
951
+ const wasActive = user?.active ?? true;
952
+ const canSubmit = !submitting && displayName.trim() !== "" && role !== "" && !isBlockReasonMissing(wasActive, active, blockedReason);
924
953
  const handleSubmit = async () => {
925
954
  if (user === null) return;
926
955
  setSubmitting(true);
927
- const patch = {
928
- displayName: displayName.trim(),
956
+ const patch = buildEditPatch({
957
+ displayName,
929
958
  role,
930
- active
931
- };
932
- if (isPlatformAdmin) patch.tenantId = tenantValueToId(tenantValue);
959
+ wasActive,
960
+ active,
961
+ blockedReason,
962
+ tenantId: isPlatformAdmin ? tenantValueToId(tenantValue) : void 0
963
+ });
933
964
  const ok = await onSubmit(user.id, patch);
934
965
  setSubmitting(false);
935
966
  if (ok) onCancel();
936
967
  };
968
+ const submitIfReady = () => {
969
+ if (canSubmit) void handleSubmit();
970
+ };
937
971
  return /* @__PURE__ */ jsx(ModalShell, { onCancel, title: t(ACCESS_ADMIN_I18N.usersEditTitle), visible, children: /* @__PURE__ */ jsxs(View, { style: modalFormStyles.body, testID: testIDPrefix, children: [
938
972
  /* @__PURE__ */ jsx(
939
973
  FormField,
940
974
  {
941
975
  label: t(ACCESS_ADMIN_I18N.usersFieldDisplayName),
942
976
  onChangeText: setDisplayName,
977
+ onSubmitEditing: submitIfReady,
943
978
  placeholder: t(ACCESS_ADMIN_I18N.usersDisplayNamePlaceholder),
944
979
  required: true,
945
980
  testID: `${testIDPrefix}-display-name`,
@@ -980,6 +1015,19 @@ var EditUserModal = ({
980
1015
  value: active
981
1016
  }
982
1017
  ),
1018
+ active ? null : /* @__PURE__ */ jsx(
1019
+ FormField,
1020
+ {
1021
+ accessibilityHint: t(ACCESS_ADMIN_I18N.usersBlockedReasonHint),
1022
+ label: t(ACCESS_ADMIN_I18N.usersFieldBlockedReason),
1023
+ onChangeText: setBlockedReason,
1024
+ onSubmitEditing: submitIfReady,
1025
+ placeholder: t(ACCESS_ADMIN_I18N.usersBlockedReasonPlaceholder),
1026
+ required: wasActive,
1027
+ testID: `${testIDPrefix}-blocked-reason`,
1028
+ value: blockedReason
1029
+ }
1030
+ ),
983
1031
  /* @__PURE__ */ jsxs(View, { style: modalFormStyles.actions, children: [
984
1032
  /* @__PURE__ */ jsx(
985
1033
  Button,
@@ -1161,7 +1209,7 @@ var UserManagementPanel = ({
1161
1209
  testID = ACCESS_ADMIN_TEST_IDS.usersPanel
1162
1210
  }) => {
1163
1211
  const palette = useAccessAdminPalette();
1164
- const styles = useMemo(() => buildStyles3(palette), [palette]);
1212
+ const styles2 = useMemo(() => buildStyles3(palette), [palette]);
1165
1213
  const controller = useUserManagement(api, t);
1166
1214
  const [query, setQuery] = useState("");
1167
1215
  const [openModal, setOpenModal] = useState("none" /* None */);
@@ -1183,10 +1231,10 @@ var UserManagementPanel = ({
1183
1231
  () => buildUserColumns({ t, tenants, testIDPrefix: testID, onEdit: openEdit, onReset: openReset }),
1184
1232
  [t, tenants, testID]
1185
1233
  );
1186
- return /* @__PURE__ */ jsxs(View, { style: styles.panel, testID, children: [
1234
+ return /* @__PURE__ */ jsxs(View, { style: styles2.panel, testID, children: [
1187
1235
  /* @__PURE__ */ jsx(ToastHost, { subscribe: controller.toastSubscribe, toastTestID: `${testID}-toast` }),
1188
- /* @__PURE__ */ jsx(Text, { style: styles.heading, children: t(ACCESS_ADMIN_I18N.usersHeading) }),
1189
- /* @__PURE__ */ jsx(Text, { style: styles.note, children: t(ACCESS_ADMIN_I18N.usersNote) }),
1236
+ /* @__PURE__ */ jsx(Text, { style: styles2.heading, children: t(ACCESS_ADMIN_I18N.usersHeading) }),
1237
+ /* @__PURE__ */ jsx(Text, { style: styles2.note, children: t(ACCESS_ADMIN_I18N.usersNote) }),
1190
1238
  /* @__PURE__ */ jsx(
1191
1239
  FilterBar,
1192
1240
  {
@@ -1203,7 +1251,7 @@ var UserManagementPanel = ({
1203
1251
  resultsCount: visibleUsers.length,
1204
1252
  resultsLabel: t(ACCESS_ADMIN_I18N.usersResultsLabel),
1205
1253
  testID: `${testID}-filter-bar`,
1206
- children: /* @__PURE__ */ jsx(View, { style: styles.search, children: /* @__PURE__ */ jsx(
1254
+ children: /* @__PURE__ */ jsx(View, { style: styles2.search, children: /* @__PURE__ */ jsx(
1207
1255
  FormField,
1208
1256
  {
1209
1257
  accessibilityLabel: t(ACCESS_ADMIN_I18N.usersSearchLabel),
@@ -1218,8 +1266,8 @@ var UserManagementPanel = ({
1218
1266
  ) })
1219
1267
  }
1220
1268
  ),
1221
- controller.loadFailed ? /* @__PURE__ */ jsxs(View, { style: styles.errorBox, testID: `${testID}-load-error`, children: [
1222
- /* @__PURE__ */ jsx(Text, { style: styles.warning, children: t(ACCESS_ADMIN_I18N.usersLoadError) }),
1269
+ controller.loadFailed ? /* @__PURE__ */ jsxs(View, { style: styles2.errorBox, testID: `${testID}-load-error`, children: [
1270
+ /* @__PURE__ */ jsx(Text, { style: styles2.warning, children: t(ACCESS_ADMIN_I18N.usersLoadError) }),
1223
1271
  /* @__PURE__ */ jsx(
1224
1272
  Button,
1225
1273
  {
@@ -1301,6 +1349,116 @@ var buildStyles3 = (c) => StyleSheet.create({
1301
1349
  warning: { color: c.danger, fontSize: FONT_SM3, fontWeight: "600" }
1302
1350
  });
1303
1351
 
1304
- export { ACCESS_ADMIN_I18N, ACCESS_ADMIN_TEST_IDS, AccessLinkPanel, AccessResultTone, AccountLinkPanel, UserManagementPanel, accountDisplayLabel, activeLinksForSubject, buildAccountOptions, buildCapabilityLinkMailto, capabilityExpiryDate, capabilityExpiryDays, daysUntilAnchor, formatLinkDate, linksForSubject, useAccessAdminPalette, useCapabilityLinkMint, useSubjectAccountLink };
1352
+ // src/PermissionMatrix/permissionMatrixModel.ts
1353
+ function buildPermissionMatrix(roles, permissions, grants) {
1354
+ return roles.map((role) => {
1355
+ const held = new Set(grants[role.id] ?? []);
1356
+ const cells = permissions.map((permission) => ({ permission, granted: held.has(permission.id) }));
1357
+ return { role, cells, grantedCount: cells.filter((cell) => cell.granted).length };
1358
+ });
1359
+ }
1360
+ function grantStatusLabel(granted, t) {
1361
+ return granted ? t(ACCESS_ADMIN_I18N.permissionsGranted) : t(ACCESS_ADMIN_I18N.permissionsNotGranted);
1362
+ }
1363
+ function cellAccessibilityLabel(role, permission, granted, t) {
1364
+ return t(ACCESS_ADMIN_I18N.permissionsCellLabel, role.label, permission.label, grantStatusLabel(granted, t));
1365
+ }
1366
+ var SPACE_XS4 = 4;
1367
+ var SPACE_SM5 = 8;
1368
+ var SPACE_MD5 = 12;
1369
+ var RADIUS = 8;
1370
+ var MAX_WIDTH = 1200;
1371
+ var LABEL_COLUMN_WIDTH = 280;
1372
+ var ROLE_COLUMN_WIDTH = 140;
1373
+ var HEADING_SIZE = 18;
1374
+ var BODY_SIZE = 14;
1375
+ var LINE_HEIGHT = 20;
1376
+ var styles = StyleSheet.create({
1377
+ root: { alignSelf: "center", gap: SPACE_MD5, maxWidth: MAX_WIDTH, width: "100%" },
1378
+ heading: { fontSize: HEADING_SIZE, fontWeight: "600", lineHeight: LINE_HEIGHT + SPACE_XS4 },
1379
+ body: { fontSize: BODY_SIZE, lineHeight: LINE_HEIGHT },
1380
+ strong: { fontSize: BODY_SIZE, fontWeight: "600", lineHeight: LINE_HEIGHT },
1381
+ list: { gap: SPACE_MD5 },
1382
+ card: { borderRadius: RADIUS, borderWidth: StyleSheet.hairlineWidth, gap: SPACE_SM5, padding: SPACE_MD5 },
1383
+ stackRow: { flexDirection: "row", flexWrap: "wrap", gap: SPACE_SM5, justifyContent: "space-between" },
1384
+ stackLabel: { flexBasis: 0, flexGrow: 1, minWidth: 0 },
1385
+ tableRow: { borderBottomWidth: StyleSheet.hairlineWidth, flexDirection: "row" },
1386
+ labelCell: { padding: SPACE_SM5, width: LABEL_COLUMN_WIDTH },
1387
+ roleCell: { padding: SPACE_SM5, width: ROLE_COLUMN_WIDTH }
1388
+ });
1389
+ var statusColor = (granted, palette) => granted ? palette.textPrimary : palette.textSecondary;
1390
+ var StackedMatrix = ({ columns, palette, t, testID }) => /* @__PURE__ */ jsx(View, { style: styles.list, children: columns.map(({ role, cells, grantedCount }) => /* @__PURE__ */ jsxs(
1391
+ View,
1392
+ {
1393
+ style: [styles.card, { backgroundColor: palette.panelSurface, borderColor: palette.border }],
1394
+ testID: `${testID}-role-${role.id}`,
1395
+ children: [
1396
+ /* @__PURE__ */ jsx(Text, { style: [styles.strong, { color: palette.textPrimary }], children: role.label }),
1397
+ /* @__PURE__ */ jsx(Text, { style: [styles.body, { color: palette.textSecondary }], children: t(ACCESS_ADMIN_I18N.permissionsRoleCount, String(grantedCount), String(cells.length)) }),
1398
+ cells.map(({ permission, granted }) => /* @__PURE__ */ jsxs(
1399
+ View,
1400
+ {
1401
+ accessibilityHint: t(ACCESS_ADMIN_I18N.permissionsReadOnlyHint),
1402
+ accessibilityLabel: cellAccessibilityLabel(role, permission, granted, t),
1403
+ accessible: true,
1404
+ style: styles.stackRow,
1405
+ testID: `${testID}-cell-${role.id}-${permission.id}`,
1406
+ children: [
1407
+ /* @__PURE__ */ jsx(Text, { style: [styles.body, styles.stackLabel, { color: palette.textPrimary }], children: permission.label }),
1408
+ /* @__PURE__ */ jsx(Text, { style: [granted ? styles.strong : styles.body, { color: statusColor(granted, palette) }], children: grantStatusLabel(granted, t) })
1409
+ ]
1410
+ },
1411
+ permission.id
1412
+ ))
1413
+ ]
1414
+ },
1415
+ role.id
1416
+ )) });
1417
+ var TableMatrix = ({ columns, permissions, palette, t, testID }) => /* @__PURE__ */ jsx(ScrollView, { horizontal: true, testID: `${testID}-table`, children: /* @__PURE__ */ jsxs(View, { style: [styles.card, { backgroundColor: palette.panelSurface, borderColor: palette.border }], children: [
1418
+ /* @__PURE__ */ jsxs(View, { style: [styles.tableRow, { borderColor: palette.border }], children: [
1419
+ /* @__PURE__ */ jsx(Text, { style: [styles.strong, styles.labelCell, { color: palette.textPrimary }], children: t(ACCESS_ADMIN_I18N.permissionsColPermission) }),
1420
+ columns.map(({ role }) => /* @__PURE__ */ jsx(Text, { style: [styles.strong, styles.roleCell, { color: palette.textPrimary }], children: role.label }, role.id))
1421
+ ] }),
1422
+ permissions.map((permission, index) => /* @__PURE__ */ jsxs(View, { style: [styles.tableRow, { borderColor: palette.border }], children: [
1423
+ /* @__PURE__ */ jsx(Text, { style: [styles.body, styles.labelCell, { color: palette.textPrimary }], children: permission.label }),
1424
+ columns.map(({ role, cells }) => {
1425
+ const granted = cells[index]?.granted === true;
1426
+ return /* @__PURE__ */ jsx(
1427
+ Text,
1428
+ {
1429
+ accessibilityHint: t(ACCESS_ADMIN_I18N.permissionsReadOnlyHint),
1430
+ accessibilityLabel: cellAccessibilityLabel(role, permission, granted, t),
1431
+ style: [granted ? styles.strong : styles.body, styles.roleCell, { color: statusColor(granted, palette) }],
1432
+ testID: `${testID}-cell-${role.id}-${permission.id}`,
1433
+ children: grantStatusLabel(granted, t)
1434
+ },
1435
+ role.id
1436
+ );
1437
+ })
1438
+ ] }, permission.id))
1439
+ ] }) });
1440
+ var PermissionMatrix = ({
1441
+ roles,
1442
+ permissions,
1443
+ grants,
1444
+ t,
1445
+ testID = ACCESS_ADMIN_TEST_IDS.permissionMatrix
1446
+ }) => {
1447
+ const palette = useAccessAdminPalette();
1448
+ const { width } = useWindowDimensions();
1449
+ const columns = useMemo(() => buildPermissionMatrix(roles, permissions, grants), [roles, permissions, grants]);
1450
+ const isWide = width >= LAYOUT_COLLAPSE_BREAKPOINT;
1451
+ const isEmpty = roles.length === 0 || permissions.length === 0;
1452
+ const partProps = { columns, permissions, palette, t, testID };
1453
+ return /* @__PURE__ */ jsxs(View, { style: styles.root, testID, children: [
1454
+ /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: [styles.heading, { color: palette.textPrimary }], children: t(ACCESS_ADMIN_I18N.permissionsHeading) }),
1455
+ /* @__PURE__ */ jsx(Text, { style: [styles.body, { color: palette.textSecondary }], children: t(ACCESS_ADMIN_I18N.permissionsNote) }),
1456
+ isEmpty ? /* @__PURE__ */ jsx(Text, { style: [styles.body, { color: palette.textSecondary }], testID: `${testID}-empty`, children: t(ACCESS_ADMIN_I18N.permissionsEmpty) }) : null,
1457
+ !isEmpty && isWide ? /* @__PURE__ */ jsx(TableMatrix, { ...partProps }) : null,
1458
+ !isEmpty && !isWide ? /* @__PURE__ */ jsx(StackedMatrix, { ...partProps }) : null
1459
+ ] });
1460
+ };
1461
+
1462
+ export { ACCESS_ADMIN_I18N, ACCESS_ADMIN_TEST_IDS, AccessLinkPanel, AccessResultTone, AccountLinkPanel, PermissionMatrix, UserManagementPanel, accountDisplayLabel, activeLinksForSubject, buildAccountOptions, buildCapabilityLinkMailto, buildPermissionMatrix, capabilityExpiryDate, capabilityExpiryDays, daysUntilAnchor, formatLinkDate, linksForSubject, useAccessAdminPalette, useCapabilityLinkMint, useSubjectAccountLink };
1305
1463
  //# sourceMappingURL=index.mjs.map
1306
1464
  //# sourceMappingURL=index.mjs.map