@godxjp/ui 18.4.0 → 18.5.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.
Files changed (131) hide show
  1. package/README.md +16 -1
  2. package/dist/app/theme-axes.d.ts +11 -3
  3. package/dist/app/theme-axes.js +2 -1
  4. package/dist/components/charts/chart-frame.d.ts +17 -3
  5. package/dist/components/charts/chart-frame.js +7 -1
  6. package/dist/components/charts/chart-summary.d.ts +21 -0
  7. package/dist/components/charts/chart-summary.js +32 -5
  8. package/dist/components/charts/compact-bar-trend.d.ts +29 -0
  9. package/dist/components/charts/compact-bar-trend.js +74 -0
  10. package/dist/components/charts/index.d.ts +3 -0
  11. package/dist/components/charts/index.js +2 -0
  12. package/dist/components/data-display/badge.d.ts +8 -0
  13. package/dist/components/data-display/badge.js +7 -1
  14. package/dist/components/data-display/data-table.d.ts +16 -1
  15. package/dist/components/data-display/data-table.js +56 -0
  16. package/dist/components/data-display/index.d.ts +3 -1
  17. package/dist/components/data-display/index.js +10 -1
  18. package/dist/components/data-display/list-row.d.ts +22 -4
  19. package/dist/components/data-display/list-row.js +21 -3
  20. package/dist/components/data-display/service-launcher-card.d.ts +40 -0
  21. package/dist/components/data-display/service-launcher-card.js +97 -0
  22. package/dist/components/data-entry/command-palette.d.ts +37 -0
  23. package/dist/components/data-entry/command-palette.js +125 -0
  24. package/dist/components/data-entry/index.d.ts +2 -0
  25. package/dist/components/data-entry/index.js +2 -0
  26. package/dist/components/feedback/dialog.d.ts +2 -0
  27. package/dist/components/feedback/dialog.js +42 -33
  28. package/dist/components/feedback/index.d.ts +4 -1
  29. package/dist/components/feedback/index.js +6 -2
  30. package/dist/components/feedback/sheet.d.ts +26 -4
  31. package/dist/components/feedback/sheet.js +54 -7
  32. package/dist/components/feedback/two-factor-setup.d.ts +31 -0
  33. package/dist/components/feedback/two-factor-setup.js +97 -0
  34. package/dist/components/general/index.d.ts +1 -1
  35. package/dist/components/general/logo.d.ts +42 -8
  36. package/dist/components/general/logo.js +67 -14
  37. package/dist/components/layout/app-shell.js +21 -5
  38. package/dist/components/layout/auth-divider.d.ts +7 -0
  39. package/dist/components/layout/auth-divider.js +21 -0
  40. package/dist/components/layout/auth-footer.d.ts +5 -0
  41. package/dist/components/layout/auth-footer.js +24 -0
  42. package/dist/components/layout/auth-identity.d.ts +5 -0
  43. package/dist/components/layout/auth-identity.js +17 -0
  44. package/dist/components/layout/auth-shell.d.ts +8 -1
  45. package/dist/components/layout/auth-shell.js +25 -6
  46. package/dist/components/layout/auth-stack.d.ts +4 -0
  47. package/dist/components/layout/auth-stack.js +8 -0
  48. package/dist/components/layout/centered-shell.d.ts +5 -2
  49. package/dist/components/layout/centered-shell.js +2 -1
  50. package/dist/components/layout/index.d.ts +18 -3
  51. package/dist/components/layout/index.js +17 -2
  52. package/dist/components/layout/legal-document-shell.d.ts +34 -0
  53. package/dist/components/layout/legal-document-shell.js +186 -0
  54. package/dist/components/layout/master-detail.d.ts +24 -0
  55. package/dist/components/layout/master-detail.js +48 -0
  56. package/dist/components/layout/org-switcher.d.ts +9 -0
  57. package/dist/components/layout/org-switcher.js +218 -0
  58. package/dist/components/layout/page-container.d.ts +1 -1
  59. package/dist/components/layout/page-container.js +3 -2
  60. package/dist/components/layout/sidebar-link.d.ts +43 -0
  61. package/dist/components/layout/sidebar-link.js +13 -0
  62. package/dist/components/layout/sidebar.d.ts +42 -5
  63. package/dist/components/layout/sidebar.js +170 -85
  64. package/dist/components/navigation/app-setting-picker.js +17 -6
  65. package/dist/components/navigation/filter-bar.d.ts +12 -3
  66. package/dist/components/navigation/filter-bar.js +8 -2
  67. package/dist/components/navigation/index.d.ts +2 -2
  68. package/dist/components/navigation/index.js +3 -1
  69. package/dist/components/navigation/tabs-scroll.d.ts +70 -0
  70. package/dist/components/navigation/tabs-scroll.js +76 -0
  71. package/dist/components/navigation/tabs.js +35 -20
  72. package/dist/email/brand-mark.d.ts +92 -0
  73. package/dist/email/brand-mark.js +83 -0
  74. package/dist/email/color.d.ts +52 -0
  75. package/dist/email/color.js +37 -0
  76. package/dist/email/geometry.d.ts +112 -0
  77. package/dist/email/geometry.js +89 -0
  78. package/dist/email/index.d.ts +78 -0
  79. package/dist/email/index.js +76 -0
  80. package/dist/email/inline-style.d.ts +25 -0
  81. package/dist/email/inline-style.js +21 -0
  82. package/dist/email/tokens.generated.d.ts +147 -0
  83. package/dist/email/tokens.generated.js +142 -0
  84. package/dist/i18n/messages/en.json +21 -2
  85. package/dist/i18n/messages/ja.json +21 -2
  86. package/dist/i18n/messages/vi.json +21 -2
  87. package/dist/inertia/index.d.ts +50 -0
  88. package/dist/inertia/index.js +5 -0
  89. package/dist/props/components/app.prop.d.ts +11 -1
  90. package/dist/props/components/charts.prop.d.ts +40 -1
  91. package/dist/props/components/data-display.prop.d.ts +13 -1
  92. package/dist/props/components/feedback.prop.d.ts +10 -0
  93. package/dist/props/components/index.d.ts +2 -2
  94. package/dist/props/components/layout.prop.d.ts +373 -10
  95. package/dist/props/components/layout.prop.js +1 -0
  96. package/dist/props/components/navigation.prop.d.ts +19 -1
  97. package/dist/props/registry.d.ts +291 -7
  98. package/dist/props/registry.js +341 -7
  99. package/dist/props/vocabulary/index.d.ts +1 -1
  100. package/dist/props/vocabulary/interaction.prop.d.ts +1 -1
  101. package/dist/props/vocabulary/layout.prop.d.ts +18 -0
  102. package/dist/styles/base.css +8 -2
  103. package/dist/styles/card-layout.css +161 -7
  104. package/dist/styles/chart-layout.css +97 -0
  105. package/dist/styles/control.css +48 -2
  106. package/dist/styles/data-display-layout.css +52 -6
  107. package/dist/styles/dialog-layout.css +105 -4
  108. package/dist/styles/fonts.css +15 -4
  109. package/dist/styles/index.css +11 -3
  110. package/dist/styles/layout.css +367 -1
  111. package/dist/styles/logo-layout.css +62 -0
  112. package/dist/styles/navigation-layout.css +43 -0
  113. package/dist/styles/shell-layout.css +488 -40
  114. package/dist/theme/dxs.canonical.css +76 -0
  115. package/dist/tokens/axes.css +39 -0
  116. package/dist/tokens/base.css +5 -0
  117. package/dist/tokens/components/card.css +48 -0
  118. package/dist/tokens/components/chart.css +47 -0
  119. package/dist/tokens/components/control.css +16 -0
  120. package/dist/tokens/components/email.css +66 -0
  121. package/dist/tokens/components/feedback.css +17 -0
  122. package/dist/tokens/components/legal-document.css +67 -0
  123. package/dist/tokens/components/list-row.css +23 -2
  124. package/dist/tokens/components/logo.css +32 -0
  125. package/dist/tokens/components/navigation.css +13 -0
  126. package/dist/tokens/components/sheet.css +19 -0
  127. package/dist/tokens/components/shell.css +138 -0
  128. package/dist/tokens/components/sidebar.css +33 -0
  129. package/dist/tokens/foundation.css +5 -2
  130. package/dist/tokens/semantic/layout.css +44 -2
  131. package/package.json +16 -5
