@eifi1/ui-kit 0.21.0 → 0.23.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 (320) hide show
  1. package/README.md +42 -25
  2. package/dist/chart.d.ts +19 -14
  3. package/dist/components/account-chips.d.ts +10 -3
  4. package/dist/components/account-chips.js +5 -2
  5. package/dist/components/account-chips.js.map +1 -1
  6. package/dist/components/account-settings.d.ts +33 -2
  7. package/dist/components/account-settings.js +43 -21
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/amount-input.d.ts +53 -14
  10. package/dist/components/amount-input.js +17 -28
  11. package/dist/components/amount-input.js.map +1 -1
  12. package/dist/components/autocomplete.d.ts +29 -0
  13. package/dist/components/autocomplete.js +134 -104
  14. package/dist/components/autocomplete.js.map +1 -1
  15. package/dist/components/button-group.d.ts +19 -14
  16. package/dist/components/calculator.d.ts +19 -14
  17. package/dist/components/checkbox-group.d.ts +104 -0
  18. package/dist/components/checkbox-group.js +111 -0
  19. package/dist/components/checkbox-group.js.map +1 -0
  20. package/dist/components/checkbox.d.ts +19 -0
  21. package/dist/components/checkbox.js +30 -7
  22. package/dist/components/checkbox.js.map +1 -1
  23. package/dist/components/chip.d.ts +18 -0
  24. package/dist/components/chip.js +47 -10
  25. package/dist/components/chip.js.map +1 -1
  26. package/dist/components/column-mapper.d.ts +182 -0
  27. package/dist/components/column-mapper.js +375 -0
  28. package/dist/components/column-mapper.js.map +1 -0
  29. package/dist/components/combobox-core.d.ts +39 -2
  30. package/dist/components/combobox-core.js +29 -6
  31. package/dist/components/combobox-core.js.map +1 -1
  32. package/dist/components/combobox.d.ts +91 -2
  33. package/dist/components/combobox.js +454 -308
  34. package/dist/components/combobox.js.map +1 -1
  35. package/dist/components/confirm-dialog.d.ts +69 -161
  36. package/dist/components/confirm-dialog.js +7 -8
  37. package/dist/components/confirm-dialog.js.map +1 -1
  38. package/dist/components/copy-button.d.ts +16 -11
  39. package/dist/components/country-select.d.ts +167 -0
  40. package/dist/components/country-select.js +293 -0
  41. package/dist/components/country-select.js.map +1 -0
  42. package/dist/components/currency-select.d.ts +17 -1
  43. package/dist/components/currency-select.js +56 -23
  44. package/dist/components/currency-select.js.map +1 -1
  45. package/dist/components/danger-confirm.d.ts +69 -166
  46. package/dist/components/danger-confirm.js +144 -50
  47. package/dist/components/danger-confirm.js.map +1 -1
  48. package/dist/components/data-table-cells.d.ts +1 -2
  49. package/dist/components/data-table-filter-popover.d.ts +1 -2
  50. package/dist/components/data-table-filters.d.ts +1 -2
  51. package/dist/components/data-table-labels.d.ts +4 -97
  52. package/dist/components/data-table-labels.js +9 -1
  53. package/dist/components/data-table-labels.js.map +1 -1
  54. package/dist/components/data-table-pagination.d.ts +3 -1
  55. package/dist/components/data-table.d.ts +1 -2
  56. package/dist/components/data-table.js +98 -3
  57. package/dist/components/data-table.js.map +1 -1
  58. package/dist/components/date-picker.d.ts +66 -5
  59. package/dist/components/date-picker.js +378 -66
  60. package/dist/components/date-picker.js.map +1 -1
  61. package/dist/components/entity-combobox.d.ts +42 -1
  62. package/dist/components/entity-combobox.js +154 -113
  63. package/dist/components/entity-combobox.js.map +1 -1
  64. package/dist/components/facing-pair.d.ts +19 -14
  65. package/dist/components/field-parts.d.ts +224 -0
  66. package/dist/components/field-parts.js +156 -0
  67. package/dist/components/field-parts.js.map +1 -0
  68. package/dist/components/field-strip.d.ts +105 -0
  69. package/dist/components/field-strip.js +46 -0
  70. package/dist/components/field-strip.js.map +1 -0
  71. package/dist/components/file-button.d.ts +19 -14
  72. package/dist/components/file-button.js +13 -3
  73. package/dist/components/file-button.js.map +1 -1
  74. package/dist/components/file-dropzone.d.ts +41 -15
  75. package/dist/components/file-dropzone.js +57 -34
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/form-actions.d.ts +18 -13
  78. package/dist/components/form-actions.js +69 -5
  79. package/dist/components/form-actions.js.map +1 -1
  80. package/dist/components/iban-input.d.ts +69 -0
  81. package/dist/components/iban-input.js +125 -0
  82. package/dist/components/iban-input.js.map +1 -0
  83. package/dist/components/icon-picker.d.ts +21 -2
  84. package/dist/components/icon-picker.js +19 -4
  85. package/dist/components/icon-picker.js.map +1 -1
  86. package/dist/components/inline-edit-field.d.ts +167 -0
  87. package/dist/components/inline-edit-field.js +293 -0
  88. package/dist/components/inline-edit-field.js.map +1 -0
  89. package/dist/components/language-select.d.ts +130 -0
  90. package/dist/components/language-select.js +18 -0
  91. package/dist/components/language-select.js.map +1 -0
  92. package/dist/components/line-items.d.ts +25 -3
  93. package/dist/components/line-items.js +55 -46
  94. package/dist/components/line-items.js.map +1 -1
  95. package/dist/components/list.d.ts +25 -0
  96. package/dist/components/list.js +16 -5
  97. package/dist/components/list.js.map +1 -1
  98. package/dist/components/money-field.d.ts +32 -14
  99. package/dist/components/money-field.js.map +1 -1
  100. package/dist/components/month-picker.d.ts +52 -4
  101. package/dist/components/month-picker.js +291 -86
  102. package/dist/components/month-picker.js.map +1 -1
  103. package/dist/components/multi-entity-combobox.d.ts +26 -1
  104. package/dist/components/multi-entity-combobox.js +143 -102
  105. package/dist/components/multi-entity-combobox.js.map +1 -1
  106. package/dist/components/multi-select.d.ts +8 -1
  107. package/dist/components/multi-select.js +9 -4
  108. package/dist/components/multi-select.js.map +1 -1
  109. package/dist/components/number-field.d.ts +34 -19
  110. package/dist/components/number-field.js +6 -20
  111. package/dist/components/number-field.js.map +1 -1
  112. package/dist/components/number-input.d.ts +79 -19
  113. package/dist/components/number-input.js +90 -38
  114. package/dist/components/number-input.js.map +1 -1
  115. package/dist/components/numpad-sheet.d.ts +19 -14
  116. package/dist/components/one-time-code-input.d.ts +100 -0
  117. package/dist/components/one-time-code-input.js +137 -0
  118. package/dist/components/one-time-code-input.js.map +1 -0
  119. package/dist/components/phone-input.d.ts +69 -0
  120. package/dist/components/phone-input.js +171 -0
  121. package/dist/components/phone-input.js.map +1 -0
  122. package/dist/components/reauth-dialog.d.ts +2 -2
  123. package/dist/components/reauth-dialog.js +6 -8
  124. package/dist/components/reauth-dialog.js.map +1 -1
  125. package/dist/components/series-chart.d.ts +19 -14
  126. package/dist/components/settings-fields.d.ts +19 -14
  127. package/dist/components/share-card.d.ts +16 -11
  128. package/dist/components/sign-chip.d.ts +66 -0
  129. package/dist/components/sign-chip.js +39 -0
  130. package/dist/components/sign-chip.js.map +1 -0
  131. package/dist/components/swatch-picker.d.ts +43 -2
  132. package/dist/components/swatch-picker.js +80 -32
  133. package/dist/components/swatch-picker.js.map +1 -1
  134. package/dist/components/switch.d.ts +19 -0
  135. package/dist/components/switch.js +24 -6
  136. package/dist/components/switch.js.map +1 -1
  137. package/dist/components/text-link.d.ts +18 -13
  138. package/dist/components/tile-radio.d.ts +88 -13
  139. package/dist/components/tile-radio.js +28 -11
  140. package/dist/components/tile-radio.js.map +1 -1
  141. package/dist/components/time-input.d.ts +24 -15
  142. package/dist/components/time-input.js.map +1 -1
  143. package/dist/components/toggle-group.d.ts +47 -1
  144. package/dist/components/toggle-group.js +110 -69
  145. package/dist/components/toggle-group.js.map +1 -1
  146. package/dist/components/trigger-aria.d.ts +3 -0
  147. package/dist/components/trigger-aria.js +3 -1
  148. package/dist/components/trigger-aria.js.map +1 -1
  149. package/dist/components/ui.d.ts +16 -11
  150. package/dist/components/ui.js +284 -88
  151. package/dist/components/ui.js.map +1 -1
  152. package/dist/components/use-table-state.d.ts +1 -2
  153. package/dist/{data-table-3mi2R3so.d.ts → data-table-labels-B7OdnM0S.d.ts} +140 -3
  154. package/dist/data-table.d.ts +1 -2
  155. package/dist/data-table.js +2 -1
  156. package/dist/data-table.js.map +1 -1
  157. package/dist/feedback/feedback-attachment.d.ts +1 -1
  158. package/dist/feedback/feedback-attachment.js +348 -41
  159. package/dist/feedback/feedback-attachment.js.map +1 -1
  160. package/dist/feedback/feedback-dialog.d.ts +1 -1
  161. package/dist/feedback/feedback-dialog.js.map +1 -1
  162. package/dist/feedback/feedback-inbox.d.ts +3 -2
  163. package/dist/feedback/feedback-inbox.js.map +1 -1
  164. package/dist/feedback/feedback-thread.d.ts +68 -253
  165. package/dist/feedback/feedback-thread.js +46 -10
  166. package/dist/feedback/feedback-thread.js.map +1 -1
  167. package/dist/{kit-labels-D1J2_jyD.d.ts → feedback-BxeQVzwq.d.ts} +1206 -20
  168. package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-fGAzZPf0.d.ts} +178 -11
  169. package/dist/feedback.d.ts +66 -2
  170. package/dist/hooks/use-file-drop.d.ts +19 -14
  171. package/dist/i18n/defaults.d.ts +19 -14
  172. package/dist/i18n/defaults.js +19 -2
  173. package/dist/i18n/defaults.js.map +1 -1
  174. package/dist/i18n/german.d.ts +19 -14
  175. package/dist/i18n/german.js +84 -5
  176. package/dist/i18n/german.js.map +1 -1
  177. package/dist/i18n/kit-labels.d.ts +15 -10
  178. package/dist/i18n/kit-labels.js +8 -2
  179. package/dist/i18n/kit-labels.js.map +1 -1
  180. package/dist/i18n/languages.d.ts +19 -14
  181. package/dist/i18n/locales/de-CH.d.ts +19 -14
  182. package/dist/i18n/locales/en.d.ts +19 -14
  183. package/dist/i18n/locales/en.js +16 -0
  184. package/dist/i18n/locales/en.js.map +1 -1
  185. package/dist/i18n/locales/es.d.ts +19 -14
  186. package/dist/i18n/locales/es.js +83 -5
  187. package/dist/i18n/locales/es.js.map +1 -1
  188. package/dist/i18n/locales/fr.d.ts +19 -14
  189. package/dist/i18n/locales/fr.js +82 -5
  190. package/dist/i18n/locales/fr.js.map +1 -1
  191. package/dist/i18n/locales/hu.d.ts +19 -14
  192. package/dist/i18n/locales/hu.js +85 -5
  193. package/dist/i18n/locales/hu.js.map +1 -1
  194. package/dist/i18n/locales/it.d.ts +19 -14
  195. package/dist/i18n/locales/it.js +84 -5
  196. package/dist/i18n/locales/it.js.map +1 -1
  197. package/dist/i18n/locales/zh.d.ts +19 -14
  198. package/dist/i18n/locales/zh.js +80 -5
  199. package/dist/i18n/locales/zh.js.map +1 -1
  200. package/dist/i18n/review.d.ts +19 -14
  201. package/dist/i18n/review.js +27 -1
  202. package/dist/i18n/review.js.map +1 -1
  203. package/dist/index.d.ts +21 -10
  204. package/dist/index.js +73 -2
  205. package/dist/index.js.map +1 -1
  206. package/dist/lib/column-mapping.d.ts +81 -0
  207. package/dist/lib/column-mapping.js +108 -0
  208. package/dist/lib/column-mapping.js.map +1 -0
  209. package/dist/lib/countries.d.ts +43 -0
  210. package/dist/lib/countries.js +39 -0
  211. package/dist/lib/countries.js.map +1 -0
  212. package/dist/lib/decimal-marks.d.ts +46 -0
  213. package/dist/lib/decimal-marks.js +40 -0
  214. package/dist/lib/decimal-marks.js.map +1 -0
  215. package/dist/lib/iban.d.ts +98 -0
  216. package/dist/lib/iban.js +154 -0
  217. package/dist/lib/iban.js.map +1 -0
  218. package/dist/lib/isin.d.ts +30 -0
  219. package/dist/lib/isin.js +36 -0
  220. package/dist/lib/isin.js.map +1 -0
  221. package/dist/lib/phone.d.ts +69 -0
  222. package/dist/lib/phone.js +121 -0
  223. package/dist/lib/phone.js.map +1 -0
  224. package/dist/lib/table-text.d.ts +109 -1
  225. package/dist/lib/table-text.js +122 -1
  226. package/dist/lib/table-text.js.map +1 -1
  227. package/dist/rhf/fields.d.ts +233 -18
  228. package/dist/rhf/fields.js +456 -3
  229. package/dist/rhf/fields.js.map +1 -1
  230. package/dist/rhf/form.d.ts +19 -14
  231. package/dist/rhf.d.ts +22 -15
  232. package/dist/rhf.js.map +1 -1
  233. package/dist/shell/app-shell.d.ts +18 -13
  234. package/dist/shell/top-bar-brand.d.ts +19 -14
  235. package/dist/shell/topbar-action-menu.d.ts +36 -3
  236. package/dist/shell/topbar-action-menu.js +74 -33
  237. package/dist/shell/topbar-action-menu.js.map +1 -1
  238. package/dist/shell.d.ts +18 -13
  239. package/dist/table-text.d.ts +1 -1
  240. package/dist/tour/tour.js +20 -24
  241. package/dist/tour/tour.js.map +1 -1
  242. package/dist/wizard/stepper-nav.d.ts +49 -16
  243. package/dist/wizard/stepper-nav.js +4 -0
  244. package/dist/wizard/stepper-nav.js.map +1 -1
  245. package/dist/wizard.d.ts +19 -14
  246. package/package.json +1 -1
  247. package/src/components/account-chips.tsx +17 -3
  248. package/src/components/account-settings.tsx +92 -26
  249. package/src/components/amount-input.tsx +68 -56
  250. package/src/components/autocomplete.tsx +198 -114
  251. package/src/components/checkbox-group.tsx +229 -0
  252. package/src/components/checkbox.tsx +72 -8
  253. package/src/components/chip.tsx +80 -10
  254. package/src/components/column-mapper.tsx +666 -0
  255. package/src/components/combobox-core.tsx +82 -9
  256. package/src/components/combobox.tsx +661 -340
  257. package/src/components/confirm-dialog.tsx +12 -8
  258. package/src/components/country-select.tsx +556 -0
  259. package/src/components/currency-select.tsx +109 -33
  260. package/src/components/danger-confirm.tsx +429 -82
  261. package/src/components/data-table-labels.ts +16 -1
  262. package/src/components/data-table.tsx +175 -6
  263. package/src/components/date-picker.tsx +595 -71
  264. package/src/components/entity-combobox.tsx +235 -124
  265. package/src/components/field-parts.tsx +403 -0
  266. package/src/components/field-strip.tsx +149 -0
  267. package/src/components/file-button.tsx +36 -3
  268. package/src/components/file-dropzone.tsx +106 -38
  269. package/src/components/form-actions.tsx +147 -6
  270. package/src/components/iban-input.tsx +231 -0
  271. package/src/components/icon-picker.tsx +47 -6
  272. package/src/components/inline-edit-field.tsx +533 -0
  273. package/src/components/language-select.tsx +78 -0
  274. package/src/components/line-items.tsx +35 -2
  275. package/src/components/list.tsx +55 -6
  276. package/src/components/money-field.tsx +13 -0
  277. package/src/components/month-picker.tsx +400 -63
  278. package/src/components/multi-entity-combobox.tsx +207 -120
  279. package/src/components/multi-select.tsx +80 -58
  280. package/src/components/number-field.tsx +28 -46
  281. package/src/components/number-input.tsx +210 -43
  282. package/src/components/one-time-code-input.tsx +288 -0
  283. package/src/components/phone-input.tsx +299 -0
  284. package/src/components/reauth-dialog.tsx +17 -18
  285. package/src/components/sign-chip.tsx +103 -0
  286. package/src/components/swatch-picker.tsx +143 -31
  287. package/src/components/switch.tsx +58 -7
  288. package/src/components/tile-radio.tsx +145 -29
  289. package/src/components/time-input.tsx +8 -1
  290. package/src/components/toggle-group.tsx +206 -73
  291. package/src/components/trigger-aria.ts +5 -0
  292. package/src/components/ui.tsx +479 -138
  293. package/src/data-table.ts +2 -1
  294. package/src/feedback/feedback-attachment.tsx +597 -46
  295. package/src/feedback/feedback-dialog.tsx +9 -3
  296. package/src/feedback/feedback-inbox.tsx +3 -2
  297. package/src/feedback/feedback-thread.tsx +113 -9
  298. package/src/i18n/defaults.ts +18 -1
  299. package/src/i18n/german.ts +89 -0
  300. package/src/i18n/kit-labels.tsx +82 -2
  301. package/src/i18n/locales/en.ts +36 -4
  302. package/src/i18n/locales/es.ts +87 -0
  303. package/src/i18n/locales/fr.ts +87 -0
  304. package/src/i18n/locales/hu.ts +83 -0
  305. package/src/i18n/locales/it.ts +88 -0
  306. package/src/i18n/locales/zh.ts +76 -0
  307. package/src/i18n/review.ts +26 -0
  308. package/src/index.ts +71 -3
  309. package/src/lib/column-mapping.ts +234 -0
  310. package/src/lib/countries.ts +91 -0
  311. package/src/lib/decimal-marks.ts +99 -0
  312. package/src/lib/iban.ts +171 -0
  313. package/src/lib/isin.ts +62 -0
  314. package/src/lib/phone.ts +211 -0
  315. package/src/lib/table-text.ts +271 -0
  316. package/src/rhf/fields.tsx +860 -9
  317. package/src/rhf.ts +3 -1
  318. package/src/shell/topbar-action-menu.tsx +134 -38
  319. package/src/tour/tour.tsx +66 -28
  320. package/src/wizard/stepper-nav.tsx +35 -1
