@boxcustodia/library 2.0.0-alpha.19 → 2.0.0-alpha.20

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 (265) hide show
  1. package/dist/components/button/button.cjs.js +1 -1
  2. package/dist/components/button/button.es.js +19 -18
  3. package/dist/components/button/components/base-button.cjs.js +1 -1
  4. package/dist/components/button/components/base-button.es.js +20 -20
  5. package/dist/components/calendar/calendar.cjs.js +1 -1
  6. package/dist/components/calendar/calendar.es.js +1 -0
  7. package/dist/components/date-picker/date-input.cjs.js +1 -1
  8. package/dist/components/date-picker/date-input.es.js +92 -75
  9. package/dist/components/date-picker/date-picker.cjs.js +1 -1
  10. package/dist/components/date-picker/date-picker.es.js +104 -95
  11. package/dist/components/date-picker/date-picker.utils.cjs.js +1 -1
  12. package/dist/components/date-picker/date-picker.utils.es.js +51 -43
  13. package/dist/components/date-picker/use-hidden-field-value.cjs.js +1 -0
  14. package/dist/components/date-picker/use-hidden-field-value.es.js +11 -0
  15. package/dist/components/menu/menu.es.js +1 -9
  16. package/dist/components/otp/otp.cjs.js +2 -0
  17. package/dist/components/otp/otp.es.js +93 -0
  18. package/dist/components/password/password.cjs.js +1 -1
  19. package/dist/components/password/password.es.js +2 -2
  20. package/dist/components/select/select.cjs.js +1 -1
  21. package/dist/components/select/select.es.js +68 -60
  22. package/dist/hooks/internal/is-apple-device.cjs.js +1 -0
  23. package/dist/hooks/internal/is-apple-device.es.js +9 -0
  24. package/dist/hooks/internal/use-latest-ref.cjs.js +1 -0
  25. package/dist/hooks/internal/use-latest-ref.es.js +11 -0
  26. package/dist/hooks/use-array/use-array.cjs.js +1 -1
  27. package/dist/hooks/use-array/use-array.es.js +54 -42
  28. package/dist/hooks/use-async/use-async.cjs.js +1 -1
  29. package/dist/hooks/use-async/use-async.es.js +53 -20
  30. package/dist/hooks/use-boolean/use-boolean.cjs.js +1 -0
  31. package/dist/hooks/use-boolean/use-boolean.es.js +25 -0
  32. package/dist/hooks/use-click-outside/use-click-outside.cjs.js +1 -1
  33. package/dist/hooks/use-click-outside/use-click-outside.es.js +26 -12
  34. package/dist/hooks/use-debounce-callback/use-debounced-callback.cjs.js +1 -1
  35. package/dist/hooks/use-debounce-callback/use-debounced-callback.es.js +27 -10
  36. package/dist/hooks/use-debounce-value/use-debounced-value.cjs.js +1 -1
  37. package/dist/hooks/use-debounce-value/use-debounced-value.es.js +7 -9
  38. package/dist/hooks/use-disclosure/use-disclosure.cjs.js +1 -1
  39. package/dist/hooks/use-disclosure/use-disclosure.es.js +21 -11
  40. package/dist/hooks/use-document-title/use-document-title.cjs.js +1 -1
  41. package/dist/hooks/use-document-title/use-document-title.es.js +14 -12
  42. package/dist/hooks/use-event-listener/use-event-listener.cjs.js +1 -1
  43. package/dist/hooks/use-event-listener/use-event-listener.es.js +17 -9
  44. package/dist/hooks/use-hotkey/use-hotkey.cjs.js +1 -1
  45. package/dist/hooks/use-hotkey/use-hotkey.es.js +30 -14
  46. package/dist/hooks/use-hotkey/utils/is-input-field.cjs.js +1 -1
  47. package/dist/hooks/use-hotkey/utils/is-input-field.es.js +4 -2
  48. package/dist/hooks/use-hotkey/utils/match-and-run.cjs.js +1 -0
  49. package/dist/hooks/use-hotkey/utils/match-and-run.es.js +12 -0
  50. package/dist/hooks/use-hotkey/utils/match-key-modifiers.cjs.js +1 -1
  51. package/dist/hooks/use-hotkey/utils/match-key-modifiers.es.js +13 -12
  52. package/dist/hooks/use-hover/use-hover.cjs.js +1 -1
  53. package/dist/hooks/use-hover/use-hover.es.js +32 -17
  54. package/dist/hooks/use-is-visible/use-is-visible.cjs.js +1 -1
  55. package/dist/hooks/use-is-visible/use-is-visible.es.js +31 -27
  56. package/dist/hooks/use-local-storage/use-local-storage.cjs.js +1 -1
  57. package/dist/hooks/use-local-storage/use-local-storage.es.js +52 -20
  58. package/dist/hooks/use-media-query/use-media-query.cjs.js +1 -1
  59. package/dist/hooks/use-media-query/use-media-query.es.js +21 -11
  60. package/dist/hooks/use-mutation/use-mutation.cjs.js +1 -1
  61. package/dist/hooks/use-mutation/use-mutation.es.js +36 -22
  62. package/dist/hooks/use-object/use-object.cjs.js +1 -1
  63. package/dist/hooks/use-object/use-object.es.js +26 -22
  64. package/dist/hooks/use-prevent-page-close/use-prevent-page-close.cjs.js +1 -0
  65. package/dist/hooks/use-prevent-page-close/use-prevent-page-close.es.js +14 -0
  66. package/dist/hooks/use-step/use-step.cjs.js +1 -1
  67. package/dist/hooks/use-step/use-step.es.js +25 -24
  68. package/dist/index.cjs.js +1 -1
  69. package/dist/index.es.js +308 -300
  70. package/dist/src/components/date-picker/date-picker.utils.d.ts +17 -0
  71. package/dist/src/components/date-picker/use-hidden-field-value.d.ts +12 -0
  72. package/dist/src/components/index.d.ts +1 -0
  73. package/dist/src/hooks/index.d.ts +2 -2
  74. package/dist/src/hooks/internal/index.d.ts +2 -0
  75. package/dist/src/hooks/internal/is-apple-device.d.ts +12 -0
  76. package/dist/src/hooks/internal/use-latest-ref.d.ts +12 -0
  77. package/dist/src/hooks/use-array/use-array.d.ts +24 -11
  78. package/dist/src/hooks/use-async/use-async.d.ts +16 -13
  79. package/dist/src/hooks/use-boolean/index.d.ts +1 -0
  80. package/dist/src/hooks/use-boolean/use-boolean.d.ts +15 -0
  81. package/dist/src/hooks/use-boolean/use-boolean.test.d.ts +1 -0
  82. package/dist/src/hooks/use-click-outside/use-click-outside.d.ts +23 -1
  83. package/dist/src/hooks/use-debounce-callback/use-debounced-callback.d.ts +19 -1
  84. package/dist/src/hooks/use-debounce-value/use-debounced-value.d.ts +10 -1
  85. package/dist/src/hooks/use-disclosure/use-disclosure.d.ts +17 -8
  86. package/dist/src/hooks/use-document-title/use-document-title.d.ts +11 -0
  87. package/dist/src/hooks/use-event-listener/use-event-listener.d.ts +18 -1
  88. package/dist/src/hooks/use-hotkey/index.d.ts +2 -1
  89. package/dist/src/hooks/use-hotkey/use-hotkey.d.ts +62 -5
  90. package/dist/src/hooks/use-hotkey/utils/index.d.ts +4 -3
  91. package/dist/src/hooks/use-hotkey/utils/is-input-field.d.ts +12 -2
  92. package/dist/src/hooks/use-hotkey/utils/is-input-field.test.d.ts +1 -0
  93. package/dist/src/hooks/use-hotkey/utils/match-and-run.d.ts +36 -0
  94. package/dist/src/hooks/use-hotkey/utils/match-and-run.test.d.ts +1 -0
  95. package/dist/src/hooks/use-hotkey/utils/match-key-modifiers.d.ts +20 -6
  96. package/dist/src/hooks/use-hotkey/utils/match-key-modifiers.test.d.ts +1 -0
  97. package/dist/src/hooks/use-hover/use-hover.d.ts +8 -4
  98. package/dist/src/hooks/use-is-visible/use-is-visible.d.ts +28 -4
  99. package/dist/src/hooks/use-local-storage/use-local-storage.d.ts +13 -2
  100. package/dist/src/hooks/use-media-query/use-media-query.d.ts +10 -1
  101. package/dist/src/hooks/use-media-query/use-media-query.test.d.ts +1 -0
  102. package/dist/src/hooks/use-mutation/use-mutation.d.ts +18 -11
  103. package/dist/src/hooks/use-object/use-object.d.ts +15 -6
  104. package/dist/src/hooks/use-prevent-page-close/index.d.ts +1 -0
  105. package/dist/src/hooks/use-prevent-page-close/use-prevent-page-close.d.ts +10 -0
  106. package/dist/src/hooks/use-prevent-page-close/use-prevent-page-close.test.d.ts +1 -0
  107. package/dist/src/hooks/use-step/use-step.d.ts +18 -11
  108. package/dist/src/utils/form.d.ts +10 -0
  109. package/package.json +1 -1
  110. package/src/components/alert-dialog/alert-dialog.test.tsx +13 -9
  111. package/src/components/auto-complete/auto-complete.test.tsx +4 -14
  112. package/src/components/avatar/avatar.test.tsx +7 -12
  113. package/src/components/button/button.test.tsx +10 -15
  114. package/src/components/button/button.tsx +14 -9
  115. package/src/components/button/components/base-button.tsx +2 -4
  116. package/src/components/calendar/calendar.test.tsx +12 -19
  117. package/src/components/calendar/calendar.tsx +4 -0
  118. package/src/components/card/card.test.tsx +4 -6
  119. package/src/components/checkbox/checkbox.test.tsx +12 -8
  120. package/src/components/checkbox-group/checkbox-group.test.tsx +7 -8
  121. package/src/components/combobox/combobox.test.tsx +24 -21
  122. package/src/components/date-picker/date-input-form.test.tsx +77 -0
  123. package/src/components/date-picker/date-input.stories.tsx +30 -18
  124. package/src/components/date-picker/date-input.tsx +77 -44
  125. package/src/components/date-picker/date-picker.stories.tsx +31 -1
  126. package/src/components/date-picker/date-picker.test.tsx +3 -13
  127. package/src/components/date-picker/date-picker.tsx +35 -16
  128. package/src/components/date-picker/date-picker.utils.test.ts +32 -14
  129. package/src/components/date-picker/date-picker.utils.ts +33 -0
  130. package/src/components/date-picker/use-date-input-popover.test.ts +3 -1
  131. package/src/components/date-picker/use-hidden-field-value.ts +23 -0
  132. package/src/components/dialog/dialog.test.tsx +10 -8
  133. package/src/components/dropzone/dropzone.test.tsx +11 -13
  134. package/src/components/empty/empty.test.tsx +4 -3
  135. package/src/components/field/field.test.tsx +12 -13
  136. package/src/components/form/form.stories.tsx +16 -1
  137. package/src/components/index.ts +1 -0
  138. package/src/components/label/label.test.tsx +3 -3
  139. package/src/components/menu/menu.tsx +1 -5
  140. package/src/components/number-input/number-input.test.tsx +6 -2
  141. package/src/components/password/password.test.tsx +20 -6
  142. package/src/components/password/password.tsx +2 -2
  143. package/src/components/popover/popover.test.tsx +4 -4
  144. package/src/components/progress/progress.test.tsx +7 -8
  145. package/src/components/radio-group/radio-group.test.tsx +17 -11
  146. package/src/components/select/select.test.tsx +10 -10
  147. package/src/components/select/select.tsx +9 -1
  148. package/src/components/stepper/stepper.stories.tsx +11 -15
  149. package/src/components/stepper/stepper.test.tsx +6 -4
  150. package/src/components/switch/switch.test.tsx +3 -3
  151. package/src/components/table/table.test.tsx +9 -3
  152. package/src/components/tabs/tabs.test.tsx +6 -2
  153. package/src/components/tag/tag.test.tsx +1 -3
  154. package/src/components/textarea/textarea.test.tsx +4 -1
  155. package/src/components/timeline/timeline.test.tsx +10 -5
  156. package/src/components/toast/toast.test.tsx +11 -14
  157. package/src/components/tooltip/tooltip.test.tsx +1 -5
  158. package/src/components/tree/tree.test.tsx +3 -1
  159. package/src/hooks/index.ts +2 -2
  160. package/src/hooks/internal/index.ts +2 -0
  161. package/src/hooks/internal/is-apple-device.test.ts +41 -0
  162. package/src/hooks/internal/is-apple-device.ts +33 -0
  163. package/src/hooks/internal/use-isomorphic-layout-effect.ts +3 -1
  164. package/src/hooks/internal/use-latest-ref.ts +21 -0
  165. package/src/hooks/use-array/use-array.stories.tsx +435 -64
  166. package/src/hooks/use-array/use-array.test.tsx +398 -15
  167. package/src/hooks/use-array/use-array.ts +105 -66
  168. package/src/hooks/use-async/use-async.stories.tsx +255 -131
  169. package/src/hooks/use-async/use-async.test.ts +397 -0
  170. package/src/hooks/use-async/use-async.ts +117 -39
  171. package/src/hooks/use-boolean/index.ts +1 -0
  172. package/src/hooks/use-boolean/use-boolean.stories.tsx +377 -0
  173. package/src/hooks/use-boolean/use-boolean.test.tsx +177 -0
  174. package/src/hooks/use-boolean/use-boolean.ts +50 -0
  175. package/src/hooks/use-click-outside/use-click-outside.stories.tsx +188 -18
  176. package/src/hooks/use-click-outside/use-click-outside.test.tsx +89 -10
  177. package/src/hooks/use-click-outside/use-click-outside.ts +62 -16
  178. package/src/hooks/use-debounce-callback/use-debounced-callback.stories.tsx +141 -41
  179. package/src/hooks/use-debounce-callback/use-debounced-callback.test.ts +217 -9
  180. package/src/hooks/use-debounce-callback/use-debounced-callback.ts +71 -11
  181. package/src/hooks/use-debounce-value/use-debounced-value.stories.tsx +247 -47
  182. package/src/hooks/use-debounce-value/use-debounced-value.test.ts +105 -10
  183. package/src/hooks/use-debounce-value/use-debounced-value.ts +19 -10
  184. package/src/hooks/use-disclosure/use-disclosure.stories.tsx +305 -14
  185. package/src/hooks/use-disclosure/use-disclosure.test.ts +198 -50
  186. package/src/hooks/use-disclosure/use-disclosure.ts +49 -29
  187. package/src/hooks/use-document-title/use-document-title.stories.tsx +54 -0
  188. package/src/hooks/use-document-title/use-document-title.test.tsx +26 -0
  189. package/src/hooks/use-document-title/{use-document-title.tsx → use-document-title.ts} +17 -3
  190. package/src/hooks/use-event-listener/use-event-listener.stories.tsx +105 -9
  191. package/src/hooks/use-event-listener/use-event-listener.test.tsx +77 -10
  192. package/src/hooks/use-event-listener/use-event-listener.ts +71 -11
  193. package/src/hooks/use-focus-trap/use-focus-trap.test.ts +31 -6
  194. package/src/hooks/use-focus-trap/use-focus-trap.ts +3 -2
  195. package/src/hooks/use-hotkey/index.ts +9 -1
  196. package/src/hooks/use-hotkey/use-hotkey.stories.tsx +279 -74
  197. package/src/hooks/use-hotkey/use-hotkey.test.tsx +286 -34
  198. package/src/hooks/use-hotkey/use-hotkey.ts +141 -17
  199. package/src/hooks/use-hotkey/utils/index.ts +8 -3
  200. package/src/hooks/use-hotkey/utils/is-input-field.test.ts +78 -0
  201. package/src/hooks/use-hotkey/utils/is-input-field.ts +31 -10
  202. package/src/hooks/use-hotkey/utils/match-and-run.test.ts +203 -0
  203. package/src/hooks/use-hotkey/utils/match-and-run.ts +62 -0
  204. package/src/hooks/use-hotkey/utils/match-key-modifiers.test.ts +65 -0
  205. package/src/hooks/use-hotkey/utils/match-key-modifiers.ts +39 -12
  206. package/src/hooks/use-hover/use-hover.stories.tsx +258 -80
  207. package/src/hooks/use-hover/use-hover.test.tsx +266 -26
  208. package/src/hooks/use-hover/use-hover.tsx +93 -28
  209. package/src/hooks/use-is-visible/use-is-visible.stories.tsx +193 -46
  210. package/src/hooks/use-is-visible/use-is-visible.test.tsx +235 -7
  211. package/src/hooks/use-is-visible/use-is-visible.ts +114 -0
  212. package/src/hooks/use-local-storage/use-local-storage.stories.tsx +129 -29
  213. package/src/hooks/use-local-storage/use-local-storage.test.ts +106 -41
  214. package/src/hooks/use-local-storage/use-local-storage.ts +100 -31
  215. package/src/hooks/use-media-query/use-media-query.stories.tsx +86 -26
  216. package/src/hooks/use-media-query/use-media-query.test.ts +132 -0
  217. package/src/hooks/use-media-query/use-media-query.ts +39 -14
  218. package/src/hooks/use-memoized-fn/use-memoized-fn.ts +0 -1
  219. package/src/hooks/use-mutation/use-mutation.stories.tsx +260 -94
  220. package/src/hooks/use-mutation/use-mutation.test.ts +359 -0
  221. package/src/hooks/use-mutation/use-mutation.ts +97 -0
  222. package/src/hooks/use-object/use-object.stories.tsx +310 -79
  223. package/src/hooks/use-object/use-object.test.tsx +235 -56
  224. package/src/hooks/use-object/use-object.ts +59 -0
  225. package/src/hooks/use-pagination/use-pagination.tsx +0 -1
  226. package/src/hooks/use-prevent-page-close/index.ts +1 -0
  227. package/src/hooks/use-prevent-page-close/use-prevent-page-close.stories.tsx +39 -0
  228. package/src/hooks/use-prevent-page-close/use-prevent-page-close.test.ts +89 -0
  229. package/src/hooks/use-prevent-page-close/use-prevent-page-close.ts +27 -0
  230. package/src/hooks/use-range-pagination/use-range-pagination.test.tsx +1 -1
  231. package/src/hooks/use-range-pagination/use-range-pagination.tsx +1 -1
  232. package/src/hooks/use-selection/use-selection.ts +0 -1
  233. package/src/hooks/use-step/use-step.stories.tsx +178 -65
  234. package/src/hooks/use-step/use-step.test.ts +178 -53
  235. package/src/hooks/use-step/use-step.ts +57 -49
  236. package/src/utils/form.test.tsx +13 -8
  237. package/src/utils/form.tsx +10 -0
  238. package/src/utils/functions/getFormData.test.ts +1 -1
  239. package/dist/hooks/use-hotkey/utils/create-hotkey-listener.cjs.js +0 -1
  240. package/dist/hooks/use-hotkey/utils/create-hotkey-listener.es.js +0 -10
  241. package/dist/hooks/use-prevent-close-window/use-prevent-close-window.cjs.js +0 -1
  242. package/dist/hooks/use-prevent-close-window/use-prevent-close-window.es.js +0 -15
  243. package/dist/hooks/use-toggle/use-toggle.cjs.js +0 -1
  244. package/dist/hooks/use-toggle/use-toggle.es.js +0 -10
  245. package/dist/src/hooks/use-hotkey/utils/create-hotkey-listener.d.ts +0 -1
  246. package/dist/src/hooks/use-prevent-close-window/index.d.ts +0 -1
  247. package/dist/src/hooks/use-prevent-close-window/use-prevent-close-window.d.ts +0 -13
  248. package/dist/src/hooks/use-toggle/index.d.ts +0 -1
  249. package/dist/src/hooks/use-toggle/use-toggle.d.ts +0 -3
  250. package/src/hooks/use-async/use-async.test.tsx +0 -68
  251. package/src/hooks/use-hotkey/utils/create-hotkey-listener.ts +0 -25
  252. package/src/hooks/use-is-visible/use-is-visible.tsx +0 -49
  253. package/src/hooks/use-mutation/use-mutation.test.tsx +0 -83
  254. package/src/hooks/use-mutation/use-mutation.tsx +0 -59
  255. package/src/hooks/use-object/use-object.tsx +0 -46
  256. package/src/hooks/use-prevent-close-window/index.ts +0 -1
  257. package/src/hooks/use-prevent-close-window/use-prevent-close-window.stories.tsx +0 -32
  258. package/src/hooks/use-prevent-close-window/use-prevent-close-window.test.ts +0 -79
  259. package/src/hooks/use-prevent-close-window/use-prevent-close-window.ts +0 -33
  260. package/src/hooks/use-toggle/index.ts +0 -1
  261. package/src/hooks/use-toggle/use-toggle.stories.tsx +0 -25
  262. package/src/hooks/use-toggle/use-toggle.test.tsx +0 -64
  263. package/src/hooks/use-toggle/use-toggle.ts +0 -14
  264. /package/dist/src/{hooks/use-prevent-close-window/use-prevent-close-window.test.d.ts → components/date-picker/date-input-form.test.d.ts} +0 -0
  265. /package/dist/src/hooks/{use-toggle/use-toggle.test.d.ts → internal/is-apple-device.test.d.ts} +0 -0