@@ -56,14 +56,19 @@
56
56
  box-shadow: 0 0 0 1px hsl(var(--primary));
57
57
  }
58
58
 
59
- /* ── Density — override the shell inset/body tokens ──────────────────── */
59
+ /* ── Density — override the shell inset/body tokens ──────────────────────
60
+ * `--card-space-shell-y: initial` re-arms the block axis to follow THIS card's inset again: the
61
+ * `density` prop is an explicit per-instance decision and must beat an ambient shell-level block
62
+ * override (e.g. AuthShell compact), exactly as it did when one inset owned both axes. */
60
63
  [data-slot="card"][data-density="tight"] {
61
64
  --card-space-inset: var(--space-3);
65
+ --card-space-shell-y: initial;
62
66
  --card-space-body-y: var(--space-3);
63
67
  }
64
68
 
65
69
  [data-slot="card"][data-density="cozy"] {
66
70
  --card-space-inset: var(--space-5);
71
+ --card-space-shell-y: initial;
67
72
  --card-space-body-y: var(--space-5);
68
73
  }
69
74
 
@@ -111,7 +116,7 @@
111
116
  }
112
117
 
113
118
  [data-slot="card-header"]:not([data-banded]) {
114
- padding-top: var(--card-space-inset);
119
+ padding-top: var(--card-space-shell-y, var(--card-space-inset));
115
120
  padding-bottom: 0;
116
121
  }
