@godxjp/ui 18.5.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.
Files changed (91) hide show
  1. package/dist/components/charts/compact-bar-trend.d.ts +2 -1
  2. package/dist/components/charts/index.d.ts +4 -1
  3. package/dist/components/data-display/data-table.d.ts +25 -4
  4. package/dist/components/data-display/data-table.js +13 -2
  5. package/dist/components/data-display/empty-state.js +1 -1
  6. package/dist/components/data-display/index.d.ts +1 -1
  7. package/dist/components/data-display/list-row.d.ts +19 -0
  8. package/dist/components/data-display/list-row.js +2 -0
  9. package/dist/components/data-display/qr-code.d.ts +1 -1
  10. package/dist/components/data-display/table.d.ts +47 -2
  11. package/dist/components/data-display/table.js +29 -4
  12. package/dist/components/data-entry/command.d.ts +10 -10
  13. package/dist/components/data-entry/index.d.ts +1 -0
  14. package/dist/components/data-entry/input-otp.d.ts +1 -0
  15. package/dist/components/feedback/dialog.d.ts +14 -1
  16. package/dist/components/feedback/dialog.js +4 -0
  17. package/dist/components/feedback/index.d.ts +1 -1
  18. package/dist/components/feedback/index.js +2 -0
  19. package/dist/components/general/logo.d.ts +7 -1
  20. package/dist/components/general/logo.js +1 -1
  21. package/dist/components/layout/app-shell.d.ts +1 -1
  22. package/dist/components/layout/app-shell.js +55 -46
  23. package/dist/components/layout/auth-account-summary.d.ts +5 -0
  24. package/dist/components/layout/auth-account-summary.js +31 -0
  25. package/dist/components/layout/auth-shell.d.ts +3 -2
  26. package/dist/components/layout/centered-shell.d.ts +1 -1
  27. package/dist/components/layout/centered-shell.js +28 -19
  28. package/dist/components/layout/error-surface.d.ts +35 -0
  29. package/dist/components/layout/error-surface.js +155 -0
  30. package/dist/components/layout/flex.d.ts +1 -1
  31. package/dist/components/layout/flex.js +4 -0
  32. package/dist/components/layout/index.d.ts +5 -0
  33. package/dist/components/layout/index.js +4 -0
  34. package/dist/components/layout/page-container.d.ts +1 -1
  35. package/dist/components/layout/page-container.js +4 -0
  36. package/dist/components/navigation/steps.js +38 -19
  37. package/dist/components/navigation/tabs.js +12 -3
  38. package/dist/components/ui/avatar.d.ts +2 -1
  39. package/dist/components/ui/avatar.js +2 -1
  40. package/dist/components/ui/input-otp.d.ts +4 -1
  41. package/dist/components/ui/input-otp.js +2 -1
  42. package/dist/components/ui/toggle-group.d.ts +7 -4
  43. package/dist/components/ui/toggle-group.js +32 -21
  44. package/dist/email/brand-mark.d.ts +21 -5
  45. package/dist/email/brand-mark.js +5 -0
  46. package/dist/email/color.d.ts +2 -1
  47. package/dist/email/geometry.d.ts +9 -1
  48. package/dist/email/geometry.js +1 -0
  49. package/dist/email/index.d.ts +6 -1
  50. package/dist/email/index.js +6 -0
  51. package/dist/email/inline-style.d.ts +7 -5
  52. package/dist/email/inline-style.js +5 -0
  53. package/dist/email/tokens.generated.d.ts +40 -20
  54. package/dist/email/tokens.generated.js +40 -20
  55. package/dist/email/urgency.d.ts +22 -0
  56. package/dist/email/urgency.js +14 -0
  57. package/dist/i18n/messages/en.json +15 -1
  58. package/dist/i18n/messages/ja.json +15 -1
  59. package/dist/i18n/messages/vi.json +15 -1
  60. package/dist/props/components/data-display.prop.d.ts +63 -1
  61. package/dist/props/components/data-entry.prop.d.ts +6 -0
  62. package/dist/props/components/index.d.ts +1 -1
  63. package/dist/props/components/layout.prop.d.ts +191 -4
  64. package/dist/props/components/navigation.prop.d.ts +5 -1
  65. package/dist/props/registry.d.ts +145 -4
  66. package/dist/props/registry.js +189 -5
  67. package/dist/props/vocabulary/data.prop.d.ts +27 -0
  68. package/dist/props/vocabulary/index.d.ts +3 -3
  69. package/dist/props/vocabulary/interaction.prop.d.ts +9 -0
  70. package/dist/props/vocabulary/layout.prop.d.ts +38 -7
  71. package/dist/styles/control.css +28 -0
  72. package/dist/styles/data-display-layout.css +26 -0
  73. package/dist/styles/layout.css +99 -0
  74. package/dist/styles/logo-layout.css +42 -9
  75. package/dist/styles/navigation-layout.css +109 -0
  76. package/dist/styles/shell-layout.css +302 -0
  77. package/dist/styles/table-layout.css +164 -1
  78. package/dist/theme/dxs.canonical.css +5 -4
  79. package/dist/tokens/axes.css +4 -3
  80. package/dist/tokens/base.css +1 -0
  81. package/dist/tokens/components/data-display.css +13 -0
  82. package/dist/tokens/components/email.css +43 -16
  83. package/dist/tokens/components/error-surface.css +36 -0
  84. package/dist/tokens/components/list-row.css +20 -0
  85. package/dist/tokens/components/logo.css +52 -10
  86. package/dist/tokens/components/navigation.css +15 -0
  87. package/dist/tokens/components/shell.css +72 -0
  88. package/dist/tokens/components/table.css +45 -0
  89. package/dist/tokens/foundation.css +26 -0
  90. package/dist/tokens/semantic/layout.css +25 -0
  91. package/package.json +8 -2
