@godxjp/ui 18.6.0 → 18.8.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/components/data-display/card.d.ts +27 -3
- package/dist/components/data-display/card.js +4 -1
- package/dist/components/data-entry/input-otp.d.ts +1 -1
- package/dist/components/feedback/alert.d.ts +20 -2
- package/dist/components/feedback/alert.js +11 -1
- package/dist/components/feedback/index.d.ts +2 -2
- package/dist/components/feedback/index.js +3 -1
- package/dist/components/layout/auth-shell.d.ts +4 -2
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/layout/index.js +2 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +19 -41
- package/dist/components/layout/page-header.d.ts +22 -0
- package/dist/components/layout/page-header.js +89 -0
- package/dist/components/navigation/filter-bar.d.ts +2 -2
- package/dist/components/navigation/filter-bar.js +44 -1
- package/dist/components/navigation/index.d.ts +1 -1
- package/dist/components/navigation/steps.d.ts +2 -2
- package/dist/components/navigation/steps.js +4 -2
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/avatar.js +2 -1
- package/dist/components/ui/input-otp.d.ts +15 -3
- package/dist/components/ui/input-otp.js +2 -1
- package/dist/email/tokens.generated.d.ts +2 -2
- package/dist/email/tokens.generated.js +2 -2
- package/dist/i18n/messages/en.json +11 -0
- package/dist/i18n/messages/ja.json +8 -0
- package/dist/i18n/messages/vi.json +8 -0
- package/dist/props/components/data-display.prop.d.ts +16 -0
- package/dist/props/components/data-entry.prop.d.ts +7 -0
- package/dist/props/components/feedback.prop.d.ts +6 -0
- package/dist/props/components/layout.prop.d.ts +53 -0
- package/dist/props/components/navigation.prop.d.ts +61 -0
- package/dist/props/registry.d.ts +75 -1
- package/dist/props/registry.js +96 -1
- package/dist/props/vocabulary/interaction.prop.d.ts +10 -2
- package/dist/props/vocabulary/layout.prop.d.ts +5 -2
- package/dist/styles/alert-layout.css +36 -0
- package/dist/styles/card-layout.css +49 -13
- package/dist/styles/control.css +12 -2
- package/dist/styles/data-display-layout.css +16 -0
- package/dist/styles/layout.css +113 -0
- package/dist/styles/navigation-layout.css +8 -3
- package/dist/styles/shell-layout.css +44 -1
- package/dist/tokens/components/card.css +12 -0
- package/dist/tokens/components/control.css +16 -0
- package/dist/tokens/components/data-display.css +12 -0
- package/dist/tokens/components/email.css +8 -2
- package/dist/tokens/components/feedback.css +20 -0
- package/dist/tokens/components/navigation.css +35 -0
- package/dist/tokens/components/shell.css +64 -4
- package/dist/tokens/semantic/layout.css +13 -0
- package/package.json +2 -2
|
@@ -51,6 +51,42 @@
|
|
|
51
51
|
padding-inline-end: calc(var(--alert-space-inset) + var(--space-8));
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/*
|
|
55
|
+
* BANNER variant (gh#255) — the same Alert, re-measured as page chrome: edge-to-edge, square,
|
|
56
|
+
* ruled only on the block-end edge so it separates itself from the content BELOW it. Declared
|
|
57
|
+
* after the base box (equal specificity → source order wins) and it touches geometry only, so
|
|
58
|
+
* every tone, the icon treatment, the actions grid and the dismiss control are inherited
|
|
59
|
+
* unchanged from the inline alert above.
|
|
60
|
+
*/
|
|
61
|
+
[data-slot="alert"][data-variant="banner"] {
|
|
62
|
+
border-radius: var(--banner-radius);
|
|
63
|
+
border-width: var(--banner-border-width);
|
|
64
|
+
border-block-end-width: var(--banner-border-block-end-width);
|
|
65
|
+
padding: var(--banner-space-block) var(--banner-space-inline);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* The dismissible reserve is re-derived from the BANNER inset — reusing the inline-alert
|
|
69
|
+
* calc would leave a ✕-sized hole on a strip whose own inset is half as deep. */
|
|
70
|
+
[data-slot="alert"][data-variant="banner"][data-dismissible] {
|
|
71
|
+
padding-inline-end: calc(var(--banner-space-inline) + var(--space-8));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
[data-slot="alert"][data-variant="banner"] > [data-slot="alert-dismiss"] {
|
|
75
|
+
top: var(--banner-dismiss-space-offset);
|
|
76
|
+
inset-inline-end: var(--banner-dismiss-space-offset);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* The compact page gutter. `.ui-page-container` steps `--space-page-active-x` down at this same
|
|
80
|
+
* breakpoint, and a banner mounted ABOVE the container (the normal case — page/app chrome) does
|
|
81
|
+
* not inherit that, so without this rule its text would sit 8px outside the page title at 390px.
|
|
82
|
+
* Bound to its own `--banner-space-inline-compact` knob rather than a literal, so a service
|
|
83
|
+
* retunes the mobile inset the same way it retunes the desktop one. */
|
|
84
|
+
@media (max-width: 720px) {
|
|
85
|
+
[data-slot="alert"][data-variant="banner"] {
|
|
86
|
+
--banner-space-inline: var(--banner-space-inline-compact);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
54
90
|
[data-slot="alert-icon"] {
|
|
55
91
|
width: 1.25rem;
|
|
56
92
|
height: 1.25rem;
|
|
@@ -51,9 +51,16 @@
|
|
|
51
51
|
background: transparent;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/* `featured` = the brand-toned perimeter. Its colour is a role-mirror knob (rule #45): declared
|
|
55
|
+
* `initial` in the token layer so the --primary default resolves HERE, at the call site, and a
|
|
56
|
+
* scoped [data-tenant]/.dark override of --primary still reaches it. A service that wants the
|
|
57
|
+
* featured ring in a non-brand tone overrides --card-featured-border-color; a card that wants a
|
|
58
|
+
* SEMANTIC perimeter (attention / destructive / success …) uses
|
|
59
|
+
* `accent="…" accentPlacement="perimeter"` instead — same geometry, tone-owned. */
|
|
54
60
|
[data-slot="card"][data-variant="featured"] {
|
|
55
|
-
border-color: hsl(var(--primary));
|
|
56
|
-
box-shadow: 0 0 0
|
|
61
|
+
border-color: var(--card-featured-border-color, hsl(var(--primary)));
|
|
62
|
+
box-shadow: 0 0 0 var(--card-featured-ring-width)
|
|
63
|
+
var(--card-featured-border-color, hsl(var(--primary)));
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
/* ── Density — override the shell inset/body tokens ──────────────────────
|
|
@@ -72,33 +79,39 @@
|
|
|
72
79
|
--card-space-body-y: var(--space-5);
|
|
73
80
|
}
|
|
74
81
|
|
|
75
|
-
/* ── Accent
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
/* ── Accent tone — ONE resolved colour per semantic accent ─────────────
|
|
83
|
+
* `--card-accent-color` is RESOLVED from the card's own `data-accent`, so both placements read
|
|
84
|
+
* the same value and a new tone is one line. It is deliberately NOT a service knob: it is set on
|
|
85
|
+
* the element, so a :root / [data-tenant] override could never reach it. Retint the ROLE
|
|
86
|
+
* (--attention, --success, …) — that is what a role is for. */
|
|
80
87
|
[data-slot="card"][data-accent="primary"] {
|
|
81
|
-
|
|
88
|
+
--card-accent-color: hsl(var(--primary));
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
[data-slot="card"][data-accent="success"] {
|
|
85
|
-
|
|
92
|
+
--card-accent-color: hsl(var(--success));
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
[data-slot="card"][data-accent="warning"] {
|
|
89
|
-
|
|
96
|
+
--card-accent-color: hsl(var(--warning));
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
[data-slot="card"][data-accent="info"] {
|
|
93
|
-
|
|
100
|
+
--card-accent-color: hsl(var(--info));
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
[data-slot="card"][data-accent="attention"] {
|
|
97
|
-
|
|
104
|
+
--card-accent-color: hsl(var(--attention));
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
[data-slot="card"][data-accent="destructive"] {
|
|
101
|
-
|
|
108
|
+
--card-accent-color: hsl(var(--destructive));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* ── Accent placement `edge` (DEFAULT) — semantic left stripe (token width), content on shell ─ */
|
|
112
|
+
[data-slot="card"][data-accent] {
|
|
113
|
+
border-inline-start-width: var(--card-accent-rail-width);
|
|
114
|
+
border-inline-start-color: var(--card-accent-color);
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
[data-slot="card"][data-accent] > [data-slot="card-header"],
|
|
@@ -107,6 +120,29 @@
|
|
|
107
120
|
padding-inline-start: calc(var(--card-space-inset) - var(--card-accent-rail-width));
|
|
108
121
|
}
|
|
109
122
|
|
|
123
|
+
/* ── Accent placement `perimeter` — the full attention border (gh#12) ───
|
|
124
|
+
* The whole edge carries the semantic tone instead of one rail, so a card can read as
|
|
125
|
+
* "action required" / "failed" at a glance without borrowing `--primary` the way
|
|
126
|
+
* `variant="featured"` does. Same optical weight as `featured` (border + 1px ring), tone-owned.
|
|
127
|
+
* Because the rail is gone, the slot padding compensation above is undone — content returns to
|
|
128
|
+
* the normal shell column, so switching placement never shifts the text. */
|
|
129
|
+
[data-slot="card"][data-accent][data-accent-placement="perimeter"] {
|
|
130
|
+
border-width: var(--card-accent-perimeter-width);
|
|
131
|
+
border-color: var(--card-accent-color);
|
|
132
|
+
box-shadow:
|
|
133
|
+
0 0 0 var(--card-accent-perimeter-ring-width) var(--card-accent-color),
|
|
134
|
+
var(--card-shadow),
|
|
135
|
+
var(--card-glow);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
[data-slot="card"][data-accent][data-accent-placement="perimeter"] > [data-slot="card-header"],
|
|
139
|
+
[data-slot="card"][data-accent][data-accent-placement="perimeter"]
|
|
140
|
+
> [data-slot="card-content"]:not([data-flush]),
|
|
141
|
+
[data-slot="card"][data-accent][data-accent-placement="perimeter"]
|
|
142
|
+
> [data-slot="card-footer"]:not([data-flush]) {
|
|
143
|
+
padding-inline-start: var(--card-space-inset);
|
|
144
|
+
}
|
|
145
|
+
|
|
110
146
|
/* ── Header ──────────────────────────────────────────────────────────── */
|
|
111
147
|
[data-slot="card-header"] {
|
|
112
148
|
display: flex;
|
package/dist/styles/control.css
CHANGED
|
@@ -714,8 +714,18 @@
|
|
|
714
714
|
.ui-otp-container {
|
|
715
715
|
display: flex;
|
|
716
716
|
align-items: center;
|
|
717
|
+
justify-content: var(--otp-container-align, flex-start);
|
|
717
718
|
gap: var(--space-2);
|
|
718
719
|
}
|
|
720
|
+
/* `align` on InputOTP lands on the hidden <input> (input-otp owns the container element), so the
|
|
721
|
+
* container reads it through :has() — the same mechanism this file already uses for the invalid
|
|
722
|
+
* and disabled states. Specificity puts the per-instance prop above the theme token. */
|
|
723
|
+
.ui-otp-container:has(.ui-otp-input[data-align="center"]) {
|
|
724
|
+
justify-content: center;
|
|
725
|
+
}
|
|
726
|
+
.ui-otp-container:has(.ui-otp-input[data-align="end"]) {
|
|
727
|
+
justify-content: flex-end;
|
|
728
|
+
}
|
|
719
729
|
.ui-otp-input:disabled {
|
|
720
730
|
cursor: not-allowed;
|
|
721
731
|
}
|
|
@@ -726,8 +736,8 @@
|
|
|
726
736
|
.ui-otp-slot {
|
|
727
737
|
position: relative;
|
|
728
738
|
display: flex;
|
|
729
|
-
width: var(--otp-slot-size, var(--control-height));
|
|
730
|
-
height: var(--otp-slot-size, var(--control-height));
|
|
739
|
+
width: var(--otp-slot-inline-size, var(--otp-slot-size, var(--control-height)));
|
|
740
|
+
height: var(--otp-slot-block-size, var(--otp-slot-size, var(--control-height)));
|
|
731
741
|
align-items: center;
|
|
732
742
|
justify-content: center;
|
|
733
743
|
font-size: var(--font-size-base);
|
|
@@ -30,6 +30,22 @@
|
|
|
30
30
|
color: var(--avatar-square-foreground, hsl(var(--primary-foreground)));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/* CAPABILITY MEDALLION — `<Avatar appearance="tinted">` (gh#12). The tinted companion of the
|
|
34
|
+
* solid entity mark: same box, same radius, a soft role wash instead of a solid fill and the
|
|
35
|
+
* glyph in the role itself. Declared AFTER the square rule (equal specificity) so it retints
|
|
36
|
+
* either shape. The glyph rule is scoped to this appearance ON PURPOSE — a global
|
|
37
|
+
* `.ui-avatar svg` would outrank the per-call-site icon classes existing avatars already use. */
|
|
38
|
+
.ui-avatar[data-appearance="tinted"] {
|
|
39
|
+
--avatar-background: var(--avatar-tinted-background, hsl(var(--primary) / 0.1));
|
|
40
|
+
color: var(--avatar-tinted-foreground, hsl(var(--primary)));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ui-avatar[data-appearance="tinted"] svg {
|
|
44
|
+
inline-size: var(--avatar-tinted-glyph-size);
|
|
45
|
+
block-size: var(--avatar-tinted-glyph-size);
|
|
46
|
+
flex: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
.ui-avatar-image {
|
|
34
50
|
width: 100%;
|
|
35
51
|
height: 100%;
|
package/dist/styles/layout.css
CHANGED
|
@@ -529,6 +529,41 @@
|
|
|
529
529
|
overflow-wrap: anywhere;
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
+
/* Title + `meta` share one baseline-aligned band (gh#255). The band only exists when `meta` is
|
|
533
|
+
* passed, so a header without it keeps its historical bare-`<h1>` DOM. `wrap` is what keeps a
|
|
534
|
+
* long JA/VI title and a status Badge legible at 390px instead of squeezing the title column. */
|
|
535
|
+
.ui-page-title-band {
|
|
536
|
+
display: flex;
|
|
537
|
+
flex-wrap: wrap;
|
|
538
|
+
align-items: baseline;
|
|
539
|
+
gap: var(--page-header-meta-gap);
|
|
540
|
+
min-inline-size: 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.ui-page-header-meta {
|
|
544
|
+
display: flex;
|
|
545
|
+
flex-wrap: wrap;
|
|
546
|
+
align-items: center;
|
|
547
|
+
gap: var(--page-header-meta-gap);
|
|
548
|
+
min-inline-size: 0;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/* `loading` placeholders — the real `<h1>`/`<p>` wearing the shared `.ui-skeleton-block` skin, so
|
|
552
|
+
* the pending band occupies the same rhythm the settled band will. `max-inline-size: 100%` keeps
|
|
553
|
+
* them inside a 390px column; the block size replaces the line box the (sr-only) text no longer
|
|
554
|
+
* fills. */
|
|
555
|
+
.ui-page-title-placeholder {
|
|
556
|
+
inline-size: var(--page-title-placeholder-measure);
|
|
557
|
+
max-inline-size: 100%;
|
|
558
|
+
min-block-size: var(--page-title-placeholder-block-size);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.ui-page-subtitle-placeholder {
|
|
562
|
+
inline-size: var(--page-subtitle-placeholder-measure);
|
|
563
|
+
max-inline-size: 100%;
|
|
564
|
+
min-block-size: var(--page-subtitle-placeholder-block-size);
|
|
565
|
+
}
|
|
566
|
+
|
|
532
567
|
/* Compact viewports use a responsive type step, not compact density: body/control text and
|
|
533
568
|
* touch targets remain readable while page chrome stops competing with primary actions. A
|
|
534
569
|
* single Flex action group must wrap even when the consumer omitted `wrap`, because clipping
|
|
@@ -918,6 +953,84 @@
|
|
|
918
953
|
font-size: var(--filter-label-font-size);
|
|
919
954
|
}
|
|
920
955
|
|
|
956
|
+
/* ── FilterBar typed model (#258) ──────────────────────────────────────────────────────────
|
|
957
|
+
* Every rule below is scoped to a region the bar only emits when its prop is passed, so a bar
|
|
958
|
+
* built the old way (children + onClear) matches none of them and is geometrically untouched.
|
|
959
|
+
*
|
|
960
|
+
* SEARCH — full-width while the strip is stacked (a bounded search box on a 390px screen wastes
|
|
961
|
+
* the row), then one shared token-owned measure from the 640px step up. This single measure is
|
|
962
|
+
* what the issue was filed against: as a plain child, every page picked its own width. */
|
|
963
|
+
.ui-filter-bar-search {
|
|
964
|
+
width: 100%;
|
|
965
|
+
min-width: 0;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
@media (min-width: 640px) {
|
|
969
|
+
.ui-filter-bar-search {
|
|
970
|
+
width: var(--filter-bar-search-width);
|
|
971
|
+
max-width: 100%;
|
|
972
|
+
flex: 0 0 auto;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/* CHIPS — always their own full-width line. Applied filters are a record of state, not another
|
|
977
|
+
* control in the row; wrapping them in beside the pickers makes both harder to scan. */
|
|
978
|
+
.ui-filter-bar-chips {
|
|
979
|
+
display: flex;
|
|
980
|
+
width: 100%;
|
|
981
|
+
flex-basis: 100%;
|
|
982
|
+
flex-wrap: wrap;
|
|
983
|
+
align-items: center;
|
|
984
|
+
gap: var(--filter-bar-chip-row-gap);
|
|
985
|
+
min-width: 0;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
.ui-filter-bar-chip {
|
|
989
|
+
display: inline-flex;
|
|
990
|
+
max-width: 100%;
|
|
991
|
+
align-items: center;
|
|
992
|
+
gap: var(--filter-bar-chip-gap);
|
|
993
|
+
padding: var(--filter-bar-chip-padding-block) var(--filter-bar-chip-padding-inline);
|
|
994
|
+
border-radius: var(--filter-bar-chip-radius);
|
|
995
|
+
background: hsl(var(--filter-bar-chip-background, var(--muted)));
|
|
996
|
+
color: hsl(var(--filter-bar-chip-foreground, var(--muted-foreground)));
|
|
997
|
+
font-size: var(--filter-bar-chip-font-size);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/* A long JA/VI chip value wraps inside the chip rather than pushing the row into a horizontal
|
|
1001
|
+
* scroll — the remove control must stay reachable at 390px. */
|
|
1002
|
+
.ui-filter-bar-chip-label {
|
|
1003
|
+
min-width: 0;
|
|
1004
|
+
overflow-wrap: anywhere;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.ui-filter-bar-chip-remove {
|
|
1008
|
+
flex: none;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
.ui-filter-bar-count {
|
|
1012
|
+
color: hsl(var(--filter-bar-count-foreground, var(--muted-foreground)));
|
|
1013
|
+
font-size: var(--filter-bar-count-font-size);
|
|
1014
|
+
/* The count is a number that changes in place — tabular figures stop the row from twitching
|
|
1015
|
+
* as it goes 9 → 10 → 100. */
|
|
1016
|
+
font-variant-numeric: tabular-nums;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
.ui-filter-bar-actions {
|
|
1020
|
+
display: flex;
|
|
1021
|
+
width: 100%;
|
|
1022
|
+
flex-wrap: wrap;
|
|
1023
|
+
align-items: center;
|
|
1024
|
+
gap: var(--space-inline-sm);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
@media (min-width: 640px) {
|
|
1028
|
+
.ui-filter-bar-actions {
|
|
1029
|
+
width: auto;
|
|
1030
|
+
flex: 0 0 auto;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
921
1034
|
/* Toolbar/FilterBar `overflow="scroll"` (#216) — one bounded row that scrolls INLINE instead of
|
|
922
1035
|
* wrapping onto extra rows. Below 640px the strip still stacks (the `wrap` base rules above own
|
|
923
1036
|
* that): a 390px viewport must never hide a filter behind a horizontal scroll it cannot see.
|
|
@@ -394,10 +394,15 @@
|
|
|
394
394
|
box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
+
/* The step NUMBER carries the emphasis. Both knobs exist because the canonical hosted-identity
|
|
398
|
+
* row marks the step with an accent TINT at normal weight, while the original marked it with
|
|
399
|
+
* bold at the inherited colour — a service picks its own emphasis instead of forking this rule
|
|
400
|
+
* (rule #45). Defaults are the original bold + inherited colour, so nothing moves. */
|
|
397
401
|
.ui-steps-inline-index {
|
|
398
402
|
flex: none;
|
|
399
403
|
font-variant-numeric: tabular-nums;
|
|
400
|
-
font-weight: var(--font-weight
|
|
404
|
+
font-weight: var(--steps-inline-index-font-weight);
|
|
405
|
+
color: var(--steps-inline-index-color, currentColor);
|
|
401
406
|
}
|
|
402
407
|
|
|
403
408
|
.ui-steps-inline-title {
|
|
@@ -418,8 +423,7 @@
|
|
|
418
423
|
color: hsl(var(--destructive));
|
|
419
424
|
}
|
|
420
425
|
|
|
421
|
-
.ui-steps-inline-item[data-status="wait"] .ui-steps-inline-control
|
|
422
|
-
.ui-steps-inline-separator {
|
|
426
|
+
.ui-steps-inline-item[data-status="wait"] .ui-steps-inline-control {
|
|
423
427
|
color: hsl(var(--muted-foreground));
|
|
424
428
|
}
|
|
425
429
|
|
|
@@ -427,5 +431,6 @@
|
|
|
427
431
|
width: var(--steps-inline-separator-size);
|
|
428
432
|
height: var(--steps-inline-separator-size);
|
|
429
433
|
flex: none;
|
|
434
|
+
color: var(--steps-inline-separator-color, hsl(var(--muted-foreground)));
|
|
430
435
|
}
|
|
431
436
|
}
|
|
@@ -192,9 +192,14 @@
|
|
|
192
192
|
text-align: center;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
/* Device-authorization also owns its CODE FIELD (gh#12): the preset's subject is the code, so
|
|
196
|
+
* the slot box comes from the preset instead of the generic square control tier. Scoped on the
|
|
197
|
+
* shell so every OTP inside the device flow inherits it; a per-instance override still wins. */
|
|
195
198
|
.ui-auth-shell[data-preset="device-authorization"] {
|
|
196
199
|
--auth-shell-card-max-width: var(--auth-shell-device-card-max-width);
|
|
197
200
|
--auth-shell-main-padding: var(--auth-shell-device-main-padding);
|
|
201
|
+
--otp-slot-inline-size: var(--auth-shell-device-otp-slot-inline-size);
|
|
202
|
+
--otp-slot-block-size: var(--auth-shell-device-otp-slot-block-size);
|
|
198
203
|
}
|
|
199
204
|
|
|
200
205
|
.ui-auth-shell[data-preset="context-selection"] {
|
|
@@ -212,6 +217,30 @@
|
|
|
212
217
|
--auth-shell-main-padding: var(--auth-shell-recovery-main-padding);
|
|
213
218
|
}
|
|
214
219
|
|
|
220
|
+
/* Registration preset (gh#256) — the sign-up measure. `flex-start` is the load-bearing line: a
|
|
221
|
+
* registration card is the tallest surface in the hosted-identity set, and a vertically CENTRED
|
|
222
|
+
* tall card overflows ABOVE the scroll origin on a short viewport, putting its first field out of
|
|
223
|
+
* reach. Start-aligned, the block-start inset is ordinary page padding the user scrolls past, and
|
|
224
|
+
* the block-end inset is the footer clearance the form keeps at the end of a long scroll. */
|
|
225
|
+
.ui-auth-shell[data-preset="registration"] {
|
|
226
|
+
--auth-shell-card-max-width: var(--auth-shell-registration-card-max-width);
|
|
227
|
+
--auth-shell-main-align: flex-start;
|
|
228
|
+
--auth-shell-main-padding: var(--auth-shell-registration-main-padding-block-start)
|
|
229
|
+
var(--auth-shell-registration-main-padding-inline)
|
|
230
|
+
var(--auth-shell-registration-main-padding-block-end);
|
|
231
|
+
--auth-shell-card-stack-gap: var(--auth-shell-registration-card-stack-gap);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* Fixed identity track — the mechanism `login` proved (gh#237), applied to the identity block
|
|
235
|
+
* wherever it sits in the column rather than through a grid row, so the preset does not constrain
|
|
236
|
+
* how many sections the page stacks. Content aligns to the slot END, so absent / one-line /
|
|
237
|
+
* wrapped two-line identity copy changes only the empty space ABOVE it and never moves the card
|
|
238
|
+
* off its canonical anchor. */
|
|
239
|
+
.ui-auth-shell[data-preset="registration"] .ui-auth-shell-card > .ui-auth-identity {
|
|
240
|
+
block-size: var(--auth-shell-registration-identity-slot-block-size);
|
|
241
|
+
justify-content: flex-end;
|
|
242
|
+
}
|
|
243
|
+
|
|
215
244
|
.ui-auth-identity {
|
|
216
245
|
display: flex;
|
|
217
246
|
flex-direction: column;
|
|
@@ -391,6 +420,16 @@
|
|
|
391
420
|
.ui-auth-shell[data-preset="account-recovery"] {
|
|
392
421
|
--auth-shell-main-padding: var(--auth-shell-recovery-main-padding-mobile);
|
|
393
422
|
}
|
|
423
|
+
|
|
424
|
+
/* registration = 15px inline at 390 (card x=15, width=360 — the same page rhythm as Login, so
|
|
425
|
+
* sign-in → sign-up never jumps on a phone). The block insets tighten but the footer clearance
|
|
426
|
+
* stays deliberately larger than the block-start inset: at 390 the form is at its tallest and
|
|
427
|
+
* the consent/legal footer must not sit flush against the submit button. */
|
|
428
|
+
.ui-auth-shell[data-preset="registration"] {
|
|
429
|
+
--auth-shell-main-padding: var(--auth-shell-registration-main-padding-block-start-mobile)
|
|
430
|
+
var(--auth-shell-registration-main-padding-inline-mobile)
|
|
431
|
+
var(--auth-shell-registration-main-padding-block-end-mobile);
|
|
432
|
+
}
|
|
394
433
|
}
|
|
395
434
|
|
|
396
435
|
/*
|
|
@@ -485,7 +524,11 @@
|
|
|
485
524
|
}
|
|
486
525
|
|
|
487
526
|
/* AppShell-owned mobile drawer trigger — hidden on desktop, shown once the docked sidebar
|
|
488
|
-
* collapses out
|
|
527
|
+
* collapses out at 900px, THE ONE AppShell breakpoint (gh#165, gh#259). NOT Tailwind's `lg`
|
|
528
|
+
* (1024px): this comment said "below lg" for six minors while the shipped rule below and the
|
|
529
|
+
* hamburger's own `max-[900px]:inline-flex` both fired at 900 — see the block comment on the
|
|
530
|
+
* `@media (width <= 56.25rem)` rule for why 900 is the canonical value. The drawer nav itself is
|
|
531
|
+
* a focus-trapped Sheet. */
|
|
489
532
|
.app-mobile-nav-trigger {
|
|
490
533
|
display: none;
|
|
491
534
|
flex: 0 0 auto;
|
|
@@ -74,6 +74,18 @@
|
|
|
74
74
|
* The slot padding compensation in card-layout.css subtracts the same token,
|
|
75
75
|
* so content stays aligned on the shell whatever the rail width. */
|
|
76
76
|
--card-accent-rail-width: 6px;
|
|
77
|
+
/* Accent placement `perimeter` (gh#12) — the FULL attention border. Two knobs so a service can
|
|
78
|
+
* tune the edge weight and the outer ring independently; the defaults reproduce the optical
|
|
79
|
+
* weight of `variant="featured"` (1px border + 1px ring) in the card's own semantic accent tone
|
|
80
|
+
* instead of --primary. Structural 1px literals, like the base card hairline. */
|
|
81
|
+
--card-accent-perimeter-width: 1px;
|
|
82
|
+
--card-accent-perimeter-ring-width: 1px;
|
|
83
|
+
/* `variant="featured"` edge — role-mirror knob (docs/TOKENS.md). `initial` so the --primary
|
|
84
|
+
* default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary
|
|
85
|
+
* reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card
|
|
86
|
+
* at once. Default = hsl(var(--primary)). */
|
|
87
|
+
--card-featured-border-color: initial;
|
|
88
|
+
--card-featured-ring-width: 1px;
|
|
77
89
|
--stat-card-label-font-size: var(--font-size-xs);
|
|
78
90
|
--stat-card-label-font-weight: var(--font-weight-medium);
|
|
79
91
|
--stat-card-label-letter-spacing: 0.04em;
|
|
@@ -67,6 +67,22 @@
|
|
|
67
67
|
* re-scopes --control-height to 36px, would silently shrink. Verified in Chromium: 36px before
|
|
68
68
|
* and after. A service opts in with a NAMED tier (`var(--control-height-lg)`), never a calc. */
|
|
69
69
|
--otp-slot-size: initial; /* default = var(--control-height) at the call site */
|
|
70
|
+
/* Per-AXIS overrides of the slot box (gh#12). --otp-slot-size stays the square shorthand; these
|
|
71
|
+
* two win over it when set, so a code field can be WIDER-than-tall or TALLER-than-wide without
|
|
72
|
+
* abandoning the token. A canonical device-grant code field is 27.5x52 per slot (a 4-slot
|
|
73
|
+
* `appearance="grouped"` box of 112x54 with its 1px group border) — not expressible with one
|
|
74
|
+
* square knob, which is why grouped OTP measured 146x38 against that artboard. `initial` for the
|
|
75
|
+
* same tier-mirror reason as --otp-slot-size: the whole fallback chain
|
|
76
|
+
* (axis → square → --control-height) must resolve at the CALL SITE so a shell that re-scopes
|
|
77
|
+
* --control-height still reaches an OTP row that sets neither axis. */
|
|
78
|
+
--otp-slot-inline-size: initial; /* default = var(--otp-slot-size, var(--control-height)) */
|
|
79
|
+
--otp-slot-block-size: initial; /* default = var(--otp-slot-size, var(--control-height)) */
|
|
80
|
+
/* Main-axis alignment of the whole OTP row (rule #44/#45 — chrome is a token, default quiet).
|
|
81
|
+
* `flex-start` is the historical behaviour, so an existing field is byte-identical; a centred
|
|
82
|
+
* code field is `align="center"` on InputOTP (which wins over this knob) or this token set once
|
|
83
|
+
* in a service theme. It exists because EVERY consumer was wrapping .ui-otp-container in a
|
|
84
|
+
* flex-centring div to get a centred challenge. */
|
|
85
|
+
--otp-container-align: flex-start;
|
|
70
86
|
|
|
71
87
|
/* Checked/on/active fills — `initial` so the --primary default re-resolves at the call site
|
|
72
88
|
* under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped
|
|
@@ -28,6 +28,18 @@
|
|
|
28
28
|
--avatar-square-background: initial;
|
|
29
29
|
--avatar-square-foreground: initial;
|
|
30
30
|
|
|
31
|
+
/* Capability medallion — `<Avatar appearance="tinted">` (gh#12): the tinted plate a capability /
|
|
32
|
+
feature glyph sits on, as opposed to the SOLID entity mark above. Pair it with shape="square"
|
|
33
|
+
for the canonical rounded square. --avatar-tinted-background / --avatar-tinted-foreground are
|
|
34
|
+
role-mirror knobs (`initial`, docs/TOKENS.md) so the --primary defaults resolve at the CALL
|
|
35
|
+
SITE and a scoped [data-tenant]/.dark override of --primary reaches them — the exact reason
|
|
36
|
+
this is a token and not the `hsl(var(--primary) / 0.1)` literal consumers were writing in page
|
|
37
|
+
CSS. Defaults = hsl(var(--primary) / 0.1) wash · hsl(var(--primary)) glyph ·
|
|
38
|
+
--control-icon-size glyph box. */
|
|
39
|
+
--avatar-tinted-background: initial;
|
|
40
|
+
--avatar-tinted-foreground: initial;
|
|
41
|
+
--avatar-tinted-glyph-size: var(--control-icon-size);
|
|
42
|
+
|
|
31
43
|
/* Progress track + fill — `initial` so the role defaults re-resolve under a scoped theme.
|
|
32
44
|
Track reads --secondary, fill reads --success; a service re-tones once.
|
|
33
45
|
Defaults = hsl(var(--secondary)) track · hsl(var(--success)) fill. */
|
|
@@ -63,8 +63,14 @@
|
|
|
63
63
|
--email-heading-font-weight: 500; /* mirrors --font-weight-medium — an email title is calm, not bold */
|
|
64
64
|
|
|
65
65
|
/* ── Primary CTA — the one action button ────────────────────────────────────────────────── */
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
/* TOUCH TARGET, deliberately DECOUPLED from --control-height-lg (36px). Do not "restore" the
|
|
67
|
+
* mirror: email is a mobile-first, touch-only medium. There is no hover state, no precise
|
|
68
|
+
* pointer, and mail clients do not reliably offer zoom or a focus affordance — so the web's AA
|
|
69
|
+
* floor (SC 2.5.8, 24x24, which 36px clears) is the wrong bar here. 44px is the AAA target size
|
|
70
|
+
* (SC 2.5.5, 44x44) and the Apple HIG 44pt / Material 48dp convention. A service that must keep
|
|
71
|
+
* the old box sets --email-cta-height + --email-cta-line-height in its own theme. */
|
|
72
|
+
--email-cta-height: 44px;
|
|
73
|
+
--email-cta-line-height: 44px; /* equal to the height: bulletproof vertical centring in Outlook */
|
|
68
74
|
--email-cta-padding-x: 16px; /* mirrors --space-4 */
|
|
69
75
|
--email-cta-radius: 6px; /* mirrors the --radius base (0.375rem) */
|
|
70
76
|
--email-cta-font-size: 14px; /* mirrors --font-size-base */
|
|
@@ -29,6 +29,26 @@
|
|
|
29
29
|
* (a brand's success-bg/-border are often more present than the faint 5%/30% default). */
|
|
30
30
|
--alert-bg-alpha: 0.05;
|
|
31
31
|
--alert-border-alpha: 0.3;
|
|
32
|
+
/* BANNER — the page-level Alert treatment (gh#255). Every constant a service would want to
|
|
33
|
+
* match to its page grid is a knob (rule #45): a banner that must keep the app's rounded card
|
|
34
|
+
* language sets --banner-radius, one that rules its top edge too sets --banner-border-width.
|
|
35
|
+
* The inline inset tracks the live page gutter, so a banner mounted above a PageContainer lines
|
|
36
|
+
* its text up with the page title instead of sitting at a second, unrelated margin. */
|
|
37
|
+
--banner-radius: 0;
|
|
38
|
+
--banner-border-width: 0;
|
|
39
|
+
--banner-border-block-end-width: 1px;
|
|
40
|
+
--banner-space-block: var(--space-3);
|
|
41
|
+
/* Inline inset tracks the page gutter so a banner's text lines up with the page title.
|
|
42
|
+
* `--space-page-active-x` steps down to the compact gutter on `.ui-page-container` only, and
|
|
43
|
+
* custom properties inherit — so a banner rendered INSIDE the container picks the compact value
|
|
44
|
+
* up for free, while the normal case (a banner mounted ABOVE the container, or in AppShell) would
|
|
45
|
+
* keep the desktop gutter and sit 8px out at 390px. The compact step below is what actually keeps
|
|
46
|
+
* the two aligned; retune it rather than hard-coding a mobile inset at the call site. */
|
|
47
|
+
--banner-space-inline: var(--space-page-active-x);
|
|
48
|
+
--banner-space-inline-compact: var(--space-page-compact-x);
|
|
49
|
+
/* Dismiss offset is measured from the banner's own (shorter) block inset, so the ✕ stays
|
|
50
|
+
* optically centred on a one-line strip instead of floating at the inline-alert offset. */
|
|
51
|
+
--banner-dismiss-space-offset: var(--space-2);
|
|
32
52
|
/* Brand glow layer for the raised dialog/sheet panel — invisible no-op at rest (rule #44).
|
|
33
53
|
* Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the
|
|
34
54
|
* global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change. */
|
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
--steps-inline-item-gap: var(--space-1);
|
|
15
15
|
--steps-inline-font-size: var(--font-size-xs);
|
|
16
16
|
--steps-inline-separator-size: var(--control-icon-size-sm);
|
|
17
|
+
/* Inline-step emphasis (gh#12). The step number's weight and tint are separate knobs so a
|
|
18
|
+
* service can express progression the canonical way — an accent TINT at normal weight — instead
|
|
19
|
+
* of the original bold. --steps-inline-index-color / --steps-inline-separator-color are
|
|
20
|
+
* role-mirror knobs (docs/TOKENS.md): `initial` so their defaults (the inherited step colour and
|
|
21
|
+
* the --muted-foreground role) resolve at the CALL SITE and a scoped [data-tenant]/.dark
|
|
22
|
+
* override still reaches them. Defaults reproduce today's row byte for byte. */
|
|
23
|
+
--steps-inline-index-font-weight: var(--font-weight-bold);
|
|
24
|
+
--steps-inline-index-color: initial; /* default = currentColor at the call site */
|
|
25
|
+
--steps-inline-separator-color: initial; /* default = hsl(var(--muted-foreground)) */
|
|
17
26
|
|
|
18
27
|
/* AppSettingPicker `compact` (gh#217) — the small, content-hugging labelled trigger used in an
|
|
19
28
|
* auth/legal footer, where the square icon-only default reads as a stray button and the full
|
|
@@ -55,4 +64,30 @@
|
|
|
55
64
|
* scrollbar never overlaps the controls. Rule #45 — a service theme retunes it to its grid
|
|
56
65
|
* (0 on an overlay-scrollbar platform). */
|
|
57
66
|
--filter-bar-scroll-padding-y: var(--space-1);
|
|
67
|
+
|
|
68
|
+
/* FilterBar typed model (#258) — the search / chip / result-count regions the bar now OWNS, so a
|
|
69
|
+
* list page never re-decides them. Every constant is a knob (rule #45): a service matches its own
|
|
70
|
+
* grid by retuning these once instead of per page.
|
|
71
|
+
*
|
|
72
|
+
* Search MEASURE — the whole point of the `search` slot. Below the 640px step the control is
|
|
73
|
+
* full-width (a bounded search box on a 390px screen wastes the row), so this binds only at and
|
|
74
|
+
* above it. 18rem = 288px: comfortable for a JA/VI query without crowding the filter groups. */
|
|
75
|
+
--filter-bar-search-width: 18rem;
|
|
76
|
+
|
|
77
|
+
/* Applied-filter chips. */
|
|
78
|
+
--filter-bar-chip-gap: var(--space-inline-xs);
|
|
79
|
+
--filter-bar-chip-row-gap: var(--space-inline-xs);
|
|
80
|
+
--filter-bar-chip-padding-inline: var(--space-2);
|
|
81
|
+
--filter-bar-chip-padding-block: var(--space-1);
|
|
82
|
+
--filter-bar-chip-radius: var(--radius-sm);
|
|
83
|
+
--filter-bar-chip-font-size: var(--font-size-xs);
|
|
84
|
+
/* Role-mirror knobs (docs/TOKENS.md) — `initial` at :root so --muted / --muted-foreground
|
|
85
|
+
* re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme. Binding a role var here
|
|
86
|
+
* would freeze the chip at the :root palette and a tenant override would never reach it. */
|
|
87
|
+
--filter-bar-chip-background: initial;
|
|
88
|
+
--filter-bar-chip-foreground: initial;
|
|
89
|
+
|
|
90
|
+
/* Result-count live region. */
|
|
91
|
+
--filter-bar-count-font-size: var(--font-size-xs);
|
|
92
|
+
--filter-bar-count-foreground: initial;
|
|
58
93
|
}
|