117
122
 
@@ -254,7 +259,7 @@
254
259
  /* Header-only card — terminal slot owns bottom shell */
255
260
  [data-slot="card"]:not(:has([data-slot="card-content"], [data-slot="card-footer"]))
256
261
  [data-slot="card-header"] {
257
- padding-bottom: var(--card-space-inset);
262
+ padding-bottom: var(--card-space-shell-y, var(--card-space-inset));
258
263
  }
259
264
 
260
265
  /* Cover — header below media uses section top, not shell */
@@ -271,18 +276,27 @@
271
276
  padding-top: var(--card-space-body-y);
272
277
  }
273
278
 
274
- /* ── Content ─────────────────────────────────────────────────────────── */
279
+ /* ── Content ─────────────────────────────────────────────────────────────
280
+ * The two axes are INDEPENDENT (gh#232): the inline column reads --card-space-inset, the block
281
+ * shell edge reads --card-space-shell-y (default = the same inset, so the resting render is
282
+ * unchanged). A shell that needs a shorter card without a narrower column — AuthShell compact —
283
+ * now moves the block axis alone instead of hitting a wall at a uniform inset. */
275
284
  [data-slot="card-content"] {
276
285
  padding-inline: var(--card-space-inset);
277
- padding-bottom: var(--card-space-inset);
286
+ padding-bottom: var(--card-space-shell-y, var(--card-space-inset));
278
287
  }
279
288
 
280
289
  [data-slot="card-content"]:not([data-tight]):not([data-solo]) {
281
290
  padding-top: var(--card-space-body-y);
282
291
  }
283
292
 
293
+ /* A solo body owns BOTH block edges (there is no header/footer to carry them), so this is
294
+ * `padding-block`, not just `padding-top` — it overrides the generic bottom edge above.
295
+ * The fallback chain solo-y → shell-y → inset means: a shell that tunes only the general block
296
+ * knob still reaches a solo body, an explicit --card-space-solo-y wins over it, and with neither
297
+ * set the render is the historical inset. */
284
298
  [data-slot="card-content"][data-solo] {
285
- padding-top: var(--card-space-inset);
299
+ padding-block: var(--card-space-solo-y, var(--card-space-shell-y, var(--card-space-inset)));
286
300
  }
287
301
 
