@godxjp/ui 21.0.0 → 23.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 (154) hide show
  1. package/README.md +4 -4
  2. package/dist/app/app-provider.js +2 -1
  3. package/dist/components/data-display/badge.d.ts +28 -1
  4. package/dist/components/data-display/badge.js +2 -0
  5. package/dist/components/data-display/data-table.d.ts +18 -2
  6. package/dist/components/data-display/data-table.js +4 -0
  7. package/dist/components/data-display/index.d.ts +1 -1
  8. package/dist/components/data-display/popover.d.ts +19 -1
  9. package/dist/components/data-display/popover.js +9 -1
  10. package/dist/components/data-display/progress.d.ts +37 -0
  11. package/dist/components/data-display/progress.js +57 -5
  12. package/dist/components/data-display/scroll-area.d.ts +24 -4
  13. package/dist/components/data-display/scroll-area.js +34 -72
  14. package/dist/components/data-entry/cascader.js +8 -6
  15. package/dist/components/data-entry/checkbox.js +2 -0
  16. package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
  17. package/dist/components/data-entry/choice-hit-target.js +12 -0
  18. package/dist/components/data-entry/control-surface.d.ts +8 -0
  19. package/dist/components/data-entry/control-surface.js +10 -1
  20. package/dist/components/data-entry/date-picker.d.ts +10 -3
  21. package/dist/components/data-entry/date-picker.js +503 -238
  22. package/dist/components/data-entry/form-field.d.ts +1 -1
  23. package/dist/components/data-entry/form-field.js +7 -3
  24. package/dist/components/data-entry/form.js +10 -1
  25. package/dist/components/data-entry/index.d.ts +0 -6
  26. package/dist/components/data-entry/index.js +0 -6
  27. package/dist/components/data-entry/label.d.ts +14 -1
  28. package/dist/components/data-entry/label.js +6 -1
  29. package/dist/components/data-entry/radio.d.ts +27 -6
  30. package/dist/components/data-entry/radio.js +89 -43
  31. package/dist/components/data-entry/search-select.js +226 -89
  32. package/dist/components/data-entry/select.d.ts +76 -14
  33. package/dist/components/data-entry/select.js +497 -163
  34. package/dist/components/data-entry/slider.d.ts +3 -11
  35. package/dist/components/data-entry/slider.js +582 -93
  36. package/dist/components/data-entry/switch.d.ts +6 -3
  37. package/dist/components/data-entry/switch.js +36 -11
  38. package/dist/components/layout/mobile-shell.d.ts +1 -1
  39. package/dist/components/layout/mobile-shell.js +2 -0
  40. package/dist/components/navigation/dropdown-menu.d.ts +11 -3
  41. package/dist/components/navigation/dropdown-menu.js +190 -15
  42. package/dist/components/navigation/index.d.ts +0 -3
  43. package/dist/components/navigation/index.js +0 -78
  44. package/dist/components/navigation/steps.d.ts +1 -1
  45. package/dist/components/navigation/steps.js +3 -1
  46. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  47. package/dist/components/navigation/tabs-scroll.js +52 -1
  48. package/dist/components/navigation/tabs.d.ts +2 -2
  49. package/dist/components/navigation/tabs.js +88 -23
  50. package/dist/components/ui/index.d.ts +0 -4
  51. package/dist/components/ui/index.js +0 -4
  52. package/dist/components/ui/input-otp.d.ts +1 -1
  53. package/dist/components/ui/segmented.d.ts +15 -1
  54. package/dist/components/ui/segmented.js +15 -6
  55. package/dist/components/ui/tag-input.js +3 -5
  56. package/dist/i18n/messages/en.json +3 -2
  57. package/dist/i18n/messages/ja.json +3 -2
  58. package/dist/i18n/messages/vi.json +3 -2
  59. package/dist/lib/datetime/picker-format.d.ts +18 -0
  60. package/dist/lib/datetime/picker-format.js +35 -1
  61. package/dist/lib/field-a11y.d.ts +12 -0
  62. package/dist/lib/field-a11y.js +14 -1
  63. package/dist/lib/select-options.d.ts +21 -0
  64. package/dist/lib/select-options.js +46 -0
  65. package/dist/props/components/data-display.prop.d.ts +16 -6
  66. package/dist/props/components/data-entry.prop.d.ts +503 -129
  67. package/dist/props/components/index.d.ts +2 -2
  68. package/dist/props/components/layout.prop.d.ts +12 -1
  69. package/dist/props/components/navigation.prop.d.ts +93 -1
  70. package/dist/props/registry.d.ts +79 -16
  71. package/dist/props/registry.js +85 -15
  72. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  73. package/dist/props/vocabulary/index.d.ts +2 -2
  74. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  75. package/dist/styles/badge-layout.css +4 -0
  76. package/dist/styles/card-layout.css +6 -6
  77. package/dist/styles/control.css +220 -29
  78. package/dist/styles/data-display-layout.css +94 -23
  79. package/dist/styles/focus-ring.css +6 -4
  80. package/dist/styles/fonts.css +88 -4
  81. package/dist/styles/navigation-layout.css +36 -236
  82. package/dist/styles/shell-layout.css +149 -139
  83. package/dist/styles/table-layout.css +41 -3
  84. package/dist/tokens/components/control.css +4 -0
  85. package/dist/tokens/components/data-display.css +9 -5
  86. package/dist/tokens/components/navigation.css +9 -5
  87. package/dist/tokens/components/scroll-area.css +7 -0
  88. package/dist/tokens/components/segmented.css +0 -2
  89. package/dist/tokens/components/shell.css +2 -0
  90. package/dist/tokens/components/table.css +3 -0
  91. package/dist/tokens/foundation.css +7 -0
  92. package/docs/COMPONENTS.md +14 -14
  93. package/docs/CONSUMER-RULES.md +24 -1
  94. package/docs/CUSTOMER-THEMING.md +7 -1
  95. package/docs/FRAME-COVERAGE-REPORT.md +5 -11
  96. package/docs/TOKENS.md +65 -0
  97. package/docs/data-display/badge.tsx +37 -0
  98. package/docs/data-display/data-table/index.tsx +45 -1
  99. package/docs/data-display/popover.tsx +43 -5
  100. package/docs/data-display/progress.tsx +28 -0
  101. package/docs/data-display/scroll-area.tsx +51 -45
  102. package/docs/data-entry/calendar.tsx +2 -2
  103. package/docs/data-entry/date-picker.tsx +144 -5
  104. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  105. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  106. package/docs/data-entry/form.tsx +4 -4
  107. package/docs/data-entry/input-otp.tsx +5 -2
  108. package/docs/data-entry/number-input.tsx +31 -0
  109. package/docs/data-entry/segmented.tsx +143 -2
  110. package/docs/data-entry/select-matrix.tsx +1 -2
  111. package/docs/data-entry/select.tsx +108 -3
  112. package/docs/data-entry/slider.tsx +125 -47
  113. package/docs/feedback/dialog.tsx +1 -2
  114. package/docs/feedback/sheet.tsx +1 -2
  115. package/docs/layout/app-shell-states.tsx +143 -0
  116. package/docs/layout/mobile-shell.tsx +5 -0
  117. package/docs/navigation/dropdown-menu.tsx +75 -4
  118. package/docs/navigation/tabs.tsx +149 -0
  119. package/docs/roadmap/antd-parity.md +35 -34
  120. package/docs/roadmap/parity-audit-data-entry.md +196 -172
  121. package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
  122. package/docs/roadmap/parity-backlog.md +8 -10
  123. package/docs/showcase/acme-portal.tsx +7 -0
  124. package/package.json +28 -64
  125. package/scripts/ui-audit.mjs +18 -5
  126. package/scripts/visual-audit-rules.mjs +0 -7
  127. package/scripts/visual-audit.mjs +6 -27
  128. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  129. package/dist/components/data-entry/date-range-picker.js +0 -349
  130. package/dist/components/data-entry/month-picker.d.ts +0 -10
  131. package/dist/components/data-entry/month-picker.js +0 -241
  132. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  133. package/dist/components/data-entry/month-range-picker.js +0 -301
  134. package/dist/components/navigation/context-menu.d.ts +0 -21
  135. package/dist/components/navigation/context-menu.js +0 -149
  136. package/dist/components/navigation/menubar.d.ts +0 -21
  137. package/dist/components/navigation/menubar.js +0 -136
  138. package/dist/components/navigation/navigation-menu.d.ts +0 -10
  139. package/dist/components/navigation/navigation-menu.js +0 -91
  140. package/dist/components/ui/context-menu.d.ts +0 -1
  141. package/dist/components/ui/context-menu.js +0 -2
  142. package/dist/components/ui/date-range-picker.d.ts +0 -1
  143. package/dist/components/ui/date-range-picker.js +0 -2
  144. package/dist/components/ui/menubar.d.ts +0 -1
  145. package/dist/components/ui/menubar.js +0 -2
  146. package/dist/components/ui/navigation-menu.d.ts +0 -1
  147. package/dist/components/ui/navigation-menu.js +0 -2
  148. package/docs/FRAME-A11Y-CI.md +0 -349
  149. package/docs/data-entry/date-range-picker.tsx +0 -175
  150. package/docs/data-entry/month-picker.tsx +0 -101
  151. package/docs/data-entry/month-range-picker.tsx +0 -115
  152. package/docs/navigation/context-menu.tsx +0 -128
  153. package/docs/navigation/menubar.tsx +0 -141
  154. package/docs/navigation/navigation-menu.tsx +0 -158