@@ -147,6 +147,51 @@
147
147
  gap: var(--auth-shell-card-stack-gap);
148
148
  }
149
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
+
150
195
  .ui-auth-shell[data-preset="device-authorization"] {
151
196
  --auth-shell-card-max-width: var(--auth-shell-device-card-max-width);
152
197
  --auth-shell-main-padding: var(--auth-shell-device-main-padding);
@@ -197,6 +242,52 @@
197
242
  height: var(--auth-requester-glyph-size);
198
243
  }
199
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
+
200
291
  .ui-auth-legal-footer {
201
292
  display: flex;
202
293
  flex-wrap: wrap;
@@ -277,6 +368,12 @@
277
368
  --auth-shell-main-padding: var(--auth-shell-canonical-main-padding-mobile);
278
369
  }
279
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
+
280
377
  /* Declared after the canonical mobile inset so a preset's mobile gutter wins at equal
281
378
  * specificity: device-authorization = 5px inline (card x=5, width=380 at 390px);
282
379
  * context-selection = edge-to-edge (0 inline). */
@@ -479,6 +576,33 @@
479
576
  .app-mobile-nav-trigger {
480
577
  display: inline-flex;
481
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
+ }
482
606
  }
483
607
 
484
608
  .sb-root {
@@ -781,6 +905,10 @@
781
905
  min-width: 0;
782
906
  flex: 1;
783
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);
784
912
  text-overflow: ellipsis;
785
913
  white-space: nowrap;
786
914
  }
@@ -1093,6 +1221,16 @@
1093
1221
  .ui-topbar-start {
1094
1222
  flex: 0 1 auto;
1095
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;
1233
+ }
1096
1234
  .ui-topbar-center {
1097
1235
  flex: 1 1 0%;
1098
1236
  justify-content: center;
@@ -1103,6 +1241,17 @@
1103
1241
  margin-inline-start: auto;
1104
1242
  }
1105
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
+
1106
1255
  .tb-chip {
1107
1256
  display: inline-flex;
1108
1257
  max-width: 15rem;
@@ -1156,6 +1305,10 @@
1156
1305
  min-width: 0;
1157
1306
  overflow: hidden;
1158
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;
1159
1312
  text-overflow: ellipsis;
1160
1313
  white-space: nowrap;
1161
1314
  }
@@ -1286,3 +1439,152 @@
1286
1439
  transition: none;
1287
1440
  }
1288
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
+ }
@@ -74,12 +74,17 @@
74
74
  gap: var(--space-inline-sm);
75
75
  }
76
76
 
77
+ /* The footer owns its own inset on BOTH axes (gh#236) — it is a self-contained slot that is
78
+ * usually dropped straight into a flush container, so relying on an ancestor for inline/block-end
79
+ * padding left the label and the page-size Select flush against the container edge and its
80
+ * closing border. Logical properties only (RTL-safe); both axes are tunable tokens (#45). */
77
81
  .ui-data-table-pagination {
78
82
  display: flex;
79
83
  align-items: center;
80
84
  justify-content: flex-end;
81
85
  gap: var(--space-inline-sm);
82
- padding-top: var(--space-stack-sm);
86
+ padding-block: var(--table-pagination-padding-y, var(--space-stack-sm));
87
+ padding-inline: var(--table-pagination-padding-x, var(--table-cell-space-x));
83
88
  }