288
302
  [data-slot="card-content"][data-flush] {
@@ -343,7 +357,7 @@
343
357
 
344
358
  [data-slot="card-footer"]:not([data-separated]) {
345
359
  padding-top: 0;
346
- padding-bottom: var(--card-space-inset);
360
+ padding-bottom: var(--card-space-shell-y, var(--card-space-inset));
347
361
  }
348
362
 
349
363
  [data-slot="card-footer"][data-separated] {
@@ -474,4 +488,144 @@
474
488
  font-size: var(--stat-card-hint-font-size);
475
489
  line-height: var(--line-height-normal);
476
490
  }
491
+
492
+ /* ── Service launcher — organization-scoped downstream app tile ─────── */
493
+ [data-slot="card"][data-service-launcher] {
494
+ display: flex;
495
+ flex-direction: column;
496
+ gap: var(--card-service-launcher-space-gap);
497
+ padding: var(--card-space-inset);
498
+ }
499
+
500
+ [data-slot="service-launcher-heading"],
501
+ [data-slot="service-launcher-skeleton-heading"] {
502
+ display: flex;
503
+ min-width: 0;
504
+ align-items: center;
505
+ gap: var(--card-service-launcher-space-gap);
506
+ }
507
+
508
+ [data-slot="service-launcher-icon"] {
509
+ display: inline-flex;
510
+ width: var(--card-service-launcher-icon-size);
511
+ height: var(--card-service-launcher-icon-size);
512
+ flex: none;
513
+ align-items: center;
514
+ justify-content: center;
515
+ border-radius: var(--card-service-launcher-icon-radius);
516
+ background: hsl(var(--card-service-launcher-icon-background, var(--accent)));
517
+ color: hsl(var(--card-service-launcher-icon-foreground, var(--primary)));
518
+ }
519
+
520
+ /* Unavailable tile (the consumer supplied a `disabledReason`): the medallion drops its brand
521
+ * wash so an un-launchable service never reads as live. Role-mirror knobs default at the CALL
522
+ * SITE, so a scoped [data-tenant]/.dark override of --muted still reaches it (docs/TOKENS.md). */
523
+ [data-slot="card"][data-service-launcher][data-unavailable] [data-slot="service-launcher-icon"] {
524
+ background: hsl(var(--card-service-launcher-unavailable-icon-background, var(--muted)));
525
+ color: hsl(var(--card-service-launcher-unavailable-icon-foreground, var(--muted-foreground)));
526
+ }
527
+
528
+ [data-slot="service-launcher-icon"] > svg {
529
+ width: var(--card-service-launcher-icon-glyph-size);
530
+ height: var(--card-service-launcher-icon-glyph-size);
531
+ }
532
+
533
+ [data-slot="service-launcher-title"] {
534
+ min-width: 0;
535
+ flex: 1;
536
+ overflow-wrap: anywhere;
537
+ font-size: var(--card-title-font-size);
538
+ font-weight: var(--card-title-font-weight);
539
+ line-height: var(--card-title-line-height);
540
+ }
541
+
542
+ [data-slot="service-launcher-status"] {
543
+ flex: none;
544
+ }
545
+
546
+ [data-slot="service-launcher-description"] {
547
+ flex: 1;
548
+ overflow-wrap: anywhere;
549
+ color: hsl(var(--muted-foreground));
550
+ font-size: var(--card-service-launcher-description-font-size);
551
+ line-height: var(--card-service-launcher-description-line-height);
552
+ }
553
+
554
+ /* Mono metadata ONLY (hostname · plan) — machine identifiers, never prose. */
555
+ [data-slot="service-launcher-metadata"] {
556
+ overflow-wrap: anywhere;
557
+ color: hsl(var(--muted-foreground));
558
+ font-family: var(--font-mono);
559
+ font-size: var(--card-service-launcher-metadata-font-size);
560
+ line-height: var(--card-service-launcher-metadata-line-height);
561
+ }
562
+
563
+ /* The disabled/unavailable reason is a localized SENTENCE — it takes the description's prose
564
+ * rhythm, not the mono metadata treatment (a JA/VI sentence in monospace is unreadable). */
565
+ [data-slot="service-launcher-disabled-reason"] {
566
+ overflow-wrap: anywhere;
567
+ color: hsl(var(--muted-foreground));
568
+ font-size: var(--card-service-launcher-description-font-size);
569
+ line-height: var(--card-service-launcher-description-line-height);
570
+ }
571
+
572
+ [data-slot="service-launcher-action"] > [data-slot="button"] {
573
+ width: 100%;
574
+ }
575
+
576
+ [data-slot="card"][data-service-catalog-cta] {
577
+ min-height: var(--card-service-launcher-cta-min-height);
578
+ display: flex;
579
+ flex-direction: column;
580
+ align-items: center;
581
+ justify-content: center;
582
+ gap: var(--card-service-launcher-space-gap);
583
+ border-style: dashed;
584
+ padding: var(--card-space-inset);
585
+ text-align: center;
586
+ }
587
+
588
+ [data-slot="service-catalog-icon"] {
589
+ width: var(--card-service-launcher-icon-glyph-size);
590
+ height: var(--card-service-launcher-icon-glyph-size);
591
+ color: hsl(var(--muted-foreground));
592
+ }
593
+
594
+ [data-slot="service-catalog-title"] {
595
+ color: hsl(var(--muted-foreground));
596
+ font-size: var(--card-service-launcher-description-font-size);
597
+ line-height: var(--card-service-launcher-description-line-height);
598
+ }
599
+
600
+ [data-slot^="service-launcher-skeleton-"] {
601
+ display: block;
602
+ }
603
+
604
+ [data-slot="service-launcher-skeleton-icon"] {
605
+ width: var(--card-service-launcher-icon-size);
606
+ height: var(--card-service-launcher-icon-size);
607
+ flex: none;
608
+ }
609
+
610
+ [data-slot="service-launcher-skeleton-title"] {
611
+ width: var(--card-service-launcher-skeleton-title-width);
612
+ height: var(--font-size-base);
613
+ }
614
+
615
+ [data-slot="service-launcher-skeleton-status"] {
616
+ width: var(--card-service-launcher-skeleton-status-width);
617
+ height: var(--font-size-xs);
618
+ margin-inline-start: auto;
619
+ }
620
+
621
+ [data-slot="service-launcher-skeleton-description"],
622
+ [data-slot="service-launcher-skeleton-metadata"],
623
+ [data-slot="service-launcher-skeleton-action"] {
624
+ width: 100%;
625
+ height: var(--font-size-xs);
626
+ }
627
+
628
+ [data-slot="service-launcher-skeleton-action"] {
629
+ height: var(--control-height);
630
+ }
477
631
  }
@@ -37,3 +37,100 @@
37
37
  color: hsl(var(--popover-foreground));
38
38
  font-size: var(--font-size-xs);
39
39
  }