package/README.md CHANGED
@@ -55,7 +55,7 @@ A value is defined **once** as a CSS var (`--primary`), mapped to a utility in t
55
55
  | **Data Display** | `@godxjp/ui/data-display` | `Table`, `DataTable`, `Card`, `StatCard`, `Badge`, `Avatar`, `Descriptions`, `Timeline`, `EmptyState`, `Progress`, `QrCode`, `CredentialReveal`, `Accordion`, `HoverCard`, `Carousel`, `Popover`, `Collapsible` |
56
56
  | **Feedback** | `@godxjp/ui/feedback` | `Dialog`, `AlertDialog`, `Sheet` (side), `Drawer` (bottom-sheet), `Toast`, `Skeleton`, `Alert`, `Tooltip` |
57
57
  | **Query** | `@godxjp/ui/query` | `DataState`, `InfiniteQueryState`, `PrefetchLink` (adapter subpath — pulls TanStack Query) |
58
- | **Navigation** | `@godxjp/ui/navigation` | `Tabs`, `Toolbar`, `DropdownMenu`, `ContextMenu`, `Menubar`, `NavigationMenu`, `Steps`, `Pagination`, `Breadcrumb`, `AppSettingPicker` |
58
+ | **Navigation** | `@godxjp/ui/navigation` | `Tabs`, `Toolbar`, `DropdownMenu`, `Steps`, `Pagination`, `Breadcrumb`, `AppSettingPicker` |
59
59
  | **App** | `@godxjp/ui/app` | `AppProvider`, `useDateTime` (adapter — i18n/datetime singleton) |
60
60
  | **Datetime** | `@godxjp/ui/datetime` | `formatDate` (mandatory for display) |
61
61
  | **Form** | `@godxjp/ui/form` | `useZodForm`, `FormRoot` (adapter subpath — pulls react-hook-form) |
