@godxjp/ui 17.0.1 → 18.0.1
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 +19 -4
- 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.d.ts +20 -1
- package/dist/components/data-display/table.js +23 -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 +15 -7
- 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/components/ui/tag-input.js +1 -0
- 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/app.prop.d.ts +9 -5
- 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/styles/layout.css +46 -9
- package/dist/styles/shell-layout.css +19 -0
- package/package.json +8 -4
|
@@ -88,12 +88,15 @@ function Pagination({
|
|
|
88
88
|
pageSizeOptions = [10, 20, 50, 100],
|
|
89
89
|
showSizeChanger,
|
|
90
90
|
showTotal,
|
|
91
|
+
hideOnSinglePage = true,
|
|
91
92
|
simple,
|
|
92
93
|
disabled,
|
|
93
94
|
className,
|
|
94
|
-
onValueChange
|
|
95
|
+
onValueChange,
|
|
96
|
+
"aria-label": ariaLabel
|
|
95
97
|
}) {
|
|
96
98
|
const { t } = useTranslation();
|
|
99
|
+
const navLabel = ariaLabel ?? t("navigation.pagination.ariaLabel");
|
|
97
100
|
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
98
101
|
const safeCurrent = Math.min(Math.max(1, value), totalPages);
|
|
99
102
|
const pages = buildPageRange(safeCurrent, totalPages);
|
|
@@ -106,112 +109,98 @@ function Pagination({
|
|
|
106
109
|
total === 0 ? 0 : (safeCurrent - 1) * pageSize + 1,
|
|
107
110
|
Math.min(safeCurrent * pageSize, total)
|
|
108
111
|
]) : showTotal ? t("navigation.pagination.total", { total }) : null;
|
|
109
|
-
if (total <=
|
|
112
|
+
if (total <= 0) return null;
|
|
113
|
+
if (hideOnSinglePage && totalPages <= 1) return null;
|
|
110
114
|
if (simple) {
|
|
111
|
-
return /* @__PURE__ */ jsxs(
|
|
112
|
-
"
|
|
115
|
+
return /* @__PURE__ */ jsxs("nav", { "aria-label": navLabel, "data-simple": "true", className: cn("ui-pagination", className), children: [
|
|
116
|
+
totalLabel && /* @__PURE__ */ jsx("span", { className: "ui-pagination-total", children: totalLabel }),
|
|
117
|
+
/* @__PURE__ */ jsx(
|
|
118
|
+
Button,
|
|
119
|
+
{
|
|
120
|
+
type: "button",
|
|
121
|
+
variant: "outline",
|
|
122
|
+
size: "sm",
|
|
123
|
+
disabled: Boolean(disabled) || safeCurrent <= 1,
|
|
124
|
+
"aria-label": t("navigation.pagination.prev"),
|
|
125
|
+
onClick: () => go(safeCurrent - 1),
|
|
126
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
|
|
127
|
+
}
|
|
128
|
+
),
|
|
129
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-pagination-count", children: [
|
|
130
|
+
safeCurrent,
|
|
131
|
+
" / ",
|
|
132
|
+
totalPages
|
|
133
|
+
] }),
|
|
134
|
+
/* @__PURE__ */ jsx(
|
|
135
|
+
Button,
|
|
136
|
+
{
|
|
137
|
+
type: "button",
|
|
138
|
+
variant: "outline",
|
|
139
|
+
size: "sm",
|
|
140
|
+
disabled: Boolean(disabled) || safeCurrent >= totalPages,
|
|
141
|
+
"aria-label": t("navigation.pagination.next"),
|
|
142
|
+
onClick: () => go(safeCurrent + 1),
|
|
143
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
] });
|
|
147
|
+
}
|
|
148
|
+
return /* @__PURE__ */ jsxs("nav", { "aria-label": navLabel, className: cn("ui-pagination", className), children: [
|
|
149
|
+
totalLabel && /* @__PURE__ */ jsx("span", { className: "ui-pagination-total", children: totalLabel }),
|
|
150
|
+
showSizeChanger && /* @__PURE__ */ jsxs(
|
|
151
|
+
Select,
|
|
113
152
|
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
153
|
+
value: String(pageSize),
|
|
154
|
+
onValueChange: (v) => go(1, Number(v)),
|
|
155
|
+
disabled,
|
|
117
156
|
children: [
|
|
118
|
-
totalLabel && /* @__PURE__ */ jsx("span", { className: "ui-pagination-total", children: totalLabel }),
|
|
119
157
|
/* @__PURE__ */ jsx(
|
|
120
|
-
|
|
158
|
+
SelectTrigger,
|
|
121
159
|
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
disabled: Boolean(disabled) || safeCurrent <= 1,
|
|
126
|
-
"aria-label": t("navigation.pagination.prev"),
|
|
127
|
-
onClick: () => go(safeCurrent - 1),
|
|
128
|
-
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
|
|
160
|
+
className: "ui-pagination-size-trigger w-[var(--pagination-size-width)]",
|
|
161
|
+
"aria-label": t("navigation.pagination.pageSize"),
|
|
162
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
129
163
|
}
|
|
130
164
|
),
|
|
131
|
-
/* @__PURE__ */
|
|
132
|
-
safeCurrent,
|
|
133
|
-
" / ",
|
|
134
|
-
totalPages
|
|
135
|
-
] }),
|
|
136
|
-
/* @__PURE__ */ jsx(
|
|
137
|
-
Button,
|
|
138
|
-
{
|
|
139
|
-
type: "button",
|
|
140
|
-
variant: "outline",
|
|
141
|
-
size: "sm",
|
|
142
|
-
disabled: Boolean(disabled) || safeCurrent >= totalPages,
|
|
143
|
-
"aria-label": t("navigation.pagination.next"),
|
|
144
|
-
onClick: () => go(safeCurrent + 1),
|
|
145
|
-
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
|
|
146
|
-
}
|
|
147
|
-
)
|
|
165
|
+
/* @__PURE__ */ jsx(SelectContent, { children: pageSizeOptions.map((size) => /* @__PURE__ */ jsx(SelectItem, { value: String(size), children: t("navigation.pagination.pageSizeOption", { size }) }, size)) })
|
|
148
166
|
]
|
|
149
167
|
}
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
),
|
|
169
|
+
/* @__PURE__ */ jsxs(PaginationContent, { children: [
|
|
170
|
+
/* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(
|
|
171
|
+
PaginationPrevious,
|
|
172
|
+
{
|
|
173
|
+
"aria-label": t("navigation.pagination.prev"),
|
|
174
|
+
onClick: () => go(safeCurrent - 1),
|
|
175
|
+
disabled: Boolean(disabled) || safeCurrent <= 1,
|
|
176
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true" })
|
|
177
|
+
}
|
|
178
|
+
) }),
|
|
179
|
+
pages.map(
|
|
180
|
+
(page, index) => page === "ellipsis" ? /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(PaginationEllipsis, {}) }, `e-${index}`) : /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(
|
|
181
|
+
PaginationLink,
|
|
161
182
|
{
|
|
162
|
-
|
|
163
|
-
|
|
183
|
+
isActive: page === safeCurrent,
|
|
184
|
+
"aria-label": t("navigation.pagination.page", { page }),
|
|
185
|
+
onClick: () => {
|
|
186
|
+
if (!disabled) go(page);
|
|
187
|
+
},
|
|
164
188
|
disabled,
|
|
165
|
-
children:
|
|
166
|
-
/* @__PURE__ */ jsx(
|
|
167
|
-
SelectTrigger,
|
|
168
|
-
{
|
|
169
|
-
className: "ui-pagination-size-trigger w-[var(--pagination-size-width)]",
|
|
170
|
-
"aria-label": t("navigation.pagination.pageSize"),
|
|
171
|
-
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
172
|
-
}
|
|
173
|
-
),
|
|
174
|
-
/* @__PURE__ */ jsx(SelectContent, { children: pageSizeOptions.map((size) => /* @__PURE__ */ jsx(SelectItem, { value: String(size), children: t("navigation.pagination.pageSizeOption", { size }) }, size)) })
|
|
175
|
-
]
|
|
189
|
+
children: page
|
|
176
190
|
}
|
|
177
|
-
),
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
PaginationLink,
|
|
191
|
-
{
|
|
192
|
-
isActive: page === safeCurrent,
|
|
193
|
-
"aria-label": t("navigation.pagination.page", { page }),
|
|
194
|
-
onClick: () => {
|
|
195
|
-
if (!disabled) go(page);
|
|
196
|
-
},
|
|
197
|
-
disabled,
|
|
198
|
-
children: page
|
|
199
|
-
}
|
|
200
|
-
) }, page)
|
|
201
|
-
),
|
|
202
|
-
/* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(
|
|
203
|
-
PaginationNext,
|
|
204
|
-
{
|
|
205
|
-
"aria-label": t("navigation.pagination.next"),
|
|
206
|
-
onClick: () => go(safeCurrent + 1),
|
|
207
|
-
disabled: Boolean(disabled) || safeCurrent >= totalPages,
|
|
208
|
-
children: /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
|
|
209
|
-
}
|
|
210
|
-
) })
|
|
211
|
-
] })
|
|
212
|
-
]
|
|
213
|
-
}
|
|
214
|
-
);
|
|
191
|
+
) }, page)
|
|
192
|
+
),
|
|
193
|
+
/* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(
|
|
194
|
+
PaginationNext,
|
|
195
|
+
{
|
|
196
|
+
"aria-label": t("navigation.pagination.next"),
|
|
197
|
+
onClick: () => go(safeCurrent + 1),
|
|
198
|
+
disabled: Boolean(disabled) || safeCurrent >= totalPages,
|
|
199
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
|
|
200
|
+
}
|
|
201
|
+
) })
|
|
202
|
+
] })
|
|
203
|
+
] });
|
|
215
204
|
}
|
|
216
205
|
export {
|
|
217
206
|
Pagination,
|
|
@@ -3,6 +3,14 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
function resolveFallbackTabValue(items, requested) {
|
|
7
|
+
if (!items || items.length === 0) return requested;
|
|
8
|
+
if (requested !== void 0) {
|
|
9
|
+
const requestedItem = items.find((item) => item.value === requested);
|
|
10
|
+
if (requestedItem && !requestedItem.disabled) return requested;
|
|
11
|
+
}
|
|
12
|
+
return items.find((item) => !item.disabled)?.value;
|
|
13
|
+
}
|
|
6
14
|
function Tabs({
|
|
7
15
|
className,
|
|
8
16
|
orientation = "horizontal",
|
|
@@ -14,8 +22,7 @@ function Tabs({
|
|
|
14
22
|
contentClassName,
|
|
15
23
|
...props
|
|
16
24
|
}) {
|
|
17
|
-
const
|
|
18
|
-
const resolvedDefault = defaultValue ?? firstValue;
|
|
25
|
+
const resolvedDefault = resolveFallbackTabValue(items, defaultValue);
|
|
19
26
|
return /* @__PURE__ */ jsx(
|
|
20
27
|
TabsPrimitive.Root,
|
|
21
28
|
{
|
|
@@ -24,7 +31,10 @@ function Tabs({
|
|
|
24
31
|
orientation,
|
|
25
32
|
value,
|
|
26
33
|
defaultValue: value === void 0 ? resolvedDefault : void 0,
|
|
27
|
-
className: cn(
|
|
34
|
+
className: cn(
|
|
35
|
+
"group/tabs flex min-w-0 gap-2 data-[orientation=horizontal]:flex-col",
|
|
36
|
+
className
|
|
37
|
+
),
|
|
28
38
|
...props,
|
|
29
39
|
children: items ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
30
40
|
/* @__PURE__ */ jsx(
|
|
@@ -72,7 +82,14 @@ const TabsList = React.forwardRef(({ className, variant = "default", ...props },
|
|
|
72
82
|
"data-slot": "tabs-list",
|
|
73
83
|
"data-variant": variant,
|
|
74
84
|
className: cn(
|
|
75
|
-
|
|
85
|
+
// `min-w-0 max-w-full` let the list shrink to (and never exceed) whatever width its
|
|
86
|
+
// ancestors actually give it instead of forcing them wider; horizontal orientation then
|
|
87
|
+
// scrolls its own overflow rather than clipping/hiding long localized labels in a narrow
|
|
88
|
+
// container (gh#175). Hidden scrollbar keeps the strip visually clean while staying
|
|
89
|
+
// swipeable on touch and reachable via keyboard (arrow-key roving focus still scrolls the
|
|
90
|
+
// newly-focused trigger into view natively). Vertical orientation is untouched — it already
|
|
91
|
+
// stacks in a column and is sized by its own `h-*`/`w-*` overrides.
|
|
92
|
+
"group/tabs-list text-muted-foreground data-[variant=default]:bg-muted inline-flex w-fit max-w-full min-w-0 items-center justify-center rounded-lg p-1 group-data-[orientation=vertical]/tabs:flex-col data-[orientation=horizontal]:[scrollbar-width:none] data-[orientation=horizontal]:overflow-x-auto data-[orientation=horizontal]:overflow-y-hidden data-[variant=line]:gap-1 data-[variant=line]:rounded-none data-[variant=line]:bg-transparent [&[data-orientation=horizontal]::-webkit-scrollbar]:hidden",
|
|
76
93
|
className
|
|
77
94
|
),
|
|
78
95
|
...props
|
|
@@ -43,6 +43,7 @@ const TagInput = React.forwardRef(
|
|
|
43
43
|
"div",
|
|
44
44
|
{
|
|
45
45
|
"data-slot": "tag-input",
|
|
46
|
+
"aria-disabled": disabled || void 0,
|
|
46
47
|
className: cn("ui-tag-input", disabled && "ui-tag-input-disabled", className),
|
|
47
48
|
children: [
|
|
48
49
|
tags.length > 0 ? /* @__PURE__ */ jsx("ul", { role: "list", className: "ui-tag-input-list", "data-slot": "tag-input-list", children: tags.map((tag, i) => /* @__PURE__ */ jsxs(
|
|
@@ -129,7 +129,9 @@
|
|
|
129
129
|
"sidebarLabel": "Sidebar",
|
|
130
130
|
"headerLabel": "Header",
|
|
131
131
|
"mainLabel": "Main content",
|
|
132
|
-
"footerLabel": "Footer"
|
|
132
|
+
"footerLabel": "Footer",
|
|
133
|
+
"openNav": "Open navigation menu",
|
|
134
|
+
"navLabel": "Menu"
|
|
133
135
|
},
|
|
134
136
|
"authShell": {
|
|
135
137
|
"brandLabel": "Brand",
|
|
@@ -129,7 +129,9 @@
|
|
|
129
129
|
"sidebarLabel": "Thanh bên",
|
|
130
130
|
"headerLabel": "Đầu trang",
|
|
131
131
|
"mainLabel": "Nội dung chính",
|
|
132
|
-
"footerLabel": "Chân trang"
|
|
132
|
+
"footerLabel": "Chân trang",
|
|
133
|
+
"openNav": "Mở menu điều hướng",
|
|
134
|
+
"navLabel": "Menu"
|
|
133
135
|
},
|
|
134
136
|
"authShell": {
|
|
135
137
|
"brandLabel": "Thương hiệu",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The accessible-name / description / validation contract that {@link FormField} injects onto its
|
|
4
|
+
* single control child (via `cloneElement`). EVERY form-capable `@godxjp/ui` component accepts
|
|
5
|
+
* these props and forwards them to its **semantic focus target** — the real `<input>` / combobox /
|
|
6
|
+
* trigger the user tabs to, never a presentational wrapper `<div>`. Without this forwarding the
|
|
7
|
+
* visible label / helper / error rendered by FormField is silently disconnected from the control
|
|
8
|
+
* for assistive technology (issue #164).
|
|
9
|
+
*
|
|
10
|
+
* These are the standard WAI-ARIA field relationships:
|
|
11
|
+
* - `aria-labelledby` — points at the FormField label → the control's accessible **name**.
|
|
12
|
+
* - `aria-describedby` — points at helper text → the control's **description**.
|
|
13
|
+
* - `aria-errormessage` + `aria-invalid` — the validation message, announced when invalid.
|
|
14
|
+
* - `aria-required` — required-field semantics.
|
|
15
|
+
* - `aria-label` — a name supplied directly (used when there is no visible label element).
|
|
16
|
+
*/
|
|
17
|
+
export interface FieldA11yProps {
|
|
18
|
+
"aria-label"?: string;
|
|
19
|
+
"aria-labelledby"?: string;
|
|
20
|
+
"aria-describedby"?: string;
|
|
21
|
+
"aria-errormessage"?: string;
|
|
22
|
+
"aria-invalid"?: React.AriaAttributes["aria-invalid"];
|
|
23
|
+
"aria-required"?: React.AriaAttributes["aria-required"];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Merge one or more space-separated id-reference lists (`aria-describedby`, `aria-labelledby`,
|
|
27
|
+
* `aria-errormessage`) into a single deduplicated token list. Returns `undefined` when empty so the
|
|
28
|
+
* attribute is omitted rather than rendered as `""`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mergeAriaIds(...values: Array<string | undefined>): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Extract ONLY the defined field-a11y attributes from a props bag, ready to spread onto a
|
|
33
|
+
* component's semantic focus target. Undefined entries are dropped so a forwarded contract never
|
|
34
|
+
* clobbers a control's own intrinsic `aria-label` / `aria-invalid`.
|
|
35
|
+
*
|
|
36
|
+
* ```tsx
|
|
37
|
+
* const fieldA11y = pickFieldA11y(props);
|
|
38
|
+
* return <input {...fieldA11y} />; // the real focus target owns the relationship
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function pickFieldA11y(props: FieldA11yProps): FieldA11yProps;
|
|
42
|
+
/**
|
|
43
|
+
* As {@link pickFieldA11y}, but resolves the accessible **name** for a control that also has an
|
|
44
|
+
* intrinsic `aria-label` (e.g. SearchInput's built-in "Search"): when the FormField supplies an
|
|
45
|
+
* `aria-labelledby`, it wins and the intrinsic `aria-label` is dropped (a control must not carry
|
|
46
|
+
* both — `aria-labelledby` takes precedence and the redundant `aria-label` only adds noise).
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveFieldA11y(props: FieldA11yProps, intrinsicAriaLabel?: string): FieldA11yProps;
|
|
49
|
+
/**
|
|
50
|
+
* The field-a11y attributes valid on a **group container** (`role="group"`), used by composite
|
|
51
|
+
* controls that have no single labelable focus target — CheckboxGroup, and the two-input range
|
|
52
|
+
* pickers / Transfer shuttle.
|
|
53
|
+
*
|
|
54
|
+
* Per WAI-ARIA 1.2 only `aria-labelledby` and `aria-describedby` are globally allowed on
|
|
55
|
+
* `role="group"`; `aria-invalid` / `aria-errormessage` / `aria-required` are widget-only and would
|
|
56
|
+
* be an invalid-ARIA violation on a group. So the validation message id (`aria-errormessage`) is
|
|
57
|
+
* **folded into `aria-describedby`** — the error text is still announced as part of the group's
|
|
58
|
+
* description, and FormField's `role="alert"` error node announces it live regardless.
|
|
59
|
+
*
|
|
60
|
+
* `role="radiogroup"` IS a widget and supports the full validation set — those groups use
|
|
61
|
+
* {@link pickFieldA11y} directly, not this helper.
|
|
62
|
+
*/
|
|
63
|
+
export declare function pickGroupFieldA11y(props: FieldA11yProps): {
|
|
64
|
+
"aria-labelledby"?: string;
|
|
65
|
+
"aria-describedby"?: string;
|
|
66
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const FIELD_A11Y_KEYS = [
|
|
2
|
+
"aria-label",
|
|
3
|
+
"aria-labelledby",
|
|
4
|
+
"aria-describedby",
|
|
5
|
+
"aria-errormessage",
|
|
6
|
+
"aria-invalid",
|
|
7
|
+
"aria-required"
|
|
8
|
+
];
|
|
9
|
+
function mergeAriaIds(...values) {
|
|
10
|
+
return Array.from(new Set(values.flatMap((value) => value?.split(/\s+/).filter(Boolean) ?? []))).join(
|
|
11
|
+
" "
|
|
12
|
+
) || void 0;
|
|
13
|
+
}
|
|
14
|
+
function pickFieldA11y(props) {
|
|
15
|
+
const out = {};
|
|
16
|
+
for (const key of FIELD_A11Y_KEYS) {
|
|
17
|
+
const value = props[key];
|
|
18
|
+
if (value !== void 0) out[key] = value;
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
function resolveFieldA11y(props, intrinsicAriaLabel) {
|
|
23
|
+
const picked = pickFieldA11y(props);
|
|
24
|
+
if (picked["aria-labelledby"]) {
|
|
25
|
+
return picked;
|
|
26
|
+
}
|
|
27
|
+
if (picked["aria-label"] === void 0 && intrinsicAriaLabel !== void 0) {
|
|
28
|
+
picked["aria-label"] = intrinsicAriaLabel;
|
|
29
|
+
}
|
|
30
|
+
return picked;
|
|
31
|
+
}
|
|
32
|
+
function pickGroupFieldA11y(props) {
|
|
33
|
+
const describedBy = mergeAriaIds(props["aria-describedby"], props["aria-errormessage"]);
|
|
34
|
+
return {
|
|
35
|
+
...props["aria-labelledby"] !== void 0 ? { "aria-labelledby": props["aria-labelledby"] } : {},
|
|
36
|
+
...describedBy !== void 0 ? { "aria-describedby": describedBy } : {}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
mergeAriaIds,
|
|
41
|
+
pickFieldA11y,
|
|
42
|
+
pickGroupFieldA11y,
|
|
43
|
+
resolveFieldA11y
|
|
44
|
+
};
|
|
@@ -69,11 +69,15 @@ export type AppSettingKind = "locale" | "timezone" | "dateFormat" | "timeFormat"
|
|
|
69
69
|
export type AppSettingPickerProp = {
|
|
70
70
|
kind: AppSettingKind;
|
|
71
71
|
/**
|
|
72
|
-
* Trigger presentation. `"labeled"`
|
|
73
|
-
*
|
|
74
|
-
* language switcher): it structurally drops the value text and the picker's owned width —
|
|
75
|
-
* descendant-selector CSS overrides needed — while always keeping the localized `aria-label`,
|
|
76
|
-
* an icon-only trigger can never ship without an accessible name.
|
|
72
|
+
* Trigger presentation. `"labeled"` shows the leading icon + the selected value in a control
|
|
73
|
+
* sized to the setting. `"icon"` renders a supported square, icon-only topbar trigger (e.g. a
|
|
74
|
+
* globe language switcher): it structurally drops the value text and the picker's owned width —
|
|
75
|
+
* no descendant-selector CSS overrides needed — while always keeping the localized `aria-label`,
|
|
76
|
+
* so an icon-only trigger can never ship without an accessible name.
|
|
77
|
+
*
|
|
78
|
+
* Default is kind-dependent: `kind="locale"` defaults to `"icon"` (its product contract is the
|
|
79
|
+
* compact language switcher); every other kind defaults to `"labeled"`. Override explicitly for
|
|
80
|
+
* e.g. a labeled locale row inside a settings form (`appearance="labeled"`).
|
|
77
81
|
*/
|
|
78
82
|
appearance?: AppSettingPickerAppearanceProp;
|
|
79
83
|
className?: ClassNameProp;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Data Display component prop types — @see docs/COMPONENTS.md#data-display */
|
|
2
2
|
import type * as React from "react";
|
|
3
|
-
import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, ChildrenProp, ToneProp } from "../vocabulary/index.js";
|
|
3
|
+
import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, ChildrenProp, ToneProp, HeadingLevelProp } from "../vocabulary/index.js";
|
|
4
4
|
/** @see EmptyState */
|
|
5
5
|
/**
|
|
6
6
|
* Semantic intent of the EmptyState icon medallion — a subset of the shared `ToneProp` vocabulary
|
|
@@ -22,6 +22,21 @@ export type EmptyStateProp = {
|
|
|
22
22
|
* consumer never hand-rolls a `.ui-success-state` class to recolour it.
|
|
23
23
|
*/
|
|
24
24
|
tone?: EmptyStateToneProp;
|
|
25
|
+
/**
|
|
26
|
+
* Semantic heading level (`h1`–`h4`) for the title. Default `3`. Choose it to
|
|
27
|
+
* keep the page outline valid (`h1 → h2 → h3`, no skipped levels), NOT for
|
|
28
|
+
* visual size — the title size is fixed by `--empty-state` styles regardless
|
|
29
|
+
* of level. A page/onboarding empty state directly under a page `h1` uses
|
|
30
|
+
* `titleLevel={2}`; an empty state nested in an already-`h2` section uses `3`.
|
|
31
|
+
*/
|
|
32
|
+
titleLevel?: HeadingLevelProp;
|
|
33
|
+
/**
|
|
34
|
+
* Render the title as a non-heading element (`p` / `div`) instead of a
|
|
35
|
+
* heading. Use for a `compact`/`section` empty state placed inside a section
|
|
36
|
+
* that already owns its heading, so the zero-state message is not announced as
|
|
37
|
+
* a heading and cannot skip an outline level. Overrides `titleLevel`.
|
|
38
|
+
*/
|
|
39
|
+
titleAs?: "h1" | "h2" | "h3" | "h4" | "p" | "div";
|
|
25
40
|
className?: ClassNameProp;
|
|
26
41
|
};
|
|
27
42
|
/** @see Descriptions */
|