40
+
41
+ /* Content that sits BELOW the plot (the activity footer slot) — outside the
42
+ * `role="img"` canvas so links/buttons in it stay reachable and announced. */
43
+ .ui-chart-footer {
44
+ margin-block-start: var(--chart-trend-footer-gap);
45
+ font-size: var(--font-size-xs);
46
+ color: hsl(var(--muted-foreground));
47
+ }
48
+
49
+ /* ─────────────────────────────────────────────────────────────────────────────
50
+ * CompactBarTrend — a dependency-free (no recharts) vertical bar trend.
51
+ * ALL geometry resolves from `--chart-trend-*` tokens; the only inline value is
52
+ * the DATUM itself (`--chart-trend-bar-value`, a 0..1 ratio), so a consumer never
53
+ * writes a height calculation or a colour of its own.
54
+ * ─────────────────────────────────────────────────────────────────────────── */
55
+
56
+ /* `size` → the active plot-height tier. Set on the <figure> as `data-size`. */
57
+ .ui-chart-trend[data-size="xs"] {
58
+ --chart-trend-plot-height: var(--chart-trend-plot-height-xs);
59
+ }
60
+
61
+ .ui-chart-trend[data-size="sm"] {
62
+ --chart-trend-plot-height: var(--chart-trend-plot-height-sm);
63
+ }
64
+
65
+ .ui-chart-trend[data-size="md"] {
66
+ --chart-trend-plot-height: var(--chart-trend-plot-height-md);
67
+ }
68
+
69
+ .ui-chart-trend[data-size="lg"] {
70
+ --chart-trend-plot-height: var(--chart-trend-plot-height-lg);
71
+ }
72
+
73
+ .ui-chart-trend-body {
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: var(--chart-trend-tick-gap);
77
+ inline-size: 100%;
78
+ min-inline-size: 0;
79
+ }
80
+
81
+ .ui-chart-trend-plot {
82
+ display: flex;
83
+ align-items: flex-end;
84
+ gap: var(--chart-trend-bar-gap);
85
+ block-size: var(--chart-trend-plot-height);
86
+ inline-size: 100%;
87
+ min-inline-size: 0;
88
+ /* Quiet by default (rule #44) — a service opts into a baseline rule with
89
+ * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`. */
90
+ border-block-end: var(--chart-trend-baseline-border, none);
91
+ }
92
+
93
+ .ui-chart-trend-column {
94
+ display: flex;
95
+ flex: 1 1 0;
96
+ align-items: flex-end;
97
+ justify-content: center;
98
+ min-inline-size: 0;
99
+ block-size: 100%;
100
+ }
101
+
102
+ .ui-chart-trend-bar {
103
+ inline-size: 100%;
104
+ max-inline-size: var(--chart-trend-bar-max-width);
105
+ /* Data-driven: the 0..1 ratio arrives as an inline custom property. */
106
+ block-size: max(var(--chart-trend-bar-min-height), calc(var(--chart-trend-bar-value, 0) * 100%));
107
+ border-start-start-radius: var(--chart-trend-bar-radius);
108
+ border-start-end-radius: var(--chart-trend-bar-radius);
109
+ background: var(
110
+ --chart-trend-bar-background,
111
+ hsl(var(--muted-foreground) / var(--chart-trend-bar-background-alpha))
112
+ );
113
+ }
114
+
115
+ .ui-chart-trend-bar[data-emphasized="true"] {
116
+ background: var(--chart-trend-bar-emphasis-background, hsl(var(--primary)));
117
+ }
118
+
119
+ .ui-chart-trend-ticks {
120
+ display: flex;
121
+ gap: var(--chart-trend-bar-gap);
122
+ inline-size: 100%;
123
+ min-inline-size: 0;
124
+ }
125
+
126
+ .ui-chart-trend-tick {
127
+ flex: 1 1 0;
128
+ min-inline-size: 0;
129
+ overflow: hidden;
130
+ font-size: var(--chart-trend-tick-font-size);
131
+ line-height: var(--line-height-tight);
132
+ color: hsl(var(--muted-foreground));
133
+ text-align: center;
134
+ text-overflow: ellipsis;
135
+ white-space: nowrap;
136
+ }
@@ -585,6 +585,52 @@
585
585
  opacity: 0.5;
586
586
  }
587
587
 
588
+ [data-slot="dialog-content"].ui-command-palette {
589
+ top: var(--command-palette-inset-top);
590
+ width: min(
591
+ var(--command-palette-width),
592
+ calc(100vw - (2 * var(--command-palette-viewport-inset)))
593
+ );
594
+ max-width: none;
595
+ transform: translateX(-50%);
596
+ gap: 0;
597
+ padding: 0;
598
+ background: hsl(var(--popover));
599
+ }
600
+
601
+ .ui-command-palette-trigger {
602
+ min-width: 0;
603
+ }
604
+
605
+ .ui-command-palette-label {
606
+ min-width: 0;
607
+ }
608
+
609
+ .ui-command-palette-meta {
610
+ margin-inline-start: auto;
611
+ color: hsl(var(--muted-foreground));
612
+ font-size: var(--font-size-xs);
613
+ }
614
+
615
+ .ui-command-palette-state {
616
+ padding-block: var(--space-6);
617
+ padding-inline: var(--space-4);
618
+ text-align: center;
619
+ color: hsl(var(--muted-foreground));
620
+ font-size: var(--font-size-base);
621
+ }
622
+
623
+ .ui-command-palette-hints {
624
+ display: flex;
625
+ flex-wrap: wrap;
626
+ gap: var(--command-palette-hint-gap);
627
+ border-top: 1px solid hsl(var(--border));
628
+ padding: var(--command-palette-hint-padding-y) var(--command-palette-hint-padding-x);
629
+ color: hsl(var(--muted-foreground));
630
+ font-family: var(--font-mono);
631
+ font-size: var(--font-size-xs);
632
+ }
633
+
588
634
  .ui-search-input {
589
635
  position: relative;
590
636
  }
