@godxjp/ui 18.8.0 → 18.10.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 (83) hide show
  1. package/LICENSE +202 -0
  2. package/dist/components/data-display/badge.d.ts +7 -0
  3. package/dist/components/data-display/badge.js +1 -1
  4. package/dist/components/data-display/card.d.ts +3 -27
  5. package/dist/components/data-display/card.js +1 -4
  6. package/dist/components/data-display/data-table.js +2 -2
  7. package/dist/components/data-display/index.d.ts +2 -0
  8. package/dist/components/data-display/index.js +2 -0
  9. package/dist/components/data-display/permission-matrix.d.ts +26 -0
  10. package/dist/components/data-display/permission-matrix.js +219 -0
  11. package/dist/components/data-display/table.d.ts +12 -0
  12. package/dist/components/data-entry/branch-scope-picker.d.ts +43 -0
  13. package/dist/components/data-entry/branch-scope-picker.js +200 -0
  14. package/dist/components/data-entry/command-palette.js +8 -1
  15. package/dist/components/data-entry/index.d.ts +2 -0
  16. package/dist/components/data-entry/index.js +2 -0
  17. package/dist/components/data-entry/input-otp.d.ts +1 -1
  18. package/dist/components/feedback/alert.d.ts +2 -20
  19. package/dist/components/feedback/alert.js +1 -11
  20. package/dist/components/feedback/banner.d.ts +21 -0
  21. package/dist/components/feedback/banner.js +15 -0
  22. package/dist/components/feedback/index.d.ts +4 -2
  23. package/dist/components/feedback/index.js +2 -2
  24. package/dist/components/general/typography.d.ts +1 -0
  25. package/dist/components/general/typography.js +32 -13
  26. package/dist/components/layout/auth-shell.d.ts +2 -2
  27. package/dist/components/layout/index.d.ts +2 -2
  28. package/dist/components/layout/index.js +2 -2
  29. package/dist/components/layout/page-container.d.ts +1 -1
  30. package/dist/components/layout/page-container.js +45 -19
  31. package/dist/components/layout/service-role-panel.d.ts +25 -0
  32. package/dist/components/layout/service-role-panel.js +206 -0
  33. package/dist/components/navigation/filter-bar.d.ts +2 -2
  34. package/dist/components/navigation/filter-bar.js +113 -34
  35. package/dist/components/navigation/index.d.ts +1 -1
  36. package/dist/components/navigation/steps.d.ts +2 -2
  37. package/dist/components/navigation/steps.js +2 -4
  38. package/dist/components/ui/avatar.d.ts +1 -1
  39. package/dist/components/ui/avatar.js +1 -2
  40. package/dist/components/ui/input-otp.d.ts +3 -15
  41. package/dist/components/ui/input-otp.js +1 -2
  42. package/dist/email/tokens.generated.d.ts +2 -2
  43. package/dist/email/tokens.generated.js +2 -2
  44. package/dist/i18n/messages/en.json +56 -4
  45. package/dist/i18n/messages/ja.json +51 -5
  46. package/dist/i18n/messages/vi.json +50 -4
  47. package/dist/props/components/data-display.prop.d.ts +66 -16
  48. package/dist/props/components/data-entry.prop.d.ts +54 -7
  49. package/dist/props/components/feedback.prop.d.ts +3 -3
  50. package/dist/props/components/general.prop.d.ts +8 -1
  51. package/dist/props/components/layout.prop.d.ts +75 -54
  52. package/dist/props/components/navigation.prop.d.ts +77 -59
  53. package/dist/props/registry.d.ts +273 -72
  54. package/dist/props/registry.js +361 -92
  55. package/dist/props/vocabulary/content.prop.d.ts +5 -0
  56. package/dist/props/vocabulary/index.d.ts +1 -1
  57. package/dist/props/vocabulary/interaction.prop.d.ts +4 -7
  58. package/dist/props/vocabulary/layout.prop.d.ts +2 -2
  59. package/dist/styles/alert-layout.css +15 -32
  60. package/dist/styles/badge-layout.css +5 -0
  61. package/dist/styles/card-layout.css +13 -49
  62. package/dist/styles/control.css +2 -12
  63. package/dist/styles/data-display-layout.css +0 -16
  64. package/dist/styles/layout.css +102 -108
  65. package/dist/styles/navigation-layout.css +3 -8
  66. package/dist/styles/shell-layout.css +17 -17
  67. package/dist/styles/table-layout.css +77 -2
  68. package/dist/styles/text-layout.css +13 -0
  69. package/dist/tokens/base.css +1 -0
  70. package/dist/tokens/components/badge.css +5 -0
  71. package/dist/tokens/components/banner.css +16 -0
  72. package/dist/tokens/components/card.css +0 -12
  73. package/dist/tokens/components/control.css +0 -16
  74. package/dist/tokens/components/data-display.css +0 -12
  75. package/dist/tokens/components/email.css +2 -8
  76. package/dist/tokens/components/feedback.css +0 -20
  77. package/dist/tokens/components/navigation.css +10 -34
  78. package/dist/tokens/components/shell.css +24 -36
  79. package/dist/tokens/components/table.css +41 -1
  80. package/dist/tokens/semantic/layout.css +5 -12
  81. package/package.json +15 -2
  82. package/dist/components/layout/page-header.d.ts +0 -22
  83. package/dist/components/layout/page-header.js +0 -89
