@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,148 +1,272 @@
1
- import { Meta, StoryObj } from "@storybook/react-vite";
2
- import { ChevronLeft, ChevronRight } from "lucide-react";
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
3
2
  import { useState } from "react";
4
- import { Button, createToastManager, Input } from "../../components";
5
- import { LibraryProvider } from "../../providers/library-provider";
6
- import { useDebouncedValue } from "../use-debounce-value/use-debounced-value";
7
- import { useAsync } from "../use-async";
3
+ import { Button } from "../../components";
4
+ import { useAsync } from "./use-async";
8
5
 
9
- const toastManager = createToastManager();
6
+ // ─── Types ─────────────────────────────────────────────────────────────────────
10
7
 
11
- const meta: Meta = {
8
+ interface DemoData {
9
+ message: string;
10
+ timestamp: string;
11
+ }
12
+
13
+ // ─── Demo component ────────────────────────────────────────────────────────────
14
+
15
+ type AsyncDemoProps = {
16
+ /** Simulated async delay in ms */
17
+ delay?: number;
18
+ /** When true the demo's fn rejects with a simulated error */
19
+ shouldFail?: boolean;
20
+ enabled?: boolean;
21
+ keepPreviousData?: boolean;
22
+ onSuccess?: (data: DemoData) => void;
23
+ onError?: (error: Error) => void;
24
+ onSettled?: (data: DemoData | null, error: Error | null) => void;
25
+ };
26
+
27
+ const AsyncDemo = ({
28
+ delay = 800,
29
+ shouldFail = false,
30
+ enabled = true,
31
+ keepPreviousData = false,
32
+ onSuccess,
33
+ onError,
34
+ onSettled,
35
+ }: AsyncDemoProps) => {
36
+ const [log, setLog] = useState<string[]>([]);
37
+
38
+ const addLog = (msg: string) =>
39
+ setLog((prev) => [
40
+ `[${new Date().toISOString().slice(11, 23)}] ${msg}`,
41
+ ...prev,
42
+ ]);
43
+
44
+ const { status, data, error, loading, refetch } = useAsync<DemoData>({
45
+ fn: async (signal) => {
46
+ await new Promise<void>((res, rej) => {
47
+ const timer = setTimeout(() => {
48
+ if (shouldFail) rej(new Error("Simulated failure"));
49
+ else res();
50
+ }, delay);
51
+ signal.addEventListener("abort", () => clearTimeout(timer));
52
+ });
53
+ return {
54
+ message: "Data loaded successfully",
55
+ timestamp: new Date().toLocaleTimeString(),
56
+ };
57
+ },
58
+ enabled,
59
+ keepPreviousData,
60
+ onSuccess: (d) => {
61
+ addLog(`onSuccess — ${d.message} at ${d.timestamp}`);
62
+ onSuccess?.(d);
63
+ },
64
+ onError: (e) => {
65
+ addLog(`onError — ${e.message}`);
66
+ onError?.(e);
67
+ },
68
+ onSettled: (d, e) => {
69
+ addLog(
70
+ `onSettled — data: ${d ? d.timestamp : "null"}, error: ${e?.message ?? "null"}`,
71
+ );
72
+ onSettled?.(d, e);
73
+ },
74
+ });
75
+
76
+ return (
77
+ <div className="flex flex-col gap-4 max-w-sm">
78
+ <Button variant="outline" onClick={refetch} disabled={loading}>
79
+ {loading ? "Loading…" : "Refetch"}
80
+ </Button>
81
+
82
+ <pre className="rounded-md bg-slate-950 p-3 text-xs text-white leading-relaxed">
83
+ {JSON.stringify(
84
+ {
85
+ status,
86
+ loading,
87
+ data,
88
+ error: error?.message ?? null,
89
+ },
90
+ null,
91
+ 2,
92
+ )}
93
+ </pre>
94
+
95
+ {log.length > 0 && (
96
+ <div className="rounded-md border bg-muted p-3 text-xs font-mono space-y-0.5">
97
+ {log.map((entry, i) => (
98
+ // biome-ignore lint/suspicious/noArrayIndexKey: log is append-only
99
+ <div key={i}>{entry}</div>
100
+ ))}
101
+ </div>
102
+ )}
103
+ </div>
104
+ );
105
+ };
106
+
107
+ // ─── Meta ─────────────────────────────────────────────────────────────────────
108
+
109
+ const meta: Meta<typeof AsyncDemo> = {
12
110
  title: "hooks/useAsync",
111
+ component: AsyncDemo,
112
+ parameters: {
113
+ docs: {
114
+ description: {
115
+ component: `
116
+ \`useAsync\` runs an async function automatically when its \`deps\` change.
117
+
118
+ **API summary**
119
+
120
+ \`\`\`ts
121
+ const { data, error, status, loading, isIdle, isSuccess, isError, refetch } =
122
+ useAsync({ fn, deps?, enabled?, keepPreviousData?, onSuccess?, onError?, onSettled? });
123
+ \`\`\`
124
+
125
+ **Key behaviors**
126
+ - \`fn(signal)\` receives an \`AbortSignal\` — pass it to \`fetch\` to cancel in-flight requests on cleanup.
127
+ - Runs on mount (when \`enabled: true\`) and re-runs whenever \`deps\` change.
128
+ - Race-safe: if \`deps\` change before \`fn\` resolves, the stale response is silently discarded.
129
+ - \`enabled: false\` keeps the hook in \`'idle'\` state — no request fires until \`enabled\` becomes \`true\`.
130
+ - \`keepPreviousData: true\` retains the last resolved \`data\` while a new request is in-flight.
131
+ - \`refetch()\` manually re-triggers without changing deps. Stable reference across renders.
132
+ - All callbacks read through \`useLatestRef\` — inline functions never cause re-subscriptions.
133
+ `.trim(),
134
+ },
135
+ },
136
+ },
137
+ argTypes: {
138
+ delay: {
139
+ control: { type: "range", min: 0, max: 3000, step: 100 },
140
+ description:
141
+ "Simulated async delay in ms. Demo control only — not a hook option.",
142
+ table: { category: "Demo controls" },
143
+ },
144
+ shouldFail: {
145
+ control: "boolean",
146
+ description:
147
+ "When true the demo's fn rejects. Toggle then click Refetch to see the error state.",
148
+ table: { category: "Demo controls" },
149
+ },
150
+ enabled: {
151
+ control: "boolean",
152
+ description:
153
+ "When false the hook stays idle and no request fires. Set to true to trigger the first run.",
154
+ table: {
155
+ category: "Hook options",
156
+ type: { summary: "boolean" },
157
+ defaultValue: { summary: "true" },
158
+ },
159
+ },
160
+ keepPreviousData: {
161
+ control: "boolean",
162
+ description:
163
+ "When true, `data` is preserved while a new request is in-flight instead of being reset to null.",
164
+ table: {
165
+ category: "Hook options",
166
+ type: { summary: "boolean" },
167
+ defaultValue: { summary: "false" },
168
+ },
169
+ },
170
+ onSuccess: {
171
+ action: "onSuccess",
172
+ description: "Called with `data` after `fn` resolves successfully.",
173
+ table: {
174
+ category: "Hook options",
175
+ type: { summary: "(data: T) => void" },
176
+ },
177
+ },
178
+ onError: {
179
+ action: "onError",
180
+ description:
181
+ "Called with the thrown `Error` when `fn` rejects. Abort errors are swallowed and never reach this callback.",
182
+ table: {
183
+ category: "Hook options",
184
+ type: { summary: "(error: Error) => void" },
185
+ },
186
+ },
187
+ onSettled: {
188
+ action: "onSettled",
189
+ description:
190
+ "Always called after `fn` settles — regardless of success or failure.",
191
+ table: {
192
+ category: "Hook options",
193
+ type: { summary: "(data: T | null, error: Error | null) => void" },
194
+ },
195
+ },
196
+ },
197
+ render: (args) => <AsyncDemo {...args} />,
13
198
  };
14
199
 
15
200
  export default meta;
16
- type Story = StoryObj;
201
+ type Story = StoryObj<typeof AsyncDemo>;
17
202
 
18
- const fetchPokemon = async (name: string) => {
19
- if (!name) throw new Error("Name is required");
20
- const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`);
21
- if (!response.ok) throw new Error("Pokemon not found");
22
- return response.json();
23
- };
203
+ // ─── Stories ──────────────────────────────────────────────────────────────────
24
204
 
25
- export const Basic = {
26
- render: () => {
27
- const [pokemonName, setPokemonName] = useState<string>("pikachu");
28
- const debouncedName = useDebouncedValue(pokemonName, 600);
29
- const { loading, error, data } = useAsync({
30
- fn: () => fetchPokemon(debouncedName),
31
- dependencies: [debouncedName],
32
- });
33
-
34
- return (
35
- <div className="space-y-4">
36
- <Input
37
- type="text"
38
- value={pokemonName}
39
- onValueChange={setPokemonName}
40
- placeholder="Enter Pokémon name"
41
- />
42
- {loading && <p>Loading...</p>}
43
- {error && <p>Error: {error.message}</p>}
44
- {data && (
45
- <div className="border w-fit mx-auto rounded-lg bg-muted">
46
- <h3 className="capitalize text-center font-semibold">
47
- {data.name}
48
- </h3>
49
- <img src={data.sprites.front_default} alt={data.name} />
50
- </div>
51
- )}
52
- </div>
53
- );
205
+ export const Default: Story = {
206
+ args: {
207
+ delay: 800,
208
+ shouldFail: false,
209
+ enabled: true,
210
+ keepPreviousData: false,
211
+ },
212
+ parameters: {
213
+ docs: {
214
+ description: {
215
+ story:
216
+ "Runs on mount, transitions through `pending` → `success`, then populates `data`. Click **Refetch** to re-trigger manually. Toggle **shouldFail** in controls then refetch to see the error path.",
217
+ },
218
+ },
54
219
  },
55
220
  };
56
221
 
57
- const fetchPokemonById = async (id: number) => {
58
- if (id === undefined) throw new Error("id is required");
59
- const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
60
- if (!response.ok) throw new Error("Pokemon not found");
61
- return response.json();
222
+ export const ErrorState: Story = {
223
+ name: "Error",
224
+ args: {
225
+ delay: 600,
226
+ shouldFail: true,
227
+ enabled: true,
228
+ keepPreviousData: false,
229
+ },
230
+ parameters: {
231
+ docs: {
232
+ description: {
233
+ story:
234
+ "`fn` rejects immediately — `status` becomes `'error'`, `error` is populated, and `data` stays null. `onError` and `onSettled` both fire. Toggle **shouldFail** off then click Refetch to recover.",
235
+ },
236
+ },
237
+ },
62
238
  };
63
239
 
64
- /**
65
- * ```tsx
66
- * interface UseAsyncOptions<T> {
67
- * onError?: (error: Error) => void;
68
- * onSuccess?: (data: T) => void;
69
- * onFinish?: () => void;
70
- * }
71
- * ```
72
- */
73
-
74
- export const Advanced: Story = {
75
- decorators: [
76
- (Story) => (
77
- <LibraryProvider>
78
- <Story />
79
- </LibraryProvider>
80
- ),
81
- ],
82
- render: () => {
83
- const [pokemonId, setPokemonId] = useState<number>(1);
84
- const [log, setLog] = useState<string[]>([]);
85
-
86
- const handleSuccess = (data: any) => {
87
- setLog((prevLog) => [...prevLog, `✅ Fetched Pokémon: ${data.name}`]);
88
- };
89
-
90
- const handleError = (error: Error) => {
91
- toastManager.add({ variant: "error", description: error.message });
92
- setLog((prevLog) => [...prevLog, `❌ Error: ${error.message}`]);
93
- };
94
-
95
- const handleSettled = () => {
96
- setLog((prevLog) => [...prevLog, `🏁 Request settled`]);
97
- };
98
-
99
- const { loading, error, data } = useAsync({
100
- fn: () => fetchPokemonById(pokemonId),
101
- dependencies: [pokemonId],
102
- onSuccess: handleSuccess,
103
- onError: handleError,
104
- onFinish: handleSettled,
105
- });
106
-
107
- return (
108
- <div className="p-4">
109
- <div className="flex justify-center items-center gap-x-4">
110
- <Button
111
- size="icon"
112
- variant="ghost"
113
- onClick={() => setPokemonId(pokemonId - 1)}
114
- >
115
- <ChevronLeft />
116
- </Button>
117
- <div className="font-bold">{pokemonId}</div>
118
- <Button
119
- size="icon"
120
- variant="ghost"
121
- onClick={() => setPokemonId(pokemonId + 1)}
122
- >
123
- <ChevronRight />
124
- </Button>
125
- </div>
240
+ export const WithEnabled: Story = {
241
+ args: {
242
+ delay: 800,
243
+ shouldFail: false,
244
+ enabled: false,
245
+ keepPreviousData: false,
246
+ },
247
+ parameters: {
248
+ docs: {
249
+ description: {
250
+ story:
251
+ "Starts with `enabled: false` — the hook is in `'idle'` state and no request fires. Flip **enabled** to true in controls to trigger the first run.",
252
+ },
253
+ },
254
+ },
255
+ };
126
256
 
127
- {loading && <p>Loading...</p>}
128
- {error && <p>Error: {error.message}</p>}
129
- {data && (
130
- <div className="border w-fit mx-auto rounded-lg bg-muted">
131
- <h3 className="capitalize text-center font-semibold">
132
- {data.name}
133
- </h3>
134
- <img src={data.sprites.front_default} alt={data.name} />
135
- </div>
136
- )}
137
- <div className="mt-4">
138
- <h4 className="font-semibold">Log:</h4>
139
- <ul className="list-disc">
140
- {log.map((entry, index) => (
141
- <li key={index}>{entry}</li>
142
- ))}
143
- </ul>
144
- </div>
145
- </div>
146
- );
257
+ export const WithKeepPreviousData: Story = {
258
+ args: {
259
+ delay: 1500,
260
+ shouldFail: false,
261
+ enabled: true,
262
+ keepPreviousData: true,
263
+ },
264
+ parameters: {
265
+ docs: {
266
+ description: {
267
+ story:
268
+ "With `keepPreviousData: true`, the previous `data` stays visible while the next request is in-flight (`status: 'pending'`). Click **Refetch** to see stale data persist during the 1.5 s delay.",
269
+ },
270
+ },
147
271
  },
148
272
  };