@@ -680,8 +726,8 @@
680
726
  .ui-otp-slot {
681
727
  position: relative;
682
728
  display: flex;
683
- width: var(--control-height);
684
- height: var(--control-height);
729
+ width: var(--otp-slot-size, var(--control-height));
730
+ height: var(--otp-slot-size, var(--control-height));
685
731
  align-items: center;
686
732
  justify-content: center;
687
733
  font-size: var(--font-size-base);
@@ -449,14 +449,27 @@
449
449
  }
450
450
 
451
451
  @layer components {
452
- /* ListRow — single-line entity row (leading · title/description · trailing) for short lists
453
- * inside a flush CardContent. Rows separate with a quiet divider; the last row leaves the
454
- * Card border to close the list. Logical properties throughout (RTL-safe). */
452
+ /* ListRow — single-line entity row (indicator · leading · title/description · trailing) for
453
+ * short lists inside a flush CardContent. Rows separate with a quiet divider; the last row
454
+ * leaves the Card border to close the list. Logical properties throughout (RTL-safe).
455
+ *
456
+ * Overflow contract (#224): the row itself may shrink (min-inline-size: 0) and WRAPS, the
457
+ * content column keeps only `min(--list-row-body-min-width, 100%)` — so it can always shrink
458
+ * below its intrinsic width and truncate/wrap — and the trailing actions wrap onto their own
459
+ * line instead of pushing the row (and the page root) wider than the viewport. */
455
460
  [data-slot="list-row"] {
456
461
  display: flex;
462
+ min-inline-size: 0;
463
+ flex-wrap: wrap;
457
464
  align-items: center;
458
465
  gap: var(--list-row-gap);
459
466
  padding: var(--list-row-padding-y) var(--list-row-padding-x);
467
+ background: var(--list-row-read-background, transparent);
468
+ }
469
+ /* Unread emphasis surface — paired with the indicator dot + its sr-only text, never colour
470
+ * alone (WCAG 1.4.1). */
471
+ [data-slot="list-row"][data-unread] {
472
+ background: var(--list-row-unread-background, hsl(var(--muted)));
460
473
  }
461
474
  [data-slot="list-row"][data-align="start"] {
462
475
  align-items: flex-start;
@@ -464,6 +477,19 @@
464
477
  [data-slot="list-row"]:not(:last-child) {
465
478
  border-block-end: var(--list-row-border, 1px solid hsl(var(--border)));
466
479
  }
480
+ [data-slot="list-row-indicator"] {
481
+ position: relative;
482
+ display: block;
483
+ flex-shrink: 0;
484
+ inline-size: var(--list-row-indicator-size);
485
+ block-size: var(--list-row-indicator-size);
486
+ border-radius: var(--radius-pill);
487
+ /* Read rows keep the gutter (titles stay on one optical axis) but paint nothing. */
488
+ background: transparent;
489
+ }
490
+ [data-slot="list-row"][data-unread] [data-slot="list-row-indicator"] {
491
+ background: hsl(var(--list-row-indicator-color, var(--primary)));
492
+ }
467
493
  [data-slot="list-row-leading"] {
468
494
  display: inline-flex;
469
495
  flex-shrink: 0;
@@ -473,18 +499,38 @@
473
499
  }
474
500
  [data-slot="list-row-body"] {
475
501
  display: flex;
476
- min-width: 0;
477
- flex: 1 1 auto;
502
+ min-inline-size: min(var(--list-row-body-min-width, 12rem), 100%);
503
+ flex: 1 1 var(--list-row-body-min-width, 12rem);
478
504
  flex-direction: column;
479
505
  }
506
+ /* `overflow="wrap"` — the title/description wrap instead of truncating; `anywhere` also lowers
507
+ * the min-content width so an unbroken token (URL, long JA/VI compound) can't widen the row. */
508
+ [data-slot="list-row"][data-overflow="wrap"] [data-slot="list-row-body"] {
509
+ overflow-wrap: anywhere;
510
+ }
480
511
  [data-slot="list-row-trailing"] {
481
512
  display: inline-flex;
513
+ max-inline-size: 100%;
482
514
  flex-shrink: 0;
515
+ flex-wrap: wrap;
483
516
  align-items: center;
484
- gap: var(--space-2);
517
+ justify-content: flex-end;
518
+ gap: var(--list-row-trailing-gap, var(--space-2));
485
519
  margin-inline-start: auto;
486
520
  }
487
521
 
522
+ @media (max-width: 359px) {
523
+ [data-slot="list-row"] {
524
+ flex-wrap: wrap;
525
+ }
526
+
527
+ [data-slot="list-row-trailing"] {
528
+ width: 100%;
529
+ flex-wrap: wrap;
530
+ justify-content: flex-end;
531
+ }
532
+ }
533
+
488
534
  /* CredentialReveal — one-time secret surface: caution banner, masked/revealed value row with
489
535
  * copy/reveal/download actions, and an optional acknowledge footer. Density-aware via the
490
536
  * --control-height tier; colours/spacing read semantic role tokens only. */
@@ -21,9 +21,22 @@
21
21
  border-top: 1px solid hsl(var(--border));
22
22
  }
23
23
 
24
- .ui-dialog-overlay,
24
+ /* Scrim = a SHARE of the ONE shared semantic scrim (--overlay-background), resolved here at the
25
+ * call site so a scoped [data-tenant]/.dark override of --overlay-background actually reaches the
26
+ * portaled overlay (docs/TOKENS.md · "Role-mirror knobs MUST be `initial`"). Setting
27
+ * --dialog-overlay-background / --sheet-overlay-background still wins outright. */
28
+ .ui-dialog-overlay {
29
+ background-color: var(
30
+ --dialog-overlay-background,
31
+ color-mix(in srgb, var(--overlay-background) var(--dialog-overlay-alpha), transparent)
32
+ );
33
+ }
34
+
25
35
  [data-slot="sheet-overlay"] {
26
- background-color: var(--overlay-background);
36
+ background-color: var(
37
+ --sheet-overlay-background,
38
+ color-mix(in srgb, var(--overlay-background) var(--sheet-overlay-alpha), transparent)
39
+ );
27
40
  }
28
41
 
29
42
  .ui-dialog-overlay {
@@ -38,8 +51,8 @@
38
51
  top: 50%;
39
52
  z-index: 50;
40
53
  display: grid;
41
- width: 100%;
42
- max-width: 32rem;
54
+ width: min(var(--dialog-width-default), calc(100% - var(--dialog-viewport-inset)));
55
+ max-width: none;
43
56
  transform: translate(-50%, -50%);
44
57
  gap: var(--dialog-space-gap);
45
58
  border: 1px solid hsl(var(--border));
@@ -133,7 +146,95 @@
133
146
  color: hsl(var(--muted-foreground));
134
147
  }
135
148
 
149
+ .ui-two-factor-setup {
150
+ width: calc(100% - 1.5rem);
151
+ max-width: 27.5rem;
152
+ }
153
+
154
+ /* No scrim override here: the two-factor dialog's backdrop is the SHARED overlay scrim
155
+ * (.ui-dialog-overlay above). The old literal `rgb(0 0 0 / 0.3)` re-stated the dialog default at a
156
+ * higher specificity and silently opted this one dialog out of --overlay-background theming. */
157
+
158
+ .ui-two-factor-setup-enrollment,
159
+ .ui-two-factor-setup-recovery {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: var(--space-stack-md);
163
+ }
164
+
165
+ .ui-two-factor-setup-grid {
166
+ display: grid;
167
+ grid-template-columns: 7.5rem minmax(0, 1fr);
168
+ align-items: center;
169
+ gap: var(--space-4);
170
+ }
171
+
172
+ .ui-two-factor-setup-grid .ui-qr-code[data-size] {
173
+ inline-size: 7.5rem;
174
+ }
175
+
176
+ .ui-two-factor-setup-fields,
177
+ .ui-two-factor-setup-code {
178
+ display: flex;
179
+ min-width: 0;
180
+ flex-direction: column;
181
+ gap: var(--space-stack-xs);
182
+ }
183
+
184
+ .ui-two-factor-setup-notice {
185
+ border: 1px dashed hsl(var(--border));
186
+ border-radius: var(--radius-md);
187
+ padding: var(--space-2) var(--space-3);
188
+ }
189
+
190
+ .ui-two-factor-setup-recovery-codes {
191
+ display: grid;
192
+ grid-template-columns: repeat(2, minmax(0, 1fr));
193
+ gap: var(--space-2);
194
+ padding: var(--space-3);
195
+ border: 1px solid hsl(var(--border));
196
+ border-radius: var(--radius-md);
197
+ background: hsl(var(--muted) / 0.4);
198
+ }
199
+
200
+ @media (max-width: 479px) {
201
+ .ui-two-factor-setup-grid {
202
+ grid-template-columns: 1fr;
203
+ justify-items: center;
204
+ }
205
+
206
+ .ui-two-factor-setup-fields {
207
+ width: 100%;
208
+ }
209
+ }
210
+
136
211
  [data-slot="dialog-header"]:not([data-tone="default"]) [data-slot="dialog-description"] {
137
212
  color: hsl(var(--foreground));
138
213
  }
139
214
  }