@@ -0,0 +1,77 @@
1
+ import { render, screen, within } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { Button } from "../button/button";
5
+ import { Field } from "../field/field";
6
+ import { Form } from "../form/form";
7
+ import { DateInput } from "./date-input";
8
+
9
+ const setup = () => {
10
+ const onFormSubmit = vi.fn();
11
+ const user = userEvent.setup();
12
+ render(
13
+ <Form onFormSubmit={onFormSubmit}>
14
+ <Field
15
+ name="startDate"
16
+ label="Start date"
17
+ error={{ message: "Start date is required.", match: "valueMissing" }}
18
+ >
19
+ <DateInput required />
20
+ </Field>
21
+ <Button type="submit">Submit</Button>
22
+ </Form>,
23
+ );
24
+ return { onFormSubmit, user };
25
+ };
26
+
27
+ describe("DateInput + Form across flows", () => {
28
+ it("A: digits typed → submits iso", async () => {
29
+ const { onFormSubmit, user } = setup();
30
+ const input = screen.getByRole("textbox");
31
+ await user.click(input);
32
+ await user.type(input, "05062026");
33
+ await user.tab();
34
+ await user.click(screen.getByRole("button", { name: /submit/i }));
35
+ expect(onFormSubmit).toHaveBeenCalledTimes(1);
36
+ expect(onFormSubmit.mock.calls[0][0]).toMatchObject({
37
+ startDate: "2026-06-05",
38
+ });
39
+ });
40
+
41
+ it("B: open+close popover then digits → submits iso", async () => {
42
+ const { onFormSubmit, user } = setup();
43
+ await user.click(screen.getByRole("button", { name: "Seleccionar fecha" }));
44
+ await screen.findByRole("grid");
45
+ await user.keyboard("{Escape}");
46
+ const input = screen.getByRole("textbox");
47
+ await user.click(input);
48
+ await user.type(input, "05062026");
49
+ await user.tab();
50
+ await user.click(screen.getByRole("button", { name: /submit/i }));
51
+ expect(onFormSubmit).toHaveBeenCalledTimes(1);
52
+ expect(onFormSubmit.mock.calls[0][0]).toMatchObject({
53
+ startDate: "2026-06-05",
54
+ });
55
+ });
56
+
57
+ it("C: select from calendar → submits iso", async () => {
58
+ const { onFormSubmit, user } = setup();
59
+ await user.click(screen.getByRole("button", { name: "Seleccionar fecha" }));
60
+ const grid = await screen.findByRole("grid");
61
+ await user.click(
62
+ within(grid).getByRole("button", { name: /15 de junio de 2026/ }),
63
+ );
64
+ await user.click(screen.getByRole("button", { name: /submit/i }));
65
+ expect(onFormSubmit).toHaveBeenCalledTimes(1);
66
+ expect(onFormSubmit.mock.calls[0][0]).toMatchObject({
67
+ startDate: "2026-06-15",
68
+ });
69
+ });
70
+
71
+ it("D: empty → blocks submit with required error", async () => {
72
+ const { onFormSubmit, user } = setup();
73
+ await user.click(screen.getByRole("button", { name: /submit/i }));
74
+ expect(onFormSubmit).not.toHaveBeenCalled();
75
+ expect(screen.getByText("Start date is required.")).toBeInTheDocument();
76
+ });
77
+ });
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
2
2
  import { lastDayOfMonth, nextSaturday } from "date-fns";
