@visns-studio/visns-components 6.17.1 → 6.20.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 (84) hide show
  1. package/package.json +10 -7
  2. package/src/components/AssociationManager.jsx +344 -177
  3. package/src/components/AsyncSelect.jsx +2 -2
  4. package/src/components/Autocomplete.jsx +57 -12
  5. package/src/components/Breadcrumb.jsx +23 -4
  6. package/src/components/ClientAssociationManager.jsx +321 -145
  7. package/src/components/DataGrid.jsx +625 -20
  8. package/src/components/DropZone.jsx +4 -1
  9. package/src/components/Form.jsx +233 -6
  10. package/src/components/MultiSelect.jsx +2 -2
  11. package/src/components/Navigation.jsx +45 -25
  12. package/src/components/TableFilter.jsx +172 -12
  13. package/src/components/UniversalSearch.jsx +390 -0
  14. package/src/components/auth/Login.jsx +164 -8
  15. package/src/components/auth/Profile.jsx +209 -22
  16. package/src/components/auth/authEndpoints.js +4 -0
  17. package/src/components/auth/webauthn.js +194 -0
  18. package/src/components/columns/ColumnRenderers.jsx +6 -1
  19. package/src/components/controls/DataGridSearch.jsx +11 -5
  20. package/src/components/generic/GenericAuth.jsx +6 -0
  21. package/src/components/generic/GenericDashboard.jsx +93 -36
  22. package/src/components/generic/GenericDetail.jsx +146 -61
  23. package/src/components/generic/GenericIndex.jsx +134 -38
  24. package/src/components/generic/GenericSettings.jsx +220 -0
  25. package/src/components/generic/StandardModal.jsx +165 -8
  26. package/src/components/generic/dashboardChartDefaults.js +230 -0
  27. package/src/components/generic/shared/indexLayout.js +38 -0
  28. package/src/components/navActive.js +25 -0
  29. package/src/components/settings/IntegrationsManager.jsx +474 -0
  30. package/src/components/sms/SmsLineSettings.jsx +16 -12
  31. package/src/components/styles/AssociationManager.module.scss +4 -363
  32. package/src/components/styles/AuditLog.module.scss +6 -6
  33. package/src/components/styles/AuditLogs.module.scss +3 -3
  34. package/src/components/styles/Autocomplete.module.scss +7 -7
  35. package/src/components/styles/BusinessCardOcr.module.scss +6 -1
  36. package/src/components/styles/CallQueueSettings.module.scss +4 -1
  37. package/src/components/styles/CameraPlacement.module.scss +19 -19
  38. package/src/components/styles/CategorizedDropZone.module.scss +6 -1
  39. package/src/components/styles/ClientAssociationManager.module.scss +5 -289
  40. package/src/components/styles/ClientAuth.module.scss +1 -1
  41. package/src/components/styles/DataGrid.module.scss +455 -8
  42. package/src/components/styles/Field.module.scss +32 -24
  43. package/src/components/styles/Form.module.scss +123 -3
  44. package/src/components/styles/GenericDashboard.module.scss +612 -825
  45. package/src/components/styles/GenericDetail.module.scss +406 -61
  46. package/src/components/styles/GenericDynamic.module.scss +25 -25
  47. package/src/components/styles/GenericEditableTable.module.scss +33 -33
  48. package/src/components/styles/GenericFormBuilder.module.scss +119 -119
  49. package/src/components/styles/GenericIndex.module.scss +483 -38
  50. package/src/components/styles/GenericMain.module.scss +6 -3
  51. package/src/components/styles/GenericQuote.module.scss +399 -81
  52. package/src/components/styles/GenericReport.module.scss +8 -8
  53. package/src/components/styles/GenericSettings.module.scss +209 -0
  54. package/src/components/styles/GenericSort.module.scss +9 -9
  55. package/src/components/styles/Integrations.module.scss +321 -0
  56. package/src/components/styles/List.module.scss +4 -4
  57. package/src/components/styles/Login.module.scss +57 -0
  58. package/src/components/styles/MultiSelect.module.scss +4 -4
  59. package/src/components/styles/Navigation.module.scss +12 -12
  60. package/src/components/styles/Notification.module.scss +12 -8
  61. package/src/components/styles/NotificationList.module.scss +5 -5
  62. package/src/components/styles/Profile.module.scss +5 -5
  63. package/src/components/styles/QrCode.module.scss +4 -4
  64. package/src/components/styles/QuickAction.module.scss +20 -20
  65. package/src/components/styles/SortableQuoteItems.module.scss +4 -4
  66. package/src/components/styles/TableFilter.module.scss +128 -17
  67. package/src/components/styles/TicketConversation.module.scss +497 -0
  68. package/src/components/styles/UniversalSearch.module.scss +229 -0
  69. package/src/components/styles/Vault.module.scss +413 -0
  70. package/src/components/styles/_associationManager.scss +467 -0
  71. package/src/components/styles/_authBrandPanel.scss +15 -9
  72. package/src/components/styles/_controls.scss +43 -39
  73. package/src/components/styles/global-datagrid.css +196 -0
  74. package/src/components/styles/global.css +168 -17
  75. package/src/components/tickets/TicketConversation.jsx +455 -0
  76. package/src/components/utils/rowActionSettings.js +117 -0
  77. package/src/components/vault/VaultClientPicker.jsx +183 -0
  78. package/src/components/vault/VaultEntryForm.jsx +23 -0
  79. package/src/components/vault/VaultManager.jsx +382 -34
  80. package/src/components/vault/VaultShareModal.jsx +454 -0
  81. package/src/components/vault/vaultEndpoints.js +16 -2
  82. package/src/components/vault/vaultShares.js +247 -0
  83. package/src/index.js +35 -0
  84. package/src/utils/fetchUtil.js +38 -14
