@godxjp/ui 18.4.0 → 18.6.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/README.md +16 -1
- package/dist/app/theme-axes.d.ts +11 -3
- package/dist/app/theme-axes.js +2 -1
- package/dist/components/charts/chart-frame.d.ts +17 -3
- package/dist/components/charts/chart-frame.js +7 -1
- package/dist/components/charts/chart-summary.d.ts +21 -0
- package/dist/components/charts/chart-summary.js +32 -5
- package/dist/components/charts/compact-bar-trend.d.ts +30 -0
- package/dist/components/charts/compact-bar-trend.js +74 -0
- package/dist/components/charts/index.d.ts +6 -0
- package/dist/components/charts/index.js +2 -0
- package/dist/components/data-display/badge.d.ts +8 -0
- package/dist/components/data-display/badge.js +7 -1
- package/dist/components/data-display/data-table.d.ts +40 -4
- package/dist/components/data-display/data-table.js +69 -2
- package/dist/components/data-display/empty-state.js +1 -1
- package/dist/components/data-display/index.d.ts +4 -2
- package/dist/components/data-display/index.js +10 -1
- package/dist/components/data-display/list-row.d.ts +41 -4
- package/dist/components/data-display/list-row.js +23 -3
- package/dist/components/data-display/qr-code.d.ts +1 -1
- package/dist/components/data-display/service-launcher-card.d.ts +40 -0
- package/dist/components/data-display/service-launcher-card.js +97 -0
- package/dist/components/data-display/table.d.ts +47 -2
- package/dist/components/data-display/table.js +29 -4
- package/dist/components/data-entry/command-palette.d.ts +37 -0
- package/dist/components/data-entry/command-palette.js +125 -0
- package/dist/components/data-entry/command.d.ts +10 -10
- package/dist/components/data-entry/index.d.ts +3 -0
- package/dist/components/data-entry/index.js +2 -0
- package/dist/components/data-entry/input-otp.d.ts +1 -0
- package/dist/components/feedback/dialog.d.ts +16 -1
- package/dist/components/feedback/dialog.js +46 -33
- package/dist/components/feedback/index.d.ts +5 -2
- package/dist/components/feedback/index.js +8 -2
- package/dist/components/feedback/sheet.d.ts +26 -4
- package/dist/components/feedback/sheet.js +54 -7
- package/dist/components/feedback/two-factor-setup.d.ts +31 -0
- package/dist/components/feedback/two-factor-setup.js +97 -0
- package/dist/components/general/index.d.ts +1 -1
- package/dist/components/general/logo.d.ts +49 -9
- package/dist/components/general/logo.js +67 -14
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +55 -30
- package/dist/components/layout/auth-account-summary.d.ts +5 -0
- package/dist/components/layout/auth-account-summary.js +31 -0
- package/dist/components/layout/auth-divider.d.ts +7 -0
- package/dist/components/layout/auth-divider.js +21 -0
- package/dist/components/layout/auth-footer.d.ts +5 -0
- package/dist/components/layout/auth-footer.js +24 -0
- package/dist/components/layout/auth-identity.d.ts +5 -0
- package/dist/components/layout/auth-identity.js +17 -0
- package/dist/components/layout/auth-shell.d.ts +9 -1
- package/dist/components/layout/auth-shell.js +25 -6
- package/dist/components/layout/auth-stack.d.ts +4 -0
- package/dist/components/layout/auth-stack.js +8 -0
- package/dist/components/layout/centered-shell.d.ts +5 -2
- package/dist/components/layout/centered-shell.js +29 -19
- package/dist/components/layout/error-surface.d.ts +35 -0
- package/dist/components/layout/error-surface.js +155 -0
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/flex.js +4 -0
- package/dist/components/layout/index.d.ts +23 -3
- package/dist/components/layout/index.js +21 -2
- package/dist/components/layout/legal-document-shell.d.ts +34 -0
- package/dist/components/layout/legal-document-shell.js +186 -0
- package/dist/components/layout/master-detail.d.ts +24 -0
- package/dist/components/layout/master-detail.js +48 -0
- package/dist/components/layout/org-switcher.d.ts +9 -0
- package/dist/components/layout/org-switcher.js +218 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +7 -2
- package/dist/components/layout/sidebar-link.d.ts +43 -0
- package/dist/components/layout/sidebar-link.js +13 -0
- package/dist/components/layout/sidebar.d.ts +42 -5
- package/dist/components/layout/sidebar.js +170 -85
- package/dist/components/navigation/app-setting-picker.js +17 -6
- package/dist/components/navigation/filter-bar.d.ts +12 -3
- package/dist/components/navigation/filter-bar.js +8 -2
- package/dist/components/navigation/index.d.ts +2 -2
- package/dist/components/navigation/index.js +3 -1
- package/dist/components/navigation/steps.js +38 -19
- package/dist/components/navigation/tabs-scroll.d.ts +70 -0
- package/dist/components/navigation/tabs-scroll.js +76 -0
- package/dist/components/navigation/tabs.js +47 -23
- package/dist/components/ui/avatar.d.ts +2 -1
- package/dist/components/ui/avatar.js +2 -1
- package/dist/components/ui/input-otp.d.ts +4 -1
- package/dist/components/ui/input-otp.js +2 -1
- package/dist/components/ui/toggle-group.d.ts +7 -4
- package/dist/components/ui/toggle-group.js +32 -21
- package/dist/email/brand-mark.d.ts +108 -0
- package/dist/email/brand-mark.js +88 -0
- package/dist/email/color.d.ts +53 -0
- package/dist/email/color.js +37 -0
- package/dist/email/geometry.d.ts +120 -0
- package/dist/email/geometry.js +90 -0
- package/dist/email/index.d.ts +83 -0
- package/dist/email/index.js +82 -0
- package/dist/email/inline-style.d.ts +27 -0
- package/dist/email/inline-style.js +26 -0
- package/dist/email/tokens.generated.d.ts +167 -0
- package/dist/email/tokens.generated.js +162 -0
- package/dist/email/urgency.d.ts +22 -0
- package/dist/email/urgency.js +14 -0
- package/dist/i18n/messages/en.json +36 -3
- package/dist/i18n/messages/ja.json +36 -3
- package/dist/i18n/messages/vi.json +36 -3
- package/dist/inertia/index.d.ts +50 -0
- package/dist/inertia/index.js +5 -0
- package/dist/props/components/app.prop.d.ts +11 -1
- package/dist/props/components/charts.prop.d.ts +40 -1
- package/dist/props/components/data-display.prop.d.ts +75 -1
- package/dist/props/components/data-entry.prop.d.ts +6 -0
- package/dist/props/components/feedback.prop.d.ts +10 -0
- package/dist/props/components/index.d.ts +3 -3
- package/dist/props/components/layout.prop.d.ts +560 -10
- package/dist/props/components/layout.prop.js +1 -0
- package/dist/props/components/navigation.prop.d.ts +24 -2
- package/dist/props/registry.d.ts +433 -8
- package/dist/props/registry.js +527 -9
- package/dist/props/vocabulary/data.prop.d.ts +27 -0
- package/dist/props/vocabulary/index.d.ts +3 -3
- package/dist/props/vocabulary/interaction.prop.d.ts +10 -1
- package/dist/props/vocabulary/layout.prop.d.ts +49 -0
- package/dist/styles/base.css +8 -2
- package/dist/styles/card-layout.css +161 -7
- package/dist/styles/chart-layout.css +97 -0
- package/dist/styles/control.css +76 -2
- package/dist/styles/data-display-layout.css +78 -6
- package/dist/styles/dialog-layout.css +105 -4
- package/dist/styles/fonts.css +15 -4
- package/dist/styles/index.css +11 -3
- package/dist/styles/layout.css +466 -1
- package/dist/styles/logo-layout.css +95 -0
- package/dist/styles/navigation-layout.css +152 -0
- package/dist/styles/shell-layout.css +790 -40
- package/dist/styles/table-layout.css +164 -1
- package/dist/theme/dxs.canonical.css +77 -0
- package/dist/tokens/axes.css +40 -0
- package/dist/tokens/base.css +6 -0
- package/dist/tokens/components/card.css +48 -0
- package/dist/tokens/components/chart.css +47 -0
- package/dist/tokens/components/control.css +16 -0
- package/dist/tokens/components/data-display.css +13 -0
- package/dist/tokens/components/email.css +93 -0
- package/dist/tokens/components/error-surface.css +36 -0
- package/dist/tokens/components/feedback.css +17 -0
- package/dist/tokens/components/legal-document.css +67 -0
- package/dist/tokens/components/list-row.css +43 -2
- package/dist/tokens/components/logo.css +74 -0
- package/dist/tokens/components/navigation.css +28 -0
- package/dist/tokens/components/sheet.css +19 -0
- package/dist/tokens/components/shell.css +210 -0
- package/dist/tokens/components/sidebar.css +33 -0
- package/dist/tokens/components/table.css +45 -0
- package/dist/tokens/foundation.css +31 -2
- package/dist/tokens/semantic/layout.css +69 -2
- package/package.json +22 -5
|
@@ -6,24 +6,31 @@
|
|
|
6
6
|
@layer components {
|
|
7
7
|
.app-root {
|
|
8
8
|
display: grid;
|
|
9
|
+
width: 100%;
|
|
10
|
+
max-width: 100vw;
|
|
11
|
+
min-width: 0;
|
|
9
12
|
grid-template-areas:
|
|
10
13
|
"sidebar topbar"
|
|
11
14
|
"sidebar main"
|
|
12
15
|
"sidebar footer";
|
|
13
|
-
|
|
16
|
+
/* Rail width is a knob, never a literal (rule #45) — a service on a different design grid sets
|
|
17
|
+
* --app-shell-sidebar-width once (e.g. 255px) instead of forking this rule. */
|
|
18
|
+
grid-template-columns: var(--app-shell-sidebar-width) minmax(0, 1fr);
|
|
14
19
|
grid-template-rows: var(--app-shell-bar-height) minmax(0, 1fr) auto;
|
|
15
20
|
height: 100vh;
|
|
16
21
|
background: hsl(var(--background));
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
.app-root[data-collapsed="true"] {
|
|
20
|
-
grid-template-columns:
|
|
25
|
+
grid-template-columns: var(--app-shell-rail-width) minmax(0, 1fr);
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
.app-sidebar {
|
|
24
29
|
grid-area: sidebar;
|
|
25
30
|
display: flex;
|
|
31
|
+
height: 100%;
|
|
26
32
|
min-width: 0;
|
|
33
|
+
min-height: 0;
|
|
27
34
|
flex-direction: column;
|
|
28
35
|
overflow: hidden;
|
|
29
36
|
border-inline-end: 1px solid hsl(var(--border));
|
|
@@ -40,9 +47,10 @@
|
|
|
40
47
|
* set to baseline; --scaling:1 alone is not enough — see foundation.css). */
|
|
41
48
|
display: flex;
|
|
42
49
|
min-width: 0;
|
|
50
|
+
overflow: hidden;
|
|
43
51
|
align-items: center;
|
|
44
|
-
gap: var(--
|
|
45
|
-
padding-inline: var(--
|
|
52
|
+
gap: var(--app-shell-bar-gap);
|
|
53
|
+
padding-inline: var(--app-shell-bar-inset);
|
|
46
54
|
border-bottom: 1px solid hsl(var(--border));
|
|
47
55
|
background: hsl(var(--card));
|
|
48
56
|
/* Opt-in brand-chrome wash — invisible by default (--topbar-gradient: none). */
|
|
@@ -105,7 +113,7 @@
|
|
|
105
113
|
flex: 1 1 auto;
|
|
106
114
|
flex-direction: column;
|
|
107
115
|
align-items: center;
|
|
108
|
-
justify-content:
|
|
116
|
+
justify-content: var(--auth-shell-main-align);
|
|
109
117
|
padding: var(--auth-shell-main-padding);
|
|
110
118
|
}
|
|
111
119
|
|
|
@@ -120,6 +128,271 @@
|
|
|
120
128
|
color: hsl(var(--muted-foreground));
|
|
121
129
|
}
|
|
122
130
|
|
|
131
|
+
.ui-auth-shell[data-variant="canonical"] {
|
|
132
|
+
--control-height: var(--auth-shell-canonical-control-height);
|
|
133
|
+
--heading-h1: var(--auth-shell-canonical-heading-size);
|
|
134
|
+
--auth-shell-card-max-width: var(--auth-shell-canonical-card-max-width);
|
|
135
|
+
--auth-shell-main-padding: var(--auth-shell-canonical-main-padding);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
* NAMED FLOW PRESETS (gh#217 / gh#220) — orthogonal to `variant` and declared AFTER it (equal
|
|
140
|
+
* specificity, so source order wins) so a preset re-measures the page while `variant="canonical"`
|
|
141
|
+
* keeps owning control density and heading size. A preset owns the card measure + page gutters,
|
|
142
|
+
* so the consumer never overrides `--auth-shell-card-max-width` by hand.
|
|
143
|
+
*/
|
|
144
|
+
.ui-auth-shell[data-preset] .ui-auth-shell-card {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
gap: var(--auth-shell-card-stack-gap);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* Login is an anchored three-part composition: AuthIdentity · Card · AuthFooter. The fixed
|
|
151
|
+
* identity track absorbs absent, one-line and wrapped two-line requester states while its
|
|
152
|
+
* content remains visually adjacent to the Card. This keeps real requester data intact without
|
|
153
|
+
* allowing its line count to move the canonical SCR-001 card anchor (gh#237). */
|
|
154
|
+
.ui-auth-shell[data-preset="login"] {
|
|
155
|
+
--auth-shell-card-max-width: var(--auth-shell-login-card-max-width);
|
|
156
|
+
--auth-shell-main-align: flex-start;
|
|
157
|
+
--auth-shell-main-padding: var(--auth-shell-login-flow-offset-block)
|
|
158
|
+
var(--auth-shell-login-main-padding-inline) var(--auth-shell-login-main-padding-block-end);
|
|
159
|
+
--auth-shell-card-stack-gap: var(--auth-shell-login-card-stack-gap);
|
|
160
|
+
--auth-shell-card-padding-block-compact: var(--auth-shell-login-card-padding-block-compact);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.ui-auth-shell[data-preset="login"] .ui-auth-shell-card {
|
|
164
|
+
display: grid;
|
|
165
|
+
grid-template-rows: var(--auth-shell-login-identity-slot-block-size) auto auto;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.ui-auth-shell[data-preset="login"] .ui-auth-shell-card > .ui-auth-identity,
|
|
169
|
+
.ui-auth-shell[data-preset="login"] .ui-auth-shell-card > :has(> .ui-auth-identity) {
|
|
170
|
+
min-width: 0;
|
|
171
|
+
min-height: 0;
|
|
172
|
+
block-size: var(--auth-shell-login-identity-slot-block-size);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.ui-auth-shell[data-preset="login"] .ui-auth-shell-card > .ui-auth-identity,
|
|
176
|
+
.ui-auth-shell[data-preset="login"]
|
|
177
|
+
.ui-auth-shell-card
|
|
178
|
+
> :has(> .ui-auth-identity)
|
|
179
|
+
> .ui-auth-identity {
|
|
180
|
+
height: 100%;
|
|
181
|
+
justify-content: flex-end;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.ui-auth-shell[data-preset="login"] .ui-auth-requester {
|
|
185
|
+
max-width: 100%;
|
|
186
|
+
justify-content: center;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.ui-auth-shell[data-preset="login"] .ui-auth-requester > :last-child {
|
|
190
|
+
min-width: 0;
|
|
191
|
+
overflow-wrap: anywhere;
|
|
192
|
+
text-align: center;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.ui-auth-shell[data-preset="device-authorization"] {
|
|
196
|
+
--auth-shell-card-max-width: var(--auth-shell-device-card-max-width);
|
|
197
|
+
--auth-shell-main-padding: var(--auth-shell-device-main-padding);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.ui-auth-shell[data-preset="context-selection"] {
|
|
201
|
+
--auth-shell-card-max-width: var(--auth-shell-context-card-max-width);
|
|
202
|
+
--auth-shell-main-padding: var(--auth-shell-context-main-padding);
|
|
203
|
+
--auth-shell-card-stack-gap: var(--auth-shell-context-card-stack-gap);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Account-recovery preset (gh#233) — one 432px measure for BOTH SCR-008 panels (password
|
|
207
|
+
* recovery and the sign-in MFA challenge). Title + description live INSIDE the bordered surface
|
|
208
|
+
* here, so the panel is `Card > CardHeader(CardTitle + CardDescription)` with no `AuthIdentity`
|
|
209
|
+
* mark above it — the preset only re-measures the page, it never dictates content. */
|
|
210
|
+
.ui-auth-shell[data-preset="account-recovery"] {
|
|
211
|
+
--auth-shell-card-max-width: var(--auth-shell-recovery-card-max-width);
|
|
212
|
+
--auth-shell-main-padding: var(--auth-shell-recovery-main-padding);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.ui-auth-identity {
|
|
216
|
+
display: flex;
|
|
217
|
+
flex-direction: column;
|
|
218
|
+
align-items: center;
|
|
219
|
+
gap: var(--auth-identity-gap);
|
|
220
|
+
text-align: center;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.ui-auth-requester {
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
gap: var(--auth-requester-gap);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.ui-auth-requester-icon {
|
|
230
|
+
display: inline-flex;
|
|
231
|
+
width: var(--auth-requester-icon-size);
|
|
232
|
+
height: var(--auth-requester-icon-size);
|
|
233
|
+
align-items: center;
|
|
234
|
+
justify-content: center;
|
|
235
|
+
border-radius: var(--radius-sm);
|
|
236
|
+
background: hsl(var(--accent));
|
|
237
|
+
color: hsl(var(--primary));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.ui-auth-requester-icon > svg {
|
|
241
|
+
width: var(--auth-requester-glyph-size);
|
|
242
|
+
height: var(--auth-requester-glyph-size);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.ui-auth-account-summary {
|
|
246
|
+
display: flex;
|
|
247
|
+
min-height: var(--auth-account-summary-min-height);
|
|
248
|
+
flex-wrap: wrap;
|
|
249
|
+
align-items: center;
|
|
250
|
+
gap: var(--auth-account-summary-gap);
|
|
251
|
+
padding: var(--auth-account-summary-padding);
|
|
252
|
+
border-radius: var(--radius-md);
|
|
253
|
+
background: hsl(var(--muted));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.ui-auth-account-identity {
|
|
257
|
+
display: flex;
|
|
258
|
+
min-width: min(100%, var(--auth-account-summary-identity-min-width));
|
|
259
|
+
flex: 1 1 var(--auth-account-summary-identity-min-width);
|
|
260
|
+
align-items: center;
|
|
261
|
+
gap: var(--auth-account-summary-gap);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.ui-auth-account-avatar {
|
|
265
|
+
width: var(--auth-account-summary-avatar-size);
|
|
266
|
+
height: var(--auth-account-summary-avatar-size);
|
|
267
|
+
flex: none;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.ui-auth-account-fallback-icon {
|
|
271
|
+
width: var(--auth-account-summary-avatar-glyph-size);
|
|
272
|
+
height: var(--auth-account-summary-avatar-glyph-size);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.ui-auth-account-email {
|
|
276
|
+
min-width: 0;
|
|
277
|
+
overflow: hidden;
|
|
278
|
+
color: hsl(var(--foreground));
|
|
279
|
+
font-size: var(--auth-account-summary-email-font-size);
|
|
280
|
+
/* Declares its own line box because it CLIPS — see gh#254. */
|
|
281
|
+
line-height: var(--auth-account-summary-email-line-height);
|
|
282
|
+
font-weight: var(--font-weight-medium);
|
|
283
|
+
text-overflow: ellipsis;
|
|
284
|
+
white-space: nowrap;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.ui-auth-account-summary > [data-slot="button"] {
|
|
288
|
+
margin-inline-start: auto;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.ui-auth-legal-footer {
|
|
292
|
+
display: flex;
|
|
293
|
+
flex-wrap: wrap;
|
|
294
|
+
align-items: center;
|
|
295
|
+
justify-content: center;
|
|
296
|
+
gap: var(--auth-footer-content-gap);
|
|
297
|
+
color: hsl(var(--muted-foreground));
|
|
298
|
+
font-family: var(--font-mono);
|
|
299
|
+
font-size: var(--auth-footer-text-font-size);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.ui-auth-legal-footer-item {
|
|
303
|
+
display: inline-flex;
|
|
304
|
+
align-items: center;
|
|
305
|
+
gap: var(--auth-footer-content-gap);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.ui-auth-stack {
|
|
309
|
+
display: flex;
|
|
310
|
+
flex-direction: column;
|
|
311
|
+
gap: var(--auth-stack-gap);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.ui-auth-shell[data-variant="canonical"] .ui-form-field {
|
|
315
|
+
--field-label-gap: var(--auth-shell-field-label-gap);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.ui-auth-shell[data-variant="canonical"] [data-slot="form-field-label"] [data-slot="label"] {
|
|
319
|
+
font-size: var(--auth-shell-field-label-font-size);
|
|
320
|
+
line-height: var(--auth-shell-field-label-line-height);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/*
|
|
324
|
+
* Compact auth card density — the two card axes are wired to SEPARATE knobs (gh#232), so a
|
|
325
|
+
* service can shorten the canonical Login card without narrowing its column (or vice versa):
|
|
326
|
+
* inline column ← --auth-shell-compact-card-inset → --card-space-inset
|
|
327
|
+
* block padding ← --auth-shell-card-padding-block-compact → --card-space-shell-y
|
|
328
|
+
* header↔body ← --auth-shell-card-body-gap-compact → --card-space-body-y
|
|
329
|
+
* solo body ← --auth-shell-card-padding-block-compact → --card-space-solo-y
|
|
330
|
+
* The block knob is `initial` at :root, so its default resolves HERE against the live inset —
|
|
331
|
+
* canonical output is byte-identical to pre-gh#232 while the knob is now actually consumed.
|
|
332
|
+
* --card-space-solo-y is mapped EXPLICITLY as well as via the shell-y fallback: a solo body
|
|
333
|
+
* takes both block edges symmetrically, and naming it here keeps that contract legible at the
|
|
334
|
+
* one place a reader looks for the compact card's spacing.
|
|
335
|
+
*/
|
|
336
|
+
.ui-auth-shell[data-density="compact"] .ui-auth-shell-card {
|
|
337
|
+
--card-space-inset: var(--auth-shell-compact-card-inset);
|
|
338
|
+
--card-space-shell-y: var(
|
|
339
|
+
--auth-shell-card-padding-block-compact,
|
|
340
|
+
var(--auth-shell-compact-card-inset)
|
|
341
|
+
);
|
|
342
|
+
--card-space-body-y: var(--auth-shell-card-body-gap-compact);
|
|
343
|
+
--card-space-solo-y: var(
|
|
344
|
+
--auth-shell-card-padding-block-compact,
|
|
345
|
+
var(--auth-shell-compact-card-inset)
|
|
346
|
+
);
|
|
347
|
+
--card-space-gap: var(--auth-shell-card-gap-compact);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.ui-auth-divider {
|
|
351
|
+
display: grid;
|
|
352
|
+
width: 100%;
|
|
353
|
+
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
|
354
|
+
align-items: center;
|
|
355
|
+
gap: var(--auth-shell-divider-gap);
|
|
356
|
+
color: hsl(var(--auth-shell-divider-label-color));
|
|
357
|
+
font-size: var(--auth-shell-divider-label-font-size);
|
|
358
|
+
line-height: 1;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.ui-auth-divider-rule {
|
|
362
|
+
height: 1px;
|
|
363
|
+
background: hsl(var(--auth-shell-divider-rule-color));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@media (max-width: 30rem) {
|
|
367
|
+
.ui-auth-shell[data-variant="canonical"] {
|
|
368
|
+
--auth-shell-main-padding: var(--auth-shell-canonical-main-padding-mobile);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.ui-auth-shell[data-preset="login"] {
|
|
372
|
+
--auth-shell-main-padding: var(--auth-shell-login-flow-offset-block-mobile)
|
|
373
|
+
var(--auth-shell-login-main-padding-inline-mobile)
|
|
374
|
+
var(--auth-shell-login-main-padding-block-end);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* Declared after the canonical mobile inset so a preset's mobile gutter wins at equal
|
|
378
|
+
* specificity: device-authorization = 5px inline (card x=5, width=380 at 390px);
|
|
379
|
+
* context-selection = edge-to-edge (0 inline). */
|
|
380
|
+
.ui-auth-shell[data-preset="device-authorization"] {
|
|
381
|
+
--auth-shell-main-padding: var(--auth-shell-device-main-padding-mobile);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.ui-auth-shell[data-preset="context-selection"] {
|
|
385
|
+
--auth-shell-main-padding: var(--auth-shell-context-main-padding-mobile);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* account-recovery = 15px inline at 390 (panel x=15, width=360). This is a DECIDED mobile
|
|
389
|
+
* contract — the supplied 390 canonical reference is a desktop 2x2 composite that overflows
|
|
390
|
+
* horizontally and is not a valid source (gh#233). */
|
|
391
|
+
.ui-auth-shell[data-preset="account-recovery"] {
|
|
392
|
+
--auth-shell-main-padding: var(--auth-shell-recovery-main-padding-mobile);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
123
396
|
/*
|
|
124
397
|
* CENTERED SHELL — authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page",
|
|
125
398
|
* account / self-service, standalone settings). A flex column over the same brand-wash surface as
|
|
@@ -165,9 +438,17 @@
|
|
|
165
438
|
.ui-centered-shell-column {
|
|
166
439
|
width: 100%;
|
|
167
440
|
margin-inline: auto;
|
|
441
|
+
/* Block offset knob — `0` (top-aligned page, the default) or `auto` (viewport-centred system
|
|
442
|
+
* surface via `align="center"`). Auto margins absorb the free space of the flex `main`, and
|
|
443
|
+
* collapse to 0 when the content overflows, so tall content still scrolls from the top. */
|
|
444
|
+
margin-block: var(--centered-shell-column-offset-block);
|
|
168
445
|
max-width: var(--centered-shell-width-md);
|
|
169
446
|
}
|
|
170
447
|
|
|
448
|
+
.ui-centered-shell-column[data-align="center"] {
|
|
449
|
+
--centered-shell-column-offset-block: auto;
|
|
450
|
+
}
|
|
451
|
+
|
|
171
452
|
.ui-centered-shell-column[data-width="sm"] {
|
|
172
453
|
max-width: var(--centered-shell-width-sm);
|
|
173
454
|
}
|
|
@@ -211,11 +492,36 @@
|
|
|
211
492
|
margin-inline-end: var(--space-1);
|
|
212
493
|
}
|
|
213
494
|
|
|
495
|
+
/* Drawer scrim = a SHARE of the ONE shared semantic scrim (--overlay-background), resolved at the
|
|
496
|
+
* call site so a scoped [data-tenant]/.dark override of --overlay-background reaches the portaled
|
|
497
|
+
* drawer (docs/TOKENS.md · "Role-mirror knobs MUST be `initial`"). Default resolves to the same
|
|
498
|
+
* rgb(0 0 0 / 0.2) it has always been; setting --app-shell-mobile-nav-background still wins. */
|
|
499
|
+
[data-slot="sheet-overlay"].app-mobile-nav-overlay {
|
|
500
|
+
background-color: var(
|
|
501
|
+
--app-shell-mobile-nav-background,
|
|
502
|
+
color-mix(in srgb, var(--overlay-background) var(--app-shell-mobile-nav-alpha), transparent)
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.app-mobile-nav-drawer {
|
|
507
|
+
padding-block-start: max(var(--sheet-pad-y), env(safe-area-inset-top));
|
|
508
|
+
padding-block-end: max(var(--sheet-pad-y), env(safe-area-inset-bottom));
|
|
509
|
+
padding-inline-start: max(var(--sheet-pad-x), env(safe-area-inset-left));
|
|
510
|
+
padding-inline-end: max(var(--sheet-pad-x), env(safe-area-inset-right));
|
|
511
|
+
overscroll-behavior: contain;
|
|
512
|
+
}
|
|
513
|
+
|
|
214
514
|
/* The drawer nav reuses the docked Sidebar node; its `.sb-root { display: contents }` flattens
|
|
215
|
-
* into the scrollable SheetBody so sections/footer flow naturally in the panel.
|
|
515
|
+
* into the scrollable SheetBody so sections/footer flow naturally in the panel.
|
|
516
|
+
* The body's INLINE inset is tokenized on the component itself
|
|
517
|
+
* (`px-[var(--app-shell-mobile-nav-inset)]` in AppShell, which wins over SheetBody's generic
|
|
518
|
+
* `px-[var(--sheet-pad-x)]` because both are utilities) — the nav owns its edge instead of being
|
|
519
|
+
* double-padded (gh#211). Do NOT set `padding-inline` here: this file is `@layer components`, so
|
|
520
|
+
* a Tailwind utility on the element always wins. */
|
|
216
521
|
.app-mobile-nav-body {
|
|
217
522
|
display: flex;
|
|
218
523
|
flex-direction: column;
|
|
524
|
+
overscroll-behavior: contain;
|
|
219
525
|
}
|
|
220
526
|
|
|
221
527
|
.app-topbar-spacer {
|
|
@@ -229,6 +535,21 @@
|
|
|
229
535
|
font-size: var(--font-size-base);
|
|
230
536
|
}
|
|
231
537
|
|
|
538
|
+
/*
|
|
539
|
+
* THE ONE AppShell breakpoint — 900px (56.25rem), the DXS canonical value. It is stated in exactly
|
|
540
|
+
* two places and they must agree: this rule and `max-[900px]:inline-flex` on the hamburger in
|
|
541
|
+
* `app-shell.tsx` (a CSS @media cannot read a custom property, so the trigger's visibility has to
|
|
542
|
+
* repeat the number; making it a public knob needs a `data-shell-mode` attribute — tracked in the
|
|
543
|
+
* #213 audit, out of scope here).
|
|
544
|
+
*
|
|
545
|
+
* There used to be a SECOND, conflicting `@media (max-width: 768px)` block further down that also
|
|
546
|
+
* restructured `.app-root`. It (a) dropped the `"footer"` grid area and set `.app-footer{display:
|
|
547
|
+
* none}`, silently deleting whatever a consumer passed to AppShell's `footer` slot on a phone, and
|
|
548
|
+
* (b) re-declared the grid ROW template with a 3rem literal, overriding --app-shell-bar-height so
|
|
549
|
+
* a themed bar height was ignored below 768px only. Between 768 and 900 the sidebar was hidden by
|
|
550
|
+
* THIS rule while the grid still reserved a sidebar track — incoherent. Both are gone; the
|
|
551
|
+
* footer row and the bar-height token now hold at every width.
|
|
552
|
+
*/
|
|
232
553
|
@media (width <= 56.25rem) {
|
|
233
554
|
.app-root,
|
|
234
555
|
.app-root[data-collapsed="true"] {
|
|
@@ -243,6 +564,11 @@
|
|
|
243
564
|
display: none;
|
|
244
565
|
}
|
|
245
566
|
|
|
567
|
+
/* The docked rail is gone, so the bar tightens against the viewport edge (knob, not a literal). */
|
|
568
|
+
.app-topbar {
|
|
569
|
+
padding-inline: var(--app-shell-bar-inset-compact);
|
|
570
|
+
}
|
|
571
|
+
|
|
246
572
|
.app-topbar-left:has(.app-mobile-sidebar-trigger) {
|
|
247
573
|
display: flex;
|
|
248
574
|
}
|
|
@@ -250,16 +576,165 @@
|
|
|
250
576
|
.app-mobile-nav-trigger {
|
|
251
577
|
display: inline-flex;
|
|
252
578
|
}
|
|
579
|
+
|
|
580
|
+
/* Some approved application contracts retain their navigation rail at narrow widths. This is
|
|
581
|
+
* an explicit AppShell mode, never a consumer media-query fork: the same sidebar node, footer,
|
|
582
|
+
* active items and token-owned rail width remain in the canonical two-track grid. */
|
|
583
|
+
.app-root[data-responsive-navigation="docked"] {
|
|
584
|
+
grid-template-areas:
|
|
585
|
+
"sidebar topbar"
|
|
586
|
+
"sidebar main"
|
|
587
|
+
"sidebar footer";
|
|
588
|
+
grid-template-columns: var(--app-shell-sidebar-width) minmax(0, 1fr);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.app-root[data-responsive-navigation="docked"][data-collapsed="true"] {
|
|
592
|
+
grid-template-columns: var(--app-shell-rail-width) minmax(0, 1fr);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.app-root[data-responsive-navigation="docked"] > .app-sidebar {
|
|
596
|
+
display: flex;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.app-root[data-responsive-navigation="docked"] > .app-topbar {
|
|
600
|
+
padding-inline: var(--app-shell-bar-inset);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.app-root[data-responsive-navigation="docked"] .app-mobile-nav-trigger {
|
|
604
|
+
display: none;
|
|
605
|
+
}
|
|
253
606
|
}
|
|
254
607
|
|
|
255
608
|
.sb-root {
|
|
256
609
|
display: contents;
|
|
257
610
|
}
|
|
258
611
|
|
|
612
|
+
/*
|
|
613
|
+
* ORG SWITCHER — shared Console/Admin tenant control. Widths, insets and state geometry stay
|
|
614
|
+
* inside the library so a consumer never needs `w-64` or descendant-selector overrides.
|
|
615
|
+
*/
|
|
616
|
+
.ui-org-switcher {
|
|
617
|
+
width: 100%;
|
|
618
|
+
min-width: 0;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.ui-org-switcher[data-collapsed="true"] {
|
|
622
|
+
display: flex;
|
|
623
|
+
justify-content: center;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.ui-org-switcher-trigger {
|
|
627
|
+
width: 100%;
|
|
628
|
+
height: var(--org-switcher-trigger-height);
|
|
629
|
+
min-width: 0;
|
|
630
|
+
justify-content: flex-start;
|
|
631
|
+
padding-inline: var(--org-switcher-trigger-padding-x);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.ui-org-switcher-trigger[data-collapsed="true"] {
|
|
635
|
+
width: var(--org-switcher-trigger-height);
|
|
636
|
+
padding: 0;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.ui-org-switcher-avatar {
|
|
640
|
+
display: grid;
|
|
641
|
+
width: var(--org-switcher-avatar-size);
|
|
642
|
+
height: var(--org-switcher-avatar-size);
|
|
643
|
+
flex: 0 0 auto;
|
|
644
|
+
place-items: center;
|
|
645
|
+
overflow: hidden;
|
|
646
|
+
border-radius: var(--radius);
|
|
647
|
+
background: hsl(var(--secondary));
|
|
648
|
+
color: hsl(var(--secondary-foreground));
|
|
649
|
+
font-size: var(--sidebar-avatar-font-size);
|
|
650
|
+
font-weight: var(--font-weight-semibold);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.ui-org-switcher-avatar > * {
|
|
654
|
+
width: 100%;
|
|
655
|
+
height: 100%;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.ui-org-switcher-current,
|
|
659
|
+
.ui-org-switcher-option {
|
|
660
|
+
display: grid;
|
|
661
|
+
min-width: 0;
|
|
662
|
+
flex: 1;
|
|
663
|
+
text-align: start;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.ui-org-switcher-name,
|
|
667
|
+
.ui-org-switcher-meta {
|
|
668
|
+
overflow: hidden;
|
|
669
|
+
line-height: 1.25;
|
|
670
|
+
text-overflow: ellipsis;
|
|
671
|
+
white-space: nowrap;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.ui-org-switcher-name {
|
|
675
|
+
font-size: var(--sidebar-user-name-font-size);
|
|
676
|
+
font-weight: var(--font-weight-medium);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.ui-org-switcher-meta {
|
|
680
|
+
color: hsl(var(--org-switcher-meta-foreground));
|
|
681
|
+
font-size: var(--sidebar-user-role-font-size);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.ui-org-switcher-chevron,
|
|
685
|
+
.ui-org-switcher-check,
|
|
686
|
+
.ui-org-switcher-spinner {
|
|
687
|
+
width: 1rem;
|
|
688
|
+
height: 1rem;
|
|
689
|
+
flex: 0 0 auto;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.ui-org-switcher-chevron {
|
|
693
|
+
margin-inline-start: auto;
|
|
694
|
+
color: hsl(var(--muted-foreground));
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.ui-org-switcher-check {
|
|
698
|
+
margin-inline-start: auto;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.ui-org-switcher-popover {
|
|
702
|
+
width: min(var(--org-switcher-menu-width), calc(100vw - var(--space-4)));
|
|
703
|
+
padding: 0;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.ui-org-switcher-sheet {
|
|
707
|
+
max-height: var(--org-switcher-sheet-max-height);
|
|
708
|
+
border-start-start-radius: var(--radius-lg);
|
|
709
|
+
border-start-end-radius: var(--radius-lg);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.ui-org-switcher-command {
|
|
713
|
+
width: 100%;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.ui-org-switcher-state {
|
|
717
|
+
display: flex;
|
|
718
|
+
min-height: var(--org-switcher-state-min-height);
|
|
719
|
+
flex-direction: column;
|
|
720
|
+
align-items: center;
|
|
721
|
+
justify-content: center;
|
|
722
|
+
gap: var(--space-3);
|
|
723
|
+
padding: var(--space-5);
|
|
724
|
+
color: hsl(var(--muted-foreground));
|
|
725
|
+
text-align: center;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.ui-org-switcher-spinner {
|
|
729
|
+
animation: spin 1s linear infinite;
|
|
730
|
+
}
|
|
731
|
+
|
|
259
732
|
.sb-product,
|
|
260
733
|
.sb-brand {
|
|
261
734
|
display: flex;
|
|
735
|
+
min-width: 0;
|
|
262
736
|
min-height: var(--app-shell-bar-height);
|
|
737
|
+
flex: 0 0 auto;
|
|
263
738
|
align-items: center;
|
|
264
739
|
gap: var(--space-2);
|
|
265
740
|
padding-inline: var(--space-4);
|
|
@@ -337,8 +812,10 @@
|
|
|
337
812
|
|
|
338
813
|
.sb-nav-scroll {
|
|
339
814
|
flex: 1;
|
|
815
|
+
min-height: 0;
|
|
816
|
+
overscroll-behavior: contain;
|
|
340
817
|
overflow-y: auto;
|
|
341
|
-
padding: var(--
|
|
818
|
+
padding: var(--sidebar-nav-scroll-padding);
|
|
342
819
|
}
|
|
343
820
|
|
|
344
821
|
.sb-section {
|
|
@@ -346,11 +823,11 @@
|
|
|
346
823
|
}
|
|
347
824
|
|
|
348
825
|
.sb-section + .sb-section {
|
|
349
|
-
margin-top: var(--
|
|
826
|
+
margin-top: var(--sidebar-section-gap);
|
|
350
827
|
}
|
|
351
828
|
|
|
352
829
|
.sb-section-label {
|
|
353
|
-
padding: 0 var(--
|
|
830
|
+
padding: 0 var(--sidebar-section-label-padding-x) var(--sidebar-section-label-padding-bottom);
|
|
354
831
|
color: hsl(var(--muted-foreground));
|
|
355
832
|
font-size: var(--sidebar-section-label-font-size);
|
|
356
833
|
font-weight: 700;
|
|
@@ -362,7 +839,7 @@
|
|
|
362
839
|
.sb-nav {
|
|
363
840
|
display: flex;
|
|
364
841
|
flex-direction: column;
|
|
365
|
-
gap:
|
|
842
|
+
gap: var(--sidebar-nav-gap);
|
|
366
843
|
}
|
|
367
844
|
|
|
368
845
|
.sb-nav-item {
|
|
@@ -370,22 +847,27 @@
|
|
|
370
847
|
width: 100%;
|
|
371
848
|
height: var(--sidebar-nav-item-height);
|
|
372
849
|
min-width: 0;
|
|
850
|
+
flex: 0 0 auto;
|
|
373
851
|
align-items: center;
|
|
374
|
-
gap:
|
|
375
|
-
padding-inline:
|
|
852
|
+
gap: var(--sidebar-nav-item-gap);
|
|
853
|
+
padding-inline: var(--sidebar-nav-item-padding-x);
|
|
376
854
|
border: 0;
|
|
377
855
|
border-radius: calc(var(--radius) - 1px);
|
|
378
856
|
background: transparent;
|
|
379
|
-
|
|
857
|
+
/* gh#228 — the row/label foreground is its own knob (default = the muted role, unchanged). The
|
|
858
|
+
* icon reads a SEPARATE knob on `.sb-icon` below, so a service can darken 16px nav icons
|
|
859
|
+
* without re-tinting every muted text. */
|
|
860
|
+
color: var(--sidebar-nav-item-foreground, hsl(var(--muted-foreground)));
|
|
380
861
|
cursor: pointer;
|
|
381
862
|
font-family: inherit;
|
|
382
863
|
font-size: var(--sidebar-nav-item-font-size);
|
|
864
|
+
line-height: 1;
|
|
383
865
|
text-align: start;
|
|
384
866
|
}
|
|
385
867
|
|
|
386
868
|
.sb-nav-item:hover {
|
|
387
869
|
background: hsl(var(--accent));
|
|
388
|
-
color: hsl(var(--foreground));
|
|
870
|
+
color: var(--sidebar-nav-item-hover-foreground, hsl(var(--foreground)));
|
|
389
871
|
}
|
|
390
872
|
|
|
391
873
|
/* Active item reads its own bg/fg tokens (defaults = the hover look) so a service can give the
|
|
@@ -399,21 +881,34 @@
|
|
|
399
881
|
|
|
400
882
|
.sb-icon {
|
|
401
883
|
display: grid;
|
|
402
|
-
width:
|
|
403
|
-
height:
|
|
884
|
+
width: var(--sidebar-nav-icon-size);
|
|
885
|
+
height: var(--sidebar-nav-icon-size);
|
|
404
886
|
flex: 0 0 auto;
|
|
887
|
+
line-height: 0;
|
|
405
888
|
place-items: center;
|
|
889
|
+
/* gh#228 — the nav ICON owns its own foreground knob; the default `currentColor` resolves to the
|
|
890
|
+
* row colour, so the resting look is byte-identical to before. A service sets
|
|
891
|
+
* `--sidebar-nav-icon-foreground` alone to get canonical darker icons beside muted labels — no
|
|
892
|
+
* page-local selector, no global `--muted-foreground` re-tint. Colour only: size/geometry stay
|
|
893
|
+
* on --sidebar-nav-icon-size. */
|
|
894
|
+
color: var(--sidebar-nav-icon-foreground, currentColor);
|
|
406
895
|
}
|
|
407
896
|
|
|
408
897
|
.sb-icon svg {
|
|
409
|
-
|
|
410
|
-
|
|
898
|
+
display: block;
|
|
899
|
+
width: var(--sidebar-nav-icon-size);
|
|
900
|
+
height: var(--sidebar-nav-icon-size);
|
|
901
|
+
flex: 0 0 auto;
|
|
411
902
|
}
|
|
412
903
|
|
|
413
904
|
.sb-label {
|
|
414
905
|
min-width: 0;
|
|
415
906
|
flex: 1;
|
|
416
907
|
overflow: hidden;
|
|
908
|
+
/* gh#254 — MUST NOT inherit the row's `line-height: 1`. This element clips, so its line box is
|
|
909
|
+
* its clip box, and a 1em box is shorter than the font's ascent+descent: every descender and
|
|
910
|
+
* Vietnamese tone mark below the baseline gets sheared off. Knob, not constant (rule #45). */
|
|
911
|
+
line-height: var(--sidebar-nav-item-line-height);
|
|
417
912
|
text-overflow: ellipsis;
|
|
418
913
|
white-space: nowrap;
|
|
419
914
|
}
|
|
@@ -464,7 +959,9 @@
|
|
|
464
959
|
.sb-nav-item--sub {
|
|
465
960
|
height: 1.75rem;
|
|
466
961
|
font-size: var(--sidebar-nav-sub-font-size);
|
|
467
|
-
|
|
962
|
+
/* Same row knob as a top-level row (gh#228) — one override themes the whole nav, sub rows
|
|
963
|
+
* included. Default unchanged. */
|
|
964
|
+
color: var(--sidebar-nav-item-foreground, hsl(var(--muted-foreground)));
|
|
468
965
|
}
|
|
469
966
|
|
|
470
967
|
.sb-nav-item--sub::before {
|
|
@@ -479,7 +976,7 @@
|
|
|
479
976
|
|
|
480
977
|
.sb-nav-item--sub:hover {
|
|
481
978
|
background: transparent;
|
|
482
|
-
color: hsl(var(--foreground));
|
|
979
|
+
color: var(--sidebar-nav-item-hover-foreground, hsl(var(--foreground)));
|
|
483
980
|
}
|
|
484
981
|
|
|
485
982
|
.sb-nav-item--sub[data-active="true"] {
|
|
@@ -493,6 +990,42 @@
|
|
|
493
990
|
opacity: 1;
|
|
494
991
|
}
|
|
495
992
|
|
|
993
|
+
/* gh#228 — nav ICON state knobs. Declared AFTER the row-state rules so they win at equal
|
|
994
|
+
* specificity, in the same order the row states resolve (hover → active → disabled). Each falls
|
|
995
|
+
* back to the base icon knob and finally to `currentColor` (= the row colour for that state), so
|
|
996
|
+
* the defaults are byte-identical to the pre-gh#228 rendering and setting only
|
|
997
|
+
* `--sidebar-nav-icon-foreground` themes every state at once. Colour only — no geometry. */
|
|
998
|
+
.sb-nav-item:hover .sb-icon {
|
|
999
|
+
color: var(
|
|
1000
|
+
--sidebar-nav-icon-hover-foreground,
|
|
1001
|
+
var(--sidebar-nav-icon-foreground, currentColor)
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.sb-nav-item[data-active="true"] .sb-icon {
|
|
1006
|
+
color: var(
|
|
1007
|
+
--sidebar-nav-icon-active-foreground,
|
|
1008
|
+
var(--sidebar-nav-icon-foreground, currentColor)
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/* Disabled rows (`aria-disabled="true"`, set by SidebarItem for item.disabled). Defaults resolve
|
|
1013
|
+
* to the RESTING row/icon colour, so a disabled row reads as inert in every state; a service dims
|
|
1014
|
+
* it further with the two disabled knobs. */
|
|
1015
|
+
.sb-nav-item[aria-disabled="true"] {
|
|
1016
|
+
color: var(
|
|
1017
|
+
--sidebar-nav-item-disabled-foreground,
|
|
1018
|
+
var(--sidebar-nav-item-foreground, hsl(var(--muted-foreground)))
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.sb-nav-item[aria-disabled="true"] .sb-icon {
|
|
1023
|
+
color: var(
|
|
1024
|
+
--sidebar-nav-icon-disabled-foreground,
|
|
1025
|
+
var(--sidebar-nav-icon-foreground, currentColor)
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
496
1029
|
/* Collapsed-rail flyout — PORTALED (Radix Popover) so it escapes the sidebar's overflow and is
|
|
497
1030
|
* never clipped: names a leaf item (tooltip) or reveals a group's submenu on hover/focus. */
|
|
498
1031
|
.sb-flyout-pop {
|
|
@@ -640,12 +1173,40 @@
|
|
|
640
1173
|
}
|
|
641
1174
|
|
|
642
1175
|
/* Topbar — a pure slot bar. start (inline-start) · center (grows, centered) · end (inline-end).
|
|
643
|
-
* The library owns ONLY this positioning; every control inside is the consumer's to compose.
|
|
1176
|
+
* The library owns ONLY this positioning; every control inside is the consumer's to compose.
|
|
1177
|
+
*
|
|
1178
|
+
* SHRINK CONTRACT (gh#226) — a hosted console packs an intrinsically wide brand/tenant string
|
|
1179
|
+
* into `start` and a fixed-width search trigger into `center`; at a compact desktop width
|
|
1180
|
+
* (1024px minus a 16rem sidebar) that content used to push `end` (locale picker, user menu) past
|
|
1181
|
+
* the bar and out of the viewport, and leak a horizontal document scroll. The contract is:
|
|
1182
|
+
* root — never wider than its AppShell grid/flex allocation (`max-width:100%` +
|
|
1183
|
+
* `min-width:0`, so it shrinks whether the parent is a grid track or a flex line);
|
|
1184
|
+
* start — shrinks first, its content clipped INSIDE the slot (never spills over center/end);
|
|
1185
|
+
* center — `flex-basis:0`, so it yields all of its box before `start` gives up a pixel;
|
|
1186
|
+
* end — `flex:0 0 auto`, so it keeps its natural width and stays anchored inline-end.
|
|
1187
|
+
* Slots clip with `clip`, NOT `hidden`: a `hidden` box is still a scroll container, so focusing a
|
|
1188
|
+
* clipped control would scroll the slot and shove its leading content out of view. The clip
|
|
1189
|
+
* margin keeps the focus ring of an edge control paintable (WCAG 2.4.11 / 2.4.13).
|
|
1190
|
+
*
|
|
1191
|
+
* BOX KNOBS (rule #45) — --topbar-height / --topbar-inset / --topbar-gap. Their defaults are the
|
|
1192
|
+
* QUIET ones (`auto` / `0` / the previous 8px), i.e. byte-identical rendering to before they
|
|
1193
|
+
* existed: inside AppShell the `.app-topbar` grid row still owns the height and inset. A service
|
|
1194
|
+
* that mounts <Topbar> DIRECTLY on a page (no AppShell) now sizes it from the theme —
|
|
1195
|
+
* `--topbar-height: 3.5rem; --topbar-inset: var(--space-4)` — instead of an app-local class.
|
|
1196
|
+
* `--topbar-gap` re-rhythms the whole bar at once: it is both the gap BETWEEN the three clusters
|
|
1197
|
+
* and the gap INSIDE each of them, so a denser bar stays internally consistent.
|
|
1198
|
+
* The shrink contract above is unaffected — none of these touch flex/overflow. */
|
|
644
1199
|
.ui-topbar {
|
|
645
1200
|
display: flex;
|
|
646
|
-
|
|
1201
|
+
height: var(--topbar-height);
|
|
1202
|
+
width: auto;
|
|
1203
|
+
max-width: 100%;
|
|
1204
|
+
min-width: 0;
|
|
1205
|
+
flex: 1 1 0%;
|
|
647
1206
|
align-items: center;
|
|
648
|
-
gap: var(--
|
|
1207
|
+
gap: var(--topbar-gap);
|
|
1208
|
+
padding-inline: var(--topbar-inset);
|
|
1209
|
+
overflow: hidden;
|
|
649
1210
|
}
|
|
650
1211
|
.ui-topbar-start,
|
|
651
1212
|
.ui-topbar-center,
|
|
@@ -653,16 +1214,44 @@
|
|
|
653
1214
|
display: flex;
|
|
654
1215
|
min-width: 0;
|
|
655
1216
|
align-items: center;
|
|
656
|
-
gap: var(--
|
|
1217
|
+
gap: var(--topbar-gap);
|
|
1218
|
+
overflow: clip;
|
|
1219
|
+
overflow-clip-margin: var(--focus-ring-width);
|
|
1220
|
+
}
|
|
1221
|
+
.ui-topbar-start {
|
|
1222
|
+
flex: 0 1 auto;
|
|
1223
|
+
}
|
|
1224
|
+
.ui-topbar-start > :last-child {
|
|
1225
|
+
max-width: 100%;
|
|
1226
|
+
min-width: 0;
|
|
1227
|
+
flex: 0 1 auto;
|
|
1228
|
+
overflow: hidden;
|
|
1229
|
+
/* Declares its own line box because it CLIPS — see gh#254. */
|
|
1230
|
+
line-height: var(--topbar-start-truncate-line-height);
|
|
1231
|
+
text-overflow: ellipsis;
|
|
1232
|
+
white-space: nowrap;
|
|
657
1233
|
}
|
|
658
1234
|
.ui-topbar-center {
|
|
659
|
-
flex: 1 1
|
|
1235
|
+
flex: 1 1 0%;
|
|
660
1236
|
justify-content: center;
|
|
661
1237
|
}
|
|
662
1238
|
.ui-topbar-end {
|
|
1239
|
+
max-width: 100%;
|
|
1240
|
+
flex: 0 0 auto;
|
|
663
1241
|
margin-inline-start: auto;
|
|
664
1242
|
}
|
|
665
1243
|
|
|
1244
|
+
/* COLLISION CONTRACT (gh#244) — with a docked 16rem sidebar, 901–1100px viewports do not have
|
|
1245
|
+
* enough room for a breadcrumb/title, full search trigger and end utilities. The center slot is
|
|
1246
|
+
* optional by definition, so remove it before it can cover either owned edge. This also gives
|
|
1247
|
+
* phones the same deterministic allocation. A consumer with a real compact center presentation
|
|
1248
|
+
* can opt it back in through the public token instead of a page-local media query. */
|
|
1249
|
+
@media (width <= 68.75rem) {
|
|
1250
|
+
.ui-topbar-center {
|
|
1251
|
+
display: var(--topbar-center-compact-display);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
666
1255
|
.tb-chip {
|
|
667
1256
|
display: inline-flex;
|
|
668
1257
|
max-width: 15rem;
|
|
@@ -716,6 +1305,10 @@
|
|
|
716
1305
|
min-width: 0;
|
|
717
1306
|
overflow: hidden;
|
|
718
1307
|
font-weight: 500;
|
|
1308
|
+
/* gh#254 — same trap as `.sb-label`: this clips, so it must own a line box tall enough for the
|
|
1309
|
+
* font's descenders. `.tb-chip` uses `font: inherit`, so without this the chip would shear its
|
|
1310
|
+
* glyphs the moment a service set a tight line-height on the bar. */
|
|
1311
|
+
line-height: 1.5;
|
|
719
1312
|
text-overflow: ellipsis;
|
|
720
1313
|
white-space: nowrap;
|
|
721
1314
|
}
|
|
@@ -807,27 +1400,18 @@
|
|
|
807
1400
|
background: hsl(var(--attention));
|
|
808
1401
|
}
|
|
809
1402
|
|
|
1403
|
+
/* TOPBAR CHIP/SEARCH DENSITY ONLY — this is NOT a second shell breakpoint. The `.app-root`
|
|
1404
|
+
* restructure that used to live here (a 768px duplicate of the 900px rule that also deleted the
|
|
1405
|
+
* footer row and re-declared the grid rows with a 3rem literal) is gone; see the note on the one
|
|
1406
|
+
* `@media (width <= 56.25rem)` block above. What remains is purely the optional `.tb-*` console
|
|
1407
|
+
* chrome collapsing to icon-only on a phone, which is independent of where the shell switches to
|
|
1408
|
+
* its drawer. */
|
|
810
1409
|
@media (max-width: 768px) {
|
|
811
|
-
.app-root,
|
|
812
|
-
.app-root[data-collapsed="true"] {
|
|
813
|
-
grid-template-areas:
|
|
814
|
-
"topbar"
|
|
815
|
-
"main";
|
|
816
|
-
grid-template-columns: minmax(0, 1fr);
|
|
817
|
-
grid-template-rows: 3rem minmax(0, 1fr);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
.app-sidebar,
|
|
821
|
-
.app-footer,
|
|
822
1410
|
.tb-chip-sep,
|
|
823
1411
|
.tb-search .kbd {
|
|
824
1412
|
display: none;
|
|
825
1413
|
}
|
|
826
1414
|
|
|
827
|
-
.app-topbar {
|
|
828
|
-
padding-inline: var(--space-3);
|
|
829
|
-
}
|
|
830
|
-
|
|
831
1415
|
.tb-search {
|
|
832
1416
|
width: auto;
|
|
833
1417
|
min-width: var(--control-height);
|
|
@@ -838,3 +1422,169 @@
|
|
|
838
1422
|
}
|
|
839
1423
|
}
|
|
840
1424
|
}
|
|
1425
|
+
|
|
1426
|
+
/*
|
|
1427
|
+
* REDUCED MOTION for the AppShell mobile drawer (WCAG 2.3.3 / 2.2.2). The drawer is a Sheet, so it
|
|
1428
|
+
* animates via Tailwind `data-[state]:animate-in|animate-out` + `slide-in-from-*` utilities.
|
|
1429
|
+
* DELIBERATELY UNLAYERED: Tailwind v4 emits those into `@layer utilities`, which beats any rule in
|
|
1430
|
+
* `@layer components` regardless of specificity — this gate was previously written inside the
|
|
1431
|
+
* components layer above and could never win. Same reasoning as the overlay gate in
|
|
1432
|
+
* dialog-layout.css (which also covers this drawer via [data-slot="sheet-*"]); kept here as well so
|
|
1433
|
+
* a consumer importing only `styles/shell-layout` still gets it.
|
|
1434
|
+
*/
|
|
1435
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1436
|
+
.app-mobile-nav-overlay,
|
|
1437
|
+
.app-mobile-nav-drawer {
|
|
1438
|
+
animation: none;
|
|
1439
|
+
transition: none;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/*
|
|
1444
|
+
* ERROR SURFACE — the 403 / 404 / 500 / 503 exception surface (gh#221, gh#251).
|
|
1445
|
+
*
|
|
1446
|
+
* Geometry is PACKAGE-OWNED at 1440 / 1024 / 390: a consumer never writes `min-h-dvh`, a
|
|
1447
|
+
* flex-centring class or a media query for an error page. In `system` mode the viewport centring
|
|
1448
|
+
* comes from `CenteredShell align="center"` (which the component renders itself); this block owns
|
|
1449
|
+
* the surface block that is identical in BOTH modes — measure, rhythm, metadata list.
|
|
1450
|
+
*
|
|
1451
|
+
* The narrow step is a CONTAINER query, not a viewport one (the SplitPane precedent, gh#165): the
|
|
1452
|
+
* surface reads its OWN width, so it compacts correctly whether the shrinking came from a 390px
|
|
1453
|
+
* phone or from an application shell whose sidebar is expanded on a laptop.
|
|
1454
|
+
*/
|
|
1455
|
+
@layer components {
|
|
1456
|
+
.ui-error-surface {
|
|
1457
|
+
container: error-surface / inline-size;
|
|
1458
|
+
display: flex;
|
|
1459
|
+
width: 100%;
|
|
1460
|
+
max-width: var(--error-surface-max-width);
|
|
1461
|
+
flex-direction: column;
|
|
1462
|
+
align-items: center;
|
|
1463
|
+
/* Logical, so the surface stays centred and its text start-aligns correctly under `dir="rtl"`. */
|
|
1464
|
+
margin-inline: auto;
|
|
1465
|
+
gap: var(--error-surface-gap);
|
|
1466
|
+
padding-block: var(--error-surface-padding-block);
|
|
1467
|
+
text-align: center;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
.ui-error-surface-brand {
|
|
1471
|
+
display: flex;
|
|
1472
|
+
justify-content: center;
|
|
1473
|
+
padding-block-end: var(--error-surface-brand-gap);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/* The EmptyState body already owns its own vertical rhythm and description measure
|
|
1477
|
+
* (--empty-state-*); the surface only removes the duplicated block padding so the two spacing
|
|
1478
|
+
* systems do not stack into a hole between the status code and the icon. */
|
|
1479
|
+
.ui-error-surface .ui-error-surface-body {
|
|
1480
|
+
width: 100%;
|
|
1481
|
+
padding-block: 0;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
.ui-error-surface-status {
|
|
1485
|
+
margin: 0;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
.ui-error-surface-meta {
|
|
1489
|
+
display: flex;
|
|
1490
|
+
width: 100%;
|
|
1491
|
+
flex-direction: column;
|
|
1492
|
+
gap: var(--error-surface-meta-gap);
|
|
1493
|
+
/* Chrome is a token, default quiet (rule #44) — `none` unless a service opts in. */
|
|
1494
|
+
border-block-start: var(--error-surface-meta-border);
|
|
1495
|
+
padding-block-start: var(--error-surface-meta-padding-block);
|
|
1496
|
+
margin: 0;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
.ui-error-surface-meta-row {
|
|
1500
|
+
display: flex;
|
|
1501
|
+
flex-wrap: wrap;
|
|
1502
|
+
justify-content: center;
|
|
1503
|
+
gap: var(--error-surface-meta-row-gap);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
.ui-error-surface-meta-row > dd {
|
|
1507
|
+
margin-inline-start: 0;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
.ui-error-surface-progress {
|
|
1511
|
+
width: 100%;
|
|
1512
|
+
max-width: var(--error-surface-progress-max-width);
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/*
|
|
1516
|
+
* NARROW STEP (the 390 case). Reclaim vertical space and stop a long ja / vi metadata row from
|
|
1517
|
+
* wrapping into a ragged two-column shape: each label stacks over its value, centred.
|
|
1518
|
+
*/
|
|
1519
|
+
@container error-surface (width < 30rem) {
|
|
1520
|
+
.ui-error-surface {
|
|
1521
|
+
padding-block: var(--error-surface-padding-block-compact);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
.ui-error-surface-meta-row {
|
|
1525
|
+
flex-direction: column;
|
|
1526
|
+
gap: 0;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
@layer components {
|
|
1532
|
+
/* ── CenteredShell · `preset="public-landing"` (gh#252) ──────────────────────────────────────
|
|
1533
|
+
* A PUBLIC landing page (marketing header · hero · sections · legal footer) is a COMPOSITION,
|
|
1534
|
+
* not a framework component (docs/COMPOSITION-VS-COMPONENT.md: Hero / Navbar / Footer / Pricing
|
|
1535
|
+
* all FAIL the Framework-Component Test). What the consumer must NOT own is the SHELL geometry —
|
|
1536
|
+
* the content measure, the header/footer measure alignment, the section rhythm and the flat
|
|
1537
|
+
* public-surface chrome. This preset owns all of it through `--centered-shell-landing-*` knobs,
|
|
1538
|
+
* so a landing composes real primitives with ZERO page-local CSS and zero descendant selectors.
|
|
1539
|
+
*
|
|
1540
|
+
* `preset="default"` emits NO `data-preset` attribute at all, so none of this can match and every
|
|
1541
|
+
* existing CenteredShell keeps its exact box (the gh#231 inert-default contract). */
|
|
1542
|
+
.ui-centered-shell[data-preset="public-landing"] {
|
|
1543
|
+
--centered-shell-main-padding: var(--centered-shell-landing-main-padding-block)
|
|
1544
|
+
var(--centered-shell-landing-inset-inline);
|
|
1545
|
+
/* The hero title is a real `Heading level={1}`; the preset re-points the h1 tier the same way
|
|
1546
|
+
* `.ui-auth-shell` re-points it — never a consumer font-size. */
|
|
1547
|
+
--heading-h1: var(--centered-shell-landing-heading-size);
|
|
1548
|
+
/* Flat public surface: every Card in the subtree loses its elevation from ONE knob instead of
|
|
1549
|
+
* a consumer `.landing .card { box-shadow: none }` descendant selector. */
|
|
1550
|
+
--card-shadow: var(--centered-shell-landing-card-shadow);
|
|
1551
|
+
background: hsl(var(--centered-shell-landing-background, var(--background)));
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
.ui-centered-shell[data-preset="public-landing"] .ui-centered-shell-column {
|
|
1555
|
+
display: flex;
|
|
1556
|
+
max-width: var(--centered-shell-landing-max-width);
|
|
1557
|
+
flex-direction: column;
|
|
1558
|
+
/* Section rhythm — the consumer stacks plain `<section>` layout elements and never sets a gap. */
|
|
1559
|
+
gap: var(--centered-shell-landing-section-gap);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
/* Header and footer share the column's measure WITHOUT a consumer max-width wrapper: the inline
|
|
1563
|
+
* padding is the page gutter until the viewport is wider than the measure, then it grows so the
|
|
1564
|
+
* bar content starts on exactly the same inline edge as the centred column. */
|
|
1565
|
+
.ui-centered-shell[data-preset="public-landing"] .ui-centered-shell-bar,
|
|
1566
|
+
.ui-centered-shell[data-preset="public-landing"] .ui-centered-shell-footer {
|
|
1567
|
+
padding-inline: max(
|
|
1568
|
+
var(--centered-shell-landing-inset-inline),
|
|
1569
|
+
calc((100% - var(--centered-shell-landing-max-width)) / 2)
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
.ui-centered-shell[data-preset="public-landing"] .ui-centered-shell-footer {
|
|
1574
|
+
padding-block: var(--centered-shell-landing-footer-padding-block);
|
|
1575
|
+
text-align: start;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
/* Compact step (the 390px acceptance width sits here): the gutter, the section rhythm and the
|
|
1579
|
+
* hero tier switch to their compact knobs. One media query, owned by the package. */
|
|
1580
|
+
@media (width < 40rem) {
|
|
1581
|
+
.ui-centered-shell[data-preset="public-landing"] {
|
|
1582
|
+
--centered-shell-landing-inset-inline: var(--centered-shell-landing-inset-inline-compact);
|
|
1583
|
+
--centered-shell-landing-main-padding-block: var(
|
|
1584
|
+
--centered-shell-landing-main-padding-block-compact
|
|
1585
|
+
);
|
|
1586
|
+
--centered-shell-landing-section-gap: var(--centered-shell-landing-section-gap-compact);
|
|
1587
|
+
--centered-shell-landing-heading-size: var(--centered-shell-landing-heading-size-compact);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|