3
3
  import { useState } from "react";
4
4
  import { action } from "storybook/actions";
5
- import { Button } from "../../components";
5
+ import { Button, Field, Form } from "../../components";
6
6
  import { DateInput } from "./date-input";
7
7
 
8
8
  /**
@@ -145,22 +145,34 @@ export const Controlled: Story = {
145
145
  };
146
146
 
147
147
  /**
148
- * `name` and `required` enable native form participation.
149
- * The value included in `FormData` is the date string in `DD/MM/YYYY` format.
150
- * An empty field submits an empty string for the key.
148
+ * `Field` wires `required` validation and routes the error message automatically.
149
+ * The value submitted to the form is an ISO date string (`yyyy-MM-dd`);
150
+ * an empty field submits an empty string, which triggers `valueMissing`.
151
151
  */
152
- export const NativeForm: Story = {
153
- render: () => (
154
- <form
155
- className="flex flex-col gap-2"
156
- onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
157
- e.preventDefault();
158
- const data = new FormData(e.currentTarget);
159
- alert(`date: ${data.get("date")}`);
160
- }}
161
- >
162
- <DateInput name="date" required />
163
- <Button type="submit">Submit</Button>
164
- </form>
165
- ),
152
+ export const WithForm: Story = {
153
+ render: () => {
154
+ const [submitted, setSubmitted] = useState<string | null>(null);
155
+
156
+ return (
157
+ <Form
158
+ className="flex flex-col gap-3"
159
+ onFormSubmit={(data) => setSubmitted(data.startDate as string)}
160
+ >
161
+ <Field
162
+ name="startDate"
163
+ label="Start date"
164
+ error={{ message: "Start date is required.", match: "valueMissing" }}
165
+ >
166
+ <DateInput required />
167
+ </Field>
168
+ <Button type="submit">Submit</Button>
169
+ {submitted !== null && (
170
+ <p className="text-sm text-muted-foreground">
171
+ Submitted:{" "}
172
+ <span className="font-mono">{submitted || "(empty)"}</span>
173
+ </p>
174
+ )}
175
+ </Form>
176
+ );
177
+ },
166
178
  };
