@visns-studio/visns-components 6.3.2 → 6.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -91,7 +91,7 @@
91
91
  "react-dom": "^17.0.0 || ^18.0.0"
92
92
  },
93
93
  "name": "@visns-studio/visns-components",
94
- "version": "6.3.2",
94
+ "version": "6.3.4",
95
95
  "description": "Various packages to assist in the development of our Custom Applications.",
96
96
  "main": "src/index.js",
97
97
  "files": [
@@ -2306,6 +2306,11 @@ function Field({
2306
2306
  data-name={settings.id}
2307
2307
  type="radio"
2308
2308
  className={`${styles.fi__customcheckbox} radio-input`}
2309
+ // Radio renderers previously ignored the
2310
+ // field's read-only state, leaving
2311
+ // `readOnlyWhen` configs claiming a lock
2312
+ // the UI didn't enforce.
2313
+ disabled={isFieldReadOnly}
2309
2314
  value={option.id}
2310
2315
  checked={
2311
2316
  inputValue !== undefined &&
@@ -2336,6 +2341,11 @@ function Field({
2336
2341
  data-name={settings.id}
2337
2342
  type="radio"
2338
2343
  className={`${styles.fi__customcheckbox} radio-input`}
2344
+ // Radio renderers previously ignored the
2345
+ // field's read-only state, leaving
2346
+ // `readOnlyWhen` configs claiming a lock
2347
+ // the UI didn't enforce.
2348
+ disabled={isFieldReadOnly}
2339
2349
  value={option.id}
2340
2350
  checked={
2341
2351
  inputValue !== undefined &&
@@ -115,6 +115,12 @@ function Form({
115
115
  const [inputClass, setInputClass] = useState({});
116
116
  const [tableData, setTableData] = useState({ columns: [], dataSource: [] });
117
117
  const [tableProps, setTableProps] = useState({});
118
+ // A `save.return.type: 'table'` form (the report filter rows in
119
+ // GenericDetail) renders nothing at all when the query comes back empty,
120
+ // so "no rows matched" and "you have not run it yet" looked identical: an
121
+ // unchanged blank panel under a "generated successfully" toast. This
122
+ // separates the two so the empty result can say so.
123
+ const [hasQueried, setHasQueried] = useState(false);
118
124
  const [uploadProgress, setUploadProgress] = useState(0);
119
125
 
120
126
  // Group selection state management
@@ -2091,6 +2097,11 @@ function Form({
2091
2097
  );
2092
2098
  }
2093
2099
 
2100
+ // Set before the branch: an empty result is
2101
+ // still a result, and that is the case the
2102
+ // empty state exists for.
2103
+ setHasQueried(true);
2104
+
2094
2105
  if (
2095
2106
  res.data[formSettings.save.return.param]
2096
2107
  ?.length > 0
@@ -3300,6 +3311,27 @@ function Form({
3300
3311
  />
3301
3312
  </div>
3302
3313
  )}
3314
+
3315
+ {/* Empty result. Only ever after a run — before that the
3316
+ panel is a filter row waiting to be used, and telling
3317
+ the user there is nothing there would be wrong. Copy is
3318
+ overridable per report via `save.return.empty`. */}
3319
+ {hasQueried &&
3320
+ formSettings?.save?.return?.type === 'table' &&
3321
+ !(tableData?.dataSource?.length > 0) && (
3322
+ <div
3323
+ className={`${styles.formItem} ${styles.fwItem} ${styles.emptyResult}`}
3324
+ >
3325
+ <p>
3326
+ {formSettings.save.return.empty?.title ||
3327
+ 'No records matched'}
3328
+ </p>
3329
+ <span>
3330
+ {formSettings.save.return.empty?.message ||
3331
+ 'Nothing came back for these filters. Widen them and run the report again.'}
3332
+ </span>
3333
+ </div>
3334
+ )}
3303
3335
  </form>
3304
3336
  <div className={styles.polActions}>
3305
3337
  {formSettings.hasOwnProperty('customButton') &&
@@ -2679,6 +2679,22 @@ function GenericDetail({
2679
2679
  >
2680
2680
  <span>{formConfig.title}</span>
2681
2681
  </div>
2682
+ {/* Optional one-line subtitle. A
2683
+ report tab is a name and a set
2684
+ of controls with nothing saying
2685
+ what it returns; a tab that has
2686
+ something to say can now say
2687
+ it, and one that has not is
2688
+ unchanged. */}
2689
+ {formConfig.description && (
2690
+ <p
2691
+ className={
2692
+ styles.gridtxt__description
2693
+ }
2694
+ >
2695
+ {formConfig.description}
2696
+ </p>
2697
+ )}
2682
2698
  <Form
2683
2699
  key={`form-${activeTabConfig.id}`}
2684
2700
  columnId={routeParams[urlParam]}
@@ -205,8 +205,16 @@
205
205
  }
206
206
  }
207
207
 
208
+ /* The gallery modal's close control — this class is consumed by
209
+ modals/GalleryModal.jsx, which imports this stylesheet (DataGrid.jsx itself
210
+ never uses it). Same hardening as the header close in Form.module.scss; the
211
+ long note on the svg block there explains why a close glyph needs an
212
+ explicit box and `flex: none` rather than trusting its size attributes. */
208
213
  .modal__close {
209
214
  width: max-content;
215
+ min-width: 2rem;
216
+ min-height: 2rem;
217
+ box-sizing: border-box;
210
218
  background: rgba(var(--paragraph-rgb), 0.05);
211
219
  cursor: pointer;
212
220
  padding: 0.25rem;
@@ -225,6 +233,9 @@
225
233
  }
226
234
 
227
235
  svg {
236
+ width: 1.25rem;
237
+ height: 1.25rem;
238
+ flex: none;
228
239
  color: var(--primary-color);
229
240
  display: block;
230
241
  }
@@ -708,24 +708,52 @@ input[type='file']:hover {
708
708
  font-size: 1.15em;
709
709
  }
710
710
 
711
+ /* The "Edit Canvas" modal's close control (Field.jsx). Hardened
712
+ the same way as the header close in Form.module.scss — see the
713
+ note on the svg block there. */
711
714
  .modal__close {
712
715
  width: max-content;
716
+ min-width: 2rem;
717
+ min-height: 2rem;
713
718
  position: absolute;
714
719
  right: 5px;
715
720
  top: 5px;
716
721
  box-sizing: border-box;
717
- background: rgba(var(--primary-color), 1.08);
722
+ /* Was `rgba(var(--primary-color), 1.08)`, which is not a
723
+ colour: `--primary-color` is a hex, rgba() wants three
724
+ channels, and an alpha above 1 is out of range either way.
725
+ The declaration was invalid and dropped, so this button had
726
+ no background at all. It sits on `&__header`, which is
727
+ `var(--primary-color)`, so what it wants is a light lift off
728
+ that ground — the same treatment the equivalent control in
729
+ GenericFormBuilder.module.scss uses on its coloured header. */
730
+ background: rgba(255, 255, 255, 0.15);
718
731
  cursor: pointer;
719
732
  padding: 8px 8px;
720
733
  margin: 0;
721
734
  outline: none;
722
735
  border: none;
723
736
  border-radius: var(--br);
724
- display: block;
737
+ display: flex;
738
+ align-items: center;
739
+ justify-content: center;
725
740
  line-height: 1;
741
+ transition: background 0.2s ease;
742
+
743
+ &:hover {
744
+ background: rgba(255, 255, 255, 0.25);
745
+ }
726
746
 
727
747
  svg {
728
- color: var(--secondary-color);
748
+ width: 1.25rem;
749
+ height: 1.25rem;
750
+ flex: none;
751
+ /* The header behind this is `--primary-color`, so the
752
+ glyph is set against it rather than in a brand hue that
753
+ may be a near-match for it. Field.jsx also passes
754
+ `color="#FFF"`, which lucide writes as a `stroke`
755
+ attribute; this keeps the two in agreement. */
756
+ color: #fff;
729
757
  display: block;
730
758
  }
731
759
  }
@@ -242,23 +242,63 @@ input[type='file'] {
242
242
  justify-content: center;
243
243
  width: 2rem;
244
244
  height: 2rem;
245
+ /* The box is stated three ways on purpose. `width`/`height` alone is not a
246
+ box a consuming app cannot move: every one of these buttons lives under
247
+ whatever ambient `button { … }` rule the host stylesheet carries, and
248
+ with the library's own `* { box-sizing: border-box }` in play, a host
249
+ padding rule eats this button's *content* box rather than growing it.
250
+ The min-* pair holds the outer size and `box-sizing` states the
251
+ assumption rather than inheriting it — but see the svg block for what
252
+ actually keeps the glyph safe. */
253
+ min-width: 2rem;
254
+ min-height: 2rem;
255
+ box-sizing: border-box;
245
256
  flex: none;
246
257
  background: none;
247
258
  cursor: pointer;
248
259
  padding: 0;
249
260
  margin: 0;
250
261
  border: none;
251
- border-radius: var(--radius-sm);
262
+ border-radius: var(--radius-sm, 6px);
252
263
  line-height: 1;
253
- transition: background-color var(--speed) var(--ease);
264
+ transition: background-color var(--speed, 0.18s) var(--ease, ease);
254
265
 
255
266
  &:hover {
256
- background: var(--hover-color);
267
+ background: var(--hover-color, rgba(var(--paragraph-color-rgb, 34, 30, 51), 0.06));
257
268
  }
258
269
 
259
270
  svg {
260
- color: var(--muted-color);
271
+ /* This is the rule that stops the icon disappearing.
272
+
273
+ An `<svg width="24" height="24">` inside a flex container is a flex
274
+ item with the default `flex-shrink: 1`, so when the button's content
275
+ box is squeezed the glyph shrinks with it — all the way down.
276
+ Hartway's plant-floor stylesheet carries
277
+ `body.tablet-mode button:not([class*='InovuaReactDataGrid'])
278
+ { padding: 8px 14px }`,
279
+ which outweighs this module's single class (0,2,2 against 0,1,0) and
280
+ left a 2rem border-box button with a 4px content box; the X
281
+ collapsed with it and read as a smudge at the end of the header.
282
+
283
+ `flex: none` refuses to shrink, and the explicit width/height also
284
+ makes the glyph immune to the `width: 100%` sizing pattern used on
285
+ svgs elsewhere in this library. Both are needed: the size fixes the
286
+ basis, `flex: none` stops it being overruled. Sized here rather than
287
+ trusting the `size` prop at each call site, so every modal header
288
+ agrees on one glyph. */
289
+ width: 1.25rem;
290
+ height: 1.25rem;
291
+ flex: none;
292
+ /* Recessive, but a control the user has to be able to find has no
293
+ business being fainter than the body text around it. */
294
+ color: var(--muted-color, #6b7688);
261
295
  display: block;
296
+ transition: color var(--speed, 0.18s) var(--ease, ease);
297
+ }
298
+
299
+ &:hover svg,
300
+ &:focus-visible svg {
301
+ color: var(--paragraph-color, #374151);
262
302
  }
263
303
  }
264
304
 
@@ -295,43 +335,54 @@ input[type='file'] {
295
335
 
296
336
 
297
337
 
298
- .saveBtn {
299
- width: max-content;
300
- display: inline-block;
301
- position: relative;
302
- padding: 0.65rem 1rem;
303
- cursor: pointer;
304
- font-size: 1rem;
305
- color: var(--tertiary-color);
306
- text-decoration: none;
307
- overflow: hidden;
308
- background: var(--primary-color);
309
- border: 1px solid rgba(var(--primary-color--rgb), 1.1);
310
- border-radius: var(--br);
311
- outline: none;
312
- transition: all 0.2s cubic-bezier(0.85, 0, 0.15, 1) 0s;
313
- font-size: 1.25em;
338
+ /* The form's primary action (Save / View Report / Refresh).
314
339
 
315
- &:hover {
316
- color: var(--primary-color);
317
- background: var(--highlight-color);
318
- border: 1px solid rgba(var(--highlight-rgb), 1.05);
319
- }
340
+ This was a bespoke button — 1.25em type on 0.65rem padding, its own radius
341
+ token, its own transition curve, and a hover that swapped to pale blue on
342
+ blue. Sat next to `.btn` (the shared primary) in the same action bar, so the
343
+ two controls that are meant to read as a pair were a different height, a
344
+ different type size and a different hover. It is the same button as every
345
+ other primary in the library; it just needed to say so. */
346
+ .saveBtn {
347
+ @include button-primary;
320
348
  }
321
349
 
350
+ /* The inline form's action bar.
351
+
352
+ Was `position: fixed; bottom: 15px; right: 20px` with `float: left`
353
+ children: the buttons left the form entirely and parked in the corner of
354
+ the *viewport*. On a short form (the report filter rows in GenericDetail —
355
+ one row of dropdowns) that put "View Report" nowhere near the filters it
356
+ runs; on a form that returns a results grid it floated on top of the grid's
357
+ last rows and its horizontal scrollbar.
358
+
359
+ It is a footer to the form it belongs to, so it lives at the end of the
360
+ form. `sticky` keeps what the fixed positioning was actually for — the
361
+ action stays reachable while a long form or a 550px results grid is
362
+ scrolled — without detaching it from its panel. */
322
363
  .polActions {
323
- position: fixed;
324
- bottom: 15px;
325
- right: 20px;
326
- width: auto;
327
- z-index: 100;
364
+ position: sticky;
365
+ bottom: 0;
366
+ z-index: 5;
367
+ display: flex;
368
+ align-items: center;
369
+ justify-content: flex-end;
370
+ gap: var(--spacing-sm, 0.5rem);
371
+ width: 100%;
372
+ margin-top: var(--spacing-lg, 1.5rem);
373
+ padding: var(--spacing-sm, 0.5rem) 0;
374
+ /* Opaque, or the form scrolls through the bar while it is stuck. */
375
+ background: var(--surface-color, #fff);
376
+ border-top: 1px solid var(--border-color, #e1e1e1);
328
377
  }
329
378
 
330
- .polActions button {
331
- display: block;
332
- float: left;
333
- padding: 0.35em 1em;
334
- margin: 0 0.15em;
379
+ /* One primary per bar. Download / Sort / Save & Exit are alternatives to the
380
+ main action, not competitors: identical geometry, no fill. Previously every
381
+ button in here was a filled brand-blue block, so a filter row offering
382
+ "Download Report" and "View Report" gave no clue which one was the thing
383
+ you came to do. */
384
+ .polActions .btn {
385
+ @include button-secondary;
335
386
  }
336
387
 
337
388
  /* For in-place editing mode */
@@ -381,6 +432,37 @@ input[type='file'] {
381
432
  display: none;
382
433
  }
383
434
 
435
+ /* Empty result for `save.return.type: 'table'` forms. Deliberately not a
436
+ card and not an alert: an empty report is an ordinary outcome, so it is a
437
+ quiet block of centred text on the panel it would have filled, drawn with
438
+ the same hairline the rest of the page separates things with. */
439
+ .emptyResult {
440
+ display: flex;
441
+ flex-direction: column;
442
+ align-items: center;
443
+ justify-content: center;
444
+ gap: var(--spacing-xs, 0.25rem);
445
+ padding: var(--spacing-2xl, 3rem) var(--spacing-md, 1rem);
446
+ text-align: center;
447
+ border: 1px dashed var(--border-color, #e1e1e1);
448
+ border-radius: var(--radius-md, 10px);
449
+ background: var(--alternate-color, transparent);
450
+
451
+ p {
452
+ margin: 0;
453
+ font-size: var(--font-size-md, 0.9375rem);
454
+ font-weight: var(--font-weight-semibold, 600);
455
+ color: var(--heading-color, inherit);
456
+ }
457
+
458
+ span {
459
+ max-width: 42ch;
460
+ font-size: var(--font-size-sm, 0.8125rem);
461
+ line-height: 1.5;
462
+ color: var(--muted-color, #6e7276);
463
+ }
464
+ }
465
+
384
466
  .buttonContainer {
385
467
  display: flex;
386
468
  justify-content: center;
@@ -86,15 +86,31 @@
86
86
  gap: 0.5rem;
87
87
  }
88
88
 
89
+ /* The tab rail. Identical spec to `.grid__subnav` in GenericIndex.module.scss
90
+ — the two pages share one rail, so keep them in step.
91
+
92
+ It was a white panel under a 50px brand-tinted blur, holding one tinted blue
93
+ pill per tab. Every item therefore carried the same weight as the selected
94
+ one, and a seven-tab rail (the reports page) read as a stack of seven
95
+ buttons rather than as navigation with one thing chosen. 6.3's answer
96
+ everywhere else on the page is a flat surface, a hairline and quiet ink, so
97
+ that is what the rail gets: unselected tabs are plain text on the panel, and
98
+ the brand blue is spent once, on the tab you are actually on. */
89
99
  .grid__subnav {
100
+ /* `flex: 1` (basis 0) against `.grid__subcontent`'s `flex: 0 1 80%` is
101
+ left exactly as it was: `.grid__subrow` is `flex-wrap: wrap`, so a
102
+ flex-basis here large enough to overflow the row would wrap the content
103
+ panel onto its own line rather than shrink it. Only the treatment
104
+ changes below. */
90
105
  flex: 1;
91
- background: white;
106
+ background: var(--surface-color, #fff);
107
+ border: 1px solid var(--border-color, #e1e1e1);
92
108
  border-radius: var(--br);
93
109
  box-sizing: border-box;
94
- padding: 5px;
110
+ padding: var(--spacing-xs, 0.25rem);
95
111
  margin: 8px 0;
96
112
  height: auto;
97
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
113
+ box-shadow: none;
98
114
  }
99
115
 
100
116
  .grid__subnav > ul {
@@ -108,32 +124,54 @@
108
124
 
109
125
  .grid__subnav > ul li {
110
126
  width: 100%;
111
- margin-bottom: 3px;
127
+ margin-bottom: var(--spacing-xs, 0.25rem);
128
+ }
129
+
130
+ .grid__subnav > ul li:last-child {
131
+ margin-bottom: 0;
112
132
  }
113
133
 
114
134
  .grid__subnav > ul li a {
115
135
  width: 100%;
116
- padding: 0.35rem 1rem;
136
+ /* 0.35rem was a 5px-tall hit area for a control people click all day. */
137
+ padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
117
138
  box-sizing: border-box;
118
139
  display: block;
119
140
  text-decoration: none;
120
- background: rgba(var(--primary-rgb), 0.05);
121
- color: var(--primary-color);
122
- border-radius: 5px;
123
- transition: background 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
124
- outline: none;
141
+ background: none;
142
+ color: var(--text-color, #374151);
143
+ font-size: var(--font-size-sm, 0.8125rem);
144
+ font-weight: var(--font-weight-medium, 500);
145
+ line-height: 1.35;
146
+ border-radius: var(--radius-sm, 6px);
147
+ transition:
148
+ background-color var(--speed, 0.18s) var(--ease, ease),
149
+ color var(--speed, 0.18s) var(--ease, ease);
125
150
  cursor: pointer;
126
151
  }
127
152
 
128
153
  .grid__subnav > ul li:hover a {
129
- background: var(--primary-color);
130
- color: var(--background-color);
154
+ /* Hover is a hint, not a commitment. It used to fill solid brand blue —
155
+ indistinguishable from the selected tab, so passing the mouse across the
156
+ rail made it look as though the selection was following the cursor. */
157
+ background: var(--hover-color, rgba(0, 0, 0, 0.04));
158
+ color: var(--heading-color, inherit);
131
159
  }
132
160
 
133
161
  .grid__subnav > ul li .subactive {
134
- font-weight: 700;
162
+ font-weight: var(--font-weight-semibold, 600);
135
163
  background: var(--primary-color);
136
- color: var(--background-color);
164
+ /* Was --background-color, the page's off-white; white is what the button
165
+ and nav-pill treatments elsewhere put on this blue. */
166
+ color: #fff;
167
+ }
168
+
169
+ /* `outline: none` used to sit on the link with nothing put back, so the rail
170
+ was unusable by keyboard. The app's own ring token, same as every other
171
+ control. */
172
+ .grid__subnav > ul li a:focus-visible {
173
+ outline: none;
174
+ box-shadow: var(--focus-ring, 0 0 0 3px rgba(0, 118, 192, 0.32));
137
175
  }
138
176
 
139
177
  .grid__minheight {
@@ -146,9 +184,12 @@
146
184
 
147
185
  .grid__subcontent {
148
186
  flex: 0 1 80%;
149
- background: var(--tertiary-color);
187
+ background: var(--surface-color, #fff);
150
188
  border-radius: var(--br);
151
- border: 1px solid rgba(var(--primary-rgb), 0.05);
189
+ /* Was the brand blue at 5% — a tint so faint the panel had no edge at all
190
+ while the rail beside it did. The shared hairline, same as every other
191
+ surface on the page. */
192
+ border: 1px solid var(--border-color, #e1e1e1);
152
193
  box-sizing: border-box;
153
194
  padding: 2px;
154
195
  margin: 8px 0;
@@ -163,6 +204,24 @@
163
204
  margin: 0 0 30px 0;
164
205
  }
165
206
 
207
+ /* The tab panel's own inset.
208
+
209
+ `.gridtxt` had no rule at all, and its container (`.grid__subcontent`,
210
+ `.grid__full`) carries `padding: 2px` — so a panel's section heading and any
211
+ form inside it sat 2px from the panel border. Only the overview `<ul>` was
212
+ readable, because it padded itself. On the reports page that meant the
213
+ filter row's dropdowns and date pickers were flush against the panel edge.
214
+
215
+ The inset belongs to the panel, not to one child of it, so it is stated
216
+ here once and the `<ul>` below gives up its horizontal padding to match.
217
+ The `<ul>`'s vertical padding stays: with several heading/list pairs in one
218
+ `.gridtxt` (the overview tabs) that is what separates one section from the
219
+ next. */
220
+ .gridtxt {
221
+ padding: var(--spacing-md, 1rem) var(--spacing-lg, 1.5rem)
222
+ var(--spacing-lg, 1.5rem);
223
+ }
224
+
166
225
  .gridtxt__header {
167
226
  width: 100%;
168
227
  display: block;
@@ -192,6 +251,21 @@
192
251
  font-weight: 700;
193
252
  }
194
253
 
254
+ /* Optional one-line subtitle under a section heading (GenericDetail renders it
255
+ from a tab's `description`). Sits in the heading's own space — it belongs to
256
+ the label, not to the content — so it takes over the heading's bottom margin
257
+ rather than adding a third gap. */
258
+ .gridtxt__description {
259
+ /* Pulls back half of the heading's 1rem bottom margin, so the subtitle sits
260
+ close under its heading and the whole block is separated from the
261
+ content below rather than floating between the two. */
262
+ margin: calc(-1 * var(--spacing-sm, 0.5rem)) 0 var(--spacing-lg, 1.5rem);
263
+ max-width: 70ch;
264
+ font-size: var(--font-size-sm, 0.8125rem);
265
+ line-height: 1.55;
266
+ color: var(--muted-color, #6e7276);
267
+ }
268
+
195
269
  .gridtxt > ul {
196
270
  width: 100%;
197
271
  display: flex;
@@ -200,7 +274,9 @@
200
274
  flex-wrap: wrap;
201
275
  list-style: none;
202
276
  box-sizing: border-box;
203
- padding: 0.85rem;
277
+ /* Horizontal padding removed — `.gridtxt` owns the panel inset now (see
278
+ the note there). The vertical padding separates stacked sections. */
279
+ padding: 0.85rem 0;
204
280
  margin: 0;
205
281
  }
206
282
 
@@ -741,8 +741,13 @@ select:not(:placeholder-shown) + .fi__span {
741
741
  letter-spacing: 0.01em;
742
742
  }
743
743
 
744
+ /* The form builder's modal close control (GenericFormBuilder.jsx, two call
745
+ sites). Hardened the same way as the header close in Form.module.scss — see
746
+ the note on the svg block there. */
744
747
  .modal__close {
745
748
  width: max-content;
749
+ min-width: 2rem;
750
+ min-height: 2rem;
746
751
  position: absolute;
747
752
  right: 8px;
748
753
  top: 8px;
@@ -754,7 +759,9 @@ select:not(:placeholder-shown) + .fi__span {
754
759
  outline: none;
755
760
  border: none;
756
761
  border-radius: 6px;
757
- display: block;
762
+ display: flex;
763
+ align-items: center;
764
+ justify-content: center;
758
765
  line-height: 1;
759
766
  transition: background 0.2s ease;
760
767
 
@@ -764,7 +771,13 @@ select:not(:placeholder-shown) + .fi__span {
764
771
  }
765
772
 
766
773
  .modal__close svg {
767
- color: var(--secondary-color);
774
+ width: 1.25rem;
775
+ height: 1.25rem;
776
+ flex: none;
777
+ /* `.modal__header` above is `var(--primary-color)`, so the glyph is set
778
+ against that ground rather than in a second brand hue that may be a
779
+ near-match for it — which is what `--secondary-color` was doing here. */
780
+ color: #fff;
768
781
  display: block;
769
782
  }
770
783
 
@@ -114,15 +114,19 @@
114
114
  gap: 0.5rem;
115
115
  }
116
116
 
117
+ /* The tab rail. Identical spec to `.grid__subnav` in GenericDetail.module.scss
118
+ — the two pages share one rail, so keep them in step; the reasoning for the
119
+ treatment is written out there. */
117
120
  .grid__subnav {
118
121
  flex: 1;
119
- background: white;
122
+ background: var(--surface-color, #fff);
123
+ border: 1px solid var(--border-color, #e1e1e1);
120
124
  border-radius: var(--br);
121
125
  box-sizing: border-box;
122
- padding: 5px;
126
+ padding: var(--spacing-xs, 0.25rem);
123
127
  margin: 8px 0;
124
128
  height: auto;
125
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
129
+ box-shadow: none;
126
130
  }
127
131
 
128
132
  .grid__subnav > ul {
@@ -136,32 +140,45 @@
136
140
 
137
141
  .grid__subnav > ul li {
138
142
  width: 100%;
139
- margin-bottom: 3px;
143
+ margin-bottom: var(--spacing-xs, 0.25rem);
144
+ }
145
+
146
+ .grid__subnav > ul li:last-child {
147
+ margin-bottom: 0;
140
148
  }
141
149
 
142
150
  .grid__subnav > ul li a {
143
151
  width: 100%;
144
- padding: 0.35rem 1rem;
152
+ padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
145
153
  box-sizing: border-box;
146
154
  display: block;
147
155
  text-decoration: none;
148
- background: rgba(var(--primary-rgb), 0.05);
149
- color: var(--primary-color);
150
- border-radius: 5px;
151
- transition: background 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
152
- outline: none;
156
+ background: none;
157
+ color: var(--text-color, #374151);
158
+ font-size: var(--font-size-sm, 0.8125rem);
159
+ font-weight: var(--font-weight-medium, 500);
160
+ line-height: 1.35;
161
+ border-radius: var(--radius-sm, 6px);
162
+ transition:
163
+ background-color var(--speed, 0.18s) var(--ease, ease),
164
+ color var(--speed, 0.18s) var(--ease, ease);
153
165
  cursor: pointer;
154
166
  }
155
167
 
156
168
  .grid__subnav > ul li:hover a {
157
- background: var(--primary-color);
158
- color: var(--tertiary-color);
169
+ background: var(--hover-color, rgba(0, 0, 0, 0.04));
170
+ color: var(--heading-color, inherit);
159
171
  }
160
172
 
161
173
  .grid__subnav > ul li .subactive {
162
- font-weight: 700;
174
+ font-weight: var(--font-weight-semibold, 600);
163
175
  background: var(--primary-color);
164
- color: var(--tertiary-color);
176
+ color: #fff;
177
+ }
178
+
179
+ .grid__subnav > ul li a:focus-visible {
180
+ outline: none;
181
+ box-shadow: var(--focus-ring, 0 0 0 3px rgba(0, 118, 192, 0.32));
165
182
  }
166
183
 
167
184
  .grid__subnav__alternate {
@@ -1,3 +1,17 @@
1
+ /**
2
+ * The tab rail's own stylesheet, which predates the 6.3 token language: it was
3
+ * still drawing its own card (a hardcoded rgba drop shadow and radius) and its
4
+ * own type scale (0.85/0.9rem literals) around items that the host surface —
5
+ * `.grid__subnav` in GenericIndex/GenericDetail — already frames. Two nested
6
+ * cards, two shadows, and a rail whose type did not match anything else on the
7
+ * page. Everything here now reads the same tokens as the rest of the library,
8
+ * and the framing is left to the surface it sits on.
9
+ *
10
+ * Literal fallbacks throughout: a bare `var()` for a token a consuming app has
11
+ * not declared is invalid at computed-value time and the whole declaration is
12
+ * dropped, which is exactly how a rail like this loses its padding.
13
+ */
14
+
1
15
  .tableFilterContainer {
2
16
  width: 100%;
3
17
  position: relative;
@@ -9,11 +23,11 @@
9
23
  list-style: none;
10
24
  padding: 0;
11
25
  margin: 0;
12
- font-size: 0.9rem;
26
+ font-size: var(--font-size-sm, 0.8125rem);
13
27
  width: 100%;
14
- border-radius: 8px;
15
- overflow: hidden;
16
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
28
+ /* No radius, no shadow, no `overflow: hidden`. The panel around this list
29
+ supplies the surface; the clipping additionally cut the focus ring off
30
+ the first and last items. */
17
31
  }
18
32
 
19
33
  /* Parent category styling */
@@ -29,19 +43,23 @@
29
43
  display: flex;
30
44
  justify-content: space-between;
31
45
  align-items: center;
32
- padding: 8px 16px;
33
- font-weight: 600;
34
- font-size: 0.9rem;
46
+ padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
47
+ border-radius: var(--radius-sm, 6px);
48
+ font-weight: var(--font-weight-semibold, 600);
49
+ font-size: var(--font-size-sm, 0.8125rem);
35
50
  background-color: var(--primary-color, #000);
36
- color: var(--tertiary-color, #fff);
37
- transition: all 0.2s ease;
51
+ color: #fff;
52
+ /* Was `all 0.2s`, which animates layout properties too. */
53
+ transition:
54
+ background-color var(--speed, 0.18s) var(--ease, ease),
55
+ color var(--speed, 0.18s) var(--ease, ease);
38
56
  position: relative;
39
57
  text-decoration: none;
40
58
  }
41
59
 
42
60
  .parentLink:hover {
43
- background-color: var(--secondary-color, #222) !important;
44
- color: var(--tertiary-color, #fff) !important;
61
+ background-color: var(--primary-color-darker, #222) !important;
62
+ color: #fff !important;
45
63
  text-decoration: none;
46
64
  }
47
65
 
@@ -63,7 +81,9 @@
63
81
  overflow: hidden;
64
82
  transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
65
83
  opacity: 0;
66
- background-color: var(--bg-color, #f5f5f5);
84
+ /* Flush with the rail. The child links carry their own hover surface now,
85
+ so a tint behind the whole submenu only made a second panel edge. */
86
+ background: none;
67
87
  }
68
88
 
69
89
  .visibleMenu {
@@ -74,7 +94,7 @@
74
94
  .childItem {
75
95
  margin: 0;
76
96
  position: relative;
77
- border-bottom: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.03);
97
+ border-bottom: 1px solid var(--border-color, #e1e1e1);
78
98
  }
79
99
 
80
100
  .childItem:last-child {
@@ -82,18 +102,20 @@
82
102
  }
83
103
 
84
104
  .childLink {
85
- padding: 6px 16px 6px 26px;
86
- background-color: var(--bg-color, #f5f5f5);
87
- color: var(--paragraph-color, #333);
88
- font-weight: normal;
89
- font-size: 0.85rem;
105
+ padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem) var(--spacing-sm, 0.5rem)
106
+ var(--spacing-xl, 1.75rem);
107
+ background: none;
108
+ color: var(--text-color, #333);
109
+ font-weight: var(--font-weight-regular, 400);
110
+ font-size: var(--font-size-sm, 0.8125rem);
111
+ border-radius: var(--radius-sm, 6px);
90
112
  display: block;
91
113
  text-decoration: none;
92
114
  }
93
115
 
94
116
  .childLink:hover {
95
117
  background-color: var(--hover-color, #e8e8e8) !important;
96
- color: var(--primary-color, #000) !important;
118
+ color: var(--heading-color, #000) !important;
97
119
  text-decoration: none;
98
120
  }
99
121
 
@@ -104,37 +126,55 @@
104
126
  color: rgba(var(--paragraph-color-rgb, 153, 153, 153), 0.6);
105
127
  }
106
128
 
107
- /* Active states */
129
+ /* Active states. The brand fill is the one loud thing in the rail, and it
130
+ marks exactly one item — which is what makes it readable as a selection
131
+ rather than as decoration. */
108
132
  .subactive {
109
- font-weight: 600;
133
+ font-weight: var(--font-weight-semibold, 600);
110
134
  background: var(--primary-color, #000) !important;
111
- color: var(--tertiary-color, #fff) !important;
135
+ /* Was --tertiary-color, which several projects set to their page
136
+ off-white; white is what sits on this blue elsewhere. */
137
+ color: #fff !important;
112
138
  }
113
139
 
114
140
  .subactivechildren {
115
- font-weight: 500;
116
- background-color: var(--hover-color, #f0f0f0) !important;
141
+ font-weight: var(--font-weight-medium, 500);
142
+ background-color: var(--primary-color-light, #f0f0f0) !important;
117
143
  color: var(--primary-color, #000) !important;
118
144
  }
119
145
 
120
- /* Base link styling */
146
+ /* Base link styling. Unselected tabs are text on the panel, not tinted
147
+ blocks — see the note on `.grid__subnav` in GenericDetail.module.scss. */
121
148
  .link {
122
149
  display: block;
123
- padding: 6px 16px;
150
+ padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
124
151
  text-decoration: none;
125
- background: var(--bg-color, #f5f5f5);
126
- color: var(--paragraph-color, #333);
127
- font-size: 0.85rem;
128
- transition: all 0.2s ease;
152
+ background: none;
153
+ color: var(--text-color, #333);
154
+ font-size: var(--font-size-sm, 0.8125rem);
155
+ font-weight: var(--font-weight-medium, 500);
156
+ border-radius: var(--radius-sm, 6px);
157
+ transition:
158
+ background-color var(--speed, 0.18s) var(--ease, ease),
159
+ color var(--speed, 0.18s) var(--ease, ease);
129
160
  cursor: pointer;
130
161
  }
131
162
 
132
163
  .link:hover {
133
164
  background: var(--hover-color, #e8e8e8) !important;
134
- color: var(--primary-color, #000) !important;
165
+ /* Was the brand blue, which on hover made an unselected tab look like the
166
+ selected one. */
167
+ color: var(--heading-color, #000) !important;
135
168
  text-decoration: none;
136
169
  }
137
170
 
171
+ .link:focus-visible,
172
+ .childLink:focus-visible,
173
+ .parentLink:focus-visible {
174
+ outline: none;
175
+ box-shadow: var(--focus-ring, 0 0 0 3px rgba(0, 118, 192, 0.32));
176
+ }
177
+
138
178
  .activetab button {
139
179
  font-weight: bold;
140
180
  background: var(--hover-color, #e0e0e0) !important;
@@ -146,19 +186,20 @@
146
186
  display: none;
147
187
  align-items: center;
148
188
  justify-content: space-between;
149
- padding: 12px 15px;
189
+ padding: var(--spacing-sm, 0.75rem) var(--spacing-md, 1rem);
150
190
  background: var(--primary-color, #000);
151
- color: var(--tertiary-color, white);
152
- border-radius: 8px;
191
+ color: #fff;
192
+ border-radius: var(--radius-sm, 6px);
153
193
  cursor: pointer;
154
- margin-bottom: 10px;
194
+ margin-bottom: var(--spacing-sm, 0.5rem);
155
195
  border: none;
156
- box-shadow: 0 1px 3px rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.2);
196
+ box-shadow: var(--shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.2));
157
197
  }
158
198
 
159
199
  .activeFilterLabel {
160
- font-weight: 600;
161
- color: var(--tertiary-color, white);
200
+ font-weight: var(--font-weight-semibold, 600);
201
+ font-size: var(--font-size-sm, 0.8125rem);
202
+ color: #fff;
162
203
  }
163
204
 
164
205
  .mobileMenuIcon {
@@ -175,7 +216,7 @@
175
216
  display: block;
176
217
  height: 2px;
177
218
  width: 100%;
178
- background-color: var(--tertiary-color, white);
219
+ background-color: #fff;
179
220
  border-radius: 2px;
180
221
  transition: all 0.3s ease;
181
222
  }
@@ -206,20 +247,17 @@
206
247
  display: none; /* Hide hamburger menu on tablets */
207
248
  }
208
249
 
209
- /* Slightly adjust padding for better tablet experience */
210
- .parentLink {
211
- padding: 9px 16px;
212
- font-size: 0.9rem;
213
- }
214
-
250
+ /* Roomier tap targets. Only the padding moves: the literal 0.85/0.9rem
251
+ sizes that used to sit here re-declared the rail's type at a scale
252
+ nothing else on the page uses, so the labels changed size at 1024px for
253
+ no reason. Density is a padding question. */
254
+ .parentLink,
215
255
  .link {
216
- padding: 9px 16px;
217
- font-size: 0.85rem;
256
+ padding: 0.65rem var(--spacing-md, 1rem);
218
257
  }
219
258
 
220
259
  .childLink {
221
- padding: 7px 16px 7px 26px;
222
- font-size: 0.85rem;
260
+ padding: 0.65rem var(--spacing-md, 1rem) 0.65rem var(--spacing-xl, 1.75rem);
223
261
  }
224
262
  }
225
263
 
@@ -240,25 +278,21 @@
240
278
  display: block;
241
279
  max-height: 1000px;
242
280
  overflow-y: auto;
243
- border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.1);
244
- border-radius: 8px;
245
- margin-bottom: 10px;
246
- box-shadow: 0 2px 5px rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.1);
247
- }
248
-
249
- .parentLink {
250
- padding: 10px 16px; /* Slightly larger touch targets for mobile */
251
- font-size: 0.9rem;
281
+ padding: var(--spacing-xs, 0.25rem);
282
+ border: 1px solid var(--border-color, #e1e1e1);
283
+ border-radius: var(--br, 8px);
284
+ margin-bottom: var(--spacing-sm, 0.5rem);
285
+ box-shadow: var(--shadow-sm, 0 2px 5px rgba(0, 0, 0, 0.1));
252
286
  }
253
287
 
288
+ /* Larger tap targets on a phone; the type scale is left alone, as above. */
289
+ .parentLink,
254
290
  .link {
255
- padding: 10px 16px; /* Slightly larger touch targets for mobile */
256
- font-size: 0.85rem;
291
+ padding: 0.75rem var(--spacing-md, 1rem);
257
292
  }
258
293
 
259
294
  .childLink {
260
- padding: 8px 16px 8px 26px;
261
- font-size: 0.85rem;
295
+ padding: 0.75rem var(--spacing-md, 1rem) 0.75rem var(--spacing-xl, 1.75rem);
262
296
  }
263
297
 
264
298
  .collapsibleMenu {