84
89
 
85
90
  /* Numbered footer may wrap only between its two semantic groups. The supported JA/EN/VI locale
@@ -115,10 +120,22 @@
115
120
 
116
121
  .ui-data-table-surface {
117
122
  overflow: hidden;
123
+ /* Narrow-viewport legibility floor — a TOKEN, not the old `min-w-[640px]` literal (gh#253,
124
+ * rule #45). Cleared at the `sm` step below, and opted out of entirely by the collection
125
+ * preset, which owns its width through the column-priority measures. */
126
+ min-inline-size: var(--table-surface-min-inline-size);
118
127
  border: 1px solid hsl(var(--border));
119
128
  border-radius: var(--radius-md);
120
129
  }
121
130
 
131
+ /* `preset="action-collection"` (gh#253) — the priority measures fit the table to its container at
132
+ * every width, so the desktop floor would re-introduce exactly the horizontal scroll the preset
133
+ * removes. Attribute-scoped: `preset="default"` emits NO attribute, so an untouched DataTable
134
+ * never matches this rule (the inert-default contract, gh#231). */
135
+ .ui-data-table-surface[data-preset="action-collection"] {
136
+ min-inline-size: 0;
137
+ }
138
+
122
139
  /* Zebra striping (opt-in via the `striped` prop). Selected rows keep their
123
140
  * own emphasis, so they are excluded from the stripe. */
124
141
  .ui-data-table-surface[data-striped] tbody tr:nth-child(even):not([data-state="selected"]) {
@@ -187,6 +204,12 @@
187
204
  .ui-data-table-scroll::after {
188
205
  inset-inline-end: 0;
189
206
  }
207
+
208
+ /* From `sm` up the viewport is already wider than the floor, so it is released and the surface
209
+ * simply fills its container (this is the `sm:min-w-0` half of the old utility pair). */
210
+ .ui-data-table-surface {
211
+ min-inline-size: 0;
212
+ }
190
213
  }
191
214
 
