@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.
Files changed (78) hide show
  1. package/dist/components/data-display/card.d.ts +37 -1
  2. package/dist/components/data-display/card.js +7 -1
  3. package/dist/components/data-display/data-table.js +18 -3
  4. package/dist/components/data-display/empty-state.d.ts +1 -1
  5. package/dist/components/data-display/empty-state.js +5 -1
  6. package/dist/components/data-display/scroll-area.js +1 -1
  7. package/dist/components/data-display/table.js +14 -9
  8. package/dist/components/data-entry/calendar.js +4 -0
  9. package/dist/components/data-entry/cascader.d.ts +1 -1
  10. package/dist/components/data-entry/cascader.js +10 -1
  11. package/dist/components/data-entry/checkbox-group.d.ts +1 -1
  12. package/dist/components/data-entry/checkbox-group.js +13 -4
  13. package/dist/components/data-entry/color-picker.d.ts +1 -1
  14. package/dist/components/data-entry/color-picker.js +5 -2
  15. package/dist/components/data-entry/command.d.ts +2 -2
  16. package/dist/components/data-entry/date-picker.d.ts +1 -1
  17. package/dist/components/data-entry/date-picker.js +11 -1
  18. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  19. package/dist/components/data-entry/date-range-picker.js +11 -3
  20. package/dist/components/data-entry/form-field.js +7 -3
  21. package/dist/components/data-entry/month-picker.d.ts +1 -1
  22. package/dist/components/data-entry/month-picker.js +13 -1
  23. package/dist/components/data-entry/month-range-picker.d.ts +1 -1
  24. package/dist/components/data-entry/month-range-picker.js +11 -3
  25. package/dist/components/data-entry/number-input.d.ts +19 -2
  26. package/dist/components/data-entry/number-input.js +5 -7
  27. package/dist/components/data-entry/radio.d.ts +1 -1
  28. package/dist/components/data-entry/radio.js +11 -3
  29. package/dist/components/data-entry/search-input.d.ts +3 -2
  30. package/dist/components/data-entry/search-input.js +5 -2
  31. package/dist/components/data-entry/search-select.d.ts +1 -1
  32. package/dist/components/data-entry/search-select.js +51 -10
  33. package/dist/components/data-entry/select.d.ts +7 -0
  34. package/dist/components/data-entry/select.js +21 -3
  35. package/dist/components/data-entry/time-picker.d.ts +1 -1
  36. package/dist/components/data-entry/time-picker.js +11 -1
  37. package/dist/components/data-entry/transfer.d.ts +1 -1
  38. package/dist/components/data-entry/transfer.js +73 -59
  39. package/dist/components/data-entry/tree-select.d.ts +1 -1
  40. package/dist/components/data-entry/tree-select.js +10 -1
  41. package/dist/components/data-entry/upload.d.ts +1 -1
  42. package/dist/components/data-entry/upload.js +7 -2
  43. package/dist/components/layout/app-shell.d.ts +1 -1
  44. package/dist/components/layout/app-shell.js +42 -2
  45. package/dist/components/layout/breadcrumb.d.ts +7 -1
  46. package/dist/components/layout/breadcrumb.js +6 -2
  47. package/dist/components/layout/page-container.d.ts +1 -1
  48. package/dist/components/layout/page-container.js +32 -22
  49. package/dist/components/layout/responsive-grid.js +1 -1
  50. package/dist/components/layout/sidebar.d.ts +1 -1
  51. package/dist/components/layout/sidebar.js +44 -11
  52. package/dist/components/layout/split-pane.js +2 -2
  53. package/dist/components/navigation/app-setting-picker.js +8 -1
  54. package/dist/components/navigation/pagination.d.ts +1 -1
  55. package/dist/components/navigation/pagination.js +84 -95
  56. package/dist/components/navigation/tabs.js +21 -4
  57. package/dist/i18n/messages/en.json +3 -1
  58. package/dist/i18n/messages/ja.json +3 -1
  59. package/dist/i18n/messages/vi.json +3 -1
  60. package/dist/lib/field-a11y.d.ts +66 -0
  61. package/dist/lib/field-a11y.js +44 -0
  62. package/dist/props/components/data-display.prop.d.ts +16 -1
  63. package/dist/props/components/data-entry.prop.d.ts +69 -19
  64. package/dist/props/components/layout.prop.d.ts +42 -0
  65. package/dist/props/components/navigation.prop.d.ts +16 -0
  66. package/dist/props/registry.d.ts +8 -3
  67. package/dist/props/registry.js +21 -3
  68. package/dist/props/vocabulary/data.prop.d.ts +9 -0
  69. package/dist/props/vocabulary/interaction.prop.d.ts +1 -1
  70. package/dist/styles/base.css +1 -1
  71. package/dist/styles/layout.css +46 -9
  72. package/dist/styles/shell-layout.css +19 -0
  73. package/dist/styles/text-layout.css +1 -1
  74. package/dist/tokens/components/card.css +1 -1
  75. package/dist/tokens/foundation.css +3 -3
  76. package/package.json +8 -4
  77. package/scripts/ui-audit.mjs +2 -2
  78. package/scripts/visual-audit-rules.mjs +2 -2
