@boxcustodia/library 2.0.0-alpha.10 → 2.0.0-alpha.12

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 (352) hide show
  1. package/dist/index.cjs.js +70 -70
  2. package/dist/index.css +2 -0
  3. package/dist/index.d.ts +420 -272
  4. package/dist/index.es.js +34448 -27816
  5. package/dist/theme.css +1 -1
  6. package/package.json +11 -6
  7. package/src/__doc__/Changelog.mdx +6 -0
  8. package/src/__doc__/Components.mdx +73 -0
  9. package/src/__doc__/Examples.tsx +69 -0
  10. package/src/__doc__/Icons.mdx +41 -0
  11. package/src/__doc__/Intro.mdx +138 -0
  12. package/src/__doc__/MCP.mdx +71 -0
  13. package/src/__doc__/Migration.mdx +475 -0
  14. package/src/__doc__/Theme.mdx +132 -0
  15. package/src/__doc__/Types.mdx +252 -0
  16. package/src/components/alert/alert.stories.tsx +142 -0
  17. package/src/components/alert/alert.tsx +109 -0
  18. package/src/components/alert/index.ts +7 -0
  19. package/src/components/alert-dialog/alert-dialog.stories.tsx +173 -0
  20. package/src/components/alert-dialog/alert-dialog.test.tsx +49 -0
  21. package/src/components/alert-dialog/alert-dialog.tsx +265 -0
  22. package/src/components/alert-dialog/index.ts +1 -0
  23. package/src/components/auto-complete/auto-complete-primitives.tsx +155 -0
  24. package/src/components/auto-complete/auto-complete.stories.tsx +241 -0
  25. package/src/components/auto-complete/auto-complete.tsx +82 -0
  26. package/src/components/auto-complete/index.ts +2 -0
  27. package/src/components/avatar/avatar.stories.tsx +84 -0
  28. package/src/components/avatar/avatar.test.tsx +61 -0
  29. package/src/components/avatar/avatar.tsx +104 -0
  30. package/src/components/avatar/index.ts +1 -0
  31. package/src/components/background-image/background-image.stories.tsx +21 -0
  32. package/src/components/background-image/background-image.test.tsx +29 -0
  33. package/src/components/background-image/background-image.tsx +23 -0
  34. package/src/components/background-image/index.ts +1 -0
  35. package/src/components/button/button.stories.tsx +396 -0
  36. package/src/components/button/button.test.tsx +58 -0
  37. package/src/components/button/button.tsx +31 -0
  38. package/src/components/button/button.variants.ts +44 -0
  39. package/src/components/button/components/base-button.tsx +86 -0
  40. package/src/components/button/components/loader-overlay.tsx +21 -0
  41. package/src/components/button/components/loading-icon.tsx +47 -0
  42. package/src/components/button/index.ts +3 -0
  43. package/src/components/calendar/calendar.model.ts +86 -0
  44. package/src/components/calendar/calendar.stories.tsx +155 -0
  45. package/src/components/calendar/calendar.test.tsx +12 -0
  46. package/src/components/calendar/calendar.tsx +185 -0
  47. package/src/components/calendar/components/calendar-navigation.tsx +141 -0
  48. package/src/components/calendar/components/day.tsx +61 -0
  49. package/src/components/calendar/components/decade-view.tsx +45 -0
  50. package/src/components/calendar/components/index.ts +6 -0
  51. package/src/components/calendar/components/month-view.tsx +58 -0
  52. package/src/components/calendar/components/week-days.tsx +27 -0
  53. package/src/components/calendar/components/year-view.tsx +29 -0
  54. package/src/components/calendar/hooks/index.ts +4 -0
  55. package/src/components/calendar/hooks/use-calendar-navigation.ts +79 -0
  56. package/src/components/calendar/hooks/use-calendar.ts +90 -0
  57. package/src/components/calendar/hooks/use-multiple-calendar.ts +34 -0
  58. package/src/components/calendar/hooks/use-range-calendar.ts +91 -0
  59. package/src/components/calendar/hooks/use-single-calendar.ts +18 -0
  60. package/src/components/calendar/index.ts +1 -0
  61. package/src/components/calendar/utils/typeguards.ts +7 -0
  62. package/src/components/card/card.stories.tsx +116 -0
  63. package/src/components/card/card.tsx +74 -0
  64. package/src/components/card/index.ts +1 -0
  65. package/src/components/center/center.stories.tsx +81 -0
  66. package/src/components/center/center.tsx +24 -0
  67. package/src/components/center/index.ts +1 -0
  68. package/src/components/checkbox/checkbox.stories.tsx +307 -0
  69. package/src/components/checkbox/checkbox.tsx +273 -0
  70. package/src/components/checkbox/index.ts +1 -0
  71. package/src/components/checkbox-group/checkbox-group.stories.tsx +104 -0
  72. package/src/components/checkbox-group/checkbox-group.tsx +16 -0
  73. package/src/components/checkbox-group/index.ts +1 -0
  74. package/src/components/combobox/combobox.stories.tsx +339 -0
  75. package/src/components/combobox/combobox.tsx +892 -0
  76. package/src/components/combobox/index.ts +1 -0
  77. package/src/components/date-picker/date-input.stories.tsx +158 -0
  78. package/src/components/date-picker/date-input.tsx +163 -0
  79. package/src/components/date-picker/date-picker.model.ts +90 -0
  80. package/src/components/date-picker/date-picker.stories.tsx +200 -0
  81. package/src/components/date-picker/date-picker.test.tsx +23 -0
  82. package/src/components/date-picker/date-picker.tsx +298 -0
  83. package/src/components/date-picker/date-picker.utils.ts +260 -0
  84. package/src/components/date-picker/index.ts +3 -0
  85. package/src/components/date-picker/use-date-input-popover.ts +48 -0
  86. package/src/components/date-picker/use-date-input.ts +125 -0
  87. package/src/components/dialog/dialog.stories.tsx +171 -0
  88. package/src/components/dialog/dialog.test.tsx +68 -0
  89. package/src/components/dialog/dialog.tsx +277 -0
  90. package/src/components/dialog/index.ts +1 -0
  91. package/src/components/divider/divider.stories.tsx +70 -0
  92. package/src/components/divider/divider.test.tsx +22 -0
  93. package/src/components/divider/divider.tsx +23 -0
  94. package/src/components/divider/index.ts +1 -0
  95. package/src/components/dropzone/dropzone.stories.tsx +210 -0
  96. package/src/components/dropzone/dropzone.tsx +154 -0
  97. package/src/components/dropzone/file-types.ts +64 -0
  98. package/src/components/dropzone/index.ts +3 -0
  99. package/src/components/dropzone/upload-primitives.tsx +310 -0
  100. package/src/components/dropzone/use-dropzone.ts +122 -0
  101. package/src/components/empty-state/empty-state.stories.tsx +56 -0
  102. package/src/components/empty-state/empty-state.tsx +39 -0
  103. package/src/components/empty-state/index.ts +1 -0
  104. package/src/components/field/field.stories.tsx +223 -0
  105. package/src/components/field/field.tsx +229 -0
  106. package/src/components/field/index.ts +1 -0
  107. package/src/components/form/form.stories.tsx +594 -0
  108. package/src/components/form/form.tsx +30 -0
  109. package/src/components/form/index.ts +1 -0
  110. package/src/components/heading/heading.stories.tsx +74 -0
  111. package/src/components/heading/heading.tsx +28 -0
  112. package/src/components/heading/heading.variants.ts +27 -0
  113. package/src/components/heading/index.ts +1 -0
  114. package/src/components/index.ts +46 -0
  115. package/src/components/input/index.ts +1 -0
  116. package/src/components/input/input.stories.tsx +104 -0
  117. package/src/components/input/input.tsx +75 -0
  118. package/src/components/kbd/index.ts +1 -0
  119. package/src/components/kbd/kbd.stories.tsx +40 -0
  120. package/src/components/kbd/kbd.tsx +31 -0
  121. package/src/components/kbd/kbd.variants.ts +26 -0
  122. package/src/components/label/index.ts +1 -0
  123. package/src/components/label/label.stories.tsx +68 -0
  124. package/src/components/label/label.test.tsx +61 -0
  125. package/src/components/label/label.tsx +62 -0
  126. package/src/components/loader/index.ts +1 -0
  127. package/src/components/loader/loader.stories.tsx +60 -0
  128. package/src/components/loader/loader.test.tsx +26 -0
  129. package/src/components/loader/loader.tsx +60 -0
  130. package/src/components/menu/index.ts +2 -0
  131. package/src/components/menu/menu-primitives.tsx +248 -0
  132. package/src/components/menu/menu.stories.tsx +203 -0
  133. package/src/components/menu/menu.tsx +100 -0
  134. package/src/components/menu/util/render-menu-item.tsx +54 -0
  135. package/src/components/multi-select/hooks/use-multi-select.ts +66 -0
  136. package/src/components/multi-select/index.ts +1 -0
  137. package/src/components/multi-select/multi-select.stories.tsx +294 -0
  138. package/src/components/multi-select/multi-select.tsx +300 -0
  139. package/src/components/multi-select/multi-select.variants.ts +22 -0
  140. package/src/components/number-input/index.ts +1 -0
  141. package/src/components/number-input/number-input.stories.tsx +209 -0
  142. package/src/components/number-input/number-input.test.tsx +87 -0
  143. package/src/components/number-input/number-input.tsx +230 -0
  144. package/src/components/pagination/components/pagination-option.tsx +27 -0
  145. package/src/components/pagination/index.ts +1 -0
  146. package/src/components/pagination/pagination.stories.tsx +80 -0
  147. package/src/components/pagination/pagination.test.tsx +76 -0
  148. package/src/components/pagination/pagination.tsx +102 -0
  149. package/src/components/password/index.ts +1 -0
  150. package/src/components/password/password.stories.tsx +104 -0
  151. package/src/components/password/password.tsx +71 -0
  152. package/src/components/popover/index.ts +1 -0
  153. package/src/components/popover/popover.stories.tsx +213 -0
  154. package/src/components/popover/popover.tsx +203 -0
  155. package/src/components/progress/index.ts +1 -0
  156. package/src/components/progress/progress.stories.tsx +124 -0
  157. package/src/components/progress/progress.test.tsx +25 -0
  158. package/src/components/progress/progress.tsx +124 -0
  159. package/src/components/scroll-area/index.ts +1 -0
  160. package/src/components/scroll-area/scroll-area.stories.tsx +166 -0
  161. package/src/components/scroll-area/scroll-area.tsx +64 -0
  162. package/src/components/select/index.ts +1 -0
  163. package/src/components/select/select.stories.tsx +253 -0
  164. package/src/components/select/select.tsx +430 -0
  165. package/src/components/show/index.ts +1 -0
  166. package/src/components/show/show.stories.tsx +197 -0
  167. package/src/components/show/show.test.tsx +41 -0
  168. package/src/components/show/show.tsx +16 -0
  169. package/src/components/skeleton/index.ts +1 -0
  170. package/src/components/skeleton/skeleton.stories.tsx +36 -0
  171. package/src/components/skeleton/skeleton.test.tsx +14 -0
  172. package/src/components/skeleton/skeleton.tsx +15 -0
  173. package/src/components/stack/index.ts +1 -0
  174. package/src/components/stack/stack.stories.tsx +194 -0
  175. package/src/components/stack/stack.tsx +52 -0
  176. package/src/components/stepper/Stepper.tsx +190 -0
  177. package/src/components/stepper/context/stepper-context.tsx +11 -0
  178. package/src/components/stepper/index.ts +1 -0
  179. package/src/components/stepper/stepper.stories.tsx +130 -0
  180. package/src/components/stepper/stepper.test.tsx +91 -0
  181. package/src/components/switch/index.ts +1 -0
  182. package/src/components/switch/switch.stories.tsx +122 -0
  183. package/src/components/switch/switch.test.tsx +30 -0
  184. package/src/components/switch/switch.tsx +86 -0
  185. package/src/components/table/index.ts +3 -0
  186. package/src/components/table/table-primitives.tsx +122 -0
  187. package/src/components/table/table.model.ts +20 -0
  188. package/src/components/table/table.stories.tsx +169 -0
  189. package/src/components/table/table.test.tsx +91 -0
  190. package/src/components/table/table.tsx +109 -0
  191. package/src/components/table-pagination/index.ts +2 -0
  192. package/src/components/table-pagination/table-pagination.model.ts +2 -0
  193. package/src/components/table-pagination/table-pagination.stories.tsx +23 -0
  194. package/src/components/table-pagination/table-pagination.test.tsx +32 -0
  195. package/src/components/table-pagination/table-pagination.tsx +108 -0
  196. package/src/components/tabs/context/tabs-context.tsx +14 -0
  197. package/src/components/tabs/index.ts +1 -0
  198. package/src/components/tabs/tabs.stories.tsx +182 -0
  199. package/src/components/tabs/tabs.test.tsx +61 -0
  200. package/src/components/tabs/tabs.tsx +175 -0
  201. package/src/components/tag/index.ts +2 -0
  202. package/src/components/tag/tag.stories.tsx +170 -0
  203. package/src/components/tag/tag.test.tsx +18 -0
  204. package/src/components/tag/tag.tsx +99 -0
  205. package/src/components/tag/tag.variants.ts +31 -0
  206. package/src/components/textarea/index.ts +1 -0
  207. package/src/components/textarea/textarea.stories.tsx +73 -0
  208. package/src/components/textarea/textarea.tsx +105 -0
  209. package/src/components/timeline/index.ts +1 -0
  210. package/src/components/timeline/timeline-status.ts +5 -0
  211. package/src/components/timeline/timeline.stories.tsx +84 -0
  212. package/src/components/timeline/timeline.tsx +147 -0
  213. package/src/components/toast/index.ts +1 -0
  214. package/src/components/toast/toast.stories.tsx +392 -0
  215. package/src/components/toast/toast.test.tsx +50 -0
  216. package/src/components/toast/toast.tsx +411 -0
  217. package/src/components/tooltip/index.ts +1 -0
  218. package/src/components/tooltip/tooltip.stories.tsx +226 -0
  219. package/src/components/tooltip/tooltip.test.tsx +46 -0
  220. package/src/components/tooltip/tooltip.tsx +171 -0
  221. package/src/components/tree/hooks/use-controllable-tree-state.ts +80 -0
  222. package/src/components/tree/index.ts +2 -0
  223. package/src/components/tree/tree-primitives.tsx +126 -0
  224. package/src/components/tree/tree.stories.tsx +468 -0
  225. package/src/components/tree/tree.tsx +42 -0
  226. package/src/hooks/index.ts +26 -0
  227. package/src/hooks/useArray/__doc__/useArray.stories.tsx +100 -0
  228. package/src/hooks/useArray/__test__/useArray.test.tsx +88 -0
  229. package/src/hooks/useArray/index.ts +1 -0
  230. package/src/hooks/useArray/useArray.ts +76 -0
  231. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +149 -0
  232. package/src/hooks/useAsync/__test__/useAsync.test.tsx +68 -0
  233. package/src/hooks/useAsync/index.ts +1 -0
  234. package/src/hooks/useAsync/useAsync.ts +58 -0
  235. package/src/hooks/useClickOutside/__doc__/useClickOutside.stories.tsx +40 -0
  236. package/src/hooks/useClickOutside/__test__/useClickOutside.test.tsx +33 -0
  237. package/src/hooks/useClickOutside/index.ts +1 -0
  238. package/src/hooks/useClickOutside/useClickOutside.ts +26 -0
  239. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +45 -0
  240. package/src/hooks/useClipboard/__test__/useClipboard.test.tsx +19 -0
  241. package/src/hooks/useClipboard/index.ts +1 -0
  242. package/src/hooks/useClipboard/useClipboard.tsx +28 -0
  243. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +84 -0
  244. package/src/hooks/useDebounceCallback/index.ts +1 -0
  245. package/src/hooks/useDebounceCallback/useDebouncedCallback.ts +23 -0
  246. package/src/hooks/useDebounceValue/__doc__/useDebouncedValue.stories.tsx +75 -0
  247. package/src/hooks/useDebounceValue/index.ts +1 -0
  248. package/src/hooks/useDebounceValue/useDebouncedValue.ts +17 -0
  249. package/src/hooks/useDisclosure/__doc__/useDisclosure.stories.tsx +39 -0
  250. package/src/hooks/useDisclosure/__test__/useDisclosure.test.ts +43 -0
  251. package/src/hooks/useDisclosure/index.ts +1 -0
  252. package/src/hooks/useDisclosure/useDisclosure.ts +37 -0
  253. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +26 -0
  254. package/src/hooks/useDocumentTitle/index.ts +1 -0
  255. package/src/hooks/useDocumentTitle/useDocumentTitle.tsx +11 -0
  256. package/src/hooks/useEventListener/__doc__/useEventListener.stories.tsx +28 -0
  257. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +26 -0
  258. package/src/hooks/useEventListener/index.ts +1 -0
  259. package/src/hooks/useEventListener/useEventListener.ts +25 -0
  260. package/src/hooks/useFocusTrap/__doc__/useFocusTrap.stories.tsx +37 -0
  261. package/src/hooks/useFocusTrap/index.ts +1 -0
  262. package/src/hooks/useFocusTrap/scopeTab.ts +38 -0
  263. package/src/hooks/useFocusTrap/tabbable.ts +70 -0
  264. package/src/hooks/useFocusTrap/useFocusTrap.ts +78 -0
  265. package/src/hooks/useHotkey/__docs__/useHotkey.stories.tsx +116 -0
  266. package/src/hooks/useHotkey/__test__/useHotkey.test.tsx +105 -0
  267. package/src/hooks/useHotkey/__utils__/create-hotkey-listener.ts +25 -0
  268. package/src/hooks/useHotkey/__utils__/index.ts +3 -0
  269. package/src/hooks/useHotkey/__utils__/is-input-field.ts +14 -0
  270. package/src/hooks/useHotkey/__utils__/match-key-modifiers.ts +25 -0
  271. package/src/hooks/useHotkey/index.ts +1 -0
  272. package/src/hooks/useHotkey/useHotkey.ts +34 -0
  273. package/src/hooks/useHover/__doc__/useHover.stories.tsx +41 -0
  274. package/src/hooks/useHover/__test__/useHover.test.tsx +45 -0
  275. package/src/hooks/useHover/index.ts +1 -0
  276. package/src/hooks/useHover/useHover.tsx +40 -0
  277. package/src/hooks/useIsVisible/__doc__/useIsVisible.stories.tsx +60 -0
  278. package/src/hooks/useIsVisible/index.ts +1 -0
  279. package/src/hooks/useIsVisible/useIsVisible.tsx +50 -0
  280. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +86 -0
  281. package/src/hooks/useLocalStorage/__test__/useLocalStorage.test.ts +85 -0
  282. package/src/hooks/useLocalStorage/index.ts +1 -0
  283. package/src/hooks/useLocalStorage/useLocalStorage.ts +57 -0
  284. package/src/hooks/useMediaQuery/__doc__/useMediaQuery.stories.tsx +39 -0
  285. package/src/hooks/useMediaQuery/index.ts +1 -0
  286. package/src/hooks/useMediaQuery/useMediaQuery.ts +22 -0
  287. package/src/hooks/useMemoizedFn/index.ts +1 -0
  288. package/src/hooks/useMemoizedFn/useMemoizedFn.ts +32 -0
  289. package/src/hooks/useMutation/__doc__/useMutation.stories.tsx +111 -0
  290. package/src/hooks/useMutation/__test__/useMutation.test.tsx +83 -0
  291. package/src/hooks/useMutation/index.ts +1 -0
  292. package/src/hooks/useMutation/useMutation.tsx +60 -0
  293. package/src/hooks/useObject/__doc__/useObject.stories.tsx +119 -0
  294. package/src/hooks/useObject/__test__/useObject.test.tsx +87 -0
  295. package/src/hooks/useObject/index.ts +1 -0
  296. package/src/hooks/useObject/useObject.tsx +48 -0
  297. package/src/hooks/usePagination/__doc__/usePagination.stories.tsx +72 -0
  298. package/src/hooks/usePagination/__test__/usePagination.test.tsx +98 -0
  299. package/src/hooks/usePagination/index.ts +2 -0
  300. package/src/hooks/usePagination/usePagination.tsx +74 -0
  301. package/src/hooks/usePortal/__doc__/usePortal.stories.tsx +19 -0
  302. package/src/hooks/usePortal/__test__/usePortal.test.tsx +20 -0
  303. package/src/hooks/usePortal/index.ts +1 -0
  304. package/src/hooks/usePortal/usePortal.ts +40 -0
  305. package/src/hooks/usePreventCloseWindow/__doc__/usePreventCloseWindow.stories.tsx +32 -0
  306. package/src/hooks/usePreventCloseWindow/index.ts +1 -0
  307. package/src/hooks/usePreventCloseWindow/usePreventCloseWindow.ts +33 -0
  308. package/src/hooks/useRangePagination/__test__/useRangePagination.test.tsx +63 -0
  309. package/src/hooks/useRangePagination/index.ts +2 -0
  310. package/src/hooks/useRangePagination/useRangePagination.tsx +72 -0
  311. package/src/hooks/useSelection/__doc__/useSelection.stories.tsx +140 -0
  312. package/src/hooks/useSelection/__test__/useSelection.test.tsx +57 -0
  313. package/src/hooks/useSelection/index.ts +1 -0
  314. package/src/hooks/useSelection/useSelection.ts +121 -0
  315. package/src/hooks/useStep/__doc__/useStep.stories.tsx +98 -0
  316. package/src/hooks/useStep/__test__/useStep.test.ts +51 -0
  317. package/src/hooks/useStep/index.ts +1 -0
  318. package/src/hooks/useStep/useStep.ts +57 -0
  319. package/src/hooks/useToggle/__doc__/useToggle.stories.tsx +25 -0
  320. package/src/hooks/useToggle/__test__/useToggle.test.tsx +43 -0
  321. package/src/hooks/useToggle/index.ts +1 -0
  322. package/src/hooks/useToggle/useToggle.ts +16 -0
  323. package/src/index.ts +6 -0
  324. package/src/lib/cn.ts +8 -0
  325. package/src/lib/index.ts +1 -0
  326. package/src/models/Generic.model.ts +67 -0
  327. package/src/models/index.ts +1 -0
  328. package/src/providers/index.ts +2 -0
  329. package/src/providers/library-provider.tsx +44 -0
  330. package/src/providers/theme/ThemeProvider.tsx +25 -0
  331. package/src/providers/theme/index.ts +3 -0
  332. package/src/providers/theme/types.ts +11 -0
  333. package/src/providers/theme/useThemeProps.ts +25 -0
  334. package/src/stores/theme.store.ts +31 -0
  335. package/src/styles/components.css +4 -0
  336. package/src/styles/index.css +2 -0
  337. package/src/styles/library.css +2 -0
  338. package/src/styles/theme.css +232 -0
  339. package/src/utils/dates/parseDateRange.utility.ts +39 -0
  340. package/src/utils/form.tsx +91 -0
  341. package/src/utils/functions/createSafeContext.ts +17 -0
  342. package/src/utils/functions/ensureReactElement.tsx +30 -0
  343. package/src/utils/functions/getFormData.ts +19 -0
  344. package/src/utils/functions/index.ts +4 -0
  345. package/src/utils/functions/mergeRefs.ts +18 -0
  346. package/src/utils/index.ts +3 -0
  347. package/src/utils/strings/extractInitials.utility.ts +10 -0
  348. package/src/utils/strings/index.ts +1 -0
  349. package/src/utils/tests/click.ts +3 -0
  350. package/src/utils/tests/index.ts +2 -0
  351. package/src/utils/tests/keyboard.ts +21 -0
  352. package/src/utils/tests/type.ts +6 -0
