@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
package/dist/styles/layout.css
CHANGED
|
@@ -195,6 +195,102 @@
|
|
|
195
195
|
min-width: 0;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/* MasterDetail owns its tracks AND its collapse threshold — both token-driven (gh#223).
|
|
199
|
+
*
|
|
200
|
+
* WHY FLEX AND NOT `@container (min-width: …)`: a media/container-query CONDITION cannot read a
|
|
201
|
+
* `var()` — it is resolved before custom properties, so a tokenised breakpoint is impossible
|
|
202
|
+
* there (the reason the first cut hard-coded `40rem`, violating rule #45). The flex threshold
|
|
203
|
+
* below ("Holy Albatross") puts the breakpoint in a `calc()`, where a `var()` DOES resolve, so
|
|
204
|
+
* `--master-detail-collapse-below` is a genuine knob a service theme can retune once globally.
|
|
205
|
+
*
|
|
206
|
+
* How it reads: the fluid region's flex-basis is `(threshold - 100%) * 999`, where 100% is the
|
|
207
|
+
* composition's own inline size (so, like the old container query, the decision comes from the
|
|
208
|
+
* space available to the layout — never the viewport).
|
|
209
|
+
* · width < threshold → a huge positive basis → each region takes a whole flex line and
|
|
210
|
+
* shrinks to it: master stacked above detail, both full width.
|
|
211
|
+
* · width ≥ threshold → the calc goes negative and is clamped to 0 → one line: the fluid
|
|
212
|
+
* region absorbs the free space while the rail keeps its token width (`max()` keeps the
|
|
213
|
+
* rail's basis at the preset because a negative operand can never win a max).
|
|
214
|
+
*
|
|
215
|
+
* `data-rail` picks WHICH region is the fixed rail. Default `detail` = the canonical
|
|
216
|
+
* "fluid list + fixed detail rail" (1fr / 320px) composition; `master` puts the fixed rail on
|
|
217
|
+
* the leading edge for a category/navigator list. DOM order is always master → detail, so the
|
|
218
|
+
* stacked order is list-then-detail either way, and the flex row follows the writing direction
|
|
219
|
+
* (RTL-safe without a single physical property). */
|
|
220
|
+
.ui-master-detail {
|
|
221
|
+
display: flex;
|
|
222
|
+
flex-wrap: wrap;
|
|
223
|
+
gap: var(--master-detail-gap);
|
|
224
|
+
--master-detail-rail-size: var(--master-detail-rail-standard);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.ui-master-detail[data-rail-width="compact"] {
|
|
228
|
+
--master-detail-rail-size: var(--master-detail-rail-compact);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* Per-instance override of the token threshold (theme sets it globally, prop overrides here). */
|
|
232
|
+
.ui-master-detail[data-collapse-below="false"] {
|
|
233
|
+
--master-detail-collapse-below: 0px;
|
|
234
|
+
}
|
|
235
|
+
.ui-master-detail[data-collapse-below="sm"] {
|
|
236
|
+
--master-detail-collapse-below: 40rem;
|
|
237
|
+
}
|
|
238
|
+
.ui-master-detail[data-collapse-below="md"] {
|
|
239
|
+
--master-detail-collapse-below: 48rem;
|
|
240
|
+
}
|
|
241
|
+
.ui-master-detail[data-collapse-below="lg"] {
|
|
242
|
+
--master-detail-collapse-below: 64rem;
|
|
243
|
+
}
|
|
244
|
+
.ui-master-detail[data-collapse-below="xl"] {
|
|
245
|
+
--master-detail-collapse-below: 80rem;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.ui-master-detail-master,
|
|
249
|
+
.ui-master-detail-detail {
|
|
250
|
+
min-inline-size: 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* The fluid region. */
|
|
254
|
+
.ui-master-detail[data-rail="detail"] > .ui-master-detail-master,
|
|
255
|
+
.ui-master-detail[data-rail="master"] > .ui-master-detail-detail {
|
|
256
|
+
flex: 1 1 calc((var(--master-detail-collapse-below) - 100%) * 999);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* The fixed rail. */
|
|
260
|
+
.ui-master-detail[data-rail="detail"] > .ui-master-detail-detail,
|
|
261
|
+
.ui-master-detail[data-rail="master"] > .ui-master-detail-master {
|
|
262
|
+
flex: 0 1
|
|
263
|
+
max(var(--master-detail-rail-size), calc((var(--master-detail-collapse-below) - 100%) * 999));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* Bounded master viewport (gh#231) — OPT-IN via `masterViewport`. `auto` (the default) emits no
|
|
267
|
+
* bound at all, so an existing composition renders byte-identically; the two presets cap the
|
|
268
|
+
* collection's BLOCK size with a token and scroll it INSIDE the region, which is what keeps a
|
|
269
|
+
* long real collection from pushing the detail thousands of pixels below the fold once the
|
|
270
|
+
* layout stacks. Block axis only — the inline axis stays owned by the flex threshold above, so
|
|
271
|
+
* the bound is writing-direction agnostic (RTL-safe).
|
|
272
|
+
*
|
|
273
|
+
* A11y: the region carries tabIndex={0} in this mode (see master-detail.tsx) so the scroll
|
|
274
|
+
* container is reachable and scrollable by keyboard alone, and the inset below reserves room
|
|
275
|
+
* for a focused row's focus ring — an overflow container clips BOTH axes, so a ring drawn at
|
|
276
|
+
* the region's edge would otherwise be cut. Focus is never trapped: the region is a plain tab
|
|
277
|
+
* stop that Tab/Shift+Tab leave normally. */
|
|
278
|
+
.ui-master-detail[data-master-viewport="compact"] > .ui-master-detail-master,
|
|
279
|
+
.ui-master-detail[data-master-viewport="standard"] > .ui-master-detail-master {
|
|
280
|
+
overflow-y: auto;
|
|
281
|
+
overscroll-behavior: contain;
|
|
282
|
+
padding: var(--master-detail-master-viewport-inset);
|
|
283
|
+
scroll-padding-block: var(--master-detail-master-viewport-inset);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.ui-master-detail[data-master-viewport="compact"] > .ui-master-detail-master {
|
|
287
|
+
max-block-size: var(--master-detail-master-viewport-compact);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.ui-master-detail[data-master-viewport="standard"] > .ui-master-detail-master {
|
|
291
|
+
max-block-size: var(--master-detail-master-viewport-standard);
|
|
292
|
+
}
|
|
293
|
+
|
|
198
294
|
@container responsive-grid (min-width: 40rem) {
|
|
199
295
|
.ui-responsive-grid {
|
|
200
296
|
grid-template-columns: repeat(var(--responsive-grid-sm, 2), minmax(0, 1fr));
|
|
@@ -315,6 +411,29 @@
|
|
|
315
411
|
max-width: 42rem;
|
|
316
412
|
}
|
|
317
413
|
|
|
414
|
+
/* `measure` (gh#245 / gh#247) — a bounded page measure axis ORTHOGONAL to `variant`, so the
|
|
415
|
+
* quiet `ghost` chrome and a narrow/medium measure finally compose (they used to be the same
|
|
416
|
+
* variant axis, which is exactly why a notification feed could not have both). Unlike
|
|
417
|
+
* `variant="narrow"` — which caps only the body and leaves the header `extra` action stranded
|
|
418
|
+
* at the page edge — this caps the HEADER and the BODY to ONE shared measure, so the action
|
|
419
|
+
* ends on the same edge as the body surface. Both elements carry the identical page gutters,
|
|
420
|
+
* so their content edges line up exactly at both ends.
|
|
421
|
+
*
|
|
422
|
+
* `measure="default"` is the inert default: it emits `data-measure="default"`, which matches NO
|
|
423
|
+
* rule here, so every existing page renders byte-identically (the gh#231 `data-layout="stack"`
|
|
424
|
+
* precedent). The cap is a max, never a width — below it the page stays fluid, so 390px keeps
|
|
425
|
+
* the full compact-gutter surface. The footer is deliberately NOT capped: its border/background
|
|
426
|
+
* is page-level chrome that must span the page when `stickyFooter` pins it. */
|
|
427
|
+
.ui-page-container[data-measure="narrow"] .ui-page-header,
|
|
428
|
+
.ui-page-container[data-measure="narrow"] .ui-page-body {
|
|
429
|
+
max-inline-size: var(--page-measure-narrow);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.ui-page-container[data-measure="medium"] .ui-page-header,
|
|
433
|
+
.ui-page-container[data-measure="medium"] .ui-page-body {
|
|
434
|
+
max-inline-size: var(--page-measure-medium);
|
|
435
|
+
}
|
|
436
|
+
|
|
318
437
|
/* flush = full-bleed body (DataTable); header/footer keep their inset. */
|
|
319
438
|
.ui-page-container--flush .ui-page-body {
|
|
320
439
|
padding-inline-start: 0;
|
|
@@ -435,6 +554,34 @@
|
|
|
435
554
|
}
|
|
436
555
|
}
|
|
437
556
|
|
|
557
|
+
/* `headerLayout="responsive-inline"` (gh#231): below the 640px step the DEFAULT arrangement
|
|
558
|
+
* drops `extra` onto its own full-width line under the subtitle, which pushes a compact header
|
|
559
|
+
* control (a member search, one primary action) off the title row on a 390px screen. This
|
|
560
|
+
* arrangement keeps it beside the title band at a token-owned measure instead, and lets the
|
|
561
|
+
* title/subtitle wrap into the space that is left. Scoped to the compact range only, so at
|
|
562
|
+
* >=640px both arrangements resolve to the identical row below — and a page that never sets
|
|
563
|
+
* `headerLayout` never matches these rules at all. */
|
|
564
|
+
@media (max-width: 639.98px) {
|
|
565
|
+
.ui-page-header[data-layout="responsive-inline"] .ui-page-header-row {
|
|
566
|
+
flex-direction: row;
|
|
567
|
+
align-items: flex-start;
|
|
568
|
+
justify-content: space-between;
|
|
569
|
+
gap: var(--space-inline-md);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.ui-page-header[data-layout="responsive-inline"] .ui-page-header-heading {
|
|
573
|
+
flex: 1 1 auto;
|
|
574
|
+
min-inline-size: 0;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.ui-page-header[data-layout="responsive-inline"] .ui-page-header-extra {
|
|
578
|
+
flex: 0 0 var(--page-header-extra-measure);
|
|
579
|
+
inline-size: var(--page-header-extra-measure);
|
|
580
|
+
max-inline-size: 100%;
|
|
581
|
+
justify-content: flex-end;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
438
585
|
@media (min-width: 640px) {
|
|
439
586
|
.ui-page-header-row {
|
|
440
587
|
flex-direction: row;
|
|
@@ -550,7 +697,8 @@
|
|
|
550
697
|
.ui-empty-state-description {
|
|
551
698
|
font-size: var(--font-size-base);
|
|
552
699
|
color: hsl(var(--muted-foreground));
|
|
553
|
-
|
|
700
|
+
/* Measure knob (rule #45) — default 28rem, retunable per service/locale. */
|
|
701
|
+
max-width: var(--empty-state-description-max-width);
|
|
554
702
|
}
|
|
555
703
|
|
|
556
704
|
.ui-empty-state-icon {
|
|
@@ -586,6 +734,20 @@
|
|
|
586
734
|
}
|
|
587
735
|
}
|
|
588
736
|
|
|
737
|
+
@layer components {
|
|
738
|
+
.ui-page-container[data-preset="admin-collection"] {
|
|
739
|
+
--control-height: var(--admin-collection-control-height);
|
|
740
|
+
--space-section-active: var(--admin-collection-section-gap);
|
|
741
|
+
--filter-bar-padding-y: var(--admin-collection-toolbar-padding-y);
|
|
742
|
+
--table-row-height: var(--admin-collection-table-row-height);
|
|
743
|
+
--table-cell-padding-y: var(--admin-collection-table-cell-padding-y);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.ui-page-container[data-preset="admin-collection"] .ui-toolbar .ui-search-input {
|
|
747
|
+
inline-size: min(100%, var(--admin-collection-search-measure));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
589
751
|
@layer components {
|
|
590
752
|
/* Pagination is ONE horizontal row and never wraps to a second desktop line (gh#153 — the old
|
|
591
753
|
* `flex-wrap: wrap` split the range / size-changer / page strip across rows). The number strip
|
|
@@ -755,4 +917,307 @@
|
|
|
755
917
|
color: hsl(var(--muted-foreground));
|
|
756
918
|
font-size: var(--filter-label-font-size);
|
|
757
919
|
}
|
|
920
|
+
|
|
921
|
+
/* Toolbar/FilterBar `overflow="scroll"` (#216) — one bounded row that scrolls INLINE instead of
|
|
922
|
+
* wrapping onto extra rows. Below 640px the strip still stacks (the `wrap` base rules above own
|
|
923
|
+
* that): a 390px viewport must never hide a filter behind a horizontal scroll it cannot see.
|
|
924
|
+
* `overflow-x` matches the rest of the package's scroll regions (.ui-data-table-scroll, tabs
|
|
925
|
+
* list) and the browser already mirrors its scroll origin under `dir="rtl"`; the INSET/margin
|
|
926
|
+
* properties below stay logical. No `tabindex` is needed — every group holds a focusable
|
|
927
|
+
* control, so the region is keyboard-reachable (axe scrollable-region-focusable / WCAG 2.1.1). */
|
|
928
|
+
@media (min-width: 640px) {
|
|
929
|
+
.ui-toolbar[data-overflow="scroll"] {
|
|
930
|
+
flex-wrap: nowrap;
|
|
931
|
+
overflow-x: auto;
|
|
932
|
+
overscroll-behavior-x: contain;
|
|
933
|
+
scrollbar-gutter: stable;
|
|
934
|
+
padding-block-end: var(--filter-bar-scroll-padding-y);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.ui-toolbar[data-overflow="scroll"] > .ui-toolbar-group {
|
|
938
|
+
flex: 0 0 auto;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/* The clear action stays parked at the inline end of the VIEWPORT of the scroll region, not at
|
|
942
|
+
* the end of its scroll width — `margin-inline-start: auto` would push it past the fold and
|
|
943
|
+
* make "clear all" unreachable without scrolling. */
|
|
944
|
+
.ui-toolbar[data-overflow="scroll"] > .ui-toolbar-clear {
|
|
945
|
+
position: sticky;
|
|
946
|
+
inset-inline-end: 0;
|
|
947
|
+
margin-inline-start: auto;
|
|
948
|
+
flex: 0 0 auto;
|
|
949
|
+
/* Above the scrolling groups, and opaque, so a filter never slides underneath it. */
|
|
950
|
+
z-index: 1;
|
|
951
|
+
background: hsl(var(--filter-bar-sticky-background, var(--background)));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/* ── LegalDocumentShell (#222) ─────────────────────────────────────────────────
|
|
956
|
+
* The long-form legal/policy document surface: a readable, measure-capped document
|
|
957
|
+
* column with a STICKY table-of-contents rail beside it. Every value reads a
|
|
958
|
+
* `--legal-document-*` component token, so a service re-tunes the measure, the rail
|
|
959
|
+
* and the section rhythm from theme.css with zero consumer `.legal-*` CSS.
|
|
960
|
+
*
|
|
961
|
+
* It OWNS its query container (`container: legal-document / inline-size`, the SplitPane
|
|
962
|
+
* precedent — gh#165), so the one-column ⇄ two-column decision comes from the SHELL's
|
|
963
|
+
* own available width, not the viewport: dropped into a narrow pane on a 1440px screen
|
|
964
|
+
* it correctly stays single-column.
|
|
965
|
+
*
|
|
966
|
+
* DOM order is header → rail (contents) → sections → footer, which is exactly the
|
|
967
|
+
* mobile reading order; the desktop branch only re-places those boxes into a grid.
|
|
968
|
+
*/
|
|
969
|
+
.ui-legal-document-scope {
|
|
970
|
+
container: legal-document / inline-size;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
.ui-legal-document {
|
|
974
|
+
display: grid;
|
|
975
|
+
grid-template-columns: minmax(0, 1fr);
|
|
976
|
+
row-gap: var(--legal-document-section-gap);
|
|
977
|
+
align-items: start;
|
|
978
|
+
min-width: 0;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
.ui-legal-document-header {
|
|
982
|
+
display: flex;
|
|
983
|
+
flex-direction: column;
|
|
984
|
+
gap: var(--legal-document-header-gap);
|
|
985
|
+
min-width: 0;
|
|
986
|
+
max-width: var(--legal-document-measure-max-width);
|
|
987
|
+
border-block-end: var(--legal-document-header-border);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
.ui-legal-document-meta {
|
|
991
|
+
display: flex;
|
|
992
|
+
flex-wrap: wrap;
|
|
993
|
+
align-items: baseline;
|
|
994
|
+
gap: var(--legal-document-meta-gap);
|
|
995
|
+
font-size: var(--legal-document-meta-font-size);
|
|
996
|
+
color: hsl(var(--legal-document-meta-foreground, var(--muted-foreground)));
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/* Long-form prose wrapping that holds in JA (kinsoku via `line-break: strict`), in EN
|
|
1000
|
+
* (long compounds / URLs via `overflow-wrap: break-word`) and in VI (diacritic-heavy
|
|
1001
|
+
* words must NOT be broken mid-word — `word-break: normal` keeps that true). */
|
|
1002
|
+
.ui-legal-document-summary,
|
|
1003
|
+
.ui-legal-document-section-body {
|
|
1004
|
+
min-width: 0;
|
|
1005
|
+
line-height: var(--legal-document-body-line-height);
|
|
1006
|
+
overflow-wrap: break-word;
|
|
1007
|
+
word-break: normal;
|
|
1008
|
+
line-break: strict;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
.ui-legal-document-summary {
|
|
1012
|
+
color: hsl(var(--legal-document-summary-foreground, var(--muted-foreground)));
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
.ui-legal-document-rail {
|
|
1016
|
+
display: flex;
|
|
1017
|
+
flex-direction: column;
|
|
1018
|
+
gap: var(--legal-document-nav-gap);
|
|
1019
|
+
min-width: 0;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.ui-legal-document-toc {
|
|
1023
|
+
display: flex;
|
|
1024
|
+
flex-direction: column;
|
|
1025
|
+
gap: var(--legal-document-toc-gap);
|
|
1026
|
+
min-width: 0;
|
|
1027
|
+
min-height: 0;
|
|
1028
|
+
border-inline-end: var(--legal-document-toc-border);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
.ui-legal-document-toc-title {
|
|
1032
|
+
font-size: var(--legal-document-toc-title-font-size);
|
|
1033
|
+
color: hsl(var(--legal-document-toc-foreground, var(--muted-foreground)));
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
.ui-legal-document-toc-list {
|
|
1037
|
+
display: flex;
|
|
1038
|
+
flex-direction: column;
|
|
1039
|
+
gap: var(--legal-document-toc-gap);
|
|
1040
|
+
margin: 0;
|
|
1041
|
+
padding: 0;
|
|
1042
|
+
list-style: none;
|
|
1043
|
+
min-width: 0;
|
|
1044
|
+
min-height: 0;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/* REAL anchors — `href="#<section id>"` — so the contents list is keyboard-reachable,
|
|
1048
|
+
* middle-clickable and deep-linkable even before JS boots. */
|
|
1049
|
+
.ui-legal-document-toc-link {
|
|
1050
|
+
display: block;
|
|
1051
|
+
padding: var(--legal-document-toc-item-padding);
|
|
1052
|
+
border-radius: var(--legal-document-toc-item-radius);
|
|
1053
|
+
border-inline-start: var(--legal-document-toc-marker-width) solid transparent;
|
|
1054
|
+
font-size: var(--legal-document-toc-font-size);
|
|
1055
|
+
line-height: 1.6;
|
|
1056
|
+
color: hsl(var(--legal-document-toc-foreground, var(--muted-foreground)));
|
|
1057
|
+
text-decoration: none;
|
|
1058
|
+
overflow-wrap: break-word;
|
|
1059
|
+
transition-property: color, background-color, border-color;
|
|
1060
|
+
transition-duration: var(--duration-fast);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
.ui-legal-document-toc-link:hover {
|
|
1064
|
+
color: hsl(var(--legal-document-toc-active-foreground, var(--foreground)));
|
|
1065
|
+
background: hsl(var(--legal-document-toc-active-background, var(--accent)) / 0.6);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
.ui-legal-document-toc-link:focus-visible {
|
|
1069
|
+
outline: none;
|
|
1070
|
+
box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/* Active entry: colour + a leading marker + a heavier weight, so the state survives a
|
|
1074
|
+
* monochrome / high-contrast rendering (never colour-only, WCAG 1.4.1). The anchor also
|
|
1075
|
+
* carries `aria-current="location"`. */
|
|
1076
|
+
.ui-legal-document-toc-link[data-active] {
|
|
1077
|
+
color: hsl(var(--legal-document-toc-active-foreground, var(--foreground)));
|
|
1078
|
+
background: hsl(var(--legal-document-toc-active-background, var(--accent)));
|
|
1079
|
+
border-inline-start-color: hsl(var(--legal-document-toc-marker-color, var(--primary)));
|
|
1080
|
+
font-weight: var(--font-weight-medium);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.ui-legal-document-content {
|
|
1084
|
+
display: flex;
|
|
1085
|
+
flex-direction: column;
|
|
1086
|
+
gap: var(--legal-document-section-gap);
|
|
1087
|
+
min-width: 0;
|
|
1088
|
+
max-width: var(--legal-document-measure-max-width);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/* `scroll-margin-block-start` (logical, so it survives vertical writing modes) is what
|
|
1092
|
+
* makes a hash jump AND a programmatic `scrollIntoView` land BELOW sticky chrome — no
|
|
1093
|
+
* JS offset arithmetic, no consumer CSS. */
|
|
1094
|
+
.ui-legal-document-section {
|
|
1095
|
+
display: flex;
|
|
1096
|
+
flex-direction: column;
|
|
1097
|
+
gap: var(--legal-document-section-title-gap);
|
|
1098
|
+
min-width: 0;
|
|
1099
|
+
scroll-margin-block-start: var(--legal-document-scroll-offset);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/* The section is `tabIndex={-1}` and receives focus when its contents anchor is
|
|
1103
|
+
* activated, so the focus ring must be VISIBLE (WCAG 2.4.7 / 2.4.11). */
|
|
1104
|
+
.ui-legal-document-section:focus-visible {
|
|
1105
|
+
outline: var(--focus-ring-width) solid hsl(var(--focus-ring-color, var(--ring)));
|
|
1106
|
+
outline-offset: var(--space-1);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
.ui-legal-document-footer {
|
|
1110
|
+
display: flex;
|
|
1111
|
+
flex-wrap: wrap;
|
|
1112
|
+
align-items: center;
|
|
1113
|
+
gap: var(--legal-document-footer-gap);
|
|
1114
|
+
min-width: 0;
|
|
1115
|
+
max-width: var(--legal-document-measure-max-width);
|
|
1116
|
+
border-block-start: var(--legal-document-footer-border);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/* Two-column mode (shell ≥56rem — a 1024px and a 1440px viewport both qualify): the
|
|
1120
|
+
* rail is PINNED beside a measure-capped document column and spans the header/body/
|
|
1121
|
+
* footer rows, so it stays in view for the whole scroll. Logical grid placement means
|
|
1122
|
+
* column 1 is the START side, i.e. it mirrors correctly under `dir="rtl"`.
|
|
1123
|
+
*
|
|
1124
|
+
* Below 56rem (the 390×844 case) NOTHING here applies: the shell stays a single column
|
|
1125
|
+
* and the rail is a STATIC compact contents block between the document header and the
|
|
1126
|
+
* first section — deliberately not sticky and not height-capped, so a phone never loses
|
|
1127
|
+
* vertical space to pinned chrome. */
|
|
1128
|
+
@container legal-document (min-width: 56rem) {
|
|
1129
|
+
.ui-legal-document {
|
|
1130
|
+
grid-template-columns:
|
|
1131
|
+
var(--legal-document-toc-width)
|
|
1132
|
+
minmax(0, var(--legal-document-measure-max-width));
|
|
1133
|
+
column-gap: var(--legal-document-column-gap);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
.ui-legal-document-header {
|
|
1137
|
+
grid-column: 2;
|
|
1138
|
+
grid-row: 1;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
.ui-legal-document-rail {
|
|
1142
|
+
grid-column: 1;
|
|
1143
|
+
grid-row: 1 / span 3;
|
|
1144
|
+
position: sticky;
|
|
1145
|
+
inset-block-start: var(--legal-document-toc-inset-block-start);
|
|
1146
|
+
align-self: start;
|
|
1147
|
+
max-height: var(--legal-document-toc-max-height);
|
|
1148
|
+
overflow-y: auto;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.ui-legal-document-content {
|
|
1152
|
+
grid-column: 2;
|
|
1153
|
+
grid-row: 2;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.ui-legal-document-footer {
|
|
1157
|
+
grid-column: 2;
|
|
1158
|
+
grid-row: 3;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
@layer components {
|
|
1164
|
+
/* ── Flex responsive region visibility (gh#252) ──────────────────────────────────────────────
|
|
1165
|
+
* `hideBelow` / `hideFrom` are the ONE public way to drop a layout region at a breakpoint step.
|
|
1166
|
+
* Without them a public header cannot hide its anchor navigation below the tablet step, or show
|
|
1167
|
+
* a compact-only affordance, without a page-local media query — which is exactly the API-first
|
|
1168
|
+
* violation gh#252 reports. The steps are the package's canonical breakpoint scale, the same
|
|
1169
|
+
* values `--master-detail-collapse-below` uses (sm 40rem · md 48rem · lg 64rem · xl 80rem); a
|
|
1170
|
+
* media query cannot read a `var()`, so the step values are the tokenized scale written out.
|
|
1171
|
+
*
|
|
1172
|
+
* Both props default to `undefined`, so NO `data-hide-*` attribute is emitted and none of these
|
|
1173
|
+
* selectors can match — every existing Flex is untouched (the gh#231 inert-default contract).
|
|
1174
|
+
* `display: none` removes the region from the a11y tree too, which is the point: the destination
|
|
1175
|
+
* must stay reachable from another region (the footer nav) at that width. */
|
|
1176
|
+
@media (width < 40rem) {
|
|
1177
|
+
.ui-flex[data-hide-below="sm"] {
|
|
1178
|
+
display: none;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
@media (width < 48rem) {
|
|
1183
|
+
.ui-flex[data-hide-below="md"] {
|
|
1184
|
+
display: none;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
@media (width < 64rem) {
|
|
1189
|
+
.ui-flex[data-hide-below="lg"] {
|
|
1190
|
+
display: none;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
@media (width < 80rem) {
|
|
1195
|
+
.ui-flex[data-hide-below="xl"] {
|
|
1196
|
+
display: none;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
@media (width >= 40rem) {
|
|
1201
|
+
.ui-flex[data-hide-from="sm"] {
|
|
1202
|
+
display: none;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
@media (width >= 48rem) {
|
|
1207
|
+
.ui-flex[data-hide-from="md"] {
|
|
1208
|
+
display: none;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
@media (width >= 64rem) {
|
|
1213
|
+
.ui-flex[data-hide-from="lg"] {
|
|
1214
|
+
display: none;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
@media (width >= 80rem) {
|
|
1219
|
+
.ui-flex[data-hide-from="xl"] {
|
|
1220
|
+
display: none;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
758
1223
|
}
|
|
@@ -38,4 +38,99 @@
|
|
|
38
38
|
height: var(--logo-size-lg);
|
|
39
39
|
font-size: var(--logo-font-size-lg);
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
/* Identity fills — the IDENTITY role --brand (canonical GoDX emerald), NOT the --success STATUS
|
|
43
|
+
* green: `tone="success"` names the semantic slot, not the status role, and a badge/progress
|
|
44
|
+
* green must never drag the product mark with it (gh#250). Role defaults live HERE (call site),
|
|
45
|
+
* not on :root, so a scoped `.dark` / `[data-tenant]` override of --brand re-resolves
|
|
46
|
+
* (docs/TOKENS.md · role-mirror knobs).
|
|
47
|
+
*
|
|
48
|
+
* The INK is --logo-identity-foreground, NOT --brand-foreground: this branch sets caller-supplied
|
|
49
|
+
* TEXT on the fill, so WCAG 2.2 SC 1.4.3 applies at 4.5:1 (14px bold is not "large text"), while
|
|
50
|
+
* --brand-foreground is the artwork KNOCKOUT colour that tracks --background and only owes 3:1 as
|
|
51
|
+
* non-text (SC 1.4.11). Knockout white on the emerald measured 3.67:1 — an AA failure on every
|
|
52
|
+
* boxed `tone="success"` glyph. See src/tokens/components/logo.css. */
|
|
53
|
+
.ui-logo[data-tone="success"] {
|
|
54
|
+
background: hsl(var(--logo-success-background, var(--brand)));
|
|
55
|
+
color: hsl(var(--logo-success-foreground, var(--logo-identity-foreground)));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* The identity mark honours `size` like every other Logo branch. The godx box has its OWN tier
|
|
59
|
+
* scale (--logo-godx-size-*) because the artwork is a horizontal capsule in a square viewBox and
|
|
60
|
+
* needs ~+0.25rem over the filled glyph box to read at the same weight — but a public `size` prop
|
|
61
|
+
* that renders identically at xs and lg is a silent no-op, and the lockup already scaled its
|
|
62
|
+
* wordmark per tier, so a frozen mark broke the mark↔wordmark proportion at `size="lg"`.
|
|
63
|
+
* These are `[data-mark][data-size]` (0,3,0) so they beat BOTH `.ui-logo[data-size]` and the
|
|
64
|
+
* `[data-mark="godx"]` base regardless of source order — the ordering bug that hid `size` here.
|
|
65
|
+
* --logo-godx-size stays the documented PIN: unset by default, and set once by a service theme it
|
|
66
|
+
* wins at every tier (call-site default, docs/TOKENS.md). */
|
|
67
|
+
.ui-logo[data-mark="godx"] {
|
|
68
|
+
width: var(--logo-godx-size, var(--logo-godx-size-md));
|
|
69
|
+
height: var(--logo-godx-size, var(--logo-godx-size-md));
|
|
70
|
+
border-radius: 0;
|
|
71
|
+
background: transparent;
|
|
72
|
+
color: hsl(var(--logo-godx-color, var(--brand)));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.ui-logo[data-mark="godx"][data-size="xs"] {
|
|
76
|
+
width: var(--logo-godx-size, var(--logo-godx-size-xs));
|
|
77
|
+
height: var(--logo-godx-size, var(--logo-godx-size-xs));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.ui-logo[data-mark="godx"][data-size="sm"] {
|
|
81
|
+
width: var(--logo-godx-size, var(--logo-godx-size-sm));
|
|
82
|
+
height: var(--logo-godx-size, var(--logo-godx-size-sm));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.ui-logo[data-mark="godx"][data-size="lg"] {
|
|
86
|
+
width: var(--logo-godx-size, var(--logo-godx-size-lg));
|
|
87
|
+
height: var(--logo-godx-size, var(--logo-godx-size-lg));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.ui-logo[data-mark="godx"] [data-slot="logo-artwork"] {
|
|
91
|
+
display: block;
|
|
92
|
+
width: 100%;
|
|
93
|
+
height: 100%;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
* LOCKUP — `<Logo wordmark="…" />`: the mark plus the readable product name as ONE element, so a
|
|
98
|
+
* shell header / auth brand bar never hand-rolls `inline-flex items-center gap-2` around a Logo
|
|
99
|
+
* and a `<Text>` (rule #46). Every knob is a token (rule #45); the wordmark colour defaults to
|
|
100
|
+
* the IDENTITY role and never reads --primary, so re-theming the action colour cannot recolour
|
|
101
|
+
* the brand — no consumer page CSS is required for the logo colour (gh#214).
|
|
102
|
+
*/
|
|
103
|
+
.ui-logo-lockup {
|
|
104
|
+
display: inline-flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: var(--logo-wordmark-gap);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.ui-logo-lockup .ui-logo-wordmark {
|
|
110
|
+
font-family: var(--logo-wordmark-font-family, var(--font-family-display));
|
|
111
|
+
font-size: var(--logo-wordmark-font-size-md);
|
|
112
|
+
font-weight: var(--logo-wordmark-font-weight);
|
|
113
|
+
letter-spacing: var(--logo-wordmark-letter-spacing);
|
|
114
|
+
line-height: 1;
|
|
115
|
+
color: hsl(var(--logo-wordmark-color, var(--foreground)));
|
|
116
|
+
white-space: nowrap;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.ui-logo-lockup[data-size="xs"] .ui-logo-wordmark {
|
|
120
|
+
font-size: var(--logo-wordmark-font-size-xs);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.ui-logo-lockup[data-size="sm"] .ui-logo-wordmark {
|
|
124
|
+
font-size: var(--logo-wordmark-font-size-sm);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.ui-logo-lockup[data-size="lg"] .ui-logo-wordmark {
|
|
128
|
+
font-size: var(--logo-wordmark-font-size-lg);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Canonical GoDX wordmark — the --brand IDENTITY role, independent of --primary AND of --success. */
|
|
132
|
+
.ui-logo-lockup[data-mark="godx"] .ui-logo-wordmark,
|
|
133
|
+
.ui-logo-lockup[data-tone="success"] .ui-logo-wordmark {
|
|
134
|
+
color: hsl(var(--logo-wordmark-color, var(--logo-godx-color, var(--brand))));
|
|
135
|
+
}
|
|
41
136
|
}
|