@godxjp/ui 18.15.4 → 19.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/dist/app/app-provider.d.ts +1 -1
  2. package/dist/app/app-provider.js +3 -0
  3. package/dist/app/date-format-labels.d.ts +1 -1
  4. package/dist/app/time-format-labels.d.ts +1 -1
  5. package/dist/components/data-display/badge.d.ts +1 -1
  6. package/dist/components/data-display/card.d.ts +4 -13
  7. package/dist/components/data-display/card.js +2 -15
  8. package/dist/components/data-display/data-table.d.ts +3 -8
  9. package/dist/components/data-display/data-table.js +106 -68
  10. package/dist/components/data-display/descriptions.js +34 -5
  11. package/dist/components/data-display/empty-state.js +1 -1
  12. package/dist/components/data-display/permission-matrix.js +7 -7
  13. package/dist/components/data-display/popover.js +2 -9
  14. package/dist/components/data-display/qr-code.d.ts +1 -1
  15. package/dist/components/data-display/scroll-area.d.ts +3 -1
  16. package/dist/components/data-display/scroll-area.js +150 -16
  17. package/dist/components/data-display/table.js +12 -3
  18. package/dist/components/data-entry/branch-scope-picker.js +4 -4
  19. package/dist/components/data-entry/calendar.js +15 -24
  20. package/dist/components/data-entry/cascader.js +70 -72
  21. package/dist/components/data-entry/checkbox-group.js +12 -5
  22. package/dist/components/data-entry/checkbox.js +30 -13
  23. package/dist/components/data-entry/command.d.ts +6 -6
  24. package/dist/components/data-entry/date-picker.js +34 -17
  25. package/dist/components/data-entry/date-range-picker.js +19 -13
  26. package/dist/components/data-entry/form-field.d.ts +1 -1
  27. package/dist/components/data-entry/form-field.js +22 -5
  28. package/dist/components/data-entry/index.d.ts +2 -1
  29. package/dist/components/data-entry/input.js +18 -22
  30. package/dist/components/data-entry/label.js +4 -2
  31. package/dist/components/data-entry/month-picker.js +20 -17
  32. package/dist/components/data-entry/month-range-picker.js +26 -19
  33. package/dist/components/data-entry/radio.js +25 -6
  34. package/dist/components/data-entry/search-select.d.ts +1 -1
  35. package/dist/components/data-entry/search-select.js +37 -31
  36. package/dist/components/data-entry/select.d.ts +8 -0
  37. package/dist/components/data-entry/select.js +68 -26
  38. package/dist/components/data-entry/switch.js +16 -2
  39. package/dist/components/data-entry/textarea.d.ts +56 -0
  40. package/dist/components/data-entry/textarea.js +110 -40
  41. package/dist/components/data-entry/time-picker.js +52 -52
  42. package/dist/components/data-entry/toggle-group.d.ts +1 -0
  43. package/dist/components/data-entry/toggle.d.ts +1 -1
  44. package/dist/components/data-entry/transfer.js +14 -15
  45. package/dist/components/data-entry/tree-select.js +19 -23
  46. package/dist/components/data-entry/upload-crop-dialog.js +4 -4
  47. package/dist/components/data-entry/upload.d.ts +1 -1
  48. package/dist/components/data-entry/upload.js +46 -46
  49. package/dist/components/feedback/alert.js +7 -14
  50. package/dist/components/feedback/dialog.d.ts +23 -32
  51. package/dist/components/feedback/dialog.js +16 -13
  52. package/dist/components/feedback/sheet.d.ts +1 -1
  53. package/dist/components/feedback/sheet.js +14 -14
  54. package/dist/components/feedback/skeleton.js +13 -10
  55. package/dist/components/feedback/sonner.d.ts +1 -1
  56. package/dist/components/feedback/sonner.js +17 -8
  57. package/dist/components/feedback/tooltip.js +1 -1
  58. package/dist/components/general/activity.d.ts +44 -0
  59. package/dist/components/general/activity.js +65 -0
  60. package/dist/components/general/button.d.ts +2 -2
  61. package/dist/components/general/button.js +11 -24
  62. package/dist/components/general/index.d.ts +2 -0
  63. package/dist/components/general/index.js +2 -0
  64. package/dist/components/layout/app-shell.js +5 -3
  65. package/dist/components/layout/auth-divider.d.ts +9 -2
  66. package/dist/components/layout/auth-divider.js +7 -11
  67. package/dist/components/layout/index.d.ts +1 -0
  68. package/dist/components/layout/page-container.d.ts +1 -1
  69. package/dist/components/layout/page-container.js +32 -20
  70. package/dist/components/layout/resizable.d.ts +12 -12
  71. package/dist/components/layout/separator.d.ts +1 -0
  72. package/dist/components/layout/service-role-panel.js +47 -42
  73. package/dist/components/layout/sidebar.js +13 -1
  74. package/dist/components/layout/split-pane.d.ts +12 -1
  75. package/dist/components/layout/split-pane.js +13 -4
  76. package/dist/components/navigation/app-setting-picker.js +5 -14
  77. package/dist/components/navigation/dropdown-menu.js +10 -16
  78. package/dist/components/navigation/menubar.d.ts +3 -6
  79. package/dist/components/navigation/pagination-utils.d.ts +15 -2
  80. package/dist/components/navigation/pagination-utils.js +27 -21
  81. package/dist/components/navigation/pagination.d.ts +4 -4
  82. package/dist/components/navigation/pagination.js +2 -2
  83. package/dist/components/navigation/steps.js +31 -67
  84. package/dist/components/navigation/tabs.js +17 -5
  85. package/dist/components/query/infinite-query-state.js +2 -2
  86. package/dist/components/ui/accordion.d.ts +1 -1
  87. package/dist/components/ui/avatar.d.ts +1 -1
  88. package/dist/components/ui/avatar.js +29 -10
  89. package/dist/components/ui/input-otp.d.ts +8 -6
  90. package/dist/components/ui/rating.js +1 -1
  91. package/dist/components/ui/separator.d.ts +27 -2
  92. package/dist/components/ui/separator.js +42 -11
  93. package/dist/components/ui/toggle-group.d.ts +15 -5
  94. package/dist/components/ui/toggle-group.js +43 -18
  95. package/dist/components/ui/toggle.d.ts +66 -5
  96. package/dist/components/ui/toggle.js +64 -9
  97. package/dist/i18n/messages/en.json +8 -0
  98. package/dist/i18n/messages/ja.json +8 -0
  99. package/dist/i18n/messages/vi.json +8 -0
  100. package/dist/lib/control-styles.d.ts +14 -0
  101. package/dist/lib/control-styles.js +2 -0
  102. package/dist/lib/field-a11y.d.ts +52 -0
  103. package/dist/lib/field-a11y.js +17 -2
  104. package/dist/props/components/app.prop.d.ts +15 -0
  105. package/dist/props/components/data-display.prop.d.ts +85 -3
  106. package/dist/props/components/data-entry.prop.d.ts +35 -2
  107. package/dist/props/components/general.prop.d.ts +52 -1
  108. package/dist/props/components/layout.prop.d.ts +137 -2
  109. package/dist/props/registry.d.ts +116 -5
  110. package/dist/props/registry.js +165 -4
  111. package/dist/props/vocabulary/index.d.ts +2 -2
  112. package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
  113. package/dist/props/vocabulary/layout.prop.d.ts +8 -0
  114. package/dist/styles/alert-layout.css +69 -3
  115. package/dist/styles/badge-layout.css +2 -2
  116. package/dist/styles/base.css +6 -0
  117. package/dist/styles/card-layout.css +25 -8
  118. package/dist/styles/control.css +1150 -29
  119. package/dist/styles/data-display-layout.css +185 -11
  120. package/dist/styles/data-entry-layout.css +352 -1
  121. package/dist/styles/density.css +2 -2
  122. package/dist/styles/dialog-layout.css +203 -4
  123. package/dist/styles/focus-ring.css +155 -0
  124. package/dist/styles/form-layout.css +50 -5
  125. package/dist/styles/index.css +1 -0
  126. package/dist/styles/layout.css +412 -23
  127. package/dist/styles/motion.css +178 -0
  128. package/dist/styles/navigation-layout.css +398 -22
  129. package/dist/styles/shell-layout.css +137 -39
  130. package/dist/styles/table-layout.css +153 -22
  131. package/dist/styles/toggle.css +59 -0
  132. package/dist/tokens/base.css +5 -0
  133. package/dist/tokens/components/activity.css +64 -0
  134. package/dist/tokens/components/badge.css +7 -0
  135. package/dist/tokens/components/card.css +14 -10
  136. package/dist/tokens/components/chart.css +1 -1
  137. package/dist/tokens/components/control.css +358 -8
  138. package/dist/tokens/components/data-display.css +75 -0
  139. package/dist/tokens/components/data-entry.css +25 -0
  140. package/dist/tokens/components/descriptions.css +22 -0
  141. package/dist/tokens/components/feedback.css +143 -4
  142. package/dist/tokens/components/form.css +16 -0
  143. package/dist/tokens/components/legal-document.css +5 -1
  144. package/dist/tokens/components/navigation.css +109 -0
  145. package/dist/tokens/components/scroll-area.css +21 -0
  146. package/dist/tokens/components/separator.css +52 -0
  147. package/dist/tokens/components/shell.css +132 -20
  148. package/dist/tokens/components/table.css +52 -3
  149. package/dist/tokens/components/toggle.css +82 -0
  150. package/dist/tokens/components/upload.css +107 -0
  151. package/dist/tokens/foundation.css +171 -10
  152. package/dist/tokens/semantic/layout.css +120 -1
  153. package/package.json +85 -68
  154. package/scripts/ui-audit.mjs +51 -2