@@ -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 firstValue = items?.[0]?.value;
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("group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className),
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
- "group/tabs-list text-muted-foreground data-[variant=default]:bg-muted inline-flex w-fit items-center justify-center rounded-lg p-1 group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:gap-1 data-[variant=line]:rounded-none data-[variant=line]:bg-transparent",
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
@@ -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": "サイドバー",
130
130
  "headerLabel": "ヘッダー",
131
131
  "mainLabel": "メインコンテンツ",
132
- "footerLabel": "フッター"
132
+ "footerLabel": "フッター",
133
+ "openNav": "ナビゲーションメニューを開く",
134
+ "navLabel": "メニュー"
133
135
  },
134
136
  "authShell": {
135
137
  "brandLabel": "ブランド",
@@ -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
+ };
@@ -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 */
@@ -7,7 +7,8 @@ import type { DayPickerProps } from "react-day-picker";
7
7
  import type { DateRange } from "react-day-picker";
8
8
  import type * as React from "react";
9
9
  import type { UploadFileItem } from "../../components/data-entry/upload-types.js";
10
- import type { ClassNameProp, DisabledProp, EmptyMessageProp, ErrorProp, HelperProp, IdProp, LabelProp, NameProp, OnChangeProp, OnValueChangeProp, OnSearchChangeProp, PlaceholderProp, RequiredProp, ValueProp, DefaultValueProp, FormLayoutProp, WidthProp, BreakpointProp, DensityProp, SizeProp } from "../vocabulary/index.js";
10
+ import type { FieldA11yProps } from "../../lib/field-a11y.js";
11
+ import type { ClassNameProp, DisabledProp, EmptyMessageProp, ErrorProp, HelperProp, IdProp, LabelProp, NameProp, OnChangeProp, OnValueChangeProp, OnSearchChangeProp, OpenProp, OnOpenChangeProp, PlaceholderProp, RequiredProp, ValueProp, DefaultValueProp, FormLayoutProp, WidthProp, BreakpointProp, DensityProp, SizeProp } from "../vocabulary/index.js";
11
12
  import type { ResponsiveGridColumnsProp } from "./layout.prop.js";
12
13
  /** @see Input */