215
+
216
+ /*
217
+ * REDUCED MOTION — every overlay enter/exit animation is gated (WCAG 2.3.3 Animation from
218
+ * Interactions / 2.2.2 Pause-Stop-Hide). Dialog, AlertDialog, CommandPalette (a Dialog), Sheet /
219
+ * mobile drawer and Popover all animate via Tailwind `data-[state]:animate-in|animate-out`
220
+ * utilities plus a `transition`; the content additionally zooms/slides, which is exactly the
221
+ * vestibular trigger the media query exists for. The overlay still fades in/out INSTANTLY, so the
222
+ * open/closed state is never ambiguous — only the motion is removed, never the affordance.
223
+ *
224
+ * DELIBERATELY UNLAYERED. Tailwind v4 emits `animate-in` / `slide-in-from-*` into `@layer
225
+ * utilities`, which beats ANY rule inside `@layer components` regardless of specificity — a gate
226
+ * written inside the components layer above would be dead code. Unlayered author declarations win
227
+ * over every layered one, so this block (and only this block) actually stops the animation.
228
+ */
229
+ @media (prefers-reduced-motion: reduce) {
230
+ [data-slot="dialog-overlay"],
231
+ [data-slot="dialog-content"],
232
+ [data-slot="sheet-overlay"],
233
+ [data-slot="sheet-content"],
234
+ [data-slot="popover-content"],
235
+ .ui-dialog-overlay,
236
+ .ui-command-palette {
237
+ animation: none;
238
+ transition: none;
239
+ }
240
+ }
@@ -7,10 +7,21 @@
7
7
  * Vietnamese glyph coverage used by the product locales.
