@godxjp/ui 21.0.0 → 22.0.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 (103) hide show
  1. package/dist/app/app-provider.js +2 -1
  2. package/dist/components/data-display/badge.d.ts +28 -1
  3. package/dist/components/data-display/badge.js +2 -0
  4. package/dist/components/data-display/data-table.d.ts +18 -2
  5. package/dist/components/data-display/data-table.js +4 -0
  6. package/dist/components/data-display/index.d.ts +1 -1
  7. package/dist/components/data-display/popover.d.ts +19 -1
  8. package/dist/components/data-display/popover.js +9 -1
  9. package/dist/components/data-display/progress.d.ts +37 -0
  10. package/dist/components/data-display/progress.js +57 -5
  11. package/dist/components/data-entry/date-picker.d.ts +10 -3
  12. package/dist/components/data-entry/date-picker.js +503 -238
  13. package/dist/components/data-entry/form-field.d.ts +1 -1
  14. package/dist/components/data-entry/form-field.js +4 -1
  15. package/dist/components/data-entry/form.js +10 -1
  16. package/dist/components/data-entry/index.d.ts +0 -6
  17. package/dist/components/data-entry/index.js +0 -6
  18. package/dist/components/data-entry/label.d.ts +14 -1
  19. package/dist/components/data-entry/label.js +6 -1
  20. package/dist/components/data-entry/radio.d.ts +27 -6
  21. package/dist/components/data-entry/radio.js +88 -43
  22. package/dist/components/data-entry/search-select.js +8 -1
  23. package/dist/components/data-entry/select.js +2 -0
  24. package/dist/components/data-entry/switch.d.ts +6 -3
  25. package/dist/components/data-entry/switch.js +34 -11
  26. package/dist/components/layout/mobile-shell.d.ts +1 -1
  27. package/dist/components/layout/mobile-shell.js +2 -0
  28. package/dist/components/navigation/menubar.d.ts +10 -3
  29. package/dist/components/navigation/steps.d.ts +1 -1
  30. package/dist/components/navigation/steps.js +3 -1
  31. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  32. package/dist/components/navigation/tabs-scroll.js +52 -1
  33. package/dist/components/navigation/tabs.d.ts +2 -2
  34. package/dist/components/navigation/tabs.js +88 -23
  35. package/dist/components/ui/index.d.ts +0 -1
  36. package/dist/components/ui/index.js +0 -1
  37. package/dist/components/ui/segmented.d.ts +15 -1
  38. package/dist/components/ui/segmented.js +13 -6
  39. package/dist/i18n/messages/en.json +3 -2
  40. package/dist/i18n/messages/ja.json +3 -2
  41. package/dist/i18n/messages/vi.json +3 -2
  42. package/dist/lib/datetime/picker-format.d.ts +18 -0
  43. package/dist/lib/datetime/picker-format.js +35 -1
  44. package/dist/lib/field-a11y.d.ts +12 -0
  45. package/dist/lib/field-a11y.js +14 -1
  46. package/dist/props/components/data-display.prop.d.ts +7 -0
  47. package/dist/props/components/data-entry.prop.d.ts +180 -99
  48. package/dist/props/components/index.d.ts +1 -1
  49. package/dist/props/components/layout.prop.d.ts +12 -1
  50. package/dist/props/components/navigation.prop.d.ts +76 -1
  51. package/dist/props/registry.d.ts +16 -16
  52. package/dist/props/registry.js +16 -15
  53. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  54. package/dist/props/vocabulary/index.d.ts +2 -2
  55. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  56. package/dist/styles/badge-layout.css +4 -0
  57. package/dist/styles/card-layout.css +6 -6
  58. package/dist/styles/control.css +15 -6
  59. package/dist/styles/data-display-layout.css +76 -13
  60. package/dist/styles/focus-ring.css +4 -2
  61. package/dist/styles/navigation-layout.css +25 -0
  62. package/dist/styles/shell-layout.css +5 -0
  63. package/dist/styles/table-layout.css +34 -0
  64. package/dist/tokens/components/data-display.css +9 -0
  65. package/dist/tokens/components/navigation.css +6 -0
  66. package/dist/tokens/components/segmented.css +0 -2
  67. package/dist/tokens/components/shell.css +2 -0
  68. package/dist/tokens/components/table.css +3 -0
  69. package/dist/tokens/foundation.css +7 -0
  70. package/docs/COMPONENTS.md +14 -14
  71. package/docs/CONSUMER-RULES.md +24 -1
  72. package/docs/FRAME-COVERAGE-REPORT.md +3 -6
  73. package/docs/TOKENS.md +65 -0
  74. package/docs/data-display/badge.tsx +37 -0
  75. package/docs/data-display/data-table/index.tsx +18 -0
  76. package/docs/data-display/popover.tsx +41 -1
  77. package/docs/data-display/progress.tsx +28 -0
  78. package/docs/data-entry/calendar.tsx +2 -2
  79. package/docs/data-entry/date-picker.tsx +143 -3
  80. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  81. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  82. package/docs/data-entry/form.tsx +4 -4
  83. package/docs/data-entry/number-input.tsx +31 -0
  84. package/docs/data-entry/segmented.tsx +103 -1
  85. package/docs/layout/mobile-shell.tsx +5 -0
  86. package/docs/navigation/tabs.tsx +149 -0
  87. package/docs/roadmap/antd-parity.md +35 -34
  88. package/docs/roadmap/parity-audit-data-entry.md +178 -165
  89. package/docs/roadmap/parity-backlog.md +6 -7
  90. package/docs/showcase/acme-portal.tsx +7 -0
  91. package/package.json +11 -23
  92. package/scripts/ui-audit.mjs +17 -4
  93. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  94. package/dist/components/data-entry/date-range-picker.js +0 -349
  95. package/dist/components/data-entry/month-picker.d.ts +0 -10
  96. package/dist/components/data-entry/month-picker.js +0 -241
  97. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  98. package/dist/components/data-entry/month-range-picker.js +0 -301
  99. package/dist/components/ui/date-range-picker.d.ts +0 -1
  100. package/dist/components/ui/date-range-picker.js +0 -2
  101. package/docs/data-entry/date-range-picker.tsx +0 -175
  102. package/docs/data-entry/month-picker.tsx +0 -101
  103. package/docs/data-entry/month-range-picker.tsx +0 -115
