@visns-studio/visns-components 6.17.2 → 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 (83) 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 +39 -23
  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/settings/IntegrationsManager.jsx +474 -0
  29. package/src/components/sms/SmsLineSettings.jsx +16 -12
  30. package/src/components/styles/AssociationManager.module.scss +4 -363
  31. package/src/components/styles/AuditLog.module.scss +6 -6
  32. package/src/components/styles/AuditLogs.module.scss +3 -3
  33. package/src/components/styles/Autocomplete.module.scss +7 -7
  34. package/src/components/styles/BusinessCardOcr.module.scss +6 -1
  35. package/src/components/styles/CallQueueSettings.module.scss +4 -1
  36. package/src/components/styles/CameraPlacement.module.scss +19 -19
  37. package/src/components/styles/CategorizedDropZone.module.scss +6 -1
  38. package/src/components/styles/ClientAssociationManager.module.scss +5 -289
  39. package/src/components/styles/ClientAuth.module.scss +1 -1
  40. package/src/components/styles/DataGrid.module.scss +455 -8
  41. package/src/components/styles/Field.module.scss +32 -24
  42. package/src/components/styles/Form.module.scss +123 -3
  43. package/src/components/styles/GenericDashboard.module.scss +612 -825
  44. package/src/components/styles/GenericDetail.module.scss +406 -61
  45. package/src/components/styles/GenericDynamic.module.scss +25 -25
  46. package/src/components/styles/GenericEditableTable.module.scss +33 -33
  47. package/src/components/styles/GenericFormBuilder.module.scss +119 -119
  48. package/src/components/styles/GenericIndex.module.scss +483 -38
  49. package/src/components/styles/GenericMain.module.scss +6 -3
  50. package/src/components/styles/GenericQuote.module.scss +399 -81
  51. package/src/components/styles/GenericReport.module.scss +8 -8
  52. package/src/components/styles/GenericSettings.module.scss +209 -0
  53. package/src/components/styles/GenericSort.module.scss +9 -9
  54. package/src/components/styles/Integrations.module.scss +321 -0
  55. package/src/components/styles/List.module.scss +4 -4
  56. package/src/components/styles/Login.module.scss +57 -0
  57. package/src/components/styles/MultiSelect.module.scss +4 -4
  58. package/src/components/styles/Navigation.module.scss +12 -12
  59. package/src/components/styles/Notification.module.scss +12 -8
  60. package/src/components/styles/NotificationList.module.scss +5 -5
  61. package/src/components/styles/Profile.module.scss +5 -5
  62. package/src/components/styles/QrCode.module.scss +4 -4
  63. package/src/components/styles/QuickAction.module.scss +20 -20
  64. package/src/components/styles/SortableQuoteItems.module.scss +4 -4
  65. package/src/components/styles/TableFilter.module.scss +128 -17
  66. package/src/components/styles/TicketConversation.module.scss +497 -0
  67. package/src/components/styles/UniversalSearch.module.scss +229 -0
  68. package/src/components/styles/Vault.module.scss +413 -0
  69. package/src/components/styles/_associationManager.scss +467 -0
  70. package/src/components/styles/_authBrandPanel.scss +15 -9
  71. package/src/components/styles/_controls.scss +43 -39
  72. package/src/components/styles/global-datagrid.css +196 -0
  73. package/src/components/styles/global.css +168 -17
  74. package/src/components/tickets/TicketConversation.jsx +455 -0
  75. package/src/components/utils/rowActionSettings.js +117 -0
  76. package/src/components/vault/VaultClientPicker.jsx +183 -0
  77. package/src/components/vault/VaultEntryForm.jsx +23 -0
  78. package/src/components/vault/VaultManager.jsx +382 -34
  79. package/src/components/vault/VaultShareModal.jsx +454 -0
  80. package/src/components/vault/vaultEndpoints.js +16 -2
  81. package/src/components/vault/vaultShares.js +247 -0
  82. package/src/index.js +35 -0
  83. package/src/utils/fetchUtil.js +38 -14