@@ -51,16 +51,9 @@
51
51
  background: transparent;
52
52
  }
53
53
 
54
- /* `featured` = the brand-toned perimeter. Its colour is a role-mirror knob (rule #45): declared
55
- * `initial` in the token layer so the --primary default resolves HERE, at the call site, and a
56
- * scoped [data-tenant]/.dark override of --primary still reaches it. A service that wants the
57
- * featured ring in a non-brand tone overrides --card-featured-border-color; a card that wants a
58
- * SEMANTIC perimeter (attention / destructive / success …) uses
59
- * `accent="…" accentPlacement="perimeter"` instead — same geometry, tone-owned. */
60
54
  [data-slot="card"][data-variant="featured"] {
61
- border-color: var(--card-featured-border-color, hsl(var(--primary)));
62
- box-shadow: 0 0 0 var(--card-featured-ring-width)
63
- var(--card-featured-border-color, hsl(var(--primary)));
55
+ border-color: hsl(var(--primary));
56
+ box-shadow: 0 0 0 1px hsl(var(--primary));
64
57
  }
65
58
 
66
59
  /* ── Density — override the shell inset/body tokens ──────────────────────
@@ -79,39 +72,33 @@
79
72
  --card-space-body-y: var(--space-5);
80
73
  }
81
74
 
82
- /* ── Accent toneONE resolved colour per semantic accent ─────────────
83
- * `--card-accent-color` is RESOLVED from the card's own `data-accent`, so both placements read
84
- * the same value and a new tone is one line. It is deliberately NOT a service knob: it is set on
85
- * the element, so a :root / [data-tenant] override could never reach it. Retint the ROLE
86
- * (--attention, --success, …) — that is what a role is for. */
75
+ /* ── Accent edgesemantic left stripe (token width), content on shell ─ */
76
+ [data-slot="card"][data-accent] {
77
+ border-inline-start-width: var(--card-accent-rail-width);
78
+ }
79
+
87
80
  [data-slot="card"][data-accent="primary"] {
88
- --card-accent-color: hsl(var(--primary));
81
+ border-inline-start-color: hsl(var(--primary));
89
82
  }
90
83
 
91
84
  [data-slot="card"][data-accent="success"] {
92
- --card-accent-color: hsl(var(--success));
85
+ border-inline-start-color: hsl(var(--success));
93
86
  }
94
87
 
95
88
  [data-slot="card"][data-accent="warning"] {
96
- --card-accent-color: hsl(var(--warning));
89
+ border-inline-start-color: hsl(var(--warning));
97
90
  }
98
91
 
99
92
  [data-slot="card"][data-accent="info"] {
100
- --card-accent-color: hsl(var(--info));
93
+ border-inline-start-color: hsl(var(--info));
101
94
  }
102
95
 
103
96
  [data-slot="card"][data-accent="attention"] {
104
- --card-accent-color: hsl(var(--attention));
97
+ border-inline-start-color: hsl(var(--attention));
105
98
  }
106
99
 
107
100
  [data-slot="card"][data-accent="destructive"] {
108
- --card-accent-color: hsl(var(--destructive));
109
- }
110
-
111
- /* ── Accent placement `edge` (DEFAULT) — semantic left stripe (token width), content on shell ─ */
112
- [data-slot="card"][data-accent] {
113
- border-inline-start-width: var(--card-accent-rail-width);
114
- border-inline-start-color: var(--card-accent-color);
101
+ border-inline-start-color: hsl(var(--destructive));
115
102
  }
116
103
 
117
104
  [data-slot="card"][data-accent] > [data-slot="card-header"],
@@ -120,29 +107,6 @@
120
107
  padding-inline-start: calc(var(--card-space-inset) - var(--card-accent-rail-width));
121
108
  }
122
109
 