@@ -155,9 +155,9 @@ pnpm release --ui <patch|minor|major> --mcp <…|skip> # publish lib + MCP in
155
155
  | `check:mcp-orphans` | every public component HAS a catalog entry (catalog can't rot) |
156
156
  | `check:core-isolation` | the root export pulls no foreign runtime (adapters stay on subpaths) |
157
157
 
158
- ### Runtime visual audit (Playwright + axe-core)
158
+ ### Runtime visual audit (Playwright)
159
159
 
160
- `scripts/visual-audit.mjs` drives a **real browser** over a running app and runs axe-core plus computed-style heuristics (target size, OKLCH accent chroma, rendered emoji, mis-laid-out alerts) — catching what the static `pnpm audit` (source regexes) can't see. Playwright + `@axe-core/playwright` are **optional peers**, installed only by apps that run the audit.
160
+ `scripts/visual-audit.mjs` drives a **real browser** over a running app and runs computed-style heuristics (target size, OKLCH accent chroma, rendered emoji, mis-laid-out alerts) — catching what the static `pnpm audit` (source regexes) can't see. Playwright + `@axe-core/playwright` are **optional peers**, installed only by apps that run the audit.
161
161
 
162
162
  ```bash
163
163
  # from a consumer, against its running dev/preview server:
@@ -166,7 +166,7 @@ node node_modules/@godxjp/ui/scripts/visual-audit.mjs http://localhost:5173 --fo
166
166
  node node_modules/@godxjp/ui/scripts/visual-audit.mjs --strict http://localhost:5173 # CI gate
167
167
  ```
168
168
 
169
- **Tested peer range** (pin one of these): `playwright >=1.55 <2` (tested 1.61.1) · `@axe-core/playwright >=4.10 <5` (tested 4.12.1) · `axe-core >=4.10 <5` (tested 4.12.1). Playwright 1.55+ is required for the `browser.newContext()` → `context.newPage()` flow axe expects; older `browser.newPage()` throws _"Please use browser.newContext()"_.
169
+ **Tested peer range** (pin one of these): `playwright >=1.55 <2` (tested 1.61.1)
170
170
 
171
171
  `--format json` **always** emits valid JSON — even on bootstrap failure — with a `status` (`ok` · `partial` · `error`) that separates **infrastructure errors** (missing peers, page won't load, axe won't inject → `errors[]`, `summary: null`/flagged) from **product findings** (`findings[]`). A tool failure can therefore never be misread as "zero violations". `pnpm check:visual-audit` is the CI smoke test: it serves a fixture page tripping all five rule families and asserts each one fires.
172
172
 
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { I18nProvider } from "react-aria-components";
4
5
  import { resolveDefaultDateFormat } from "./date-format-labels.js";
5
6
  import { getDateFnsLocale, getDayPickerLocale } from "./locales.js";
6
7
  import { syncAppRequestHeaders } from "./request-headers.js";
@@ -362,7 +363,7 @@ function AppProvider({
362
363
  setScaling
363
364
  ]
364
365
  );
365
- return /* @__PURE__ */ jsx(AppContext.Provider, { value, children });
366
+ return /* @__PURE__ */ jsx(I18nProvider, { locale, children: /* @__PURE__ */ jsx(AppContext.Provider, { value, children }) });
366
367
  }
367
368
  function useAppContext() {
368
369
  const ctx = React.useContext(AppContext);
@@ -45,9 +45,36 @@ export interface BadgeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
45
45
  className?: string;
46
46
  }> | null;
47
47
  status?: string;
48
+ /**
49
+ * Lining, fixed-width figures — for a chip whose content is a COUNT that sits in a column with
50
+ * other counts (a queue length per row, an unread tally per tab, a defect count per site). With
51
+ * proportional figures a `1` is narrower than a `0`, so the chips jitter and the numbers do not
52
+ * line up down the column; it is the same axis `Text`, `TableCell` and `StatCard` already carry,
53
+ * and its absence here was the asymmetry rather than a decision.
54
+ *
55
+ * MEASURE IT BEFORE YOU EXPECT PIXELS TO MOVE. With the stack this library ships,
56
+ * `font-variant-numeric` changes nothing: measured in Chromium at the chip's own 12.47px,
57
+ * `"1111"` and `"0000"` both advance **31.094px** with and without `tabular-nums`, because the
58
+ * resolved face already gives its figures one advance. What this prop buys is the DECLARATION —
59
+ * it survives a service retuning `--font-family-sans` to a face whose figures are proportional,
60
+ * which is the same reason `Text`, `TableCell` and `StatCard` carry the axis.
61
+ *
62
+ * It is also NOT the fix for chips of DIFFERENT WIDTHS down a column: `11` and `100` are two
63
+ * and three figures, so they are two widths whatever the face does. That is a minimum measure on
64
+ * the chip, and it belongs to the screen.
65
+ *
66
+ * There WAS a legal move — `<Badge><Text size="xs" tabular>12</Text></Badge>` measures the same
67
+ * 12.47px as the chip's own label. It just requires the call site to know that a Badge's type
68
+ * step is `xs`, which is the chip's own `--badge-font-size` token and not a fact a consumer
69
+ * should have to carry: retune that token in a theme and every such call site silently desyncs.
70
+ *
71
+ * Off by default. Tabular figures are wider than proportional ones in a face that has both, so a
72
+ * chip carrying WORDS should not pay for them.
73
+ */
74
+ tabular?: boolean;
48
75
  children?: React.ReactNode;
49
76
  }
