@visns-studio/visns-components 6.6.4 → 6.15.1

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 (98) hide show
  1. package/README.md +1623 -18
  2. package/package.json +10 -4
  3. package/src/components/DataGrid.jsx +181 -38
  4. package/src/components/Fetch.jsx +80 -3
  5. package/src/components/Form.jsx +9 -0
  6. package/src/components/Navigation.jsx +109 -50
  7. package/src/components/Notification.jsx +279 -9
  8. package/src/components/TableFilter.jsx +14 -1
  9. package/src/components/auth/AuthBrandPanel.jsx +43 -0
  10. package/src/components/auth/AuthLoading.jsx +78 -0
  11. package/src/components/auth/AuthShell.jsx +59 -0
  12. package/src/components/auth/ClientAuth.jsx +161 -0
  13. package/src/components/auth/ClientAuthFrame.jsx +59 -0
  14. package/src/components/auth/ClientLogin.jsx +266 -55
  15. package/src/components/auth/ClientOTPVerify.jsx +587 -115
  16. package/src/components/auth/ImpersonateGate.jsx +254 -0
  17. package/src/components/auth/Login.jsx +134 -41
  18. package/src/components/auth/LogoutScreen.jsx +112 -0
  19. package/src/components/auth/Reset.jsx +134 -77
  20. package/src/components/auth/TwoFactorAuth.jsx +475 -297
  21. package/src/components/auth/Verify.jsx +237 -126
  22. package/src/components/auth/authEndpoints.js +105 -0
  23. package/src/components/auth/authFont.js +23 -0
  24. package/src/components/auth/authHelpers.js +465 -0
  25. package/src/components/auth/clientAuthProtocols.js +240 -0
  26. package/src/components/auth/useOptionalRouter.js +49 -0
  27. package/src/components/callQueue/CallQueuePop.jsx +1502 -0
  28. package/src/components/callQueue/CallQueueSettings.jsx +508 -0
  29. package/src/components/callQueue/callQueueHelpers.js +280 -0
  30. package/src/components/callQueue/callQueueSettingsHelpers.js +75 -0
  31. package/src/components/columns/ColumnRenderers.jsx +53 -7
  32. package/src/components/generic/GenericAuth.jsx +163 -96
  33. package/src/components/generic/GenericDetail.jsx +215 -90
  34. package/src/components/generic/GenericIndex.jsx +20 -47
  35. package/src/components/generic/GenericMain.jsx +5 -0
  36. package/src/components/generic/GroupedReportRenderer.jsx +1 -5
  37. package/src/components/generic/StandardModal.jsx +17 -5
  38. package/src/components/generic/reportSemanticSteps/SemanticEntityStep.jsx +1 -6
  39. package/src/components/generic/reportSemanticSteps/SemanticFieldsStep.jsx +4 -11
  40. package/src/components/generic/reportSemanticSteps/SemanticFiltersStep.jsx +9 -27
  41. package/src/components/generic/reportSemanticSteps/SemanticGroupingStep.jsx +5 -13
  42. package/src/components/generic/reportSemanticSteps/SemanticParameterPrompt.jsx +5 -13
  43. package/src/components/generic/reportSemanticSteps/SemanticPreviewStep.jsx +14 -31
  44. package/src/components/generic/reportSemanticSteps/SemanticRelationsStep.jsx +3 -9
  45. package/src/components/generic/reportSemanticSteps/SemanticValueInput.jsx +2 -15
  46. package/src/components/navActive.js +60 -0
  47. package/src/components/notify/desktopNotifications.js +256 -0
  48. package/src/components/sketch/SketchField.jsx +12 -2
  49. package/src/components/sms/SmsComposeModal.jsx +495 -0
  50. package/src/components/sms/SmsInbox.jsx +596 -0
  51. package/src/components/sms/SmsInboxBadge.jsx +468 -0
  52. package/src/components/sms/SmsLineSettings.jsx +854 -0
  53. package/src/components/sms/SmsThreadPanel.jsx +1184 -0
  54. package/src/components/sms/smsEndpoints.js +56 -0
  55. package/src/components/sms/smsHelpers.js +1075 -0
  56. package/src/components/sms/smsLiveState.js +132 -0
  57. package/src/components/sms/useSmsLive.js +307 -0
  58. package/src/components/styles/CallQueuePop.module.scss +646 -0
  59. package/src/components/styles/CallQueueSettings.module.scss +460 -0
  60. package/src/components/styles/ClientAuth.module.scss +229 -0
  61. package/src/components/styles/GenericDetail.module.scss +153 -26
  62. package/src/components/styles/GenericIndex.module.scss +13 -0
  63. package/src/components/styles/GenericMain.module.scss +19 -2
  64. package/src/components/styles/ImpersonateGate.module.scss +85 -0
  65. package/src/components/styles/Login.module.scss +13 -122
  66. package/src/components/styles/LogoutScreen.module.scss +89 -0
  67. package/src/components/styles/Navigation.module.scss +509 -223
  68. package/src/components/styles/Notification.module.scss +103 -0
  69. package/src/components/styles/Reset.module.scss +52 -186
  70. package/src/components/styles/Sms.module.scss +2382 -0
  71. package/src/components/styles/TableFilter.module.scss +118 -98
  72. package/src/components/styles/TwoFactorAuth.module.scss +115 -176
  73. package/src/components/styles/Vault.module.scss +1983 -0
  74. package/src/components/styles/Verify.module.scss +57 -180
  75. package/src/components/styles/_authBrandPanel.scss +163 -0
  76. package/src/components/styles/_authShell.scss +369 -0
  77. package/src/components/styles/global-datagrid.css +27 -2
  78. package/src/components/utils/buildEnv.js +67 -0
  79. package/src/components/utils/displayValue.js +94 -0
  80. package/src/components/utils/useDensity.js +345 -9
  81. package/src/components/vault/OtpChip.jsx +208 -0
  82. package/src/components/vault/PasswordGenerator.jsx +145 -0
  83. package/src/components/vault/QrScanner.jsx +326 -0
  84. package/src/components/vault/VaultAccessLog.jsx +225 -0
  85. package/src/components/vault/VaultConfirmPanel.jsx +113 -0
  86. package/src/components/vault/VaultEntryForm.jsx +740 -0
  87. package/src/components/vault/VaultManager.jsx +1000 -0
  88. package/src/components/vault/VaultQuickSearch.jsx +681 -0
  89. package/src/components/vault/qrDecode.js +257 -0
  90. package/src/components/vault/useDebouncedValue.js +22 -0
  91. package/src/components/vault/useVaultReveal.js +160 -0
  92. package/src/components/vault/vaultClipboard.js +33 -0
  93. package/src/components/vault/vaultEndpoints.js +40 -0
  94. package/src/components/vault/vaultFit.js +116 -0
  95. package/src/components/vault/vaultHelpers.js +547 -0
  96. package/src/components/vault/vaultNavigation.jsx +58 -0
  97. package/src/components/vault/vaultOtp.js +105 -0
  98. package/src/index.js +233 -1