123
- /* ── Accent placement `perimeter` — the full attention border (gh#12) ───
124
- * The whole edge carries the semantic tone instead of one rail, so a card can read as
125
- * "action required" / "failed" at a glance without borrowing `--primary` the way
126
- * `variant="featured"` does. Same optical weight as `featured` (border + 1px ring), tone-owned.
127
- * Because the rail is gone, the slot padding compensation above is undone — content returns to
128
- * the normal shell column, so switching placement never shifts the text. */
129
- [data-slot="card"][data-accent][data-accent-placement="perimeter"] {
130
- border-width: var(--card-accent-perimeter-width);
131
- border-color: var(--card-accent-color);
132
- box-shadow:
133
- 0 0 0 var(--card-accent-perimeter-ring-width) var(--card-accent-color),
134
- var(--card-shadow),
135
- var(--card-glow);
136
- }
137
-
138
- [data-slot="card"][data-accent][data-accent-placement="perimeter"] > [data-slot="card-header"],
139
- [data-slot="card"][data-accent][data-accent-placement="perimeter"]
140
- > [data-slot="card-content"]:not([data-flush]),
141
- [data-slot="card"][data-accent][data-accent-placement="perimeter"]
142
- > [data-slot="card-footer"]:not([data-flush]) {
143
- padding-inline-start: var(--card-space-inset);
144
- }
145
-
146
110
  /* ── Header ──────────────────────────────────────────────────────────── */