50
- export declare function Badge({ as: Element, className, variant, shape, tone, color, icon, status, style, children, ...props }: BadgeProps): React.JSX.Element;
77
+ export declare function Badge({ as: Element, className, variant, shape, tone, color, icon, status, tabular, style, children, ...props }: BadgeProps): React.JSX.Element;
51
78
  /**
52
79
  * Status-aware badge with the shared domain-status-to-tone mapping. `Badge` remains the
53
80
  * general-purpose primitive.
@@ -97,6 +97,7 @@ function Badge({
97
97
  color,
98
98
  icon,
99
99
  status,
100
+ tabular,
100
101
  style,
101
102
  children,
102
103
  ...props
@@ -114,6 +115,7 @@ function Badge({
114
115
  "data-tone": tinted ? void 0 : resolvedTone,
115
116
  "data-tinted": tinted ? "" : void 0,
116
117
  "data-shape": shape ?? "default",
118
+ "data-tabular": tabular ? "" : void 0,
117
119
  className: cn(
118
120
  badgeVariants({
119
121
  variant: tinted ? "tinted" : variant ?? "default",
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { flexRender, type ColumnVisibilityState, type OnChangeFn, type PaginationState } from "@tanstack/react-table";
3
- import type { BreakpointProp, ColumnDefProp, DensityProp, OnColumnFilterChangeProp, OnRowProp, SortDirectionProp, SortStateProp, TableExpandableProp, TablePaginationProp, TablePresetProp, TableRowSelectionProp, TableScrollProp, TableStickyProp, TableSummaryProp } from "../../props/vocabulary/index.js";
3
+ import type { BreakpointProp, ColumnDefProp, DensityProp, OnColumnFilterChangeProp, OnRowProp, RowToneProp, SortDirectionProp, SortStateProp, TableExpandableProp, TablePaginationProp, TablePresetProp, TableRowSelectionProp, TableScrollProp, TableStickyProp, TableSummaryProp } from "../../props/vocabulary/index.js";
4
4
  export type Density = DensityProp;
5
5
  /**
6
6
  * Lean column definition — the simple, common-case column API. `render` shapes a cell; `sortable`
@@ -82,6 +82,22 @@ interface DataTableProps<T> {
82
82
  * classes are appended last, so they win over the built-in hover/selected fills.
83
83
  */
84
84
  rowClassName?: (row: T) => string | undefined;
85
+ /**
86
+ * Per-row STATE, drawn as a leading-edge rail plus a wash — the same six tone names `Card accent`
87
+ * carries, so a row that needs attention and a card that needs attention read as one vocabulary.
88
+ * Return `undefined` for an ordinary row.
89
+ *
90
+ * This is the token-driven answer to what `rowClassName` could only do with utilities, and
91
+ * `ui-audit` blocks those in a consumer: `rowClassName` matches the gate's `class`-name suffix
92
+ * rule, so every colour and spacing rule scans the arrow body — a leading-edge border utility
93
+ * plus a raw palette fill in there is two errors, not zero. Consumers fell back to a badge in a
94
+ * cell instead.
95
+ *
96
+ * It is never the ONLY signal: colour alone cannot carry meaning (WCAG 1.4.1), so keep the
97
+ * reason in a cell — a Badge, a status column — and let the rail make that cell findable in a
98
+ * long table.
99
+ */
100
+ rowTone?: RowToneProp<T>;
85
101
  /**
86
102
  * Full row-selection configuration (antd `rowSelection`). Supersedes — and can be mixed with —
87
103
  * `selectable` / `selected` / `onSelectChange`, which drive the same state.
@@ -114,7 +130,7 @@ interface DataTableProps<T> {
114
130
  className?: string;
115
131
  children?: React.ReactNode;
116
132
  }
117
- export declare function DataTable<T>({ data, columns, getRowId, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: paginationProp, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, error, denied, onRetry, striped, hoverable, stickyHeader, preset, collapseBelow, rowClassName, rowSelection, expandable, summary, scroll, sticky, onRow, bordered, showSorterTooltip, sortDirections, onFilterChange, className, children, }: DataTableProps<T>): React.JSX.Element;
133
+ export declare function DataTable<T>({ data, columns, getRowId, selectable, selected: controlledSelected, onSelectChange, onRowClick, density: controlledDensity, onDensityChange, sort, onSortChange, globalFilter: controlledGlobalFilter, onGlobalFilterChange, pagination: paginationProp, onPaginationChange, rowCount, columnVisibility: controlledVisibility, onColumnVisibilityChange, manualSorting, manualFiltering, manualPagination, loading, empty, error, denied, onRetry, striped, hoverable, stickyHeader, preset, collapseBelow, rowClassName, rowTone, rowSelection, expandable, summary, scroll, sticky, onRow, bordered, showSorterTooltip, sortDirections, onFilterChange, className, children, }: DataTableProps<T>): React.JSX.Element;
118
134
  export declare namespace DataTable {
119
135
  var Toolbar: ({ children, className, }: {
120
136
  children?: React.ReactNode;
@@ -219,6 +219,7 @@ function DataTable({
219
219
  preset = "default",
220
220
  collapseBelow = "sm",
221
221
  rowClassName,
222
+ rowTone,
222
223
  rowSelection,
223
224
  expandable,
224
225
  summary,
@@ -431,6 +432,7 @@ function DataTable({
431
432
  preset,
432
433
  collapseBelow,
433
434
  rowClassName,
435
+ rowTone,
434
436
  bordered,
435
437
  scroll,
436
438
  sticky,
@@ -751,6 +753,7 @@ DataTable.Content = function DataTableContent() {
751
753
  preset,
752
754
  collapseBelow,
753
755
  rowClassName,
756
+ rowTone,
754
757
  bordered,
755
758
  scroll,
756
759
  sticky,
@@ -1106,6 +1109,7 @@ DataTable.Content = function DataTableContent() {
1106
1109
  {
1107
1110
  ...rowProps,
1108
1111
  "data-state": isSelected ? "selected" : void 0,
1112
+ "data-tone": rowTone?.(original),
1109
1113
  tabIndex: onRowClick ? 0 : rowProps.tabIndex,
1110
1114
  onClick: (e) => {
1111
1115
  rowProps.onClick?.(e);
@@ -1,5 +1,5 @@
1
1
  export { Badge, StatusBadge } from "./badge.js";
2
- export type { BadgeProps } from "./badge.js";
2
+ export type { BadgeProps, BadgeTone } from "./badge.js";
3
3
  export { Legend } from "./legend.js";
4
4
  export type { LegendItemProp, LegendProp, LegendProps } from "./legend.js";
5
5
  export { ListRow } from "./list-row.js";
@@ -33,7 +33,25 @@ export declare function PopoverAnchor({ asChild, ref, ...props }: PopoverAnchorP
33
33
  type PopoverContentFlush = {
34
34
  flush?: FlushProp;
35
35
  };
36
+ /**
37
+ * How the panel is MEASURED across the inline axis.
38
+ *
39
+ * `"panel"` (default) is the popover's own measure, `--popover-width` (18rem) — right for prose and
40
+ * for a list the panel itself sizes. `"auto"` lets the CONTENT decide, which is the only correct
41
+ * answer for something that already has a width of its own: a two-month `Calendar` is far wider
42
+ * than 18rem and was being clipped by it. `"trigger"` matches the anchor, the shape every
43
+ * select-like control in this library wants.
44
+ *
45
+ * The axis exists because `flush` had no counterpart. The catalog's own rule says a panel must
46
+ * never zero its padding with a `p-0` utility, "that is a per-call-site constant no service theme
47
+ * can retune" — and until now the only way to get a content-width panel was exactly such a
48
+ * constant, `className="w-auto"`. The library's internal `.ui-control-panel-flush` has always set
49
+ * both knobs; this is the public half of it.
50
+ */
51
+ type PopoverContentWidth = "panel" | "auto" | "trigger";
36
52
  interface PopoverContentProps extends React.ComponentPropsWithRef<"div">, PopoverContentFlush {
53
+ /** Inline measure of the panel — `panel` (default) · `auto` (content decides) · `trigger`. */
54
+ width?: PopoverContentWidth;
37
55
  /**
38
56
  * Render THIS panel somewhere other than the default. Almost always unnecessary: mounting the
39
57
  * whole tree in a shadow root is what `OverlayPortalProvider` is for, and it moves every overlay
@@ -73,7 +91,7 @@ interface PopoverContentProps extends React.ComponentPropsWithRef<"div">, Popove
73
91
  */
74
92
  onCloseAutoFocus?: (event: Event) => void;
75
93
  }
