@godxjp/ui 17.0.0 → 18.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.
- package/dist/components/data-display/card.d.ts +37 -1
- package/dist/components/data-display/card.js +7 -1
- package/dist/components/data-display/data-table.js +18 -3
- package/dist/components/data-display/empty-state.d.ts +1 -1
- package/dist/components/data-display/empty-state.js +5 -1
- package/dist/components/data-display/scroll-area.js +1 -1
- package/dist/components/data-display/table.js +14 -9
- package/dist/components/data-entry/calendar.js +4 -0
- package/dist/components/data-entry/cascader.d.ts +1 -1
- package/dist/components/data-entry/cascader.js +10 -1
- package/dist/components/data-entry/checkbox-group.d.ts +1 -1
- package/dist/components/data-entry/checkbox-group.js +13 -4
- package/dist/components/data-entry/color-picker.d.ts +1 -1
- package/dist/components/data-entry/color-picker.js +5 -2
- package/dist/components/data-entry/command.d.ts +2 -2
- package/dist/components/data-entry/date-picker.d.ts +1 -1
- package/dist/components/data-entry/date-picker.js +11 -1
- package/dist/components/data-entry/date-range-picker.d.ts +1 -1
- package/dist/components/data-entry/date-range-picker.js +11 -3
- package/dist/components/data-entry/form-field.js +7 -3
- package/dist/components/data-entry/month-picker.d.ts +1 -1
- package/dist/components/data-entry/month-picker.js +13 -1
- package/dist/components/data-entry/month-range-picker.d.ts +1 -1
- package/dist/components/data-entry/month-range-picker.js +11 -3
- package/dist/components/data-entry/number-input.d.ts +19 -2
- package/dist/components/data-entry/number-input.js +5 -7
- package/dist/components/data-entry/radio.d.ts +1 -1
- package/dist/components/data-entry/radio.js +11 -3
- package/dist/components/data-entry/search-input.d.ts +3 -2
- package/dist/components/data-entry/search-input.js +5 -2
- package/dist/components/data-entry/search-select.d.ts +1 -1
- package/dist/components/data-entry/search-select.js +51 -10
- package/dist/components/data-entry/select.d.ts +7 -0
- package/dist/components/data-entry/select.js +21 -3
- package/dist/components/data-entry/time-picker.d.ts +1 -1
- package/dist/components/data-entry/time-picker.js +11 -1
- package/dist/components/data-entry/transfer.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +73 -59
- package/dist/components/data-entry/tree-select.d.ts +1 -1
- package/dist/components/data-entry/tree-select.js +10 -1
- package/dist/components/data-entry/upload.d.ts +1 -1
- package/dist/components/data-entry/upload.js +7 -2
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +42 -2
- package/dist/components/layout/breadcrumb.d.ts +7 -1
- package/dist/components/layout/breadcrumb.js +6 -2
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +32 -22
- package/dist/components/layout/responsive-grid.js +1 -1
- package/dist/components/layout/sidebar.d.ts +1 -1
- package/dist/components/layout/sidebar.js +44 -11
- package/dist/components/layout/split-pane.js +2 -2
- package/dist/components/navigation/app-setting-picker.js +8 -1
- package/dist/components/navigation/pagination.d.ts +1 -1
- package/dist/components/navigation/pagination.js +84 -95
- package/dist/components/navigation/tabs.js +21 -4
- package/dist/i18n/messages/en.json +3 -1
- package/dist/i18n/messages/ja.json +3 -1
- package/dist/i18n/messages/vi.json +3 -1
- package/dist/lib/field-a11y.d.ts +66 -0
- package/dist/lib/field-a11y.js +44 -0
- package/dist/props/components/data-display.prop.d.ts +16 -1
- package/dist/props/components/data-entry.prop.d.ts +69 -19
- package/dist/props/components/layout.prop.d.ts +42 -0
- package/dist/props/components/navigation.prop.d.ts +16 -0
- package/dist/props/registry.d.ts +8 -3
- package/dist/props/registry.js +21 -3
- package/dist/props/vocabulary/data.prop.d.ts +9 -0
- package/dist/props/vocabulary/interaction.prop.d.ts +1 -1
- package/dist/styles/base.css +1 -1
- package/dist/styles/layout.css +46 -9
- package/dist/styles/shell-layout.css +19 -0
- package/dist/styles/text-layout.css +1 -1
- package/dist/tokens/components/card.css +1 -1
- package/dist/tokens/foundation.css +3 -3
- package/package.json +8 -4
- package/scripts/ui-audit.mjs +2 -2
- package/scripts/visual-audit-rules.mjs +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type VariantProps } from "class-variance-authority";
|
|
3
3
|
import type { LucideIcon } from "lucide-react";
|
|
4
|
+
import type { HeadingLevelProp } from "../../props/vocabulary/index.js";
|
|
4
5
|
type CardSize = "md" | "compact";
|
|
5
6
|
/** Semantic leading-edge accent stripe (border-inline-start; width via the
|
|
6
7
|
* --card-accent-rail-width token, default 6px). */
|
|
@@ -37,7 +38,42 @@ export declare const CardHeader: React.ForwardRefExoticComponent<React.HTMLAttri
|
|
|
37
38
|
/** Muted background + border-bottom — section band (mirror footer `separated`). */
|
|
38
39
|
banded?: boolean;
|
|
39
40
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
40
|
-
export
|
|
41
|
+
export type CardTitleProps = React.HTMLAttributes<HTMLHeadingElement> & {
|
|
42
|
+
/**
|
|
43
|
+
* Semantic heading level (`h1`–`h4`). Default `3`. Choose it to keep the
|
|
44
|
+
* document outline valid (`h1 → h2 → h3`, no skipped levels) for the page the
|
|
45
|
+
* card sits on — NOT for visual size: the title's size is fixed by the
|
|
46
|
+
* `--card-title-*` tokens and does not change with `level`. A section card
|
|
47
|
+
* directly under a page `h1` uses `level={2}`; a card nested inside an
|
|
48
|
+
* already-`h2` section uses `level={3}` (the default).
|
|
49
|
+
*/
|
|
50
|
+
level?: HeadingLevelProp;
|
|
51
|
+
/**
|
|
52
|
+
* Render a different element than the `level` heading. Use `"p"` / `"div"`
|
|
53
|
+
* when the card title is a styled label rather than a section heading in the
|
|
54
|
+
* outline (so it is not announced as a heading and cannot skip a level).
|
|
55
|
+
* Overrides `level`.
|
|
56
|
+
*/
|
|
57
|
+
as?: "h1" | "h2" | "h3" | "h4" | "p" | "div";
|
|
58
|
+
};
|
|
59
|
+
export declare const CardTitle: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLHeadingElement> & {
|
|
60
|
+
/**
|
|
61
|
+
* Semantic heading level (`h1`–`h4`). Default `3`. Choose it to keep the
|
|
62
|
+
* document outline valid (`h1 → h2 → h3`, no skipped levels) for the page the
|
|
63
|
+
* card sits on — NOT for visual size: the title's size is fixed by the
|
|
64
|
+
* `--card-title-*` tokens and does not change with `level`. A section card
|
|
65
|
+
* directly under a page `h1` uses `level={2}`; a card nested inside an
|
|
66
|
+
* already-`h2` section uses `level={3}` (the default).
|
|
67
|
+
*/
|
|
68
|
+
level?: HeadingLevelProp;
|
|
69
|
+
/**
|
|
70
|
+
* Render a different element than the `level` heading. Use `"p"` / `"div"`
|
|
71
|
+
* when the card title is a styled label rather than a section heading in the
|
|
72
|
+
* outline (so it is not announced as a heading and cannot skip a level).
|
|
73
|
+
* Overrides `level`.
|
|
74
|
+
*/
|
|
75
|
+
as?: "h1" | "h2" | "h3" | "h4" | "p" | "div";
|
|
76
|
+
} & React.RefAttributes<HTMLHeadingElement>>;
|
|
41
77
|
export declare const CardDescription: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLParagraphElement> & React.RefAttributes<HTMLParagraphElement>>;
|
|
42
78
|
export type CardContentProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
43
79
|
/** Edge-to-edge body (tables, tabs list). Horizontal padding removed. */
|
|
@@ -44,7 +44,13 @@ const CardHeader = React.forwardRef(
|
|
|
44
44
|
)
|
|
45
45
|
);
|
|
46
46
|
CardHeader.displayName = "CardHeader";
|
|
47
|
-
const CardTitle = React.forwardRef(
|
|
47
|
+
const CardTitle = React.forwardRef(
|
|
48
|
+
({ className, children, level = 3, as, ...props }, ref) => React.createElement(
|
|
49
|
+
as ?? `h${level}`,
|
|
50
|
+
{ ref, "data-slot": "card-title", className, ...props },
|
|
51
|
+
children
|
|
52
|
+
)
|
|
53
|
+
);
|
|
48
54
|
CardTitle.displayName = "CardTitle";
|
|
49
55
|
const CardDescription = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("p", { ref, "data-slot": "card-description", className, ...props }));
|
|
50
56
|
CardDescription.displayName = "CardDescription";
|
|
@@ -284,6 +284,9 @@ DataTable.ViewOptions = function DataTableViewOptions({ className }) {
|
|
|
284
284
|
] });
|
|
285
285
|
};
|
|
286
286
|
DataTable.ViewOptions.displayName = "DataTable.ViewOptions";
|
|
287
|
+
function isEmptyHeader(header) {
|
|
288
|
+
return header == null || header === "" || header === false;
|
|
289
|
+
}
|
|
287
290
|
function columnLabel(column) {
|
|
288
291
|
const header = column.columnDef.meta?.lean?.header;
|
|
289
292
|
if (typeof header === "string" && header.length > 0) return header;
|
|
@@ -386,6 +389,15 @@ DataTable.Content = function DataTableContent() {
|
|
|
386
389
|
const visibleColumns = table.getVisibleLeafColumns().map((c) => c.columnDef.meta?.lean).filter((c) => !!c);
|
|
387
390
|
const emptyColSpan = visibleColumns.length + (selectable ? 1 : 0);
|
|
388
391
|
const hasPinEnd = visibleColumns.some((col) => col.pin === "end");
|
|
392
|
+
const missingHeaderNames = visibleColumns.filter((col) => isEmptyHeader(col.header) && !col.ariaLabel).map((col) => col.key).join("|");
|
|
393
|
+
React.useEffect(() => {
|
|
394
|
+
if (process.env.NODE_ENV === "production" || !missingHeaderNames) return;
|
|
395
|
+
for (const key of missingHeaderNames.split("|")) {
|
|
396
|
+
console.warn(
|
|
397
|
+
`[DataTable] Column "${key}" renders a <th> with no visible or accessible text. Give it a visible \`header\`, or set \`ariaLabel\` (e.g. "Actions"/"Select") so screen readers can announce the column and axe reports no empty-table-header violation.`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}, [missingHeaderNames]);
|
|
389
401
|
const activeSort = sort ?? sortingStateToSort(table.getState().sorting);
|
|
390
402
|
const isControlledSort = sort !== void 0 || !!onSortChange;
|
|
391
403
|
const onHeaderClick = (col) => {
|
|
@@ -409,6 +421,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
409
421
|
{
|
|
410
422
|
className: cn("ui-data-table-scroll", hasPinEnd && "ui-data-table-has-pin-end"),
|
|
411
423
|
"aria-busy": loading,
|
|
424
|
+
tabIndex: 0,
|
|
412
425
|
children: /* @__PURE__ */ jsx(
|
|
413
426
|
"div",
|
|
414
427
|
{
|
|
@@ -422,14 +435,16 @@ DataTable.Content = function DataTableContent() {
|
|
|
422
435
|
const isSortable = !!col.sortable;
|
|
423
436
|
const isActiveSort = isSortable && activeSort?.key === col.key;
|
|
424
437
|
const sortIndicator = isSortable ? isActiveSort ? activeSort?.direction === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "text-muted-foreground size-3", "aria-hidden": "true" }) : null;
|
|
438
|
+
const headerEmpty = isEmptyHeader(col.header);
|
|
439
|
+
const headerContent = headerEmpty && col.ariaLabel ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: col.ariaLabel }) : col.header;
|
|
425
440
|
const label = /* @__PURE__ */ jsxs("span", { className: "ui-data-table-sort-label", children: [
|
|
426
|
-
|
|
441
|
+
headerContent,
|
|
427
442
|
sortIndicator
|
|
428
443
|
] });
|
|
429
444
|
return /* @__PURE__ */ jsx(
|
|
430
445
|
TableHead,
|
|
431
446
|
{
|
|
432
|
-
"data-empty":
|
|
447
|
+
"data-empty": headerEmpty || void 0,
|
|
433
448
|
"aria-sort": isSortable ? isActiveSort ? activeSort?.direction === "asc" ? "ascending" : "descending" : "none" : void 0,
|
|
434
449
|
className: cn(
|
|
435
450
|
col.width,
|
|
@@ -494,7 +509,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
494
509
|
colSpan: emptyColSpan,
|
|
495
510
|
className: "ui-data-table-empty",
|
|
496
511
|
"aria-live": "polite",
|
|
497
|
-
children: empty ?? /* @__PURE__ */ jsx(EmptyState, { title: t("dataTable.empty") })
|
|
512
|
+
children: empty ?? /* @__PURE__ */ jsx(EmptyState, { title: t("dataTable.empty"), titleAs: "p" })
|
|
498
513
|
}
|
|
499
514
|
) }) : dataRows.map((row) => {
|
|
500
515
|
const original = row.original;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { EmptyStateProp } from "../../props/components/data-display.prop.js";
|
|
2
2
|
export type { EmptyStateProp, EmptyStateProp as EmptyStateProps, } from "../../props/components/data-display.prop.js";
|
|
3
|
-
export declare function EmptyState({ icon: Icon, title, description, action, variant, tone, className, }: EmptyStateProp): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export declare function EmptyState({ icon: Icon, title, description, action, variant, tone, titleLevel, titleAs, className, }: EmptyStateProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createElement } from "react";
|
|
2
3
|
import { cn } from "../../lib/utils.js";
|
|
3
4
|
function EmptyState({
|
|
4
5
|
icon: Icon,
|
|
@@ -7,8 +8,11 @@ function EmptyState({
|
|
|
7
8
|
action,
|
|
8
9
|
variant = "page",
|
|
9
10
|
tone = "muted",
|
|
11
|
+
titleLevel = 3,
|
|
12
|
+
titleAs,
|
|
10
13
|
className
|
|
11
14
|
}) {
|
|
15
|
+
const TitleTag = titleAs ?? `h${titleLevel}`;
|
|
12
16
|
return /* @__PURE__ */ jsxs(
|
|
13
17
|
"div",
|
|
14
18
|
{
|
|
@@ -19,7 +23,7 @@ function EmptyState({
|
|
|
19
23
|
className: cn("ui-empty-state", className),
|
|
20
24
|
children: [
|
|
21
25
|
Icon && variant !== "compact" && /* @__PURE__ */ jsx("div", { className: "ui-empty-state-icon", children: /* @__PURE__ */ jsx(Icon, { className: "text-muted-foreground size-6", "aria-hidden": "true" }) }),
|
|
22
|
-
|
|
26
|
+
createElement(TitleTag, { className: "ui-empty-state-title" }, title),
|
|
23
27
|
description && /* @__PURE__ */ jsx("p", { className: "ui-empty-state-description", children: description }),
|
|
24
28
|
action && /* @__PURE__ */ jsx("div", { children: action })
|
|
25
29
|
]
|
|
@@ -10,7 +10,7 @@ const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) =>
|
|
|
10
10
|
className: cn("relative overflow-hidden", className),
|
|
11
11
|
...props,
|
|
12
12
|
children: [
|
|
13
|
-
/* @__PURE__ */ jsx(ScrollAreaPrimitive.Viewport, { className: "size-full rounded-[inherit]", children }),
|
|
13
|
+
/* @__PURE__ */ jsx(ScrollAreaPrimitive.Viewport, { tabIndex: 0, className: "size-full rounded-[inherit]", children }),
|
|
14
14
|
/* @__PURE__ */ jsx(ScrollBar, {}),
|
|
15
15
|
/* @__PURE__ */ jsx(ScrollAreaPrimitive.Corner, {})
|
|
16
16
|
]
|
|
@@ -3,15 +3,20 @@ import * as React from "react";
|
|
|
3
3
|
import { tableHeadHeightClass } from "../../lib/control-styles.js";
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
5
|
const Table = React.forwardRef(
|
|
6
|
-
({ className, ...props }, ref) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
({ className, ...props }, ref) => (
|
|
7
|
+
// A table wider than its container scrolls horizontally in this wrapper; keep it
|
|
8
|
+
// keyboard-reachable so it can be scrolled without a pointer (WCAG 2.1.1 / axe
|
|
9
|
+
// scrollable-region-focusable). No landmark role — avoids landmark-unique collisions.
|
|
10
|
+
/* @__PURE__ */ jsx("div", { className: "relative w-full overflow-auto", tabIndex: 0, children: /* @__PURE__ */ jsx(
|
|
11
|
+
"table",
|
|
12
|
+
{
|
|
13
|
+
ref,
|
|
14
|
+
"data-slot": "table",
|
|
15
|
+
className: cn("w-full caption-bottom text-sm", className),
|
|
16
|
+
...props
|
|
17
|
+
}
|
|
18
|
+
) })
|
|
19
|
+
)
|
|
15
20
|
);
|
|
16
21
|
Table.displayName = "Table";
|
|
17
22
|
const TableHeader = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("thead", { ref, className: cn("[&_tr]:border-b", className), ...props }));
|
|
@@ -70,6 +70,10 @@ function Calendar({
|
|
|
70
70
|
),
|
|
71
71
|
selected: cn(
|
|
72
72
|
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
|
73
|
+
// The day is a ghost <button> that sets its OWN (dark --foreground) text colour, which
|
|
74
|
+
// overrides the cell's text-primary-foreground and fails contrast on the blue fill
|
|
75
|
+
// (axe color-contrast). Force the button label to primary-foreground through hover/focus.
|
|
76
|
+
"[&>button]:text-primary-foreground [&>button:hover]:bg-primary [&>button:hover]:text-primary-foreground [&>button:focus]:text-primary-foreground",
|
|
73
77
|
classNames?.selected
|
|
74
78
|
),
|
|
75
79
|
today: cn("bg-accent text-accent-foreground", classNames?.today),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { CascaderProp } from "../../props/components/data-entry.prop.js";
|
|
2
2
|
export type { CascaderProp, CascaderProp as CascaderProps, } from "../../props/components/data-entry.prop.js";
|
|
3
3
|
export type { TreeOption, TreeFieldNames } from "./tree-utils.js";
|
|
4
|
-
export declare function Cascader({ options: optionsProp, value, defaultValue, onValueChange, multiple, changeOnSelect, showSearch, placeholder, disabled, className, id, expandTrigger, fieldNames, allowClear, }: CascaderProp): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
export declare function Cascader({ options: optionsProp, value, defaultValue, onValueChange, multiple, changeOnSelect, showSearch, placeholder, disabled, className, id, expandTrigger, fieldNames, allowClear, ...ariaProps }: CascaderProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { Check, ChevronRight, ChevronsUpDown, Minus, X } from "lucide-react";
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { pickFieldA11y } from "../../lib/field-a11y.js";
|
|
7
8
|
import { controlOpenRingClass } from "../../lib/control-styles.js";
|
|
8
9
|
import { Button } from "../general/button.js";
|
|
9
10
|
import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
@@ -80,9 +81,13 @@ function Cascader({
|
|
|
80
81
|
id,
|
|
81
82
|
expandTrigger = "click",
|
|
82
83
|
fieldNames,
|
|
83
|
-
allowClear = true
|
|
84
|
+
allowClear = true,
|
|
85
|
+
...ariaProps
|
|
84
86
|
}) {
|
|
85
87
|
const { t } = useTranslation();
|
|
88
|
+
const fieldA11y = pickFieldA11y(ariaProps);
|
|
89
|
+
const reactId = React.useId();
|
|
90
|
+
const panelId = `${id ?? reactId}-panel`;
|
|
86
91
|
const options = React.useMemo(
|
|
87
92
|
() => normalizeTreeOptions(optionsProp, fieldNames),
|
|
88
93
|
[optionsProp, fieldNames]
|
|
@@ -239,6 +244,9 @@ function Cascader({
|
|
|
239
244
|
variant: "outline",
|
|
240
245
|
role: "combobox",
|
|
241
246
|
"aria-expanded": open,
|
|
247
|
+
"aria-haspopup": "listbox",
|
|
248
|
+
"aria-controls": open ? panelId : void 0,
|
|
249
|
+
...fieldA11y,
|
|
242
250
|
disabled,
|
|
243
251
|
className: cn(
|
|
244
252
|
"w-full justify-start font-normal",
|
|
@@ -253,6 +261,7 @@ function Cascader({
|
|
|
253
261
|
/* @__PURE__ */ jsxs(
|
|
254
262
|
PopoverContent,
|
|
255
263
|
{
|
|
264
|
+
id: panelId,
|
|
256
265
|
className: "w-auto min-w-[var(--radix-popover-trigger-width)] p-0",
|
|
257
266
|
align: "start",
|
|
258
267
|
children: [
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { CheckboxGroupProp } from "../../props/components/data-entry.prop.js";
|
|
2
2
|
export type { CheckboxGroupProp, CheckboxGroupProp as CheckboxGroupProps, } from "../../props/components/data-entry.prop.js";
|
|
3
|
-
export declare function CheckboxGroup({ value: controlledValue, defaultValue, onValueChange, options, orientation, disabled, name, className, children, }: CheckboxGroupProp): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export declare function CheckboxGroup({ value: controlledValue, defaultValue, onValueChange, options, orientation, disabled, name, id, className, children, ...ariaProps }: CheckboxGroupProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
|
+
import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
|
|
5
6
|
import { Checkbox } from "./checkbox.js";
|
|
6
7
|
import { Field } from "./field.js";
|
|
7
8
|
import { choiceGroupClassName } from "./choice-option.js";
|
|
@@ -24,11 +25,14 @@ function CheckboxGroup({
|
|
|
24
25
|
orientation = "vertical",
|
|
25
26
|
disabled,
|
|
26
27
|
name,
|
|
28
|
+
id,
|
|
27
29
|
className,
|
|
28
|
-
children
|
|
30
|
+
children,
|
|
31
|
+
...ariaProps
|
|
29
32
|
}) {
|
|
30
33
|
const reactId = React.useId();
|
|
31
34
|
const [value, setValue] = useControllableArray(controlledValue, defaultValue);
|
|
35
|
+
const groupA11y = pickGroupFieldA11y(ariaProps);
|
|
32
36
|
const toggle = (optionValue) => {
|
|
33
37
|
const next = value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue];
|
|
34
38
|
setValue(next);
|
|
@@ -39,16 +43,18 @@ function CheckboxGroup({
|
|
|
39
43
|
"div",
|
|
40
44
|
{
|
|
41
45
|
role: "group",
|
|
46
|
+
id,
|
|
47
|
+
...groupA11y,
|
|
42
48
|
"aria-disabled": disabled ? true : void 0,
|
|
43
49
|
"data-orientation": orientation,
|
|
44
50
|
className: choiceGroupClassName(orientation, className),
|
|
45
51
|
children: options.map((opt, index) => {
|
|
46
|
-
const
|
|
52
|
+
const id2 = `${reactId}-${opt.value}-${index}`;
|
|
47
53
|
const checked = value.includes(opt.value);
|
|
48
|
-
return /* @__PURE__ */ jsx(Field, { id, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(
|
|
54
|
+
return /* @__PURE__ */ jsx(Field, { id: id2, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(
|
|
49
55
|
Checkbox,
|
|
50
56
|
{
|
|
51
|
-
id,
|
|
57
|
+
id: id2,
|
|
52
58
|
name,
|
|
53
59
|
value: opt.value,
|
|
54
60
|
checked,
|
|
@@ -66,6 +72,9 @@ function CheckboxGroup({
|
|
|
66
72
|
"div",
|
|
67
73
|
{
|
|
68
74
|
role: "group",
|
|
75
|
+
id,
|
|
76
|
+
...groupA11y,
|
|
77
|
+
"aria-disabled": disabled ? true : void 0,
|
|
69
78
|
"data-orientation": orientation,
|
|
70
79
|
className: cn(choiceGroupClassName(orientation), className),
|
|
71
80
|
children
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ColorPickerProp } from "../../props/components/data-entry.prop.js";
|
|
2
2
|
export type { ColorPickerProp, ColorPickerProp as ColorPickerProps, } from "../../props/components/data-entry.prop.js";
|
|
3
|
-
export declare function ColorPicker({ value, onValueChange, disabled, className, id, showHexInput, }: ColorPickerProp): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export declare function ColorPicker({ value, onValueChange, disabled, className, id, showHexInput, ...ariaProps }: ColorPickerProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -3,6 +3,7 @@ import { 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
|
+
import { resolveFieldA11y } from "../../lib/field-a11y.js";
|
|
6
7
|
import { controlIconClass } from "../../lib/control-styles.js";
|
|
7
8
|
import { Input } from "./input.js";
|
|
8
9
|
const HEX_PATTERN = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
@@ -16,11 +17,13 @@ function ColorPicker({
|
|
|
16
17
|
disabled,
|
|
17
18
|
className,
|
|
18
19
|
id,
|
|
19
|
-
showHexInput = true
|
|
20
|
+
showHexInput = true,
|
|
21
|
+
...ariaProps
|
|
20
22
|
}) {
|
|
21
23
|
const { t } = useTranslation();
|
|
22
24
|
const [draft, setDraft] = React.useState(null);
|
|
23
25
|
const display = draft ?? value;
|
|
26
|
+
const swatchA11y = resolveFieldA11y(ariaProps, t("dataEntry.colorPicker.ariaLabel"));
|
|
24
27
|
const commit = (next) => {
|
|
25
28
|
const normalized = normalizeHex(next);
|
|
26
29
|
if (!HEX_PATTERN.test(normalized)) {
|
|
@@ -49,7 +52,7 @@ function ColorPicker({
|
|
|
49
52
|
disabled,
|
|
50
53
|
onChange: (event) => commit(event.target.value),
|
|
51
54
|
className: "ui-color-picker-input",
|
|
52
|
-
|
|
55
|
+
...swatchA11y
|
|
53
56
|
}
|
|
54
57
|
)
|
|
55
58
|
] }),
|
|
@@ -46,7 +46,7 @@ export declare const CommandGroup: React.ForwardRefExoticComponent<Omit<{
|
|
|
46
46
|
ref?: React.Ref<HTMLDivElement>;
|
|
47
47
|
} & {
|
|
48
48
|
asChild?: boolean;
|
|
49
|
-
}, keyof React.HTMLAttributes<HTMLDivElement> | "key" | "asChild">, "
|
|
49
|
+
}, keyof React.HTMLAttributes<HTMLDivElement> | "key" | "asChild">, "value" | "heading"> & {
|
|
50
50
|
heading?: React.ReactNode;
|
|
51
51
|
value?: string;
|
|
52
52
|
forceMount?: boolean;
|
|
@@ -57,7 +57,7 @@ export declare const CommandItem: React.ForwardRefExoticComponent<Omit<{
|
|
|
57
57
|
ref?: React.Ref<HTMLDivElement>;
|
|
58
58
|
} & {
|
|
59
59
|
asChild?: boolean;
|
|
60
|
-
}, keyof React.HTMLAttributes<HTMLDivElement> | "key" | "asChild">, "onSelect" | "
|
|
60
|
+
}, keyof React.HTMLAttributes<HTMLDivElement> | "key" | "asChild">, "onSelect" | "value" | "disabled"> & {
|
|
61
61
|
disabled?: boolean;
|
|
62
62
|
onSelect?: (value: string) => void;
|
|
63
63
|
value?: string;
|
|
@@ -6,4 +6,4 @@ export type { DatePickerProp, DatePickerProp as DatePickerProps, } from "../../p
|
|
|
6
6
|
* `name`), screen-reader friendly, and e2e-testable by simply filling the input. The calendar
|
|
7
7
|
* popover is the visual affordance; typing and the calendar stay in sync.
|
|
8
8
|
*/
|
|
9
|
-
export declare function DatePicker({ value: valueProp, defaultValue, onValueChange, placeholder, disabled, className, id, name, locale: localeProp, fromDate, toDate, allowClear, }: DatePickerProp): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export declare function DatePicker({ value: valueProp, defaultValue, onValueChange, placeholder, disabled, className, id, name, locale: localeProp, fromDate, toDate, allowClear, ...ariaProps }: DatePickerProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -5,6 +5,7 @@ import { CalendarIcon } from "lucide-react";
|
|
|
5
5
|
import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { parseDateInput, toIsoDate } from "../../lib/datetime/parse.js";
|
|
7
7
|
import { useControlledLatch } from "../../lib/hooks.js";
|
|
8
|
+
import { pickFieldA11y } from "../../lib/field-a11y.js";
|
|
8
9
|
import { cn } from "../../lib/utils.js";
|
|
9
10
|
import { Input } from "./input.js";
|
|
10
11
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
@@ -22,11 +23,15 @@ function DatePicker({
|
|
|
22
23
|
locale: localeProp,
|
|
23
24
|
fromDate,
|
|
24
25
|
toDate,
|
|
25
|
-
allowClear = true
|
|
26
|
+
allowClear = true,
|
|
27
|
+
...ariaProps
|
|
26
28
|
}) {
|
|
27
29
|
const { t } = useTranslation();
|
|
28
30
|
const { dayPickerLocale } = usePickerLocales(localeProp);
|
|
29
31
|
const [open, setOpen] = React.useState(false);
|
|
32
|
+
const fieldA11y = pickFieldA11y(ariaProps);
|
|
33
|
+
const reactId = React.useId();
|
|
34
|
+
const dialogId = `${id ?? reactId}-dialog`;
|
|
30
35
|
const isControlled = useControlledLatch(valueProp !== void 0);
|
|
31
36
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
32
37
|
const value = isControlled ? valueProp : internalValue;
|
|
@@ -69,6 +74,8 @@ function DatePicker({
|
|
|
69
74
|
role: "combobox",
|
|
70
75
|
"aria-expanded": open,
|
|
71
76
|
"aria-haspopup": "dialog",
|
|
77
|
+
"aria-controls": open ? dialogId : void 0,
|
|
78
|
+
...fieldA11y,
|
|
72
79
|
allowClear,
|
|
73
80
|
onClear: clear,
|
|
74
81
|
trailingIcon: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
@@ -106,6 +113,9 @@ function DatePicker({
|
|
|
106
113
|
/* @__PURE__ */ jsx(
|
|
107
114
|
PopoverContent,
|
|
108
115
|
{
|
|
116
|
+
id: dialogId,
|
|
117
|
+
role: "dialog",
|
|
118
|
+
"aria-label": t("dataEntry.datePicker.openCalendar") ?? "Calendar",
|
|
109
119
|
className: "w-auto p-0",
|
|
110
120
|
align: "start",
|
|
111
121
|
onOpenAutoFocus: (event) => event.preventDefault(),
|
|
@@ -7,4 +7,4 @@ export type { DateRangePickerProp, DateRangePickerProp as DateRangePickerProps,
|
|
|
7
7
|
* `${name}_to`, screen-reader friendly, e2e-testable by filling either input); the
|
|
8
8
|
* range calendar is the visual affordance.
|
|
9
9
|
*/
|
|
10
|
-
export declare function DateRangePicker({ value: valueProp, defaultValue, onValueChange, placeholder, disabled, className, id, name, locale: localeProp, fromDate, toDate, allowClear, }: DateRangePickerProp): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function DateRangePicker({ value: valueProp, defaultValue, onValueChange, placeholder, disabled, className, id, name, locale: localeProp, fromDate, toDate, allowClear, ...ariaProps }: DateRangePickerProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -5,6 +5,7 @@ import { ArrowRight, CalendarIcon, X } from "lucide-react";
|
|
|
5
5
|
import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { parseDateInput, toIsoDate } from "../../lib/datetime/index.js";
|
|
7
7
|
import { useControlledLatch } from "../../lib/hooks.js";
|
|
8
|
+
import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
|
|
8
9
|
import { cn } from "../../lib/utils.js";
|
|
9
10
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
10
11
|
import { Calendar } from "./calendar.js";
|
|
@@ -21,14 +22,17 @@ function DateRangePicker({
|
|
|
21
22
|
locale: localeProp,
|
|
22
23
|
fromDate,
|
|
23
24
|
toDate,
|
|
24
|
-
allowClear = true
|
|
25
|
+
allowClear = true,
|
|
26
|
+
...ariaProps
|
|
25
27
|
}) {
|
|
26
28
|
const { t } = useTranslation();
|
|
27
29
|
const { dayPickerLocale } = usePickerLocales(localeProp);
|
|
28
30
|
const [open, setOpen] = React.useState(false);
|
|
31
|
+
const groupA11y = pickGroupFieldA11y(ariaProps);
|
|
29
32
|
const autoId = React.useId();
|
|
30
|
-
const
|
|
31
|
-
const
|
|
33
|
+
const groupId = id ?? autoId;
|
|
34
|
+
const fromId = `${groupId}-from`;
|
|
35
|
+
const toId = `${groupId}-to`;
|
|
32
36
|
const isControlled = useControlledLatch(valueProp !== void 0);
|
|
33
37
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
34
38
|
const value = isControlled ? valueProp : internalValue;
|
|
@@ -70,6 +74,10 @@ function DateRangePicker({
|
|
|
70
74
|
return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
71
75
|
"div",
|
|
72
76
|
{
|
|
77
|
+
role: "group",
|
|
78
|
+
id: groupId,
|
|
79
|
+
...groupA11y,
|
|
80
|
+
"aria-disabled": disabled ? true : void 0,
|
|
73
81
|
className: cn(
|
|
74
82
|
// One input-styled shell for the whole range — mirrors Input's control
|
|
75
83
|
// tokens (border/radius/ring) so it reads as a single form field.
|
|
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Label } from "../data-entry/label.js";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { mergeAriaIds } from "../../lib/field-a11y.js";
|
|
6
7
|
import { useFormLayout } from "./form.js";
|
|
7
8
|
const toCssLength = (v) => typeof v === "number" ? `${v}px` : v;
|
|
8
9
|
const FOCUSABLE_SELECTOR = 'input:not([type="hidden"]), select, textarea, button, [tabindex]:not([tabindex="-1"])';
|
|
@@ -37,15 +38,18 @@ function FormField({
|
|
|
37
38
|
);
|
|
38
39
|
}
|
|
39
40
|
const childProps = React.isValidElement(children) ? children.props : void 0;
|
|
40
|
-
const mergeIds =
|
|
41
|
-
" "
|
|
42
|
-
) || void 0;
|
|
41
|
+
const mergeIds = mergeAriaIds;
|
|
43
42
|
const childWithA11y = React.isValidElement(children) ? React.cloneElement(children, {
|
|
44
43
|
// The label is associated via aria-labelledby (not <label for>): composite
|
|
45
44
|
// controls (Radio.Group, checkbox lists, range pairs) have no labelable root,
|
|
46
45
|
// and a dangling `for` triggers Chrome's "Incorrect use of <label>" issue.
|
|
47
46
|
id: childProps?.id ?? resolvedId,
|
|
48
47
|
"aria-labelledby": childProps?.["aria-labelledby"] ?? labelId,
|
|
48
|
+
// Redundant `aria-label` fallback (belt-and-suspenders): the accessible name is the
|
|
49
|
+
// SAME string as the visible label, just reachable even if an aria-labelledby lookup
|
|
50
|
+
// ever comes back empty (id-ref timing, AT quirks). Only when `label` is plain text and
|
|
51
|
+
// the child hasn't already set its own aria-label.
|
|
52
|
+
"aria-label": childProps?.["aria-label"] ?? (typeof label === "string" ? label : void 0),
|
|
49
53
|
// Helper and error can coexist: helper stays on aria-describedby, the error on
|
|
50
54
|
// aria-errormessage (surfaced when aria-invalid is true).
|
|
51
55
|
"aria-describedby": mergeIds(
|
|
@@ -6,4 +6,4 @@ export type { MonthPickerProp, MonthPickerProp as MonthPickerProps, } from "../.
|
|
|
6
6
|
* The field stays a real, typeable input (form-submittable via `name` as
|
|
7
7
|
* `yyyy-MM`); the grid is the visual affordance.
|
|
8
8
|
*/
|
|
9
|
-
export declare function MonthPicker({ value: valueProp, defaultValue, onValueChange, placeholder, disabled, className, id, name, fromYear, toYear, allowClear, }: MonthPickerProp): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export declare function MonthPicker({ value: valueProp, defaultValue, onValueChange, placeholder, disabled, className, id, name, fromYear, toYear, allowClear, ...ariaProps }: MonthPickerProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { CalendarIcon, ChevronLeft, ChevronRight, X } from "lucide-react";
|
|
5
5
|
import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { useControlledLatch } from "../../lib/hooks.js";
|
|
7
|
+
import { pickFieldA11y } from "../../lib/field-a11y.js";
|
|
7
8
|
import { cn } from "../../lib/utils.js";
|
|
8
9
|
import { Button } from "../general/button.js";
|
|
9
10
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
@@ -27,13 +28,16 @@ function MonthPicker({
|
|
|
27
28
|
name,
|
|
28
29
|
fromYear,
|
|
29
30
|
toYear,
|
|
30
|
-
allowClear = true
|
|
31
|
+
allowClear = true,
|
|
32
|
+
...ariaProps
|
|
31
33
|
}) {
|
|
32
34
|
const { t } = useTranslation();
|
|
33
35
|
const { locale } = usePickerLocales();
|
|
34
36
|
const [open, setOpen] = React.useState(false);
|
|
35
37
|
const autoId = React.useId();
|
|
36
38
|
const inputId = id ?? autoId;
|
|
39
|
+
const dialogId = `${inputId}-dialog`;
|
|
40
|
+
const fieldA11y = pickFieldA11y(ariaProps);
|
|
37
41
|
const isControlled = useControlledLatch(valueProp !== void 0);
|
|
38
42
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
39
43
|
const value = isControlled ? valueProp : internalValue;
|
|
@@ -78,6 +82,11 @@ function MonthPicker({
|
|
|
78
82
|
placeholder: placeholder ?? t("dataEntry.monthPicker.placeholder") ?? YM_HINT,
|
|
79
83
|
inputMode: "numeric",
|
|
80
84
|
autoComplete: "off",
|
|
85
|
+
role: "combobox",
|
|
86
|
+
"aria-expanded": open,
|
|
87
|
+
"aria-haspopup": "dialog",
|
|
88
|
+
"aria-controls": open ? dialogId : void 0,
|
|
89
|
+
...fieldA11y,
|
|
81
90
|
className: "placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent tabular-nums outline-none disabled:cursor-not-allowed",
|
|
82
91
|
onKeyDown: (event) => {
|
|
83
92
|
if (event.key === "ArrowDown") {
|
|
@@ -124,6 +133,9 @@ function MonthPicker({
|
|
|
124
133
|
/* @__PURE__ */ jsxs(
|
|
125
134
|
PopoverContent,
|
|
126
135
|
{
|
|
136
|
+
id: dialogId,
|
|
137
|
+
role: "dialog",
|
|
138
|
+
"aria-label": t("dataEntry.monthPicker.openGrid") ?? "Month grid",
|
|
127
139
|
className: "w-auto p-3",
|
|
128
140
|
align: "start",
|
|
129
141
|
onOpenAutoFocus: (event) => event.preventDefault(),
|
|
@@ -8,4 +8,4 @@ export type { MonthRangePickerProp, MonthRangePickerProp as MonthRangePickerProp
|
|
|
8
8
|
* affordance. Grid picks are two-step (from, then to — swapped when picked
|
|
9
9
|
* backwards) and a pick on a COMPLETE range starts a new one (reset-on-complete).
|
|
10
10
|
*/
|
|
11
|
-
export declare function MonthRangePicker({ value: valueProp, defaultValue, onValueChange, placeholder, disabled, className, id, name, fromYear, toYear, allowClear, }: MonthRangePickerProp): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function MonthRangePicker({ value: valueProp, defaultValue, onValueChange, placeholder, disabled, className, id, name, fromYear, toYear, allowClear, ...ariaProps }: MonthRangePickerProp): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { ArrowRight, CalendarIcon, ChevronLeft, ChevronRight, X } from "lucide-react";
|
|
5
5
|
import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { useControlledLatch } from "../../lib/hooks.js";
|
|
7
|
+
import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
|
|
7
8
|
import { cn } from "../../lib/utils.js";
|
|
8
9
|
import { Button } from "../general/button.js";
|
|
9
10
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
@@ -28,14 +29,17 @@ function MonthRangePicker({
|
|
|
28
29
|
name,
|
|
29
30
|
fromYear,
|
|
30
31
|
toYear,
|
|
31
|
-
allowClear = true
|
|
32
|
+
allowClear = true,
|
|
33
|
+
...ariaProps
|
|
32
34
|
}) {
|
|
33
35
|
const { t } = useTranslation();
|
|
34
36
|
const { locale } = usePickerLocales();
|
|
35
37
|
const [open, setOpen] = React.useState(false);
|
|
38
|
+
const groupA11y = pickGroupFieldA11y(ariaProps);
|
|
36
39
|
const autoId = React.useId();
|
|
37
|
-
const
|
|
38
|
-
const
|
|
40
|
+
const groupId = id ?? autoId;
|
|
41
|
+
const fromId = `${groupId}-from`;
|
|
42
|
+
const toId = `${groupId}-to`;
|
|
39
43
|
const isControlled = useControlledLatch(valueProp !== void 0);
|
|
40
44
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
41
45
|
const value = isControlled ? valueProp : internalValue;
|
|
@@ -100,6 +104,10 @@ function MonthRangePicker({
|
|
|
100
104
|
return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
101
105
|
"div",
|
|
102
106
|
{
|
|
107
|
+
role: "group",
|
|
108
|
+
id: groupId,
|
|
109
|
+
...groupA11y,
|
|
110
|
+
"aria-disabled": disabled ? true : void 0,
|
|
103
111
|
className: cn(
|
|
104
112
|
"ui-control border-input bg-background flex w-full min-w-0 items-center gap-2 rounded-[var(--control-radius)] transition-[color,box-shadow] outline-none",
|
|
105
113
|
"focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]",
|