@@ -0,0 +1,119 @@
1
+ import { Meta } from "@storybook/react-vite";
2
+ import { ChevronLeft, ChevronRight } from "lucide-react";
3
+ import { Button, Input } from "../../../components";
4
+ import useObject from "../useObject";
5
+
6
+ /**
7
+ * Hook para manejar un estado en forma de objeto
8
+ *
9
+ * `@param initialValue` - Valor inicial del estado, se recomienda pasar para evitar el caso donde el estado `T` pueda ser `T | undefined`
10
+ *
11
+ * `@returns` [state, setState, reset]
12
+ */
13
+ const meta: Meta = {
14
+ title: "hooks/useObject",
15
+ };
16
+
17
+ export default meta;
18
+
19
+ interface Form {
20
+ name: string;
21
+ email: string;
22
+ }
23
+
24
+ function Results({ state }: { state?: Record<string, any> }) {
25
+ if (!state) return;
26
+ return (
27
+ <pre className="rounded-md bg-slate-950 p-4 text-white">
28
+ {Object.keys(state).map((key, i) => (
29
+ <code className="block flex gap-2" key={i}>
30
+ <span>{key}:</span>
31
+ <span className="text-blue-300">
32
+ {JSON.stringify(state[key as string], null, 2)}
33
+ </span>
34
+ </code>
35
+ ))}
36
+ </pre>
37
+ );
38
+ }
39
+
40
+ export const Default = {
41
+ render: () => {
42
+ const [state, setState, reset] = useObject<Form>({ name: "", email: "" });
43
+
44
+ return (
45
+ <div className="grid gap-2">
46
+ <Input
47
+ name="name"
48
+ autoComplete="0"
49
+ value={state?.name}
50
+ onChange={(name) =>
51
+ setState({
52
+ name,
53
+ })
54
+ }
55
+ />
56
+ <Input
57
+ name="email"
58
+ autoComplete="0"
59
+ value={state?.email}
60
+ onChange={(email) =>
61
+ setState({
62
+ email,
63
+ })
64
+ }
65
+ />
66
+
67
+ <Button type="button" onClick={reset}>
68
+ Reset
69
+ </Button>
70
+
71
+ <Results state={state} />
72
+ </div>
73
+ );
74
+ },
75
+ };
76
+
77
+ interface Counter {
78
+ count: number;
79
+ }
80
+
81
+ export const Counter = {
82
+ render: () => {
83
+ const [state, setState, reset] = useObject<Counter>({ count: 0 });
84
+
85
+ return (
86
+ <div className="space-y-2">
87
+ <div className="flex items-center gap-x-2">
88
+ <Button
89
+ size="icon"
90
+ variant="ghost"
91
+ onClick={() =>
92
+ setState((state) => ({
93
+ count: state.count - 1,
94
+ }))
95
+ }
96
+ >
97
+ <ChevronLeft />
98
+ </Button>
99
+ {state?.count}
100
+ <Button
101
+ size="icon"
102
+ variant="ghost"
103
+ onClick={() =>
104
+ setState((state) => ({
105
+ count: state.count + 1,
106
+ }))
107
+ }
108
+ >
109
+ <ChevronRight />
110
+ </Button>
111
+ <Button type="button" onClick={reset}>
112
+ Reset
113
+ </Button>
114
+ </div>
115
+ <Results state={state} />
116
+ </div>
117
+ );
118
+ },
119
+ };
@@ -0,0 +1,87 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { act } from "react";
3
+ import { describe, expect, it } from "vitest";
4
+ import useObject from "../useObject";
5
+
6
+ describe("useObject hook", () => {
7
+ it("should initialize with default state", () => {
8
+ const initialValue = { count: 0 };
9
+ const { result } = renderHook(() => useObject(initialValue));
10
+
11
+ expect(result.current[0]).toEqual(initialValue);
12
+ });
13
+
14
+ it("should handle object updates", () => {
15
+ const initialValue = { count: 0 };
16
+ const { result } = renderHook(() => useObject<{}>(initialValue));
17
+
18
+ act(() => {
19
+ result.current[1]({ count: 1 });
20
+ });
21
+
22
+ expect(result.current[0]).toEqual({ count: 1 });
23
+
24
+ act(() => {
25
+ result.current[1]({ name: "test" });
26
+ });
27
+
28
+ expect(result.current[0]).toEqual({ count: 1, name: "test" });
29
+ });
30
+
31
+ it("should handle function updates", () => {
32
+ const initialValue = { count: 0 };
33
+ const { result } = renderHook(() => useObject(initialValue));
34
+
35
+ act(() => {
36
+ result.current[1]((state) => ({ count: (state?.count ?? 0) + 1 }));
37
+ });
38
+
39
+ expect(result.current[0]).toEqual({ count: 1 });
40
+
41
+ act(() => {
42
+ result.current[1]((state) => ({ count: (state?.count ?? 0) + 1 }));
43
+ });
44
+
45
+ expect(result.current[0]).toEqual({ count: 2 });
46
+ });
47
+
48
+ it("should reset to initial value", () => {
49
+ const initialValue = { count: 0 };
50
+ const { result } = renderHook(() => useObject(initialValue));
51
+
52
+ act(() => {
53
+ result.current[1]({ count: 1 });
54
+ });
55
+
56
+ expect(result.current[0]).toEqual({ count: 1 });
57
+
58
+ act(() => {
59
+ result.current[2](); // reset
60
+ });
61
+
62
+ expect(result.current[0]).toEqual(initialValue);
63
+ });
64
+
65
+ it("should initialize with undefined if no initial value is provided", () => {
66
+ const { result } = renderHook(() => useObject());
67
+
68
+ expect(result.current[0]).toBeUndefined();
69
+ });
70
+
71
+ it("should handle partial updates", () => {
72
+ const initialValue = { count: 0, name: "initial" };
73
+ const { result } = renderHook(() => useObject(initialValue));
74
+
75
+ act(() => {
76
+ result.current[1]({ count: 1 });
77
+ });
78
+
79
+ expect(result.current[0]).toEqual({ count: 1, name: "initial" });
80
+
81
+ act(() => {
82
+ result.current[1]({ name: "updated" });
83
+ });
84
+
85
+ expect(result.current[0]).toEqual({ count: 1, name: "updated" });
86
+ });
87
+ });
@@ -0,0 +1 @@
1
+ export { default as useObject } from "./useObject";
@@ -0,0 +1,48 @@
1
+ import { isFunction, isObject } from "lodash";
2
+ import { useCallback, useState } from "react";
3
+
4
+ type GenericObject = Record<string, any>;
5
+ type Return<
6
+ TState,
7
+ TArgs = ((state: TState) => Partial<TState>) | Partial<TState>,
8
+ > = [TState, (arg: TArgs) => void, () => void];
9
+ type MaybeReturn<TState> = Return<
10
+ TState | undefined,
11
+ ((state: TState | undefined) => Partial<TState>) | Partial<TState>
12
+ >;
13
+
14
+ function useObject<T extends GenericObject>(): MaybeReturn<T>;
15
+ function useObject<T extends GenericObject>(initialValue: T): Return<T>;
16
+ function useObject<T extends GenericObject>(initialValue?: T) {
17
+ const [state, setState] = useState<T | undefined>(initialValue);
18
+
19
+ const reset = useCallback(() => setState(initialValue), [initialValue]);
20
+
21
+ const setNewState = useCallback(
22
+ (arg: ((state: T | undefined) => Partial<T>) | Partial<T>) => {
23
+ setState((prevState) => {
24
+ if (isFunction(arg)) {
25
+ const newState = arg(prevState);
26
+ if (isObject(newState)) {
27
+ return {
28
+ ...prevState,
29
+ ...newState,
30
+ } as T;
31
+ }
32
+ return prevState;
33
+ } else if (isObject(arg)) {
34
+ return {
35
+ ...prevState,
36
+ ...arg,
37
+ } as T;
38
+ }
39
+ return prevState;
40
+ });
41
+ },
42
+ [],
43
+ );
44
+
45
+ return [state, setNewState, reset] as const;
46
+ }
47
+
48
+ export default useObject;
@@ -0,0 +1,72 @@
1
+ import { Meta } from "@storybook/react-vite";
2
+ import { useState } from "react";
3
+ import { action } from "storybook/actions";
4
+ import { Button } from "../../../components";
5
+ import usePagination from "../usePagination";
6
+
7
+ const TABLE_PAGE_SIZES = [5, 10, 20, 50, 100];
8
+ /**
9
+ * Hook que facilita el manejo de paginaciones, recibe como param la cantidad de elementos, la cantidad de elementos por página y una función
10
+ * que se ejecuta cuando cambia la pagina
11
+ */
12
+ const meta: Meta = {
13
+ title: "hooks/usePagination",
14
+ tags: ["autodocs"],
15
+ };
16
+
17
+ export default meta;
18
+
19
+ export const Default = () => {
20
+ const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZES[0]);
21
+ const { currentPage, maxPage, isLastPage, isFirstPage, prev, next, goTo } =
22
+ usePagination({
23
+ totalItems: 100,
24
+ pageSize: pageSize,
25
+ onChange: action("change"),
26
+ initialCurrentPage: 1,
27
+ });
28
+
29
+ return (
30
+ <div className="space-y-2">
31
+ <pre className="rounded-md bg-slate-950 p-4 text-white">
32
+ <code className="block">
33
+ CurrentPage:{" "}
34
+ <span className="text-blue-300">
35
+ {JSON.stringify(currentPage, null, 2)}
36
+ </span>
37
+ </code>
38
+ <code className="block">PageSize: {pageSize}</code>
39
+ <code className="block">Maxpage: {maxPage}</code>
40
+ <code className="block">
41
+ isLastPage:{" "}
42
+ <span className={isLastPage ? "text-green-500" : "text-red-500"}>
43
+ {JSON.stringify(isLastPage)}
44
+ </span>
45
+ </code>
46
+ <code className="block">
47
+ IsFirstPage:{" "}
48
+ <span className={isFirstPage ? "text-green-500" : "text-red-500"}>
49
+ {JSON.stringify(isFirstPage)}
50
+ </span>
51
+ </code>
52
+ </pre>
53
+
54
+ <div className="flex gap-2">
55
+ <select
56
+ className="border rounded w-24 px-2"
57
+ value={pageSize}
58
+ onChange={(event) => setPageSize(Number(event.target.value))}
59
+ >
60
+ {TABLE_PAGE_SIZES.map((option: number, index) => (
61
+ <option key={index} value={option}>
62
+ {option}
63
+ </option>
64
+ ))}
65
+ </select>
66
+ <Button onClick={prev}>Página anterior</Button>
67
+ <Button onClick={next}>Siguiente pagina</Button>
68
+ <Button onClick={() => goTo(10)}>Ir a la pagina 10</Button>
69
+ </div>
70
+ </div>
71
+ );
72
+ };
@@ -0,0 +1,98 @@
1
+ import { act, renderHook, waitFor } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+ import usePagination from "../usePagination";
4
+
5
+ describe("usePagination hook", () => {
6
+ it("should be defined", () => {
7
+ expect(usePagination).toBeDefined();
8
+ });
9
+
10
+ it("should show total items and items per page", () => {
11
+ const { result } = renderHook(() =>
12
+ usePagination({
13
+ totalItems: 100,
14
+ pageSize: 10,
15
+ }),
16
+ );
17
+ const { maxPage, currentPage } = result.current;
18
+
19
+ expect(maxPage).toBe(10);
20
+ expect(currentPage).toBe(1);
21
+ });
22
+
23
+ it("should recive initial current page", () => {
24
+ const { result } = renderHook(() =>
25
+ usePagination({
26
+ totalItems: 100,
27
+ pageSize: 10,
28
+ initialCurrentPage: 5,
29
+ }),
30
+ );
31
+ const { currentPage } = result.current;
32
+ expect(currentPage).toBe(5);
33
+ });
34
+
35
+ it("should go next", () => {
36
+ const { result } = renderHook(() =>
37
+ usePagination({
38
+ totalItems: 100,
39
+ pageSize: 10,
40
+ }),
41
+ );
42
+ const { next } = result.current;
43
+ act(() => next());
44
+
45
+ const { currentPage } = result.current;
46
+ expect(currentPage).toBe(2);
47
+ });
48
+
49
+ it("should go previous", () => {
50
+ const { result } = renderHook(() =>
51
+ usePagination({
52
+ totalItems: 100,
53
+ pageSize: 10,
54
+ initialCurrentPage: 3,
55
+ }),
56
+ );
57
+ const { prev } = result.current;
58
+ act(() => prev());
59
+
60
+ const { currentPage } = result.current;
61
+ expect(currentPage).toBe(2);
62
+ });
63
+
64
+ it("should go to page number", () => {
65
+ const { result } = renderHook(() =>
66
+ usePagination({
67
+ totalItems: 100,
68
+ pageSize: 10,
69
+ }),
70
+ );
71
+ const { goTo, currentPage } = result.current;
72
+ act(() => goTo(5));
73
+
74
+ waitFor(() => {
75
+ expect(currentPage).toBe(5);
76
+ });
77
+ });
78
+
79
+ it("should show isFirstPage and isLastPage", () => {
80
+ const { result } = renderHook(() =>
81
+ usePagination({
82
+ totalItems: 100,
83
+ pageSize: 10,
84
+ initialCurrentPage: 5,
85
+ }),
86
+ );
87
+
88
+ const { isFirstPage, isLastPage, goTo, maxPage } = result.current;
89
+ expect(isFirstPage).toBe(false);
90
+ expect(isLastPage).toBe(false);
91
+
92
+ act(() => goTo(maxPage));
93
+
94
+ waitFor(() => {
95
+ expect(isLastPage).toBe(true);
96
+ });
97
+ });
98
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./usePagination";
2
+ export { default as usePagination } from "./usePagination";
@@ -0,0 +1,74 @@
1
+ import { useState } from "react";
2
+
3
+ export type usePaginationProps = {
4
+ /**
5
+ * Cantidad total de elementos
6
+ */
7
+ totalItems: number;
8
+ /**
9
+ * Cantidad de elementos por página
10
+ */
11
+ pageSize: number;
12
+ /**
13
+ * Pagina inicial
14
+ */
15
+ initialCurrentPage?: number;
16
+ /**
17
+ * Callback que se ejecuta cuando cambia la pagina
18
+ */
19
+ onChange?: (value: { currentPage: number; pageSize: number }) => void;
20
+ siblingCount?: number;
21
+ };
22
+
23
+ function usePagination({
24
+ totalItems,
25
+ pageSize,
26
+ initialCurrentPage = 1,
27
+ onChange,
28
+ }: usePaginationProps) {
29
+ const [currentPage, setCurrentPage] = useState<number>(initialCurrentPage);
30
+ const maxPage = Math.ceil(totalItems / pageSize);
31
+ const isLastPage = currentPage === maxPage;
32
+ const isFirstPage = currentPage === 1;
33
+ const start = (currentPage - 1) * pageSize + 1;
34
+ const end = Math.min(currentPage * pageSize, totalItems);
35
+
36
+ const next = () => {
37
+ if (isLastPage) return;
38
+ const newPage = Math.min(currentPage + 1, maxPage);
39
+ handleNewPage(newPage);
40
+ };
41
+
42
+ const prev = () => {
43
+ if (isFirstPage) return;
44
+ const newPage = Math.max(currentPage - 1, 1);
45
+ handleNewPage(newPage);
46
+ };
47
+
48
+ const goTo = (page: number) => {
49
+ if (page === 1 && isFirstPage) return;
50
+ if (page === maxPage && isLastPage) return;
51
+
52
+ const pageNumber = Math.max(1, page);
53
+ const newPage = Math.min(pageNumber, maxPage);
54
+ handleNewPage(newPage);
55
+ };
56
+
57
+ const handleNewPage = (page: number) => {
58
+ setCurrentPage(page);
59
+ onChange?.({ currentPage: page, pageSize });
60
+ };
61
+
62
+ return {
63
+ next,
64
+ prev,
65
+ goTo,
66
+ currentPage,
67
+ maxPage,
68
+ isFirstPage,
69
+ isLastPage,
70
+ range: { start, end },
71
+ };
72
+ }
73
+
74
+ export default usePagination;
@@ -0,0 +1,19 @@
1
+ import { Meta } from "@storybook/react-vite";
2
+ import usePortal from "../usePortal";
3
+
4
+ const meta: Meta = {
5
+ title: "hooks/usePortal",
6
+ };
7
+
8
+ export default meta;
9
+
10
+ export const Default = {
11
+ render: () => {
12
+ const { render } = usePortal("id");
13
+ return render(
14
+ <div className="fixed-center bg-primary text-primary-foreground rounded p-4">
15
+ Este contenido se muestra en un portal 🧱
16
+ </div>,
17
+ );
18
+ },
19
+ };
@@ -0,0 +1,20 @@
1
+ import { render, renderHook, screen } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+ import usePortal from "../usePortal";
4
+
5
+ describe("usePortal hook", () => {
6
+ it("should be defined", () => {
7
+ const { result } = renderHook(() => usePortal("id"));
8
+ expect(result.current).toBeDefined();
9
+ });
10
+
11
+ it("should render element", () => {
12
+ const Component = () => {
13
+ const { render } = usePortal("id2");
14
+ return render(<div>Hello World</div>);
15
+ };
16
+
17
+ render(<Component />);
18
+ expect(screen.getByText("Hello World")).toBeInTheDocument();
19
+ });
20
+ });
@@ -0,0 +1 @@
1
+ export { default as usePortal } from "./usePortal";
@@ -0,0 +1,40 @@
1
+ import type { ReactNode, ReactPortal } from "react";
2
+ import { useEffect, useMemo } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { Nullable } from "@/models";
5
+
6
+ function usePortal(id: string) {
7
+ const getWrapper = (): Nullable<HTMLElement> => document.getElementById(id);
8
+
9
+ const createWrapper = (): HTMLElement => {
10
+ const element = document.createElement("div");
11
+ element.id = id;
12
+ return element;
13
+ };
14
+
15
+ const wrapper = useMemo(() => {
16
+ const element = getWrapper();
17
+ if (element) return element;
18
+ return createWrapper();
19
+ }, [id]);
20
+
21
+ useEffect(() => {
22
+ if (!document.getElementById(id)) {
23
+ document.body.appendChild(wrapper);
24
+ }
25
+
26
+ return () => {
27
+ const element = document.getElementById(id);
28
+ if (element) {
29
+ document.body.removeChild(element);
30
+ }
31
+ };
32
+ }, [id, wrapper]);
33
+
34
+ return {
35
+ render: (children: ReactNode): ReactPortal | null =>
36
+ createPortal(children, wrapper),
37
+ };
38
+ }
39
+
40
+ export default usePortal;
@@ -0,0 +1,32 @@
1
+ import { Meta } from "@storybook/react-vite";
2
+ import usePreventCloseWindow from "../usePreventCloseWindow";
3
+
4
+ /**
5
+ * Hook que permite prevenir que se cierre la ventana (muestra mensaje de aviso)
6
+ * Es útil para evitar que el usuario cierre la ventana accidentalmente mientras tiene cambios pendientes de guardar
7
+ */
8
+ const meta: Meta = {
9
+ title: "hooks/usePreventCloseWindow",
10
+ args: {
11
+ shouldPrevent: true,
12
+ message: "No te vayas 😞",
13
+ },
14
+ argTypes: {
15
+ shouldPrevent: {
16
+ description: "Propiedad que determina si la ventana se puede cerrar",
17
+ },
18
+ message: {
19
+ description:
20
+ "Mensaje que se muestra al cerrar la ventana, no soportado por navegadores modernos",
21
+ },
22
+ },
23
+ };
24
+
25
+ export default meta;
26
+
27
+ export const Default = {
28
+ render: (args: any) => {
29
+ usePreventCloseWindow(args);
30
+ return <div>Intenta cerrar ventana</div>;
31
+ },
32
+ };
@@ -0,0 +1 @@
1
+ export { default as usePreventCloseWindow } from "./usePreventCloseWindow";
@@ -0,0 +1,33 @@
1
+ import { useEffect } from "react";
2
+
3
+ interface UsePreventUnloadProps {
4
+ /**
5
+ * Propiedad que determina si la ventana se puede cerrar o no
6
+ */
7
+ shouldPrevent: boolean;
8
+ /**
9
+ * Mensaje que se va a mostrar al usuario antes de cerrar la ventana
10
+ * @deprecated ya no es soportado por los navegadores modernos
11
+ */
12
+ message?: string;
13
+ }
14
+
15
+ function usePreventClose({
16
+ shouldPrevent,
17
+ message = "Es posible que no se guarden los cambios hayas hecho.",
18
+ }: UsePreventUnloadProps) {
19
+ const handleBeforeUnload = (event: BeforeUnloadEvent): void | string => {
20
+ if (!shouldPrevent) return;
21
+
22
+ event.preventDefault();
23
+ event.returnValue = message;
24
+ return message;
25
+ };
26
+
27
+ useEffect(() => {
28
+ window.addEventListener("beforeunload", handleBeforeUnload);
29
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
30
+ }, [message, shouldPrevent]);
31
+ }
32
+
33
+ export default usePreventClose;