@@ -24,20 +24,36 @@
24
24
  --nav-item-gap gap between top-level nav items. default: 0.5rem
25
25
  --nav-item-padding padding on a nav item's link/span/button.
26
26
  default: 0.55rem 0.7rem
27
- --nav-item-font-size nav item type size. default: var(--font-size-sm)
28
- --nav-action-size the square action chips on the right.
29
- default: 36px
27
+ --nav-item-font-size nav item type size. default: 0.9rem
28
+ --nav-item-radius corner radius of the pill a nav item wears on
29
+ hover and when it is the current page.
30
+ default: var(--radius, 6px)
31
+ --nav-active-bar-color the 2px rule that marks the current page,
32
+ sitting on the bar's bottom edge.
33
+ default: var(--highlight-color, #fff)
34
+ --nav-action-size the round action chips on the right.
35
+ default: 34px
30
36
  --nav-action-size-sm the smaller chip variant, and the minimum slot
31
37
  a chip sits in. default: 32px
32
38
  --nav-action-gap space between action chips. default: 2px
33
39
  --nav-action-padding padding of the pill that groups the action
34
40
  chips. default: 4px
41
+ --nav-chip-radius corner radius of a single action chip. Fully
42
+ round by default, so the chips nest inside the
43
+ group pill at either end; a project that wants
44
+ squircles sets this once.
45
+ default: var(--radius-pill, 999px)
35
46
  --nav-dropdown-item-padding padding on a dropdown menu row.
36
47
  default: 8px 20px
37
48
  --nav-logo-height height of the logo plate, and the same figure
38
49
  everywhere the rail, the content header and the
39
- mobile drawer offset themselves by it.
40
- default: 50px
50
+ mobile drawer offset themselves by it. In the
51
+ full header it is also the bar's minimum height
52
+ and the cap on the logo image, so the bar is one
53
+ predictable height rather than whatever the
54
+ tallest thing in it happens to measure. Total
55
+ bar height is this plus --nav-bar-padding: 52px
56
+ and 4px gives the 60px bar. default: 50px
41
57
 
42
58
  Two deliberate omissions: the icon-to-label gap inside a nav item and the
43
59
  padding on the chips themselves are internal alignment, not density — the
@@ -55,8 +71,37 @@
55
71
  align-items: center;
56
72
  gap: var(--nav-bar-gap, var(--padding));
57
73
  width: 100%;
74
+ /* One predictable bar height. Before this the bar was as tall as whatever
75
+ the tallest thing in it happened to be — usually the logo image, so a
76
+ project swapping its mark changed the height of the whole application
77
+ shell. The logo is capped to the same figure below. */
78
+ min-height: var(--nav-logo-height, 50px);
58
79
  position: relative; /* Establish a positioning context */
59
80
 
81
+ /* The navigation column fills the bar's full height so the current-page
82
+ rule can sit on the bar's bottom edge rather than floating under the
83
+ item. `.navwrap` scrolls horizontally, which clips vertically too, so
84
+ the rule cannot simply be pushed out of the item with a negative
85
+ offset — the column has to actually reach that far. */
86
+ > .navwrap {
87
+ align-self: stretch;
88
+ }
89
+
90
+ .app-nav {
91
+ height: 100%;
92
+
93
+ /* Full-height items: the hover/active pill still centres inside them,
94
+ but the item's own box now runs bar-top to bar-bottom, which is what
95
+ `.active::after` anchors to. Scoped to the header — the sidebar's
96
+ `.app-nav` is a column and wants none of this. */
97
+ > .nav-item,
98
+ > .nav-item-alternate {
99
+ align-self: stretch;
100
+ display: flex;
101
+ align-items: center;
102
+ }
103
+ }
104
+
60
105
  .logo {
61
106
  width: var(--logo-width);
62
107
  padding: var(--nav-logo-padding, 0);
@@ -81,6 +126,10 @@
81
126
  img {
82
127
  width: 100%;
83
128
  height: auto;
129
+ /* Whatever the mark's aspect ratio, it sits inside the bar's
130
+ height instead of setting it. */
131
+ max-height: var(--nav-logo-height, 50px);
132
+ object-fit: contain;
84
133
  display: block;
85
134
  margin: 0 auto;
86
135
  }
@@ -156,8 +205,8 @@
156
205
  a,
157
206
  span,
158
207
  button {
159
- font-size: var(--nav-item-font-size, var(--font-size-sm));
160
- font-weight: var(--font-weight-medium);
208
+ font-size: var(--nav-item-font-size, 0.9rem);
209
+ font-weight: var(--font-weight-medium, 500);
161
210
  display: flex;
162
211
  flex-wrap: nowrap;
163
212
  align-items: center;
@@ -165,19 +214,59 @@
165
214
  box-sizing: border-box;
166
215
  /* Was 0.35em/0.5em — a tap target under the 44px guideline and
167
216
  visually cramped against the taller header. */
168
- padding: var(--nav-item-padding, 0.55rem 0.7rem);
217
+ padding: var(--nav-item-padding, 0.5rem 0.8rem);
169
218
  margin: 0;
170
219
  line-height: 1;
171
220
  text-decoration: none;
172
- border-radius: var(--radius-sm);
221
+ /* Was `var(--radius-sm)` with no fallback: a project that does not
222
+ declare that token got an INVALID declaration, which the browser
223
+ throws away — square corners, not the intended ones. Every
224
+ custom property in this file's own rules carries its default. */
225
+ border-radius: var(--nav-item-radius, var(--radius, 6px));
173
226
  /* `all` also animated layout properties. Only what changes. */
174
227
  transition:
175
- background-color var(--speed) var(--ease),
176
- color var(--speed) var(--ease);
228
+ background-color var(--speed, 0.18s) var(--ease, ease),
229
+ color var(--speed, 0.18s) var(--ease, ease);
177
230
  background: none;
178
231
  border: none;
179
232
  cursor: pointer;
180
233
  font-family: inherit;
234
+
235
+ /* Keyboard users could not see where they were: the bar suppressed
236
+ outlines everywhere and put nothing back. Pointer users never
237
+ see this. */
238
+ &:focus-visible {
239
+ outline: 2px solid
240
+ var(--nav-active-bar-color, var(--highlight-color, #fff));
241
+ outline-offset: 2px;
242
+ }
243
+ }
244
+
245
+ /* A caret on the items that open something, so a menu is not a
246
+ surprise. There is no caret element in the markup and none is
247
+ needed — an item with children is exactly an item with a `> ul`. */
248
+ &:has(> ul) > a::after,
249
+ &:has(> ul) > span::after,
250
+ &:has(> ul) > button::after {
251
+ content: '';
252
+ width: 0;
253
+ height: 0;
254
+ flex: none;
255
+ margin-left: 0.1rem;
256
+ border-left: 3.5px solid transparent;
257
+ border-right: 3.5px solid transparent;
258
+ border-top: 4px solid currentColor;
259
+ opacity: 0.55;
260
+ transition:
261
+ opacity var(--speed, 0.18s) var(--ease, ease),
262
+ transform var(--speed, 0.18s) var(--ease, ease);
263
+ }
264
+
265
+ &:has(> ul):hover > a::after,
266
+ &:has(> ul):hover > span::after,
267
+ &:has(> ul):hover > button::after {
268
+ opacity: 1;
269
+ transform: rotate(180deg);
181
270
  }
182
271
 
183
272
  &.nav-item {
@@ -189,10 +278,10 @@
189
278
 
190
279
  /* Nothing responded to the pointer unless it was already the
191
280
  active page, so the bar felt inert. */
192
- &:not(.active):hover a,
193
- &:not(.active):hover span,
194
- &:not(.active):hover button {
195
- background: rgba(255, 255, 255, 0.12);
281
+ &:not(.active):hover > a,
282
+ &:not(.active):hover > span,
283
+ &:not(.active):hover > button {
284
+ background: rgba(255, 255, 255, 0.08);
196
285
  color: #fff;
197
286
  }
198
287
 
@@ -202,95 +291,140 @@
202
291
  visibility: hidden;
203
292
  }
204
293
 
205
- /* Only the current page wears the brand red. Hover shared it
206
- before, so pointing at any item made it look selected and the
207
- real active item was impossible to pick out while moving the
208
- mouse across the bar. Hover is handled above, as a quiet lift. */
209
- &.active a,
210
- &.active span,
211
- &.active button {
212
- background: var(--secondary-color);
294
+ /* The current page: a filled pill AND the rule below. The pill
295
+ alone was a solid brand-red block that fought the bar; the rule
296
+ alone is too quiet to find at a glance while scanning. Hover is
297
+ the same shape at half the weight, so moving across the bar
298
+ previews the selection rather than imitating it. */
299
+ &.active > a,
300
+ &.active > span,
301
+ &.active > button {
302
+ background: rgba(255, 255, 255, 0.14);
213
303
  color: #fff;
214
- font-weight: var(--font-weight-semibold);
304
+ font-weight: var(--font-weight-semibold, 600);
215
305
  }
216
306
 
217
- &.active svg,
307
+ /* Sits on the bar's bottom edge, not under the item — the item is
308
+ stretched to the full height of the bar for exactly this. */
309
+ &.active::after {
310
+ content: '';
311
+ position: absolute;
312
+ left: 0.35rem;
313
+ right: 0.35rem;
314
+ bottom: 0;
315
+ height: 2px;
316
+ border-radius: 2px 2px 0 0;
317
+ background: var(
318
+ --nav-active-bar-color,
319
+ var(--highlight-color, #fff)
320
+ );
321
+ pointer-events: none;
322
+ }
323
+
324
+ &.active > svg,
218
325
  &:hover svg {
219
326
  color: #fff;
220
327
  }
221
328
 
222
329
  > ul {
223
- width: 200px;
330
+ width: 220px;
224
331
  position: absolute;
332
+ /* Anchored explicitly rather than left wherever its static
333
+ position lands: the item is now a full-height flex box, so
334
+ an un-anchored absolute child is at the mercy of the flex
335
+ line. */
336
+ top: 100%;
337
+ left: 0;
225
338
  height: auto;
226
339
  margin: 0;
227
- padding: 0;
340
+ /* The rows used to sit flush against the panel edge, which is
341
+ the only reason each one carried its own first/last corner
342
+ juggling. A ring of padding lets every row wear the same
343
+ radius as the nav items above it, and the special cases go
344
+ away with it. */
345
+ padding: 4px;
228
346
  list-style: none;
229
347
  display: none;
230
348
  opacity: 0;
231
- background: rgba(var(--primary-rgb), 0.75);
232
- border-radius: var(--br);
349
+ background: var(--primary-color);
350
+ border: 1px solid rgba(255, 255, 255, 0.1);
351
+ border-radius: var(--radius, 6px);
233
352
  overflow: visible;
234
- margin-top: 1px;
235
- clip-path: inset(0 0 0 0 round var(--br));
236
353
  z-index: 1000; /* Ensure dropdown appears above other elements */
237
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); /* Add shadow for better visibility */
354
+ box-shadow:
355
+ 0 6px 16px rgba(0, 0, 0, 0.22),
356
+ 0 2px 4px rgba(0, 0, 0, 0.16);
238
357
 
239
358
  li {
240
359
  display: block;
241
360
  position: relative;
242
361
  margin: 1px 0;
243
- border-radius: 0;
244
-
245
- &:first-of-type > a {
246
- border-top-right-radius: var(--br);
247
- border-top-left-radius: var(--br);
248
- border-bottom-right-radius: 0;
249
- border-bottom-left-radius: 0;
250
- }
251
-
252
- &:last-of-type {
253
- margin-bottom: 0;
254
-
255
- > a {
256
- border-bottom-right-radius: var(--br);
257
- border-bottom-left-radius: var(--br);
258
- border-top-right-radius: 0;
259
- border-top-left-radius: 0;
260
- }
261
- }
262
-
263
- &:not(:first-of-type):not(:last-of-type) > a {
264
- border-radius: 0;
265
- }
266
362
 
267
363
  > a,
268
364
  > button {
269
365
  width: 100%;
270
- padding: var(--nav-dropdown-item-padding, 8px 20px);
366
+ padding: var(--nav-dropdown-item-padding, 8px 12px);
271
367
  box-sizing: border-box;
272
368
  display: block;
273
369
  text-decoration: none;
274
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
370
+ border-radius: var(
371
+ --nav-item-radius,
372
+ var(--radius, 6px)
373
+ );
374
+ transition:
375
+ background-color var(--speed, 0.18s)
376
+ var(--ease, ease),
377
+ color var(--speed, 0.18s) var(--ease, ease);
275
378
  outline: none;
276
379
  margin: 0;
277
- background: rgba(var(--primary-rgb), 1);
278
- color: var(--tertiary-color) !important;
380
+ background: none;
381
+ /* Was `!important` over an opaque fill, so the hover
382
+ and current-page colours below could never apply —
383
+ the menu looked dead to the pointer. */
384
+ color: rgba(255, 255, 255, 0.82);
385
+ font-size: var(--nav-item-font-size, 0.9rem);
386
+ font-family: inherit;
279
387
  position: relative;
280
388
  border: none;
281
389
  cursor: pointer;
282
- font-family: inherit;
283
390
  text-align: left;
391
+
392
+ &:focus-visible {
393
+ outline: 2px solid
394
+ var(
395
+ --nav-active-bar-color,
396
+ var(--highlight-color, #fff)
397
+ );
398
+ outline-offset: -2px;
399
+ }
284
400
  }
285
401
 
286
- &:hover a,
287
- &:hover button {
288
- background: rgba(var(--primary-rgb), 1);
289
- color: var(--tertiary-color);
402
+ /* Only the row for the page you are ON is emphasised;
403
+ the parent's active styling is scoped to its own link
404
+ (child combinators above) so the menu does not light up
405
+ wholesale when the parent is active. */
406
+ > a.active,
407
+ > button.active {
408
+ background: rgba(255, 255, 255, 0.14);
409
+ color: #fff;
410
+ font-weight: var(--font-weight-semibold, 600);
411
+ }
412
+
413
+ &:hover > a,
414
+ &:hover > button {
415
+ background: rgba(255, 255, 255, 0.1);
416
+ color: #fff;
417
+ }
418
+
419
+ > a.active,
420
+ > button.active {
421
+ background: rgba(255, 255, 255, 0.14);
422
+ color: #fff;
423
+ font-weight: var(--font-weight-semibold, 600);
290
424
  }
291
425
 
292
426
  svg {
293
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
427
+ transition: color var(--speed, 0.18s) var(--ease, ease);
294
428
  }
295
429
  }
296
430
  }
@@ -302,22 +436,50 @@
302
436
  }
303
437
 
304
438
  &.nav-item-alternate {
439
+ /* The light mirror of `.nav-item`: the same pill, the same rule on
440
+ the bar's bottom edge, read against a pale surface instead of
441
+ the brand colour. Hover and active shared one declaration here,
442
+ so on this theme too there was no way to tell which item was the
443
+ current page while the pointer was anywhere near the bar. */
305
444
  a,
306
- span {
307
- color: var(--paragraph-color);
308
- background: var(--tertiary-color);
445
+ span,
446
+ button {
447
+ color: var(--paragraph-color, #2b2b2b);
448
+ background: none;
309
449
  }
310
450
 
311
451
  svg {
312
452
  color: var(--secondary-color);
313
453
  }
314
454
 
315
- &:hover a,
316
- &.active a,
317
- &:hover span,
318
- &.active span {
319
- background: var(--primary-color);
320
- color: var(--tertiary-color);
455
+ &:not(.active):hover > a,
456
+ &:not(.active):hover > span,
457
+ &:not(.active):hover > button {
458
+ background: rgba(0, 0, 0, 0.06);
459
+ color: var(--primary-color);
460
+ }
461
+
462
+ &.active > a,
463
+ &.active > span,
464
+ &.active > button {
465
+ background: rgba(var(--primary-rgb), 0.1);
466
+ color: var(--primary-color);
467
+ font-weight: var(--font-weight-semibold, 600);
468
+ }
469
+
470
+ &.active::after {
471
+ content: '';
472
+ position: absolute;
473
+ left: 0.35rem;
474
+ right: 0.35rem;
475
+ bottom: 0;
476
+ height: 2px;
477
+ border-radius: 2px 2px 0 0;
478
+ background: var(
479
+ --nav-active-bar-color,
480
+ var(--highlight-color, var(--primary-color))
481
+ );
482
+ pointer-events: none;
321
483
  }
322
484
 
323
485
  &:hover svg,
@@ -326,64 +488,67 @@
326
488
  }
327
489
 
328
490
  > ul {
329
- width: 200px;
491
+ width: 220px;
330
492
  position: absolute;
493
+ top: 100%;
494
+ left: 0;
331
495
  height: auto;
332
496
  margin: 0;
333
- padding: 0;
497
+ padding: 4px;
334
498
  list-style: none;
335
499
  display: none;
336
500
  opacity: 0;
337
- background: rgba(var(--primary-rgb), 0.9);
338
- border-radius: var(--br);
501
+ background: var(--surface-color, #fff);
502
+ border: 1px solid rgba(0, 0, 0, 0.08);
503
+ border-radius: var(--radius, 6px);
339
504
  overflow: visible;
340
- margin-top: 1px;
341
- clip-path: inset(0 0 0 0 round var(--br));
342
505
  z-index: 1000; /* Ensure dropdown appears above other elements */
343
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); /* Add shadow for better visibility */
506
+ box-shadow:
507
+ 0 6px 16px rgba(0, 0, 0, 0.12),
508
+ 0 2px 4px rgba(0, 0, 0, 0.08);
344
509
 
345
510
  li {
346
511
  display: block;
347
512
  position: relative;
348
- margin-bottom: 2px;
349
- border-radius: 0;
350
-
351
- &:first-of-type a {
352
- border-top-right-radius: var(--br);
353
- border-top-left-radius: var(--br);
354
- border-bottom-right-radius: 0;
355
- border-bottom-left-radius: 0;
356
- }
357
-
358
- &:last-of-type {
359
- margin-bottom: 0;
360
-
361
- a {
362
- border-bottom-right-radius: var(--br);
363
- border-bottom-left-radius: var(--br);
364
- border-top-right-radius: 0;
365
- border-top-left-radius: 0;
366
- }
367
- }
368
-
369
- &:not(:first-of-type):not(:last-of-type) a {
370
- border-radius: 0;
371
- }
513
+ margin: 1px 0;
372
514
 
373
515
  a {
374
516
  width: 100%;
375
- padding: var(--nav-dropdown-item-padding, 8px 20px);
517
+ padding: var(--nav-dropdown-item-padding, 8px 12px);
376
518
  box-sizing: border-box;
377
519
  display: block;
378
520
  text-decoration: none;
379
- background: rgba(var(--primary-rgb), 0.9);
380
- color: var(--paragraph-color);
381
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
521
+ background: none;
522
+ color: var(--paragraph-color, #2b2b2b);
523
+ font-size: var(--nav-item-font-size, 0.9rem);
524
+ border-radius: var(
525
+ --nav-item-radius,
526
+ var(--radius, 6px)
527
+ );
528
+ transition:
529
+ background-color var(--speed, 0.18s)
530
+ var(--ease, ease),
531
+ color var(--speed, 0.18s) var(--ease, ease);
532
+
533
+ &:focus-visible {
534
+ outline: 2px solid
535
+ var(
536
+ --nav-active-bar-color,
537
+ var(--highlight-color, currentColor)
538
+ );
539
+ outline-offset: -2px;
540
+ }
382
541
  }
383
542
 
384
543
  &:hover a {
385
- background: rgba(var(--primary-rgb), 1.05);
386
- color: var(--secondary-color);
544
+ background: rgba(0, 0, 0, 0.06);
545
+ color: var(--primary-color);
546
+ }
547
+
548
+ a.active {
549
+ background: rgba(var(--primary-rgb), 0.1);
550
+ color: var(--primary-color);
551
+ font-weight: var(--font-weight-semibold, 600);
387
552
  }
388
553
  }
389
554
  }
@@ -416,8 +581,15 @@
416
581
  actions here, so this is padding and a pill radius rather than
417
582
  anything sized to a fixed set. If a project mounts more than fits,
418
583
  the group scrolls instead of pushing the navigation. */
419
- background: rgba(255, 255, 255, 0.1);
420
- border-radius: var(--radius-pill);
584
+ /* Quieter than it was, and with no border: at 0.1 with a visible
585
+ edge the group read as a control of its own sitting in the bar
586
+ rather than as the ground the chips sit on. */
587
+ background: rgba(255, 255, 255, 0.08);
588
+ /* Was `var(--radius-pill)` with no fallback. A project that does not
589
+ declare that token got an invalid declaration, which the browser
590
+ discards — so the "pill" rendered as a square box with square chips
591
+ inside it, which is exactly how it looked. */
592
+ border-radius: var(--radius-pill, 999px);
421
593
  /* An even ring around the chips; any more and the group reads as a
422
594
  separate control rather than a container for them. */
423
595
  padding: var(--nav-action-padding, 4px);
@@ -453,7 +625,22 @@
453
625
  position: relative;
454
626
  z-index: 1001;
455
627
 
456
- li {
628
+ /* `> li`, NOT a bare `li` — for exactly the reason the `a` / `button`
629
+ rules below carry their exclusion, and this one was missed.
630
+
631
+ A popover mounted in this cluster brings its own markup, and a list
632
+ in it (the vault's results, the messaging inbox's threads) is a
633
+ `ul > li` several levels down. Every one of those rows was matching
634
+ here and inheriting `display: flex`, `justify-content: center`, a
635
+ 32px minimum box and 2px side margins — at 0,1,2, which no single
636
+ module class can beat. A row laid out as a grid quietly became a
637
+ centred flex line, and the 2px margin is the "rows look indented"
638
+ in the screenshots.
639
+
640
+ The chips are always direct children of this `ul`, so scoping to
641
+ `> li` loses nothing and cannot reach into anything a chip opens.
642
+ The `:not()` is belt and braces for a project that nests a chip. */
643
+ > li:not([data-nav-overlay] *) {
457
644
  margin: 0 var(--nav-action-gap, 2px);
458
645
  position: relative;
459
646
  display: flex;
@@ -465,8 +652,40 @@
465
652
  z-index: 1002;
466
653
  }
467
654
 
468
- a,
469
- button {
655
+ /* Signing out is not the same kind of action as the eight chips
656
+ before it, and a hairline says so without a second container.
657
+ Positional, deliberately: whatever a project mounts last is the
658
+ one it wants set apart. */
659
+ > li:not([data-nav-overlay] *):last-child {
660
+ margin-left: calc(var(--nav-action-gap, 2px) + 7px);
661
+
662
+ &::before {
663
+ content: '';
664
+ position: absolute;
665
+ left: calc(-1 * (var(--nav-action-gap, 2px) + 4px));
666
+ top: 22%;
667
+ bottom: 22%;
668
+ width: 1px;
669
+ background: rgba(255, 255, 255, 0.16);
670
+ pointer-events: none;
671
+ }
672
+ }
673
+
674
+ /* NOT a bare `a, button` — that reached into the overlays these
675
+ chips open. A popover mounted inside this cluster (the notification
676
+ dropdown, the vault quick search) is a panel of ordinary controls,
677
+ and every one of them was inheriting the chip's white text, its
678
+ fixed 36px box and its pill radius, at a specificity the popover's
679
+ own module classes could not beat. The vault's popover rendered as
680
+ blank white boxes on a white panel.
681
+
682
+ `:not([data-nav-overlay] *)` is a pure exclusion: everything that
683
+ matched before still matches, unless it sits inside an element
684
+ flagged as an open overlay — the same attribute the `:has()` rule
685
+ above already relies on. Triggers keep their styling, because a
686
+ trigger is a sibling of the overlay it opens, never inside it. */
687
+ a:not([data-nav-overlay] *),
688
+ button:not([data-nav-overlay] *) {
470
689
  color: var(--tertiary-color);
471
690
  box-sizing: border-box;
472
691
  text-decoration: none;
@@ -481,34 +700,59 @@
481
700
  position — a rounded square cut across the container's curve at
482
701
  the two ends. Also stays correct however many actions a project
483
702
  mounts, since no chip is styled by its position. */
484
- border-radius: var(--radius-pill);
703
+ /* The fallback matters: without one, a project that has not
704
+ declared `--radius-pill` got an invalid declaration and square
705
+ chips inside a square group. */
706
+ border-radius: var(--nav-chip-radius, var(--radius-pill, 999px));
485
707
  /* `all` animated layout too; and the hover scale made the icons
486
708
  wobble against a static header. Colour alone is enough feedback
487
- on a 36px target. */
488
- transition: background-color var(--speed) var(--ease);
709
+ on a 34px target. */
710
+ transition:
711
+ background-color var(--speed, 0.18s) var(--ease, ease),
712
+ color var(--speed, 0.18s) var(--ease, ease);
489
713
  display: flex;
490
714
  align-items: center;
491
715
  justify-content: center;
492
716
  /* 32px was below the 44px guideline and awkward to hit. */
493
- width: var(--nav-action-size, 36px);
494
- height: var(--nav-action-size, 36px);
717
+ width: var(--nav-action-size, 34px);
718
+ height: var(--nav-action-size, 34px);
719
+ min-width: var(--nav-action-size, 34px);
495
720
  position: relative;
496
721
  z-index: 1003;
722
+ outline: none;
497
723
  appearance: none;
724
+ cursor: pointer;
498
725
 
499
- /* Brand red on hover, matching the rest of the bar. A neutral
500
- white lift read as a system default rather than a project
501
- control, and left this cluster looking unrelated to everything
502
- around it. */
503
- &:hover,
504
- &.active {
505
- background: var(--secondary-color);
726
+ /* A lift off the group's own surface, not a second colour. The
727
+ brand-red fill made nine chips read as nine separate buttons
728
+ parked in a tray; the current one is picked out by the icon
729
+ taking the highlight colour instead. */
730
+ &:hover {
731
+ background: rgba(255, 255, 255, 0.14);
506
732
 
507
733
  svg {
508
734
  color: #fff !important;
509
735
  }
510
736
  }
511
737
 
738
+ &.active,
739
+ &[aria-expanded='true'] {
740
+ background: rgba(255, 255, 255, 0.14);
741
+
742
+ svg {
743
+ color: var(
744
+ --nav-active-bar-color,
745
+ var(--highlight-color, #fff)
746
+ ) !important;
747
+ }
748
+ }
749
+
750
+ &:focus-visible {
751
+ outline: 2px solid
752
+ var(--nav-active-bar-color, var(--highlight-color, #fff));
753
+ outline-offset: 2px;
754
+ }
755
+
512
756
  svg {
513
757
  /* Components mounted here (notifications, switch account)
514
758
  colour their own icons, which is why they hovered
@@ -522,67 +766,58 @@
522
766
  transform, against 36px flat siblings. Rather than chase each one by
523
767
  name, normalise any button nested inside a wrapper element so the
524
768
  cluster stays uniform however a project fills it. */
525
- li > div,
526
- li > span {
769
+ /* `> li >`, for the same reason: a popover row's own spans are
770
+ children of a `li`, and this rule was turning the text cell of a
771
+ grid row into a flex box — which is what stopped the ellipsis
772
+ engaging on a long preview. */
773
+ > li > div:not([data-nav-overlay] *),
774
+ > li > span:not([data-nav-overlay] *) {
527
775
  display: flex;
528
776
  align-items: center;
529
777
  }
530
778
 
531
- li button {
532
- width: var(--nav-action-size, 36px);
533
- height: var(--nav-action-size, 36px);
534
- min-width: var(--nav-action-size, 36px);
779
+ > li button:not([data-nav-overlay] *) {
780
+ width: var(--nav-action-size, 34px);
781
+ height: var(--nav-action-size, 34px);
782
+ min-width: var(--nav-action-size, 34px);
535
783
  padding: 0;
536
- border-radius: var(--radius-pill) !important;
784
+ border: none;
785
+ border-radius: var(
786
+ --nav-chip-radius,
787
+ var(--radius-pill, 999px)
788
+ ) !important;
537
789
  background: none;
538
790
  transform: none !important;
539
791
  color: var(--tertiary-color);
540
792
 
541
793
  &:hover {
542
- background: var(--secondary-color) !important;
794
+ background: rgba(255, 255, 255, 0.14) !important;
543
795
  color: #fff;
544
796
 
545
797
  svg {
546
798
  color: #fff !important;
547
799
  }
548
800
  }
549
- }
550
801
 
551
- button {
552
- color: var(--tertiary-color);
553
- box-sizing: border-box;
554
- padding: 0.35rem;
555
- text-decoration: none;
556
- background: none;
557
- border: 1px solid var(--primary-color);
558
- border-radius: 100%;
559
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1); // Reduced duration
560
- display: flex;
561
- align-items: center;
562
- justify-content: center;
563
- width: var(--nav-action-size-sm, 32px); // Reduced from 36px
564
- height: var(--nav-action-size-sm, 32px); // Reduced from 36px
565
- outline: none;
566
- appearance: none;
567
- position: relative;
568
- z-index: 1003;
569
- transform: translateZ(0) translateX(0) translateY(0); // Force compositor layer
570
- will-change: transform, background-color; // Optimize for animations
571
- /* Remove contain: layout to prevent rendering optimization conflicts */
572
- backface-visibility: hidden; // Prevent rendering glitches
573
- -webkit-backface-visibility: hidden;
574
- isolation: isolate;
575
-
576
- &:hover,
577
- &.active {
578
- background: var(--primary-color);
579
- transform: translateZ(0) scale(1.05); // Slight scale on hover
802
+ &.active,
803
+ &[aria-expanded='true'] {
804
+ background: rgba(255, 255, 255, 0.14) !important;
580
805
 
581
806
  svg {
582
- color: var(--secondary-color);
807
+ color: var(
808
+ --nav-active-bar-color,
809
+ var(--highlight-color, #fff)
810
+ ) !important;
583
811
  }
584
812
  }
585
813
  }
814
+
815
+ /* The second copy of the chip rule that used to live here — 32px, a
816
+ 1px border in the bar's own colour, a 1.05 hover scale — is gone.
817
+ It restated `a, button` above it with different numbers, so every
818
+ chip that happened to be a `button` was a different size and shape
819
+ from the links beside it, and the invisible border is what made the
820
+ group read as a row of outlined squares. One rule now. */
586
821
  }
587
822
  }
588
823
 
@@ -595,8 +830,29 @@
595
830
  > ul {
596
831
  list-style: none;
597
832
  display: flex;
598
- padding: 0;
833
+ /* The light mirror of `.hactions`: the same group pill, the same
834
+ borderless round chips, read against a pale bar. This copy had no
835
+ container at all, so the alternate theme showed a loose row of
836
+ outlined circles where the primary theme showed a tray. */
837
+ background: rgba(0, 0, 0, 0.05);
838
+ border-radius: var(--radius-pill, 999px);
839
+ padding: var(--nav-action-padding, 4px);
599
840
  margin: 0;
841
+ max-width: 100%;
842
+ overflow-x: auto;
843
+ scrollbar-width: none;
844
+
845
+ &::-webkit-scrollbar {
846
+ display: none;
847
+ }
848
+
849
+ /* Same reason as `.hactions`: a scroll container clips both axes, and
850
+ the popovers these chips open are absolutely positioned below them.
851
+ While one is open the group stops scrolling and lets it out. */
852
+ &:has([data-nav-overlay='open']) {
853
+ overflow: visible;
854
+ }
855
+
600
856
  flex-wrap: nowrap;
601
857
  flex-direction: row;
602
858
  align-items: center;
@@ -605,7 +861,9 @@
605
861
  position: relative;
606
862
  z-index: 1001;
607
863
 
608
- li {
864
+ /* Direct children only — see the note on the same rule in
865
+ `.hactions`, which this block is the second copy of. */
866
+ > li:not([data-nav-overlay] *) {
609
867
  margin: 0 var(--nav-action-gap, 2px);
610
868
  position: relative;
611
869
  display: flex;
@@ -617,81 +875,109 @@
617
875
  z-index: 1002;
618
876
  }
619
877
 
620
- a {
621
- color: var(--paragraph-color);
622
- box-sizing: border-box;
623
- padding: 0.35rem;
624
- text-decoration: none;
625
- background: none;
626
- border: 1px solid var(--primary-color);
627
- border-radius: 100%;
628
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1); // Reduced duration
629
- display: flex;
630
- align-items: center;
631
- justify-content: center;
632
- width: var(--nav-action-size-sm, 32px); // Reduced from 36px
633
- height: var(--nav-action-size-sm, 32px); // Reduced from 36px
634
- position: relative;
635
- z-index: 1003;
636
- transform: translateZ(0) translateX(0) translateY(0); // Force compositor layer
637
- will-change: transform, background-color; // Optimize for animations
638
- /* Remove contain: layout to prevent rendering optimization conflicts */
639
- outline: none;
640
- appearance: none;
641
- backface-visibility: hidden; // Prevent rendering glitches
642
- -webkit-backface-visibility: hidden;
643
- /* Force onto own layer to prevent culling */
644
- isolation: isolate;
645
-
646
- &:hover,
647
- &.active {
648
- background: var(--primary-color);
649
- transform: translateZ(0) scale(1.05); // Slight scale on hover
878
+ /* The sign-out chip, set apart by a hairline rather than a container. */
879
+ > li:not([data-nav-overlay] *):last-child {
880
+ margin-left: calc(var(--nav-action-gap, 2px) + 7px);
650
881
 
651
- svg {
652
- color: var(--tertiary-color);
653
- }
882
+ &::before {
883
+ content: '';
884
+ position: absolute;
885
+ left: calc(-1 * (var(--nav-action-gap, 2px) + 4px));
886
+ top: 22%;
887
+ bottom: 22%;
888
+ width: 1px;
889
+ background: rgba(0, 0, 0, 0.12);
890
+ pointer-events: none;
654
891
  }
655
892
  }
656
893
 
657
- button {
658
- color: var(--paragraph-color);
894
+ a:not([data-nav-overlay] *),
895
+ button:not([data-nav-overlay] *) {
896
+ color: var(--paragraph-color, #2b2b2b);
659
897
  box-sizing: border-box;
660
- padding: 0.35rem;
898
+ padding: 0;
899
+ margin: 0;
661
900
  text-decoration: none;
662
901
  background: none;
663
- border: 1px solid var(--primary-color);
664
- border-radius: 100%;
665
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1); // Reduced duration
902
+ font: inherit;
903
+ /* Was a 1px border and a 1.05 hover scale: the border is what made
904
+ these read as nine outlined boxes, and the scale wobbled the
905
+ icons against a bar that does not move. */
906
+ border: none;
907
+ border-radius: var(--nav-chip-radius, var(--radius-pill, 999px));
908
+ transition:
909
+ background-color var(--speed, 0.18s) var(--ease, ease),
910
+ color var(--speed, 0.18s) var(--ease, ease);
666
911
  display: flex;
667
912
  align-items: center;
668
913
  justify-content: center;
669
- width: var(--nav-action-size-sm, 32px); // Reduced from 36px
670
- height: var(--nav-action-size-sm, 32px); // Reduced from 36px
914
+ width: var(--nav-action-size, 34px);
915
+ height: var(--nav-action-size, 34px);
916
+ min-width: var(--nav-action-size, 34px);
671
917
  outline: none;
672
918
  appearance: none;
673
919
  position: relative;
674
920
  z-index: 1003;
675
- transform: translateZ(0) translateX(0) translateY(0); // Force compositor layer
676
- will-change: transform, background-color; // Optimize for animations
677
- /* Remove contain: layout to prevent rendering optimization conflicts */
678
- backface-visibility: hidden; // Prevent rendering glitches
679
- -webkit-backface-visibility: hidden;
680
- isolation: isolate;
681
-
682
- &:hover,
683
- &.active {
684
- background: var(--primary-color);
685
- transform: translateZ(0) scale(1.05); // Slight scale on hover
921
+ transform: none;
922
+ cursor: pointer;
923
+
924
+ &:hover {
925
+ background: rgba(0, 0, 0, 0.08);
686
926
 
687
927
  svg {
688
- color: var(--tertiary-color);
928
+ color: var(--primary-color) !important;
689
929
  }
690
930
  }
931
+
932
+ &.active,
933
+ &[aria-expanded='true'] {
934
+ background: rgba(0, 0, 0, 0.08);
935
+
936
+ svg {
937
+ color: var(
938
+ --nav-active-bar-color,
939
+ var(--highlight-color, var(--primary-color))
940
+ ) !important;
941
+ }
942
+ }
943
+
944
+ &:focus-visible {
945
+ outline: 2px solid
946
+ var(
947
+ --nav-active-bar-color,
948
+ var(--highlight-color, var(--primary-color))
949
+ );
950
+ outline-offset: 2px;
951
+ }
952
+
953
+ svg {
954
+ color: var(--paragraph-color, #2b2b2b);
955
+ }
956
+ }
957
+
958
+ /* Wrapper elements around a mounted component's own trigger, as in
959
+ `.hactions`. */
960
+ > li > div:not([data-nav-overlay] *),
961
+ > li > span:not([data-nav-overlay] *) {
962
+ display: flex;
963
+ align-items: center;
964
+ }
965
+
966
+ > li button:not([data-nav-overlay] *) {
967
+ width: var(--nav-action-size, 34px);
968
+ height: var(--nav-action-size, 34px);
969
+ min-width: var(--nav-action-size, 34px);
970
+ padding: 0;
971
+ border: none;
972
+ border-radius: var(
973
+ --nav-chip-radius,
974
+ var(--radius-pill, 999px)
975
+ ) !important;
976
+ background: none;
977
+ transform: none !important;
691
978
  }
692
979
  }
693
980
  }
694
-
695
981
  .cwrap {
696
982
  display: flex;
697
983
  }