@@ -1,7 +1,7 @@
1
1
  /** Barrel — all vocabulary prop types. */
2
2
  export type { ClassNameProp, ChildrenProp, IdProp, OpenProp, DefaultOpenProp, OnOpenChangeProp, HandlerProp, PendingProp, RequiredProp, DisabledProp, LabelProp, HelperProp, ErrorProp, ErrorBagProp, PlaceholderProp, NameProp, ValueProp, DefaultValueProp, OnValueChangeProp, OnChangeProp, OnClickProp, AsChildProp, FlushProp, WidthProp, ControlWidthProp, AllowClearProp, MaxTagCountProp, MaxTagPlaceholderProp, NotFoundContentProp, PopupMatchWidthProp, } from "./shared.prop.js";
3
3
  export type { TitleProp, SubtitleProp, StatusProp, DescriptionProp, ExtraProp, FooterProp, ActionProp, IconProp, ConfirmLabelProp, CancelLabelProp, ActionsProp, EmptyMessageProp, } from "./content.prop.js";
4
- export type { PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, MobileShellHeightProp, OrientationProp, TableDensityProp, DensityProp, GapNameProp, PadProp, PadRawProp, PadSides, GapProp, GapStepProp, } from "./layout.prop.js";
4
+ export type { PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, MobileShellHeightProp, MobileShellWidthProp, OrientationProp, TableDensityProp, DensityProp, GapNameProp, PadProp, PadRawProp, PadSides, GapProp, GapStepProp, } from "./layout.prop.js";
5
5
  export type { ButtonVariantProp, ButtonSizeProp, BadgeVariantProp, AppSettingPickerAppearanceProp, AppSettingToggleAppearanceProp, ShapeProp, AvatarShapeProp, TextSizeProp, TextToneProp, FontWeightProp, HeadingLevelProp, TextAlignProp, TextWhitespaceProp, SizeProp, ControlStatusProp, ControlVariantProp, FormLayoutProp, DescriptionsLayoutProp, BreakpointProp, ConfirmVariantProp, ToneProp, AlertVariantProp, SortDirectionProp, ColumnAlignProp, SortStateProp, RevealDelayProp, ActivityVariantProp, ActivityAnnounceProp, } from "./interaction.prop.js";
6
6
  export type { BreadcrumbItemProp, BreadcrumbProp, BreadcrumbItemMenuProp, BreadcrumbItemMenuEntryProp, BreadcrumbSeparatorProp, BreadcrumbItemRenderProp, } from "./navigation.prop.js";
7
- export type { GetRowIdProp, OnRowClickProp, ColumnDefProp, TablePresetProp, TableColumnPriorityProp, TableCellIndentProp, SelectedIdsProp, OnSelectChangeProp, OnTableDensityChangeProp, OnSortChangeProp, OnSearchChangeProp, OnClearFiltersProp, HasActiveFiltersProp, StickyProp, ColumnFixedProp, ColumnFilterValueProp, ColumnFilterItemProp, ColumnFilterStateProp, OnColumnFilterChangeProp, ColumnCompareProp, ColumnSorterProp, TableSelectionItemProp, TableRowSelectionProp, TableExpandableProp, TableSummaryProp, TableScrollProp, TableStickyProp, OnRowProp, TablePaginationProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, } from "./data.prop.js";
7
+ export type { GetRowIdProp, OnRowClickProp, ColumnDefProp, TablePresetProp, TableColumnPriorityProp, TableCellIndentProp, SelectedIdsProp, OnSelectChangeProp, OnTableDensityChangeProp, TableRowToneProp, RowToneProp, OnSortChangeProp, OnSearchChangeProp, OnClearFiltersProp, HasActiveFiltersProp, StickyProp, ColumnFixedProp, ColumnFilterValueProp, ColumnFilterItemProp, ColumnFilterStateProp, OnColumnFilterChangeProp, ColumnCompareProp, ColumnSorterProp, TableSelectionItemProp, TableRowSelectionProp, TableExpandableProp, TableSummaryProp, TableScrollProp, TableStickyProp, OnRowProp, TablePaginationProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, } from "./data.prop.js";
@@ -26,6 +26,17 @@ export type CenteredShellAlignProp = "start" | "center";
26
26
  * where a viewport-tall root would overflow its frame. Nothing else differs between the two.
