@eifi1/ui-kit 0.21.0 → 0.22.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 (278) hide show
  1. package/dist/chart.d.ts +10 -8
  2. package/dist/components/account-chips.d.ts +10 -3
  3. package/dist/components/account-chips.js +5 -2
  4. package/dist/components/account-chips.js.map +1 -1
  5. package/dist/components/account-settings.d.ts +33 -2
  6. package/dist/components/account-settings.js +43 -21
  7. package/dist/components/account-settings.js.map +1 -1
  8. package/dist/components/amount-input.d.ts +27 -8
  9. package/dist/components/amount-input.js +15 -28
  10. package/dist/components/amount-input.js.map +1 -1
  11. package/dist/components/autocomplete.d.ts +8 -0
  12. package/dist/components/autocomplete.js +9 -4
  13. package/dist/components/autocomplete.js.map +1 -1
  14. package/dist/components/button-group.d.ts +10 -8
  15. package/dist/components/calculator.d.ts +10 -8
  16. package/dist/components/checkbox-group.d.ts +104 -0
  17. package/dist/components/checkbox-group.js +111 -0
  18. package/dist/components/checkbox-group.js.map +1 -0
  19. package/dist/components/checkbox.d.ts +19 -0
  20. package/dist/components/checkbox.js +30 -7
  21. package/dist/components/checkbox.js.map +1 -1
  22. package/dist/components/chip.d.ts +18 -0
  23. package/dist/components/chip.js +47 -10
  24. package/dist/components/chip.js.map +1 -1
  25. package/dist/components/combobox.d.ts +13 -2
  26. package/dist/components/combobox.js +17 -8
  27. package/dist/components/combobox.js.map +1 -1
  28. package/dist/components/confirm-dialog.d.ts +66 -161
  29. package/dist/components/copy-button.d.ts +10 -8
  30. package/dist/components/country-select.d.ts +124 -0
  31. package/dist/components/country-select.js +237 -0
  32. package/dist/components/country-select.js.map +1 -0
  33. package/dist/components/currency-select.d.ts +17 -1
  34. package/dist/components/currency-select.js +56 -23
  35. package/dist/components/currency-select.js.map +1 -1
  36. package/dist/components/danger-confirm.d.ts +66 -166
  37. package/dist/components/danger-confirm.js +133 -49
  38. package/dist/components/danger-confirm.js.map +1 -1
  39. package/dist/components/data-table-cells.d.ts +1 -2
  40. package/dist/components/data-table-filter-popover.d.ts +1 -2
  41. package/dist/components/data-table-filters.d.ts +1 -2
  42. package/dist/components/data-table-labels.d.ts +4 -97
  43. package/dist/components/data-table-labels.js +9 -1
  44. package/dist/components/data-table-labels.js.map +1 -1
  45. package/dist/components/data-table-pagination.d.ts +3 -1
  46. package/dist/components/data-table.d.ts +1 -2
  47. package/dist/components/data-table.js +98 -3
  48. package/dist/components/data-table.js.map +1 -1
  49. package/dist/components/date-picker.d.ts +25 -5
  50. package/dist/components/date-picker.js +110 -59
  51. package/dist/components/date-picker.js.map +1 -1
  52. package/dist/components/entity-combobox.d.ts +8 -1
  53. package/dist/components/entity-combobox.js +9 -4
  54. package/dist/components/entity-combobox.js.map +1 -1
  55. package/dist/components/facing-pair.d.ts +10 -8
  56. package/dist/components/field-anatomy.d.ts +95 -0
  57. package/dist/components/field-anatomy.js +84 -0
  58. package/dist/components/field-anatomy.js.map +1 -0
  59. package/dist/components/field-parts.d.ts +114 -0
  60. package/dist/components/field-parts.js +72 -0
  61. package/dist/components/field-parts.js.map +1 -0
  62. package/dist/components/file-button.d.ts +10 -8
  63. package/dist/components/file-button.js +13 -3
  64. package/dist/components/file-button.js.map +1 -1
  65. package/dist/components/file-dropzone.d.ts +32 -9
  66. package/dist/components/file-dropzone.js +57 -34
  67. package/dist/components/file-dropzone.js.map +1 -1
  68. package/dist/components/form-actions.d.ts +10 -8
  69. package/dist/components/form-actions.js +61 -3
  70. package/dist/components/form-actions.js.map +1 -1
  71. package/dist/components/iban-input.d.ts +66 -0
  72. package/dist/components/iban-input.js +125 -0
  73. package/dist/components/iban-input.js.map +1 -0
  74. package/dist/components/icon-picker.d.ts +12 -2
  75. package/dist/components/icon-picker.js +9 -2
  76. package/dist/components/icon-picker.js.map +1 -1
  77. package/dist/components/inline-edit-field.d.ts +167 -0
  78. package/dist/components/inline-edit-field.js +293 -0
  79. package/dist/components/inline-edit-field.js.map +1 -0
  80. package/dist/components/language-select.d.ts +127 -0
  81. package/dist/components/language-select.js +18 -0
  82. package/dist/components/language-select.js.map +1 -0
  83. package/dist/components/line-items.d.ts +25 -3
  84. package/dist/components/line-items.js +55 -46
  85. package/dist/components/line-items.js.map +1 -1
  86. package/dist/components/list.d.ts +25 -0
  87. package/dist/components/list.js +16 -5
  88. package/dist/components/list.js.map +1 -1
  89. package/dist/components/money-field.d.ts +20 -8
  90. package/dist/components/money-field.js.map +1 -1
  91. package/dist/components/month-picker.d.ts +52 -4
  92. package/dist/components/month-picker.js +290 -86
  93. package/dist/components/month-picker.js.map +1 -1
  94. package/dist/components/multi-select.d.ts +8 -1
  95. package/dist/components/multi-select.js +9 -4
  96. package/dist/components/multi-select.js.map +1 -1
  97. package/dist/components/number-field.d.ts +25 -13
  98. package/dist/components/number-field.js +6 -20
  99. package/dist/components/number-field.js.map +1 -1
  100. package/dist/components/number-input.d.ts +70 -13
  101. package/dist/components/number-input.js +90 -38
  102. package/dist/components/number-input.js.map +1 -1
  103. package/dist/components/numpad-sheet.d.ts +10 -8
  104. package/dist/components/one-time-code-input.d.ts +100 -0
  105. package/dist/components/one-time-code-input.js +137 -0
  106. package/dist/components/one-time-code-input.js.map +1 -0
  107. package/dist/components/phone-input.d.ts +66 -0
  108. package/dist/components/phone-input.js +171 -0
  109. package/dist/components/phone-input.js.map +1 -0
  110. package/dist/components/series-chart.d.ts +10 -8
  111. package/dist/components/settings-fields.d.ts +10 -8
  112. package/dist/components/share-card.d.ts +8 -6
  113. package/dist/components/sign-chip.d.ts +66 -0
  114. package/dist/components/sign-chip.js +39 -0
  115. package/dist/components/sign-chip.js.map +1 -0
  116. package/dist/components/swatch-picker.d.ts +27 -2
  117. package/dist/components/swatch-picker.js +73 -31
  118. package/dist/components/swatch-picker.js.map +1 -1
  119. package/dist/components/switch.d.ts +19 -0
  120. package/dist/components/switch.js +24 -6
  121. package/dist/components/switch.js.map +1 -1
  122. package/dist/components/text-link.d.ts +10 -8
  123. package/dist/components/tile-radio.d.ts +68 -13
  124. package/dist/components/tile-radio.js +3 -3
  125. package/dist/components/tile-radio.js.map +1 -1
  126. package/dist/components/time-input.d.ts +15 -9
  127. package/dist/components/time-input.js.map +1 -1
  128. package/dist/components/toggle-group.d.ts +47 -1
  129. package/dist/components/toggle-group.js +110 -69
  130. package/dist/components/toggle-group.js.map +1 -1
  131. package/dist/components/ui.d.ts +10 -8
  132. package/dist/components/ui.js +280 -78
  133. package/dist/components/ui.js.map +1 -1
  134. package/dist/components/use-table-state.d.ts +1 -2
  135. package/dist/{data-table-3mi2R3so.d.ts → data-table-labels-B7OdnM0S.d.ts} +140 -3
  136. package/dist/data-table.d.ts +1 -2
  137. package/dist/data-table.js +2 -1
  138. package/dist/data-table.js.map +1 -1
  139. package/dist/feedback/feedback-attachment.d.ts +1 -1
  140. package/dist/feedback/feedback-attachment.js +210 -3
  141. package/dist/feedback/feedback-attachment.js.map +1 -1
  142. package/dist/feedback/feedback-dialog.d.ts +1 -1
  143. package/dist/feedback/feedback-dialog.js.map +1 -1
  144. package/dist/feedback/feedback-inbox.d.ts +1 -1
  145. package/dist/feedback/feedback-thread.d.ts +37 -3
  146. package/dist/feedback/feedback-thread.js +38 -9
  147. package/dist/feedback/feedback-thread.js.map +1 -1
  148. package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-NhUm7Zga.d.ts} +70 -5
  149. package/dist/feedback.d.ts +2 -2
  150. package/dist/hooks/use-file-drop.d.ts +10 -8
  151. package/dist/i18n/defaults.d.ts +10 -8
  152. package/dist/i18n/defaults.js +17 -2
  153. package/dist/i18n/defaults.js.map +1 -1
  154. package/dist/i18n/german.d.ts +10 -8
  155. package/dist/i18n/german.js +50 -5
  156. package/dist/i18n/german.js.map +1 -1
  157. package/dist/i18n/kit-labels.d.ts +10 -8
  158. package/dist/i18n/kit-labels.js +8 -2
  159. package/dist/i18n/kit-labels.js.map +1 -1
  160. package/dist/i18n/languages.d.ts +10 -8
  161. package/dist/i18n/locales/de-CH.d.ts +10 -8
  162. package/dist/i18n/locales/en.d.ts +10 -8
  163. package/dist/i18n/locales/en.js +9 -0
  164. package/dist/i18n/locales/en.js.map +1 -1
  165. package/dist/i18n/locales/es.d.ts +10 -8
  166. package/dist/i18n/locales/es.js +48 -5
  167. package/dist/i18n/locales/es.js.map +1 -1
  168. package/dist/i18n/locales/fr.d.ts +10 -8
  169. package/dist/i18n/locales/fr.js +48 -5
  170. package/dist/i18n/locales/fr.js.map +1 -1
  171. package/dist/i18n/locales/hu.d.ts +10 -8
  172. package/dist/i18n/locales/hu.js +49 -5
  173. package/dist/i18n/locales/hu.js.map +1 -1
  174. package/dist/i18n/locales/it.d.ts +10 -8
  175. package/dist/i18n/locales/it.js +49 -5
  176. package/dist/i18n/locales/it.js.map +1 -1
  177. package/dist/i18n/locales/zh.d.ts +10 -8
  178. package/dist/i18n/locales/zh.js +48 -5
  179. package/dist/i18n/locales/zh.js.map +1 -1
  180. package/dist/i18n/review.d.ts +10 -8
  181. package/dist/i18n/review.js +8 -1
  182. package/dist/i18n/review.js.map +1 -1
  183. package/dist/index.d.ts +17 -10
  184. package/dist/index.js +57 -2
  185. package/dist/index.js.map +1 -1
  186. package/dist/{kit-labels-D1J2_jyD.d.ts → kit-labels-R8mIAc9N.d.ts} +811 -8
  187. package/dist/lib/countries.d.ts +43 -0
  188. package/dist/lib/countries.js +39 -0
  189. package/dist/lib/countries.js.map +1 -0
  190. package/dist/lib/decimal-marks.d.ts +46 -0
  191. package/dist/lib/decimal-marks.js +40 -0
  192. package/dist/lib/decimal-marks.js.map +1 -0
  193. package/dist/lib/iban.d.ts +98 -0
  194. package/dist/lib/iban.js +154 -0
  195. package/dist/lib/iban.js.map +1 -0
  196. package/dist/lib/isin.d.ts +30 -0
  197. package/dist/lib/isin.js +36 -0
  198. package/dist/lib/isin.js.map +1 -0
  199. package/dist/lib/phone.d.ts +69 -0
  200. package/dist/lib/phone.js +121 -0
  201. package/dist/lib/phone.js.map +1 -0
  202. package/dist/rhf/fields.d.ts +148 -11
  203. package/dist/rhf/fields.js +332 -3
  204. package/dist/rhf/fields.js.map +1 -1
  205. package/dist/rhf/form.d.ts +10 -8
  206. package/dist/rhf.d.ts +13 -9
  207. package/dist/rhf.js.map +1 -1
  208. package/dist/shell/app-shell.d.ts +10 -8
  209. package/dist/shell/top-bar-brand.d.ts +10 -8
  210. package/dist/shell.d.ts +10 -8
  211. package/dist/tour/tour.js +20 -24
  212. package/dist/tour/tour.js.map +1 -1
  213. package/dist/wizard/stepper-nav.d.ts +10 -8
  214. package/dist/wizard.d.ts +10 -8
  215. package/package.json +1 -1
  216. package/src/components/account-chips.tsx +17 -3
  217. package/src/components/account-settings.tsx +92 -26
  218. package/src/components/amount-input.tsx +49 -56
  219. package/src/components/autocomplete.tsx +133 -110
  220. package/src/components/checkbox-group.tsx +229 -0
  221. package/src/components/checkbox.tsx +72 -8
  222. package/src/components/chip.tsx +80 -10
  223. package/src/components/combobox.tsx +292 -253
  224. package/src/components/country-select.tsx +443 -0
  225. package/src/components/currency-select.tsx +109 -33
  226. package/src/components/danger-confirm.tsx +345 -77
  227. package/src/components/data-table-labels.ts +16 -1
  228. package/src/components/data-table.tsx +175 -6
  229. package/src/components/date-picker.tsx +173 -62
  230. package/src/components/entity-combobox.tsx +107 -87
  231. package/src/components/field-anatomy.tsx +190 -0
  232. package/src/components/field-parts.tsx +184 -0
  233. package/src/components/file-button.tsx +36 -3
  234. package/src/components/file-dropzone.tsx +106 -38
  235. package/src/components/form-actions.tsx +115 -2
  236. package/src/components/iban-input.tsx +231 -0
  237. package/src/components/icon-picker.tsx +26 -4
  238. package/src/components/inline-edit-field.tsx +533 -0
  239. package/src/components/language-select.tsx +78 -0
  240. package/src/components/line-items.tsx +35 -2
  241. package/src/components/list.tsx +55 -6
  242. package/src/components/money-field.tsx +10 -0
  243. package/src/components/month-picker.tsx +399 -63
  244. package/src/components/multi-select.tsx +80 -58
  245. package/src/components/number-field.tsx +28 -46
  246. package/src/components/number-input.tsx +210 -43
  247. package/src/components/one-time-code-input.tsx +288 -0
  248. package/src/components/phone-input.tsx +299 -0
  249. package/src/components/sign-chip.tsx +103 -0
  250. package/src/components/swatch-picker.tsx +118 -30
  251. package/src/components/switch.tsx +58 -7
  252. package/src/components/tile-radio.tsx +71 -16
  253. package/src/components/time-input.tsx +8 -1
  254. package/src/components/toggle-group.tsx +206 -73
  255. package/src/components/ui.tsx +472 -105
  256. package/src/data-table.ts +2 -1
  257. package/src/feedback/feedback-attachment.tsx +313 -9
  258. package/src/feedback/feedback-dialog.tsx +2 -0
  259. package/src/feedback/feedback-thread.tsx +66 -5
  260. package/src/i18n/defaults.ts +16 -1
  261. package/src/i18n/german.ts +47 -0
  262. package/src/i18n/kit-labels.tsx +78 -2
  263. package/src/i18n/locales/en.ts +10 -0
  264. package/src/i18n/locales/es.ts +46 -0
  265. package/src/i18n/locales/fr.ts +45 -0
  266. package/src/i18n/locales/hu.ts +46 -0
  267. package/src/i18n/locales/it.ts +47 -0
  268. package/src/i18n/locales/zh.ts +44 -0
  269. package/src/i18n/review.ts +7 -0
  270. package/src/index.ts +53 -3
  271. package/src/lib/countries.ts +91 -0
  272. package/src/lib/decimal-marks.ts +99 -0
  273. package/src/lib/iban.ts +171 -0
  274. package/src/lib/isin.ts +62 -0
  275. package/src/lib/phone.ts +211 -0
  276. package/src/rhf/fields.tsx +606 -9
  277. package/src/rhf.ts +2 -1
  278. package/src/tour/tour.tsx +66 -28
