@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
@@ -1,111 +1,277 @@
1
- import { Meta } from "@storybook/react-vite";
2
- import { FormEvent } from "react";
3
- import { Button, Input } from "../../components";
4
- import { getFormData } from "../../utils";
5
- import { useAsync } from "../use-async/use-async";
6
- import { useMutation } from "../use-mutation";
7
-
8
- /**
9
- * El hook `useMutation` permite manejar operaciones de mutación asincrónicas, proporcionando estados de carga, error y éxito.
10
- *
11
- * Opciones de configuración:
12
- * - `fn`: La función de mutación que se ejecutará.
13
- * - `onError`: Callback opcional que se llama cuando ocurre un error.
14
- * - `onSuccess`: Callback opcional que se llama cuando la mutación es exitosa.
15
- * - `onFinish`: Callback opcional que se llama cuando la mutación termina, sin importar si tuvo éxito o error. Es útil para hacer un `refetch` (de `useAsync`)
16
- *
17
- * Devuelve un objeto con las siguientes propiedades:
18
- * - `mutate`: Función para ejecutar la mutación.
19
- * - `loading`: Indica si la mutación está en progreso.
20
- * - `error`: Contiene el error si la mutación falla.
21
- * - `data`: Contiene los datos si la mutación es exitosa.
22
- */
23
-
24
- const meta: Meta = {
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { useState } from "react";
3
+ import { Button } from "../../components";
4
+ import { type UseMutationOptions, useMutation } from "./use-mutation";
5
+
6
+ // ─── Demo component ──────────────────────────────────────────────────────────
7
+
8
+ interface DemoVariables {
9
+ value: string;
10
+ }
11
+
12
+ interface DemoData {
13
+ message: string;
14
+ timestamp: number;
15
+ }
16
+
17
+ type MutationDemoProps = UseMutationOptions<DemoVariables, DemoData> & {
18
+ /** Simulated async delay in ms */
19
+ delay?: number;
20
+ /** When true the mutation will reject with an error */
21
+ shouldFail?: boolean;
22
+ };
23
+
24
+ const MutationDemo = ({
25
+ delay = 800,
26
+ shouldFail = false,
27
+ onMutate,
28
+ onSuccess,
29
+ onError,
30
+ onSettled,
31
+ }: MutationDemoProps) => {
32
+ const [log, setLog] = useState<string[]>([]);
33
+
34
+ const addLog = (msg: string) =>
35
+ setLog((prev) => [
36
+ `[${new Date().toISOString().slice(11, 23)}] ${msg}`,
37
+ ...prev,
38
+ ]);
39
+
40
+ const mutation = useMutation<DemoVariables, DemoData>({
41
+ fn: async (vars) => {
42
+ await new Promise((res, rej) =>
43
+ setTimeout(
44
+ () =>
45
+ shouldFail ? rej(new Error("Simulated failure")) : res(undefined),
46
+ delay,
47
+ ),
48
+ );
49
+ return { message: `Created: ${vars.value}`, timestamp: Date.now() };
50
+ },
51
+ onMutate: (vars) => {
52
+ addLog(`onMutate — vars: ${JSON.stringify(vars)}`);
53
+ onMutate?.(vars);
54
+ },
55
+ onSuccess: (data, vars) => {
56
+ addLog(`onSuccess — data: ${data.message}`);
57
+ onSuccess?.(data, vars);
58
+ },
59
+ onError: (err, vars) => {
60
+ addLog(`onError — ${err.message}`);
61
+ onError?.(err, vars);
62
+ },
63
+ onSettled: (data, err, vars) => {
64
+ addLog(
65
+ `onSettled — data: ${data?.message ?? "null"}, error: ${err?.message ?? "null"}`,
66
+ );
67
+ onSettled?.(data, err, vars);
68
+ },
69
+ });
70
+
71
+ return (
72
+ <div className="flex flex-col gap-4 max-w-sm">
73
+ <div className="flex gap-2">
74
+ <Button
75
+ onClick={() => mutation.mutate({ value: "item-1" })}
76
+ disabled={mutation.isPending}
77
+ >
78
+ {mutation.isPending ? "Running…" : "Mutate"}
79
+ </Button>
80
+ <Button
81
+ variant="outline"
82
+ onClick={() => mutation.reset()}
83
+ disabled={mutation.isPending}
84
+ >
85
+ Reset
86
+ </Button>
87
+ </div>
88
+
89
+ <pre className="rounded-md bg-slate-950 p-3 text-xs text-white leading-relaxed">
90
+ {JSON.stringify(
91
+ {
92
+ status: mutation.status,
93
+ data: mutation.data,
94
+ error: mutation.error?.message ?? null,
95
+ },
96
+ null,
97
+ 2,
98
+ )}
99
+ </pre>
100
+
101
+ {log.length > 0 && (
102
+ <div className="rounded-md border bg-muted p-3 text-xs font-mono space-y-0.5">
103
+ {log.map((entry, i) => (
104
+ <div key={i}>{entry}</div>
105
+ ))}
106
+ </div>
107
+ )}
108
+ </div>
109
+ );
110
+ };
111
+
112
+ // ─── Meta ─────────────────────────────────────────────────────────────────────
113
+
114
+ const meta: Meta<typeof MutationDemo> = {
25
115
  title: "hooks/useMutation",
116
+ component: MutationDemo,
26
117
  parameters: {
27
118
  docs: {
28
- source: { type: "code" },
119
+ description: {
120
+ component: `
121
+ \`useMutation\` manages async mutation lifecycle with stable callback identity.
122
+
123
+ **API summary**
124
+
125
+ \`\`\`ts
126
+ const { mutate, mutateAsync, reset, status, data, error, isPending, isSuccess, isError, isIdle } =
127
+ useMutation({ fn, onMutate?, onSuccess?, onError?, onSettled? });
128
+ \`\`\`
129
+
130
+ **Lifecycle order (success):** \`onMutate\` → \`fn\` → \`onSuccess\` → \`onSettled\`
131
+
132
+ **Lifecycle order (error):** \`onMutate\` → \`fn\` throws → \`onError\` → \`onSettled\`
133
+
134
+ **Key guarantees**
135
+ - \`mutate\` is fire-and-forget: returns \`void\`, never throws.
136
+ - \`mutateAsync\` returns a \`Promise\` that re-throws on failure.
137
+ - \`onSettled\` is always called — even if \`onError\` throws.
138
+ - All callbacks read through \`useLatestRef\`, so inline functions never recreate \`mutate\`.
139
+ `.trim(),
140
+ },
29
141
  },
30
142
  },
143
+ argTypes: {
144
+ delay: {
145
+ control: { type: "range", min: 0, max: 3000, step: 100 },
146
+ description:
147
+ "Simulated async delay in ms — demo control only, not a hook option.",
148
+ table: { category: "Demo controls" },
149
+ },
150
+ shouldFail: {
151
+ control: "boolean",
152
+ description:
153
+ "When true the demo's fn rejects — simulates an API error. Demo control only, not a hook option.",
154
+ table: { category: "Demo controls" },
155
+ },
156
+ onMutate: {
157
+ action: "onMutate",
158
+ description:
159
+ "Called synchronously with `variables` before `fn` is awaited. Use for optimistic updates.",
160
+ table: {
161
+ category: "Hook options",
162
+ type: { summary: "(variables: TVariables) => void" },
163
+ },
164
+ },
165
+ onSuccess: {
166
+ action: "onSuccess",
167
+ description:
168
+ "Called with `(data, variables)` after `fn` resolves successfully.",
169
+ table: {
170
+ category: "Hook options",
171
+ type: { summary: "(data: TData, variables: TVariables) => void" },
172
+ },
173
+ },
174
+ onError: {
175
+ action: "onError",
176
+ description: "Called with `(error, variables)` when `fn` rejects.",
177
+ table: {
178
+ category: "Hook options",
179
+ type: { summary: "(error: Error, variables: TVariables) => void" },
180
+ },
181
+ },
182
+ onSettled: {
183
+ action: "onSettled",
184
+ description:
185
+ "Always called after `onSuccess` or `onError`, even if one of them throws.",
186
+ table: {
187
+ category: "Hook options",
188
+ type: {
189
+ summary:
190
+ "(data: TData | null, error: Error | null, variables: TVariables) => void",
191
+ },
192
+ },
193
+ },
194
+ },
195
+ render: (args) => <MutationDemo {...args} />,
31
196
  };
32
197
 
33
198
  export default meta;
199
+ type Story = StoryObj<typeof MutationDemo>;
34
200
 
35
- interface FakeTodo {
36
- userId: number;
37
- id: number;
38
- title: string;
39
- completed: boolean;
40
- }
201
+ // ─── Stories ──────────────────────────────────────────────────────────────────
41
202
 
42
- const fetchTodos = async (): Promise<FakeTodo> => {
43
- const response = fetch("https://jsonplaceholder.typicode.com/todos/1").then(
44
- (response) => response.json(),
45
- );
46
- if (!response) throw new Error("Todo not found");
47
- return response;
203
+ export const Idle: Story = {
204
+ args: {
205
+ delay: 800,
206
+ shouldFail: false,
207
+ },
208
+ parameters: {
209
+ docs: {
210
+ description: {
211
+ story:
212
+ "Default idle state before any mutation is triggered. All booleans are false except `isIdle`.",
213
+ },
214
+ },
215
+ },
48
216
  };
49
217
 
50
- const createTodo = async (title: string): Promise<FakeTodo> => {
51
- if (!title) throw new Error("Title is required");
52
- return await fetch("https://jsonplaceholder.typicode.com/posts", {
53
- method: "POST",
54
- body: JSON.stringify({
55
- title: title,
56
- body: "bar",
57
- userId: 1,
58
- }),
59
- headers: {
60
- "Content-type": "application/json; charset=UTF-8",
61
- },
62
- })
63
- .then((response) => response.json())
64
- .then((json) => json);
218
+ export const Pending: Story = {
219
+ args: {
220
+ delay: 3000,
221
+ shouldFail: false,
222
+ },
223
+ parameters: {
224
+ docs: {
225
+ description: {
226
+ story:
227
+ "Click **Mutate** to see `isPending=true` while the operation runs. The button disables until the promise settles.",
228
+ },
229
+ },
230
+ },
65
231
  };
66
232
 
67
- const Result = ({ loading = false, data = null, error = null }: any) => (
68
- <pre className="rounded-md bg-slate-950 p-4 text-white mt-2">
69
- {JSON.stringify(
70
- {
71
- loading,
72
- error,
73
- data,
233
+ export const Success: Story = {
234
+ args: {
235
+ delay: 600,
236
+ shouldFail: false,
237
+ },
238
+ parameters: {
239
+ docs: {
240
+ description: {
241
+ story:
242
+ "After the mutation resolves, `status` becomes `'success'` and `data` is populated. `error` remains null.",
74
243
  },
75
- undefined,
76
- 2,
77
- )}
78
- </pre>
79
- );
80
-
81
- export const Form = {
82
- render: () => {
83
- const { refetch } = useAsync<FakeTodo>({
84
- fn: fetchTodos,
85
- });
86
-
87
- const { mutate, loading, data, error } = useMutation({
88
- fn: createTodo,
89
- onFinish: () => refetch(),
90
- });
91
-
92
- const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
93
- event.preventDefault();
94
- const data = getFormData(event);
95
- if ("title" in data) mutate(data.title);
96
- };
97
-
98
- return (
99
- <div>
100
- <form className="space-y-2" onSubmit={handleSubmit}>
101
- <Input placeholder="title" name="title" autoComplete="0" />
102
- <Button type="submit" loading={loading}>
103
- Enviar
104
- </Button>
105
- </form>
106
-
107
- <Result loading={loading} data={data} error={error?.message} />
108
- </div>
109
- );
244
+ },
245
+ },
246
+ };
247
+
248
+ export const ErrorState: Story = {
249
+ name: "Error",
250
+ args: {
251
+ delay: 600,
252
+ shouldFail: true,
253
+ },
254
+ parameters: {
255
+ docs: {
256
+ description: {
257
+ story:
258
+ "Toggle **shouldFail** in controls to simulate a failing API call. `status` becomes `'error'`, `error` is set, and `data` stays null. `mutate` swallows the rejection silently — use `mutateAsync` if you need to catch it.",
259
+ },
260
+ },
261
+ },
262
+ };
263
+
264
+ export const ResetAfterSuccess: Story = {
265
+ args: {
266
+ delay: 600,
267
+ shouldFail: false,
268
+ },
269
+ parameters: {
270
+ docs: {
271
+ description: {
272
+ story:
273
+ "After a successful mutation, click **Reset** to return to `idle` and clear `data`. `reset()` does not abort in-flight mutations — it only clears state.",
274
+ },
275
+ },
110
276
  },
111
277
  };