27
27
  */
28
28
  export type MobileShellHeightProp = "viewport" | "fill";
29
+ /**
30
+ * MobileShell inline-size contract — the axis `height` had and `width` did not.
31
+ * `"fill"` (default, and what the shell has always done) takes the whole inline size it is given.
32
+ * `"phone"` caps it at the handheld measure (`--mobile-shell-max-inline-size`) and centres the
33
+ * column, for the case the shell's own docstring already names on the block axis: a handheld
34
+ * screen rendered on a viewport wider than a handheld — a device-frame preview, a desktop browser
35
+ * opening a mobile route, a phone view embedded in a wider page. Measured at a 1280px viewport
36
+ * before this existed: the shell drew 1232px wide with `max-inline-size: none`, so a 44px tab bar
37
+ * spread its four destinations across a metre of screen.
38
+ */
39
+ export type MobileShellWidthProp = "fill" | "phone";
29
40
  /**
30
41
  * ErrorSurface shell contract — WHERE the exception surface lives, not how it looks.
31
42
  * `"application"` (403 / 404): the failure happened INSIDE the authenticated app, so the surface
@@ -26,6 +26,10 @@
26
26
  }
27
27
  }
28
28
 
29
+ [data-slot="badge"][data-tabular] {
30
+ font-variant-numeric: tabular-nums;
31
+ }
32
+
29
33
  [data-slot="badge"][data-tinted] {
30
34
 
31
35
  background-color: color-mix(
@@ -44,27 +44,27 @@
44
44
  }
45
45
 
46
46
  [data-slot="card"][data-accent="primary"] {
47
- --card-accent-color: hsl(var(--primary));
47
+ --card-accent-color: hsl(var(--mark-primary));
48
48
  }
49
49
 
50
50
  [data-slot="card"][data-accent="success"] {
51
- --card-accent-color: hsl(var(--success));
51
+ --card-accent-color: hsl(var(--mark-success));
52
52
  }
53
53
 
54
54
  [data-slot="card"][data-accent="warning"] {
55
- --card-accent-color: hsl(var(--warning));
55
+ --card-accent-color: hsl(var(--mark-warning));
56
56
  }
57
57
 
58
58
  [data-slot="card"][data-accent="info"] {
59
- --card-accent-color: hsl(var(--info));
59
+ --card-accent-color: hsl(var(--mark-info));
60
60
  }
61
61
 
62
62
  [data-slot="card"][data-accent="attention"] {
63
- --card-accent-color: hsl(var(--attention));
63
+ --card-accent-color: hsl(var(--mark-attention));
64
64
  }
65
65
 
66
66
  [data-slot="card"][data-accent="destructive"] {
67
- --card-accent-color: hsl(var(--destructive));
67
+ --card-accent-color: hsl(var(--mark-destructive));
68
68
  }
69
69
 
70
70
  [data-slot="card"][data-accent] {
@@ -452,7 +452,7 @@
452
452
  fill: currentColor;
453
453
  }
454
454
 
455
- .ui-switch[aria-invalid="true"] {
455
+ .ui-switch:has(input[aria-invalid="true"]) {
456
456
  --tw-ring-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--destructive));
457
457
  box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--destructive));
458
458
  }
@@ -988,6 +988,8 @@
988
988
  }
989
989
 
990
990
  .ui-segmented {
991
+
992
+ --segmented-item-height: calc(var(--control-height) - var(--segmented-track-padding) * 2);
991
993
  display: inline-flex;
992
994
  max-width: 100%;
993
995
  align-items: stretch;
@@ -1222,14 +1224,15 @@
1222
1224
  font-size: var(--font-size-base);
1223
1225
  pointer-events: none;
1224
1226
  }
1225
- .ui-number-input-affix:first-of-type {
1227
+
1228
+ [data-slot="number-input-prefix"] {
1226
1229
  inset-inline-start: var(--control-padding-x);
1227
1230
  }
1228
1231
 
1229
- .ui-number-input:has(.ui-number-input-affix:first-of-type) .ui-number-input-field {
1232
+ .ui-number-input:has([data-slot="number-input-prefix"]) .ui-number-input-field {
1230
1233
  padding-inline-start: calc(var(--control-padding-x) + 1.25rem);
1231
1234
  }
1232
- .ui-number-input-affix:last-of-type {
1235
+ [data-slot="number-input-suffix"] {
1233
1236
  inset-inline-end: calc(var(--control-height) + var(--space-2));
1234
1237
  }
1235
1238
  .ui-number-input-steppers {
@@ -2407,6 +2410,11 @@
2407
2410
  gap: var(--month-picker-grid-space-gap);
2408
2411
  }
2409
2412
 
2413
+ .ui-month-picker-row,
2414
+ .ui-month-picker-gridcell {
2415
+ display: contents;
2416
+ }
2417
+
2410
2418
  .ui-month-picker-cell {
2411
2419
  padding-inline: var(--month-picker-cell-space-inline);
2412
2420
  font-weight: var(--font-weight-normal);
@@ -2782,7 +2790,7 @@
2782
2790
  border-color: hsl(var(--choice-button-solid-background));
2783
2791
  }
2784
2792
 
2785
- .ui-radio-button:disabled {
2793
+ .ui-radio-button[data-disabled] {
2786
2794
  cursor: not-allowed;
2787
2795
  opacity: var(--disabled-opacity);
2788
2796
  }
@@ -2792,7 +2800,7 @@
2792
2800
  inline-size: 100%;
2793
2801
  }
2794
2802
 
2795
- .ui-segmented[data-block="true"] > .ui-segmented-item {
2803
+ .ui-segmented[data-block="true"]:not([data-orientation="vertical"]) > .ui-segmented-item {
2796
2804
  flex: 1 1 0;
2797
2805
  min-inline-size: 0;
2798
2806
  justify-content: center;
@@ -2803,6 +2811,7 @@
2803
2811
  }
2804
2812
 
2805
2813
  .ui-segmented[data-orientation="vertical"] > .ui-segmented-item {
2814
+ block-size: var(--control-height);
2806
2815
  justify-content: flex-start;
2807
2816
  }
2808
2817
 
@@ -143,7 +143,7 @@
143
143
  }
144
144
 
145
145
  .ui-progress-track {
146
- height: 0.5rem;
146
+ height: var(--progress-meter-block-size);
147
147
  overflow: hidden;
148
148
  border-radius: var(--radius-pill);
149
149
  background: var(--progress-track-background, hsl(var(--secondary)));
@@ -152,19 +152,19 @@
152
152
  .ui-progress-bar {
153
153
  height: 100%;
154
154
  border-radius: inherit;
155
- background: var(--progress-fill-background, hsl(var(--success)));
155
+ background: var(--progress-fill-background, hsl(var(--mark-success)));
156
156
  }
157
157
 
158
158
  .ui-progress[data-tone="warning"] .ui-progress-bar {
159
- background: hsl(var(--warning));
159
+ background: hsl(var(--mark-warning));
160
160
  }
161
161
 
162
162
  .ui-progress[data-tone="destructive"] .ui-progress-bar {
163
- background: hsl(var(--destructive));
163
+ background: hsl(var(--mark-destructive));
164
164
  }
165
165
 
166
166
  .ui-progress[data-over] .ui-progress-bar {
167
- background-color: hsl(var(--destructive));
167
+ background-color: hsl(var(--mark-destructive));
168
168
  background-image: repeating-linear-gradient(
169
169
  45deg,
170
170
  hsl(var(--destructive-foreground) / 0.4) 0,
@@ -180,20 +180,28 @@
180
180
  border-radius: var(--progress-breakdown-radius);
181
181
  }
182
182
 
183
+ .ui-progress[data-size="sm"] .ui-progress-track {
184
+ height: var(--progress-meter-block-size-sm);
185
+ }
186
+
187
+ .ui-progress[data-breakdown][data-size="sm"] .ui-progress-track {
188
+ height: var(--progress-breakdown-block-size-sm);
189
+ }
190
+
183
191
  .ui-progress-segment {
184
192
  height: 100%;
185
193
  }
186
194
 
187
195
  .ui-progress-segment[data-tone="success"] {
188
- background: hsl(var(--success));
196
+ background: hsl(var(--mark-success));
189
197
  }
190
198
 
191
199
  .ui-progress-segment[data-tone="warning"] {
192
- background: hsl(var(--warning));
200
+ background: hsl(var(--mark-warning));
193
201
  }
194
202
 
195
203
  .ui-progress-segment[data-tone="destructive"] {
196
- background: hsl(var(--destructive));
204
+ background: hsl(var(--mark-destructive));
197
205
  }
198
206
 
199
207
  .ui-progress-label {
@@ -201,6 +209,61 @@
201
209
  font-size: var(--progress-label-font-size);
202
210
  }
203
211
 
212
+ .ui-progress[data-shape="ring"] {
213
+ display: inline-flex;
214
+ }
215
+
216
+ .ui-progress-ring {
217
+ position: relative;
218
+ display: inline-grid;
219
+ inline-size: var(--progress-ring-size);
220
+ block-size: var(--progress-ring-size);
221
+ place-items: center;
222
+ }
223
+
224
+ .ui-progress[data-size="sm"] .ui-progress-ring {
225
+ --progress-ring-size: var(--progress-ring-size-sm);
226
+ }
227
+
228
+ .ui-progress-ring-svg {
229
+ position: absolute;
230
+ inset: 0;
231
+ inline-size: 100%;
232
+ block-size: 100%;
233
+ }
234
+
235
+ .ui-progress-ring-track,
236
+ .ui-progress-ring-indicator {
237
+ fill: none;
238
+ stroke-width: var(--progress-ring-border-width);
239
+ }
240
+
241
+ .ui-progress-ring-track {
242
+ stroke: var(--progress-track-background, hsl(var(--secondary)));
243
+ }
244
+
245
+ .ui-progress-ring-indicator {
246
+ stroke: var(--progress-fill-background, hsl(var(--mark-success)));
247
+
248
+ stroke-linecap: butt;
249
+ }
250
+
251
+ .ui-progress[data-tone="warning"] .ui-progress-ring-indicator {
252
+ stroke: hsl(var(--mark-warning));
253
+ }
254
+
255
+ .ui-progress[data-tone="destructive"] .ui-progress-ring-indicator {
256
+ stroke: hsl(var(--mark-destructive));
257
+ }
258
+
259
+ .ui-progress-ring-label {
260
+ position: relative;
261
+ color: hsl(var(--foreground));
262
+ font-size: var(--progress-ring-label-font-size);
263
+ font-variant-numeric: tabular-nums;
264
+ line-height: 1;
265
+ }
266
+
204
267
  .ui-legend {
205
268
  display: flex;
206
269
  flex-wrap: wrap;
@@ -227,23 +290,23 @@
227
290
  }
228
291
 
229
292
  .ui-legend-swatch[data-tone="default"] {
230
- background: hsl(var(--primary));
293
+ background: hsl(var(--mark-primary));
231
294
  }
232
295
 
233
296
  .ui-legend-swatch[data-tone="success"] {
234
- background: hsl(var(--success));
297
+ background: hsl(var(--mark-success));
235
298
  }
236
299
 
237
300
  .ui-legend-swatch[data-tone="warning"] {
238
- background: hsl(var(--warning));
301
+ background: hsl(var(--mark-warning));
239
302
  }
240
303
 
241
304
  .ui-legend-swatch[data-tone="destructive"] {
242
- background: hsl(var(--destructive));
305
+ background: hsl(var(--mark-destructive));
243
306
  }
244
307
 
245
308
  .ui-legend-swatch[data-tone="info"] {
246
- background: hsl(var(--info));
309
+ background: hsl(var(--mark-info));
247
310
  }
248
311
 
249
312
  .ui-legend-swatch[data-tone="muted"],
@@ -14,7 +14,6 @@
14
14
  .ui-checkbox,
15
15
  .ui-radio,
16
16
  .ui-slider-thumb,
17
- .ui-switch,
18
17
  .ui-steps-inline-control,
19
18
  .ui-pagination-link,
20
19
  .ui-legal-document-toc-link,
@@ -30,6 +29,8 @@
30
29
  ):focus-visible,
31
30
 
32
31
  .ui-checkbox[data-focus-visible],
32
+ .ui-switch[data-focus-visible],
33
+ .ui-focus-ring[data-focus-visible],
33
34
  .ui-control-trigger[data-state="open"],
34
35
  .ui-tag-input:focus-within,
35
36
  .ui-control-composite-field:focus-within,
@@ -59,7 +60,6 @@
59
60
  .ui-checkbox,
60
61
  .ui-radio,
61
62
  .ui-slider-thumb,
62
- .ui-switch,
63
63
  .ui-steps-inline-control,
64
64
  .ui-pagination-link,
65
65
  .ui-legal-document-toc-link,
@@ -75,6 +75,8 @@
75
75
  ):focus-visible,
76
76
 
77
77
  :root[data-focus-outline="on"] .ui-checkbox[data-focus-visible],
78
+ :root[data-focus-outline="on"] .ui-switch[data-focus-visible],
79
+ :root[data-focus-outline="on"] .ui-focus-ring[data-focus-visible],
78
80
  :root[data-focus-outline="on"] .ui-control-trigger[data-state="open"],
79
81
  :root[data-focus-outline="on"] .ui-tag-input:focus-within,
80
82
  :root[data-focus-outline="on"] .ui-control-composite-field:focus-within,
@@ -96,6 +96,10 @@
96
96
 
97
97
  .ui-tabs-bar > [data-slot="tabs-list"] {
98
98
  min-inline-size: 0;
99
+ flex: 0 1 auto;
100
+ }
101
+
102
+ [data-slot="tabs"]:not([data-orientation="vertical"]) .ui-tabs-bar > [data-slot="tabs-list"] {
99
103
  flex: 1 1 auto;
100
104
  }
101
105
 
@@ -146,6 +150,27 @@
146
150
  block-size: var(--tabs-tab-remove-icon-size);
147
151
  }
148
152
 
153
+ .ui-tabs-overflow {
154
+ display: inline-flex;
155
+ flex: 0 0 auto;
156
+ inline-size: var(--tabs-overflow-size);
157
+ block-size: var(--tabs-overflow-size);
158
+ align-items: center;
159
+ justify-content: center;
160
+ border-radius: var(--tabs-overflow-radius);
161
+ color: hsl(var(--muted-foreground));
162
+ }
163
+
164
+ .ui-tabs-overflow:hover {
165
+ background: hsl(var(--accent));
166
+ color: hsl(var(--accent-foreground));
167
+ }
168
+
169
+ .ui-tabs-overflow-icon {
170
+ inline-size: var(--tabs-overflow-icon-size);
171
+ block-size: var(--tabs-overflow-icon-size);
172
+ }
173
+
149
174
  .ui-tabs-add {
150
175
  display: inline-flex;
151
176
  flex: 0 0 auto;
@@ -2332,6 +2332,11 @@
2332
2332
  --mobile-shell-block-size: 100%;
2333
2333
  }
2334
2334
 
2335
+ .ui-mobile-shell[data-width="phone"] {
2336
+ max-inline-size: var(--mobile-shell-max-inline-size);
2337
+ margin-inline: auto;
2338
+ }
2339
+
2335
2340
  .ui-mobile-shell-chrome {
2336
2341
  display: flex;
2337
2342
  min-inline-size: 0;
@@ -15,6 +15,40 @@
15
15
  border-block-end-width: 0;
16
16
  }
17
17
 
18
+ .ui-table-row[data-tone="primary"] {
19
+ --table-row-tone-color: var(--mark-primary);
20
+ }
21
+
22
+ .ui-table-row[data-tone="success"] {
23
+ --table-row-tone-color: var(--mark-success);
24
+ }
25
+
26
+ .ui-table-row[data-tone="warning"] {
27
+ --table-row-tone-color: var(--mark-warning);
28
+ }
29
+
30
+ .ui-table-row[data-tone="info"] {
31
+ --table-row-tone-color: var(--mark-info);
32
+ }
33
+
34
+ .ui-table-row[data-tone="attention"] {
35
+ --table-row-tone-color: var(--mark-attention);
36
+ }
37
+
38
+ .ui-table-row[data-tone="destructive"] {
39
+ --table-row-tone-color: var(--mark-destructive);
40
+ }
41
+
42
+ .ui-table-row[data-tone] {
43
+ background: hsl(var(--table-row-tone-color) / var(--table-row-tone-wash-alpha));
44
+ }
45
+
46
+ .ui-table-row[data-tone] > [data-slot="table-cell"]:first-child {
47
+ border-inline-start: var(--table-row-tone-rail-width) solid
48
+ hsl(var(--table-row-tone-color));
49
+ padding-inline-start: calc(var(--table-cell-space-x) - var(--table-row-tone-rail-width));
50
+ }
51
+
18
52
  [data-slot="table-head"] {
19
53
  height: var(--table-row-height);
20
54
  padding: var(--table-cell-padding-y) var(--table-cell-space-x);
@@ -65,6 +65,15 @@
65
65
  --progress-breakdown-block-size: 1.375rem;
66
66
  --progress-breakdown-radius: var(--radius);
67
67
 
68
+ --progress-meter-block-size: 0.5rem;
69
+ --progress-meter-block-size-sm: 0.25rem;
70
+ --progress-breakdown-block-size-sm: 0.5rem;
71
+
72
+ --progress-ring-size: 2.75rem;
73
+ --progress-ring-size-sm: 2rem;
74
+ --progress-ring-border-width: var(--stroke-lg);
75
+ --progress-ring-label-font-size: var(--font-size-xs);
76
+
68
77
  --legend-gap: 0.875rem;
69
78
  --legend-item-gap: 0.3125rem;
70
79
  --legend-swatch-size: 0.625rem;
@@ -49,6 +49,8 @@
49
49
 
50
50
  --tabs-list-line-space-inset: 0px;
51
51
 
52
+ --tabs-list-line-space-gap: 0.25rem;
53
+
52
54
  --tabs-list-focus-ring-space-inset: initial;
53
55
 
54
56
  --tabs-trigger-line-radius: 0px;
@@ -74,6 +76,10 @@
74
76
 
75
77
  --tabs-bar-gap: var(--space-2);
76
78
  --tabs-extra-gap: var(--space-2);
79
+
80
+ --tabs-overflow-size: var(--band-height-md);
81
+ --tabs-overflow-radius: var(--radius-md);
82
+ --tabs-overflow-icon-size: var(--control-icon-size);
77
83
  --tabs-add-size: var(--band-height-md);
78
84
  --tabs-add-radius: var(--radius-md);
79
85
  --tabs-add-icon-size: var(--control-icon-size);
@@ -9,8 +9,6 @@
9
9
  --segmented-track-radius: var(--radius);
10
10
  --segmented-item-radius: var(--radius-md);
11
11
 
12
- --segmented-item-height: calc(var(--control-height) - var(--segmented-track-padding) * 2);
13
-
14
12
  --segmented-item-padding-inline: calc(var(--control-padding-x) - 1px);
15
13
 
16
14
  --segmented-item-gap: calc(var(--space-3) / 2);
@@ -355,6 +355,8 @@
355
355
 
356
356
  :root {
357
357
  --mobile-shell-block-size: 100dvh;
358
+
359
+ --mobile-shell-max-inline-size: 26.875rem;
358
360
  --mobile-shell-safe-inset-block-start: env(safe-area-inset-top);
359
361
  --mobile-shell-safe-inset-block-end: env(safe-area-inset-bottom);
360
362
  --mobile-shell-safe-inset-inline: max(env(safe-area-inset-left), env(safe-area-inset-right));
@@ -91,4 +91,7 @@
91
91
  --table-row-border-width: var(--stroke-hairline);
92
92
 
93
93
  --table-flush-divider-width: var(--table-row-border-width);
94
+
95
+ --table-row-tone-rail-width: var(--stroke-2xl);
96
+ --table-row-tone-wash-alpha: 0.06;
94
97
  }
@@ -47,6 +47,13 @@
47
47
  --text-info: 221 70% 38%;
48
48
  --text-error: 357 75% 38%;
49
49
 
50
+ --mark-primary: var(--text-primary);
51
+ --mark-success: var(--text-success);
52
+ --mark-warning: var(--text-warning);
53
+ --mark-info: var(--text-info);
54
+ --mark-destructive: var(--text-error);
55
+ --mark-attention: var(--attention);
56
+
50
57
  --secondary-hover: 45 15% 91%;
51
58
  --secondary-active: 45 15% 88%;
52
59
 
@@ -48,8 +48,8 @@ All UI is **mobile-first**: base layout targets ~320–428px viewport; `sm` (640
48
48
 
49
49
  Default `direction="row"`; use `direction="col"` for vertical rhythm.
50
50
 
51
- | Component | Prop | Key props |
52
- | --------- | ---------- | ---------------------------------------------------------------------------------------------------- |
51
+ | Component | Prop | Key props |
52
+ | --------- | ---------- | ------------------------------------------------------------------------------------------------------------------- |
53
53
  | `Flex` | `FlexProp` | `direction: "row" \| "col"`, `gap: GapProp`, `gapRaw`, `pad`, `padRaw`, `fill`, `width`, `align`, `justify`, `wrap` |
54
54
 
55
55
  `fill` / `width` are the `fixed | elastic | fixed` row: `width` pins a column (number = px, and
@@ -82,25 +82,25 @@ and sets `min-inline-size: 0` so a truncating child ellipses instead of widening
82
82
  | `Textarea` | `TextareaProp` | extends native textarea |
83
83
  | `FormField` | `FormFieldProp` | `id`, `label`, `required`, `helper`, `error` |
84
84
  | `SearchInput` | `SearchInputProp` | `onSearchChange: OnSearchChangeProp` |
85
- | `Checkbox` | `CheckboxProp` | Radix checkbox; `Checkbox.Group` với `options` |
85
+ | `Checkbox` | `CheckboxProp` | Checkbox (RAC); `Checkbox.Group` với `options` |
86
86
  | `Radio` | `RadioGroupProp` | `Radio.Group` single-select; `Radio.Root` compound |
87
87
 
88
88
  ---
89
89
 
90
90
  ## Data Display
91
91
 
92
- | Component | Prop type | Key props |
93
- | -------------- | ------------------ | ---------------------------------------- |
94
- | `EmptyState` | `EmptyStateProp` | `icon`, `title`, `description`, `action` |
95
- | `DataTable` | `DataTableProp<T>` | `columns`, `density: TableDensityProp` |
96
- | `Descriptions` | `DescriptionsProp` | `items[]` |
97
- | `Badge` | `BadgeProp` | `status`, `tone: ToneProp` |
98
- | `Badge` | `BadgeProp` | shadcn badge variants |
92
+ | Component | Prop type | Key props |
93
+ | -------------- | ------------------ | ----------------------------------------- |
94
+ | `EmptyState` | `EmptyStateProp` | `icon`, `title`, `description`, `action` |
95
+ | `DataTable` | `DataTableProp<T>` | `columns`, `density: TableDensityProp` |
96
+ | `Descriptions` | `DescriptionsProp` | `items[]` |
97
+ | `Badge` | `BadgeProp` | `status`, `tone: ToneProp` |
98
+ | `Badge` | `BadgeProp` | shadcn badge variants |
99
99
  | `Progress` | `ProgressProp` | `value` (meter) \| `segments` (breakdown) |
100
- | `Legend` | `LegendProp` | `items[]` — the key for a tone |
101
- | `Popover` | Radix primitives | floating content panel |
102
- | `ScrollArea` | Radix primitives | scrollable regions |
103
- | `Collapsible` | Radix primitives | expand/collapse sections |
100
+ | `Legend` | `LegendProp` | `items[]` — the key for a tone |
101
+ | `Popover` | Radix primitives | floating content panel |
102
+ | `ScrollArea` | Radix primitives | scrollable regions |
103
+ | `Collapsible` | Radix primitives | expand/collapse sections |
104
104
 
105
105
  ---
106
106
 
@@ -6,7 +6,7 @@ Read this once; the audit enforces it. Everything else in `docs/` is for contrib
6
6
  2. Every page is `<PageContainer title subtitle extra footer>`; its sections are spaced by the page. Group items inside a section with `<Flex direction="col" gap>` or `<ResponsiveGrid>`.
7
7
  3. No Tailwind layout on your own elements: no `flex`, `grid`, `gap-*`, `p-*`, `m-*`, `space-*`. Rows are `<Flex>` (default row), stacks are `<Flex direction="col">`, grids are `<ResponsiveGrid>`.
8
8
  4. No hand-rolled surfaces: no `rounded-* border bg-*` divs. A box is `Card`, a pill is `Badge`, a person is `Avatar`, a row is `ListRow`, a label/value pair is `Descriptions`, an empty area is `EmptyState`.
9
- 5. Real controls only: `Button`, `Input`, `Select`, `Textarea`, `Checkbox`… never raw `<button>`/`<input>`; a labelled control lives in `<FormField label>`. A Select outside a form takes `width="auto"`.
9
+ 5. Real controls only: `Button`, `Input`, `Select`, `Textarea`, `Checkbox`… never raw `<button>`/`<input>`; a labelled control lives in `<FormField label>`. A Select outside a form takes `width="auto"`. **A disabled control's reason is visible text, never a tooltip** — see below.
10
10
  6. Text is `<Text>` / `<Heading>` with `tone`, `size`, `weight`, `truncate`, `mono` — not `className="text-muted-foreground font-semibold"`.
11
11
  7. Colours are semantic tokens (`tone="destructive"`, `bg-primary`), never palette names, hex, or `bg-black` / `text-white`.
12
12
  8. Sizes come from props (`size`, `width`, `columns`), never `w-[240px]` / `max-h-[420px]`.
@@ -25,3 +25,26 @@ const legacyClasses = ["gap-3", "p-2"];
25
25
  ```
26
26
 
27
27
  A block with no reason (or under 12 characters of it) is ignored and the finding stands; an unclosed block runs to the end of the file. The class-shaped rules (`gap-*`, `bg-red-500`, `w-[37px]`, `pr-*`, `dark:*`) only read class expressions — a `className`/`class` attribute, a class-named binding (`baseClass`, `statusStyles`, `badgeVariants`) or a `cn()`/`clsx()`/`cva()` call — so product copy or an i18n value that happens to spell a utility is never a finding.
28
+
29
+ ---
30
+
31
+ ## Why a disabled control's reason cannot be a tooltip
32
+
33
+ A disabled `<button>` receives no pointer events and is not in the tab order, so an explanation
34
+ attached to it never reaches anyone. Measured in Chromium on this package's own tooltip page: the
35
+ same trigger produced **1** tooltip on hover while enabled and **0** while disabled, and calling
36
+ `focus()` on it left `document.activeElement` as `BODY` — so keyboard and touch get nothing either.
37
+ Hover-only would fail a touch user in any case.
38
+
39
+ This is not a gap waiting for a DS prop. The reason is required information, and required
40
+ information belongs on the screen:
41
+
42
+ - Render it as a `<Text size="xs" tone="muted">` line **before** the control, so a screen-reader or
43
+ keyboard user meets the explanation ahead of the thing they cannot use (WCAG 2.2 · 1.3.2,
44
+ meaningful sequence).
45
+ - `ServiceLauncherCard` already has this shape as a prop — `disabledReason`, rendered above the
46
+ action — and it is the pattern to copy anywhere else.
47
+
48
+ If the control is disabled *pending something the user can fix*, prefer leaving it enabled and
49
+ explaining on submit: an inert control with a paragraph beside it is often a validation message
50
+ wearing a disguise.
@@ -3,8 +3,8 @@
3
3
  > Generated by `scripts/frame-coverage.mjs` (issue #163). Do not edit by hand — run `pnpm check:frame-coverage`.
4
4
  > Standard: [FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md). A missing dimension is **UNTESTED**, never a pass.
5
5
 
6
- - Public components: **141**
7
- - With ≥1 frame: **136** · zero-frame: **5**
6
+ - Public components: **138**
7
+ - With ≥1 frame: **133** · zero-frame: **5**
8
8
  - Fully covered (every contract axis declared): **0**
9
9
 
10
10
  ## UNTESTED — no `/frame/**` route
@@ -60,7 +60,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
60
60
  | QrCode | ✓ | ✓ | N/A | · | · | · | · | · | · | · |
61
61
  | Descriptions | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
62
62
  | EmptyState | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
63
- | Progress | ✓ | | N/A | · | · | · | · | · | · | · |
63
+ | Progress | ✓ | · | N/A | · | · | · | · | · | · | · |
64
64
  | CodeBlock | ✓ | · | N/A | N/A | · | · | · | · | · | · |
65
65
  | Prose | ✓ | · | N/A | N/A | · | · | · | · | · | · |
66
66
  | Timeline | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
@@ -80,8 +80,6 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
80
80
  | Label | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
81
81
  | Checkbox | ✓ | N/A | · | · | · | · | · | · | · | · |
82
82
  | RadioGroup | ✓ | N/A | · | · | · | · | · | · | · | · |
83
- | MonthPicker | ✓ | · | · | · | · | · | · | · | · | · |
84
- | MonthRangePicker | ✓ | · | · | · | · | · | · | · | · | · |
85
83
  | DatePicker | ✓ | · | · | · | · | · | · | · | · | · |
86
84
  | Dialog | ✓ | N/A | N/A | · | · | · | · | · | · | · |
87
85
  | AlertDialog | ✓ | ✓ | · | · | · | · | · | · | · | · |
@@ -99,7 +97,6 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
99
97
  | AppProvider | ✓ | · | · | · | · | · | · | · | · | · |
100
98
  | formatDate | ✓ | · | · | · | · | · | · | · | · | · |
101
99
  | TimePicker | ✓ | · | · | · | · | · | · | · | · | · |
102
- | DateRangePicker | ✓ | · | · | · | · | · | · | · | · | · |
103
100
  | Cascader | ✓ | ✓ | · | · | · | · | · | · | · | · |
104
101
  | TreeSelect | ✓ | ✓ | · | · | · | · | · | · | · | · |
105
102
  | Transfer | ✓ | N/A | · | · | · | · | · | · | · | · |