8
8
  * · Noto Sans JP — retained as the CJK fallback after M PLUS 2.
9
9
  *
10
- * The all-in-one `@godxjp/ui/styles` entry pulls this in automatically. If you
11
- * manage fonts yourself (e.g. next/font), DON'T import this — import
12
- * `@godxjp/ui/styles/base` + the layout layers you use and supply your own
13
- * faces via the --font-family-* tokens.
10
+ * IMPORT ORDER IS LOAD-BEARING this file MUST be imported AFTER
11
+ * `@godxjp/ui/styles/base`:
12
+ *
13
+ * @import "@godxjp/ui/styles/base"; /· tokens: --font-sans-base = system stack ·/
14
+ * @import "@godxjp/ui/styles/fonts"; /· THEN the bundled faces overwrite it ·/
15
+ *
16
+ * The token fills below and foundation.css's font-agnostic default both target
17
+ * `:root` at identical specificity (0,1,0), so whichever is parsed LAST wins.
18
+ * Imported first, this file is silently dead — the system stack renders and the
19
+ * ~800 KB of @font-face is downloaded for nothing (issue #210).
20
+ *
21
+ * The all-in-one `@godxjp/ui/styles` entry pulls this in automatically, in the
22
+ * correct position. If you manage fonts yourself (e.g. next/font), DON'T import
23
+ * this — import `@godxjp/ui/styles/base` + the layout layers you use and supply
24
+ * your own faces via the --font-family-* tokens.
14
25
  * ───────────────────────────────────────────────────────────────────────── */
15
26
  @import "@fontsource/m-plus-2/400.css";
16
27
  @import "@fontsource/m-plus-2/500.css";
@@ -7,13 +7,21 @@
7
7
  * @import "@godxjp/ui/styles/control"; // + only the layout layers you use
8
8
  * @import "@godxjp/ui/styles/dialog-layout";
9
9
  * ...
10
- * and add `@godxjp/ui/styles/fonts` only if you want the bundled faces.
11
- * See src/styles/base.css for the full list and rules.
10
+ * and add `@godxjp/ui/styles/fonts` AFTER `styles/base` if you want the bundled
11
+ * faces. See src/styles/base.css for the full list and rules.
12
+ *
13
+ * ORDER IS LOAD-BEARING — `fonts.css` MUST come AFTER `base.css`.
14
+ * `base.css` pulls in `tokens/base.css` → `tokens/foundation.css`, which declares
15
+ * the font-agnostic `:root { --font-sans-base: <system stack> }` default. Both
16
+ * declarations sit on `:root` at identical specificity (0,1,0), so the LATER one
17
+ * wins. With `fonts.css` first the bundled faces could never apply and every
18
+ * consumer downloaded ~800 KB of dead @font-face (issue #210).
12
19
  * ───────────────────────────────────────────────────────────────────────── */
13
20
  @import "sonner/dist/styles.css";
14
21
  @import "react-day-picker/style.css";
15
- @import "./fonts.css";
16
22
  @import "./base.css";
23
+ /* AFTER the token layers — see the ORDER IS LOAD-BEARING note above. */
24
+ @import "./fonts.css";
17
25
  @import "./shell-layout.css";
18
26
  @import "./motion.css";
19
27
  @import "./layout.css";