@@ -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-anatomy.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,
@@ -126,6 +138,10 @@ function MonthFieldTrigger({
126
138
  FIELD_TRIGGER,
127
139
  "pe-9",
128
140
  padded && FIELD_FLOATING_PAD,
141
+ // keksdose K13 (live #260): an IconButton's 36px, and only as wide as the
142
+ // month — `py-0` with a fixed height so the box is 36px whatever the type's
143
+ // line height. `pe-9` stays: the calendar glyph still sits at the end.
144
+ small && "h-9 w-auto py-0",
129
145
  disabled && "cursor-not-allowed opacity-50",
130
146
  invalid && FIELD_INVALID,
131
147
  className
@@ -291,50 +307,212 @@ function MonthGrid({
291
307
  }) }, row)) })
292
308
  ] });
293
309
  }
310
+ const YEAR_PAGE = 12;
311
+ function YearGrid({
312
+ value,
313
+ onPick,
314
+ locale,
315
+ minYear,
316
+ maxYear,
317
+ currentYear,
318
+ labels
319
+ }) {
320
+ const clamp = (y) => Math.min(maxYear ?? y, Math.max(minYear ?? y, y));
321
+ const whole = minYear !== null && maxYear !== null && maxYear >= minYear && maxYear - minYear < YEAR_PAGE ? { first: minYear, count: maxYear - minYear + 1 } : null;
322
+ const [active, setActive] = useState(() => clamp(value ?? currentYear));
323
+ const anchor = minYear ?? (maxYear !== null ? maxYear - (YEAR_PAGE - 1) : 0);
324
+ const first = whole ? whole.first : anchor + Math.floor((active - anchor) / YEAR_PAGE) * YEAR_PAGE;
325
+ const count = whole ? whole.count : YEAR_PAGE;
326
+ const years = Array.from({ length: count }, (_, i) => first + i);
327
+ const outOfBounds = (y) => minYear !== null && y < minYear || maxYear !== null && y > maxYear;
328
+ const gridRef = useRef(null);
329
+ const captionId = `${useId()}-years`;
330
+ const yearFormat = useMemo(() => new Intl.DateTimeFormat(locale, YEAR_FORMAT), [locale]);
331
+ const caption = yearFormat.formatRange(new Date(first, 0, 1), new Date(first + count - 1, 0, 1));
332
+ const pendingFocus = useRef(null);
333
+ useEffect(() => {
334
+ const year = pendingFocus.current;
335
+ if (year === null) return;
336
+ pendingFocus.current = null;
337
+ gridRef.current?.querySelector(`[data-year="${year}"]`)?.focus();
338
+ });
339
+ useEffect(() => {
340
+ gridRef.current?.querySelector('[tabindex="0"]')?.focus();
341
+ }, []);
342
+ const moveTo = (next, focus) => {
343
+ const year = clamp(next);
344
+ if (focus) pendingFocus.current = year;
345
+ setActive(year);
346
+ };
347
+ const onCellKeyDown = (e) => {
348
+ const step = horizontalStep(e.key, e.currentTarget);
349
+ const col = (active - first) % COLUMNS;
350
+ let delta;
351
+ switch (step ? "horizontal" : e.key) {
352
+ case "horizontal":
353
+ delta = step;
354
+ break;
355
+ case "ArrowUp":
356
+ delta = -COLUMNS;
357
+ break;
358
+ case "ArrowDown":
359
+ delta = COLUMNS;
360
+ break;
361
+ case "Home":
362
+ delta = -col;
363
+ break;
364
+ case "End":
365
+ delta = Math.min(COLUMNS - 1 - col, first + count - 1 - active);
366
+ break;
367
+ case "PageUp":
368
+ delta = -YEAR_PAGE;
369
+ break;
370
+ case "PageDown":
371
+ delta = YEAR_PAGE;
372
+ break;
373
+ default:
374
+ return;
375
+ }
376
+ e.preventDefault();
377
+ if (delta !== 0) moveTo(active + delta, true);
378
+ };
379
+ const prevBlocked = minYear !== null && first - 1 < minYear;
380
+ const nextBlocked = maxYear !== null && first + count > maxYear;
381
+ 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";
382
+ return /* @__PURE__ */ jsxs("div", { className: "select-none", children: [
383
+ /* @__PURE__ */ jsxs("div", { className: cn("mb-1 flex items-center", whole ? "h-7 justify-center" : "justify-between"), children: [
384
+ !whole && /* @__PURE__ */ jsx(
385
+ "button",
386
+ {
387
+ type: "button",
388
+ "aria-label": labels.earlierYears,
389
+ disabled: prevBlocked,
390
+ onClick: () => moveTo(active - YEAR_PAGE, false),
391
+ className: arrow,
392
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4 rtl:-scale-x-100" })
393
+ }
394
+ ),
395
+ /* @__PURE__ */ jsx("div", { id: captionId, "aria-live": "polite", className: "text-sm font-semibold tabular-nums text-[var(--text-primary)]", children: caption }),
396
+ !whole && /* @__PURE__ */ jsx(
397
+ "button",
398
+ {
399
+ type: "button",
400
+ "aria-label": labels.laterYears,
401
+ disabled: nextBlocked,
402
+ onClick: () => moveTo(active + YEAR_PAGE, false),
403
+ className: arrow,
404
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4 rtl:-scale-x-100" })
405
+ }
406
+ )
407
+ ] }),
408
+ /* @__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) => {
409
+ const disabled = outOfBounds(year);
410
+ const selected = year === value;
411
+ const current = year === currentYear;
412
+ return /* @__PURE__ */ jsx(
413
+ "button",
414
+ {
415
+ type: "button",
416
+ role: "gridcell",
417
+ "data-year": year,
418
+ tabIndex: year === active ? 0 : -1,
419
+ "aria-selected": selected,
420
+ "aria-disabled": disabled || void 0,
421
+ "aria-current": current ? "date" : void 0,
422
+ onClick: () => {
423
+ if (disabled) return;
424
+ setActive(year);
425
+ onPick(String(year));
426
+ },
427
+ onKeyDown: onCellKeyDown,
428
+ className: cn(
429
+ "rounded px-2 py-1.5 text-sm tabular-nums transition-colors",
430
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset",
431
+ selected && !disabled ? "focus-visible:ring-[var(--brand-contrast)]" : "focus-visible:ring-[var(--brand)]",
432
+ 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)]",
433
+ current && !selected && !disabled && "ring-1 ring-inset ring-[var(--border-strong)]"
434
+ ),
435
+ children: yearFormat.format(new Date(year, 0, 1))
436
+ },
437
+ year
438
+ );
439
+ }) }, row)) })
440
+ ] });
441
+ }
294
442
  function upperFirst(text, locale) {
295
443
  return text.charAt(0).toLocaleUpperCase(locale) + text.slice(1);
296
444
  }
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
- }) {
445
+ function MonthPicker(props) {
446
+ const {
447
+ value,
448
+ onChange,
449
+ locale: localeProp,
450
+ min,
451
+ max,
452
+ label,
453
+ placeholder,
454
+ disabled,
455
+ invalid,
456
+ error,
457
+ hint,
458
+ size = "md",
459
+ mode = "month",
460
+ formatOptions,
461
+ currentMonth,
462
+ labels: labelsProp,
463
+ className,
464
+ triggerClassName,
465
+ variant = "field",
466
+ headingLevel = 2,
467
+ showToday = true,
468
+ ...rest
469
+ } = props;
317
470
  const labels = useMonthPickerLabels(labelsProp);
318
471
  const locale = useKitLocale(localeProp);
319
- const selected = parseMonth(value);
472
+ const fromProvider = useKitDateFormatter();
473
+ const years = mode === "year";
474
+ const selected = years ? null : parseMonth(value);
475
+ const selectedYear = years ? parseYear(value) : null;
320
476
  const minKey = boundKey(min);
321
477
  const maxKey = boundKey(max);
478
+ const minYear = parseYear(min);
479
+ const maxYear = parseYear(max);
322
480
  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)
481
+ const currentYear = parseYear(currentKey);
482
+ const shownKey = years ? selectedYear !== null ? String(selectedYear) : null : selected ? toKey(selected) : null;
483
+ const triggerText = shownKey ? formatOptions === void 0 && fromProvider?.(shownKey, { unit: years ? "year" : "month", source: "monthPicker", locale, weekday: false }) || upperFirst(
484
+ new Intl.DateTimeFormat(locale, formatOptions ?? (years ? YEAR_FORMAT : DEFAULT_TRIGGER_FORMAT)).format(
485
+ years ? new Date(selectedYear, 0, 1) : firstOf(selected)
326
486
  ),
327
487
  locale
328
488
  ) : placeholder ?? "";
329
489
  const id = useId();
330
- const [aria, wrapperRest] = splitTriggerAria(rest);
490
+ const messages = useFieldMessages({ hint, error, describedBy: rest["aria-describedby"] });
491
+ const [ownAria, wrapperRest] = splitTriggerAria(rest);
492
+ const aria = { ...ownAria, "aria-describedby": messages.describedBy };
331
493
  const labelId = `${id}-label`;
332
494
  const valueId = `${id}-value`;
333
495
  const panelId = `${id}-panel`;
334
496
  const named = typeof label === "string";
497
+ const small = size === "sm" && label === void 0;
335
498
  const rootRef = useRef(null);
336
499
  const [dir, setDir] = useState("ltr");
337
- const grid = (close) => /* @__PURE__ */ jsx(
500
+ const panelLabel = years ? labels.yearPanel : labels.panel;
501
+ const grid = (close) => years ? /* @__PURE__ */ jsx(
502
+ YearGrid,
503
+ {
504
+ value: selectedYear,
505
+ locale,
506
+ minYear,
507
+ maxYear,
508
+ currentYear,
509
+ labels,
510
+ onPick: (key) => {
511
+ onChange(key);
512
+ close();
513
+ }
514
+ }
515
+ ) : /* @__PURE__ */ jsx(
338
516
  MonthGrid,
339
517
  {
340
518
  value: selected,
@@ -350,17 +528,33 @@ function MonthPicker({
350
528
  }
351
529
  );
352
530
  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);
531
+ let prevKey;
532
+ let nextKey;
533
+ let nowKey;
534
+ let outside;
535
+ if (years) {
536
+ const base = selectedYear ?? currentYear;
537
+ prevKey = String(base - 1);
538
+ nextKey = String(base + 1);
539
+ nowKey = String(currentYear);
540
+ outside = (key) => {
541
+ const y = Number(key);
542
+ return minYear !== null && y < minYear || maxYear !== null && y > maxYear;
543
+ };
544
+ } else {
545
+ const base = selected ?? parseMonth(currentKey);
546
+ prevKey = toKey(shiftMonth(base, -1));
547
+ nextKey = toKey(shiftMonth(base, 1));
548
+ nowKey = currentKey;
549
+ outside = (key) => Boolean(minKey && key < minKey || maxKey && key > maxKey);
550
+ }
357
551
  const Heading = headingLevel ? `h${headingLevel}` : null;
358
552
  const trigger = /* @__PURE__ */ jsx(
359
553
  Popover,
360
554
  {
361
555
  width: 256,
362
556
  panelId,
363
- labels: { panel: labels.panel },
557
+ labels: { panel: panelLabel },
364
558
  dir,
365
559
  trigger: ({ open, toggle, ref }) => /* @__PURE__ */ jsx(
366
560
  MonthHeadingTrigger,
@@ -374,7 +568,7 @@ function MonthPicker({
374
568
  text: triggerText,
375
569
  panelId,
376
570
  disabled,
377
- aria
571
+ aria: ownAria
378
572
  }
379
573
  ),
380
574
  children: grid
@@ -388,17 +582,17 @@ function MonthPicker({
388
582
  {
389
583
  variant: "secondary",
390
584
  size: "sm",
391
- disabled: disabled || outside(currentKey),
585
+ disabled: disabled || outside(nowKey),
392
586
  onClick: () => {
393
- if (currentKey !== value) onChange(currentKey);
587
+ if (nowKey !== shownKey) onChange(nowKey);
394
588
  },
395
- children: labels.today
589
+ children: years ? labels.thisYear : labels.today
396
590
  }
397
591
  ),
398
592
  /* @__PURE__ */ jsx(
399
593
  IconButton,
400
594
  {
401
- "aria-label": labels.previousMonth,
595
+ "aria-label": years ? labels.previousYear : labels.previousMonth,
402
596
  disabled: disabled || outside(prevKey),
403
597
  onClick: () => onChange(prevKey),
404
598
  children: /* @__PURE__ */ jsx(ChevronLeft, { className: "rtl:-scale-x-100" })
@@ -407,7 +601,7 @@ function MonthPicker({
407
601
  /* @__PURE__ */ jsx(
408
602
  IconButton,
409
603
  {
410
- "aria-label": labels.nextMonth,
604
+ "aria-label": years ? labels.nextYear : labels.nextMonth,
411
605
  disabled: disabled || outside(nextKey),
412
606
  onClick: () => onChange(nextKey),
413
607
  children: /* @__PURE__ */ jsx(ChevronRight, { className: "rtl:-scale-x-100" })
@@ -416,52 +610,62 @@ function MonthPicker({
416
610
  ] })
417
611
  ] });
418
612
  }
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
- ] });
613
+ const field = (
614
+ // `w-fit` for the toolbar size: the calendar glyph is placed against this box, and a
615
+ // full-width box would park it at the far end of the row, away from its trigger.
616
+ /* @__PURE__ */ jsxs("div", { ...wrapperRest, ref: rootRef, className: cn("relative", small && "w-fit max-w-full", className), children: [
617
+ label !== void 0 && /* @__PURE__ */ jsx(FieldLabelLine, { label, hint: messages.labelHint }),
618
+ named && /* @__PURE__ */ jsx("span", { id: labelId, "aria-hidden": true, className: "sr-only-fixed", children: label }),
619
+ /* @__PURE__ */ jsx(
620
+ Popover,
621
+ {
622
+ width: 256,
623
+ panelId,
624
+ labels: { panel: panelLabel },
625
+ dir,
626
+ trigger: ({ open, toggle, ref }) => /* @__PURE__ */ jsx(
627
+ MonthFieldTrigger,
628
+ {
629
+ open,
630
+ toggle: () => {
631
+ setDir(dirOf(rootRef.current));
632
+ toggle();
633
+ },
634
+ triggerRef: ref,
635
+ triggerText,
636
+ hasValue: shownKey !== null,
637
+ labelledBy: [
638
+ aria["aria-labelledby"],
639
+ named ? labelId : !aria["aria-labelledby"] && aria.id ? aria.id : void 0,
640
+ valueId
641
+ ].filter(Boolean).join(" "),
642
+ aria,
643
+ valueId,
644
+ panelId,
645
+ padded: label !== void 0,
646
+ small,
647
+ disabled,
648
+ invalid: invalid || hasContent(error),
649
+ className: triggerClassName
650
+ }
651
+ ),
652
+ children: grid
653
+ }
654
+ ),
655
+ /* @__PURE__ */ jsx(
656
+ Calendar,
657
+ {
658
+ "aria-hidden": true,
659
+ className: "pointer-events-none absolute end-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]"
660
+ }
661
+ )
662
+ ] })
663
+ );
664
+ const endHint = label === void 0 ? messages.labelHint : void 0;
665
+ 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: [
666
+ /* @__PURE__ */ jsx("div", { className: cn("min-w-0", !small && "flex-1"), children: field }),
667
+ /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", children: endHint })
668
+ ] }) });
465
669
  }
466
670
  export {
467
671
  DEFAULT_MONTH_PICKER_LABELS,