@@ -34,56 +34,52 @@
34
34
  gap: 1rem;
35
35
  }
36
36
 
37
- .grid__three {
38
- width: 33%;
39
- background: var(--item-color);
40
- border-radius: var(--br);
41
- border: 1px solid rgba(var(--item-color-rgb), 1.15);
37
+ /* ----------------------------------------------------------------------------
38
+ The card set. Same four cards, same spec, as `GenericIndex.module.scss` —
39
+ see the block comment there for why they were unified. The two files carry
40
+ the same declarations on purpose so a detail page and an index page sit on
41
+ the same baseline; change one, change the other.
42
+ -------------------------------------------------------------------------- */
43
+
44
+ @mixin card-surface {
45
+ background: var(--item-color, #fff);
46
+ border: 1px solid var(--border-color, #e1e1e1);
47
+ border-radius: var(--card-radius, 10px);
48
+ box-shadow: var(
49
+ --shadow-sm,
50
+ 0 1px 2px rgba(var(--primary-rgb, 27, 57, 51), 0.08),
51
+ 0 2px 8px rgba(var(--primary-rgb, 27, 57, 51), 0.06)
52
+ );
42
53
  box-sizing: border-box;
54
+ /* Load-bearing: selects, popovers and tooltips open out of these. */
43
55
  overflow: visible;
44
56
  position: relative;
45
- padding: 20px;
46
57
  margin: 8px 0;
47
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
58
+ }
59
+
60
+ .grid__three {
61
+ @include card-surface;
62
+ width: 33%;
63
+ padding: 20px;
48
64
  }
49
65
 
50
66
  .grid__half {
67
+ @include card-surface;
51
68
  width: 50%;
52
- background: var(--item-color);
53
- border-radius: 6px;
54
- border: 1px solid #dadce0;
55
- box-sizing: border-box;
56
- overflow: visible;
57
- position: relative;
58
69
  padding: 20px;
59
- margin: 8px 0;
60
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
61
70
  }
62
71
 
63
72
  .grid__full {
73
+ @include card-surface;
64
74
  width: 100%;
65
- background: var(--item-color);
66
- border-radius: var(--br);
67
- border: 1px solid rgba(var(--item-color-rgb), 1.15);
68
- box-sizing: border-box;
69
- overflow: visible;
70
- position: relative;
71
- padding: 2px;
72
- margin: 8px 0;
73
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
75
+ /* Was 2px — see GenericIndex. */
76
+ padding: 0;
74
77
  }
75
78
 
76
79
  .grid__dashfull {
80
+ @include card-surface;
77
81
  width: 100%;
78
- background: var(--item-color);
79
- border-radius: 6px;
80
- border: 1px solid #dadce0;
81
- box-sizing: border-box;
82
- overflow: visible;
83
- position: relative;
84
82
  padding: 20px;
85
- margin: 8px 0;
86
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
87
83
  }
88
84
 
89
85
  .grid__subrow {
@@ -94,10 +90,10 @@
94
90
  gap: 0.5rem;
95
91
  }
96
92
 
97
- /* `page.tabLayout: "underline"` — tabs above the panel rather than beside it.
98
- See the note on `tabsUnderlined` in GenericDetail.jsx for when a page wants
99
- this. The row becomes a column and both children take the full width; the
100
- rail gives up its card, because an underline strip is not a panel. */
93
+ /* The default detail-page layout: tabs above the panel rather than beside it.
94
+ See the note on `tabsUnderlined` in GenericDetail.jsx. The row becomes a
95
+ column and both children take the full width; the rail gives up its card,
96
+ because an underline strip is not a panel. */
101
97
  .grid__subrow--stacked {
102
98
  flex-direction: column;
103
99
  flex-wrap: nowrap;
@@ -107,8 +103,13 @@
107
103
  .grid__subnav--underline {
108
104
  flex: 0 0 auto;
109
105
  width: 100%;
106
+ /* `min-width: 0` is what lets the strip inside actually scroll: a flex item
107
+ floors at its content's min-content width by default, so a strip of ten
108
+ tabs would push this container — and the page — wider instead of
109
+ overflowing within it. */
110
+ min-width: 0;
110
111
  padding: 0;
111
- margin: 8px 0 0;
112
+ margin: 0;
112
113
  background: none;
113
114
  border: none;
114
115
  border-radius: 0;
@@ -375,7 +376,7 @@
375
376
 
376
377
  span {
377
378
  font-size: 0.875em;
378
- color: rgba(var(--background-color-rgb), 0.65);
379
+ color: rgba(var(--background-color-rgb, 255, 255, 255), 0.65);
379
380
  }
380
381
  }
381
382
 
@@ -533,7 +534,7 @@
533
534
  display: flex;
534
535
  align-items: center;
535
536
  justify-content: space-between;
536
- background: rgba(var(--primary-rgb), 0.05);
537
+ background: rgba(var(--primary-rgb, 27, 57, 51), 0.05);
537
538
  color: var(--primary-color);
538
539
  border-radius: 6px;
539
540
  transition: all 0.2s ease;
@@ -541,7 +542,7 @@
541
542
  padding: 12px 16px;
542
543
  box-sizing: border-box;
543
544
  position: relative;
544
- border: 1px solid rgba(var(--primary-rgb), 0.15);
545
+ border: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.15);
545
546
  line-height: 1.4;
546
547
  font-size: 0.9rem;
547
548
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
@@ -549,7 +550,7 @@
549
550
  &:hover {
550
551
  transform: translateY(-2px);
551
552
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
552
- background: rgba(var(--primary-rgb), 0.08);
553
+ background: rgba(var(--primary-rgb, 27, 57, 51), 0.08);
553
554
  }
554
555
 
555
556
  .file-info {
@@ -580,7 +581,7 @@
580
581
 
581
582
  .file-size {
582
583
  font-size: 0.75rem;
583
- color: rgba(var(--primary-rgb), 0.7);
584
+ color: rgba(var(--primary-rgb, 27, 57, 51), 0.7);
584
585
  margin-top: 2px;
585
586
  font-weight: 400;
586
587
  }
@@ -601,7 +602,7 @@
601
602
  transition: background-color 0.2s ease;
602
603
 
603
604
  &:hover {
604
- background-color: rgba(var(--primary-rgb), 0.1);
605
+ background-color: rgba(var(--primary-rgb, 27, 57, 51), 0.1);
605
606
  }
606
607
 
607
608
  svg {
@@ -729,7 +730,7 @@
729
730
  width: auto;
730
731
  margin: 0;
731
732
  padding: 0 2em;
732
- border-right: 1px solid rgba(var(--primary-rgb), 0.35);
733
+ border-right: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.35);
733
734
 
734
735
  &:first-child {
735
736
  padding-left: 0;
@@ -741,7 +742,7 @@
741
742
 
742
743
  span {
743
744
  display: block;
744
- color: rgba(var(--primary-rgb), 0.7);
745
+ color: rgba(var(--primary-rgb, 27, 57, 51), 0.7);
745
746
  }
746
747
  }
747
748
  }
@@ -944,7 +945,7 @@
944
945
  .schedulingTable td {
945
946
  padding: 0.75rem;
946
947
  text-align: left;
947
- border: 1px solid rgba(var(--primary-rgb), 0.1);
948
+ border: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.1);
948
949
  }
949
950
 
950
951
  .schedulingTable th {
@@ -954,11 +955,11 @@
954
955
  }
955
956
 
956
957
  .schedulingTable td {
957
- background-color: rgba(var(--primary-rgb), 0.05);
958
+ background-color: rgba(var(--primary-rgb, 27, 57, 51), 0.05);
958
959
  }
959
960
 
960
961
  .categoryRow {
961
- background-color: rgba(var(--primary-rgb), 0.1);
962
+ background-color: rgba(var(--primary-rgb, 27, 57, 51), 0.1);
962
963
  text-align: left;
963
964
  font-weight: bold;
964
965
  font-size: 1.2rem;
@@ -966,7 +967,7 @@
966
967
  }
967
968
 
968
969
  .totalRow {
969
- background-color: rgba(var(--primary-rgb), 0.05);
970
+ background-color: rgba(var(--primary-rgb, 27, 57, 51), 0.05);
970
971
  font-weight: bold;
971
972
  text-align: right;
972
973
  }
@@ -976,7 +977,7 @@
976
977
  height: 14px !important;
977
978
  border-radius: 4px;
978
979
  background-color: var(--item-color);
979
- border: 2px solid rgba(var(--primary-rgb), 0.15);
980
+ border: 2px solid rgba(var(--primary-rgb, 27, 57, 51), 0.15);
980
981
  position: relative;
981
982
  cursor: pointer;
982
983
  transition: background-color 0.3s, border-color 0.3s;
@@ -989,13 +990,13 @@
989
990
 
990
991
  .schedulingTable td input[type='checkbox']:focus {
991
992
  outline: none;
992
- box-shadow: 0 0 0 2px rgba(var(--primary-rgb), 0.2);
993
+ box-shadow: 0 0 0 2px rgba(var(--primary-rgb, 27, 57, 51), 0.2);
993
994
  }
994
995
 
995
996
  /* Custom hover effect for better user experience */
996
997
  .schedulingTable td input[type='checkbox']:hover {
997
- background-color: rgba(var(--primary-rgb), 0.1);
998
- border-color: rgba(var(--primary-rgb), 0.25);
998
+ background-color: rgba(var(--primary-rgb, 27, 57, 51), 0.1);
999
+ border-color: rgba(var(--primary-rgb, 27, 57, 51), 0.25);
999
1000
  }
1000
1001
 
1001
1002
  .noDataMessage {
@@ -1015,15 +1016,15 @@
1015
1016
  /* Enhanced JSON Rendering Styles */
1016
1017
  .jsonQuestionAnswer {
1017
1018
  padding: 0.75rem;
1018
- background: rgba(var(--primary-rgb), 0.02);
1019
+ background: rgba(var(--primary-rgb, 27, 57, 51), 0.02);
1019
1020
  border-radius: 4px;
1020
- border: 1px solid rgba(var(--primary-rgb), 0.08);
1021
+ border: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.08);
1021
1022
  }
1022
1023
 
1023
1024
  .questionAnswerPair {
1024
1025
  margin-bottom: 1rem;
1025
1026
  padding: 0.75rem;
1026
- background: rgba(var(--primary-rgb), 0.01);
1027
+ background: rgba(var(--primary-rgb, 27, 57, 51), 0.01);
1027
1028
  border-radius: 4px;
1028
1029
  border-left: 3px solid var(--secondary-color);
1029
1030
 
@@ -1051,15 +1052,15 @@
1051
1052
 
1052
1053
  .jsonKeyValue {
1053
1054
  padding: 0.75rem;
1054
- background: rgba(var(--primary-rgb), 0.02);
1055
+ background: rgba(var(--primary-rgb, 27, 57, 51), 0.02);
1055
1056
  border-radius: 4px;
1056
- border: 1px solid rgba(var(--primary-rgb), 0.08);
1057
+ border: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.08);
1057
1058
  }
1058
1059
 
1059
1060
  .keyValuePair {
1060
1061
  margin-bottom: 0.6rem;
1061
1062
  padding: 0.5rem;
1062
- background: rgba(var(--primary-rgb), 0.01);
1063
+ background: rgba(var(--primary-rgb, 27, 57, 51), 0.01);
1063
1064
  border-radius: 3px;
1064
1065
  display: flex;
1065
1066
  gap: 0.5rem;
@@ -1077,8 +1078,8 @@
1077
1078
  }
1078
1079
 
1079
1080
  .jsonRaw {
1080
- background: rgba(var(--primary-rgb), 0.03);
1081
- border: 1px solid rgba(var(--primary-rgb), 0.1);
1081
+ background: rgba(var(--primary-rgb, 27, 57, 51), 0.03);
1082
+ border: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.1);
1082
1083
  border-radius: 4px;
1083
1084
  padding: 0.75rem;
1084
1085
  font-family: monospace;
@@ -1217,3 +1218,347 @@
1217
1218
  position: relative;
1218
1219
  }
1219
1220
 
1221
+
1222
+ /* ==========================================================================
1223
+ 2026 pass — the detail page
1224
+ --------------------------------------------------------------------------
1225
+ Appended, so these are targeted overrides of the rules above and the cascade
1226
+ order is doing real work.
1227
+
1228
+ The 6.15 overview rework (label-above-value pairs on a real two-column grid,
1229
+ a muted dash for an empty field) stands and is not touched. What is dated is
1230
+ everything around it: the title in its own white slab, `--br` corners at 5px
1231
+ when every other surface in the app is now 12-14px, no elevation at all, and
1232
+ a floating action bar that hangs over the page with no ground of its own.
1233
+ ========================================================================== */
1234
+
1235
+ .grid__subrow,
1236
+ .grid__subrow--stacked {
1237
+ --gd-surface: var(--background-color, #fff);
1238
+ --gd-ink: var(--paragraph-color, #2b2b2b);
1239
+ /* The three below defer to the app's own tokens first so a detail panel,
1240
+ an index card and the data grid all draw the SAME hairline, the same
1241
+ corner and the same lift. The local expressions stay as fallbacks for a
1242
+ host that declares none of them. */
1243
+ --gd-line: var(
1244
+ --border-color,
1245
+ color-mix(in srgb, var(--gd-ink) 12%, transparent)
1246
+ );
1247
+ --gd-muted: color-mix(in srgb, var(--gd-ink) 62%, transparent);
1248
+ --gd-quiet: color-mix(in srgb, var(--gd-ink) 3%, var(--gd-surface));
1249
+ --gd-radius: var(--detail-card-radius, var(--card-radius, 14px));
1250
+ --gd-motion: var(--speed, 160ms) var(--ease, cubic-bezier(0.4, 0, 0.2, 1));
1251
+ /* One contact shadow plus one ambient — a single large blur reads as a
1252
+ smudge rather than as a raised surface. */
1253
+ --gd-shadow: var(
1254
+ --shadow-sm,
1255
+ 0 1px 2px color-mix(in srgb, var(--primary-color, #1b3933) 8%, transparent),
1256
+ 0 8px 24px -12px color-mix(in srgb, var(--primary-color, #1b3933) 18%, transparent)
1257
+ );
1258
+
1259
+ align-items: flex-start;
1260
+ gap: var(--spacing-md, 1rem);
1261
+ }
1262
+
1263
+ /* -- the title ------------------------------------------------------------ */
1264
+
1265
+ /* Was a full-width white slab with a hard edge, so the page arrived as two
1266
+ stacked boxes before any content. The breadcrumb is type on the page ground
1267
+ now, and the panels below are the only containers. */
1268
+ .crmtitle,
1269
+ .crmtitle--alternate {
1270
+ padding: 0 0.25rem 0.85rem;
1271
+ background: none;
1272
+ border: 0;
1273
+ box-shadow: none;
1274
+ }
1275
+
1276
+ .titleInfo {
1277
+ color: var(--muted-color, #6b7688);
1278
+ font-size: 0.75rem;
1279
+ font-variant-numeric: tabular-nums;
1280
+ }
1281
+
1282
+ /* -- the two panels ------------------------------------------------------- */
1283
+
1284
+ .grid__subnav,
1285
+ .grid__subcontent {
1286
+ border: 1px solid var(--gd-line);
1287
+ border-radius: var(--gd-radius);
1288
+ background: var(--gd-surface);
1289
+ box-shadow: var(--gd-shadow);
1290
+ }
1291
+
1292
+ /* The rail is a list of destinations, not a panel of content: it gets the
1293
+ same surface but tighter padding so the tabs sit closer to their edge. */
1294
+ .grid__subnav {
1295
+ padding: 0.4rem;
1296
+ align-self: flex-start;
1297
+ }
1298
+
1299
+ .grid__subcontent {
1300
+ padding: var(--spacing-md, 1rem);
1301
+ min-width: 0;
1302
+ overflow: hidden;
1303
+ }
1304
+
1305
+ /* The underline tab strip is a strip, not a panel — giving it a card as well
1306
+ would box a row of text. */
1307
+ .grid__subnav--underline {
1308
+ padding: 0;
1309
+ border: 0;
1310
+ border-bottom: 1px solid var(--gd-line);
1311
+ border-radius: 0;
1312
+ background: none;
1313
+ box-shadow: none;
1314
+ }
1315
+
1316
+ /* -- section headings ----------------------------------------------------- */
1317
+
1318
+ .gridtxt__header {
1319
+ display: flex;
1320
+ align-items: baseline;
1321
+ justify-content: space-between;
1322
+ gap: 0.75rem;
1323
+ padding-bottom: 0.6rem;
1324
+ margin-bottom: 0.9rem;
1325
+ border-bottom: 1px solid var(--gd-line);
1326
+ color: var(--heading-color, var(--primary-color, #1b3933));
1327
+ font-size: 0.9rem;
1328
+ font-weight: 600;
1329
+ }
1330
+
1331
+ .gridtxt__meta {
1332
+ color: var(--gd-muted);
1333
+ font-size: 0.75rem;
1334
+ font-weight: 400;
1335
+ }
1336
+
1337
+ /* -- overview fields ------------------------------------------------------ */
1338
+
1339
+ .overviewLabel {
1340
+ color: var(--gd-muted);
1341
+ font-size: 0.6875rem;
1342
+ font-weight: 600;
1343
+ letter-spacing: 0.05em;
1344
+ text-transform: uppercase;
1345
+ }
1346
+
1347
+ .overviewValue {
1348
+ color: var(--gd-ink);
1349
+ font-size: 0.875rem;
1350
+ line-height: 1.5;
1351
+ /* A value can be an address or a long note; it must wrap rather than push
1352
+ the column wider than its track. */
1353
+ overflow-wrap: anywhere;
1354
+ }
1355
+
1356
+ .overviewEmpty {
1357
+ color: var(--gd-muted);
1358
+ }
1359
+
1360
+ /* -- the floating action bar ---------------------------------------------- */
1361
+
1362
+ /* It stays pinned bottom-right — every detail page in every project is laid
1363
+ out around that — but it had no ground of its own, so on a short page the
1364
+ buttons floated over the page background with nothing behind them. A pill
1365
+ with the same elevation as the panels gives them somewhere to sit. */
1366
+ .polActions {
1367
+ padding: 0.4rem;
1368
+ border: 1px solid var(--gd-line);
1369
+ border-radius: var(--radius-pill, 999px);
1370
+ background: color-mix(in srgb, var(--gd-surface) 92%, transparent);
1371
+ backdrop-filter: blur(8px);
1372
+ box-shadow:
1373
+ 0 2px 4px color-mix(in srgb, var(--primary-color, #1b3933) 10%, transparent),
1374
+ 0 16px 32px -14px color-mix(in srgb, var(--primary-color, #1b3933) 30%, transparent);
1375
+ }
1376
+
1377
+ /* -- states --------------------------------------------------------------- */
1378
+
1379
+ .noDataMessage {
1380
+ display: flex;
1381
+ align-items: center;
1382
+ justify-content: center;
1383
+ min-height: 6rem;
1384
+ color: var(--gd-muted);
1385
+ font-size: 0.875rem;
1386
+ }
1387
+
1388
+ /* A nested grid already carries its own card from DataGrid's 2026 pass;
1389
+ double-carding it puts a panel inside a panel. */
1390
+ .grid__subcontent [class*='dataGridContainer'] {
1391
+ border: 0;
1392
+ border-radius: 0;
1393
+ box-shadow: none;
1394
+ }
1395
+
1396
+ @media (max-width: 900px) {
1397
+ .grid__subrow,
1398
+ .grid__subrow--stacked {
1399
+ flex-direction: column;
1400
+ }
1401
+
1402
+ .grid__subnav {
1403
+ width: 100%;
1404
+ }
1405
+ }
1406
+
1407
+ /* ============================================================================
1408
+ * 2026 pass — the rail column
1409
+ * ----------------------------------------------------------------------------
1410
+ * The same two structural faults GenericIndex had, and they are worse on a
1411
+ * detail page than on an index:
1412
+ *
1413
+ * 1. The row was flex, with the rail on `flex: 1` and the panel on
1414
+ * `flex: 0 1 80%`. Those two fight over the leftover space, so the rail's
1415
+ * width moved with the content beside it — a tab list that changes width
1416
+ * when you switch tabs.
1417
+ * 2. The rail did not stick. A detail page is the LONGEST page in the app —
1418
+ * a form, its tables, its notes — so scrolling to the bottom of one tab
1419
+ * left no way to reach another without scrolling all the way back.
1420
+ *
1421
+ * The card stays, unlike on the index. There the grid drew its own card and the
1422
+ * rail's was a second one nested around it; here the content panel is a real
1423
+ * panel holding a form, so a card rail beside a card panel is consistent
1424
+ * rather than doubled.
1425
+ *
1426
+ * Tokens shared with GenericIndex (`--index-rail-*`) on purpose: the two pages
1427
+ * share one rail, and a project retuning it should not have to find two names.
1428
+ * ==========================================================================*/
1429
+
1430
+ .grid__subrow {
1431
+ display: grid;
1432
+ grid-template-columns:
1433
+ minmax(var(--index-rail-min, 190px), var(--index-rail-width, 224px))
1434
+ minmax(0, 1fr);
1435
+ align-items: start;
1436
+ }
1437
+
1438
+ .grid__subnav {
1439
+ position: sticky;
1440
+ top: var(--index-rail-top, 88px);
1441
+ max-height: calc(100vh - var(--index-rail-offset, 120px));
1442
+ overflow-y: auto;
1443
+ overscroll-behavior: contain;
1444
+ scrollbar-width: thin;
1445
+
1446
+ /* No card, matching GenericIndex.
1447
+
1448
+ This kept its card at first, on the reasoning that a card rail beside a
1449
+ card content panel is consistent. In use it is not: the rail is 250px of
1450
+ content next to a panel ten times its height, so the card ENDS and
1451
+ leaves a bordered box floating above a large empty column — the same
1452
+ fault the index had. And the two pages sit next to each other in one
1453
+ app, so a bordered rail on one and a borderless rail on the other reads
1454
+ as a bug rather than as a distinction. */
1455
+ background: none;
1456
+ border: none;
1457
+ box-shadow: none;
1458
+ padding: 0;
1459
+ }
1460
+
1461
+ /* The rail items need the same accent-edge gutter the index gives them, or the
1462
+ 3px edge on the selected item pushes its label sideways. */
1463
+ .grid__subnav > ul li a {
1464
+ position: relative;
1465
+ padding-left: calc(var(--spacing-md, 1rem) - 3px);
1466
+ border-left: 3px solid transparent;
1467
+ border-radius: var(--radius-sm, 6px);
1468
+ color: var(--paragraph-color, #52606d);
1469
+ }
1470
+
1471
+ .grid__subnav > ul li:hover a {
1472
+ background: color-mix(
1473
+ in srgb,
1474
+ var(--primary-color, #1b3933) 6%,
1475
+ transparent
1476
+ );
1477
+ color: var(--heading-color, #111827);
1478
+ }
1479
+
1480
+ /* The stacked layout puts the tabs ABOVE the panel, so the row is one column
1481
+ and nothing sticks — a strip pinned over the content it labels would cover
1482
+ the first field of every form.
1483
+
1484
+ `grid-template-columns: none` undoes the two-column track the rail layout
1485
+ sets above; without it the strip and the panel would each be handed a
1486
+ column and the panel would keep the rail's 224px hole beside it. */
1487
+ .grid__subrow--stacked {
1488
+ display: flex;
1489
+ grid-template-columns: none;
1490
+ }
1491
+
1492
+ .grid__subrow--stacked .grid__subnav {
1493
+ position: static;
1494
+ max-height: none;
1495
+ overflow: visible;
1496
+ }
1497
+
1498
+ /* Both children run the full width. The panel's own `flex: 0 1 80%` was
1499
+ written for a ROW — in a column it is a height basis, which is nonsense
1500
+ here — and its 80% was the rail's column tax. That column is reclaimed:
1501
+ this is the whole point of the layout. */
1502
+ .grid__subrow--stacked > .grid__subnav,
1503
+ .grid__subrow--stacked > .grid__subcontent {
1504
+ flex: 0 0 auto;
1505
+ width: 100%;
1506
+ min-width: 0;
1507
+ }
1508
+
1509
+ /* The strip is separated from the panel by the panel's own margin, so the
1510
+ strip's is zeroed above and the gap is stated once, here. A tab row butted
1511
+ straight onto the card below it reads as a header of that card rather than
1512
+ as a control above it. */
1513
+ .grid__subrow--stacked > .grid__subcontent {
1514
+ margin: var(--spacing-md, 1rem) 0 0;
1515
+ }
1516
+
1517
+ @media (max-width: 759px) {
1518
+ .grid__subrow {
1519
+ grid-template-columns: minmax(0, 1fr);
1520
+ }
1521
+
1522
+ .grid__subnav {
1523
+ position: static;
1524
+ max-height: none;
1525
+ overflow: visible;
1526
+ }
1527
+ }
1528
+
1529
+ /* ============================================================================
1530
+ * The grid card — one card, not two
1531
+ * ----------------------------------------------------------------------------
1532
+ * The same repair as the block at the end of GenericIndex.module.scss, for the
1533
+ * grids a detail page renders in a `.grid__full`. See that file for the full
1534
+ * reasoning; in short, `.grid__full` is the only element wrapping both the
1535
+ * toolbar and the table, so it is the card, and the grid's own container gives
1536
+ * up its border, radius and shadow to sit flush inside it.
1537
+ *
1538
+ * `[class*='dataGridContainer']` matches DataGrid.module.scss's hashed local
1539
+ * name, which this file cannot spell directly. It does NOT match
1540
+ * `dataGridTopContainer` or `dataGridBottomContainer` — neither contains that
1541
+ * substring.
1542
+ * ==========================================================================*/
1543
+
1544
+ .grid__full:has([class*='dataGridContainer']) {
1545
+ /* Selects, row-action popovers and the pager dropdown open out of the card. */
1546
+ overflow: visible;
1547
+
1548
+ /* Descendant, not `>`: the toolbar and the grid share an unclassed wrapper
1549
+ div, so the card's only direct child is that wrapper. And the radius is
1550
+ spelled out rather than `inherit`, because inheriting would take the
1551
+ wrapper's 0. See the matching block in GenericIndex.module.scss. */
1552
+ [class*='dataGridContainer'] {
1553
+ border: 0;
1554
+ box-shadow: none;
1555
+ border-radius: calc(var(--card-radius, 10px) - 1px);
1556
+ overflow: hidden;
1557
+ }
1558
+
1559
+ [class*='dataGridTopContainer'] {
1560
+ border-top-left-radius: calc(var(--card-radius, 10px) - 1px);
1561
+ border-top-right-radius: calc(var(--card-radius, 10px) - 1px);
1562
+ border-bottom-color: var(--border-color, #e1e1e1);
1563
+ }
1564
+ }