@@ -2,6 +2,7 @@ import { useMemo, useId } from "react";
2
2
  import type { ComponentPropsWithoutRef, ReactNode } from "react";
3
3
  import { Check } from "lucide-react";
4
4
  import { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_INVALID, FIELD_FLOATING_PAD } from "./ui";
5
+ import { hasMessage, mergeDescribedBy } from "./choice-parts";
5
6
  import { cn } from "../lib/cn";
6
7
  import { DropdownPanel, DropdownSearchHeader, useDropdownSearch } from "./dropdown";
7
8
  import {
@@ -117,6 +118,22 @@ export interface CurrencySelectProps extends Omit<ComponentPropsWithoutRef<"div"
117
118
  * this package carries it now, so a form can mark any of its fields rather than
118
119
  * only the one that happened to have it first. */
119
120
  invalid?: boolean;
121
+ /**
122
+ * What is wrong with the choice, in the caller's own words — {@link Input}'s `error`
123
+ * (keksdose K4: of the kit's pickers this one had neither a message nor a caption, so
124
+ * "the account is in EUR" sat in a loose `<p>` beside it that nothing pointed at).
125
+ * Rendered under the field, after a text `hint`; merged into the trigger's
126
+ * `aria-describedby`; implies `invalid`. `null`, `false` and `""` are no message.
127
+ */
128
+ error?: ReactNode;
129
+ /**
130
+ * {@link Select}'s `hint`, the same rule (keksdose K4). A {@link FieldHint} "?" rides
131
+ * the label's line; on an unlabelled picker it sits at the field's end edge, outside
132
+ * the box. Plain TEXT (a string or a number) is a caption UNDER the field, attached
133
+ * to the trigger through `aria-describedby` — the label line has no room for a
134
+ * sentence.
135
+ */
136
+ hint?: ReactNode;
120
137
  /** Every user-facing string this control owns, shaped like `AmountInput`'s
121
138
  * `labels` so the two currency pickers in the kit are configured the same way.
122
139
  *
@@ -156,9 +173,13 @@ export function CurrencySelect({
156
173
  label,
157
174
  options,
158
175
  invalid,
176
+ error,
177
+ hint,
159
178
  labels,
160
179
  currencyNames,
161
180
  "aria-label": ariaLabel,
181
+ "aria-describedby": ariaDescribedBy,
182
+ "aria-invalid": ariaInvalid,
162
183
  ...rest
163
184
  }: CurrencySelectProps) {
164
185
  const { open, setOpen, wrapperRef, query, setQuery, inputRef } = useDropdownSearch();
@@ -185,6 +206,18 @@ export function CurrencySelect({
185
206
  }, [query, pool, currencyNames, locale]);
186
207
 
187
208
  const listboxId = `${useId()}-listbox`;
209
+ const hintId = useId();
210
+ const errorId = useId();
211
+ // Text is a caption under the field; anything else (a FieldHint) rides the label
212
+ // line. See `hint`.
213
+ const textHint = (typeof hint === "string" && hint !== "") || typeof hint === "number";
214
+ const labelHint = !textHint && hasMessage(hint) ? hint : undefined;
215
+ const hasError = hasMessage(error);
216
+ // On the TRIGGER, which is the control with a role: a caller's description used to
217
+ // land on the wrapper `<div>` through the spread, where nothing reads it. The
218
+ // caller's ids first, then the caption, then the error — Select's order.
219
+ const describedBy = mergeDescribedBy(ariaDescribedBy, textHint && hintId, hasError && errorId);
220
+ const isInvalid = Boolean(invalid) || hasError || ariaInvalid === true || ariaInvalid === "true";
188
221
 
189
222
  // The trigger's NAME. A combobox does not take its name from its content, and the
190
223
  // floating label is a <span>, not a <label for> — so without `aria-label` from the
@@ -194,44 +227,70 @@ export function CurrencySelect({
194
227
  const fieldName = typeof label === "string" ? label : text.currency;
195
228
  const name = ariaLabel ?? common.fieldValue(fieldName, selected ? selected.code : (placeholder ?? text.currency));
196
229
 
230
+ const trigger = (
231
+ <button
232
+ type="button"
233
+ aria-label={name}
234
+ onClick={() => setOpen((v) => !v)}
235
+ // The implicit `button` role supports neither `aria-expanded` nor
236
+ // `aria-invalid`, so this trigger previously wore a red border and announced
237
+ // nothing about being invalid or about there being a list behind it. The role
238
+ // that DESCRIBES it is the one that also supports the attributes — the same
239
+ // treatment its siblings (MultiSelect, the entity comboboxes) now carry, so a
240
+ // form row of pickers reads the same way throughout.
241
+ role="combobox"
242
+ aria-haspopup="listbox"
243
+ aria-controls={listboxId}
244
+ aria-expanded={open}
245
+ aria-invalid={isInvalid || undefined}
246
+ aria-describedby={describedBy}
247
+ className={cn(FIELD_TRIGGER, "pe-9", label !== undefined && FIELD_FLOATING_PAD, isInvalid && FIELD_INVALID)}
248
+ >
249
+ {/* The trigger stays COMPACT — flag + code only — so it never clips in a
250
+ narrow field; the full names live in the (wider) popup (feedback
251
+ #308). */}
252
+ <span className={cn("flex items-center gap-2 min-w-0", selected ? "text-[var(--text-primary)]" : "text-[var(--text-placeholder)]")}>
253
+ {selected ? (
254
+ <>
255
+ <CurrencyFlag country={selected.country} />
256
+ <span className="font-medium">{selected.code}</span>
257
+ </>
258
+ ) : (
259
+ placeholder ?? text.currency
260
+ )}
261
+ </span>
262
+ <FieldChevron />
263
+ </button>
264
+ );
265
+
197
266
  return (
198
267
  // The wrapper takes `rest`; the trigger takes the name. This field's label is a
199
268
  // floating <span> rather than a <label for>, so a caller who wants the control to
200
269
  // announce more than the code inside it has nowhere else to put it.
201
270
  <div {...rest} ref={wrapperRef} className={cn("relative", className)}>
202
- {label !== undefined && <FieldLabel>{label}</FieldLabel>}
203
- <button
204
- type="button"
205
- aria-label={name}
206
- onClick={() => setOpen((v) => !v)}
207
- // The implicit `button` role supports neither `aria-expanded` nor
208
- // `aria-invalid`, so this trigger previously wore a red border and announced
209
- // nothing about being invalid or about there being a list behind it. The role
210
- // that DESCRIBES it is the one that also supports the attributes — the same
211
- // treatment its siblings (MultiSelect, the entity comboboxes) now carry, so a
212
- // form row of pickers reads the same way throughout.
213
- role="combobox"
214
- aria-haspopup="listbox"
215
- aria-controls={listboxId}
216
- aria-expanded={open}
217
- aria-invalid={invalid || undefined}
218
- className={cn(FIELD_TRIGGER, "pe-9", label !== undefined && FIELD_FLOATING_PAD, invalid && FIELD_INVALID)}
219
- >
220
- {/* The trigger stays COMPACT — flag + code only — so it never clips in a
221
- narrow field; the full names live in the (wider) popup (feedback
222
- #308). */}
223
- <span className={cn("flex items-center gap-2 min-w-0", selected ? "text-[var(--text-primary)]" : "text-[var(--text-placeholder)]")}>
224
- {selected ? (
225
- <>
226
- <CurrencyFlag country={selected.country} />
227
- <span className="font-medium">{selected.code}</span>
228
- </>
229
- ) : (
230
- placeholder ?? text.currency
231
- )}
232
- </span>
233
- <FieldChevron />
234
- </button>
271
+ {label !== undefined &&
272
+ (labelHint === undefined ? (
273
+ <FieldLabel>{label}</FieldLabel>
274
+ ) : (
275
+ // The label and its "?" as one row on the label's line, as FloatingField lays
276
+ // out a static label with a hint: the row places both, the label truncates
277
+ // and the hint keeps its width. `z-10` like FieldLabel's own: the trigger is
278
+ // `relative` and comes later, so it would paint over the strip.
279
+ <div className="pointer-events-none absolute inset-x-3 top-1 z-10 flex items-center gap-1">
280
+ <FieldLabel className="static z-auto min-w-0 max-w-none">{label}</FieldLabel>
281
+ <span className="pointer-events-auto flex shrink-0 items-center">{labelHint}</span>
282
+ </div>
283
+ ))}
284
+ {label === undefined && labelHint !== undefined ? (
285
+ // No label line to ride: at the field's end edge, outside the box, as an
286
+ // unlabelled Select places its FieldHint.
287
+ <div className="flex items-center gap-1.5">
288
+ <div className="min-w-0 flex-1">{trigger}</div>
289
+ <span className="flex shrink-0 items-center">{labelHint}</span>
290
+ </div>
291
+ ) : (
292
+ trigger
293
+ )}
235
294
  {open && (
236
295
  <DropdownPanel
237
296
  // At least as wide as the trigger, but grows to fit the full currency
@@ -278,6 +337,23 @@ export function CurrencySelect({
278
337
  })}
279
338
  </DropdownPanel>
280
339
  )}
340
+ {/* After the panel, not before it, as MultiSelect places its message: the panel
341
+ is `absolute` with no `top`, so it opens at its place in the flow — under the
342
+ caption, had the caption come first. */}
343
+ {textHint && (
344
+ <p id={hintId} className="mt-1 text-[11px] leading-tight text-[var(--text-muted)]">
345
+ {hint}
346
+ </p>
347
+ )}
348
+ {hasError && (
349
+ <p id={errorId} className={FIELD_ERROR_CLASS}>
350
+ {error}
351
+ </p>
352
+ )}
281
353
  </div>
282
354
  );
283
355
  }
356
+
357
+ // The error line under a field — the type of `ui.tsx`'s (module-private) one, so a
358
+ // CurrencySelect's message is indistinguishable from an Input's.
359
+ const FIELD_ERROR_CLASS = "mt-1 text-[11px] leading-tight text-[var(--danger)]";