@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
@@ -5,10 +5,11 @@ import { Calendar, ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
5
5
  import { cn } from "../lib/cn.js";
6
6
  import { dirOf, horizontalStep } from "../lib/direction.js";
7
7
  import { monthKey, pad } from "../lib/dates.js";
8
- import { Button, FieldLabel, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, IconButton } from "./ui.js";
8
+ import { Button, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, IconButton } from "./ui.js";
9
9
  import { Popover } from "./popover.js";
10
10
  import { splitTriggerAria } from "./trigger-aria.js";
11
- import { useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
11
+ import { useKitDateFormatter, useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
12
+ import { FieldBox, FieldLabelLine, hasContent, useFieldMessages } from "./field-parts.js";
12
13
  const DEFAULT_MONTH_PICKER_LABELS = {
13
14
  previousYear: "Previous year",
14
15
  nextYear: "Next year",
@@ -16,7 +17,11 @@ const DEFAULT_MONTH_PICKER_LABELS = {
16
17
  month: (monthYear) => monthYear,
17
18
  previousMonth: "Previous month",
18
19
  nextMonth: "Next month",
19
- today: "Today"
20
+ today: "Today",
21
+ yearPanel: "Choose a year",
22
+ earlierYears: "Earlier years",
23
+ laterYears: "Later years",
24
+ thisYear: "This year"
20
25
  };
21
26
  function useMonthPickerLabels(partial) {
22
27
  return useKitLabels("monthPicker", DEFAULT_MONTH_PICKER_LABELS, partial);
@@ -44,6 +49,12 @@ function firstOf({ year, month }) {
44
49
  }
45
50
  const DEFAULT_TRIGGER_FORMAT = { month: "long", year: "numeric" };
46
51
  const MONTH_NAME_FORMAT = { month: "long", year: "numeric" };
52
+ const YEAR_FORMAT = { year: "numeric" };
53
+ const YEAR = /^(\d{4})/;
54
+ function parseYear(key) {
55
+ const m = key ? YEAR.exec(key) : null;
56
+ return m ? Number(m[1]) : null;
57
+ }
47
58
  function useFocusBackOnClose(open, triggerRef) {
48
59
  const wasOpen = useRef(false);
49
60
  useEffect(() => {
@@ -100,6 +111,7 @@ function MonthFieldTrigger({
100
111
  valueId,
101
112
  panelId,
102
113
  padded,
114
+ small,
103
115
  disabled,
104
116
  invalid,
105
117
  className,
@@ -119,6 +131,7 @@ function MonthFieldTrigger({
119
131
  "aria-expanded": open,
120
132
  id: aria.id,
121
133
  "aria-invalid": invalid || aria["aria-invalid"] === true || aria["aria-invalid"] === "true" || void 0,
134
+ "aria-required": aria["aria-required"],
122
135
  "aria-labelledby": aria["aria-label"] && !aria["aria-labelledby"] ? void 0 : labelledBy,
123
136
  "aria-label": aria["aria-label"],
124
137
  "aria-describedby": aria["aria-describedby"],
@@ -126,6 +139,10 @@ function MonthFieldTrigger({
126
139
  FIELD_TRIGGER,
127
140
  "pe-9",
128
141
  padded && FIELD_FLOATING_PAD,
142
+ // keksdose K13 (live #260): an IconButton's 36px, and only as wide as the
143
+ // month — `py-0` with a fixed height so the box is 36px whatever the type's
144
+ // line height. `pe-9` stays: the calendar glyph still sits at the end.
145
+ small && "h-9 w-auto py-0",
129
146
  disabled && "cursor-not-allowed opacity-50",
130
147
  invalid && FIELD_INVALID,
131
148
  className
@@ -291,50 +308,212 @@ function MonthGrid({
291
308
  }) }, row)) })
292
309
  ] });
293
310
  }
311
+ const YEAR_PAGE = 12;
312
+ function YearGrid({
313
+ value,
314
+ onPick,
315
+ locale,
316
+ minYear,
317
+ maxYear,
318
+ currentYear,
319
+ labels
320
+ }) {
321
+ const clamp = (y) => Math.min(maxYear ?? y, Math.max(minYear ?? y, y));
322
+ const whole = minYear !== null && maxYear !== null && maxYear >= minYear && maxYear - minYear < YEAR_PAGE ? { first: minYear, count: maxYear - minYear + 1 } : null;
323
+ const [active, setActive] = useState(() => clamp(value ?? currentYear));
324
+ const anchor = minYear ?? (maxYear !== null ? maxYear - (YEAR_PAGE - 1) : 0);
325
+ const first = whole ? whole.first : anchor + Math.floor((active - anchor) / YEAR_PAGE) * YEAR_PAGE;
326
+ const count = whole ? whole.count : YEAR_PAGE;
327
+ const years = Array.from({ length: count }, (_, i) => first + i);
328
+ const outOfBounds = (y) => minYear !== null && y < minYear || maxYear !== null && y > maxYear;
329
+ const gridRef = useRef(null);
330
+ const captionId = `${useId()}-years`;
331
+ const yearFormat = useMemo(() => new Intl.DateTimeFormat(locale, YEAR_FORMAT), [locale]);
332
+ const caption = yearFormat.formatRange(new Date(first, 0, 1), new Date(first + count - 1, 0, 1));
333
+ const pendingFocus = useRef(null);
334
+ useEffect(() => {
335
+ const year = pendingFocus.current;
336
+ if (year === null) return;
337
+ pendingFocus.current = null;
338
+ gridRef.current?.querySelector(`[data-year="${year}"]`)?.focus();
339
+ });
340
+ useEffect(() => {
341
+ gridRef.current?.querySelector('[tabindex="0"]')?.focus();
342
+ }, []);
343
+ const moveTo = (next, focus) => {
344
+ const year = clamp(next);
345
+ if (focus) pendingFocus.current = year;
346
+ setActive(year);
347
+ };
348
+ const onCellKeyDown = (e) => {
349
+ const step = horizontalStep(e.key, e.currentTarget);
350
+ const col = (active - first) % COLUMNS;
351
+ let delta;
352
+ switch (step ? "horizontal" : e.key) {
353
+ case "horizontal":
354
+ delta = step;
355
+ break;
356
+ case "ArrowUp":
357
+ delta = -COLUMNS;
358
+ break;
359
+ case "ArrowDown":
360
+ delta = COLUMNS;
361
+ break;
362
+ case "Home":
363
+ delta = -col;
364
+ break;
365
+ case "End":
366
+ delta = Math.min(COLUMNS - 1 - col, first + count - 1 - active);
367
+ break;
368
+ case "PageUp":
369
+ delta = -YEAR_PAGE;
370
+ break;
371
+ case "PageDown":
372
+ delta = YEAR_PAGE;
373
+ break;
374
+ default:
375
+ return;
376
+ }
377
+ e.preventDefault();
378
+ if (delta !== 0) moveTo(active + delta, true);
379
+ };
380
+ const prevBlocked = minYear !== null && first - 1 < minYear;
381
+ const nextBlocked = maxYear !== null && first + count > maxYear;
382
+ const arrow = "flex size-7 items-center justify-center rounded text-[var(--text-muted)] hover:bg-[var(--bg-hover)] disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent";
383
+ return /* @__PURE__ */ jsxs("div", { className: "select-none", children: [
384
+ /* @__PURE__ */ jsxs("div", { className: cn("mb-1 flex items-center", whole ? "h-7 justify-center" : "justify-between"), children: [
385
+ !whole && /* @__PURE__ */ jsx(
386
+ "button",
387
+ {
388
+ type: "button",
389
+ "aria-label": labels.earlierYears,
390
+ disabled: prevBlocked,
391
+ onClick: () => moveTo(active - YEAR_PAGE, false),
392
+ className: arrow,
393
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4 rtl:-scale-x-100" })
394
+ }
395
+ ),
396
+ /* @__PURE__ */ jsx("div", { id: captionId, "aria-live": "polite", className: "text-sm font-semibold tabular-nums text-[var(--text-primary)]", children: caption }),
397
+ !whole && /* @__PURE__ */ jsx(
398
+ "button",
399
+ {
400
+ type: "button",
401
+ "aria-label": labels.laterYears,
402
+ disabled: nextBlocked,
403
+ onClick: () => moveTo(active + YEAR_PAGE, false),
404
+ className: arrow,
405
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4 rtl:-scale-x-100" })
406
+ }
407
+ )
408
+ ] }),
409
+ /* @__PURE__ */ jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": captionId, className: "grid gap-1", children: Array.from({ length: Math.ceil(count / COLUMNS) }, (_, row) => /* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-3 gap-1", children: years.slice(row * COLUMNS, row * COLUMNS + COLUMNS).map((year) => {
410
+ const disabled = outOfBounds(year);
411
+ const selected = year === value;
412
+ const current = year === currentYear;
413
+ return /* @__PURE__ */ jsx(
414
+ "button",
415
+ {
416
+ type: "button",
417
+ role: "gridcell",
418
+ "data-year": year,
419
+ tabIndex: year === active ? 0 : -1,
420
+ "aria-selected": selected,
421
+ "aria-disabled": disabled || void 0,
422
+ "aria-current": current ? "date" : void 0,
423
+ onClick: () => {
424
+ if (disabled) return;
425
+ setActive(year);
426
+ onPick(String(year));
427
+ },
428
+ onKeyDown: onCellKeyDown,
429
+ className: cn(
430
+ "rounded px-2 py-1.5 text-sm tabular-nums transition-colors",
431
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset",
432
+ selected && !disabled ? "focus-visible:ring-[var(--brand-contrast)]" : "focus-visible:ring-[var(--brand)]",
433
+ disabled ? "cursor-not-allowed text-[var(--text-placeholder)]" : selected ? "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)]" : "text-[var(--text-primary)] hover:bg-[var(--bg-hover)]",
434
+ current && !selected && !disabled && "ring-1 ring-inset ring-[var(--border-strong)]"
435
+ ),
436
+ children: yearFormat.format(new Date(year, 0, 1))
437
+ },
438
+ year
439
+ );
440
+ }) }, row)) })
441
+ ] });
442
+ }
294
443
  function upperFirst(text, locale) {
295
444
  return text.charAt(0).toLocaleUpperCase(locale) + text.slice(1);
296
445
  }