192
215
  /* ── Pinned (sticky) inline-end column ────────────────────────────────
@@ -233,3 +256,143 @@
233
256
  );
234
257
  }
235
258
  }
259
+
260
+ @layer components {
261
+ /* ── Table · `preset="action-collection"` (gh#253) ───────────────────────────────────────────
262
+ * The canonical dense approval / action queue: requester · target · reason · requested date ·
263
+ * row actions, all five discoverable in the initial 390px frame, WITHOUT a consumer width, a
264
+ * hidden column or a page-local breakpoint.
265
+ *
266
+ * It keeps the REAL table: no `display` change, no role rewriting, no card transformation — so
267
+ * `<table>/<thead>/<tr>/<th>/<td>` semantics, header association, `aria-sort` and screen-reader
268
+ * table navigation survive exactly as they are at 1440. What the preset replaces is the SIZING
269
+ * model: the desktop INTRINSIC column widths (a `white-space: nowrap` reason column is what
270
+ * makes a five-column queue 1511px wide and forces the horizontal scroll gh#253 reports) give
271
+ * way to `table-layout: fixed` plus token-owned column-PRIORITY measures, and cells wrap.
272
+ *
273
+ * The collapse step is measured against the TABLE'S OWN container, not the viewport (a table in
274
+ * a master rail must collapse before the page does), so it is a container query. Below the step
275
+ * only the MEASURES and the type/padding tier change — the structure is identical. */
276
+ .ui-table-collection {
277
+ container: ui-table-collection / inline-size;
278
+ }
279
+
280
+ .ui-table-collection [data-slot="table"] {
281
+ table-layout: fixed;
282
+ inline-size: 100%;
283
+ }
284
+
285
+ .ui-table-collection :is([data-slot="table-head"], [data-slot="table-cell"]) {
286
+ /* The column measure comes from `priority`; an unmarked column takes what is left (`auto`),
287
+ * which is the free-text column. */
288
+ inline-size: var(--table-action-collection-column-width, auto);
289
+ overflow-wrap: anywhere;
290
+ white-space: normal;
291
+ vertical-align: top;
292
+ }
293
+
294
+ .ui-table-collection [data-priority="primary"] {
295
+ --table-action-collection-column-width: var(--table-action-collection-primary-width);
296
+ }
297
+
298
+ .ui-table-collection [data-priority="secondary"] {
299
+ --table-action-collection-column-width: var(--table-action-collection-secondary-width);
300
+ }
301
+
302
+ .ui-table-collection [data-priority="meta"] {
303
+ --table-action-collection-column-width: var(--table-action-collection-meta-width);
304
+ }
305
+
306
+ .ui-table-collection [data-priority="actions"] {
307
+ --table-action-collection-column-width: var(--table-action-collection-actions-width);
308
+ text-align: end;
309
+ }
310
+
311
+ /* Below the step the SOURCE measures are re-pointed to their compact tier (and the type / row
312
+ * density with them); nothing else moves. One block per canonical step — a media/container
313
+ * query cannot read a `var()`, so the tokenized scale is written out (sm 40rem · md 48rem ·
314
+ * lg 64rem · xl 80rem, the same steps as `--master-detail-collapse-below`). */
315
+ @container ui-table-collection (width < 40rem) {
316
+ [data-collapse-below="sm"] [data-slot="table"] {
317
+ --table-action-collection-primary-width: var(--table-action-collection-primary-width-compact);
318
+ --table-action-collection-secondary-width: var(
319
+ --table-action-collection-secondary-width-compact
320
+ );
321
+ --table-action-collection-meta-width: var(--table-action-collection-meta-width-compact);
322
+ --table-action-collection-actions-width: var(--table-action-collection-actions-width-compact);
323
+ --table-row-height: var(--table-action-collection-row-height-compact);
324
+ --table-cell-space-x: var(--table-action-collection-cell-space-x-compact);
325
+ --table-cell-padding-y: var(--table-action-collection-cell-padding-y-compact);
326
+ font-size: var(--table-action-collection-font-size-compact);
327
+ }
328
+
329
+ /* A nowrap child (a Badge pill, a chip) would otherwise bleed into the next column at the
330
+ * compact measure — inside a collapsed action cell everything wraps. */
331
+ [data-collapse-below="sm"] [data-slot="table-cell"] * {
332
+ white-space: normal;
333
+ }
334
+ }
335
+
336
+ @container ui-table-collection (width < 48rem) {
337
+ [data-collapse-below="md"] [data-slot="table"] {
338
+ --table-action-collection-primary-width: var(--table-action-collection-primary-width-compact);
339
+ --table-action-collection-secondary-width: var(
340
+ --table-action-collection-secondary-width-compact
341
+ );
342
+ --table-action-collection-meta-width: var(--table-action-collection-meta-width-compact);
343
+ --table-action-collection-actions-width: var(--table-action-collection-actions-width-compact);
344
+ --table-row-height: var(--table-action-collection-row-height-compact);
345
+ --table-cell-space-x: var(--table-action-collection-cell-space-x-compact);
346
+ --table-cell-padding-y: var(--table-action-collection-cell-padding-y-compact);
347
+ font-size: var(--table-action-collection-font-size-compact);
348
+ }
349
+
350
+ /* A nowrap child (a Badge pill, a chip) would otherwise bleed into the next column at the
351
+ * compact measure — inside a collapsed action cell everything wraps. */
352
+ [data-collapse-below="md"] [data-slot="table-cell"] * {
353
+ white-space: normal;
354
+ }
355
+ }
356
+
357
+ @container ui-table-collection (width < 64rem) {
358
+ [data-collapse-below="lg"] [data-slot="table"] {
359
+ --table-action-collection-primary-width: var(--table-action-collection-primary-width-compact);
360
+ --table-action-collection-secondary-width: var(
361
+ --table-action-collection-secondary-width-compact
362
+ );
363
+ --table-action-collection-meta-width: var(--table-action-collection-meta-width-compact);
364
+ --table-action-collection-actions-width: var(--table-action-collection-actions-width-compact);
365
+ --table-row-height: var(--table-action-collection-row-height-compact);
366
+ --table-cell-space-x: var(--table-action-collection-cell-space-x-compact);
367
+ --table-cell-padding-y: var(--table-action-collection-cell-padding-y-compact);
368
+ font-size: var(--table-action-collection-font-size-compact);
369
+ }
370
+
371
+ /* A nowrap child (a Badge pill, a chip) would otherwise bleed into the next column at the
372
+ * compact measure — inside a collapsed action cell everything wraps. */
373
+ [data-collapse-below="lg"] [data-slot="table-cell"] * {
374
+ white-space: normal;
375
+ }
376
+ }
377
+
378
+ @container ui-table-collection (width < 80rem) {
379
+ [data-collapse-below="xl"] [data-slot="table"] {
380
+ --table-action-collection-primary-width: var(--table-action-collection-primary-width-compact);
381
+ --table-action-collection-secondary-width: var(
382
+ --table-action-collection-secondary-width-compact
383
+ );
384
+ --table-action-collection-meta-width: var(--table-action-collection-meta-width-compact);
385
+ --table-action-collection-actions-width: var(--table-action-collection-actions-width-compact);
386
+ --table-row-height: var(--table-action-collection-row-height-compact);
387
+ --table-cell-space-x: var(--table-action-collection-cell-space-x-compact);
388
+ --table-cell-padding-y: var(--table-action-collection-cell-padding-y-compact);
389
+ font-size: var(--table-action-collection-font-size-compact);
390
+ }
391
+
392
+ /* A nowrap child (a Badge pill, a chip) would otherwise bleed into the next column at the
393
+ * compact measure — inside a collapsed action cell everything wraps. */
394
+ [data-collapse-below="xl"] [data-slot="table-cell"] * {
395
+ white-space: normal;
396
+ }
397
+ }
398
+ }
@@ -33,10 +33,11 @@
33
33
 