@@ -4,7 +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
+ import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
8
8
  import { cn } from "../../lib/utils.js";
9
9
  import { Button } from "../general/button.js";
10
10
  import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
@@ -38,6 +38,7 @@ function MonthPicker({
38
38
  const inputId = id ?? autoId;
39
39
  const dialogId = `${inputId}-dialog`;
40
40
  const fieldA11y = pickFieldA11y(ariaProps);
41
+ const identity = useFieldIdentity({ id, name, "data-field": fieldA11y["data-field"] });
41
42
  const isControlled = useControlledLatch(valueProp !== void 0);
42
43
  const [internalValue, setInternalValue] = React.useState(defaultValue);
43
44
  const value = isControlled ? valueProp : internalValue;
@@ -61,11 +62,11 @@ function MonthPicker({
61
62
  return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
62
63
  "div",
63
64
  {
65
+ "data-disabled": disabled ? "" : void 0,
64
66
  className: cn(
65
- "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",
67
+ "ui-control ui-control-composite-field",
66
68
  "focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]",
67
69
  open && "border-ring ring-ring/50 ring-[3px]",
68
- disabled && "pointer-events-none cursor-not-allowed opacity-50",
69
70
  className
70
71
  ),
71
72
  onClick: () => {
@@ -76,7 +77,8 @@ function MonthPicker({
76
77
  "input",
77
78
  {
78
79
  id: inputId,
79
- name,
80
+ name: name ?? identity.name,
81
+ "data-field": fieldA11y["data-field"] ?? identity["data-field"],
80
82
  value: text,
81
83
  disabled,
82
84
  placeholder: placeholder ?? t("dataEntry.monthPicker.placeholder") ?? YM_HINT,
@@ -87,7 +89,7 @@ function MonthPicker({
87
89
  "aria-haspopup": "dialog",
88
90
  "aria-controls": open ? dialogId : void 0,
89
91
  ...fieldA11y,
90
- className: "placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent tabular-nums outline-none disabled:cursor-not-allowed",
92
+ className: "ui-month-picker-input",
91
93
  onKeyDown: (event) => {
92
94
  if (event.key === "ArrowDown") {
93
95
  event.preventDefault();
@@ -117,9 +119,10 @@ function MonthPicker({
117
119
  emit(void 0);
118
120
  setText("");
119
121
  },
120
- children: /* @__PURE__ */ jsx(X, { className: "size-4 shrink-0", "aria-hidden": "true" })
122
+ children: /* @__PURE__ */ jsx(X, { className: "ui-month-picker-icon", "aria-hidden": "true" })
121
123
  }
122
- ) : /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
124
+ ) : null,
125
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
123
126
  "button",
124
127
  {
125
128
  type: "button",
@@ -127,7 +130,7 @@ function MonthPicker({
127
130
  tabIndex: -1,
128
131
  "aria-label": t("dataEntry.monthPicker.openGrid") ?? "Open month grid",
129
132
  className: "text-muted-foreground hover:text-foreground shrink-0",
130
- children: /* @__PURE__ */ jsx(CalendarIcon, { className: "size-4 shrink-0", "aria-hidden": "true" })
133
+ children: /* @__PURE__ */ jsx(CalendarIcon, { className: "ui-month-picker-icon", "aria-hidden": "true" })
131
134
  }
132
135
  ) }),
133
136
  /* @__PURE__ */ jsxs(
@@ -136,12 +139,12 @@ function MonthPicker({
136
139
  id: dialogId,
137
140
  role: "dialog",
138
141
  "aria-label": t("dataEntry.monthPicker.openGrid") ?? "Month grid",
139
- className: "w-auto p-3",
142
+ className: "ui-month-picker-panel",
140
143
  align: "start",
141
144
  onOpenAutoFocus: (event) => event.preventDefault(),
142
145
  onClick: (event) => event.stopPropagation(),
143
146
  children: [
144
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
147
+ /* @__PURE__ */ jsxs("div", { className: "ui-month-picker-nav", children: [
145
148
  /* @__PURE__ */ jsx(
146
149
  Button,
147
150
  {
@@ -150,12 +153,12 @@ function MonthPicker({
150
153
  size: "icon-sm",
151
154
  disabled: prevDisabled,
152
155
  "aria-label": t("dataEntry.monthPicker.previousYear") ?? "Previous year",
153
- className: "bg-transparent opacity-70 hover:opacity-100",
156
+ className: "ui-month-picker-nav-button",
154
157
  onClick: () => setViewYear((y) => y - 1),
155
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
158
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "ui-month-picker-icon", "aria-hidden": "true" })
156
159
  }
157
160
  ),
158
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", "aria-live": "polite", children: viewYear }),
161
+ /* @__PURE__ */ jsx("span", { className: "ui-month-picker-nav-label", "aria-live": "polite", children: viewYear }),
159
162
  /* @__PURE__ */ jsx(
160
163
  Button,
161
164
  {
@@ -164,13 +167,13 @@ function MonthPicker({
164
167
  size: "icon-sm",
165
168
  disabled: nextDisabled,
166
169
  "aria-label": t("dataEntry.monthPicker.nextYear") ?? "Next year",
167
- className: "bg-transparent opacity-70 hover:opacity-100",
170
+ className: "ui-month-picker-nav-button",
168
171
  onClick: () => setViewYear((y) => y + 1),
169
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
172
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "ui-month-picker-icon", "aria-hidden": "true" })
170
173
  }
171
174
  )
172
175
  ] }),
173
- /* @__PURE__ */ jsx("div", { role: "grid", "aria-label": String(viewYear), className: "mt-3 grid grid-cols-3 gap-1", children: monthLabels.map((label, i) => {
176
+ /* @__PURE__ */ jsx("div", { role: "grid", "aria-label": String(viewYear), className: "ui-month-picker-grid", children: monthLabels.map((label, i) => {
174
177
  const selected = value?.getFullYear() === viewYear && value?.getMonth() === i;
175
178
  return /* @__PURE__ */ jsx(
176
179
  Button,
@@ -179,7 +182,7 @@ function MonthPicker({
179
182
  variant: selected ? "default" : "ghost",
180
183
  size: "sm",
181
184
  "aria-pressed": selected,
182
- className: "px-4 font-normal",
185
+ className: "ui-month-picker-cell",
183
186
  onClick: () => {
184
187
  emit(new Date(viewYear, i, 1));
185
188
  setOpen(false);
@@ -4,7 +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
+ import { pickGroupFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
8
8
  import { cn } from "../../lib/utils.js";
9
9
  import { Button } from "../general/button.js";
10
10
  import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
@@ -40,6 +40,9 @@ function MonthRangePicker({
40
40
  const groupId = id ?? autoId;
41
41
  const fromId = `${groupId}-from`;
42
42
  const toId = `${groupId}-to`;
43
+ const identity = useFieldIdentity({ id: groupId, name, "data-field": groupA11y["data-field"] });
44
+ const rangeField = groupA11y["data-field"] ?? identity["data-field"];
45
+ const rangeName = name ?? identity.name;
43
46
  const isControlled = useControlledLatch(valueProp !== void 0);
44
47
  const [internalValue, setInternalValue] = React.useState(defaultValue);
45
48
  const value = isControlled ? valueProp : internalValue;
@@ -99,7 +102,7 @@ function MonthRangePicker({
99
102
  }
100
103
  }
101
104
  };
102
- const innerInputClass = "min-w-0 flex-1 bg-transparent tabular-nums outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed";
105
+ const innerInputClass = "ui-month-picker-input";
103
106
  const resolvedPlaceholder = placeholder ?? t("dataEntry.monthPicker.placeholder") ?? YM_HINT;
104
107
  return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
105
108
  "div",
@@ -107,12 +110,13 @@ function MonthRangePicker({
107
110
  role: "group",
108
111
  id: groupId,
109
112
  ...groupA11y,
113
+ "data-field": rangeField,
110
114
  "aria-disabled": disabled ? true : void 0,
115
+ "data-disabled": disabled ? "" : void 0,
111
116
  className: cn(
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",
117
+ "ui-control ui-control-composite-field",
113
118
  "focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]",
114
119
  open && "border-ring ring-ring/50 ring-[3px]",
115
- disabled && "pointer-events-none cursor-not-allowed opacity-50",
116
120
  className
117
121
  ),
118
122
  onClick: () => {
@@ -123,7 +127,8 @@ function MonthRangePicker({
123
127
  "input",
124
128
  {
125
129
  id: fromId,
126
- name: name ? `${name}_from` : void 0,
130
+ "data-field": rangeField ? `${rangeField}_from` : void 0,
131
+ name: rangeName ? `${rangeName}_from` : void 0,
127
132
  value: fromText,
128
133
  disabled,
129
134
  placeholder: resolvedPlaceholder,
@@ -139,12 +144,13 @@ function MonthRangePicker({
139
144
  onBlur: () => setFromText(toYmText(value?.from))
140
145
  }
141
146
  ),
142
- /* @__PURE__ */ jsx(ArrowRight, { className: "text-muted-foreground size-3.5 shrink-0", "aria-hidden": "true" }),
147
+ /* @__PURE__ */ jsx(ArrowRight, { className: "ui-month-picker-separator-icon", "aria-hidden": "true" }),
143
148
  /* @__PURE__ */ jsx(
144
149
  "input",
145
150
  {
146
151
  id: toId,
147
- name: name ? `${name}_to` : void 0,
152
+ "data-field": rangeField ? `${rangeField}_to` : void 0,
153
+ name: rangeName ? `${rangeName}_to` : void 0,
148
154
  value: toText,
149
155
  disabled,
150
156
  placeholder: resolvedPlaceholder,
@@ -171,9 +177,10 @@ function MonthRangePicker({
171
177
  event.stopPropagation();
172
178
  clear();
173
179
  },
174
- children: /* @__PURE__ */ jsx(X, { className: "size-4 shrink-0", "aria-hidden": "true" })
180
+ children: /* @__PURE__ */ jsx(X, { className: "ui-month-picker-icon", "aria-hidden": "true" })
175
181
  }
176
- ) : /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
182
+ ) : null,
183
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
177
184
  "button",
178
185
  {
179
186
  type: "button",
@@ -181,18 +188,18 @@ function MonthRangePicker({
181
188
  tabIndex: -1,
182
189
  "aria-label": t("dataEntry.monthPicker.openGrid") ?? "Open month grid",
183
190
  className: "text-muted-foreground hover:text-foreground shrink-0",
184
- children: /* @__PURE__ */ jsx(CalendarIcon, { className: "size-4 shrink-0", "aria-hidden": "true" })
191
+ children: /* @__PURE__ */ jsx(CalendarIcon, { className: "ui-month-picker-icon", "aria-hidden": "true" })
185
192
  }
186
193
  ) }),
187
194
  /* @__PURE__ */ jsxs(
188
195
  PopoverContent,
189
196
  {
190
- className: "w-auto p-3",
197
+ className: "ui-month-picker-panel",
191
198
  align: "start",
192
199
  onOpenAutoFocus: (event) => event.preventDefault(),
193
200
  onClick: (event) => event.stopPropagation(),
194
201
  children: [
195
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
202
+ /* @__PURE__ */ jsxs("div", { className: "ui-month-picker-nav", children: [
196
203
  /* @__PURE__ */ jsx(
197
204
  Button,
198
205
  {
@@ -201,12 +208,12 @@ function MonthRangePicker({
201
208
  size: "icon-sm",
202
209
  disabled: prevDisabled,
203
210
  "aria-label": t("dataEntry.monthPicker.previousYear") ?? "Previous year",
204
- className: "bg-transparent opacity-70 hover:opacity-100",
211
+ className: "ui-month-picker-nav-button",
205
212
  onClick: () => setViewYear((y) => y - 1),
206
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
213
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "ui-month-picker-icon", "aria-hidden": "true" })
207
214
  }
208
215
  ),
209
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", "aria-live": "polite", children: viewYear }),
216
+ /* @__PURE__ */ jsx("span", { className: "ui-month-picker-nav-label", "aria-live": "polite", children: viewYear }),
210
217
  /* @__PURE__ */ jsx(
211
218
  Button,
212
219
  {
@@ -215,13 +222,13 @@ function MonthRangePicker({
215
222
  size: "icon-sm",
216
223
  disabled: nextDisabled,
217
224
  "aria-label": t("dataEntry.monthPicker.nextYear") ?? "Next year",
218
- className: "bg-transparent opacity-70 hover:opacity-100",
225
+ className: "ui-month-picker-nav-button",
219
226
  onClick: () => setViewYear((y) => y + 1),
220
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
227
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "ui-month-picker-icon", "aria-hidden": "true" })
221
228
  }
222
229
  )
223
230
  ] }),
224
- /* @__PURE__ */ jsx("div", { role: "grid", "aria-label": String(viewYear), className: "mt-3 grid grid-cols-3 gap-1", children: monthLabels.map((label, i) => {
231
+ /* @__PURE__ */ jsx("div", { role: "grid", "aria-label": String(viewYear), className: "ui-month-picker-grid", children: monthLabels.map((label, i) => {
225
232
  const cell = new Date(viewYear, i, 1);
226
233
  const cellIdx = ymIndex(cell);
227
234
  const fromIdx = value?.from ? ymIndex(value.from) : void 0;
@@ -235,7 +242,7 @@ function MonthRangePicker({
235
242
  variant: isEdge ? "default" : inRange ? "secondary" : "ghost",
236
243
  size: "sm",
237
244
  "aria-pressed": isEdge,
238
- className: "px-4 font-normal",
245
+ className: "ui-month-picker-cell",
239
246
  onClick: () => pickMonth(cell),
240
247
  children: label
241
248
  },
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
5
5
  import { Circle } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
- import { pickFieldA11y } from "../../lib/field-a11y.js";
7
+ import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
8
8
  import { Field } from "./field.js";
9
9
  import { choiceGroupClassName } from "./choice-option.js";
10
10
  const RadioGroupRoot = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
@@ -23,7 +23,12 @@ const RadioItem = React.forwardRef(({ className, ...props }, ref) => /* @__PURE_
23
23
  ref,
24
24
  "data-slot": "radio-group-item",
25
25
  className: cn(
26
- "ui-radio focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 shrink-0 shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
26
+ // `disabled:cursor-not-allowed disabled:opacity-50` DELETED, not moved (#319):
27
+ // `.ui-radio:disabled, .ui-radio[data-disabled]` in styles/control.css already declares both
28
+ // and reads --disabled-opacity. The utility was layered after components, so it silently
29
+ // outranked that token — a service theme's --disabled-opacity never reached a radio.
30
+ // Byte-identical: --disabled-opacity defaults to 0.5.
31
+ "ui-radio aria-invalid:border-destructive aria-invalid:ring-destructive/20 shrink-0 shadow-xs transition-shadow outline-none",
27
32
  className
28
33
  ),
29
34
  ...props,
@@ -53,6 +58,10 @@ function RadioGroupOptions({
53
58
  }) {
54
59
  const reactId = React.useId();
55
60
  const groupA11y = pickFieldA11y(ariaProps);
61
+ const identity = useFieldIdentity({ id, name, "data-field": groupA11y["data-field"] });
62
+ const resolvedName = name ?? identity.name;
63
+ const resolvedField = groupA11y["data-field"] ?? identity["data-field"];
64
+ const optionDomId = (optionValue, index) => id ? `${id}-${optionValue}` : `${reactId}-${optionValue}-${index}`;
56
65
  if (options && options.length > 0) {
57
66
  return /* @__PURE__ */ jsx(
58
67
  RadioGroupRoot,
@@ -61,14 +70,23 @@ function RadioGroupOptions({
61
70
  defaultValue,
62
71
  onValueChange,
63
72
  disabled,
64
- name,
73
+ name: resolvedName,
65
74
  id,
66
75
  ...groupA11y,
76
+ "data-field": resolvedField,
67
77
  "data-orientation": orientation,
68
78
  className: choiceGroupClassName(orientation, className),
69
79
  children: options.map((opt, index) => {
70
- const id2 = `${reactId}-${opt.value}-${index}`;
71
- return /* @__PURE__ */ jsx(Field, { id: id2, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(RadioItem, { id: id2, value: opt.value, disabled: opt.disabled }) }, opt.value);
80
+ const optionId = optionDomId(opt.value, index);
81
+ return /* @__PURE__ */ jsx(Field, { id: optionId, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(
82
+ RadioItem,
83
+ {
84
+ id: optionId,
85
+ value: opt.value,
86
+ disabled: opt.disabled,
87
+ "data-field": resolvedField
88
+ }
89
+ ) }, opt.value);
72
90
  })
73
91
  }
74
92
  );
@@ -80,9 +98,10 @@ function RadioGroupOptions({
80
98
  defaultValue,
81
99
  onValueChange,
82
100
  disabled,
83
- name,
101
+ name: resolvedName,
84
102
  id,
85
103
  ...groupA11y,
104
+ "data-field": resolvedField,
86
105
  "data-orientation": orientation,
87
106
  className: choiceGroupClassName(orientation, className),
88
107
  children
@@ -9,4 +9,4 @@ export type { SearchSelectProp, SearchSelectProp as SearchSelectProps, SearchSel
9
9
  * Custom per-option rendering via `renderOption` (Ant-Design style). Form-submittable via
10
10
  * `name`; e2e-testable by the trigger's `data-testid` + each option's `${data-testid}-option-${value}`.
11
11
  */
12
- export declare function SearchSelect({ value: valueProp, defaultValue, onValueChange, options: staticOptions, loadOptions, renderOption, labelRender, selectedLabel, selectedIcon, placeholder, searchPlaceholder, emptyMessage, loadingMessage, errorMessage, clearLabel, clearable, disabled, readOnly, size, open: openProp, onOpenChange, search: searchProp, onSearchChange, filterOption, renderError, renderLoadMore, name, id, className, "data-testid": dataTestId, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-errormessage": ariaErrorMessage, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: SearchSelectProp): React.JSX.Element;
12
+ export declare function SearchSelect({ value: valueProp, defaultValue, onValueChange, options: staticOptions, loadOptions, renderOption, labelRender, selectedLabel, selectedIcon, placeholder, searchPlaceholder, emptyMessage, loadingMessage, errorMessage, clearLabel, clearable, disabled, readOnly, size, open: openProp, onOpenChange, search: searchProp, onSearchChange, filterOption, renderError, renderLoadMore, name, id, className, "data-testid": dataTestId, "data-field": dataField, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-errormessage": ariaErrorMessage, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: SearchSelectProp): React.JSX.Element;
@@ -3,7 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { ChevronsUpDown, Loader2, X } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
- import { useFieldNameFallback } from "../../lib/field-a11y.js";
6
+ import { useFieldIdentity, useFieldNameFallback } from "../../lib/field-a11y.js";
7
7
  import { cn } from "../../lib/utils.js";
8
8
  import { controlOpenRingClass } from "../../lib/control-styles.js";
9
9
  import { Button } from "../general/button.js";
@@ -42,6 +42,7 @@ function SearchSelect({
42
42
  id,
43
43
  className,
44
44
  "data-testid": dataTestId,
45
+ "data-field": dataField,
45
46
  "aria-label": ariaLabel,
46
47
  "aria-labelledby": ariaLabelledby,
47
48
  "aria-describedby": ariaDescribedby,
@@ -54,6 +55,9 @@ function SearchSelect({
54
55
  "aria-label": ariaLabel,
55
56
  "aria-labelledby": ariaLabelledby
56
57
  });
58
+ const identity = useFieldIdentity({ id, name, "data-field": dataField });
59
+ const resolvedName = name ?? identity.name;
60
+ const resolvedField = dataField ?? identity["data-field"];
57
61
  const triggerAriaLabel = ariaLabel ?? nameFallback["aria-label"];
58
62
  const triggerAriaLabelledby = ariaLabelledby ?? nameFallback["aria-labelledby"];
59
63
  const reactId = React.useId();
@@ -254,17 +258,19 @@ function SearchSelect({
254
258
  "aria-readonly": readOnly || void 0,
255
259
  disabled,
256
260
  "data-testid": dataTestId,
261
+ "data-field": resolvedField,
262
+ "data-value": value || void 0,
257
263
  className: cn(
258
264
  "w-full justify-start font-normal",
259
265
  controlOpenRingClass,
260
266
  // Reserve trailing room for the single clear-or-chevron overlay rendered below.
261
- "pe-9"
267
+ "ui-control-trigger-affixed"
262
268
  ),
263
269
  children: /* @__PURE__ */ jsx(
264
270
  "span",
265
271
  {
266
272
  className: cn(
267
- "flex min-w-0 items-center gap-2 text-start",
273
+ "ui-search-select-option-body text-start",
268
274
  !value && "text-muted-foreground"
269
275
  ),
270
276
  children: value && labelRender ? labelRender({ value, label: currentLabel, option: selectedOption ?? void 0 }) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -275,7 +281,7 @@ function SearchSelect({
275
281
  )
276
282
  }
277
283
  ) }),
278
- name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value, readOnly: true }) : null,
284
+ resolvedName ? /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value, readOnly: true }) : null,
279
285
  /* @__PURE__ */ jsx(
280
286
  PopoverContent,
281
287
  {
@@ -284,9 +290,9 @@ function SearchSelect({
284
290
  align: "start",
285
291
  sideOffset: 4,
286
292
  collisionPadding: 12,
287
- className: "flex max-h-[var(--radix-popover-content-available-height)] w-max max-w-[min(32rem,calc(100vw-1.5rem))] min-w-[var(--radix-popover-trigger-width)] flex-col p-0",
288
- children: /* @__PURE__ */ jsxs(Command, { value, shouldFilter: false, className: "flex min-h-0 flex-col", children: [
289
- /* @__PURE__ */ jsx("div", { className: "border-border shrink-0 border-b", children: /* @__PURE__ */ jsx(
293
+ className: "ui-search-select-panel",
294
+ children: /* @__PURE__ */ jsxs(Command, { value, shouldFilter: false, className: "ui-search-select-command", children: [
295
+ /* @__PURE__ */ jsx("div", { className: "ui-search-select-search", children: /* @__PURE__ */ jsx(
290
296
  Input,
291
297
  {
292
298
  autoFocus: true,
@@ -298,7 +304,7 @@ function SearchSelect({
298
304
  onChange: (event) => setQuery(event.target.value),
299
305
  onKeyDown,
300
306
  placeholder: searchPlaceholder ?? t("dataEntry.searchSelect.search"),
301
- className: "rounded-none border-0 shadow-none focus-visible:ring-0"
307
+ className: "ui-search-select-search-input"
302
308
  }
303
309
  ) }),
304
310
  /* @__PURE__ */ jsxs(
@@ -307,7 +313,7 @@ function SearchSelect({
307
313
  id: listId,
308
314
  role: "listbox",
309
315
  "aria-busy": loading,
310
- className: "max-h-none min-h-0 flex-1 overflow-y-auto p-1",
316
+ className: "ui-search-select-list",
311
317
  onScroll,
312
318
  children: [
313
319
  grouped.map((group) => {
@@ -331,11 +337,11 @@ function SearchSelect({
331
337
  onMouseEnter: () => !option.disabled && setActiveIndex(index),
332
338
  onMouseDown: (event) => event.preventDefault(),
333
339
  onClick: () => select(option),
334
- children: renderOption ? /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: renderOption(option) }) : /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
340
+ children: renderOption ? /* @__PURE__ */ jsx("div", { className: "ui-search-select-option-slot", children: renderOption(option) }) : /* @__PURE__ */ jsxs("div", { className: "ui-search-select-option-body", children: [
335
341
  option.icon ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", "aria-hidden": "true", children: option.icon }) : null,
336
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col", children: [
337
- /* @__PURE__ */ jsx("span", { className: "truncate text-sm", children: option.label }),
338
- option.sublabel ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate text-xs", children: option.sublabel }) : null
342
+ /* @__PURE__ */ jsxs("div", { className: "ui-search-select-option-text", children: [
343
+ /* @__PURE__ */ jsx("span", { className: "ui-search-select-option-label", children: option.label }),
344
+ option.sublabel ? /* @__PURE__ */ jsx("span", { className: "ui-search-select-option-sublabel", children: option.sublabel }) : null
339
345
  ] })
340
346
  ] })
341
347
  },
@@ -343,17 +349,10 @@ function SearchSelect({
343
349
  ));
344
350
  return group.heading ? /* @__PURE__ */ jsx(CommandGroup, { heading: group.heading, children: rows }, group.heading) : /* @__PURE__ */ jsx(React.Fragment, { children: rows }, "__ungrouped");
345
351
  }),
346
- loading ? /* @__PURE__ */ jsxs(
347
- "div",
348
- {
349
- role: "status",
350
- className: "text-muted-foreground flex items-center gap-2 px-2 py-3 text-sm",
351
- children: [
352
- /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }),
353
- loadingMessage ?? t("dataEntry.searchSelect.loading")
354
- ]
355
- }
356
- ) : error ? renderError ? renderError({
352
+ loading ? /* @__PURE__ */ jsxs("div", { role: "status", className: "ui-search-select-status", children: [
353
+ /* @__PURE__ */ jsx(Loader2, { className: "ui-search-select-spinner animate-spin", "aria-hidden": "true" }),
354
+ loadingMessage ?? t("dataEntry.searchSelect.loading")
355
+ ] }) : error ? renderError ? renderError({
357
356
  message: errorMessage ?? t("dataEntry.searchSelect.error"),
358
357
  // Retry always reloads from the first page (a defined, predictable recovery —
359
358
  // NOT a resume of a failed page-N append, which would need to re-derive state).
@@ -364,7 +363,8 @@ function SearchSelect({
364
363
  role: "option",
365
364
  "aria-disabled": "true",
366
365
  "aria-selected": false,
367
- className: "text-destructive px-2 py-6 text-center text-sm",
366
+ className: "ui-search-select-placeholder",
367
+ "data-tone": "destructive",
368
368
  children: errorMessage ?? t("dataEntry.searchSelect.error")
369
369
  }
370
370
  ) : loaded.length === 0 ? /* @__PURE__ */ jsx(
@@ -373,7 +373,7 @@ function SearchSelect({
373
373
  role: "option",
374
374
  "aria-disabled": "true",
375
375
  "aria-selected": false,
376
- className: "text-muted-foreground px-2 py-6 text-center text-sm",
376
+ className: "ui-search-select-placeholder",
377
377
  children: emptyMessage ?? t("dataEntry.searchSelect.empty")
378
378
  }
379
379
  ) : null
@@ -383,7 +383,7 @@ function SearchSelect({
383
383
  renderLoadMore && hasMore ? /* @__PURE__ */ jsx(
384
384
  "div",
385
385
  {
386
- className: "border-border shrink-0 border-t p-1",
386
+ className: "ui-search-select-footer",
387
387
  onKeyDown: (event) => event.stopPropagation(),
388
388
  children: renderLoadMore({
389
389
  hasMore,
@@ -400,17 +400,23 @@ function SearchSelect({
400
400
  ]
401
401
  }
402
402
  ),
403
- /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-y-0 end-2 flex items-center", children: showClear ? /* @__PURE__ */ jsx(
403
+ /* @__PURE__ */ jsx("div", { className: "ui-control-affix", children: showClear ? /* @__PURE__ */ jsx(
404
404
  "button",
405
405
  {
406
406
  type: "button",
407
407
  "aria-label": clearLabel ?? t("dataEntry.searchSelect.clear"),
408
408
  "data-testid": optionTestId("clear"),
409
- className: "pointer-events-auto flex size-6 items-center justify-center rounded-sm opacity-50 hover:opacity-100 focus-visible:opacity-100",
409
+ className: "ui-control-affix-action",
410
410
  onClick: clear,
411
- children: /* @__PURE__ */ jsx(X, { className: "size-4", "aria-hidden": "true" })
411
+ children: /* @__PURE__ */ jsx(X, { className: "ui-control-affix-icon", "aria-hidden": "true" })
412
+ }
413
+ ) : /* @__PURE__ */ jsx(
414
+ ChevronsUpDown,
415
+ {
416
+ className: "ui-control-affix-icon ui-control-affix-indicator",
417
+ "aria-hidden": "true"
412
418
  }
413
- ) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "size-4 shrink-0 opacity-50", "aria-hidden": "true" }) })
419
+ ) })
414
420
  ] });
415
421
  }
416
422
  export {
@@ -27,6 +27,14 @@ export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectP
27
27
  * descendant CSS is needed to remove it.
28
28
  */
29
29
  showIndicator?: boolean;
30
+ /** Stable machine key (gh#337) — normally inherited from `FormField` through the Select. */
31
+ "data-field"?: string;
32
+ /**
33
+ * The selected CODE (gh#337). Normally supplied by `Select` itself — the trigger shows the
34
+ * option's LABEL, and this is the only place the underlying value is readable from the DOM
35
+ * without reaching into Radix's aria-hidden native `<select>`.
36
+ */
37
+ "data-value"?: string;
30
38
  } & React.RefAttributes<HTMLButtonElement>>;
31
39
  export declare const SelectScrollUpButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
32
40
  export declare const SelectScrollDownButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollDownButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;