297
- function MonthPicker({
298
- value,
299
- onChange,
300
- locale: localeProp,
301
- min,
302
- max,
303
- label,
304
- placeholder,
305
- disabled,
306
- invalid,
307
- formatOptions,
308
- currentMonth,
309
- labels: labelsProp,
310
- className,
311
- triggerClassName,
312
- variant = "field",
313
- headingLevel = 2,
314
- showToday = true,
315
- ...rest
316
- }) {
446
+ function MonthPicker(props) {
447
+ const {
448
+ value,
449
+ onChange,
450
+ locale: localeProp,
451
+ min,
452
+ max,
453
+ label,
454
+ placeholder,
455
+ disabled,
456
+ invalid,
457
+ error,
458
+ hint,
459
+ size = "md",
460
+ mode = "month",
461
+ formatOptions,
462
+ currentMonth,
463
+ labels: labelsProp,
464
+ className,
465
+ triggerClassName,
466
+ variant = "field",
467
+ headingLevel = 2,
468
+ showToday = true,
469
+ ...rest
470
+ } = props;
317
471
  const labels = useMonthPickerLabels(labelsProp);
318
472
  const locale = useKitLocale(localeProp);
319
- const selected = parseMonth(value);
473
+ const fromProvider = useKitDateFormatter();
474
+ const years = mode === "year";
475
+ const selected = years ? null : parseMonth(value);
476
+ const selectedYear = years ? parseYear(value) : null;
320
477
  const minKey = boundKey(min);
321
478
  const maxKey = boundKey(max);
479
+ const minYear = parseYear(min);
480
+ const maxYear = parseYear(max);
322
481
  const currentKey = boundKey(currentMonth) ?? monthKey(/* @__PURE__ */ new Date());
323
- const triggerText = selected ? upperFirst(
324
- new Intl.DateTimeFormat(locale, formatOptions ?? DEFAULT_TRIGGER_FORMAT).format(
325
- firstOf(selected)
482
+ const currentYear = parseYear(currentKey);
483
+ const shownKey = years ? selectedYear !== null ? String(selectedYear) : null : selected ? toKey(selected) : null;
484
+ const triggerText = shownKey ? formatOptions === void 0 && fromProvider?.(shownKey, { unit: years ? "year" : "month", source: "monthPicker", locale, weekday: false }) || upperFirst(
485
+ new Intl.DateTimeFormat(locale, formatOptions ?? (years ? YEAR_FORMAT : DEFAULT_TRIGGER_FORMAT)).format(
486
+ years ? new Date(selectedYear, 0, 1) : firstOf(selected)
326
487
  ),
327
488
  locale
328
489
  ) : placeholder ?? "";
329
490
  const id = useId();
330
- const [aria, wrapperRest] = splitTriggerAria(rest);
491
+ const messages = useFieldMessages({ hint, error, describedBy: rest["aria-describedby"] });
492
+ const [ownAria, wrapperRest] = splitTriggerAria(rest);
493
+ const aria = { ...ownAria, "aria-describedby": messages.describedBy };
331
494
  const labelId = `${id}-label`;
332
495
  const valueId = `${id}-value`;
333
496
  const panelId = `${id}-panel`;
334
497
  const named = typeof label === "string";
498
+ const small = size === "sm" && label === void 0;
335
499
  const rootRef = useRef(null);
336
500
  const [dir, setDir] = useState("ltr");
337
- const grid = (close) => /* @__PURE__ */ jsx(
501
+ const panelLabel = years ? labels.yearPanel : labels.panel;
502
+ const grid = (close) => years ? /* @__PURE__ */ jsx(
503
+ YearGrid,
504
+ {
505
+ value: selectedYear,
506
+ locale,
507
+ minYear,
508
+ maxYear,
509
+ currentYear,
510
+ labels,
511
+ onPick: (key) => {
512
+ onChange(key);
513
+ close();
514
+ }
515
+ }
516
+ ) : /* @__PURE__ */ jsx(
338
517
  MonthGrid,
339
518
  {
340
519
  value: selected,
@@ -350,17 +529,33 @@ function MonthPicker({
350
529
  }
351
530
  );
352
531
  if (variant === "stepper") {
353
- const base = selected ?? parseMonth(currentKey);
354
- const prevKey = toKey(shiftMonth(base, -1));
355
- const nextKey = toKey(shiftMonth(base, 1));
356
- const outside = (key) => Boolean(minKey && key < minKey || maxKey && key > maxKey);
532
+ let prevKey;
533
+ let nextKey;
534
+ let nowKey;
535
+ let outside;
536
+ if (years) {
537
+ const base = selectedYear ?? currentYear;
538
+ prevKey = String(base - 1);
539
+ nextKey = String(base + 1);
540
+ nowKey = String(currentYear);
541
+ outside = (key) => {
542
+ const y = Number(key);
543
+ return minYear !== null && y < minYear || maxYear !== null && y > maxYear;
544
+ };
545
+ } else {
546
+ const base = selected ?? parseMonth(currentKey);
547
+ prevKey = toKey(shiftMonth(base, -1));
548
+ nextKey = toKey(shiftMonth(base, 1));
549
+ nowKey = currentKey;
550
+ outside = (key) => Boolean(minKey && key < minKey || maxKey && key > maxKey);
551
+ }
357
552
  const Heading = headingLevel ? `h${headingLevel}` : null;
358
553
  const trigger = /* @__PURE__ */ jsx(
359
554
  Popover,
360
555
  {
361
556
  width: 256,
362
557
  panelId,
363
- labels: { panel: labels.panel },
558
+ labels: { panel: panelLabel },
364
559
  dir,
365
560
  trigger: ({ open, toggle, ref }) => /* @__PURE__ */ jsx(
366
561
  MonthHeadingTrigger,
@@ -374,7 +569,7 @@ function MonthPicker({
374
569
  text: triggerText,
375
570
  panelId,
376
571
  disabled,
377
- aria
572
+ aria: ownAria
378
573
  }
379
574
  ),
380
575
  children: grid
@@ -388,17 +583,17 @@ function MonthPicker({
388
583
  {
389
584
  variant: "secondary",
390
585
  size: "sm",
391
- disabled: disabled || outside(currentKey),
586
+ disabled: disabled || outside(nowKey),
392
587
  onClick: () => {
393
- if (currentKey !== value) onChange(currentKey);
588
+ if (nowKey !== shownKey) onChange(nowKey);
394
589
  },
395
- children: labels.today
590
+ children: years ? labels.thisYear : labels.today
396
591
  }
397
592
  ),
398
593
  /* @__PURE__ */ jsx(
399
594
  IconButton,
400
595
  {
401
- "aria-label": labels.previousMonth,
596
+ "aria-label": years ? labels.previousYear : labels.previousMonth,
402
597
  disabled: disabled || outside(prevKey),
403
598
  onClick: () => onChange(prevKey),
404
599
  children: /* @__PURE__ */ jsx(ChevronLeft, { className: "rtl:-scale-x-100" })
@@ -407,7 +602,7 @@ function MonthPicker({
407
602
  /* @__PURE__ */ jsx(
408
603
  IconButton,
409
604
  {
410
- "aria-label": labels.nextMonth,
605
+ "aria-label": years ? labels.nextYear : labels.nextMonth,
411
606
  disabled: disabled || outside(nextKey),
412
607
  onClick: () => onChange(nextKey),
413
608
  children: /* @__PURE__ */ jsx(ChevronRight, { className: "rtl:-scale-x-100" })
@@ -416,52 +611,62 @@ function MonthPicker({
416
611
  ] })
417
612
  ] });
418
613
  }
419
- return /* @__PURE__ */ jsxs("div", { ...wrapperRest, ref: rootRef, className: cn("relative", className), children: [
420
- label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
421
- named && /* @__PURE__ */ jsx("span", { id: labelId, "aria-hidden": true, className: "sr-only-fixed", children: label }),
422
- /* @__PURE__ */ jsx(
423
- Popover,
424
- {
425
- width: 256,
426
- panelId,
427
- labels: { panel: labels.panel },
428
- dir,
429
- trigger: ({ open, toggle, ref }) => /* @__PURE__ */ jsx(
430
- MonthFieldTrigger,
431
- {
432
- open,
433
- toggle: () => {
434
- setDir(dirOf(rootRef.current));
435
- toggle();
436
- },
437
- triggerRef: ref,
438
- triggerText,
439
- hasValue: selected != null,
440
- labelledBy: [
441
- aria["aria-labelledby"],
442
- named ? labelId : !aria["aria-labelledby"] && aria.id ? aria.id : void 0,
443
- valueId
444
- ].filter(Boolean).join(" "),
445
- aria,
446
- valueId,
447
- panelId,
448
- padded: label !== void 0,
449
- disabled,
450
- invalid,
451
- className: triggerClassName
452
- }
453
- ),
454
- children: grid
455
- }
456
- ),
457
- /* @__PURE__ */ jsx(
458
- Calendar,
459
- {
460
- "aria-hidden": true,
461
- className: "pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]"
462
- }
463
- )
464
- ] });
614
+ const field = (
615
+ // `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a
616
+ // full-width box would park it at the far end of the row, away from its trigger.
617
+ /* @__PURE__ */ jsxs("div", { ...wrapperRest, ref: rootRef, className: cn("relative", small && "w-fit max-w-full", className), children: [
618
+ label !== void 0 && /* @__PURE__ */ jsx(FieldLabelLine, { label, hint: messages.labelHint }),
619
+ named && /* @__PURE__ */ jsx("span", { id: labelId, "aria-hidden": true, className: "sr-only-fixed", children: label }),
620
+ /* @__PURE__ */ jsx(
621
+ Popover,
622
+ {
623
+ width: 256,
624
+ panelId,
625
+ labels: { panel: panelLabel },
626
+ dir,
627
+ trigger: ({ open, toggle, ref }) => /* @__PURE__ */ jsx(
628
+ MonthFieldTrigger,
629
+ {
630
+ open,
631
+ toggle: () => {
632
+ setDir(dirOf(rootRef.current));
633
+ toggle();
634
+ },
635
+ triggerRef: ref,
636
+ triggerText,
637
+ hasValue: shownKey !== null,
638
+ labelledBy: [
639
+ aria["aria-labelledby"],
640
+ named ? labelId : !aria["aria-labelledby"] && aria.id ? aria.id : void 0,
641
+ valueId
642
+ ].filter(Boolean).join(" "),
643
+ aria,
644
+ valueId,
645
+ panelId,
646
+ padded: label !== void 0,
647
+ small,
648
+ disabled,
649
+ invalid: invalid || hasContent(error),
650
+ className: triggerClassName
651
+ }
652
+ ),
653
+ children: grid
654
+ }
655
+ ),
656
+ /* @__PURE__ */ jsx(
657
+ Calendar,
658
+ {
659
+ "aria-hidden": true,
660
+ className: "pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]"
661
+ }
662
+ )
663
+ ] })
664
+ );
665
+ const endHint = label === void 0 ? messages.labelHint : void 0;
666
+ return /* @__PURE__ */ jsx(FieldBox, { below: messages.below, reserve: "error" in props || "hint" in props, children: endHint === void 0 ? field : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
667
+ /* @__PURE__ */ jsx("div", { className: cn("min-w-0", !small && "flex-1"), children: field }),
668
+ /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", children: endHint })
669
+ ] }) });
465
670
  }
466
671
  export {
467
672
  DEFAULT_MONTH_PICKER_LABELS,