@@ -1,12 +1,13 @@
1
1
  "use client";
2
2
 
3
3
  import { useControllableState } from "@radix-ui/react-use-controllable-state";
4
- import { isDate } from "date-fns";
4
+ import { format, isDate } from "date-fns";
5
5
  import { CalendarIcon } from "lucide-react";
6
6
  import { KeyboardEvent } from "react";
7
7
  import {
8
8
  Calendar,
9
9
  FieldControl,
10
+ FieldRoot,
10
11
  inputBaseClasses,
11
12
  PopoverPopup,
12
13
  PopoverRoot,
@@ -16,6 +17,7 @@ import { cn } from "../../lib";
16
17
  import type { DateInputProps } from "./date-picker.model";
17
18
  import { useDateInput } from "./use-date-input";
18
19
  import { useDateInputPopover } from "./use-date-input-popover";
20
+ import { useHiddenFieldValue } from "./use-hidden-field-value";
19
21
 
20
22
  export const DateInput = (props: DateInputProps) => {
21
23
  const {
@@ -29,6 +31,7 @@ export const DateInput = (props: DateInputProps) => {
29
31
  renderFooter,
30
32
  disabled,
31
33
  onBlur: onBlurProp,
34
+ required,
32
35
  ...rest
33
36
  } = props;
34
37
 
@@ -38,6 +41,12 @@ export const DateInput = (props: DateInputProps) => {
38
41
  defaultProp: defaultValueProp ?? null,
39
42
  });
40
43
 
44
+ // Value handed to the form, derived from selectedDate. Empty string when no
45
+ // date so `required` triggers `valueMissing`.
46
+ const formValue = selectedDate ? format(selectedDate, "yyyy-MM-dd") : "";
47
+
48
+ const hiddenInputRef = useHiddenFieldValue(formValue);
49
+
41
50
  const {
42
51
  inputValue,
43
52
  handleChange,
@@ -90,10 +99,12 @@ export const DateInput = (props: DateInputProps) => {
90
99
  <div
91
100
  className={cn(
92
101
  inputBaseClasses,
93
- "flex items-center gap-2 h-8",
102
+ "relative flex items-center gap-2 h-8",
94
103
  "has-[:focus-visible]:border-ring",
95
104
  "aria-invalid:border-error has-[:focus-visible]:aria-invalid:ring-error/20",
96
- "has-aria-invalid:border-error has-[:focus-visible]:has-aria-invalid:ring-error/20",
105
+ // Re-assert the error border on focus: `has-[:focus-visible]:border-ring`
106
+ // ties on specificity and would otherwise wash the red out while typing.
107
+ "has-aria-invalid:border-error has-[:focus-visible]:has-aria-invalid:border-error has-[:focus-visible]:has-aria-invalid:ring-error/20",
97
108
  "has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50",
98
109
  !selectedDate && "text-muted-foreground",
99
110
  className,
@@ -103,6 +114,43 @@ export const DateInput = (props: DateInputProps) => {
103
114
  if (e.target === e.currentTarget) inputRef.current?.focus();
104
115
  }}
105
116
  >
117
+ {/* Hidden form control — owns the Field's name + validity. Left
118
+ uncontrolled and driven imperatively via `emitNativeInputChange`: a
119
+ controlled `value` keeps React's value tracker in sync, which would
120
+ suppress the synthetic `input` event Base UI needs to re-validate.
121
+ `onFocus` redirects to the visible input so that when the Form focuses
122
+ the invalid control on submit, focus does not trap on this sr-only node. */}
123
+ <FieldControl
124
+ render={
125
+ <input
126
+ ref={hiddenInputRef}
127
+ type="text"
128
+ className="sr-only"
129
+ tabIndex={-1}
130
+ aria-hidden="true"
131
+ required={required}
132
+ disabled={disabled}
133
+ onFocus={() => inputRef.current?.focus()}
134
+ />
135
+ }
136
+ />
137
+ <input
138
+ type="text"
139
+ value={inputValue}
140
+ onChange={handleChange}
141
+ onBlur={(e) => {
142
+ handleBlur();
143
+ onBlurProp?.(e);
144
+ }}
145
+ onKeyDown={handleKeyDown}
146
+ placeholder={placeholder ?? "dd/mm/aaaa"}
147
+ autoComplete={autoComplete ?? "off"}
148
+ disabled={disabled}
149
+ className="flex-1 min-w-0 bg-transparent outline-none text-start truncate pt-px"
150
+ aria-label={placeholder}
151
+ ref={inputRef}
152
+ {...rest}
153
+ />
106
154
  <PopoverTrigger
107
155
  type="button"
108
156
  disabled={disabled}
@@ -112,53 +160,38 @@ export const DateInput = (props: DateInputProps) => {
112
160
  <CalendarIcon data-slot="date-input-icon" />
113
161
  <span className="sr-only">Seleccionar fecha</span>
114
162
  </PopoverTrigger>
115
- <FieldControl
116
- render={
117
- <input
118
- type="text"
119
- value={inputValue}
120
- onChange={handleChange}
121
- onBlur={(e) => {
122
- handleBlur();
123
- onBlurProp?.(e);
124
- }}
125
- onKeyDown={handleKeyDown}
126
- placeholder={placeholder ?? "dd/mm/aaaa"}
127
- autoComplete={autoComplete ?? "off"}
128
- disabled={disabled}
129
- className="flex-1 min-w-0 bg-transparent outline-none text-start truncate pt-px"
130
- aria-label={placeholder}
131
- ref={inputRef}
132
- {...rest}
133
- />
134
- }
135
- />
136
163
  </div>
137
164
  <PopoverPopup
138
165
  className="w-auto p-0"
139
166
  align="start"
140
167
  data-slot="date-input-content"
141
168
  >
142
- <Calendar
143
- className="border-none"
144
- mode="single"
145
- selected={selectedDate ?? undefined}
146
- onSelect={(next) => {
147
- if (!next || !isDate(next)) {
148
- setFromExternalDate(null);
149
- return;
150
- }
151
- if (disabledDate?.(next)) return;
152
- setFromExternalDate(next);
153
- setOpen(false);
154
- }}
155
- disabled={disabledDate}
156
- />
157
- {footerContent && (
158
- <div className="p-2" data-slot="date-input-footer">
159
- {footerContent}
160
- </div>
161
- )}
169
+ {/* Isolate the popover content in its own FieldRoot. The Calendar's
170
+ month/year `Select`s are Base UI form controls; without this they
171
+ connect to the OUTER Field and hijack its shared validation ref,
172
+ nulling it on close and breaking submit validation. */}
173
+ <FieldRoot className="contents">
174
+ <Calendar
175
+ className="border-none"
176
+ mode="single"
177
+ selected={selectedDate ?? undefined}
178
+ onSelect={(next) => {
179
+ if (!next || !isDate(next)) {
180
+ setFromExternalDate(null);
181
+ return;
182
+ }
183
+ if (disabledDate?.(next)) return;
184
+ setFromExternalDate(next);
185
+ setOpen(false);
186
+ }}
187
+ disabled={disabledDate}
188
+ />
189
+ {footerContent && (
190
+ <div className="p-2" data-slot="date-input-footer">
191
+ {footerContent}
192
+ </div>
193
+ )}
194
+ </FieldRoot>
162
195
  </PopoverPopup>
163
196
  </PopoverRoot>
164
197
  );
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
2
2
  import { lastDayOfMonth, nextSaturday } from "date-fns";
3
3
  import { useState } from "react";
4
4
  import { action } from "storybook/actions";
5
- import { Button } from "../../components";
5
+ import { Button, Field, Form } from "../../components";
6
6
  import { DatePicker } from "./date-picker";
7
7
  import type { DatePickerFooterProps, DateRange } from "./date-picker.model";
8
8
 
@@ -224,3 +224,33 @@ export const WithFooter: Story = {
224
224
  return <DatePicker mode="single" renderFooter={Footer} />;
225
225
  },
226
226
  };
227
+
228
+ /**
229
+ * Native form integration with the plain `Form` component — no schema library.
230
+ *
231
+ * `DatePicker` keeps a hidden `FieldControl` whose value is the ISO date
232
+ * (`yyyy-MM-dd` in single mode, `""` when empty). That hidden control owns the
233
+ * `name`, carries `required`, and is what lands in the submitted values.
234
+ *
235
+ * Validation is the browser's: `required` + an empty string make the field
236
+ * `valueMissing`, so the browser blocks submit and `Field`'s `error.match`
237
+ * surfaces the message. `onFormSubmit` only fires once the value is valid —
238
+ * the `console.log` shows the submitted values (`startDate` as `yyyy-MM-dd`).
239
+ */
240
+ export const WithForm: Story = {
241
+ render: () => (
242
+ <Form
243
+ className="flex flex-col gap-3"
244
+ onFormSubmit={(values, e) => console.log("onSubmit", values, e)}
245
+ >
246
+ <Field
247
+ name="startDate"
248
+ label="Start date"
249
+ error={{ message: "Start date is required.", match: "valueMissing" }}
250
+ >
251
+ <DatePicker mode="single" required />
252
+ </Field>
253
+ <Button type="submit">Submit</Button>
254
+ </Form>
255
+ ),
256
+ };
@@ -170,9 +170,7 @@ describe("DatePicker component", () => {
170
170
 
171
171
  describe("multiple mode", () => {
172
172
  it("shows formatted date when single date selected", () => {
173
- render(
174
- <DatePicker mode="multiple" value={[new Date(2024, 0, 15)]} />,
175
- );
173
+ render(<DatePicker mode="multiple" value={[new Date(2024, 0, 15)]} />);
176
174
  expect(screen.getByRole("button")).toHaveTextContent("15/01/2024");
177
175
  });
178
176
 
@@ -288,10 +286,7 @@ describe("DatePicker component", () => {
288
286
 
289
287
  it("passes data-slot date-picker-footer to footer wrapper", async () => {
290
288
  render(
291
- <DatePicker
292
- mode="single"
293
- renderFooter={() => <div>Footer</div>}
294
- />,
289
+ <DatePicker mode="single" renderFooter={() => <div>Footer</div>} />,
295
290
  );
296
291
  await userEvent.click(screen.getByRole("button"));
297
292
  await waitFor(() => {
@@ -481,12 +476,7 @@ describe("DateInput component", () => {
481
476
  });
482
477
 
483
478
  it("footer data-slot date-input-footer present when open", async () => {
484
- render(
485
- <DateInput
486
- name="date"
487
- renderFooter={() => <div>Footer</div>}
488
- />,
489
- );
479
+ render(<DateInput name="date" renderFooter={() => <div>Footer</div>} />);
490
480
  await userEvent.click(
491
481
  screen.getByRole("button", { name: "Seleccionar fecha" }),
492
482
  );
@@ -6,6 +6,7 @@ import { type ReactElement, ReactNode, useRef, useState } from "react";
6
6
  import {
7
7
  Calendar,
8
8
  FieldControl,
9
+ FieldRoot,
9
10
  inputBaseClasses,
10
11
  PopoverPopup,
11
12
  PopoverRoot,
@@ -21,10 +22,12 @@ import type {
21
22
  SingleDatePickerProps,
22
23
  } from "./date-picker.model";
23
24
  import {
25
+ formatISODate,
24
26
  formatMultipleDates,
25
27
  formatRangeDate,
26
28
  formatSingleDate,
27
29
  } from "./date-picker.utils";
30
+ import { useHiddenFieldValue } from "./use-hidden-field-value";
28
31
 
29
32
  export function DatePicker(props: SingleDatePickerProps): ReactElement;
30
33
  export function DatePicker(props: RangeDatePickerProps): ReactElement;
@@ -67,6 +70,7 @@ const PickerShell = ({
67
70
  required,
68
71
  }: PickerShellProps) => {
69
72
  const triggerRef = useRef<HTMLButtonElement>(null);
73
+ const hiddenInputRef = useHiddenFieldValue(formValue);
70
74
 
71
75
  return (
72
76
  <PopoverRoot data-slot={slot} open={open} onOpenChange={onOpenChange}>
@@ -80,7 +84,11 @@ const PickerShell = ({
80
84
  "inline-flex items-center gap-2 h-8 cursor-pointer text-left",
81
85
  "focus-visible:border-ring",
82
86
  "aria-invalid:border-error focus-visible:aria-invalid:ring-error/20",
83
- "group-data-[invalid]:border-error focus-visible:group-data-[invalid]:ring-error/20",
87
+ // Attribute selector (specificity 0,2,0) beats `border-input`;
88
+ // `group-data-[invalid]` ties and loses. Matches the OTP convention.
89
+ // The `focus-visible` variant re-asserts the error border so it is
90
+ // not washed out by `focus-visible:border-ring` while focused.
91
+ "in-[[data-slot=field][data-invalid]]:border-error focus-visible:in-[[data-slot=field][data-invalid]]:border-error focus-visible:in-[[data-slot=field][data-invalid]]:ring-error/20",
84
92
  "disabled:cursor-not-allowed disabled:opacity-50",
85
93
  !hasValue && "text-muted-foreground",
86
94
  className,
@@ -88,19 +96,24 @@ const PickerShell = ({
88
96
  />
89
97
  }
90
98
  >
91
- <CalendarIcon className="shrink-0" />
92
- <span className="truncate">{displayText}</span>
99
+ <span className="flex-1 truncate">{displayText}</span>
100
+ <CalendarIcon
101
+ data-slot="date-picker-icon"
102
+ className="shrink-0 text-muted-foreground"
103
+ />
93
104
  </PopoverTrigger>
94
- {/* Hidden input registered with Field for validity tracking and FormData. */}
105
+ {/* Hidden input registered with Field for validity tracking and FormData.
106
+ Left uncontrolled and driven imperatively via `emitNativeInputChange`:
107
+ a controlled `value` keeps React's value tracker in sync, which would
108
+ suppress the synthetic `input` event Base UI needs to re-validate. */}
95
109
  <FieldControl
96
110
  render={
97
111
  <input
112
+ ref={hiddenInputRef}
98
113
  type="text"
99
114
  tabIndex={-1}
100
115
  aria-hidden="true"
101
116
  className="sr-only"
102
- value={formValue}
103
- onChange={() => {}}
104
117
  required={required}
105
118
  onFocus={() => triggerRef.current?.focus()}
106
119
  />
@@ -111,15 +124,21 @@ const PickerShell = ({
111
124
  align="start"
112
125
  data-slot={`${slot}-content`}
113
126
  >
114
- {children}
115
- {footer && (
116
- <div
117
- className={cn("p-2", classNames?.footer)}
118
- data-slot={`${slot}-footer`}
119
- >
120
- {footer}
121
- </div>
122
- )}
127
+ {/* Isolate popover content in its own FieldRoot. The Calendar's
128
+ month/year `Select`s are Base UI form controls; without this they
129
+ register on the OUTER Field, evict the hidden control's registration
130
+ on close, and the field submits empty. */}
131
+ <FieldRoot className="contents">
132
+ {children}
133
+ {footer && (
134
+ <div
135
+ className={cn("p-2", classNames?.footer)}
136
+ data-slot={`${slot}-footer`}
137
+ >
138
+ {footer}
139
+ </div>
140
+ )}
141
+ </FieldRoot>
123
142
  </PopoverPopup>
124
143
  </PopoverRoot>
125
144
  );
@@ -170,7 +189,7 @@ const SinglePicker = ({
170
189
  className={className}
171
190
  classNames={classNames}
172
191
  footer={renderFooter?.(footerProps)}
173
- formValue={formatSingleDate(value, "")}
192
+ formValue={formatISODate(value)}
174
193
  required={required}
175
194
  >
176
195
  <Calendar
@@ -24,7 +24,9 @@ describe("formatSingleDate", () => {
24
24
  });
25
25
 
26
26
  it("formats a valid date as dd/MM/yyyy", () => {
27
- expect(formatSingleDate(new Date(2025, 0, 15), "Select")).toBe("15/01/2025");
27
+ expect(formatSingleDate(new Date(2025, 0, 15), "Select")).toBe(
28
+ "15/01/2025",
29
+ );
28
30
  });
29
31
  });
30
32
 
@@ -36,24 +38,29 @@ describe("formatRangeDate", () => {
36
38
  });
37
39
 
38
40
  it("returns placeholder when both start and end are null", () => {
39
- expect(formatRangeDate({ start: null, end: null }, "Pick range")).toBe("Pick range");
41
+ expect(formatRangeDate({ start: null, end: null }, "Pick range")).toBe(
42
+ "Pick range",
43
+ );
40
44
  });
41
45
 
42
46
  it("formats range with only start as '... → start'", () => {
43
- expect(formatRangeDate({ start: null, end: new Date(2025, 11, 31) }, "Pick")).toBe(
44
- "... 31/12/2025",
45
- );
47
+ expect(
48
+ formatRangeDate({ start: null, end: new Date(2025, 11, 31) }, "Pick"),
49
+ ).toBe("... → 31/12/2025");
46
50
  });
47
51
 
48
52
  it("formats range with only end as 'start → ...'", () => {
49
- expect(formatRangeDate({ start: new Date(2025, 0, 1), end: null }, "Pick")).toBe(
50
- "01/01/2025 ...",
51
- );
53
+ expect(
54
+ formatRangeDate({ start: new Date(2025, 0, 1), end: null }, "Pick"),
55
+ ).toBe("01/01/2025 → ...");
52
56
  });
53
57
 
54
58
  it("formats range with both dates", () => {
55
59
  expect(
56
- formatRangeDate({ start: new Date(2025, 0, 1), end: new Date(2025, 11, 31) }, "Pick"),
60
+ formatRangeDate(
61
+ { start: new Date(2025, 0, 1), end: new Date(2025, 11, 31) },
62
+ "Pick",
63
+ ),
57
64
  ).toBe("01/01/2025 → 31/12/2025");
58
65
  });
59
66
  });
@@ -70,12 +77,17 @@ describe("formatMultipleDates", () => {
70
77
  });
71
78
 
72
79
  it("formats single date as dd/MM/yyyy", () => {
73
- expect(formatMultipleDates([new Date(2025, 0, 15)], "Select")).toBe("15/01/2025");
80
+ expect(formatMultipleDates([new Date(2025, 0, 15)], "Select")).toBe(
81
+ "15/01/2025",
82
+ );
74
83
  });
75
84
 
76
85
  it("shows count for multiple dates", () => {
77
86
  expect(
78
- formatMultipleDates([new Date(2025, 0, 1), new Date(2025, 0, 2)], "Select"),
87
+ formatMultipleDates(
88
+ [new Date(2025, 0, 1), new Date(2025, 0, 2)],
89
+ "Select",
90
+ ),
79
91
  ).toBe("2 fechas seleccionadas");
80
92
  });
81
93
  });
@@ -226,17 +238,23 @@ describe("maskDateInput — full date (5-8 digits)", () => {
226
238
  });
227
239
 
228
240
  it("truncates year to 4 digits max", () => {
229
- expect(maskDateInput("120620259", ctx("9", "12/06/2025"))).toBe("12/06/2025");
241
+ expect(maskDateInput("120620259", ctx("9", "12/06/2025"))).toBe(
242
+ "12/06/2025",
243
+ );
230
244
  });
231
245
  });
232
246
 
233
247
  describe("maskDateInput — segment-based (2+ slashes present)", () => {
234
248
  it("uses segment parsing when raw already has two slashes", () => {
235
- expect(maskDateInput("12/06/2025", ctx(null, "12/06/2025"))).toBe("12/06/2025");
249
+ expect(maskDateInput("12/06/2025", ctx(null, "12/06/2025"))).toBe(
250
+ "12/06/2025",
251
+ );
236
252
  });
237
253
 
238
254
  it("handles delete within a segment (two slashes)", () => {
239
- expect(maskDateInput("12/0/2025", ctx("Backspace", "12/06/2025"))).toBe("12/0/2025");
255
+ expect(maskDateInput("12/0/2025", ctx("Backspace", "12/06/2025"))).toBe(
256
+ "12/0/2025",
257
+ );
240
258
  });
241
259
 
242
260
  it("returns empty string when all segment parts are empty", () => {
@@ -11,6 +11,16 @@ export const formatSingleDate = (
11
11
  return format(date, DISPLAY_DATE_FORMAT);
12
12
  };
13
13
 
14
+ /**
15
+ * ISO 8601 calendar date (`yyyy-MM-dd`) used as the form value — locale-agnostic,
16
+ * sortable, and parseable by `new Date()` / backends without a custom format.
17
+ * The display string keeps `DISPLAY_DATE_FORMAT`; this is the value layer.
18
+ */
19
+ export const formatISODate = (date: Date | null | undefined): string => {
20
+ if (!date || !isDate(date)) return "";
21
+ return format(date, "yyyy-MM-dd");
22
+ };
23
+
14
24
  export const formatRangeDate = (
15
25
  range: DateRange | undefined,
16
26
  placeholder: string,
@@ -38,6 +48,29 @@ export const formatDate = (date: Date | null): string => {
38
48
  return format(date, DISPLAY_DATE_FORMAT);
39
49
  };
40
50
 
51
+ /**
52
+ * Emits a native `input` event on a hidden form control so Base UI's Field
53
+ * re-runs validation. Base UI clears the error and re-validates only inside the
54
+ * control's `onChange`; assigning `value` via a React prop never fires a native
55
+ * event, so date changes driven from outside (calendar selection, typing into
56
+ * the visible input) would leave the field stuck in its invalid state.
57
+ *
58
+ * Sets the value through the prototype setter so React's value tracker detects
59
+ * the diff, then dispatches a bubbling `input` event React maps to `onChange`.
60
+ */
61
+ export const emitNativeInputChange = (
62
+ input: HTMLInputElement | null,
63
+ value: string,
64
+ ): void => {
65
+ if (!input) return;
66
+ const setter = Object.getOwnPropertyDescriptor(
67
+ HTMLInputElement.prototype,
68
+ "value",
69
+ )?.set;
70
+ setter?.call(input, value);
71
+ input.dispatchEvent(new Event("input", { bubbles: true }));
72
+ };
73
+
41
74
  export const parseInputToDate = (raw: string): Date | null => {
42
75
  const trimmed = raw.trim();
43
76
  if (!trimmed) return null;
@@ -41,7 +41,9 @@ describe("useDateInputPopover", () => {
41
41
  });
42
42
 
43
43
  it("disabled=true prevents keydown from opening popover", () => {
44
- const { result } = renderHook(() => useDateInputPopover({ disabled: true }));
44
+ const { result } = renderHook(() =>
45
+ useDateInputPopover({ disabled: true }),
46
+ );
45
47
  const event = { key: " ", preventDefault: vi.fn() } as any;
46
48
  act(() => result.current.handleInputKeyDown(event));
47
49
  expect(result.current.open).toBe(false);