@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,396 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { cva, VariantProps } from "class-variance-authority";
3
+ import { Flame, SearchIcon } from "lucide-react";
4
+ import { ComponentProps } from "react";
5
+ import { BaseButton, Button, Heading } from "../../components";
6
+ import { cn } from "../../lib";
7
+ import { ThemeProvider } from "../../providers";
8
+
9
+ /**
10
+ * Componente principal para ejecutar acciones.
11
+ *
12
+ * Extendiende al componente `BaseButton` agregándole variantes
13
+ */
14
+ const meta: Meta<typeof Button> = {
15
+ title: "Data entry/Button",
16
+ component: Button,
17
+ args: {
18
+ children: "Click me!",
19
+ },
20
+ };
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof Button>;
24
+
25
+ export const Default: Story = {};
26
+
27
+ export const Outline: Story = {
28
+ args: {
29
+ variant: "outline",
30
+ },
31
+ };
32
+
33
+ export const Secondary: Story = {
34
+ args: {
35
+ variant: "secondary",
36
+ },
37
+ };
38
+
39
+ export const Ghost: Story = {
40
+ args: {
41
+ variant: "ghost",
42
+ },
43
+ };
44
+
45
+ export const Link: Story = {
46
+ args: {
47
+ variant: "link",
48
+ },
49
+ };
50
+
51
+ export const Error: Story = {
52
+ args: {
53
+ variant: "error",
54
+ },
55
+ };
56
+
57
+ export const Success: Story = {
58
+ args: {
59
+ variant: "success",
60
+ },
61
+ };
62
+
63
+ /**
64
+ * Se puede agregar un ícono al botón con la prop `icon`.
65
+ */
66
+ export const Icon: Story = {
67
+ args: {
68
+ icon: <SearchIcon />,
69
+ },
70
+ };
71
+
72
+ /**
73
+ * Se puede cambiar la posición del ícono con la prop `iconPosition`.
74
+ *
75
+ * Los valores posibles son `start` y `end`.
76
+ */
77
+ export const IconPosition: Story = {
78
+ args: {
79
+ icon: <SearchIcon />,
80
+ iconPosition: "end",
81
+ },
82
+ };
83
+
84
+ /**
85
+ * El button recibe la propieda `loading` que permite controlar el estado de carga del botón.
86
+ * Se puede cambiar la forma en la que se muestra el loader con la prop `loaderReplace`.
87
+ * Si `loaderReplace` es `true`, el loader reemplaza todo el contenido manteniendo el ancho original del botón.
88
+ * Por defecto, el loader se agrega al contenido existente o reemplaza al ícono en caso de que exista.
89
+ */
90
+ export const Loading: Story = {
91
+ render: () => {
92
+ const sleep = (): Promise<void> => {
93
+ return new Promise((resolve) => setTimeout(resolve, 2000));
94
+ };
95
+
96
+ return (
97
+ <div className="space-y-8">
98
+ {/* Modo Append (default) */}
99
+ <section>
100
+ <Heading as="h2" className="mb-4">
101
+ Modo Append (default)
102
+ </Heading>
103
+ <p className="text-sm text-muted-foreground mb-4">
104
+ En este modo, el loader se agrega al contenido existente. Si hay un
105
+ ícono, el loader lo reemplaza.
106
+ </p>
107
+ <div className="flex flex-wrap items-center gap-4">
108
+ <Button onClick={sleep}>Por izquierda</Button>
109
+ <Button onClick={sleep} iconPosition="end">
110
+ Por derecha
111
+ </Button>
112
+ <Button onClick={sleep} icon={<SearchIcon />}>
113
+ Ícono inicio
114
+ </Button>
115
+ <Button onClick={sleep} icon={<SearchIcon />} iconPosition="end">
116
+ Ícono final
117
+ </Button>
118
+ <Button onClick={sleep} icon={<SearchIcon />} size="icon" />
119
+ </div>
120
+ </section>
121
+
122
+ {/* Modo Replace */}
123
+ <section>
124
+ <Heading as="h2" className="mb-4">
125
+ Modo Replace (loaderReplace = true)
126
+ </Heading>
127
+ <p className="text-sm text-muted-foreground mb-4">
128
+ En este modo, el loader reemplaza todo el contenido manteniendo el
129
+ ancho original del botón.
130
+ </p>
131
+ <div className="flex flex-wrap items-center gap-4">
132
+ <Button onClick={sleep} loaderReplace>
133
+ Solo texto
134
+ </Button>
135
+ <Button onClick={sleep} loaderReplace icon={<SearchIcon />}>
136
+ Ícono inicio
137
+ </Button>
138
+ <Button
139
+ onClick={sleep}
140
+ loaderReplace
141
+ icon={<SearchIcon />}
142
+ iconPosition="end"
143
+ >
144
+ Ícono final
145
+ </Button>
146
+ <Button
147
+ onClick={sleep}
148
+ loaderReplace
149
+ icon={<SearchIcon />}
150
+ size="icon"
151
+ />
152
+ </div>
153
+ </section>
154
+
155
+ {/* Variantes */}
156
+ <section>
157
+ <Heading as="h2" className="mb-4">
158
+ Variantes
159
+ </Heading>
160
+ <p className="text-sm text-muted-foreground mb-4">
161
+ El loader funciona con todas las variantes de botón.
162
+ </p>
163
+ <div className="flex flex-wrap items-center gap-4">
164
+ <Button onClick={sleep} variant="outline" icon={<SearchIcon />}>
165
+ Outline
166
+ </Button>
167
+ <Button onClick={sleep} variant="secondary" icon={<SearchIcon />}>
168
+ Secondary
169
+ </Button>
170
+ <Button onClick={sleep} variant="ghost" icon={<SearchIcon />}>
171
+ Ghost
172
+ </Button>
173
+ <Button onClick={sleep} variant="link" icon={<SearchIcon />}>
174
+ Link
175
+ </Button>
176
+ <Button onClick={sleep} variant="error" icon={<SearchIcon />}>
177
+ Error
178
+ </Button>
179
+ <Button onClick={sleep} variant="success" icon={<SearchIcon />}>
180
+ Success
181
+ </Button>
182
+ </div>
183
+ </section>
184
+ </div>
185
+ );
186
+ },
187
+ };
188
+
189
+ /**
190
+ * Al setear `asChild` a `true`, puedes renderizar cualquier elemento con los estilos, props y referencias del `Button`.
191
+ *
192
+ * En este caso, se usa un elemento `a` que hereda los estilos, props y referencias del `Button`.
193
+ * Esto es útil para aplicar los mismos estilos y garantizar consistencia en la interfaz de usuario de su aplicación.
194
+ * Se puede verificar abriendo la consola del navegador.
195
+ */
196
+ export const AsChild: Story = {
197
+ args: {
198
+ children: <a href="#">Click me</a>,
199
+ variant: "error",
200
+ asChild: true,
201
+ },
202
+ };
203
+
204
+ /**
205
+ * El componente recibe todas las propiedades de `button`, por lo que se puede customizar los estilos, accesibilidad, referencias, eventos, etc
206
+ */
207
+ export const Custom: Story = {
208
+ args: {
209
+ className: "bg-amber-500 hover:bg-amber-600",
210
+ },
211
+ };
212
+
213
+ /**
214
+ * Estos son los tamanos disponibles para el `Button`.
215
+ */
216
+ export const Sizes: Story = {
217
+ render: () => {
218
+ const sizes = ["default", "sm", "lg", "icon"] as const;
219
+
220
+ return (
221
+ <>
222
+ {sizes.map((size) => (
223
+ <div key={size} className="mb-1">
224
+ <p className="font-semibold">{`size: ${size}`}</p>
225
+ <Button size={size} key={size}>
226
+ {size === "icon" ? <Flame /> : "Click me"}
227
+ </Button>
228
+ </div>
229
+ ))}
230
+ </>
231
+ );
232
+ },
233
+ };
234
+
235
+ export const Shapes: Story = {
236
+ render: () => {
237
+ const shapes = ["rounded", "square", "circle"] as const;
238
+
239
+ return (
240
+ <div className="flex flex-wrap items-center gap-4">
241
+ {shapes.map((shape) => (
242
+ <div key={shape} className="flex flex-col gap-2">
243
+ <p className="font-semibold capitalize">{shape}</p>
244
+ <Button shape={shape} key={shape} size="icon" icon={<Flame />} />
245
+ <Button shape={shape} key={shape}>
246
+ {shape}
247
+ </Button>
248
+ </div>
249
+ ))}
250
+ </div>
251
+ );
252
+ },
253
+ };
254
+
255
+ const buttonVariants = cva(
256
+ [
257
+ "flex items-center justify-center gap-2 px-4 py-2",
258
+ "text-sm font-medium transition-all",
259
+ "rounded ring-offset-background",
260
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
261
+ "disabled:pointer-events-none disabled:opacity-50",
262
+ ],
263
+ {
264
+ variants: {
265
+ variant: {
266
+ white: "bg-white text-primary border",
267
+ black: "bg-black text-white",
268
+ },
269
+ },
270
+ defaultVariants: {
271
+ variant: "white",
272
+ },
273
+ },
274
+ );
275
+
276
+ type BaseButtonProps = ComponentProps<typeof BaseButton>;
277
+ interface Props extends BaseButtonProps, VariantProps<typeof buttonVariants> {}
278
+
279
+ const CustomButton = ({ variant, ...props }: Props) => {
280
+ return <BaseButton className={cn(buttonVariants({ variant }))} {...props} />;
281
+ };
282
+
283
+ /**
284
+ * #### Crea tus propias variantes
285
+ * En caso de que necesites customizar demasiado a tu componente, lo mejor es crear tus propias variantes.
286
+ * Para esto se puede importar el componente `BaseButton` el cuál no incluye estilos por defecto y se puede extender para crear variantes personalizadas.
287
+ *
288
+ * A continuación, se definen las variantes de botón que puedes extender o modificar a tus necesidades.
289
+ *
290
+ * ```tsx
291
+ * import { ComponentProps } from 'react';
292
+ * import { cva, type VariantProps } from 'class-variance-authority';
293
+ * import { BaseButton, cn } from '@boxcustodia/library';
294
+ *
295
+ * export const buttonVariants = cva(
296
+ * [
297
+ * 'flex items-center justify-center gap-2',
298
+ * 'text-sm font-medium transition-all',
299
+ * 'rounded-md ring-offset-background',
300
+ * 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
301
+ * 'disabled:pointer-events-none disabled:opacity-50',
302
+ * 'hover:brightness-105',
303
+ * ],
304
+ * {
305
+ * variants: {
306
+ * variant: {
307
+ * default: "bg-primary text-primary-foreground",
308
+ * error: "bg-error text-error-foreground",
309
+ * success: "bg-success text-success-foreground",
310
+ * outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
311
+ * secondary: "bg-secondary text-secondary-foreground hover:brightness-100 hover:bg-secondary/80",
312
+ * ghost: "hover:bg-accent hover:brightness-100 hover:text-accent-foreground",
313
+ * link: "text-primary underline-offset-4 hover:underline",
314
+ * white: 'bg-white text-primary', <-- Nueva variante
315
+ * black: 'bg-black text-white', <-- Nueva variante
316
+ * },
317
+ * size: {
318
+ * default: "h-10 px-4 py-2",
319
+ * sm: "h-9 rounded-md px-3",
320
+ * lg: "h-11 rounded-md px-8",
321
+ * icon: "w-10 aspect-square",
322
+ * },
323
+ * },
324
+ * defaultVariants: {
325
+ * variant: 'default',
326
+ * size: 'default',
327
+ * },
328
+ * },
329
+ * );
330
+ *
331
+ * type BaseButtonProps = ComponentProps<typeof BaseButton>;
332
+ * typer Props = BaseButtonProps, VariantProps<typeof buttonVariants>
333
+ *
334
+ * export const Button = ({ className, variant, size, ...props }: Props) => {
335
+ * return (
336
+ * <BaseButton
337
+ * {...props}
338
+ * className={cn(buttonVariants({ variant, size }), className)}
339
+ * />
340
+ * );
341
+ * };
342
+ *
343
+ * ```
344
+ *
345
+ */
346
+
347
+ export const CustomVariants: Story = {
348
+ render: () => (
349
+ <div className="space-y-4">
350
+ <CustomButton variant="black">Default Button</CustomButton>
351
+ <CustomButton variant="white">Error Button</CustomButton>
352
+ </div>
353
+ ),
354
+ };
355
+
356
+ /**
357
+ * ThemeProvider permite customizar de manera global las propiedades del componente
358
+ *
359
+ * Ejemplo:
360
+ * ```tsx
361
+ * <ThemeProvider
362
+ * theme={{
363
+ * Button: {
364
+ * className: "bg-red-500",
365
+ * icon: <SearchIcon />,
366
+ * iconPosition: "end",
367
+ * loaderReplace: true,
368
+ * },
369
+ * }}
370
+ * >
371
+ * <Button>Default Button</Button>
372
+ * </ThemeProvider>
373
+ * ```
374
+ */
375
+ export const Theme: Story = {
376
+ render: () => {
377
+ const sleep = (): Promise<void> => {
378
+ return new Promise((resolve) => setTimeout(resolve, 2000));
379
+ };
380
+
381
+ return (
382
+ <ThemeProvider
383
+ theme={{
384
+ Button: {
385
+ className: "bg-red-500",
386
+ icon: <SearchIcon />,
387
+ iconPosition: "end",
388
+ loaderReplace: true,
389
+ },
390
+ }}
391
+ >
392
+ <Button onClick={sleep}>Default Button</Button>
393
+ </ThemeProvider>
394
+ );
395
+ },
396
+ };
@@ -0,0 +1,58 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { createRef } from "react";
3
+ import { describe, expect, it } from "vitest";
4
+ import { Button } from "../../components";
5
+
6
+ describe("Button component", () => {
7
+ it("should render correctly", () => {
8
+ render(<Button />);
9
+ const button = screen.getByRole("button");
10
+
11
+ expect(button).toBeInTheDocument();
12
+ });
13
+
14
+ it("should render children correctly", () => {
15
+ render(<Button>Click me</Button>);
16
+ const button = screen.getByRole("button");
17
+
18
+ expect(button).toHaveTextContent("Click me");
19
+ });
20
+
21
+ it("should render as child correctly", () => {
22
+ render(
23
+ <Button asChild>
24
+ <a href="#">Click me</a>
25
+ </Button>,
26
+ );
27
+
28
+ // No debe renderizarse como un button
29
+ const button = screen.queryByRole("button");
30
+ expect(button).not.toBeInTheDocument();
31
+
32
+ // Debe renderizarse como un link
33
+ const link = screen.getByRole("link");
34
+ expect(link).toBeInTheDocument();
35
+ expect(link).toHaveTextContent("Click me");
36
+ });
37
+
38
+ it("should accept className prop", () => {
39
+ const TEST_CLASS = "lorem";
40
+ render(<Button className={TEST_CLASS} />);
41
+ const button = screen.getByRole("button");
42
+
43
+ expect(button).toHaveClass(TEST_CLASS);
44
+ });
45
+
46
+ it("should accept ref prop", () => {
47
+ const ref = createRef<HTMLButtonElement>();
48
+ render(<Button ref={ref} />);
49
+
50
+ expect(ref.current).not.toBeNull();
51
+ expect(ref.current).toBeInstanceOf(HTMLButtonElement);
52
+ });
53
+
54
+ it("should show loader", () => {
55
+ render(<Button loading />);
56
+ expect(screen.getByTestId("btn-loader")).toBeInTheDocument();
57
+ });
58
+ });
@@ -0,0 +1,31 @@
1
+ import { type VariantProps } from "class-variance-authority";
2
+ import { ComponentProps } from "react";
3
+ import { cn } from "../../lib";
4
+ import { useThemeProps } from "../../providers/theme/useThemeProps";
5
+ import { buttonVariants } from "./button.variants";
6
+ import { BaseButton } from "./components/base-button";
7
+
8
+ type BaseButtonProps = ComponentProps<typeof BaseButton>;
9
+ type Props = BaseButtonProps & VariantProps<typeof buttonVariants>;
10
+
11
+ export const Button = (props: Props) => {
12
+ const { variant, size, shape, className, ...rest } = useThemeProps(
13
+ "Button",
14
+ props,
15
+ );
16
+
17
+ return (
18
+ <BaseButton
19
+ {...rest}
20
+ className={cn(
21
+ buttonVariants({
22
+ variant,
23
+ size,
24
+ shape,
25
+ }),
26
+ className,
27
+ )}
28
+ data-variant={variant || "default"}
29
+ />
30
+ );
31
+ };
@@ -0,0 +1,44 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ export const buttonVariants = cva(
4
+ [
5
+ "inline-flex items-center justify-center gap-2",
6
+ "text-sm font-medium transition-all",
7
+ "ring-offset-background",
8
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
9
+ "disabled:pointer-events-none disabled:opacity-50",
10
+ "hover:brightness-105",
11
+ ],
12
+ {
13
+ variants: {
14
+ variant: {
15
+ default: "bg-primary text-primary-foreground",
16
+ error: "bg-error text-error-foreground",
17
+ success: "bg-success text-success-foreground",
18
+ outline:
19
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
20
+ secondary:
21
+ "bg-secondary text-secondary-foreground hover:brightness-100 hover:bg-secondary/80",
22
+ ghost:
23
+ "hover:bg-accent hover:brightness-100 hover:text-accent-foreground",
24
+ link: "text-primary underline-offset-4 hover:underline",
25
+ },
26
+ size: {
27
+ default: "h-10 px-4 py-2",
28
+ sm: "h-9 px-3",
29
+ lg: "h-11 px-8",
30
+ icon: "w-10 aspect-square",
31
+ },
32
+ shape: {
33
+ rounded: "rounded-md",
34
+ square: "rounded-none",
35
+ circle: "rounded-full",
36
+ },
37
+ },
38
+ defaultVariants: {
39
+ variant: "default",
40
+ size: "default",
41
+ shape: "rounded",
42
+ },
43
+ },
44
+ );
@@ -0,0 +1,86 @@
1
+ import { Slot, Slottable } from "@radix-ui/react-slot";
2
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
3
+ import { ComponentProps, ReactNode } from "react";
4
+ import { ClickEvent } from "@/models";
5
+ import { LoaderOverlay } from "./loader-overlay";
6
+ import { LoadingIcon } from "./loading-icon";
7
+
8
+ interface Props extends ComponentProps<"button"> {
9
+ asChild?: boolean;
10
+ loading?: boolean;
11
+ showLoader?: boolean;
12
+ icon?: ReactNode;
13
+ iconPosition?: "start" | "end";
14
+ loaderReplace?: true;
15
+ }
16
+
17
+ const baseStyles = {
18
+ display: "inline-flex",
19
+ alignItems: "center",
20
+ gap: "0.5rem",
21
+ };
22
+
23
+ export const BaseButton = ({
24
+ asChild = false,
25
+ onClick,
26
+ loading: prop,
27
+ showLoader = true,
28
+ icon,
29
+ iconPosition = "start",
30
+ loaderReplace,
31
+ style,
32
+ ...props
33
+ }: Props) => {
34
+ const Comp = asChild ? Slot : "button";
35
+ const [loading = false, setLoading] = useControllableState<boolean>({
36
+ prop,
37
+ defaultProp: false,
38
+ });
39
+
40
+ const handleClick = async (e: ClickEvent) => {
41
+ if (!onClick || loading) return;
42
+
43
+ const onClickResult = onClick(e) as unknown;
44
+
45
+ if (onClickResult instanceof Promise) {
46
+ showLoader && setLoading(true);
47
+ await onClickResult.finally(() => setLoading(false));
48
+ }
49
+ };
50
+
51
+ // TODO: Refactorizar lógica de renderización de iconos
52
+ const renderIconWithLoader = (position: "start" | "end") => {
53
+ if (position !== iconPosition) return null;
54
+ const isLoading = loaderReplace ? false : loading;
55
+
56
+ return (
57
+ <>
58
+ <LoadingIcon loading={isLoading && !icon} animate={true} />
59
+ {icon && <LoadingIcon loading={isLoading} animate={false} />}
60
+ {icon && !isLoading && icon}
61
+ </>
62
+ );
63
+ };
64
+
65
+ const isLoading = loading && showLoader;
66
+ const isReplaceMode = loaderReplace && isLoading;
67
+
68
+ return (
69
+ <Comp
70
+ type="button"
71
+ data-slot="button"
72
+ {...props}
73
+ onClick={handleClick}
74
+ style={{
75
+ ...baseStyles,
76
+ position: isReplaceMode ? "relative" : undefined,
77
+ ...style,
78
+ }}
79
+ >
80
+ {renderIconWithLoader("start")}
81
+ <Slottable>{props.children}</Slottable>
82
+ {renderIconWithLoader("end")}
83
+ {isReplaceMode && <LoaderOverlay loading={true} />}
84
+ </Comp>
85
+ );
86
+ };
@@ -0,0 +1,21 @@
1
+ import { LoadingIcon } from "./loading-icon";
2
+
3
+ interface LoaderOverlayProps {
4
+ loading: boolean;
5
+ }
6
+
7
+ export const LoaderOverlay = ({ loading }: LoaderOverlayProps) => (
8
+ <span
9
+ style={{
10
+ position: "absolute",
11
+ inset: 0,
12
+ display: "flex",
13
+ alignItems: "center",
14
+ justifyContent: "center",
15
+ background: "inherit",
16
+ borderRadius: "inherit",
17
+ }}
18
+ >
19
+ <LoadingIcon loading={loading} animate={false} />
20
+ </span>
21
+ );
@@ -0,0 +1,47 @@
1
+ import { LoaderCircle, LucideProps } from "lucide-react";
2
+ import { useRef } from "react";
3
+ import { CSSTransition } from "react-transition-group";
4
+ import { cn } from "../../../lib";
5
+
6
+ interface LoadingIconProps extends LucideProps {
7
+ loading?: boolean;
8
+ animate?: boolean;
9
+ }
10
+
11
+ const LoadingIcon = ({
12
+ loading,
13
+ animate = true,
14
+ ...props
15
+ }: LoadingIconProps) => {
16
+ const nodeRef = useRef(null);
17
+
18
+ if (!animate) {
19
+ return loading ? (
20
+ <LoaderCircle
21
+ {...props}
22
+ data-testid="btn-loader"
23
+ className={cn("animate-spin", props.className)}
24
+ />
25
+ ) : null;
26
+ }
27
+
28
+ return (
29
+ <CSSTransition
30
+ unmountOnExit
31
+ mountOnEnter
32
+ timeout={200}
33
+ in={loading}
34
+ classNames="btn-loader"
35
+ nodeRef={nodeRef}
36
+ >
37
+ <LoaderCircle
38
+ {...props}
39
+ ref={nodeRef}
40
+ data-testid="btn-loader"
41
+ className={cn("animate-spin", props.className)}
42
+ />
43
+ </CSSTransition>
44
+ );
45
+ };
46
+
47
+ export { LoadingIcon, type LoadingIconProps };
@@ -0,0 +1,3 @@
1
+ export * from "./button";
2
+ export * from "./button.variants";
3
+ export * from "./components/base-button";