@godxjp/ui 18.15.3 → 19.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/date-format-labels.d.ts +1 -1
- package/dist/app/time-format-labels.d.ts +1 -1
- package/dist/components/data-display/badge.d.ts +1 -1
- package/dist/components/data-display/card.d.ts +4 -13
- package/dist/components/data-display/card.js +2 -15
- package/dist/components/data-display/data-table.d.ts +3 -8
- package/dist/components/data-display/data-table.js +106 -68
- package/dist/components/data-display/descriptions.js +34 -5
- package/dist/components/data-display/empty-state.js +1 -1
- package/dist/components/data-display/permission-matrix.js +7 -7
- package/dist/components/data-display/popover.js +2 -9
- package/dist/components/data-display/qr-code.d.ts +1 -1
- package/dist/components/data-display/scroll-area.d.ts +3 -1
- package/dist/components/data-display/scroll-area.js +150 -16
- package/dist/components/data-display/table.js +12 -3
- package/dist/components/data-entry/branch-scope-picker.js +4 -4
- package/dist/components/data-entry/calendar.js +15 -24
- package/dist/components/data-entry/cascader.js +67 -71
- package/dist/components/data-entry/checkbox.js +9 -1
- package/dist/components/data-entry/command.d.ts +6 -6
- package/dist/components/data-entry/date-picker.js +28 -15
- package/dist/components/data-entry/date-range-picker.js +10 -10
- package/dist/components/data-entry/form-field.js +7 -3
- package/dist/components/data-entry/index.d.ts +2 -1
- package/dist/components/data-entry/input.js +8 -19
- package/dist/components/data-entry/label.js +4 -2
- package/dist/components/data-entry/month-picker.js +16 -15
- package/dist/components/data-entry/month-range-picker.js +17 -16
- package/dist/components/data-entry/radio.js +6 -1
- package/dist/components/data-entry/search-select.js +29 -29
- package/dist/components/data-entry/select.js +15 -18
- package/dist/components/data-entry/switch.js +7 -1
- package/dist/components/data-entry/textarea.d.ts +56 -0
- package/dist/components/data-entry/textarea.js +103 -40
- package/dist/components/data-entry/time-picker.js +52 -52
- package/dist/components/data-entry/toggle-group.d.ts +1 -0
- package/dist/components/data-entry/toggle.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +14 -15
- package/dist/components/data-entry/tree-select.js +16 -22
- package/dist/components/data-entry/upload-crop-dialog.js +4 -4
- package/dist/components/data-entry/upload.d.ts +1 -1
- package/dist/components/data-entry/upload.js +46 -46
- package/dist/components/feedback/alert.js +7 -14
- package/dist/components/feedback/dialog.d.ts +23 -32
- package/dist/components/feedback/dialog.js +16 -13
- package/dist/components/feedback/sheet.d.ts +1 -1
- package/dist/components/feedback/sheet.js +14 -14
- package/dist/components/feedback/skeleton.js +13 -10
- package/dist/components/feedback/sonner.d.ts +1 -1
- package/dist/components/feedback/sonner.js +17 -8
- package/dist/components/feedback/tooltip.js +1 -1
- package/dist/components/general/activity.d.ts +44 -0
- package/dist/components/general/activity.js +65 -0
- package/dist/components/general/button.d.ts +2 -2
- package/dist/components/general/button.js +11 -24
- package/dist/components/general/index.d.ts +2 -0
- package/dist/components/general/index.js +2 -0
- package/dist/components/layout/app-shell.js +5 -3
- package/dist/components/layout/auth-divider.d.ts +9 -2
- package/dist/components/layout/auth-divider.js +7 -11
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +32 -20
- package/dist/components/layout/resizable.d.ts +12 -12
- package/dist/components/layout/separator.d.ts +1 -0
- package/dist/components/layout/service-role-panel.js +47 -42
- package/dist/components/layout/sidebar.js +13 -1
- package/dist/components/layout/split-pane.d.ts +12 -1
- package/dist/components/layout/split-pane.js +13 -4
- package/dist/components/navigation/app-setting-picker.js +5 -14
- package/dist/components/navigation/dropdown-menu.js +10 -16
- package/dist/components/navigation/menubar.d.ts +3 -6
- package/dist/components/navigation/pagination-utils.d.ts +15 -2
- package/dist/components/navigation/pagination-utils.js +27 -21
- package/dist/components/navigation/pagination.d.ts +4 -4
- package/dist/components/navigation/pagination.js +2 -2
- package/dist/components/navigation/steps.js +31 -67
- package/dist/components/navigation/tabs.js +17 -5
- package/dist/components/query/infinite-query-state.js +2 -2
- package/dist/components/ui/accordion.d.ts +1 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/avatar.js +29 -10
- package/dist/components/ui/input-otp.d.ts +8 -6
- package/dist/components/ui/rating.js +1 -1
- package/dist/components/ui/separator.d.ts +27 -2
- package/dist/components/ui/separator.js +42 -11
- package/dist/components/ui/toggle-group.d.ts +15 -5
- package/dist/components/ui/toggle-group.js +43 -18
- package/dist/components/ui/toggle.d.ts +66 -5
- package/dist/components/ui/toggle.js +64 -9
- package/dist/i18n/messages/en.json +8 -0
- package/dist/i18n/messages/ja.json +8 -0
- package/dist/i18n/messages/vi.json +8 -0
- package/dist/lib/control-styles.d.ts +14 -0
- package/dist/lib/control-styles.js +2 -0
- package/dist/props/components/data-display.prop.d.ts +85 -3
- package/dist/props/components/data-entry.prop.d.ts +17 -0
- package/dist/props/components/general.prop.d.ts +52 -1
- package/dist/props/components/layout.prop.d.ts +137 -2
- package/dist/props/registry.d.ts +116 -5
- package/dist/props/registry.js +165 -4
- package/dist/props/vocabulary/index.d.ts +2 -2
- package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
- package/dist/props/vocabulary/layout.prop.d.ts +8 -0
- package/dist/styles/alert-layout.css +69 -3
- package/dist/styles/badge-layout.css +2 -2
- package/dist/styles/base.css +6 -0
- package/dist/styles/card-layout.css +27 -7
- package/dist/styles/control.css +1150 -29
- package/dist/styles/data-display-layout.css +185 -11
- package/dist/styles/data-entry-layout.css +352 -1
- package/dist/styles/density.css +2 -2
- package/dist/styles/dialog-layout.css +203 -4
- package/dist/styles/focus-ring.css +155 -0
- package/dist/styles/form-layout.css +50 -5
- package/dist/styles/index.css +1 -0
- package/dist/styles/layout.css +412 -23
- package/dist/styles/motion.css +178 -0
- package/dist/styles/navigation-layout.css +398 -22
- package/dist/styles/shell-layout.css +137 -39
- package/dist/styles/table-layout.css +153 -22
- package/dist/styles/toggle.css +59 -0
- package/dist/tokens/base.css +5 -0
- package/dist/tokens/components/activity.css +64 -0
- package/dist/tokens/components/badge.css +7 -0
- package/dist/tokens/components/card.css +14 -10
- package/dist/tokens/components/chart.css +1 -1
- package/dist/tokens/components/control.css +358 -8
- package/dist/tokens/components/data-display.css +75 -0
- package/dist/tokens/components/data-entry.css +25 -0
- package/dist/tokens/components/descriptions.css +22 -0
- package/dist/tokens/components/feedback.css +143 -4
- package/dist/tokens/components/form.css +16 -0
- package/dist/tokens/components/legal-document.css +5 -1
- package/dist/tokens/components/navigation.css +109 -0
- package/dist/tokens/components/scroll-area.css +21 -0
- package/dist/tokens/components/separator.css +52 -0
- package/dist/tokens/components/shell.css +132 -20
- package/dist/tokens/components/table.css +52 -3
- package/dist/tokens/components/toggle.css +82 -0
- package/dist/tokens/components/upload.css +107 -0
- package/dist/tokens/foundation.css +171 -10
- package/dist/tokens/semantic/layout.css +120 -1
- package/package.json +84 -68
- package/scripts/ui-audit.mjs +51 -2
package/dist/styles/layout.css
CHANGED
|
@@ -4,19 +4,94 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
@layer components {
|
|
7
|
+
/* SEPARATOR — a tokenized rule, optionally INTERRUPTED by a label (gh#308).
|
|
8
|
+
*
|
|
9
|
+
* Two shapes share one class. WITHOUT a label the element IS the rule, exactly as before: the
|
|
10
|
+
* `:not([data-labelled])` guard keeps the plain form byte-identical (the `1px` and the `--border`
|
|
11
|
+
* are now `--separator-rule-{size,color}`, whose defaults are those same values). WITH a label
|
|
12
|
+
* the root becomes the three-cell grid `rule · label · rule` — `minmax(0, 1fr)` on both halves so
|
|
13
|
+
* a long ja/vi label clamps the rules instead of overflowing the container, and `auto` on the
|
|
14
|
+
* label so it is never squeezed to zero. `labelAlign` only re-measures the SHORT half, and it is
|
|
15
|
+
* a grid TRACK on the inline axis, so start/end swap under `dir="rtl"` with no extra rule.
|
|
16
|
+
*
|
|
17
|
+
* `tone` re-points the two colour knobs on the root rather than restating `background`/`color`
|
|
18
|
+
* per tone, so one declaration paints both shapes and a service override still wins. */
|
|
7
19
|
.ui-separator {
|
|
8
20
|
flex-shrink: 0;
|
|
9
|
-
background: hsl(var(--border));
|
|
10
21
|
}
|
|
11
22
|
|
|
12
|
-
.ui-separator[data-orientation="horizontal"] {
|
|
23
|
+
.ui-separator:not([data-labelled])[data-orientation="horizontal"] {
|
|
13
24
|
width: 100%;
|
|
14
|
-
height:
|
|
25
|
+
height: var(--separator-rule-size);
|
|
26
|
+
background: hsl(var(--separator-rule-color, var(--border)));
|
|
15
27
|
}
|
|
16
28
|
|
|
17
|
-
.ui-separator[data-orientation="vertical"] {
|
|
18
|
-
width:
|
|
29
|
+
.ui-separator:not([data-labelled])[data-orientation="vertical"] {
|
|
30
|
+
width: var(--separator-rule-size);
|
|
19
31
|
height: 100%;
|
|
32
|
+
background: hsl(var(--separator-rule-color, var(--border)));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.ui-separator[data-labelled] {
|
|
36
|
+
display: grid;
|
|
37
|
+
width: 100%;
|
|
38
|
+
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: var(--separator-label-gap, var(--space-3));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ui-separator[data-labelled][data-label-align="start"] {
|
|
44
|
+
grid-template-columns: var(--separator-label-inset, var(--space-6)) auto minmax(0, 1fr);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.ui-separator[data-labelled][data-label-align="end"] {
|
|
48
|
+
grid-template-columns: minmax(0, 1fr) auto var(--separator-label-inset, var(--space-6));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ui-separator-rule {
|
|
52
|
+
block-size: var(--separator-rule-size);
|
|
53
|
+
background: hsl(var(--separator-rule-color, var(--border)));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.ui-separator-label {
|
|
57
|
+
min-inline-size: 0;
|
|
58
|
+
color: hsl(var(--separator-label-color, var(--muted-foreground)));
|
|
59
|
+
font-size: var(--separator-label-font-size);
|
|
60
|
+
font-weight: var(--separator-label-font-weight);
|
|
61
|
+
line-height: var(--separator-label-line-height);
|
|
62
|
+
/* A single unbroken token (a URL, a long ja compound) must still wrap rather than push the
|
|
63
|
+
* grid past its container. */
|
|
64
|
+
overflow-wrap: anywhere;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.ui-separator[data-tone="muted"] {
|
|
68
|
+
--separator-rule-color: var(--separator-tone-muted-rule-color, var(--border));
|
|
69
|
+
--separator-label-color: var(--separator-tone-muted-label-color, var(--muted-foreground));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.ui-separator[data-tone="primary"] {
|
|
73
|
+
--separator-rule-color: var(--separator-tone-primary-rule-color, var(--primary));
|
|
74
|
+
--separator-label-color: var(--separator-tone-primary-label-color, var(--primary));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ui-separator[data-tone="success"] {
|
|
78
|
+
--separator-rule-color: var(--separator-tone-success-rule-color, var(--success));
|
|
79
|
+
--separator-label-color: var(--separator-tone-success-label-color, var(--success));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.ui-separator[data-tone="warning"] {
|
|
83
|
+
--separator-rule-color: var(--separator-tone-warning-rule-color, var(--warning));
|
|
84
|
+
--separator-label-color: var(--separator-tone-warning-label-color, var(--warning));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ui-separator[data-tone="destructive"] {
|
|
88
|
+
--separator-rule-color: var(--separator-tone-destructive-rule-color, var(--destructive));
|
|
89
|
+
--separator-label-color: var(--separator-tone-destructive-label-color, var(--destructive));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.ui-separator[data-tone="info"] {
|
|
93
|
+
--separator-rule-color: var(--separator-tone-info-rule-color, var(--info));
|
|
94
|
+
--separator-label-color: var(--separator-tone-info-label-color, var(--info));
|
|
20
95
|
}
|
|
21
96
|
|
|
22
97
|
.ui-aspect-ratio {
|
|
@@ -291,6 +366,53 @@
|
|
|
291
366
|
max-block-size: var(--master-detail-master-viewport-standard);
|
|
292
367
|
}
|
|
293
368
|
|
|
369
|
+
/* ── ServiceRolePanel (gh#319) ─────────────────────────────────────────────────
|
|
370
|
+
* MasterDetail above owns every bit of the two-track geometry; what follows is only the role
|
|
371
|
+
* LIST that sits in the master rail. `.ui-service-role-panel` was a bare hook (class emitted,
|
|
372
|
+
* no rule anywhere), so all of this used to be Tailwind literals on the component. */
|
|
373
|
+
.ui-service-role-panel-list {
|
|
374
|
+
display: flex;
|
|
375
|
+
flex-direction: column;
|
|
376
|
+
gap: var(--service-role-panel-list-gap);
|
|
377
|
+
list-style: none;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/* One role: the select button plus the optional delete button, as SIBLINGS (never nested
|
|
381
|
+
* interactive controls). Deliberately NOT `.ui-inline-xs` — that helper wraps, and the icon
|
|
382
|
+
* button must stay on the row beside the flex-1 select button at every width. */
|
|
383
|
+
.ui-service-role-panel-row {
|
|
384
|
+
display: flex;
|
|
385
|
+
align-items: center;
|
|
386
|
+
gap: var(--service-role-panel-row-gap);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* ORDERING TRAP (gh#319): this needs the `.ui-service-role-panel` ancestor. The role button is
|
|
390
|
+
* a real `<Button>`, and `.ui-button--md { padding-block: var(--button-space-block) }` lives in
|
|
391
|
+
* control.css, which styles/index.css imports AFTER layout.css — same `@layer components`, so
|
|
392
|
+
* at equal (0,1,0) specificity control.css would win and the row would snap back to the single
|
|
393
|
+
* control height. (0,2,0) settles it. `h-auto` and `flex-1` stay UTILITIES on the component for
|
|
394
|
+
* the same reason inverted: they must beat the same rule from a layer that always outranks it. */
|
|
395
|
+
.ui-service-role-panel .ui-service-role-panel-item {
|
|
396
|
+
padding-block: var(--service-role-panel-item-padding-block);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* The button's label column. `min-inline-size: 0` is what lets the name and the meta line
|
|
400
|
+
* truncate instead of forcing the rail wider (the flexbox min-content default). */
|
|
401
|
+
.ui-service-role-panel-item-body {
|
|
402
|
+
display: flex;
|
|
403
|
+
flex-direction: column;
|
|
404
|
+
align-items: flex-start;
|
|
405
|
+
min-inline-size: 0;
|
|
406
|
+
line-height: var(--line-height-tight);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.ui-service-role-panel-item-title {
|
|
410
|
+
display: inline-flex;
|
|
411
|
+
align-items: center;
|
|
412
|
+
gap: var(--service-role-panel-item-title-gap);
|
|
413
|
+
max-inline-size: 100%;
|
|
414
|
+
}
|
|
415
|
+
|
|
294
416
|
@container responsive-grid (min-width: 40rem) {
|
|
295
417
|
.ui-responsive-grid {
|
|
296
418
|
grid-template-columns: repeat(var(--responsive-grid-sm, 2), minmax(0, 1fr));
|
|
@@ -324,6 +446,17 @@
|
|
|
324
446
|
grid-template-columns: minmax(0, 1fr);
|
|
325
447
|
}
|
|
326
448
|
|
|
449
|
+
/* CLOSED rail — `aside={null}`. The `<aside>` element is gone, so the pane is one full-width
|
|
450
|
+
* column and there is nothing left for the gap to separate. The wrappers stay mounted (that is
|
|
451
|
+
* what keeps `children` from remounting), so this attribute is the ONLY thing that changes.
|
|
452
|
+
* The second attribute is deliberate: it lifts the rule above the `[data-aside-width]` presets
|
|
453
|
+
* in the container queries below, which sit at equal specificity, so a closed pane stays one
|
|
454
|
+
* column no matter where those blocks end up in source order. */
|
|
455
|
+
.ui-split-pane[data-aside="closed"][data-aside-width] {
|
|
456
|
+
grid-template-columns: minmax(0, 1fr);
|
|
457
|
+
gap: 0;
|
|
458
|
+
}
|
|
459
|
+
|
|
327
460
|
.ui-resizable-panel-group {
|
|
328
461
|
display: flex;
|
|
329
462
|
min-height: 0;
|
|
@@ -400,18 +533,82 @@
|
|
|
400
533
|
min-width: 0;
|
|
401
534
|
}
|
|
402
535
|
|
|
536
|
+
/* `--page-band-gap` is INTERNAL plumbing, not a theming knob (the same shape as
|
|
537
|
+
* --master-detail-rail-size above): the ONE resolved number that separates the page's bands,
|
|
538
|
+
* so the container's `gap` and the toolbar band's negation of it can never disagree. It has to
|
|
539
|
+
* be a named variable because the number is not always --space-section-active — `ghost` swaps
|
|
540
|
+
* it for --space-stack-md, while the 720px step and the admin-collection preset swap the
|
|
541
|
+
* section token underneath it. A toolbar rule that hard-coded
|
|
542
|
+
* `calc(-1 * var(--space-section-active))` would look correct only because those two tokens
|
|
543
|
+
* both resolve to 16px today: retune --space-section in a service theme and a ghost page's
|
|
544
|
+
* band would overlap or float by the difference, silently. Services still retune the SPACING
|
|
545
|
+
* through --space-section / --space-section-compact, exactly as before. */
|
|
403
546
|
.ui-page-container {
|
|
547
|
+
--page-band-gap: var(--space-section-active);
|
|
548
|
+
|
|
404
549
|
display: flex;
|
|
405
550
|
flex-direction: column;
|
|
406
551
|
min-height: 100%;
|
|
407
552
|
padding: var(--space-page-active-y) 0;
|
|
408
|
-
gap: var(--
|
|
553
|
+
gap: var(--page-band-gap);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/* `headerScale="chrome"` — the top band is CHROME, so it sits ON the frame's edge, not in a
|
|
557
|
+
* document's top margin. The shorthand above opens every page with --space-page-active-y (24px,
|
|
558
|
+
* 16px below the 720px step), which is right for a document: a title needs air above it. Chrome
|
|
559
|
+
* does not. A chat channel head, a mail thread's subject bar, an IDE tab strip are the FRAME —
|
|
560
|
+
* measured against the design of a consumer chat screen at 1512x805, the design's channel head
|
|
561
|
+
* occupies y 0..47 while the app's identical-height head started at y 24, pushing the whole top
|
|
562
|
+
* chrome down and taking those 24px straight off the scroll viewport (design 617px of transcript,
|
|
563
|
+
* app 587px). The band heights already matched; the page was simply floating.
|
|
564
|
+
*
|
|
565
|
+
* This is the SAME question `headerScale` already answers, not a second one: "is the first row a
|
|
566
|
+
* document title or the surface's own furniture". The type step (see .ui-page-title below) and
|
|
567
|
+
* the flush edge are two consequences of that one fact, which is why they ride the same
|
|
568
|
+
* attribute rather than a second prop that every real call site would have to set in lockstep.
|
|
569
|
+
*
|
|
570
|
+
* BLOCK-START ONLY, deliberately. `headerScale` names the HEADER, so it may only speak for the
|
|
571
|
+
* top edge; the bottom belongs to `stickyFooter`, which already zeroes `padding-block-end` for
|
|
572
|
+
* its own (documented) reason. A longhand, so that contract is untouched and the shorthand above
|
|
573
|
+
* still owns the default page.
|
|
574
|
+
*
|
|
575
|
+
* Chrome is a token, default quiet (rule #44): the inset is `--page-pad-block-start-chrome`,
|
|
576
|
+
* 0 by default (flush IS the quiet state for chrome), so a service whose design grid wants its
|
|
577
|
+
* chrome to breathe retunes it once instead of forking the page shell. The specificity (0,2,0)
|
|
578
|
+
* out-ranks both the base rule and the 720px block — which re-declares the TOKEN, never this
|
|
579
|
+
* padding — so the mechanism holds identically on both sides of that step. A page that never
|
|
580
|
+
* passes `headerScale` emits no attribute and is geometrically byte-identical. */
|
|
581
|
+
.ui-page-container[data-header-scale="chrome"] {
|
|
582
|
+
padding-block-start: var(--page-pad-block-start-chrome);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/* `headerScale="chrome"`, the BAND-HEIGHT consequence of that same fact (gh#331). A document
|
|
586
|
+
* header is content-height — correctly, a title is as tall as the title is. Chrome is furniture,
|
|
587
|
+
* and furniture has a band that things centre INTO; without one, the band's vertical centre is a
|
|
588
|
+
* function of the copy inside it (measured here: 42.02px with an `extra` control, 40.38px
|
|
589
|
+
* without), so nothing in the page can ever be aligned to it and no service can aim at a number.
|
|
590
|
+
*
|
|
591
|
+
* A MIN, not a height: an `lg` control or a wrapped JA/VI channel name must still fit, and
|
|
592
|
+
* `min-block-size` floors the band without ever clipping it. `justify-content: center` is the
|
|
593
|
+
* half that makes the floor useful — a taller band with top-packed content is a band with dead
|
|
594
|
+
* air under it, not a centred one — and it is inert while the knob is `auto`, because a column
|
|
595
|
+
* whose min IS its content height has nothing to distribute. Both declarations are therefore
|
|
596
|
+
* byte-identical for every page shipped before this token existed, chrome pages included.
|
|
597
|
+
*
|
|
598
|
+
* Scoped to the container attribute (0,2,0), like every other half of the chrome contract, so a
|
|
599
|
+
* document page never matches and the 720px block (which re-declares tokens, never these
|
|
600
|
+
* properties) cannot out-rank it. The band-height axis has ONE owner and it is
|
|
601
|
+
* `--app-shell-bar-height`: see tokens/semantic/layout.css for why the DEFAULT still is not it. */
|
|
602
|
+
.ui-page-container[data-header-scale="chrome"] .ui-page-header {
|
|
603
|
+
min-block-size: var(--page-header-min-block-size-chrome);
|
|
604
|
+
justify-content: center;
|
|
409
605
|
}
|
|
410
606
|
|
|
411
607
|
/* Horizontal page insets live on the header/body/footer — NOT the container —
|
|
412
608
|
* so the header divider and footer border span the full page width
|
|
413
609
|
* (full-bleed) while their content stays aligned with the body. */
|
|
414
610
|
.ui-page-header,
|
|
611
|
+
.ui-page-toolbar,
|
|
415
612
|
.ui-page-body,
|
|
416
613
|
.ui-page-footer {
|
|
417
614
|
padding-inline-start: var(--space-page-active-x);
|
|
@@ -436,16 +633,21 @@
|
|
|
436
633
|
* the full compact-gutter surface. The footer is deliberately NOT capped: its border/background
|
|
437
634
|
* is page-level chrome that must span the page when `stickyFooter` pins it. */
|
|
438
635
|
.ui-page-container[data-measure="narrow"] .ui-page-header,
|
|
636
|
+
.ui-page-container[data-measure="narrow"] .ui-page-toolbar,
|
|
439
637
|
.ui-page-container[data-measure="narrow"] .ui-page-body {
|
|
440
638
|
max-inline-size: var(--page-measure-narrow);
|
|
441
639
|
}
|
|
442
640
|
|
|
443
641
|
.ui-page-container[data-measure="medium"] .ui-page-header,
|
|
642
|
+
.ui-page-container[data-measure="medium"] .ui-page-toolbar,
|
|
444
643
|
.ui-page-container[data-measure="medium"] .ui-page-body {
|
|
445
644
|
max-inline-size: var(--page-measure-medium);
|
|
446
645
|
}
|
|
447
646
|
|
|
448
|
-
/* flush = full-bleed body (DataTable
|
|
647
|
+
/* flush = full-bleed body AND toolbar band (DataTable, a full-width filter strip);
|
|
648
|
+
* header/footer keep their inset. `PageContainer.Inset` re-applies the page gutter to any
|
|
649
|
+
* padded strip inside either one, so a toolbar's own content can still align with the title. */
|
|
650
|
+
.ui-page-container--flush .ui-page-toolbar,
|
|
449
651
|
.ui-page-container--flush .ui-page-body {
|
|
450
652
|
padding-inline-start: 0;
|
|
451
653
|
padding-inline-end: 0;
|
|
@@ -457,21 +659,64 @@
|
|
|
457
659
|
padding-inline-end: var(--space-page-active-x);
|
|
458
660
|
}
|
|
459
661
|
|
|
662
|
+
/* `ghost` is the quiet chrome WEIGHT: no header rule, no header bottom pad. The two halves are
|
|
663
|
+
* NOT the same kind of decision, and the border half used to get it wrong.
|
|
664
|
+
*
|
|
665
|
+
* Hard-setting `none` here threw away an EXPLICIT `--page-header-divider` as well as the
|
|
666
|
+
* nothing it was meant to block — so a service could opt the page chrome into a rule and a
|
|
667
|
+
* ghost page would still refuse to draw it. Measured against the design of a consumer chat
|
|
668
|
+
* screen: the design carries TWO horizontal rules in the top chrome (channel head at y=47, work
|
|
669
|
+
* band at y=88) and the app rendered ONE (y=133) — the missing y=47 is this line eating the
|
|
670
|
+
* divider the service had already turned on. It is the identical bug that was fixed on
|
|
671
|
+
* `.ui-page-toolbar` below, and it takes the identical cure: re-declare the property from the
|
|
672
|
+
* SAME knob with a `none` fallback. Nothing inherits in, the opt-in passes through, and the
|
|
673
|
+
* default (`--page-header-divider: none`) is byte-identical to the hard-set version.
|
|
674
|
+
*
|
|
675
|
+
* `padding-bottom: 0` is untouched — THAT is the real quiet half of ghost, it answers "how loud
|
|
676
|
+
* is the chrome", not "did the service ask for a rule", and no token mediates it. */
|
|
460
677
|
.ui-page-container--ghost .ui-page-header {
|
|
461
|
-
border-bottom: none;
|
|
678
|
+
border-bottom: var(--page-header-divider, none);
|
|
462
679
|
padding-bottom: 0;
|
|
463
680
|
}
|
|
464
681
|
|
|
682
|
+
/* The band's half of the same contract, and it must stay in step with the header rule above:
|
|
683
|
+
* neither one lets a divider INHERIT in, and both let an explicit opt-in through — otherwise a
|
|
684
|
+
* ghost page would draw a rule under the band that the header itself refuses to draw, or vice
|
|
685
|
+
* versa.
|
|
686
|
+
*
|
|
687
|
+
* Inherited, and only inherited. An earlier version hard-set `none` here, which also threw away
|
|
688
|
+
* an EXPLICIT `--page-toolbar-divider` — so a service could paint the band (ghost deliberately
|
|
689
|
+
* leaves the ground alone) but could never rule it, and the two knobs disagreed about who ghost
|
|
690
|
+
* outranks. A chat shell is the case that exposed it: a quiet header, and a workflow band that
|
|
691
|
+
* has to read as a separate surface from the transcript below it. Re-declaring the property with
|
|
692
|
+
* a `none` fallback keeps the original guarantee (nothing inherits in) while letting the opt-in
|
|
693
|
+
* through, which is what the paragraph above always meant. */
|
|
694
|
+
.ui-page-container--ghost .ui-page-toolbar {
|
|
695
|
+
border-block-end: var(--page-toolbar-divider, none);
|
|
696
|
+
}
|
|
697
|
+
|
|
465
698
|
.ui-page-container--ghost {
|
|
466
|
-
gap: var(--space-stack-md);
|
|
699
|
+
--page-band-gap: var(--space-stack-md);
|
|
467
700
|
}
|
|
468
701
|
|
|
469
702
|
.ui-page-container--sticky-footer {
|
|
470
703
|
min-height: 100%;
|
|
704
|
+
|
|
705
|
+
/* The container's own block-end padding must NOT stay under a sticky
|
|
706
|
+
* footer. A sticky box cannot travel into its containing block's padding
|
|
707
|
+
* area, so `bottom: 0` resolves that much ABOVE the true bottom edge —
|
|
708
|
+
* the footer floats short of the fold and page content scrolls through
|
|
709
|
+
* the gap behind it. Measured in Chromium at the 24px page padding: the
|
|
710
|
+
* footer pinned at 837px inside an 861px scroller.
|
|
711
|
+
*
|
|
712
|
+
* The space itself is still wanted, so it moves onto the body, which is
|
|
713
|
+
* what needs breathing room above the footer anyway. */
|
|
714
|
+
padding-block-end: 0;
|
|
471
715
|
}
|
|
472
716
|
|
|
473
717
|
.ui-page-container--sticky-footer .ui-page-body {
|
|
474
718
|
flex: 1;
|
|
719
|
+
padding-block-end: var(--space-page-active-y);
|
|
475
720
|
}
|
|
476
721
|
|
|
477
722
|
.ui-page-container--sticky-footer .ui-page-footer {
|
|
@@ -518,6 +763,53 @@
|
|
|
518
763
|
background-image: var(--gradient-hero);
|
|
519
764
|
}
|
|
520
765
|
|
|
766
|
+
/* `toolbar` — the FIXED chrome band between the page header and the body: a filter strip, a
|
|
767
|
+
* status bar, a channel workflow rail. There was no slot for it before, so a page either put
|
|
768
|
+
* the strip inside the body (where `fill` scrolls it away) or hand-laid `position: sticky` at
|
|
769
|
+
* the call site — the exact "never hand-lay page chrome" the system forbids.
|
|
770
|
+
*
|
|
771
|
+
* `flex: none` is the whole contract: under `fill` the BODY is the scroll viewport (see
|
|
772
|
+
* .ui-page-container--fill below), so the band is a plain flex item OUTSIDE the scroller and
|
|
773
|
+
* cannot be squeezed by it. Sticky could not give that — sticky keeps content flowing
|
|
774
|
+
* BENEATH the pinned box, which is the half-sliced row a hand-rolled strip always shows.
|
|
775
|
+
*
|
|
776
|
+
* Chrome is a token, default quiet (rule #44): the band draws NO rule of its own, and its
|
|
777
|
+
* bottom rule falls back — at the CALL SITE, never bound at :root — to whatever the page
|
|
778
|
+
* header's divider is, so a service opting into `--page-header-divider` gets a consistent
|
|
779
|
+
* page chrome in ONE declaration and can still silence just this band with
|
|
780
|
+
* `--page-toolbar-divider: none`.
|
|
781
|
+
*
|
|
782
|
+
* The GROUND is a knob too, defaulting to `transparent` so the band looks exactly as it did
|
|
783
|
+
* before the token existed. Without it the only way to lift the band off the page ground was a
|
|
784
|
+
* `bg-card` utility at the call site — hand-laid page chrome, which is what this whole slot
|
|
785
|
+
* exists to abolish, and which cannot be re-themed per tenant. It is the `background`
|
|
786
|
+
* shorthand, not `background-color`, so a service may equally hand it a gradient.
|
|
787
|
+
*
|
|
788
|
+
* The NEGATIVE block margins are what make it read as chrome rather than as a third section.
|
|
789
|
+
* The container is a flex column with a `--page-band-gap` between every band, which is right
|
|
790
|
+
* for a document page (header / body / footer are three separate blocks) and wrong here: a
|
|
791
|
+
* band that carries a ground and a bottom rule and then FLOATS between two 16px voids divides
|
|
792
|
+
* nothing — measured on a 1512×805 chat page, 24/69 header · 85/134 band · 150/685 body, i.e.
|
|
793
|
+
* 32px of dead air wrapped around the one element whose job is to be attached. Chrome sits
|
|
794
|
+
* between the header and the scrolling body; chrome is flush. Cancelling the gap from the band
|
|
795
|
+
* itself keeps that in ONE declaration, leaves the container's rhythm intact for every page
|
|
796
|
+
* that passes no `toolbar`, and keeps `--page-toolbar-pad-block` as what it always was: the
|
|
797
|
+
* band's own INSIDE, the only place it is allowed to breathe. */
|
|
798
|
+
.ui-page-toolbar {
|
|
799
|
+
flex: none;
|
|
800
|
+
margin-block: calc(-1 * var(--page-band-gap));
|
|
801
|
+
padding-block: var(--page-toolbar-pad-block);
|
|
802
|
+
background: var(--page-toolbar-background);
|
|
803
|
+
border-block-end: var(--page-toolbar-divider, var(--page-header-divider));
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/* The band is never the FIRST child — the header always renders — but it CAN be the last one
|
|
807
|
+
* (a page given a `toolbar` and no `children`). There is no gap under a last flex item to
|
|
808
|
+
* cancel, so the negation would eat the container's own bottom padding instead. */
|
|
809
|
+
.ui-page-toolbar:last-child {
|
|
810
|
+
margin-block-end: 0;
|
|
811
|
+
}
|
|
812
|
+
|
|
521
813
|
.ui-page-header-row {
|
|
522
814
|
display: flex;
|
|
523
815
|
flex-direction: column;
|
|
@@ -525,6 +817,16 @@
|
|
|
525
817
|
gap: var(--space-stack-sm);
|
|
526
818
|
}
|
|
527
819
|
|
|
820
|
+
/* The title/subtitle column is the SHRINKABLE side of the header row (gh#300): a flex item's
|
|
821
|
+
* automatic minimum size is its content, so without this the heading refuses to shrink and the
|
|
822
|
+
* whole overflow is charged to `extra`. The component used to carry it as a bare `min-w-0`
|
|
823
|
+
* utility, which meant the >=640px arrangement depended on a Tailwind class while the compact
|
|
824
|
+
* arrangement below declared it in CSS — the same property owned in two places (gh#319).
|
|
825
|
+
* Unconditional, so both arrangements read one rule. */
|
|
826
|
+
.ui-page-header-heading {
|
|
827
|
+
min-inline-size: 0;
|
|
828
|
+
}
|
|
829
|
+
|
|
528
830
|
.ui-page-header-extra {
|
|
529
831
|
display: flex;
|
|
530
832
|
flex-wrap: wrap;
|
|
@@ -563,6 +865,20 @@
|
|
|
563
865
|
overflow-wrap: anywhere;
|
|
564
866
|
}
|
|
565
867
|
|
|
868
|
+
/* `headerScale="chrome"` — the page's top row is CHROME (a chat channel, a mail thread, an IDE
|
|
869
|
+
* tab), not a document title, so the `<h1>` takes the body type step. The ELEMENT is untouched:
|
|
870
|
+
* still an `<h1>`, still the page's one level-1 heading, because the screen-reader outline is
|
|
871
|
+
* what a page is navigated by — only the type step moves, and only through a token.
|
|
872
|
+
*
|
|
873
|
+
* The compound selector's specificity (0,2,0) is load-bearing, not decoration: the 720px block
|
|
874
|
+
* below re-declares `.ui-page-title` (0,1,0) at the COMPACT DOCUMENT step, which is h2 (18px) —
|
|
875
|
+
* BIGGER than this. Without out-ranking it, a chrome header would swell on a phone, i.e. exactly
|
|
876
|
+
* where the height matters most. The responsive step itself is untouched and still governs every
|
|
877
|
+
* document-scaled page. */
|
|
878
|
+
.ui-page-container[data-header-scale="chrome"] .ui-page-title {
|
|
879
|
+
font-size: var(--page-title-font-size-chrome);
|
|
880
|
+
}
|
|
881
|
+
|
|
566
882
|
/* Compact viewports use a responsive type step, not compact density: body/control text and
|
|
567
883
|
* touch targets remain readable while page chrome stops competing with primary actions. A
|
|
568
884
|
* single Flex action group must wrap even when the consumer omitted `wrap`, because clipping
|
|
@@ -644,6 +960,25 @@
|
|
|
644
960
|
min-inline-size: 0;
|
|
645
961
|
justify-content: flex-end;
|
|
646
962
|
}
|
|
963
|
+
|
|
964
|
+
/* `headerScale="chrome"`, second consequence of the SAME fact. The row's `align-items:
|
|
965
|
+
* flex-start` above is right for a DOCUMENT: a `Save`/`Publish` group belongs on the first
|
|
966
|
+
* line of a tall `<h1>`, not floating beside its second line. A chrome band has no tall
|
|
967
|
+
* heading to align to — it is one bar, and its actions sit on the bar's middle. Measured
|
|
968
|
+
* against the reference chat screen the difference was 8.65px: the header row is 45.3px, the
|
|
969
|
+
* icon buttons 28px, so top-packing put their centres at y=14 while the title block's centre
|
|
970
|
+
* sat at y=22.65. That gap is what reads, correctly, as "the icons are not centred".
|
|
971
|
+
*
|
|
972
|
+
* `align-self` on the EXTRA box, not `align-items` on the row: the heading keeps the stretch
|
|
973
|
+
* it has today (nothing about the title's own alignment is in question), and the two axes stay
|
|
974
|
+
* independent. Scoped INSIDE the 640px block on purpose — below it the row is a COLUMN, where
|
|
975
|
+
* the cross axis is horizontal and this same declaration would centre `extra` sideways and
|
|
976
|
+
* release its full-width stretch. `headerLayout="responsive-inline"` runs its own row
|
|
977
|
+
* arrangement under 640px and keeps its own flex-start; that arrangement exists to hold ONE
|
|
978
|
+
* compact control beside a wrapping title band, which is the document case again. */
|
|
979
|
+
.ui-page-container[data-header-scale="chrome"] .ui-page-header-extra {
|
|
980
|
+
align-self: center;
|
|
981
|
+
}
|
|
647
982
|
}
|
|
648
983
|
|
|
649
984
|
.ui-page-subtitle {
|
|
@@ -652,6 +987,24 @@
|
|
|
652
987
|
margin-top: var(--space-1);
|
|
653
988
|
}
|
|
654
989
|
|
|
990
|
+
/* `headerScale="chrome"`, third consequence of the SAME fact: if the top row is chrome, the line
|
|
991
|
+
* under it is a caption ON that chrome (a channel purpose, a mail preview), not a document's
|
|
992
|
+
* standfirst. The document step is --font-size-base — the identical step the chrome TITLE takes —
|
|
993
|
+
* so a chrome header was shipping a title and its subtitle at one size, which is not a hierarchy.
|
|
994
|
+
* Same size is also not the expensive half: the step drives the LINE BOX (14px × 1.7 = 23.8px vs
|
|
995
|
+
* 11px × 1.7 = 18.9px), and a band that exists to give the transcript room was spending 5px of it
|
|
996
|
+
* on a caption. Type only — no line-height, no colour, no weight: `--line-height-body` still owns
|
|
997
|
+
* the rhythm so a wrapped JA/VI purpose line stays readable at the smaller step.
|
|
998
|
+
*
|
|
999
|
+
* The specificity is load-bearing exactly as it is on the title above: the 720px block declares
|
|
1000
|
+
* `.ui-page-header .ui-page-subtitle` (0,2,0) — already compounded, unlike the bare `.ui-page-title`
|
|
1001
|
+
* — so this rule out-ranks it only because the container attribute makes it (0,3,0). Without that
|
|
1002
|
+
* the compact step would win below 720px and a chrome subtitle would step UP to 14px on a phone,
|
|
1003
|
+
* i.e. exactly where the band's height is worth the most. */
|
|
1004
|
+
.ui-page-container[data-header-scale="chrome"] .ui-page-subtitle {
|
|
1005
|
+
font-size: var(--page-subtitle-font-size-chrome);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
655
1008
|
/* `headerLoading` placeholders — the real `<h1>`/`<p>` wearing the shared `.ui-skeleton-block`
|
|
656
1009
|
* skin, so the pending band occupies the same rhythm the settled band will. `max-inline-size:
|
|
657
1010
|
* 100%` keeps them inside a 390px column; the block size replaces the line box the (sr-only)
|
|
@@ -680,18 +1033,46 @@
|
|
|
680
1033
|
/* fill = body grows to fill the remaining shell height (full-height DataTable,
|
|
681
1034
|
* split-pane, or a chat surface whose message list scrolls and whose composer
|
|
682
1035
|
* is pinned to the bottom via `footer` + `stickyFooter`). */
|
|
1036
|
+
/* `fill` means the body OCCUPIES the remaining height — so when its content is
|
|
1037
|
+
* taller, the content scrolls INSIDE it. The container therefore flexes to the
|
|
1038
|
+
* region rather than growing past it, and the body owns the scroll.
|
|
1039
|
+
*
|
|
1040
|
+
* This is what makes a chat surface behave: header and footer are plain flex
|
|
1041
|
+
* items at the two ends, outside the scroller, so transcript content can never
|
|
1042
|
+
* travel underneath them. Sticky bands cannot give that — sticky keeps the
|
|
1043
|
+
* content flowing beneath the pinned box, which is exactly the half-sliced
|
|
1044
|
+
* message line the chat page was showing. */
|
|
1045
|
+
.ui-page-container--fill {
|
|
1046
|
+
flex: 1;
|
|
1047
|
+
min-height: 0;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
683
1050
|
.ui-page-container--fill .ui-page-body {
|
|
684
1051
|
flex: 1;
|
|
685
1052
|
min-height: 0;
|
|
1053
|
+
overflow-y: auto;
|
|
686
1054
|
}
|
|
687
1055
|
|
|
1056
|
+
/* The THIRD chrome band, and until now the only one whose rule was not a knob. The header reads
|
|
1057
|
+
* --page-header-divider and the toolbar reads --page-toolbar-divider, but this line was a
|
|
1058
|
+
* hard-copied literal, so a page could not turn it off at all. Measured on a consumer chat
|
|
1059
|
+
* screen: the composer lives in this slot and is itself a bordered Card, so the full-width rule
|
|
1060
|
+
* landed straight above it as a SECOND line — a pixel diff against the design caught a 100%-wide
|
|
1061
|
+
* rule at y=701 that the design does not have.
|
|
1062
|
+
*
|
|
1063
|
+
* Unlike the other two, the default here is a rule rather than silence, and that is deliberate:
|
|
1064
|
+
* `footer` is the shared slot a form's Save/Cancel bar lands in, where the line separating the
|
|
1065
|
+
* actions from the content is the behaviour every existing page already depends on. The literal
|
|
1066
|
+
* simply moves into the fallback, so `--page-footer-divider` unset is byte-identical to what
|
|
1067
|
+
* this rule drew before, and `--page-footer-divider: none` is the opt-OUT the chat composer
|
|
1068
|
+
* needed. */
|
|
688
1069
|
.ui-page-footer {
|
|
689
1070
|
display: flex;
|
|
690
1071
|
flex-direction: column-reverse;
|
|
691
1072
|
align-items: stretch;
|
|
692
1073
|
gap: var(--space-inline-sm);
|
|
693
1074
|
padding-top: var(--space-stack-md);
|
|
694
|
-
border-top: 1px solid hsl(var(--border));
|
|
1075
|
+
border-top: var(--page-footer-divider, 1px solid hsl(var(--border)));
|
|
695
1076
|
}
|
|
696
1077
|
|
|
697
1078
|
.ui-page-footer > * {
|
|
@@ -770,8 +1151,8 @@
|
|
|
770
1151
|
display: flex;
|
|
771
1152
|
align-items: center;
|
|
772
1153
|
justify-content: center;
|
|
773
|
-
|
|
774
|
-
|
|
1154
|
+
inline-size: var(--empty-state-icon-size);
|
|
1155
|
+
block-size: var(--empty-state-icon-size);
|
|
775
1156
|
border-radius: var(--radius-pill);
|
|
776
1157
|
/* Medallion fill + glyph colour read their knobs, defaulting to the LIVE --muted /
|
|
777
1158
|
* --muted-foreground roles at the call site (re-resolves under a scoped theme). */
|
|
@@ -779,6 +1160,11 @@
|
|
|
779
1160
|
color: var(--empty-state-icon-foreground, hsl(var(--muted-foreground)));
|
|
780
1161
|
}
|
|
781
1162
|
|
|
1163
|
+
.ui-empty-state-icon svg {
|
|
1164
|
+
inline-size: var(--empty-state-icon-glyph-size);
|
|
1165
|
+
block-size: var(--empty-state-icon-glyph-size);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
782
1168
|
/* tone — set the medallion foreground + tint from the matching role token, so a consumer never
|
|
783
1169
|
* hand-rolls a `.ui-success-state` class. `muted` (default) keeps the neutral role fallback. */
|
|
784
1170
|
.ui-empty-state[data-tone="success"] {
|
|
@@ -884,18 +1270,31 @@
|
|
|
884
1270
|
color: hsl(var(--muted-foreground));
|
|
885
1271
|
}
|
|
886
1272
|
|
|
1273
|
+
/* The button GROWS with its label instead of being a rigid square. `width: var(--control-height)`
|
|
1274
|
+
* capped every control at one control-height, so a five-figure page number spilled out of its
|
|
1275
|
+
* own box (measured: `12345678` wanted 59.3px of text inside a 30px content box; `1043` cleared
|
|
1276
|
+
* a 32px box by 0.3px and clipped outright at the consumer's 29px density). Ant Design, MUI and
|
|
1277
|
+
* shadcn all size the same way — a min-width for the rhythm, padding for the overflow — so the
|
|
1278
|
+
* one- and two-digit buttons keep the square look while longer ones widen. Height stays pinned so
|
|
1279
|
+
* the row never changes rhythm. */
|
|
887
1280
|
.ui-pagination-link {
|
|
888
1281
|
display: inline-flex;
|
|
889
|
-
width: var(--control-height);
|
|
1282
|
+
min-width: var(--control-height);
|
|
890
1283
|
height: var(--control-height);
|
|
891
1284
|
align-items: center;
|
|
892
1285
|
justify-content: center;
|
|
1286
|
+
padding-inline: var(--pagination-page-padding-x);
|
|
893
1287
|
border-radius: var(--radius-sm);
|
|
894
1288
|
border: 1px solid hsl(var(--border));
|
|
895
1289
|
color: hsl(var(--foreground));
|
|
896
1290
|
text-decoration: none;
|
|
897
1291
|
}
|
|
898
1292
|
|
|
1293
|
+
/* Keyboard focus: drawn by the single-source rule in styles/focus-ring.css
|
|
1294
|
+
* (`.ui-pagination-link` is in its shadow-form list). Before that, focus here was
|
|
1295
|
+
* left to the browser, which paints its OWN ring — measured
|
|
1296
|
+
* `outline: rgb(0, 95, 204) auto 1px`, a blue belonging to no theme. */
|
|
1297
|
+
|
|
899
1298
|
.ui-pagination-link[data-active="true"] {
|
|
900
1299
|
border-color: hsl(var(--ring) / 0.5);
|
|
901
1300
|
background: hsl(var(--primary));
|
|
@@ -1214,11 +1613,6 @@
|
|
|
1214
1613
|
background: hsl(var(--legal-document-toc-active-background, var(--accent)) / 0.6);
|
|
1215
1614
|
}
|
|
1216
1615
|
|
|
1217
|
-
.ui-legal-document-toc-link:focus-visible {
|
|
1218
|
-
outline: none;
|
|
1219
|
-
box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
1616
|
/* Active entry: colour + a leading marker + a heavier weight, so the state survives a
|
|
1223
1617
|
* monochrome / high-contrast rendering (never colour-only, WCAG 1.4.1). The anchor also
|
|
1224
1618
|
* carries `aria-current="location"`. */
|
|
@@ -1250,11 +1644,6 @@
|
|
|
1250
1644
|
|
|
1251
1645
|
/* The section is `tabIndex={-1}` and receives focus when its contents anchor is
|
|
1252
1646
|
* activated, so the focus ring must be VISIBLE (WCAG 2.4.7 / 2.4.11). */
|
|
1253
|
-
.ui-legal-document-section:focus-visible {
|
|
1254
|
-
outline: var(--focus-ring-width) solid hsl(var(--focus-ring-color, var(--ring)));
|
|
1255
|
-
outline-offset: var(--space-1);
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
1647
|
.ui-legal-document-footer {
|
|
1259
1648
|
display: flex;
|
|
1260
1649
|
flex-wrap: wrap;
|