13
14
  export type InputProp = React.InputHTMLAttributes<HTMLInputElement> & {
@@ -33,7 +34,7 @@ export type TextareaProp = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
33
34
  * the stepper buttons and ArrowUp/ArrowDown (Shift = ×10); `precision` sets the committed decimal
34
35
  * places (inferred from `step` when omitted). Value commits clamped to `min`/`max` on blur/Enter.
35
36
  */
36
- export type NumberInputProp = {
37
+ export type NumberInputProp = FieldA11yProps & {
37
38
  value?: ValueProp<number | null>;
38
39
  defaultValue?: DefaultValueProp<number | null>;
39
40
  onValueChange?: OnValueChangeProp<number | null>;
@@ -58,9 +59,6 @@ export type NumberInputProp = {
58
59
  name?: NameProp;
59
60
  id?: IdProp;
60
61
  className?: ClassNameProp;
61
- "aria-label"?: string;
62
- "aria-labelledby"?: string;
63
- "aria-describedby"?: string;
64
62
  "data-testid"?: string;
65
63
  };
66
64
  /**
@@ -101,7 +99,7 @@ export type FormFieldProp = {
101
99
  children: React.ReactNode;
102
100
  };
103
101
  /** @see SearchInput */
104
- export type SearchInputProp = {
102
+ export type SearchInputProp = FieldA11yProps & {
105
103
  id?: IdProp;
106
104
  label?: LabelProp;
107
105
  placeholder?: PlaceholderProp;
@@ -121,7 +119,7 @@ export type ChoiceOptionProp = {
121
119
  description?: React.ReactNode;
122
120
  };
123
121
  /** @see Checkbox.Group */
124
- export type CheckboxGroupProp = {
122
+ export type CheckboxGroupProp = FieldA11yProps & {
125
123
  value?: ValueProp<string[]>;
126
124
  defaultValue?: DefaultValueProp<string[]>;
127
125
  onValueChange?: OnValueChangeProp<string[]>;
@@ -129,11 +127,13 @@ export type CheckboxGroupProp = {
129
127
  orientation?: "horizontal" | "vertical";
130
128
  disabled?: DisabledProp;
131
129
  name?: NameProp;
130
+ /** Injected by FormField (or set directly) — applied to the `role="group"` container. */
131
+ id?: IdProp;
132
132
  className?: ClassNameProp;
133
133
  children?: React.ReactNode;
134
134
  };
135
135
  /** @see Radio.Group */
136
- export type RadioGroupProp = {
136
+ export type RadioGroupProp = FieldA11yProps & {
137
137
  value?: ValueProp;
138
138
  defaultValue?: DefaultValueProp;
139
139
  onValueChange?: OnValueChangeProp;
@@ -141,6 +141,8 @@ export type RadioGroupProp = {
141
141
  orientation?: "horizontal" | "vertical";
142
142
  disabled?: DisabledProp;
143
143
  name?: NameProp;
144
+ /** Injected by FormField (or set directly) — applied to the `role="radiogroup"` container. */
145
+ id?: IdProp;
144
146
  className?: ClassNameProp;
145
147
  children?: React.ReactNode;
146
148
  };
@@ -163,7 +165,7 @@ export type SliderProp = React.ComponentPropsWithoutRef<typeof SliderPrimitive.R
163
165
  /** @see Calendar — react-day-picker DayPicker. */
164
166
  export type CalendarProp = DayPickerProps;
165
167
  /** @see DatePicker */
166
- export type DatePickerProp = {
168
+ export type DatePickerProp = FieldA11yProps & {
167
169
  value?: ValueProp<Date>;
168
170
  defaultValue?: DefaultValueProp<Date | undefined>;
169
171
  onValueChange?: OnValueChangeProp<Date | undefined>;
@@ -180,7 +182,7 @@ export type DatePickerProp = {
180
182
  allowClear?: boolean;
181
183
  };
182
184
  /** @see MonthPicker */
183
- export type MonthPickerProp = {
185
+ export type MonthPickerProp = FieldA11yProps & {
184
186
  value?: ValueProp<Date>;
185
187
  defaultValue?: DefaultValueProp<Date | undefined>;
186
188
  onValueChange?: OnValueChangeProp<Date | undefined>;
@@ -200,7 +202,7 @@ export type MonthPickerProp = {
200
202
  * @see MonthRangePicker — both edges are normalized to the FIRST day of their month
201
203
  * (the `DateRange` shape is shared with DateRangePicker so ranges interop).
202
204
  */
203
- export type MonthRangePickerProp = {
205
+ export type MonthRangePickerProp = FieldA11yProps & {
204
206
  value?: ValueProp<DateRange>;
205
207
  defaultValue?: DefaultValueProp<DateRange | undefined>;
206
208
  onValueChange?: OnValueChangeProp<DateRange | undefined>;
@@ -217,7 +219,7 @@ export type MonthRangePickerProp = {
217
219
  allowClear?: boolean;
218
220
  };
219
221
  /** @see DateRangePicker */
220
- export type DateRangePickerProp = {
222
+ export type DateRangePickerProp = FieldA11yProps & {
221
223
  value?: ValueProp<DateRange>;
222
224
  defaultValue?: DefaultValueProp<DateRange | undefined>;
223
225
  onValueChange?: OnValueChangeProp<DateRange | undefined>;
@@ -234,7 +236,7 @@ export type DateRangePickerProp = {
234
236
  allowClear?: boolean;
235
237
  };
236
238
  /** @see TimePicker — popover HH:mm picker (canonical 24h storage). */
237
- export type TimePickerProp = {
239
+ export type TimePickerProp = FieldA11yProps & {
238
240
  value?: ValueProp;
239
241
  defaultValue?: DefaultValueProp;
240
242
  onValueChange?: OnValueChangeProp;
@@ -250,7 +252,7 @@ export type TimePickerProp = {
250
252
  allowClear?: boolean;
251
253
  };
252
254
  /** @see ColorPicker */
253
- export type ColorPickerProp = {
255
+ export type ColorPickerProp = FieldA11yProps & {
254
256
  value?: ValueProp;
255
257
  onValueChange?: OnValueChangeProp;
256
258
  disabled?: DisabledProp;
@@ -320,12 +322,51 @@ export type SearchSelectProp = {
320
322
  searchPlaceholder?: PlaceholderProp;
321
323
  emptyMessage?: EmptyMessageProp;
322
324
  loadingMessage?: string;
323
- /** Message shown when an async `loadOptions` rejects — a distinct state from empty/loading. */
325
+ /** Message shown when an async `loadOptions` rejects — a distinct state from empty/loading. Used
326
+ * as-is unless `renderError` is provided. */
324
327
  errorMessage?: string;
325
328
  clearLabel?: string;
326
329
  /** Show a "clear" row when a value is selected (default true). */
327
330
  clearable?: boolean;
328
331
  disabled?: DisabledProp;
332
+ /**
333
+ * Read-only: the current value is shown (and the clear affordance hidden) but the popover cannot
334
+ * be opened — no new pick, no search. Mirrors the Input/NumberInput readOnly contract (stays
335
+ * focusable + submits its value, unlike `disabled`). Default false.
336
+ */
337
+ readOnly?: boolean;
338
+ /** Trigger height tier — forwarded to the underlying Button. Default matches Button's own default. */
339
+ size?: SizeProp;
340
+ /** Controlled open state for the popover (uncontrolled by default). */
341
+ open?: OpenProp;
342
+ onOpenChange?: OnOpenChangeProp;
343
+ /** Controlled search-box query (uncontrolled by default). Pairs with `onSearchChange`. */
344
+ search?: string;
345
+ onSearchChange?: OnSearchChangeProp;
346
+ /**
347
+ * Override the default client-side filter (`options` mode only — ignored with `loadOptions`,
348
+ * which is responsible for its own server-side filtering). Receives the option and the trimmed
349
+ * query; return true to keep the row. Only consulted while the query is non-empty.
350
+ */
351
+ filterOption?: (option: SearchSelectOptionProp, query: string) => boolean;
352
+ /**
353
+ * Custom error slot — receives the resolved message and a `retry` callback that reloads from the
354
+ * first page (a predictable recovery, not a resume of a failed page-N append). Overrides the
355
+ * default `errorMessage` row.
356
+ */
357
+ renderError?: (params: {
358
+ message: string;
359
+ retry: () => void;
360
+ }) => React.ReactNode;
361
+ /**
362
+ * Custom "load more" affordance appended below the list while another page is available — pairs
363
+ * with (does not replace) the built-in scroll-triggered pagination.
364
+ */
365
+ renderLoadMore?: (params: {
366
+ hasMore: boolean;
367
+ loading: boolean;
368
+ loadMore: () => void;
369
+ }) => React.ReactNode;
329
370
  /** Form field name — submits the selected value via a hidden input. */
330
371
  name?: NameProp;
331
372
  id?: IdProp;
@@ -342,6 +383,11 @@ export type SearchSelectProp = {
342
383
  * Data-driven (Ant-style) form of {@link Select} — one component covering static `options` or
343
384
  * async `loadOptions`, with `showSearch` toggling the searchable combobox vs a plain listbox.
344
385
  * Passing `options`/`loadOptions` to `<Select>` switches it from the compound API to this one.
386
+ *
387
+ * `readOnly`/`size`/`open`/`onOpenChange`/`search`/`onSearchChange`/`filterOption`/`renderError`/
388
+ * `renderLoadMore` (inherited from {@link SearchSelectProp}) take effect ONLY in searchable mode
389
+ * (`showSearch` or `loadOptions`) — they configure the `SearchSelect` engine that mode delegates
390
+ * to. The plain listbox (no search) is a native Radix listbox and ignores them.
345
391
  */
346
392
  export type SelectDataProp = SearchSelectProp & {
347
393
  /** Show the search box (combobox). Defaults to true when `loadOptions` is set, otherwise false. */
@@ -352,7 +398,7 @@ export type UploadFileItemProp = UploadFileItem;
352
398
  /** @see Upload */
353
399
  export type UploadVariantProp = "dropzone" | "button" | "picture-card" | "picture" | "avatar" | "avatar-crop";
354
400
  /** @see Upload — presentational; wire `onUpload` to media-service in app api.ts */
355
- export type UploadProp = {
401
+ export type UploadProp = FieldA11yProps & {
356
402
  variant?: UploadVariantProp;
357
403
  value?: ValueProp<UploadFileItemProp[]>;
358
404
  defaultValue?: DefaultValueProp<UploadFileItemProp[]>;
@@ -368,6 +414,8 @@ export type UploadProp = {
368
414
  mediaId: string;
369
415
  previewUrl?: string;
370
416
  }>;
417
+ /** Injected by FormField (or set directly) — applied to the native `<input type="file">`. */
418
+ id?: IdProp;
371
419
  className?: ClassNameProp;
372
420
  children?: React.ReactNode;
373
421
  };
@@ -386,7 +434,7 @@ export type TreeFieldNamesProp = {
386
434
  children?: string;
387
435
  };
388
436
  /** @see Cascader — cascade picker (Popover + multi-column). */
389
- export type CascaderProp = {
437
+ export type CascaderProp = FieldA11yProps & {
390
438
  options: TreeOptionProp[];
391
439
  value?: ValueProp<string[] | string[][]>;
392
440
  defaultValue?: DefaultValueProp<string[] | string[][]>;
@@ -404,7 +452,7 @@ export type CascaderProp = {
404
452
  };
405
453
  export type ShowCheckedStrategyProp = "SHOW_CHILD" | "SHOW_PARENT" | "SHOW_ALL";
406
454
  /** @see TreeSelect — tree in Popover (cmdk search + expand/collapse). */
407
- export type TreeSelectProp = {
455
+ export type TreeSelectProp = FieldA11yProps & {
408
456
  treeData: TreeOptionProp[];
409
457
  value?: ValueProp<string | string[]>;
410
458
  defaultValue?: DefaultValueProp<string | string[]>;
@@ -430,7 +478,7 @@ export type TransferItemProp = {
430
478
  disabled?: boolean;
431
479
  };
432
480
  /** @see Transfer — dual-list shuttle (Checkbox + SearchInput). */
433
- export type TransferProp = {
481
+ export type TransferProp = FieldA11yProps & {
434
482
  dataSource: TransferItemProp[];
435
483
  targetKeys: string[];
436
484
  onValueChange?: (targetKeys: string[], direction: "left" | "right", moveKeys: string[]) => void;
@@ -438,6 +486,8 @@ export type TransferProp = {
438
486
  showSearch?: boolean;
439
487
  oneWay?: boolean;
440
488
  disabled?: DisabledProp;
489
+ /** Injected by FormField (or set directly) — applied to the `role="group"` shuttle container. */
490
+ id?: IdProp;
441
491
  className?: ClassNameProp;
442
492
  selectedKeys?: [string[], string[]];
443
493
  onSelectChange?: (sourceSelectedKeys: string[], targetSelectedKeys: string[]) => void;
@@ -9,6 +9,13 @@ export type PageContainerProp = {
9
9
  extra?: ExtraProp;
10
10
  footer?: FooterProp;
11
11
  breadcrumb?: BreadcrumbProp;
12
+ /**
13
+ * Override the breadcrumb `<nav>` landmark's accessible name. Defaults to a localized
14
+ * "Breadcrumb". Needed when more than one `PageContainer` (each with its own `breadcrumb`)
15
+ * renders on the same page/view — two `<nav>` landmarks sharing one name/role fail axe's
16
+ * `landmark-unique` (WCAG 2.4.1 / 1.3.1).
17
+ */
18
+ breadcrumbAriaLabel?: string;
12
19
  linkComponent?: React.ElementType;
13
20
  density?: PageDensityProp;
14
21
  variant?: PageContainerVariantProp;
@@ -66,6 +73,21 @@ export type AppShellProp = {
66
73
  breadcrumb?: ReactNode;
67
74
  footer?: ReactNode;
68
75
  sidebarCollapsed?: boolean;
76
+ /**
77
+ * Navigation shown in the mobile drawer below the `lg` breakpoint, where the docked sidebar is
78
+ * hidden. AppShell OWNS the drawer: it renders a hamburger trigger in the topbar and a focus-
79
+ * trapped Sheet (Esc + overlay close, focus returns to the trigger) — hiding the sidebar without
80
+ * a reachable alternative is invalid (gh#165). Defaults to `sidebar`, so the same nav is
81
+ * available on mobile with no extra wiring; pass a distinct node for a mobile-tailored menu, or
82
+ * `null` to opt out (only when navigation lives elsewhere, e.g. a bottom bar).
83
+ */
84
+ mobileNav?: ReactNode;
85
+ /** Accessible title for the mobile navigation drawer. Defaults to the localized "Menu". */
86
+ mobileNavLabel?: string;
87
+ /** Controlled open state of the mobile drawer. Omit for AppShell-owned (uncontrolled) state. */
88
+ mobileNavOpen?: boolean;
89
+ /** Change handler for the mobile drawer open state (pairs with `mobileNavOpen`). */
90
+ onMobileNavOpenChange?: (open: boolean) => void;
69
91
  };
70
92
  /**
71
93
  * @see AuthShell — centred auth/login page shell (login · mfa · passkey · device · reset). A
@@ -97,6 +119,13 @@ export type SidebarItemProp = {
97
119
  icon: ComponentType<SVGProps<SVGSVGElement>>;
98
120
  badge?: ReactNode;
99
121
  disabled?: boolean;
122
+ /**
123
+ * Render the row as a real anchor (`<a href>`) — the link is the SOLE interactive element (no
124
+ * nested `<button>`). Use for MPA links / right-click-open-in-new-tab. Omit for SPA rows that
125
+ * report selection via `onSelect(id)`. For a framework router `<Link>`, use `renderItem` instead
126
+ * (its returned element is merged as the row via Slot, so there is still no nested interactive).
127
+ */
128
+ href?: string;
100
129
  /** Nested rows — renders a collapsible submenu group (the parent reads active when any child is). */
101
130
  children?: SidebarItemProp[];
102
131
  };
@@ -117,8 +146,21 @@ export type SidebarProp = {
117
146
  brand?: ReactNode;
118
147
  collapsed?: boolean;
119
148
  children?: ChildrenProp;
149
+ /**
150
+ * Escape hatch to render a leaf row as a custom element — typically a framework router `<Link>`.
151
+ * Return a SINGLE interactive element; the Sidebar merges the row styling + active state onto it
152
+ * via Slot (so it is the row and the sole interactive element — no nested `<button>`). Any
153
+ * secondary affix (a star, a count) must be a non-interactive descendant of that element.
154
+ */
120
155
  renderItem?: (item: SidebarItemData) => ReactNode;
121
156
  footer?: ReactNode;
157
+ /**
158
+ * Override the nav landmark's accessible name. Defaults to a localized "Main navigation".
159
+ * Needed when more than one Sidebar renders on the same page/view (e.g. a docked sidebar +
160
+ * its mobile-drawer twin, both mounted at once) — two `<nav>` landmarks sharing one name/role
161
+ * fail axe's `landmark-unique` (WCAG 2.4.1 / 1.3.1).
162
+ */
163
+ "aria-label"?: string;
122
164
  };
123
165
  /**
124
166
  * @see Topbar — a PURE SLOT bar (no baked chrome). The library only positions three clusters; the
@@ -22,10 +22,26 @@ export type PaginationProp = {
22
22
  pageSizeOptions?: number[];
23
23
  showSizeChanger?: boolean;
24
24
  showTotal?: boolean | ((total: number, range: [number, number]) => React.ReactNode);
25
+ /**
26
+ * Hide the whole control when there is nothing to page through — zero items OR exactly one page.
27
+ * `true` (default) keeps table footers clean: pagination is navigation between multiple result
28
+ * pages, so a lone disabled `1 / 1` bar is noise. Set `false` for the explicit opt-in when a
29
+ * consumer still wants the bar on a single page (e.g. to keep `showTotal` visible). A control
30
+ * with `total === 0` is ALWAYS hidden — there is no data to navigate. (gh#153)
31
+ */
32
+ hideOnSinglePage?: boolean;
25
33
  simple?: boolean;
26
34
  disabled?: DisabledProp;
27
35
  className?: ClassNameProp;
28
36
  onValueChange?: (page: number, pageSize: number) => void;
37
+ /**
38
+ * Override the `<nav>` landmark's accessible name. Defaults to a localized "Pagination".
39
+ * Multiple Pagination instances on one page/view (e.g. two independent result lists) need a
40
+ * DISTINCT name each — two `<nav>` landmarks sharing one name/role fail axe's `landmark-unique`
41
+ * (WCAG 2.4.1 / 1.3.1). Pass something that names what is being paged, e.g. `"注文一覧の
42
+ * ページネーション"` vs `"請求書一覧のページネーション"`.
43
+ */
44
+ "aria-label"?: string;
29
45
  };
30
46
  export type StepStatusProp = "wait" | "process" | "finish" | "error";
31
47
  /** @see StepItem */
@@ -610,13 +610,13 @@ export declare const COMPONENT_PROP_REGISTRY: {
610
610
  readonly SearchSelectProp: {
611
611
  readonly group: "data-entry";
612
612
  readonly file: "components/data-entry.prop.ts";
613
- readonly vocabulary: readonly ["ValueProp", "OnValueChangeProp", "PlaceholderProp", "EmptyMessageProp", "DisabledProp", "NameProp", "IdProp", "ClassNameProp"];
613
+ readonly vocabulary: readonly ["ValueProp", "OnValueChangeProp", "PlaceholderProp", "EmptyMessageProp", "DisabledProp", "NameProp", "IdProp", "ClassNameProp", "OpenProp", "OnOpenChangeProp", "SizeProp"];
614
614
  readonly note: "Internal — the searchable engine behind `<Select options showSearch>` (not public API); use Select.";
615
615
  };
616
616
  readonly SelectDataProp: {
617
617
  readonly group: "data-entry";
618
618
  readonly file: "components/data-entry.prop.ts";
619
- readonly vocabulary: readonly ["ValueProp", "OnValueChangeProp", "PlaceholderProp", "EmptyMessageProp", "DisabledProp", "NameProp", "IdProp", "ClassNameProp"];
619
+ readonly vocabulary: readonly ["ValueProp", "OnValueChangeProp", "PlaceholderProp", "EmptyMessageProp", "DisabledProp", "NameProp", "IdProp", "ClassNameProp", "OpenProp", "OnOpenChangeProp", "SizeProp"];
620
620
  readonly note: "Ant-style data-driven form of Select (options|loadOptions + showSearch). One Select for all single-selects.";
621
621
  };
622
622
  readonly SearchSelectOptionProp: {
@@ -687,7 +687,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
687
687
  readonly EmptyStateProp: {
688
688
  readonly group: "data-display";
689
689
  readonly file: "components/data-display.prop.ts";
690
- readonly vocabulary: readonly ["IconProp", "TitleProp", "DescriptionProp", "ActionProp", "ToneProp"];
690
+ readonly vocabulary: readonly ["IconProp", "TitleProp", "DescriptionProp", "ActionProp", "ToneProp", "HeadingLevelProp"];
691
691
  };
692
692
  readonly EmptyStateToneProp: {
693
693
  readonly group: "data-display";
@@ -1097,6 +1097,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
1097
1097
  readonly file: "components/data-display/card.tsx";
1098
1098
  readonly vocabulary: readonly ["TitleProp", "DescriptionProp", "ClassNameProp", "ChildrenProp"];
1099
1099
  };
1100
+ readonly CardTitleProp: {
1101
+ readonly group: "data-display";
1102
+ readonly file: "components/data-display/card.tsx";
1103
+ readonly vocabulary: readonly ["HeadingLevelProp", "ClassNameProp", "ChildrenProp"];
1104
+ };
1100
1105
  readonly CardContentProp: {
1101
1106
  readonly group: "data-display";
1102
1107
  readonly file: "components/data-display/card.tsx";
@@ -637,7 +637,10 @@ const COMPONENT_PROP_REGISTRY = {
637
637
  "DisabledProp",
638
638
  "NameProp",
639
639
  "IdProp",
640
- "ClassNameProp"
640
+ "ClassNameProp",
641
+ "OpenProp",
642
+ "OnOpenChangeProp",
643
+ "SizeProp"
641
644
  ],
642
645
  note: "Internal \u2014 the searchable engine behind `<Select options showSearch>` (not public API); use Select."
643
646
  },
@@ -652,7 +655,10 @@ const COMPONENT_PROP_REGISTRY = {
652
655
  "DisabledProp",
653
656
  "NameProp",
654
657
  "IdProp",
655
- "ClassNameProp"
658
+ "ClassNameProp",
659
+ "OpenProp",
660
+ "OnOpenChangeProp",
661
+ "SizeProp"
656
662
  ],
657
663
  note: "Ant-style data-driven form of Select (options|loadOptions + showSearch). One Select for all single-selects."
658
664
  },
@@ -742,7 +748,14 @@ const COMPONENT_PROP_REGISTRY = {
742
748
  EmptyStateProp: {
743
749
  group: "data-display",
744
750
  file: "components/data-display.prop.ts",
745
- vocabulary: ["IconProp", "TitleProp", "DescriptionProp", "ActionProp", "ToneProp"]
751
+ vocabulary: [
752
+ "IconProp",
753
+ "TitleProp",
754
+ "DescriptionProp",
755
+ "ActionProp",
756
+ "ToneProp",
757
+ "HeadingLevelProp"
758
+ ]
746
759
  },
747
760
  EmptyStateToneProp: {
748
761
  group: "data-display",
@@ -1104,6 +1117,11 @@ const COMPONENT_PROP_REGISTRY = {
1104
1117
  file: "components/data-display/card.tsx",
1105
1118
  vocabulary: ["TitleProp", "DescriptionProp", "ClassNameProp", "ChildrenProp"]
1106
1119
  },
1120
+ CardTitleProp: {
1121
+ group: "data-display",
1122
+ file: "components/data-display/card.tsx",
1123
+ vocabulary: ["HeadingLevelProp", "ClassNameProp", "ChildrenProp"]
1124
+ },
1107
1125
  CardContentProp: {
1108
1126
  group: "data-display",
1109
1127
  file: "components/data-display/card.tsx",