@godxjp/ui 22.0.0 → 23.1.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.
- package/README.md +4 -4
- package/dist/app/date-format-labels.d.ts +14 -2
- package/dist/app/date-format-labels.js +1 -1
- package/dist/app/date-formats.d.ts +14 -2
- package/dist/app/date-formats.js +9 -2
- package/dist/components/data-display/data-table.d.ts +8 -1
- package/dist/components/data-display/data-table.js +9 -2
- package/dist/components/data-display/scroll-area.d.ts +24 -4
- package/dist/components/data-display/scroll-area.js +34 -72
- package/dist/components/data-entry/cascader.js +8 -6
- package/dist/components/data-entry/checkbox.js +3 -1
- package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
- package/dist/components/data-entry/choice-hit-target.js +12 -0
- package/dist/components/data-entry/control-surface.d.ts +8 -0
- package/dist/components/data-entry/control-surface.js +10 -1
- package/dist/components/data-entry/form-field.js +5 -4
- package/dist/components/data-entry/input.js +1 -1
- package/dist/components/data-entry/number-input.js +27 -6
- package/dist/components/data-entry/radio.js +4 -3
- package/dist/components/data-entry/search-select.js +225 -95
- package/dist/components/data-entry/select.d.ts +76 -14
- package/dist/components/data-entry/select.js +495 -163
- package/dist/components/data-entry/slider.d.ts +3 -11
- package/dist/components/data-entry/slider.js +582 -93
- package/dist/components/data-entry/switch.js +3 -1
- package/dist/components/feedback/dialog.js +2 -2
- package/dist/components/feedback/sheet.js +2 -22
- package/dist/components/navigation/dropdown-menu.d.ts +11 -3
- package/dist/components/navigation/dropdown-menu.js +190 -15
- package/dist/components/navigation/filter-bar.js +2 -1
- package/dist/components/navigation/index.d.ts +0 -3
- package/dist/components/navigation/index.js +0 -78
- package/dist/components/navigation/tabs.js +16 -4
- package/dist/components/ui/index.d.ts +0 -3
- package/dist/components/ui/index.js +0 -3
- package/dist/components/ui/input-otp.d.ts +1 -1
- package/dist/components/ui/segmented.js +3 -1
- package/dist/components/ui/tag-input.js +3 -5
- package/dist/i18n/messages/en.json +5 -1
- package/dist/i18n/messages/ja.json +5 -1
- package/dist/i18n/messages/vi.json +5 -1
- package/dist/lib/breakpoint-token.d.ts +8 -0
- package/dist/lib/breakpoint-token.js +29 -0
- package/dist/lib/select-options.d.ts +21 -0
- package/dist/lib/select-options.js +46 -0
- package/dist/props/components/data-display.prop.d.ts +17 -7
- package/dist/props/components/data-entry.prop.d.ts +323 -30
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/navigation.prop.d.ts +25 -1
- package/dist/props/registry.d.ts +69 -1
- package/dist/props/registry.js +75 -0
- package/dist/props/vocabulary/data.prop.d.ts +5 -0
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/styles/control.css +246 -28
- package/dist/styles/data-display-layout.css +18 -10
- package/dist/styles/focus-ring.css +28 -0
- package/dist/styles/fonts.css +88 -4
- package/dist/styles/form-layout.css +4 -0
- package/dist/styles/layout.css +2 -1
- package/dist/styles/navigation-layout.css +24 -238
- package/dist/styles/shell-layout.css +148 -139
- package/dist/styles/table-layout.css +12 -5
- package/dist/tokens/components/control.css +12 -3
- package/dist/tokens/components/data-display.css +0 -5
- package/dist/tokens/components/navigation.css +5 -5
- package/dist/tokens/components/scroll-area.css +7 -0
- package/docs/CONSUMER-RULES.md +7 -2
- package/docs/CUSTOMER-THEMING.md +25 -9
- package/docs/DATETIME.md +11 -2
- package/docs/FRAME-COVERAGE-REPORT.md +4 -7
- package/docs/SPACING.md +35 -5
- package/docs/TOKENS.md +1 -1
- package/docs/data-display/data-table/index.tsx +44 -3
- package/docs/data-display/popover.tsx +4 -6
- package/docs/data-display/scroll-area.tsx +51 -45
- package/docs/data-display/table.tsx +1 -1
- package/docs/data-entry/date-picker.tsx +2 -3
- package/docs/data-entry/input-otp.tsx +5 -2
- package/docs/data-entry/segmented-in-filter-row.tsx +77 -0
- package/docs/data-entry/segmented.tsx +40 -1
- package/docs/data-entry/select-matrix.tsx +1 -2
- package/docs/data-entry/select.tsx +108 -3
- package/docs/data-entry/slider.tsx +125 -47
- package/docs/feedback/dialog.tsx +1 -2
- package/docs/feedback/sheet.tsx +1 -2
- package/docs/layout/app-shell-states.tsx +143 -0
- package/docs/navigation/dropdown-menu.tsx +77 -6
- package/docs/navigation/tabs.tsx +72 -0
- package/docs/roadmap/ai-chat-components.md +1 -2
- package/docs/roadmap/parity-audit-data-entry.md +44 -33
- package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
- package/docs/roadmap/parity-backlog.md +2 -3
- package/docs/roadmap/tree-components.md +0 -1
- package/package.json +25 -55
- package/scripts/_agent-setup.mjs +18 -0
- package/scripts/guinea-pig-skill.md +12 -6
- package/scripts/ui-audit.mjs +185 -7
- package/scripts/visual-audit-rules.mjs +0 -7
- package/scripts/visual-audit.mjs +6 -27
- package/dist/components/navigation/context-menu.d.ts +0 -21
- package/dist/components/navigation/context-menu.js +0 -149
- package/dist/components/navigation/menubar.d.ts +0 -28
- package/dist/components/navigation/menubar.js +0 -136
- package/dist/components/navigation/navigation-menu.d.ts +0 -10
- package/dist/components/navigation/navigation-menu.js +0 -91
- package/dist/components/ui/context-menu.d.ts +0 -1
- package/dist/components/ui/context-menu.js +0 -2
- package/dist/components/ui/menubar.d.ts +0 -1
- package/dist/components/ui/menubar.js +0 -2
- package/dist/components/ui/navigation-menu.d.ts +0 -1
- package/dist/components/ui/navigation-menu.js +0 -2
- package/docs/FRAME-A11Y-CI.md +0 -349
- package/docs/navigation/context-menu.tsx +0 -128
- package/docs/navigation/menubar.tsx +0 -141
- 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`, `
|
|
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
|
|
158
|
+
### Runtime visual audit (Playwright)
|
|
159
159
|
|
|
160
|
-
`scripts/visual-audit.mjs` drives a **real browser** over a running app and runs
|
|
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)
|
|
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,8 +1,20 @@
|
|
|
1
1
|
import type { AppLocale } from "./types.js";
|
|
2
2
|
import type { AppDateFormat } from "./date-formats.js";
|
|
3
3
|
export declare const APP_DATE_FORMAT_OPTIONS: {
|
|
4
|
-
value: "dmy" | "iso" | "mdy";
|
|
4
|
+
value: "dmy" | "iso" | "mdy" | "ymd";
|
|
5
5
|
}[];
|
|
6
6
|
export declare function getDateFormatLabel(dateFormat: AppDateFormat, locale: AppLocale, fallbackLocale?: AppLocale): string;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Suggested default per locale — vi → dmy, ja → ymd, en → mdy.
|
|
9
|
+
*
|
|
10
|
+
* `ja` used to be `iso` (`yyyy-MM-dd`). Nothing recorded a reason for it, and it is not the form a
|
|
11
|
+
* Japanese business document is written in: those use `YYYY/MM/DD` (or `YYYY年MM月DD日`). The cost
|
|
12
|
+
* of the old default was measured in a consumer — gino-cloud shipped its own date formatter as an
|
|
13
|
+
* explicit stopgap purely to get the slashes, which is the package's own rule 1 broken by the
|
|
14
|
+
* package's own default.
|
|
15
|
+
*
|
|
16
|
+
* ONLY THE DEFAULT MOVES. A stored preference still wins (see `resolveDateFormat` in
|
|
17
|
+
* app-provider.tsx), a service can pin any value through `defaultDateFormat`, and `iso` is still
|
|
18
|
+
* one keystroke away in `DateFormatPicker`.
|
|
19
|
+
*/
|
|
8
20
|
export declare function resolveDefaultDateFormat(locale: AppLocale): AppDateFormat;
|
|
@@ -6,7 +6,7 @@ function getDateFormatLabel(dateFormat, locale, fallbackLocale = "en") {
|
|
|
6
6
|
}
|
|
7
7
|
function resolveDefaultDateFormat(locale) {
|
|
8
8
|
if (locale === "en") return "mdy";
|
|
9
|
-
if (locale === "ja") return "
|
|
9
|
+
if (locale === "ja") return "ymd";
|
|
10
10
|
return "dmy";
|
|
11
11
|
}
|
|
12
12
|
export {
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
/** Date-only display preset — sent as `x-date-format` to backend. */
|
|
2
2
|
import { type AppTimeFormat } from "./time-formats.js";
|
|
3
|
-
export type AppDateFormat = "iso" | "dmy" | "mdy";
|
|
4
|
-
|
|
3
|
+
export type AppDateFormat = "iso" | "ymd" | "dmy" | "mdy";
|
|
4
|
+
/**
|
|
5
|
+
* `ymd` (`yyyy/MM/dd`) IS THE JAPANESE BUSINESS FORM, AND ITS ABSENCE WAS A HOLE WITH NO WAY OUT.
|
|
6
|
+
*
|
|
7
|
+
* The axis shipped three values and none of them said `2026/05/01`: `iso` writes hyphens, and the
|
|
8
|
+
* two slash forms put the day or the month first. A Japanese-first consumer (gino-cloud) therefore
|
|
9
|
+
* wrote its own formatter and marked it an explicit stopgap — the tell that a named axis was
|
|
10
|
+
* missing a value rather than the consumer wanting something exotic, since the only alternative
|
|
11
|
+
* the package left was the thing docs/DATETIME.md rule 1 forbids ("never use date-fns/format,
|
|
12
|
+
* toLocaleString, or raw ISO slices for UI").
|
|
13
|
+
*
|
|
14
|
+
* It sits BEFORE the day/month-first pair because it is the other year-first form, next to `iso`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const APP_DATE_FORMATS: readonly ["iso", "ymd", "dmy", "mdy"];
|
|
5
17
|
export declare const APP_REQUEST_HEADER_DATE_FORMAT: "x-date-format";
|
|
6
18
|
/** date-fns pattern for date-only display. */
|
|
7
19
|
export declare function getDatePattern(dateFormat: AppDateFormat): string;
|
package/dist/app/date-formats.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { getTimePattern } from "./time-formats.js";
|
|
2
|
-
const APP_DATE_FORMATS = [
|
|
2
|
+
const APP_DATE_FORMATS = [
|
|
3
|
+
"iso",
|
|
4
|
+
"ymd",
|
|
5
|
+
"dmy",
|
|
6
|
+
"mdy"
|
|
7
|
+
];
|
|
3
8
|
const APP_REQUEST_HEADER_DATE_FORMAT = "x-date-format";
|
|
4
9
|
function getDatePattern(dateFormat) {
|
|
5
10
|
switch (dateFormat) {
|
|
11
|
+
case "ymd":
|
|
12
|
+
return "yyyy/MM/dd";
|
|
6
13
|
case "dmy":
|
|
7
14
|
return "dd/MM/yyyy";
|
|
8
15
|
case "mdy":
|
|
@@ -16,7 +23,7 @@ function getDateTimePattern(timeFormat, dateFormat) {
|
|
|
16
23
|
return `${getDatePattern(dateFormat)} ${getTimePattern(timeFormat)}`;
|
|
17
24
|
}
|
|
18
25
|
function isAppDateFormat(value) {
|
|
19
|
-
return value
|
|
26
|
+
return APP_DATE_FORMATS.includes(value);
|
|
20
27
|
}
|
|
21
28
|
export {
|
|
22
29
|
APP_DATE_FORMATS,
|
|
@@ -13,6 +13,13 @@ interface DataTableProps<T> {
|
|
|
13
13
|
columns: ColumnDef<T>[];
|
|
14
14
|
/** Required when `selectable` is true. Default: assume row.id (typed as any). */
|
|
15
15
|
getRowId?: (row: T) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Human name of a row, announced by its selection checkbox or radio as "Select row {label}".
|
|
18
|
+
* Default: the text of the `priority: "primary"` column, else of the first column, when that
|
|
19
|
+
* value is a string or number; the row id only as a last resort — an id is a KEY, and announced
|
|
20
|
+
* it reads a UUID aloud. `rowSelection.getCheckboxProps` `aria-label` still overrides a row.
|
|
21
|
+
*/
|
|
22
|
+
getRowLabel?: (row: T) => string;
|
|
16
23
|
selectable?: boolean;
|
|
17
24
|
selected?: Set<string>;
|
|
18
25
|
onSelectChange?: (next: Set<string>) => void;
|
|
@@ -130,7 +137,7 @@ interface DataTableProps<T> {
|
|
|
130
137
|
className?: string;
|
|
131
138
|
children?: React.ReactNode;
|
|
132
139
|
}
|
|
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;
|
|
140
|
+
export declare function DataTable<T>({ data, columns, getRowId, getRowLabel, 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;
|
|
134
141
|
export declare namespace DataTable {
|
|
135
142
|
var Toolbar: ({ children, className, }: {
|
|
136
143
|
children?: React.ReactNode;
|
|
@@ -190,6 +190,7 @@ function DataTable({
|
|
|
190
190
|
data,
|
|
191
191
|
columns,
|
|
192
192
|
getRowId = noopGetRowId,
|
|
193
|
+
getRowLabel,
|
|
193
194
|
selectable = false,
|
|
194
195
|
selected: controlledSelected,
|
|
195
196
|
onSelectChange,
|
|
@@ -443,6 +444,7 @@ function DataTable({
|
|
|
443
444
|
toggleExpanded,
|
|
444
445
|
rowSelection,
|
|
445
446
|
getRowId,
|
|
447
|
+
getRowLabel,
|
|
446
448
|
showSorterTooltip,
|
|
447
449
|
sortDirections,
|
|
448
450
|
sortPriorities,
|
|
@@ -764,6 +766,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
764
766
|
toggleExpanded,
|
|
765
767
|
rowSelection,
|
|
766
768
|
getRowId,
|
|
769
|
+
getRowLabel,
|
|
767
770
|
showSorterTooltip,
|
|
768
771
|
sortDirections,
|
|
769
772
|
sortPriorities
|
|
@@ -791,6 +794,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
791
794
|
);
|
|
792
795
|
}
|
|
793
796
|
}, [missingHeaderNames]);
|
|
797
|
+
const selectLabelColumn = visibleColumns.find((col) => col.priority === "primary") ?? visibleColumns[0];
|
|
794
798
|
const scrollRef = React.useRef(null);
|
|
795
799
|
const [hasOverflowEnd, setHasOverflowEnd] = React.useState(false);
|
|
796
800
|
React.useEffect(() => {
|
|
@@ -1096,6 +1100,9 @@ DataTable.Content = function DataTableContent() {
|
|
|
1096
1100
|
const rowKey = getRowId(original);
|
|
1097
1101
|
const isSelected = row.getIsSelected();
|
|
1098
1102
|
const checkboxProps = rowSelection?.getCheckboxProps?.(original) ?? {};
|
|
1103
|
+
const labelValue = selectLabelColumn ? original[selectLabelColumn.key] : void 0;
|
|
1104
|
+
const rowLabel = getRowLabel?.(original) ?? (typeof labelValue === "string" && labelValue.trim() !== "" || typeof labelValue === "number" ? String(labelValue) : row.id);
|
|
1105
|
+
const selectRowLabel = checkboxProps["aria-label"] ?? t("dataTable.selectRow", { id: rowLabel });
|
|
1099
1106
|
const canExpand = expandColumnShown && (expandable?.rowExpandable?.(original) ?? true);
|
|
1100
1107
|
const isExpanded = canExpand && expandedKeys.includes(rowKey);
|
|
1101
1108
|
const rowProps = onRow?.(original, rowIndex) ?? {};
|
|
@@ -1206,7 +1213,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1206
1213
|
RadioItem,
|
|
1207
1214
|
{
|
|
1208
1215
|
value: rowKey,
|
|
1209
|
-
"aria-label":
|
|
1216
|
+
"aria-label": selectRowLabel,
|
|
1210
1217
|
onClick: (e) => {
|
|
1211
1218
|
e.stopPropagation();
|
|
1212
1219
|
}
|
|
@@ -1222,7 +1229,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1222
1229
|
onCheckedChange: (v) => {
|
|
1223
1230
|
row.toggleSelected(!!v);
|
|
1224
1231
|
},
|
|
1225
|
-
"aria-label":
|
|
1232
|
+
"aria-label": selectRowLabel,
|
|
1226
1233
|
onClick: (e) => {
|
|
1227
1234
|
e.stopPropagation();
|
|
1228
1235
|
}
|
|
@@ -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<
|
|
5
|
-
export declare const ScrollArea: React.ForwardRefExoticComponent<ScrollAreaProp & Omit<Omit<
|
|
6
|
-
|
|
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
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
),
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 =
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
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 = () =>
|
|
315
|
-
|
|
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
|
-
|
|
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 });
|
|
@@ -6,6 +6,7 @@ import { Check, Minus } from "lucide-react";
|
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
8
|
import { CheckboxGroup } from "./checkbox-group.js";
|
|
9
|
+
import { withOwnHitTarget } from "./choice-hit-target.js";
|
|
9
10
|
function CheckboxGlyph({ state }) {
|
|
10
11
|
return state === "indeterminate" ? /* @__PURE__ */ jsx(Minus, { className: "ui-checkbox-icon", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Check, { className: "ui-checkbox-icon", "aria-hidden": "true" });
|
|
11
12
|
}
|
|
@@ -93,9 +94,10 @@ const CheckboxRoot = React.forwardRef((props, ref) => {
|
|
|
93
94
|
// itself). react-aria's root is a `<label>`, which is `display:inline` — without this the
|
|
94
95
|
// 16px box collapses to nothing. CheckboxVisual already carries the same three for the
|
|
95
96
|
// same reason.
|
|
96
|
-
"peer ui-checkbox data-[invalid]:border-destructive data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow
|
|
97
|
+
"peer ui-checkbox data-[invalid]:border-destructive data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow",
|
|
97
98
|
className
|
|
98
99
|
),
|
|
100
|
+
render: (domProps) => /* @__PURE__ */ jsx("label", { ...domProps, children: withOwnHitTarget(domProps.children) }),
|
|
99
101
|
onChange: (next) => {
|
|
100
102
|
setUncontrolled(next);
|
|
101
103
|
onCheckedChange?.(next);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* THE PAINTED BOX OF A CHOICE CONTROL IS ITS OWN HIT TARGET (gh#476).
|
|
4
|
+
*
|
|
5
|
+
* react-aria-components renders the real `<input>` inside a `VisuallyHidden` span, and that span
|
|
6
|
+
* carries its geometry as an INLINE style: `position:absolute; width:1px; height:1px;
|
|
7
|
+
* clip:rect(0 0 0 0); clip-path:inset(50%)`, pinned at the label's top-left. The thing a user
|
|
8
|
+
* sees and aims at is the `<label>` wrapped around it, so every pointer landing on the control
|
|
9
|
+
* hits the LABEL, and the input's own hit area is a 13x13 box at a different origin than the
|
|
10
|
+
* 16x16 one that is painted.
|
|
11
|
+
*
|
|
12
|
+
* Measured on `data-entry-checkbox` before this helper existed: of 49 points scanned across the
|
|
13
|
+
* box, **0** reached the input, 46 reached the label; Playwright `check()` / `uncheck()` without
|
|
14
|
+
* `force` timed out on `<label data-slot="checkbox"> intercepts pointer events`, the same check a
|
|
15
|
+
* browser applies to a real pointer. `force: true` passed, which is the tell: the control is
|
|
16
|
+
* functional, it is simply covered.
|
|
17
|
+
*
|
|
18
|
+
* An inline style can only be beaten with `!important`, so the WRAPPER is replaced instead of
|
|
19
|
+
* fought. The `render` prop hands over the DOM children react-aria built; the VisuallyHidden
|
|
20
|
+
* element among them becomes a `.ui-choice-input` span that control.css sizes to the painted box.
|
|
21
|
+
* The `<input>` itself — its props, its ref, its keyboard handling, its place in the a11y tree —
|
|
22
|
+
* is passed through untouched, so this is a geometry change and nothing else.
|
|
23
|
+
*/
|
|
24
|
+
export declare function withOwnHitTarget(children: React.ReactNode): React.ReactNode;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { VisuallyHidden } from "react-aria-components";
|
|
4
|
+
function withOwnHitTarget(children) {
|
|
5
|
+
return React.Children.map(
|
|
6
|
+
children,
|
|
7
|
+
(child) => React.isValidElement(child) && child.type === VisuallyHidden ? /* @__PURE__ */ jsx("span", { "data-slot": "choice-input", className: "ui-choice-input", children: child.props.children }) : child
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
withOwnHitTarget
|
|
12
|
+
};
|
|
@@ -59,3 +59,11 @@ export declare function applyMaxTagCount<T extends {
|
|
|
59
59
|
omitted: T[];
|
|
60
60
|
overflow: React.ReactNode | undefined;
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Split a run of text on antd's `tokenSeparators` — the contract behind "paste a column out of a
|
|
64
|
+
* spreadsheet and get one value per line".
|
|
65
|
+
*
|
|
66
|
+
* Shared by `TagInput` and the tags/multiple `Select`: they are the two controls that turn typing
|
|
67
|
+
* into several values, and two copies of this would be two answers to "what is a separator".
|
|
68
|
+
*/
|
|
69
|
+
export declare function splitByTokenSeparators(text: string, separators: string[]): string[];
|
|
@@ -31,9 +31,18 @@ function applyMaxTagCount(items, maxTagCount, maxTagPlaceholder) {
|
|
|
31
31
|
const overflow = typeof maxTagPlaceholder === "function" ? maxTagPlaceholder(omitted.map(({ value, label }) => ({ value, label }))) : maxTagPlaceholder;
|
|
32
32
|
return { visible, omitted, overflow };
|
|
33
33
|
}
|
|
34
|
+
function escapeForCharClass(character) {
|
|
35
|
+
return character.replace(/[\\\]^-]/g, "\\$&");
|
|
36
|
+
}
|
|
37
|
+
function splitByTokenSeparators(text, separators) {
|
|
38
|
+
if (!separators.length) return [text];
|
|
39
|
+
const pattern = new RegExp(`[${separators.map(escapeForCharClass).join("")}]`);
|
|
40
|
+
return text.split(pattern);
|
|
41
|
+
}
|
|
34
42
|
export {
|
|
35
43
|
applyMaxTagCount,
|
|
36
44
|
controlSurfaceAttrs,
|
|
37
45
|
resolveAllowClear,
|
|
38
|
-
resolveAriaInvalid
|
|
46
|
+
resolveAriaInvalid,
|
|
47
|
+
splitByTokenSeparators
|
|
39
48
|
};
|
|
@@ -47,7 +47,7 @@ function FormField({
|
|
|
47
47
|
const resolvedId = id ?? autoId;
|
|
48
48
|
const labelId = `${resolvedId}-label`;
|
|
49
49
|
const helperId = helper ? `${resolvedId}-helper` : void 0;
|
|
50
|
-
const helperNode = helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "text-muted-foreground text-xs", children: helper }) : null;
|
|
50
|
+
const helperNode = helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "ui-form-field-note text-muted-foreground text-xs", children: helper }) : null;
|
|
51
51
|
const validationStatus = error ? "error" : validateStatus;
|
|
52
52
|
const feedbackId = hasFeedback && validationStatus ? `${resolvedId}-feedback` : void 0;
|
|
53
53
|
const FeedbackIcon = validationStatus === "validating" ? LoaderCircle : validationStatus === "error" ? CircleAlert : validationStatus === "warning" ? TriangleAlert : CheckCircle;
|
|
@@ -69,6 +69,7 @@ function FormField({
|
|
|
69
69
|
[fieldKey, emitFieldNames]
|
|
70
70
|
);
|
|
71
71
|
const childProps = React.isValidElement(children) ? children.props : void 0;
|
|
72
|
+
const controlId = childProps?.id ?? resolvedId;
|
|
72
73
|
const mergeIds = mergeAriaIds;
|
|
73
74
|
const childWithA11y = isStatic ? (
|
|
74
75
|
// read-only FormField row and a Descriptions value are indistinguishable when mixed. That
|
|
@@ -78,7 +79,7 @@ function FormField({
|
|
|
78
79
|
// The label is associated via aria-labelledby (not <label for>): composite
|
|
79
80
|
// controls (Radio.Group, checkbox lists, range pairs) have no labelable root,
|
|
80
81
|
// and a dangling `for` triggers Chrome's "Incorrect use of <label>" issue.
|
|
81
|
-
id:
|
|
82
|
+
id: controlId,
|
|
82
83
|
...form?.disabled && childProps?.disabled !== false ? { disabled: true } : {},
|
|
83
84
|
...validationStatus === "validating" ? { "aria-busy": true } : {},
|
|
84
85
|
// Read the child's own value first in BOTH cases: cloneElement
|
|
@@ -133,7 +134,7 @@ function FormField({
|
|
|
133
134
|
"span",
|
|
134
135
|
{
|
|
135
136
|
onClick: () => {
|
|
136
|
-
const el = document.getElementById(
|
|
137
|
+
const el = document.getElementById(controlId);
|
|
137
138
|
if (!(el instanceof HTMLElement)) return;
|
|
138
139
|
const focusable = el.matches(FOCUSABLE_SELECTOR) ? el : el.querySelector(FOCUSABLE_SELECTOR);
|
|
139
140
|
(focusable ?? el).focus();
|
|
@@ -174,7 +175,7 @@ function FormField({
|
|
|
174
175
|
t(`dataEntry.form.${validationStatus}`)
|
|
175
176
|
] }) : null,
|
|
176
177
|
helperPlacement === "after" ? helperNode : null,
|
|
177
|
-
error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-destructive text-xs", children: error }) : null
|
|
178
|
+
error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "ui-form-field-note text-destructive text-xs", children: error }) : null
|
|
178
179
|
] })
|
|
179
180
|
]
|
|
180
181
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./control-appearance.js";
|
|
14
14
|
import { resolveAllowClear } from "./control-surface.js";
|
|
15
15
|
const inputBaseClass = [
|
|
16
|
-
"ui-control ui-input w-full rounded-[var(--control-radius)] transition-[color,box-shadow]
|
|
16
|
+
"ui-control ui-input w-full rounded-[var(--control-radius)] transition-[color,box-shadow]",
|
|
17
17
|
"selection:bg-primary selection:text-primary-foreground",
|
|
18
18
|
"placeholder:text-muted-foreground",
|
|
19
19
|
"aria-invalid:border-destructive",
|
|
@@ -57,6 +57,21 @@ const NumberInput = React.forwardRef(
|
|
|
57
57
|
}, ref) => {
|
|
58
58
|
const { t, locale } = useTranslation();
|
|
59
59
|
const fieldA11y = pickFieldA11y(ariaProps);
|
|
60
|
+
const stepperVerbId = React.useId();
|
|
61
|
+
const fieldLabel = typeof fieldA11y["aria-label"] === "string" ? fieldA11y["aria-label"] : void 0;
|
|
62
|
+
const fieldLabelledBy = typeof fieldA11y["aria-labelledby"] === "string" ? fieldA11y["aria-labelledby"] : void 0;
|
|
63
|
+
const stepperName = (direction) => {
|
|
64
|
+
const verb = t(`ui.numberInput.${direction}`);
|
|
65
|
+
if (fieldLabelledBy) {
|
|
66
|
+
return { "aria-labelledby": `${fieldLabelledBy} ${stepperVerbId}-${direction}` };
|
|
67
|
+
}
|
|
68
|
+
if (fieldLabel) {
|
|
69
|
+
return {
|
|
70
|
+
"aria-label": t(`ui.numberInput.${direction}Field`, { label: fieldLabel }) || verb
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { "aria-label": verb };
|
|
74
|
+
};
|
|
60
75
|
const isControlled = controlledValue !== void 0;
|
|
61
76
|
const [internal, setInternal] = React.useState(defaultValue);
|
|
62
77
|
const numericValue = isControlled ? controlledValue ?? null : internal;
|
|
@@ -228,7 +243,7 @@ const NumberInput = React.forwardRef(
|
|
|
228
243
|
}
|
|
229
244
|
) : null,
|
|
230
245
|
controls ? /* @__PURE__ */ jsxs("span", { "data-slot": "number-input-steppers", className: "ui-number-input-steppers", children: [
|
|
231
|
-
/* @__PURE__ */
|
|
246
|
+
/* @__PURE__ */ jsxs(
|
|
232
247
|
Button,
|
|
233
248
|
{
|
|
234
249
|
type: "button",
|
|
@@ -237,12 +252,15 @@ const NumberInput = React.forwardRef(
|
|
|
237
252
|
className: "ui-number-input-step ui-number-input-step-up",
|
|
238
253
|
tabIndex: -1,
|
|
239
254
|
disabled: !interactive || atMax,
|
|
240
|
-
|
|
255
|
+
...stepperName("increment"),
|
|
241
256
|
onClick: () => stepBy(1),
|
|
242
|
-
children:
|
|
257
|
+
children: [
|
|
258
|
+
/* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": "true" }),
|
|
259
|
+
/* @__PURE__ */ jsx("span", { id: `${stepperVerbId}-increment`, className: "sr-only", children: t("ui.numberInput.increment") })
|
|
260
|
+
]
|
|
243
261
|
}
|
|
244
262
|
),
|
|
245
|
-
/* @__PURE__ */
|
|
263
|
+
/* @__PURE__ */ jsxs(
|
|
246
264
|
Button,
|
|
247
265
|
{
|
|
248
266
|
type: "button",
|
|
@@ -251,9 +269,12 @@ const NumberInput = React.forwardRef(
|
|
|
251
269
|
className: "ui-number-input-step ui-number-input-step-down",
|
|
252
270
|
tabIndex: -1,
|
|
253
271
|
disabled: !interactive || atMin,
|
|
254
|
-
|
|
272
|
+
...stepperName("decrement"),
|
|
255
273
|
onClick: () => stepBy(-1),
|
|
256
|
-
children:
|
|
274
|
+
children: [
|
|
275
|
+
/* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" }),
|
|
276
|
+
/* @__PURE__ */ jsx("span", { id: `${stepperVerbId}-decrement`, className: "sr-only", children: t("ui.numberInput.decrement") })
|
|
277
|
+
]
|
|
257
278
|
}
|
|
258
279
|
)
|
|
259
280
|
] }) : null
|