@@ -54,56 +54,69 @@
54
54
  gap: 1rem;
55
55
  }
56
56
 
57
- .grid__three {
58
- width: 33%;
59
- background: var(--item-color);
60
- border-radius: var(--br);
61
- border: 1px solid rgba(var(--item-color-rgb), 1.15);
57
+ /* ----------------------------------------------------------------------------
58
+ The card set.
59
+
60
+ One surface, one radius, one hairline, one elevation. Before this the four
61
+ cards on the same page disagreed on all four: two radii (`--br` and a
62
+ literal 6px), two borders (a WHITE `rgba(--item-color-rgb, 1)` — i.e. no
63
+ visible edge at all — and a hardcoded `#dadce0`), and three shadows
64
+ (`0 10px 50px`, which at 5% over a near-white ground is a smudge nobody can
65
+ see, and a `0 4px 0` hard offset that reads as a 90s button).
66
+
67
+ The border is what does the work now that the page ground is the warm stone
68
+ rather than a near-white: a card needs one edge, not a 50px blur.
69
+ `--shadow-sm` is the single elevation token and is deliberately quiet — the
70
+ contrast between white and the ground carries the lift.
71
+
72
+ `overflow: visible` is load-bearing on every one of these: the filter
73
+ selects, the row-action popovers and the contact tooltips all render inside
74
+ a card and have to escape it. Clipping happens one level in, on the grid's
75
+ own container, where nothing needs to overflow.
76
+ -------------------------------------------------------------------------- */
77
+
78
+ @mixin card-surface {
79
+ background: var(--item-color, #fff);
80
+ border: 1px solid var(--border-color, #e1e1e1);
81
+ border-radius: var(--card-radius, 10px);
82
+ box-shadow: var(
83
+ --shadow-sm,
84
+ 0 1px 2px rgba(var(--primary-rgb, 27, 57, 51), 0.08),
85
+ 0 2px 8px rgba(var(--primary-rgb, 27, 57, 51), 0.06)
86
+ );
62
87
  box-sizing: border-box;
63
88
  overflow: visible;
64
89
  position: relative;
65
- padding: 20px;
66
90
  margin: 8px 0;
67
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
91
+ }
92
+
93
+ .grid__three {
94
+ @include card-surface;
95
+ width: 33%;
96
+ padding: 20px;
68
97
  }
69
98
 
70
99
  .grid__half {
100
+ @include card-surface;
71
101
  width: 50%;
72
- background: var(--item-color);
73
- border-radius: 6px;
74
- border: 1px solid #dadce0;
75
- box-sizing: border-box;
76
- overflow: visible;
77
- position: relative;
78
102
  padding: 20px;
79
- margin: 8px 0;
80
- box-shadow: 0 4px 0 rgba(var(--primary-color), 0.05);
81
103
  }
82
104
 
83
105
  .grid__full {
106
+ @include card-surface;
84
107
  width: 100%;
85
- background: var(--item-color);
86
- border-radius: var(--br);
87
- border: 1px solid rgba(var(--item-color-rgb), 1.15);
88
- box-sizing: border-box;
89
- overflow: visible;
90
- position: relative;
91
- padding: 2px;
92
- margin: 8px 0;
93
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
108
+ /* Was 2px. Two pixels of card showing between the card's own border and
109
+ whatever it wraps is the "bit of padding" that made the grid look like
110
+ it did not line up with its own corners — the outer radius curving
111
+ around a square inner edge three pixels away. A card whose only child is
112
+ a full-bleed surface has no padding of its own; the child brings its. */
113
+ padding: 0;
94
114
  }
95
115
 
96
116
  .grid__dashfull {
117
+ @include card-surface;
97
118
  width: 100%;
98
- background: var(--item-color);
99
- border-radius: 6px;
100
- border: 1px solid #dadce0;
101
- box-sizing: border-box;
102
- overflow: visible;
103
- position: relative;
104
119
  padding: 20px;
105
- margin: 8px 0;
106
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
107
120
  }
108
121
 
109
122
  .grid__subrow {
@@ -196,14 +209,14 @@
196
209
 
197
210
  .grid__subnav__alternate {
198
211
  flex: 1;
199
- background: rgba(var(--item-color-rgb), 0.05);
212
+ background: rgba(var(--item-color-rgb, 255, 255, 255), 0.05);
200
213
  border-radius: var(--br);
201
- border: 1px solid rgba(var(--item-color-rgb), 1.15);
214
+ border: 1px solid rgba(var(--item-color-rgb, 255, 255, 255), 1);
202
215
  box-sizing: border-box;
203
216
  padding: 5px;
204
217
  margin: 8px 0;
205
218
  height: auto;
206
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
219
+ box-shadow: 0 10px 50px rgba(var(--primary-rgb, 27, 57, 51), 0.05);
207
220
  }
208
221
 
209
222
  .grid__subnav__alternate > ul {
@@ -226,7 +239,7 @@
226
239
  box-sizing: border-box;
227
240
  display: block;
228
241
  text-decoration: none;
229
- background: rgba(var(--item-color-rgb), 1.05);
242
+ background: rgba(var(--item-color-rgb, 255, 255, 255), 1);
230
243
  color: var(--secondary-color);
231
244
  border-radius: 5px;
232
245
  transition: background 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
@@ -257,13 +270,13 @@
257
270
  flex: 0 1 80%;
258
271
  background: var(--item-color);
259
272
  border-radius: var(--br);
260
- border: 1px solid rgba(var(--item-color-rgb), 1.15);
273
+ border: 1px solid rgba(var(--item-color-rgb, 255, 255, 255), 1);
261
274
  box-sizing: border-box;
262
275
  padding: 2px;
263
276
  margin: 8px 0;
264
277
  height: auto;
265
278
  position: relative;
266
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
279
+ box-shadow: 0 10px 50px rgba(var(--primary-rgb, 27, 57, 51), 0.05);
267
280
  }
268
281
 
269
282
  .grid__subcontent h2 {
@@ -1205,3 +1218,435 @@
1205
1218
  width: 100%;
1206
1219
  position: relative;
1207
1220
  }
1221
+
1222
+ // ============================================================================
1223
+ // 2026 pass — the index page frame
1224
+ // ----------------------------------------------------------------------------
1225
+ // The page title bar and the filter rail around the grid. Appended for the
1226
+ // same reason as DataGrid's pass: these are targeted overrides of rules above
1227
+ // and the cascade order is load-bearing.
1228
+ // ============================================================================
1229
+
1230
+ .crmtitle,
1231
+ .crmtitleSimple {
1232
+ display: flex;
1233
+ align-items: center;
1234
+ justify-content: space-between;
1235
+ gap: 1rem;
1236
+ // The title sat in its own white slab with a hard edge, which made the page
1237
+ // read as two stacked boxes. It is type on the page ground now, and the
1238
+ // grid card below is the only container.
1239
+ padding: 0 0.25rem 0.85rem;
1240
+ background: none;
1241
+ border: 0;
1242
+ }
1243
+
1244
+ .crmtitle h1,
1245
+ .crmtitleSimple h1,
1246
+ .crmtitle h2,
1247
+ .crmtitleSimple h2 {
1248
+ margin: 0;
1249
+ font-size: 1.375rem;
1250
+ font-weight: 700;
1251
+ letter-spacing: -0.01em;
1252
+ color: var(--heading-color, var(--primary-color, #1b3933));
1253
+ }
1254
+
1255
+ // -- the filter rail ---------------------------------------------------------
1256
+ // Tabs on the left rail were solid brand-green blocks with square corners, one
1257
+ // per filter — a column of buttons shouting at the same volume as the data.
1258
+
1259
+ .grid__subnav,
1260
+ .grid__subnav__alternate {
1261
+ display: flex;
1262
+ flex-direction: column;
1263
+ gap: 0.15rem;
1264
+ padding: 0.4rem;
1265
+ border: 1px solid color-mix(in srgb, var(--paragraph-color, #2b2b2b) 12%, transparent);
1266
+ border-radius: 12px;
1267
+ background: var(--background-color, #fff);
1268
+ box-shadow: 0 1px 2px color-mix(in srgb, var(--primary-color, #1b3933) 6%, transparent);
1269
+ }
1270
+
1271
+ .grid__subnav > *,
1272
+ .grid__subnav__alternate > * {
1273
+ display: flex;
1274
+ align-items: center;
1275
+ padding: 0.5rem 0.7rem;
1276
+ border: 0;
1277
+ border-radius: var(--radius-sm, 8px);
1278
+ background: none;
1279
+ color: var(--muted-color, #6b7688);
1280
+ font-size: 0.8125rem;
1281
+ font-weight: 500;
1282
+ text-align: left;
1283
+ text-decoration: none;
1284
+ cursor: pointer;
1285
+ transition:
1286
+ background var(--speed, 160ms) var(--ease, cubic-bezier(0.4, 0, 0.2, 1)),
1287
+ color var(--speed, 160ms) var(--ease, cubic-bezier(0.4, 0, 0.2, 1));
1288
+ }
1289
+
1290
+ .grid__subnav > *:hover,
1291
+ .grid__subnav__alternate > *:hover {
1292
+ background: color-mix(in srgb, var(--primary-color, #1b3933) 5%, transparent);
1293
+ color: var(--primary-color, #1b3933);
1294
+ }
1295
+
1296
+ // NOTE: the filter tabs themselves are TableFilter's, wrapped in a container
1297
+ // inside this rail — so their selected state is styled in
1298
+ // TableFilter.module.scss (`.subactive`), not from here. A `.grid__subnav > *`
1299
+ // rule reaches the wrapper and nothing else.
1300
+
1301
+ // -- states ------------------------------------------------------------------
1302
+
1303
+ .noData,
1304
+ .loadingContainer {
1305
+ display: flex;
1306
+ align-items: center;
1307
+ justify-content: center;
1308
+ min-height: 8rem;
1309
+ color: var(--muted-color, #6b7688);
1310
+ font-size: 0.875rem;
1311
+ }
1312
+
1313
+ .loadingSpinner {
1314
+ width: 20px;
1315
+ height: 20px;
1316
+ border: 2px solid color-mix(in srgb, var(--paragraph-color, #2b2b2b) 14%, transparent);
1317
+ border-top-color: var(--primary-color, #1b3933);
1318
+ border-radius: 50%;
1319
+ animation: genericIndexSpin 0.7s linear infinite;
1320
+ }
1321
+
1322
+ @keyframes genericIndexSpin {
1323
+ to { transform: rotate(360deg); }
1324
+ }
1325
+
1326
+ @media (prefers-reduced-motion: reduce) {
1327
+ .loadingSpinner { animation-duration: 2s; }
1328
+ }
1329
+
1330
+ /* ============================================================================
1331
+ * 2026 pass — the rail / content split
1332
+ * ----------------------------------------------------------------------------
1333
+ * Three measured faults, not a matter of taste. On /settings at 1440x1500:
1334
+ * the rail card was 241px tall next to a 1264px content pane, so ~1020px of
1335
+ * the left column was a dead L-shaped void; the content pane drew a bordered,
1336
+ * shadowed card around a grid that already draws its own, so every index page
1337
+ * wore two card edges; and the selected rail item was a 10%-opacity tint with
1338
+ * no accent, which at a glance did not read as selected at all.
1339
+ *
1340
+ * The pattern itself stays — a vertical rail beside content is still right in
1341
+ * 2026, and it is the only one of the three that scales as sections are added
1342
+ * (horizontal tabs stop working somewhere around seven). What changes is that
1343
+ * the rail becomes a COLUMN rather than a card: it has no edge of its own to
1344
+ * terminate, so there is no void under it, and it sticks while the content
1345
+ * scrolls past.
1346
+ *
1347
+ * Appended rather than folded in, so the cascade order is doing the
1348
+ * overriding and the original rules stay readable. Every token carries a
1349
+ * literal fallback — a bare var() for a token the host has not declared is
1350
+ * invalid at computed-value time and takes the whole declaration with it.
1351
+ * ==========================================================================*/
1352
+
1353
+ .grid__subrow {
1354
+ /* Grid, not flex: the rail column gets a real, stable width instead of
1355
+ `flex: 1` fighting `flex: 0 1 80%` for it, and `start` alignment stops
1356
+ the two columns forcing each other to equal height — which is what
1357
+ created the void in the first place. */
1358
+ display: grid;
1359
+ grid-template-columns:
1360
+ minmax(var(--index-rail-min, 190px), var(--index-rail-width, 224px))
1361
+ minmax(0, 1fr);
1362
+ align-items: start;
1363
+ gap: var(--index-rail-gap, 1.25rem);
1364
+ }
1365
+
1366
+ .grid__subnav,
1367
+ .grid__subnav__alternate {
1368
+ /* No card. The rail is part of the page ground, so it has no bottom edge
1369
+ to leave a thousand pixels of nothing beneath. */
1370
+ background: none;
1371
+ border: none;
1372
+ box-shadow: none;
1373
+ padding: 0;
1374
+ margin: 8px 0;
1375
+
1376
+ /* It follows the reader instead of scrolling away. The offset is the app
1377
+ chrome above it; a host with different chrome retunes one token. */
1378
+ position: sticky;
1379
+ top: var(--index-rail-top, 88px);
1380
+
1381
+ /* A rail longer than the window scrolls itself, never the page. */
1382
+ max-height: calc(100vh - var(--index-rail-offset, 120px));
1383
+ overflow-y: auto;
1384
+ overscroll-behavior: contain;
1385
+ scrollbar-width: thin;
1386
+ }
1387
+
1388
+ .grid__subnav > ul li a,
1389
+ .grid__subnav__alternate > ul li a {
1390
+ /* Room for the accent edge below, so selecting an item does not shift its
1391
+ label sideways by 3px. */
1392
+ position: relative;
1393
+ padding-left: calc(var(--spacing-md, 1rem) - 3px);
1394
+ border-left: 3px solid transparent;
1395
+ border-radius: var(--radius-sm, 6px);
1396
+ color: var(--paragraph-color, #52606d);
1397
+ }
1398
+
1399
+ .grid__subnav > ul li:hover a,
1400
+ .grid__subnav__alternate > ul li:hover a {
1401
+ background: color-mix(
1402
+ in srgb,
1403
+ var(--primary-color, #1b3933) 6%,
1404
+ transparent
1405
+ );
1406
+ color: var(--heading-color, #111827);
1407
+ }
1408
+
1409
+ /* The SELECTED item is not styled here, and cannot be: `.subactive` is
1410
+ declared in TableFilter.module.scss, so a `.subactive` written in this file
1411
+ compiles to a different hashed class name and matches nothing. The accent
1412
+ edge lives with the class it belongs to, in TableFilter. The transparent
1413
+ 3px border above is what reserves the space for it, so selecting an item
1414
+ does not shift its label. */
1415
+
1416
+ .grid__subcontent {
1417
+ /* The grid inside already draws a bordered, shadowed, rounded card. This
1418
+ drew a second one around it. */
1419
+ background: none;
1420
+ border: none;
1421
+ box-shadow: none;
1422
+ padding: 0;
1423
+ margin: 8px 0;
1424
+ min-width: 0;
1425
+ }
1426
+
1427
+ /* Narrow viewports: one column. TableFilter already turns the rail into a
1428
+ horizontal underline strip below 760px, so the two only have to agree on
1429
+ where that happens. */
1430
+ @media (max-width: 759px) {
1431
+ .grid__subrow {
1432
+ grid-template-columns: minmax(0, 1fr);
1433
+ gap: var(--spacing-sm, 0.5rem);
1434
+ }
1435
+
1436
+ .grid__subnav,
1437
+ .grid__subnav__alternate {
1438
+ /* A strip that sticks would cover the rows it is filtering. */
1439
+ position: static;
1440
+ max-height: none;
1441
+ overflow: visible;
1442
+ }
1443
+ }
1444
+
1445
+ /* ============================================================================
1446
+ * Embedded mode — an index rendered inside another page's panel
1447
+ * ----------------------------------------------------------------------------
1448
+ * See the block comment on `GenericIndex` for what asks for this. Two shapes:
1449
+ *
1450
+ * - `tabLayout="underline"`: the filter menu becomes a horizontal strip
1451
+ * ABOVE the grid instead of a rail beside it. The strip itself is
1452
+ * TableFilter's (`variant="underline"`); everything here is the container
1453
+ * around it, which has to undo the rail's card, its sticky positioning and
1454
+ * its scroller — a strip is none of those things.
1455
+ * - `showTitle={false}`: no page-title bar, so the record count is rendered
1456
+ * against the grid instead. It sits at the right-hand end of the strip
1457
+ * when there is one, and in `.embeddedInfoRow` when there is not.
1458
+ *
1459
+ * Appended, like the passes above it: these are targeted overrides of rules
1460
+ * earlier in this file and the cascade order is what does the overriding.
1461
+ * ==========================================================================*/
1462
+
1463
+ /* The rail layout is a two-column grid (`.grid__subrow` above). Stacked, it is
1464
+ one column: `grid-template-columns: none` is what gives the reclaimed rail
1465
+ width back to the grid rather than leaving a 224px hole beside it. */
1466
+ .grid__subrow--stacked {
1467
+ display: flex;
1468
+ flex-direction: column;
1469
+ flex-wrap: nowrap;
1470
+ grid-template-columns: none;
1471
+ align-items: stretch;
1472
+ gap: 0;
1473
+ }
1474
+
1475
+ .grid__subrow--stacked > .grid__subnav,
1476
+ .grid__subrow--stacked > .grid__subcontent {
1477
+ flex: 0 0 auto;
1478
+ width: 100%;
1479
+ /* Without this the strip's min-content width floors the flex item, so a
1480
+ row of ten tabs widens the page instead of scrolling inside it. */
1481
+ min-width: 0;
1482
+ }
1483
+
1484
+ /* The strip is separated from the grid card by one gap, stated once here —
1485
+ `.grid__subnav--underline` zeroes its own margin. A tab row butted straight
1486
+ onto the card below reads as that card's header rather than as a control
1487
+ above it. */
1488
+ .grid__subrow--stacked > .grid__subcontent {
1489
+ margin: var(--spacing-md, 1rem) 0 0;
1490
+ }
1491
+
1492
+ .grid__subnav--underline {
1493
+ /* Not a card, not sticky, not a scroller. */
1494
+ position: static;
1495
+ max-height: none;
1496
+ overflow: visible;
1497
+ overscroll-behavior: auto;
1498
+ padding: 0;
1499
+ margin: 0;
1500
+ background: none;
1501
+ border: none;
1502
+ border-radius: 0;
1503
+ box-shadow: none;
1504
+ /* The one hairline under the whole strip; the selected tab's 3px accent
1505
+ edge sits on top of it. */
1506
+ border-bottom: 1px solid
1507
+ color-mix(in srgb, var(--paragraph-color, #2b2b2b) 12%, transparent);
1508
+
1509
+ /* A row, so the record count can sit at the far end of it rather than
1510
+ costing a line of its own. `flex-end` keeps the count on the strip's
1511
+ baseline instead of floating above the tabs. */
1512
+ display: flex;
1513
+ flex-direction: row;
1514
+ align-items: flex-end;
1515
+ gap: var(--spacing-md, 1rem);
1516
+ }
1517
+
1518
+ /* `.grid__subnav > *` above pads, tints and FLEXES the TableFilter wrapper for
1519
+ the rail. In a strip that padding indents the first tab off the page edge,
1520
+ the hover tint lights up the whole row, and the flex is the one that
1521
+ actually breaks it: as a flex container the tab list floors at its own
1522
+ min-content width, so a row wider than the panel widens the page instead of
1523
+ scrolling inside itself. Back to a block, which is what TableFilter's own
1524
+ stylesheet expects around the scroller. */
1525
+ .grid__subnav--underline > * {
1526
+ display: block;
1527
+ min-width: 0;
1528
+ padding: 0;
1529
+ background: none;
1530
+ }
1531
+
1532
+ .grid__subnav--underline > *:hover {
1533
+ background: none;
1534
+ }
1535
+
1536
+ /* The strip takes the room; the count takes what it needs. */
1537
+ .grid__subnav--underline > *:first-child {
1538
+ flex: 1 1 auto;
1539
+ min-width: 0;
1540
+ }
1541
+
1542
+ .grid__subnav--underline > .titleInfo {
1543
+ flex: 0 0 auto;
1544
+ /* Clear of the hairline, on the same optical line as the tab labels. */
1545
+ padding-bottom: 0.55rem;
1546
+ white-space: nowrap;
1547
+ }
1548
+
1549
+ /* No strip to sit in: the count gets its own row, right-aligned above the
1550
+ grid card and matched to the card's own horizontal inset. */
1551
+ .embeddedInfoRow {
1552
+ display: flex;
1553
+ justify-content: flex-end;
1554
+ padding: 0 0.25rem 0.5rem;
1555
+ }
1556
+
1557
+ .grid__subnav--underline > .titleInfo,
1558
+ .embeddedInfoRow > .titleInfo {
1559
+ /* The `.titleInfo` rule inside `.crmtitle` positions it for the title bar;
1560
+ neither of these is the title bar. */
1561
+ position: static;
1562
+ display: flex;
1563
+ align-items: baseline;
1564
+ gap: 0.75rem;
1565
+ width: max-content;
1566
+ }
1567
+
1568
+ .grid__subnav--underline > .titleInfo span,
1569
+ .embeddedInfoRow > .titleInfo span {
1570
+ font-size: 0.8125rem;
1571
+ color: var(--muted-color, #6b7688);
1572
+ }
1573
+
1574
+ .grid__subnav--underline > .titleInfo strong,
1575
+ .embeddedInfoRow > .titleInfo strong {
1576
+ color: var(--heading-color, var(--primary-color, #1b3933));
1577
+ font-weight: 600;
1578
+ }
1579
+
1580
+ /* Narrow viewports need nothing of their own here. The rail's phone rules sit
1581
+ above this block, so a stacked row's own `gap: 0` and single column already
1582
+ win on source order — and the strip inside is the same one TableFilter shows
1583
+ on a phone anyway. */
1584
+
1585
+ /* ============================================================================
1586
+ * The grid card — one card, not two
1587
+ * ----------------------------------------------------------------------------
1588
+ * `GenericIndex` renders the data grid as
1589
+ *
1590
+ * .grid__full <- a card: white, bordered, rounded, lifted
1591
+ * .dataGridTopContainer <- the search / actions toolbar (a SIBLING)
1592
+ * .dataGridContainer <- ANOTHER card: bordered, rounded 14px,
1593
+ * shadowed, overflow hidden
1594
+ *
1595
+ * so the page drew a card inside a card, three pixels apart, with two
1596
+ * different corner radii, and put the toolbar in the gap between them — which
1597
+ * is exactly the "rounded corners but the white background ruins it, doesn't
1598
+ * line up, has a bit of padding" the owner is looking at. On a near-white page
1599
+ * ground the two whites were indistinguishable, so all you saw was a rounded
1600
+ * outline that didn't follow anything.
1601
+ *
1602
+ * The outer element is the only one that wraps BOTH the toolbar and the table,
1603
+ * so it is the one that gets to be the card. The inner container gives up its
1604
+ * border, its radius and its shadow and becomes a plain surface flush inside
1605
+ * it: search row, column header, rows and pager all on one white card, one
1606
+ * hairline around the lot, corners that clip what is actually at them.
1607
+ *
1608
+ * Scoped by attribute rather than by `:has()` on a module class, because
1609
+ * `.dataGridContainer` is DataGrid.module.scss's local name and hashes to
1610
+ * something this file cannot spell. `[class*='dataGridContainer']` matches the
1611
+ * generated `_dataGridContainer_hwl8s_579` and nothing else on the page. The
1612
+ * same trick is used for the auth brand panel in the app's _tokens.scss.
1613
+ *
1614
+ * Grids rendered anywhere OTHER than inside a `.grid__full` — in a modal, on a
1615
+ * detail tab — are untouched by this and keep drawing their own card, which is
1616
+ * correct: there is nothing else around them to be the card.
1617
+ * ==========================================================================*/
1618
+
1619
+ .grid__full:has([class*='dataGridContainer']) {
1620
+ /* `overflow` stays visible: the column filter selects, the row-action
1621
+ popovers and the pager's per-page dropdown all open out of the card and
1622
+ are clipped to nothing the moment this is `hidden`. The clipping that
1623
+ the radius needs happens on the inner container instead, which contains
1624
+ only the table and has nothing that overflows. */
1625
+ overflow: visible;
1626
+
1627
+ /* Descendant, not `>`. Measured: `GenericIndex` wraps the toolbar and the
1628
+ grid container together in one unclassed `<div>`, so the card's only
1629
+ direct child is that wrapper and a child combinator matches nothing. */
1630
+ [class*='dataGridContainer'] {
1631
+ border: 0;
1632
+ box-shadow: none;
1633
+ /* NOT `inherit`: the parent element is that unclassed wrapper, whose
1634
+ radius is 0, so inheriting would square the corners off.
1635
+ The card's INNER curve is its radius less its own 1px border, and
1636
+ this element sits exactly on that padding box — so that is the
1637
+ radius that meets the card's edge without a sliver of white
1638
+ overshooting the curve, or a gap short of it. */
1639
+ border-radius: calc(var(--card-radius, 10px) - 1px);
1640
+ overflow: hidden;
1641
+ }
1642
+
1643
+ /* The toolbar is the card's top edge now rather than a strip floating on
1644
+ it, so it loses the horizontal inset it needed when the card had 2px of
1645
+ padding, and its hairline runs the full width to meet the card's own
1646
+ border on both sides. */
1647
+ [class*='dataGridTopContainer'] {
1648
+ border-top-left-radius: calc(var(--card-radius, 10px) - 1px);
1649
+ border-top-right-radius: calc(var(--card-radius, 10px) - 1px);
1650
+ border-bottom-color: var(--border-color, #e1e1e1);
1651
+ }
1652
+ }
@@ -52,8 +52,11 @@
52
52
  ========================================================================= */
53
53
 
54
54
  .header {
55
- background: var(--primary-color);
56
- color: var(--primary-color);
55
+ /* `color` was also var(--primary-color) — the same value as the
56
+ background, so every piece of header text was invisible against it
57
+ wherever the token was defined. */
58
+ background: var(--primary-color, #1b3933);
59
+ color: var(--tertiary-color, #fff);
57
60
  position: sticky;
58
61
  top: 0;
59
62
  width: 100%;
@@ -102,7 +105,7 @@
102
105
  background: var(--bg);
103
106
 
104
107
  .content-header {
105
- background: rgba(var(--tertiary-color-rgb), 1.1);
108
+ background: rgba(var(--tertiary-color-rgb, 255, 255, 255), 1);
106
109
  border-bottom: 1px solid rgba(0, 0, 0, 0.14);
107
110
  box-sizing: border-box;
108
111
  padding: 1em;