76
- export declare function PopoverContent({ className, style, children, ref, flush, side, align, sideOffset, alignOffset, avoidCollisions, collisionPadding, sticky: _sticky, hideWhenDetached: _hideWhenDetached, forceMount: _forceMount, onOpenAutoFocus, onCloseAutoFocus, portalContainer, ...props }: PopoverContentProps): React.JSX.Element;
94
+ export declare function PopoverContent({ className, style, children, ref, flush, width, side, align, sideOffset, alignOffset, avoidCollisions, collisionPadding, sticky: _sticky, hideWhenDetached: _hideWhenDetached, forceMount: _forceMount, onOpenAutoFocus, onCloseAutoFocus, portalContainer, ...props }: PopoverContentProps): React.JSX.Element;
77
95
  export declare const PopoverHeader: ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => React.JSX.Element;
78
96
  export declare const PopoverTitle: ({ className, id, ...props }: React.HTMLAttributes<HTMLDivElement>) => React.JSX.Element;
79
97
  export declare const PopoverDescription: ({ className, id, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => React.JSX.Element;
@@ -126,12 +126,18 @@ function PopoverAnchor({ asChild, ref, ...props }) {
126
126
  }, [setAnchored]);
127
127
  return /* @__PURE__ */ jsx(Comp, { "data-slot": "popover-anchor", ...props, ref: mergeRefs(ref, root.anchorRef) });
128
128
  }