34
34
  :root {
35
35
  /* ── Brand palette — the shipped GodX Navy ramp (identical to the `brand`
36
- * axis preset). The brand-green IDENTITY mark is deliberately NOT here:
37
- * `--logo-godx-color` / `--logo-wordmark-color` default to the `--success`
38
- * identity role at their call sites and never read `--primary`, so a
39
- * re-themed action colour can never recolour the GoDX mark or wordmark. */
36
+ * axis preset). The emerald IDENTITY mark is deliberately NOT here:
37
+ * `--logo-godx-color` / `--logo-wordmark-color` default to the `--brand`
38
+ * IDENTITY role at their call sites and never read `--primary` (nor the
39
+ * `--success` status green gh#250), so a re-themed action colour can
40
+ * never recolour the GoDX mark or wordmark. */
40
41
  --primary: 211 73% 15%;
41
42
  --primary-foreground: 0 0% 100%;
42
43
  --ring: 24 99% 46%;
@@ -73,9 +73,10 @@
73
73
  * `src/theme/dxs.canonical.css` mirrors this block for stylesheet-only apps;
74
74
  * `src/tokens/__tests__/dxs-canonical-theme.test.ts` fails if the two drift.
75
75
  *
76
- * The brand-green GoDX identity mark needs no binding: `--logo-godx-color` /
77
- * `--logo-wordmark-color` already default to the `--success` identity role at
78
- * their call sites and never reference `--primary`. */
76
+ * The GoDX identity mark needs no binding: `--logo-godx-color` /
77
+ * `--logo-wordmark-color` already default to the `--brand` IDENTITY role (the
78
+ * canonical emerald) at their call sites, and never reference `--primary` — nor
79
+ * the `--success` status green they wrongly borrowed before gh#250. */
79
80
  :root[data-brand="dxs"] {
80
81
  --primary: 211 73% 15%;
81
82
  --primary-foreground: 0 0% 100%;
@@ -24,3 +24,4 @@
24
24
  @import "./components/shell.css";
25
25
  @import "./components/sidebar.css";
26
26
  @import "./components/legal-document.css";
27
+ @import "./components/error-surface.css";
@@ -15,6 +15,19 @@
15
15
  Painted as an overlay so a service sets --avatar-tint: hsl(var(--primary) / 0.08). */
16
16
  --avatar-tint: transparent;
17
17
 
18
+ /* Entity-header mark — `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an
19
+ organization/service header uses, as opposed to the round person avatar. Every value is a knob
20
+ (rule #45) so a service matches the mark to its own grid without a className override.
21
+ --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the
22
+ --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped
23
+ [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value).
24
+ Defaults = --radius-lg corners · --control-height box (same as the circle avatar, so swapping
25
+ shape never reflows a header) · hsl(var(--primary)) fill · hsl(var(--primary-foreground)) glyph. */
26
+ --avatar-square-radius: var(--radius-lg);
27
+ --avatar-square-size: var(--control-height);
28
+ --avatar-square-background: initial;
29
+ --avatar-square-foreground: initial;
30
+
18
31
  /* Progress track + fill — `initial` so the role defaults re-resolve under a scoped theme.
19
32
  Track reads --secondary, fill reads --success; a service re-tones once.
20
33
  Defaults = hsl(var(--secondary)) track · hsl(var(--success)) fill. */