147
111
  [data-slot="card-header"] {
148
112
  display: flex;
@@ -714,18 +714,8 @@
714
714
  .ui-otp-container {
715
715
  display: flex;
716
716
  align-items: center;
717
- justify-content: var(--otp-container-align, flex-start);
718
717
  gap: var(--space-2);
719
718
  }
720
- /* `align` on InputOTP lands on the hidden <input> (input-otp owns the container element), so the
721
- * container reads it through :has() — the same mechanism this file already uses for the invalid
722
- * and disabled states. Specificity puts the per-instance prop above the theme token. */
723
- .ui-otp-container:has(.ui-otp-input[data-align="center"]) {
724
- justify-content: center;
725
- }
726
- .ui-otp-container:has(.ui-otp-input[data-align="end"]) {
727
- justify-content: flex-end;
728
- }
729
719
  .ui-otp-input:disabled {
730
720
  cursor: not-allowed;
731
721
  }
@@ -736,8 +726,8 @@
736
726
  .ui-otp-slot {
737
727
  position: relative;
738
728
  display: flex;
739
- width: var(--otp-slot-inline-size, var(--otp-slot-size, var(--control-height)));
740
- height: var(--otp-slot-block-size, var(--otp-slot-size, var(--control-height)));
729
+ width: var(--otp-slot-size, var(--control-height));
730
+ height: var(--otp-slot-size, var(--control-height));
741
731
  align-items: center;
742
732
  justify-content: center;
743
733
  font-size: var(--font-size-base);
@@ -30,22 +30,6 @@
30
30
  color: var(--avatar-square-foreground, hsl(var(--primary-foreground)));
31
31
  }
32
32
 
33
- /* CAPABILITY MEDALLION — `<Avatar appearance="tinted">` (gh#12). The tinted companion of the
34
- * solid entity mark: same box, same radius, a soft role wash instead of a solid fill and the
35
- * glyph in the role itself. Declared AFTER the square rule (equal specificity) so it retints
36
- * either shape. The glyph rule is scoped to this appearance ON PURPOSE — a global
37
- * `.ui-avatar svg` would outrank the per-call-site icon classes existing avatars already use. */
38
- .ui-avatar[data-appearance="tinted"] {
39
- --avatar-background: var(--avatar-tinted-background, hsl(var(--primary) / 0.1));
40
- color: var(--avatar-tinted-foreground, hsl(var(--primary)));
41
- }
42
-
43
- .ui-avatar[data-appearance="tinted"] svg {
44
- inline-size: var(--avatar-tinted-glyph-size);
45
- block-size: var(--avatar-tinted-glyph-size);
46
- flex: none;
47
- }
48
-
49
33
  .ui-avatar-image {
50
34
  width: 100%;
51
35
  height: 100%;
@@ -521,47 +521,35 @@
521
521
  width: 100%;
522
522
  }
523
523
 
524
- .ui-page-title {
525
- font-size: var(--page-title-font-size);
526
- font-weight: var(--font-weight-medium);
527
- letter-spacing: var(--letter-spacing-tight);
528
- line-height: var(--line-height-tight);
529
- overflow-wrap: anywhere;
530
- }
531
-
532
- /* Title + `meta` share one baseline-aligned band (gh#255). The band only exists when `meta` is
533
- * passed, so a header without it keeps its historical bare-`<h1>` DOM. `wrap` is what keeps a
534
- * long JA/VI title and a status Badge legible at 390px instead of squeezing the title column. */
535
- .ui-page-title-band {
524
+ /* Title line + status/meta band (PageContainer `status`, godxjp-ui#255). The band sits on
525
+ * the title's baseline row at the token-owned gap and WRAPS under the title when the
526
+ * viewport (390px) or a long JA/VI title leaves no room — never clips, never overlaps. */
527
+ .ui-page-header-title-row {
536
528
  display: flex;
537
529
  flex-wrap: wrap;
538
- align-items: baseline;
539
- gap: var(--page-header-meta-gap);
530
+ align-items: center;
531
+ gap: var(--page-header-status-gap);
532
+ min-inline-size: 0;
533
+ }
534
+
535
+ .ui-page-header-title-row > .ui-page-title {
540
536
  min-inline-size: 0;
541
537
  }
542
538
 
543
- .ui-page-header-meta {
539
+ .ui-page-header-status {
544
540
  display: flex;
545
541
  flex-wrap: wrap;
546
542
  align-items: center;
547
- gap: var(--page-header-meta-gap);
543
+ gap: var(--page-header-status-gap);
548
544
  min-inline-size: 0;
549
545
  }
550
546
 
551
- /* `loading` placeholders — the real `<h1>`/`<p>` wearing the shared `.ui-skeleton-block` skin, so
552
- * the pending band occupies the same rhythm the settled band will. `max-inline-size: 100%` keeps
553
- * them inside a 390px column; the block size replaces the line box the (sr-only) text no longer
554
- * fills. */
555
- .ui-page-title-placeholder {
556
- inline-size: var(--page-title-placeholder-measure);
557
- max-inline-size: 100%;
558
- min-block-size: var(--page-title-placeholder-block-size);
559
- }
560
-
561
- .ui-page-subtitle-placeholder {
562
- inline-size: var(--page-subtitle-placeholder-measure);
563
- max-inline-size: 100%;
564
- min-block-size: var(--page-subtitle-placeholder-block-size);
547
+ .ui-page-title {
548
+ font-size: var(--page-title-font-size);
549
+ font-weight: var(--font-weight-medium);
550
+ letter-spacing: var(--letter-spacing-tight);
551
+ line-height: var(--line-height-tight);
552
+ overflow-wrap: anywhere;
565
553
  }
566
554
 
567
555
  /* Compact viewports use a responsive type step, not compact density: body/control text and
@@ -953,84 +941,6 @@
953
941
  font-size: var(--filter-label-font-size);
954
942
  }
955
943
 
956
- /* ── FilterBar typed model (#258) ──────────────────────────────────────────────────────────
957
- * Every rule below is scoped to a region the bar only emits when its prop is passed, so a bar
958
- * built the old way (children + onClear) matches none of them and is geometrically untouched.
959
- *
960
- * SEARCH — full-width while the strip is stacked (a bounded search box on a 390px screen wastes
961
- * the row), then one shared token-owned measure from the 640px step up. This single measure is
962
- * what the issue was filed against: as a plain child, every page picked its own width. */
963
- .ui-filter-bar-search {
964
- width: 100%;
965
- min-width: 0;
966
- }
967
-
968
- @media (min-width: 640px) {
969
- .ui-filter-bar-search {
970
- width: var(--filter-bar-search-width);
971
- max-width: 100%;
972
- flex: 0 0 auto;
973
- }
974
- }
975
-
976
- /* CHIPS — always their own full-width line. Applied filters are a record of state, not another
977
- * control in the row; wrapping them in beside the pickers makes both harder to scan. */
978
- .ui-filter-bar-chips {
979
- display: flex;
980
- width: 100%;
981
- flex-basis: 100%;
982
- flex-wrap: wrap;
983
- align-items: center;
984
- gap: var(--filter-bar-chip-row-gap);
985
- min-width: 0;
986
- }
987
-
988
- .ui-filter-bar-chip {
989
- display: inline-flex;
990
- max-width: 100%;
991
- align-items: center;
992
- gap: var(--filter-bar-chip-gap);
993
- padding: var(--filter-bar-chip-padding-block) var(--filter-bar-chip-padding-inline);
994
- border-radius: var(--filter-bar-chip-radius);
995
- background: hsl(var(--filter-bar-chip-background, var(--muted)));
996
- color: hsl(var(--filter-bar-chip-foreground, var(--muted-foreground)));
997
- font-size: var(--filter-bar-chip-font-size);
998
- }
999
-
1000
- /* A long JA/VI chip value wraps inside the chip rather than pushing the row into a horizontal
1001
- * scroll — the remove control must stay reachable at 390px. */
1002
- .ui-filter-bar-chip-label {
1003
- min-width: 0;
1004
- overflow-wrap: anywhere;
1005
- }
1006
-
1007
- .ui-filter-bar-chip-remove {
1008
- flex: none;
1009
- }
1010
-
1011
- .ui-filter-bar-count {
1012
- color: hsl(var(--filter-bar-count-foreground, var(--muted-foreground)));
1013
- font-size: var(--filter-bar-count-font-size);
1014
- /* The count is a number that changes in place — tabular figures stop the row from twitching
1015
- * as it goes 9 → 10 → 100. */
1016
- font-variant-numeric: tabular-nums;
1017
- }
1018
-
1019
- .ui-filter-bar-actions {
1020
- display: flex;
1021
- width: 100%;
1022
- flex-wrap: wrap;
1023
- align-items: center;
1024
- gap: var(--space-inline-sm);
1025
- }
1026
-
1027
- @media (min-width: 640px) {
1028
- .ui-filter-bar-actions {
1029
- width: auto;
1030
- flex: 0 0 auto;
1031
- }
1032
- }
1033
-
1034
944
  /* Toolbar/FilterBar `overflow="scroll"` (#216) — one bounded row that scrolls INLINE instead of
1035
945
  * wrapping onto extra rows. Below 640px the strip still stacks (the `wrap` base rules above own
1036
946
  * that): a 390px viewport must never hide a filter behind a horizontal scroll it cannot see.
@@ -1063,6 +973,90 @@
1063
973
  z-index: 1;
1064
974
  background: hsl(var(--filter-bar-sticky-background, var(--background)));
1065
975
  }
976
+
977
+ /* Typed-model reset+actions cluster gets the same viewport-pinned treatment (gh#258). */
978
+ .ui-toolbar[data-overflow="scroll"] > .ui-filter-bar-end {
979
+ position: sticky;
980
+ inset-inline-end: 0;
981
+ margin-inline-start: auto;
982
+ flex: 0 0 auto;
983
+ z-index: 1;
984
+ background: hsl(var(--filter-bar-sticky-background, var(--background)));
985
+ }
986
+ }
987
+
988
+ /* ── FilterBar typed model (gh#258) ─────────────────────────────────────────────
989
+ * Canonical model layout: strip (search → filters → children → reset → actions) →
990
+ * applied-chips row → result-count/error line. Geometry is entirely token-owned
991
+ * (`--filter-bar-*`); stacking below 640px reuses the `.ui-toolbar` base rules, so
992
+ * 1440/1024/390 behave identically to the composition form. */
993
+ .ui-filter-bar {
994
+ display: flex;
995
+ flex-direction: column;
996
+ gap: var(--filter-bar-section-gap);
997
+ }
998
+
999
+ .ui-filter-bar .ui-filter-bar-search {
1000
+ inline-size: 100%;
1001
+ }
1002
+
1003
+ .ui-filter-bar-end {
1004
+ display: flex;
1005
+ flex-wrap: wrap;
1006
+ align-items: center;
1007
+ gap: var(--space-2);
1008
+ width: 100%;
1009
+ }
1010
+
1011
+ /* Inside the end cluster the clear button gives up its own auto margin — the CLUSTER is what
1012
+ * parks at the inline end, so reset and actions stay adjacent in the keyboard/DOM order. */
1013
+ .ui-filter-bar-end .ui-toolbar-clear {
1014
+ margin-inline-start: 0;
1015
+ }
1016
+
1017
+ .ui-filter-bar-actions {
1018
+ display: flex;
1019
+ flex-wrap: wrap;
1020
+ align-items: center;
1021
+ gap: var(--space-2);
1022
+ }
1023
+
1024
+ @media (min-width: 640px) {
1025
+ .ui-filter-bar .ui-filter-bar-search {
1026
+ inline-size: min(100%, var(--filter-bar-search-width));
1027
+ }
1028
+
1029
+ .ui-filter-bar .ui-filter-bar-filter {
1030
+ min-inline-size: min(100%, var(--filter-bar-filter-width));
1031
+ }
1032
+
1033
+ .ui-filter-bar-end {
1034
+ width: auto;
1035
+ margin-inline-start: auto;
1036
+ }
1037
+ }
1038
+
1039
+ .ui-filter-bar-chips {
1040
+ display: flex;
1041
+ flex-wrap: wrap;
1042
+ align-items: center;
1043
+ gap: var(--filter-bar-chip-gap);
1044
+ }
1045
+
1046
+ .ui-filter-bar-chip {
1047
+ display: inline-flex;
1048
+ align-items: center;
1049
+ gap: var(--space-1);
1050
+ min-width: 0;
1051
+ }
1052
+
1053
+ .ui-filter-bar-meta {
1054
+ font-size: var(--filter-label-font-size);
1055
+ color: hsl(var(--muted-foreground));
1056
+ }
1057
+
1058
+ .ui-filter-bar-error {
1059
+ color: hsl(var(--destructive));
1066
1060
  }
1067
1061
 
1068
1062
  /* ── LegalDocumentShell (#222) ─────────────────────────────────────────────────
@@ -394,15 +394,10 @@
394
394
  box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
395
395
  }
396
396
 
397
- /* The step NUMBER carries the emphasis. Both knobs exist because the canonical hosted-identity
398
- * row marks the step with an accent TINT at normal weight, while the original marked it with
399
- * bold at the inherited colour — a service picks its own emphasis instead of forking this rule
400
- * (rule #45). Defaults are the original bold + inherited colour, so nothing moves. */
401
397
  .ui-steps-inline-index {
402
398
  flex: none;
403
399
  font-variant-numeric: tabular-nums;
404
- font-weight: var(--steps-inline-index-font-weight);
405
- color: var(--steps-inline-index-color, currentColor);
400
+ font-weight: var(--font-weight-bold);
406
401
  }
407
402
 
408
403
  .ui-steps-inline-title {
@@ -423,7 +418,8 @@
423
418
  color: hsl(var(--destructive));
424
419
  }
425
420
 
426
- .ui-steps-inline-item[data-status="wait"] .ui-steps-inline-control {
421
+ .ui-steps-inline-item[data-status="wait"] .ui-steps-inline-control,
422
+ .ui-steps-inline-separator {
427
423
  color: hsl(var(--muted-foreground));
428
424
  }
429
425
 
@@ -431,6 +427,5 @@
431
427
  width: var(--steps-inline-separator-size);
432
428
  height: var(--steps-inline-separator-size);
433
429
  flex: none;
434
- color: var(--steps-inline-separator-color, hsl(var(--muted-foreground)));
435
430
  }
436
431
  }
@@ -192,14 +192,9 @@
192
192
  text-align: center;
193
193
  }
194
194
 
195
- /* Device-authorization also owns its CODE FIELD (gh#12): the preset's subject is the code, so
196
- * the slot box comes from the preset instead of the generic square control tier. Scoped on the
197
- * shell so every OTP inside the device flow inherits it; a per-instance override still wins. */
198
195
  .ui-auth-shell[data-preset="device-authorization"] {
199
196
  --auth-shell-card-max-width: var(--auth-shell-device-card-max-width);
200
197
  --auth-shell-main-padding: var(--auth-shell-device-main-padding);
201
- --otp-slot-inline-size: var(--auth-shell-device-otp-slot-inline-size);
202
- --otp-slot-block-size: var(--auth-shell-device-otp-slot-block-size);
203
198
  }
204
199
 
205
200
  .ui-auth-shell[data-preset="context-selection"] {
@@ -343,12 +338,21 @@
343
338
  .ui-auth-shell[data-variant="canonical"] .ui-form-field {
344
339
  --field-label-gap: var(--auth-shell-field-label-gap);
345
340
  }
341
+ }
346
342
 
347
- .ui-auth-shell[data-variant="canonical"] [data-slot="form-field-label"] [data-slot="label"] {
348
- font-size: var(--auth-shell-field-label-font-size);
349
- line-height: var(--auth-shell-field-label-line-height);
350
- }
343
+ /* INTENTIONALLY UNLAYERED (like density.css) — the Label primitive carries `text-sm leading-none`
344
+ * Tailwind utility classes, and the utilities layer is declared after `components`, so a
345
+ * components-layer declaration of the same properties can NEVER win regardless of specificity.
346
+ * That left `--auth-shell-field-label-font-size` / `--auth-shell-field-label-line-height` dead and
347
+ * the canonical field label rendering as a 14px/1.0 line box instead of the canonical 12px/1.5 =
348
+ * 18px, sitting the email input 4px high (gh#263). Unlayered author CSS out-cascades every layer;
349
+ * the two tokens remain the public tuning knobs. */
350
+ .ui-auth-shell[data-variant="canonical"] [data-slot="form-field-label"] [data-slot="label"] {
351
+ font-size: var(--auth-shell-field-label-font-size);
352
+ line-height: var(--auth-shell-field-label-line-height);
353
+ }
351
354
 
355
+ @layer components {
352
356
  /*
353
357
  * Compact auth card density — the two card axes are wired to SEPARATE knobs (gh#232), so a
354
358
  * service can shorten the canonical Login card without narrowing its column (or vice versa):
@@ -384,7 +388,7 @@
384
388
  gap: var(--auth-shell-divider-gap);
385
389
  color: hsl(var(--auth-shell-divider-label-color));
386
390
  font-size: var(--auth-shell-divider-label-font-size);
387
- line-height: 1;
391
+ line-height: var(--auth-shell-divider-label-line-height);
388
392
  }
389
393
 
390
394
  .ui-auth-divider-rule {
@@ -423,8 +427,8 @@
423
427
 
424
428
  /* registration = 15px inline at 390 (card x=15, width=360 — the same page rhythm as Login, so
425
429
  * sign-in → sign-up never jumps on a phone). The block insets tighten but the footer clearance
426
- * stays deliberately larger than the block-start inset: at 390 the form is at its tallest and
427
- * the consent/legal footer must not sit flush against the submit button. */
430
+ * stays deliberately larger than a bare gutter: at 390 the form is at its tallest and the
431
+ * consent/legal footer must not sit flush against the submit button. */
428
432
  .ui-auth-shell[data-preset="registration"] {
429
433
  --auth-shell-main-padding: var(--auth-shell-registration-main-padding-block-start-mobile)
430
434
  var(--auth-shell-registration-main-padding-inline-mobile)
@@ -524,11 +528,7 @@
524
528
  }
525
529
 
526
530
  /* AppShell-owned mobile drawer trigger — hidden on desktop, shown once the docked sidebar
527
- * collapses out at 900px, THE ONE AppShell breakpoint (gh#165, gh#259). NOT Tailwind's `lg`
528
- * (1024px): this comment said "below lg" for six minors while the shipped rule below and the
529
- * hamburger's own `max-[900px]:inline-flex` both fired at 900 — see the block comment on the
530
- * `@media (width <= 56.25rem)` rule for why 900 is the canonical value. The drawer nav itself is
531
- * a focus-trapped Sheet. */
531
+ * collapses out below `lg` (gh#165). The drawer nav itself is a focus-trapped Sheet. */
532
532
  .app-mobile-nav-trigger {
533
533
  display: none;
534
534
  flex: 0 0 auto;
@@ -284,8 +284,23 @@
284
284
 
285
285
  .ui-table-collection :is([data-slot="table-head"], [data-slot="table-cell"]) {
286
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);
287
+ * which is the free-text column — EXCEPT past the seven-column budget below the collapse
288
+ * step, where the `:has()` tier re-points the middle fallback at the flex floor (gh#262):
289
+ * the fixed algorithm hands an auto column 0px once the marked columns over-constrain the
290
+ * table, which is the one-character-per-line shredding this preset must never produce.
291
+ * There every column carries a rem floor-measure; once the floors outgrow the container the
292
+ * table's used width grows past its specified 100% (CSS 2.1 §17.5.2.1) and the DIRECT
293
+ * wrapper's keyboard-reachable overflow-x region scrolls — the SC 1.4.10-permitted
294
+ * one-dimensional scroll, instead of unreadable shredding (see tokens/components/table.css). */
295
+ inline-size: var(
296
+ --table-action-collection-column-width,
297
+ var(--table-action-collection-flex-width, auto)
298
+ );
299
+ /* Wrapping guard, considered for gh#262: this stays `overflow-wrap: anywhere`, NOT
300
+ * `word-break: keep-all`. Under `table-layout: fixed` a column cannot grow to fit an
301
+ * unbreakable CJK run, so keep-all would overflow/clip a long JA header instead of widening
302
+ * its column. With the floors guaranteeing a ~5-glyph measure, a wrapped JA header is two
303
+ * readable lines — never a vertical character column — and losing no text beats clipping. */
289
304
  overflow-wrap: anywhere;
290
305
  white-space: normal;
291
306
  vertical-align: top;
@@ -331,6 +346,21 @@
331
346
  [data-collapse-below="sm"] [data-slot="table-cell"] * {
332
347
  white-space: normal;
333
348
  }
349
+
350
+ /* SEVEN columns and up — the wide-collection floor tier (gh#262). Percent shares are
351
+ * guaranteed to sum past 100% here and fixed layout would normalize them into sub-glyph
352
+ * columns, so every column (the unmarked free-text one included) takes its rem floor
353
+ * instead; the over-constrained table grows past 100% and its wrapper scrolls. Up to six
354
+ * columns this rule never matches and the percent tier above keeps the canonical queues
355
+ * scroll-free — the preset's documented column-count ceiling. */
356
+ [data-collapse-below="sm"] [data-slot="table"]:has([data-slot="table-head"]:nth-child(7)) {
357
+ --table-action-collection-primary-width: var(--table-action-collection-primary-width-floor);
358
+ --table-action-collection-secondary-width: var(
359
+ --table-action-collection-secondary-width-floor
360
+ );
361
+ --table-action-collection-meta-width: var(--table-action-collection-meta-width-floor);
362
+ --table-action-collection-flex-width: var(--table-action-collection-flex-width-floor);
363
+ }
334
364
  }
335
365
 
336
366
  @container ui-table-collection (width < 48rem) {
@@ -352,6 +382,21 @@
352
382
  [data-collapse-below="md"] [data-slot="table-cell"] * {
353
383
  white-space: normal;
354
384
  }
385
+
386
+ /* SEVEN columns and up — the wide-collection floor tier (gh#262). Percent shares are
387
+ * guaranteed to sum past 100% here and fixed layout would normalize them into sub-glyph
388
+ * columns, so every column (the unmarked free-text one included) takes its rem floor
389
+ * instead; the over-constrained table grows past 100% and its wrapper scrolls. Up to six
390
+ * columns this rule never matches and the percent tier above keeps the canonical queues
391
+ * scroll-free — the preset's documented column-count ceiling. */
392
+ [data-collapse-below="md"] [data-slot="table"]:has([data-slot="table-head"]:nth-child(7)) {
393
+ --table-action-collection-primary-width: var(--table-action-collection-primary-width-floor);
394
+ --table-action-collection-secondary-width: var(
395
+ --table-action-collection-secondary-width-floor
396
+ );
397
+ --table-action-collection-meta-width: var(--table-action-collection-meta-width-floor);
398
+ --table-action-collection-flex-width: var(--table-action-collection-flex-width-floor);
399
+ }
355
400
  }
356
401
 
357
402
  @container ui-table-collection (width < 64rem) {
@@ -373,6 +418,21 @@
373
418
  [data-collapse-below="lg"] [data-slot="table-cell"] * {
374
419
  white-space: normal;
375
420
  }
421
+
422
+ /* SEVEN columns and up — the wide-collection floor tier (gh#262). Percent shares are
423
+ * guaranteed to sum past 100% here and fixed layout would normalize them into sub-glyph
424
+ * columns, so every column (the unmarked free-text one included) takes its rem floor
425
+ * instead; the over-constrained table grows past 100% and its wrapper scrolls. Up to six
426
+ * columns this rule never matches and the percent tier above keeps the canonical queues
427
+ * scroll-free — the preset's documented column-count ceiling. */
428
+ [data-collapse-below="lg"] [data-slot="table"]:has([data-slot="table-head"]:nth-child(7)) {
429
+ --table-action-collection-primary-width: var(--table-action-collection-primary-width-floor);
430
+ --table-action-collection-secondary-width: var(
431
+ --table-action-collection-secondary-width-floor
432
+ );
433
+ --table-action-collection-meta-width: var(--table-action-collection-meta-width-floor);
434
+ --table-action-collection-flex-width: var(--table-action-collection-flex-width-floor);
435
+ }
376
436
  }
377
437
 
378
438
  @container ui-table-collection (width < 80rem) {
@@ -394,5 +454,20 @@
394
454
  [data-collapse-below="xl"] [data-slot="table-cell"] * {
395
455
  white-space: normal;
396
456
  }
457
+
458
+ /* SEVEN columns and up — the wide-collection floor tier (gh#262). Percent shares are
459
+ * guaranteed to sum past 100% here and fixed layout would normalize them into sub-glyph
460
+ * columns, so every column (the unmarked free-text one included) takes its rem floor
461
+ * instead; the over-constrained table grows past 100% and its wrapper scrolls. Up to six
462
+ * columns this rule never matches and the percent tier above keeps the canonical queues
463
+ * scroll-free — the preset's documented column-count ceiling. */
464
+ [data-collapse-below="xl"] [data-slot="table"]:has([data-slot="table-head"]:nth-child(7)) {
465
+ --table-action-collection-primary-width: var(--table-action-collection-primary-width-floor);
466
+ --table-action-collection-secondary-width: var(
467
+ --table-action-collection-secondary-width-floor
468
+ );
469
+ --table-action-collection-meta-width: var(--table-action-collection-meta-width-floor);
470
+ --table-action-collection-flex-width: var(--table-action-collection-flex-width-floor);
471
+ }
397
472
  }
398
473
  }
@@ -107,6 +107,19 @@
107
107
  * having to add `min-w-0` — the documented flex-truncate idiom (issue #114). */
108
108
  min-width: 0;
109
109
  }
110
+ /* Multi-line clamp (`clamp` prop, issue #261) — the token-owned line-clamp so consumers never
111
+ * write a page-local `line-clamp-N` utility. Visual-only: the full text stays in the DOM and
112
+ * the accessible name. `--text-clamp` is set inline by the component from the prop value. */
113
+ [data-slot="text"][data-clamp] {
114
+ display: -webkit-box;
115
+ -webkit-box-orient: vertical;
116
+ -webkit-line-clamp: var(--text-clamp, 2);
117
+ line-clamp: var(--text-clamp, 2);
118
+ overflow: hidden;
119
+ /* Same flex-shrink contract as truncate (issue #114): clamps inside a flex row without
120
+ * the consumer adding `min-w-0`. */
121
+ min-width: 0;
122
+ }
110
123
  [data-slot="text"][data-tabular] {
111
124
  font-variant-numeric: tabular-nums;
112
125
  }
@@ -13,6 +13,7 @@
13
13
  @import "./components/descriptions.css";
14
14
  @import "./components/table.css";
15
15
  @import "./components/feedback.css";
16
+ @import "./components/banner.css";
16
17
  @import "./components/sheet.css";
17
18
  @import "./components/badge.css";
18
19
  @import "./components/navigation.css";
@@ -7,4 +7,9 @@
7
7
  /* Small-by-design (badge/pill/counter). A knob (rule #45) so a service can
8
8
  * re-tune badge text without touching the global --font-size-xs step. */
9
9
  --badge-font-size: var(--font-size-xs);
10
+ /* Companion to --badge-font-size (gh#260). The cva's old `text-xs` utility also set
11
+ * line-height via Tailwind's default `--text-xs--line-height: calc(1 / 0.75)` (the theme
12
+ * remaps --text-xs but never that companion). Same unitless ratio here keeps the default
13
+ * badge pixel-identical now that badge-layout.css owns the type metrics. */
14
+ --badge-line-height: calc(1 / 0.75);
10
15
  }