129
+ const POPOVER_SURFACE_INLINE_SIZE = {
130
+ panel: null,
131
+ auto: "auto",
132
+ trigger: "var(--trigger-width)"
133
+ };
129
134
  function PopoverContent({
130
135
  className,
131
136
  style,
132
137
  children,
133
138
  ref,
134
139
  flush,
140
+ width,
135
141
  side = "bottom",
136
142
  align = "center",
137
143
  sideOffset = 4,
@@ -214,6 +220,7 @@ function PopoverContent({
214
220
  "data-align": align,
215
221
  "data-state": isExiting ? "closed" : "open",
216
222
  "data-flush": flush ? "" : void 0,
223
+ "data-width": width && width !== "panel" ? width : void 0,
217
224
  ...props,
218
225
  "aria-labelledby": props["aria-labelledby"] ?? (props["aria-label"] ? void 0 : root.hasTitle ? root.titleId : root.triggerId),
219
226
  "aria-describedby": props["aria-describedby"] ?? (root.hasDescription ? root.descriptionId : void 0),
@@ -234,7 +241,8 @@ function PopoverContent({
234
241
  "--radix-popover-content-available-height": typeof availableHeight === "number" ? `${availableHeight}px` : availableHeight
235
242
  },
236
243
  ...style,
237
- ...flush ? { "--popover-space-inset": "0" } : null
244
+ ...flush ? { "--popover-space-inset": "0" } : null,
245
+ ...width && POPOVER_SURFACE_INLINE_SIZE[width] ? { "--popover-surface-inline-size": POPOVER_SURFACE_INLINE_SIZE[width] } : null
238
246
  },
239
247
  children
240
248
  }
@@ -14,6 +14,20 @@ export type ProgressSegment = {
14
14
  label: string;
15
15
  };
16
16
  type ProgressBase = Omit<React.HTMLAttributes<HTMLDivElement>, "role" | "children" | "aria-valuenow" | "aria-valuemin" | "aria-valuemax" | "aria-valuetext"> & {
17
+ /**
18
+ * Bar thickness — `md` (default) or `sm`.
19
+ *
20
+ * `md` is the size each form was designed at, and it stays the default for the reason spelled
21
+ * out on the breakdown below: three abutting fills need height before their ratios are
22
+ * comparable. `sm` is for a bar that is not the subject of the screen but a column of one — an
23
+ * in-table capacity bar next to a row of numbers, where a 22px partition outweighs the row it
24
+ * annotates and sets the row height for the whole table.
25
+ *
26
+ * Both steps come from the same token pair (`--progress-*-block-size`,
27
+ * `--progress-*-block-size-sm`), so a theme retunes the scale rather than one call site. Nothing
28
+ * about the ARIA changes: `sm` is thickness, not meaning.
29
+ */
30
+ size?: "sm" | "md";
17
31
  /**
18
32
  * Visible caption under the bar; it also becomes the bar's accessible name. Omit it and the bar
19
33
  * falls back to the catalogue name — or to whatever `aria-label`/`aria-labelledby` the caller
@@ -21,10 +35,31 @@ type ProgressBase = Omit<React.HTMLAttributes<HTMLDivElement>, "role" | "childre
21
35
  */
22
36
  label?: string;
23
37
  };
38
+ /**
39
+ * Geometry of the METER — the same measurement, drawn two ways.
40
+ *
41
+ * `bar` is the default and what every existing call site gets. `ring` draws the identical ratio as
42
+ * an arc, for a header with no room for a full-width bar: a phone app bar that has to show
43
+ * "18 of 42 done" beside a title has one square of space, and a bar plus its caption needs two
44
+ * stacked rows. Reach for it when the SPACE is square, not when the number is important — the two
45
+ * shapes say exactly the same thing and carry the same ARIA.
46
+ *
47
+ * It is a meter shape only. A ring around a `segments` breakdown is a pie chart, which is
48
+ * `PieChart donut` and belongs to the charts entry point; that one is a part-to-whole across
49
+ * CATEGORIES with a legend and tooltips, and a screen reader should hear it as an image, not as a
50
+ * progressbar.
51
+ */
52
+ export type ProgressShape = "bar" | "ring";
24
53
  /** A METER: one ratio of one whole. */
25
54
  type ProgressMeterProps = ProgressBase & {
26
55
  value: number;
27
56
  tone?: ProgressTone;
57
+ /**
58
+ * `bar` (default) or `ring` — the same measurement drawn as a full-width bar or as an arc.
59
+ * `label` moves INSIDE the ring, which is the point of it: the readout and its proportion take
60
+ * one square instead of two stacked rows.
61
+ */
62
+ shape?: ProgressShape;
28
63
  /**
29
64
  * Allow `value` to exceed 100 and render an OVER-CAPACITY fill: the bar caps at 100% width but
30
65
  * gets a diagonal striped overlay + destructive tone, so an over-limit meter (e.g. 252% of a
@@ -57,6 +92,8 @@ type ProgressBreakdownProps = ProgressBase & {
57
92
  value?: never;
58
93
  tone?: never;
59
94
  over?: never;
95
+ /** A ring around a partition is a pie chart — that is `PieChart donut`, not this. */
96
+ shape?: never;
60
97
  };
61
98
  export type ProgressProps = ProgressMeterProps | ProgressBreakdownProps;
62
99
  export declare function Progress(props: ProgressProps): React.JSX.Element;
@@ -1,8 +1,12 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { useTranslation } from "../../i18n/use-translation.js";
5
5
  import { cn } from "../../lib/utils.js";
6
+ const RING_BOX = 40;
7
+ const RING_CENTRE = RING_BOX / 2;
8
+ const RING_RADIUS = 16;
9
+ const RING_ROTATION = `rotate(-90 ${RING_CENTRE} ${RING_CENTRE})`;
6
10
  function Progress(props) {
7
11
  const { t } = useTranslation();
8
12
  const labelId = React.useId();
@@ -12,6 +16,8 @@ function Progress(props) {
12
16
  segments,
13
17
  value,
14
18
  tone,
19
+ size,
20
+ shape,
15
21
  over = false,
16
22
  "aria-label": ariaLabel,
17
23
  "aria-labelledby": ariaLabelledBy,
@@ -30,6 +36,7 @@ function Progress(props) {
30
36
  {
31
37
  className: cn("ui-progress", className),
32
38
  "data-breakdown": "",
39
+ "data-size": size === "sm" ? "sm" : void 0,
33
40
  role: "img",
34
41
  "aria-labelledby": ariaLabelledBy,
35
42
  "aria-label": ariaLabelledBy !== void 0 ? void 0 : ariaLabel !== void 0 ? `${ariaLabel}${separator}${spoken}` : label !== void 0 ? `${label}${separator}${spoken}` : spoken,
@@ -53,12 +60,14 @@ function Progress(props) {
53
60
  const isOver = over && meterValue > 100;
54
61
  const boundedValue = Math.max(0, Math.min(100, meterValue));
55
62
  const effectiveTone = tone ?? (isOver ? "destructive" : "success");
56
- return /* @__PURE__ */ jsxs(
63
+ return /* @__PURE__ */ jsx(
57
64
  "div",
58
65
  {
59
66
  className: cn("ui-progress", className),
60
67
  "data-tone": effectiveTone,
61
- "data-over": isOver ? "" : void 0,
68
+ "data-size": size === "sm" ? "sm" : void 0,
69
+ "data-shape": shape === "ring" ? "ring" : void 0,
70
+ "data-over": isOver && shape !== "ring" ? "" : void 0,
62
71
  role: "progressbar",
63
72
  "aria-valuenow": boundedValue,
64
73
  "aria-valuemin": 0,
@@ -67,10 +76,53 @@ function Progress(props) {
67
76
  "aria-labelledby": labelledBy,
68
77
  "aria-label": labelledBy !== void 0 ? void 0 : named ? ariaLabel : t("dataDisplay.progress.ariaLabel"),
69
78
  ...domProps,
70
- children: [
79
+ children: shape === "ring" ? (
80
+ /*
81
+ * The ARC is aria-hidden and the ring carries no text of its own for a screen reader: the
82
+ * element above is already `role="progressbar"` with `aria-valuenow`/`aria-valuetext`, so
83
+ * announcing the drawing as well would say the same number twice. `focusable="false"`
84
+ * because IE-era SVG semantics still reach some AT through the tab order.
85
+ */
86
+ /* @__PURE__ */ jsxs("div", { className: "ui-progress-ring", children: [
87
+ /* @__PURE__ */ jsxs(
88
+ "svg",
89
+ {
90
+ className: "ui-progress-ring-svg",
91
+ viewBox: `0 0 ${RING_BOX} ${RING_BOX}`,
92
+ "aria-hidden": "true",
93
+ focusable: "false",
94
+ children: [
95
+ /* @__PURE__ */ jsx(
96
+ "circle",
97
+ {
98
+ className: "ui-progress-ring-track",
99
+ cx: RING_CENTRE,
100
+ cy: RING_CENTRE,
101
+ r: RING_RADIUS,
102
+ pathLength: 100
103
+ }
104
+ ),
105
+ /* @__PURE__ */ jsx(
106
+ "circle",
107
+ {
108
+ className: "ui-progress-ring-indicator",
109
+ cx: RING_CENTRE,
110
+ cy: RING_CENTRE,
111
+ r: RING_RADIUS,
112
+ pathLength: 100,
113
+ transform: RING_ROTATION,
114
+ strokeDasharray: `${boundedValue} 100`
115
+ }
116
+ )
117
+ ]
118
+ }
119
+ ),
120
+ label !== void 0 ? /* @__PURE__ */ jsx("div", { className: "ui-progress-ring-label", id: labelId, children: label }) : null
121
+ ] })
122
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
71
123
  /* @__PURE__ */ jsx("div", { className: "ui-progress-track", children: /* @__PURE__ */ jsx("div", { className: "ui-progress-bar", style: { width: `${boundedValue}%` } }) }),
72
124
  caption
73
- ]
125
+ ] })
74
126
  }
75
127
  );
76
128
  }
@@ -1,6 +1,26 @@
1
1
  import * as React from "react";
2
- import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
3
2
  import type { ScrollAreaProp } from "../../props/components/data-display.prop.js";
4
- export type ScrollAreaProps = ScrollAreaProp & Omit<React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>, keyof ScrollAreaProp>;
5
- export declare const ScrollArea: React.ForwardRefExoticComponent<ScrollAreaProp & Omit<Omit<ScrollAreaPrimitive.ScrollAreaProps & React.RefAttributes<HTMLDivElement>, "ref">, keyof ScrollAreaProp> & React.RefAttributes<HTMLDivElement>>;
6
- export declare const ScrollBar: React.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaScrollbarProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
3
+ export type ScrollAreaProps = ScrollAreaProp & Omit<React.ComponentPropsWithoutRef<"div">, keyof ScrollAreaProp>;
4
+ export declare const ScrollArea: React.ForwardRefExoticComponent<ScrollAreaProp & Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, keyof ScrollAreaProp> & React.RefAttributes<HTMLDivElement>>;
5
+ /**
6
+ * Deliberately NOT exported: a deprecated no-op must not grow the package's public type surface.
7
+ * Nothing was exported for it before either — it used to be typed straight off Radix.
8
+ */
9
+ type ScrollBarProps = {
10
+ orientation?: "vertical" | "horizontal";
11
+ className?: string;
12
+ };
13
+ /**
14
+ * @deprecated Renders nothing. Native scrolling has no separate bar element to mount: the browser
15
+ * draws the scrollbar for whichever axis overflows, and WHICH axes may overflow is
16
+ * `<ScrollArea orientation="vertical | horizontal | both">`.
17
+ *
18
+ * Under Radix, mounting a `ScrollBar` was what ENABLED its axis (Radix read `scrollbarXEnabled` /
19
+ * `scrollbarYEnabled` from these children and wrote the viewport's inline `overflowX`/`overflowY`
20
+ * from them), so `<ScrollBar orientation="horizontal" />` was load-bearing rather than decorative.
21
+ * It is kept as an inert export so existing call sites keep compiling; move the intent to
22
+ * `orientation` — `<ScrollArea orientation="both">` is the replacement for a vertical area that
23
+ * also mounted a horizontal bar.
24
+ */
25
+ export declare function ScrollBar(_props: ScrollBarProps): null;
26
+ export {};
@@ -1,8 +1,6 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useLayoutEffect } from "@react-aria/utils";
2
+ import { jsx } from "react/jsx-runtime";
4
3
  import * as React from "react";
5
- import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
6
4
  import { cn } from "../../lib/utils.js";
7
5
  const ANCHOR_OFFSET_TOKEN = "--scroll-area-anchor-offset";
8
6
  const ANCHOR_OFFSET_FALLBACK_PX = 48;
@@ -113,18 +111,6 @@ function useBottomAnchor(viewport, enabled, offset, onAnchoredChange) {
113
111
  };
114
112
  }, [viewport, enabled, offset]);
115
113
  }
116
- function useAmbientDirection(root, provided) {
117
- const [ambient, setAmbient] = React.useState(void 0);
118
- useLayoutEffect(() => {
119
- if (provided !== void 0) return;
120
- const parent = root?.parentElement;
121
- if (!parent || typeof window === "undefined" || typeof window.getComputedStyle !== "function") {
122
- return;
123
- }
124
- setAmbient(window.getComputedStyle(parent).direction === "rtl" ? "rtl" : "ltr");
125
- }, [root, provided]);
126
- return provided ?? ambient;
127
- }
128
114
  const ScrollArea = React.forwardRef(
129
115
  ({
130
116
  className,
@@ -134,75 +120,51 @@ const ScrollArea = React.forwardRef(
134
120
  anchorOffset,
135
121
  onAnchoredChange,
136
122
  orientation = "vertical",
137
- dir,
138
123
  ...props
139
124
  }, ref) => {
140
125
  const [viewport, setViewport] = React.useState(null);
141
- const [root, setRoot] = React.useState(null);
142
- const attachRoot = React.useCallback(
143
- (node) => {
144
- setRoot(node);
145
- if (typeof ref === "function") ref(node);
146
- else if (ref) ref.current = node;
147
- },
148
- [ref]
149
- );
150
- const direction = useAmbientDirection(root, dir);
151
126
  const attachViewport = React.useCallback(
152
127
  (node) => {
153
128
  setViewport(node);
154
- if (typeof viewportRef === "function") viewportRef(node);
155
- else if (viewportRef)
156
- viewportRef.current = node;
129
+ for (const target of [ref, viewportRef]) {
130
+ if (typeof target === "function") target(node);
131
+ else if (target) target.current = node;
132
+ }
157
133
  },
158
- [viewportRef]
134
+ [ref, viewportRef]
159
135
  );
160
136
  useBottomAnchor(viewport, anchor === "bottom", anchorOffset, onAnchoredChange);
161
- return /* @__PURE__ */ jsxs(
162
- ScrollAreaPrimitive.Root,
163
- {
164
- ref: attachRoot,
165
- dir: direction,
166
- className: cn("relative overflow-hidden", className),
167
- ...props,
168
- children: [
169
- /* @__PURE__ */ jsx(
170
- ScrollAreaPrimitive.Viewport,
171
- {
172
- ref: attachViewport,
173
- tabIndex: 0,
174
- "data-slot": "scroll-area-viewport",
175
- "data-anchor": anchor,
176
- "data-orientation": orientation,
177
- className: "size-full rounded-[inherit]",
178
- children
179
- }
180
- ),
181
- orientation !== "horizontal" && /* @__PURE__ */ jsx(ScrollBar, { orientation: "vertical" }),
182
- orientation !== "vertical" && /* @__PURE__ */ jsx(ScrollBar, { orientation: "horizontal" }),
183
- /* @__PURE__ */ jsx(ScrollAreaPrimitive.Corner, {})
184
- ]
185
- }
137
+ return (
138
+ // `tabIndex={0}` keeps the scroll viewport keyboard-reachable so overflowing content can be
139
+ // scrolled without a pointer (WCAG 2.1.1 / axe scrollable-region-focusable).
140
+ //
141
+ // NO `dir` IS STAMPED HERE, deliberately. Radix's Root called `useDirection(dir)`, which
142
+ // falls back to the literal "ltr" and writes it onto the element — and a `dir` attribute is
143
+ // not advisory: it resets the inline axis for the whole subtree, so every `margin-inline-*`,
144
+ // `text-align: start` and `align-items: flex-end` inside resolved LTR on an RTL page
145
+ // (measured in Chromium: a chat feed inside `dir="rtl"` kept its own bubbles on the wrong
146
+ // side). A plain element inherits the page's direction, which is the correct answer and
147
+ // needs no code. A caller may still pass `dir` explicitly; it rides through with the rest.
148
+ /* @__PURE__ */ jsx(
149
+ "div",
150
+ {
151
+ ref: attachViewport,
152
+ tabIndex: 0,
153
+ "data-slot": "scroll-area-viewport",
154
+ "data-anchor": anchor,
155
+ "data-orientation": orientation,
156
+ className: cn("ui-scroll-area", className),
157
+ ...props,
158
+ children: /* @__PURE__ */ jsx("div", { "data-slot": "scroll-area-content", className: "ui-scroll-area-content", children })
159
+ }
160
+ )
186
161
  );
187
162
  }
188
163
  );
189
- ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
190
- const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsx(
191
- ScrollAreaPrimitive.ScrollAreaScrollbar,
192
- {
193
- ref,
194
- orientation,
195
- className: cn(
196
- "ui-scroll-area-bar flex touch-none transition-colors select-none",
197
- orientation === "vertical" && "ui-scroll-area-bar--vertical h-full border-s border-s-transparent",
198
- orientation === "horizontal" && "ui-scroll-area-bar--horizontal flex-col border-t border-t-transparent",
199
- className
200
- ),
201
- ...props,
202
- children: /* @__PURE__ */ jsx(ScrollAreaPrimitive.ScrollAreaThumb, { className: "ui-scroll-area-thumb bg-border relative flex-1" })
203
- }
204
- ));
205
- ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
164
+ ScrollArea.displayName = "ScrollArea";
165
+ function ScrollBar(_props) {
166
+ return null;
167
+ }
206
168
  export {
207
169
  ScrollArea,
208
170
  ScrollBar
@@ -13,7 +13,7 @@ import {
13
13
  resolveAriaInvalid
14
14
  } from "./control-surface.js";
15
15
  import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
16
- import { ScrollArea, ScrollBar } from "../data-display/scroll-area.js";
16
+ import { ScrollArea } from "../data-display/scroll-area.js";
17
17
  import { Command, CommandInput } from "./command.js";
18
18
  import {
19
19
  filterTreeOptions,
@@ -311,8 +311,11 @@ function Cascader({
311
311
  if (multiple) commitMulti([]);
312
312
  else commitSingle([]);
313
313
  };
314
- const renderCascadeColumns = () => /* @__PURE__ */ jsxs(ScrollArea, { className: "w-full", children: [
315
- /* @__PURE__ */ jsx("div", { className: "ui-cascader-columns", children: columns.map((col, colIndex) => /* @__PURE__ */ jsx(
314
+ const renderCascadeColumns = () => (
315
+ // `both`: the columns strip overflows sideways, and a tall column scrolls down. Under Radix
316
+ // this was a vertical area plus a mounted `<ScrollBar orientation="horizontal" />`, which was
317
+ // how Radix was told to open the second axis at all.
318
+ /* @__PURE__ */ jsx(ScrollArea, { className: "w-full", orientation: "both", children: /* @__PURE__ */ jsx("div", { className: "ui-cascader-columns", children: columns.map((col, colIndex) => /* @__PURE__ */ jsx(
316
319
  "ul",
317
320
  {
318
321
  role: "listbox",
@@ -371,9 +374,8 @@ function Cascader({
371
374
  })
372
375
  },
373
376
  colIndex
374
- )) }),
375
- /* @__PURE__ */ jsx(ScrollBar, { orientation: "horizontal" })
376
- ] });
377
+ )) }) })
378
+ );
377
379
  const clearControl = resolveAllowClear(allowClear, true, t("dataEntry.cascader.clear"));
378
380
  const showClear = clearControl.enabled && hasValue && !disabled && !readOnly && !loading;
379
381
  const surface = controlSurfaceAttrs({ variant, status, size });