@boxcustodia/library 2.0.0-alpha.11 → 2.0.0-alpha.13

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 +38 -38
  2. package/dist/index.css +1 -1
  3. package/dist/index.d.ts +28 -29
  4. package/dist/index.es.js +6804 -6792
  5. package/package.json +4 -3
  6. package/src/__doc__/Changelog.mdx +6 -0
  7. package/src/__doc__/Components.mdx +73 -0
  8. package/src/__doc__/Examples.tsx +69 -0
  9. package/src/__doc__/Icons.mdx +41 -0
  10. package/src/__doc__/Intro.mdx +138 -0
  11. package/src/__doc__/MCP.mdx +71 -0
  12. package/src/__doc__/Migration.mdx +451 -0
  13. package/src/__doc__/Theme.mdx +132 -0
  14. package/src/__doc__/Types.mdx +252 -0
  15. package/src/components/alert/alert.stories.tsx +142 -0
  16. package/src/components/alert/alert.tsx +109 -0
  17. package/src/components/alert/index.ts +7 -0
  18. package/src/components/alert-dialog/alert-dialog.stories.tsx +173 -0
  19. package/src/components/alert-dialog/alert-dialog.test.tsx +49 -0
  20. package/src/components/alert-dialog/alert-dialog.tsx +265 -0
  21. package/src/components/alert-dialog/index.ts +1 -0
  22. package/src/components/auto-complete/auto-complete-primitives.tsx +155 -0
  23. package/src/components/auto-complete/auto-complete.stories.tsx +241 -0
  24. package/src/components/auto-complete/auto-complete.tsx +82 -0
  25. package/src/components/auto-complete/index.ts +2 -0
  26. package/src/components/avatar/avatar.stories.tsx +84 -0
  27. package/src/components/avatar/avatar.test.tsx +61 -0
  28. package/src/components/avatar/avatar.tsx +104 -0
  29. package/src/components/avatar/index.ts +1 -0
  30. package/src/components/background-image/background-image.stories.tsx +21 -0
  31. package/src/components/background-image/background-image.test.tsx +29 -0
  32. package/src/components/background-image/background-image.tsx +23 -0
  33. package/src/components/background-image/index.ts +1 -0
  34. package/src/components/button/button.stories.tsx +396 -0
  35. package/src/components/button/button.test.tsx +58 -0
  36. package/src/components/button/button.tsx +31 -0
  37. package/src/components/button/button.variants.ts +44 -0
  38. package/src/components/button/components/base-button.tsx +86 -0
  39. package/src/components/button/components/loader-overlay.tsx +21 -0
  40. package/src/components/button/components/loading-icon.tsx +47 -0
  41. package/src/components/button/index.ts +3 -0
  42. package/src/components/calendar/calendar.model.ts +86 -0
  43. package/src/components/calendar/calendar.stories.tsx +155 -0
  44. package/src/components/calendar/calendar.test.tsx +12 -0
  45. package/src/components/calendar/calendar.tsx +185 -0
  46. package/src/components/calendar/components/calendar-navigation.tsx +141 -0
  47. package/src/components/calendar/components/day.tsx +61 -0
  48. package/src/components/calendar/components/decade-view.tsx +45 -0
  49. package/src/components/calendar/components/index.ts +6 -0
  50. package/src/components/calendar/components/month-view.tsx +58 -0
  51. package/src/components/calendar/components/week-days.tsx +27 -0
  52. package/src/components/calendar/components/year-view.tsx +29 -0
  53. package/src/components/calendar/hooks/index.ts +4 -0
  54. package/src/components/calendar/hooks/use-calendar-navigation.ts +79 -0
  55. package/src/components/calendar/hooks/use-calendar.ts +90 -0
  56. package/src/components/calendar/hooks/use-multiple-calendar.ts +34 -0
  57. package/src/components/calendar/hooks/use-range-calendar.ts +91 -0
  58. package/src/components/calendar/hooks/use-single-calendar.ts +18 -0
  59. package/src/components/calendar/index.ts +1 -0
  60. package/src/components/calendar/utils/typeguards.ts +7 -0
  61. package/src/components/card/card.stories.tsx +116 -0
  62. package/src/components/card/card.tsx +74 -0
  63. package/src/components/card/index.ts +1 -0
  64. package/src/components/center/center.stories.tsx +81 -0
  65. package/src/components/center/center.tsx +24 -0
  66. package/src/components/center/index.ts +1 -0
  67. package/src/components/checkbox/checkbox.stories.tsx +307 -0
  68. package/src/components/checkbox/checkbox.tsx +273 -0
  69. package/src/components/checkbox/index.ts +1 -0
  70. package/src/components/checkbox-group/checkbox-group.stories.tsx +104 -0
  71. package/src/components/checkbox-group/checkbox-group.tsx +16 -0
  72. package/src/components/checkbox-group/index.ts +1 -0
  73. package/src/components/combobox/combobox.stories.tsx +339 -0
  74. package/src/components/combobox/combobox.tsx +898 -0
  75. package/src/components/combobox/index.ts +1 -0
  76. package/src/components/date-picker/date-input.stories.tsx +158 -0
  77. package/src/components/date-picker/date-input.tsx +163 -0
  78. package/src/components/date-picker/date-picker.model.ts +90 -0
  79. package/src/components/date-picker/date-picker.stories.tsx +200 -0
  80. package/src/components/date-picker/date-picker.test.tsx +23 -0
  81. package/src/components/date-picker/date-picker.tsx +298 -0
  82. package/src/components/date-picker/date-picker.utils.ts +260 -0
  83. package/src/components/date-picker/index.ts +3 -0
  84. package/src/components/date-picker/use-date-input-popover.ts +48 -0
  85. package/src/components/date-picker/use-date-input.ts +125 -0
  86. package/src/components/dialog/dialog.stories.tsx +171 -0
  87. package/src/components/dialog/dialog.test.tsx +68 -0
  88. package/src/components/dialog/dialog.tsx +277 -0
  89. package/src/components/dialog/index.ts +1 -0
  90. package/src/components/divider/divider.stories.tsx +139 -0
  91. package/src/components/divider/divider.test.tsx +22 -0
  92. package/src/components/divider/divider.tsx +23 -0
  93. package/src/components/divider/index.ts +1 -0
  94. package/src/components/dropzone/dropzone.stories.tsx +210 -0
  95. package/src/components/dropzone/dropzone.tsx +154 -0
  96. package/src/components/dropzone/file-types.ts +64 -0
  97. package/src/components/dropzone/index.ts +3 -0
  98. package/src/components/dropzone/upload-primitives.tsx +310 -0
  99. package/src/components/dropzone/use-dropzone.ts +122 -0
  100. package/src/components/empty-state/empty-state.stories.tsx +56 -0
  101. package/src/components/empty-state/empty-state.tsx +39 -0
  102. package/src/components/empty-state/index.ts +1 -0
  103. package/src/components/field/field.stories.tsx +223 -0
  104. package/src/components/field/field.tsx +229 -0
  105. package/src/components/field/index.ts +1 -0
  106. package/src/components/form/form.stories.tsx +594 -0
  107. package/src/components/form/form.tsx +30 -0
  108. package/src/components/form/index.ts +1 -0
  109. package/src/components/heading/heading.stories.tsx +74 -0
  110. package/src/components/heading/heading.tsx +28 -0
  111. package/src/components/heading/heading.variants.ts +27 -0
  112. package/src/components/heading/index.ts +1 -0
  113. package/src/components/index.ts +46 -0
  114. package/src/components/input/index.ts +1 -0
  115. package/src/components/input/input.stories.tsx +104 -0
  116. package/src/components/input/input.tsx +75 -0
  117. package/src/components/kbd/index.ts +1 -0
  118. package/src/components/kbd/kbd.stories.tsx +40 -0
  119. package/src/components/kbd/kbd.tsx +31 -0
  120. package/src/components/kbd/kbd.variants.ts +26 -0
  121. package/src/components/label/index.ts +1 -0
  122. package/src/components/label/label.stories.tsx +68 -0
  123. package/src/components/label/label.test.tsx +61 -0
  124. package/src/components/label/label.tsx +62 -0
  125. package/src/components/loader/index.ts +1 -0
  126. package/src/components/loader/loader.stories.tsx +60 -0
  127. package/src/components/loader/loader.test.tsx +26 -0
  128. package/src/components/loader/loader.tsx +60 -0
  129. package/src/components/menu/index.ts +2 -0
  130. package/src/components/menu/menu-primitives.tsx +248 -0
  131. package/src/components/menu/menu.stories.tsx +203 -0
  132. package/src/components/menu/menu.tsx +100 -0
  133. package/src/components/menu/util/render-menu-item.tsx +54 -0
  134. package/src/components/multi-select/hooks/use-multi-select.ts +66 -0
  135. package/src/components/multi-select/index.ts +1 -0
  136. package/src/components/multi-select/multi-select.stories.tsx +294 -0
  137. package/src/components/multi-select/multi-select.tsx +300 -0
  138. package/src/components/multi-select/multi-select.variants.ts +22 -0
  139. package/src/components/number-input/index.ts +1 -0
  140. package/src/components/number-input/number-input.stories.tsx +209 -0
  141. package/src/components/number-input/number-input.test.tsx +87 -0
  142. package/src/components/number-input/number-input.tsx +232 -0
  143. package/src/components/pagination/components/pagination-option.tsx +27 -0
  144. package/src/components/pagination/index.ts +1 -0
  145. package/src/components/pagination/pagination.stories.tsx +80 -0
  146. package/src/components/pagination/pagination.test.tsx +76 -0
  147. package/src/components/pagination/pagination.tsx +102 -0
  148. package/src/components/password/index.ts +1 -0
  149. package/src/components/password/password.stories.tsx +104 -0
  150. package/src/components/password/password.tsx +75 -0
  151. package/src/components/popover/index.ts +1 -0
  152. package/src/components/popover/popover.stories.tsx +213 -0
  153. package/src/components/popover/popover.tsx +203 -0
  154. package/src/components/progress/index.ts +1 -0
  155. package/src/components/progress/progress.stories.tsx +124 -0
  156. package/src/components/progress/progress.test.tsx +25 -0
  157. package/src/components/progress/progress.tsx +124 -0
  158. package/src/components/scroll-area/index.ts +1 -0
  159. package/src/components/scroll-area/scroll-area.stories.tsx +166 -0
  160. package/src/components/scroll-area/scroll-area.tsx +64 -0
  161. package/src/components/select/index.ts +1 -0
  162. package/src/components/select/select.stories.tsx +253 -0
  163. package/src/components/select/select.tsx +430 -0
  164. package/src/components/show/index.ts +1 -0
  165. package/src/components/show/show.stories.tsx +197 -0
  166. package/src/components/show/show.test.tsx +41 -0
  167. package/src/components/show/show.tsx +16 -0
  168. package/src/components/skeleton/index.ts +1 -0
  169. package/src/components/skeleton/skeleton.stories.tsx +36 -0
  170. package/src/components/skeleton/skeleton.test.tsx +14 -0
  171. package/src/components/skeleton/skeleton.tsx +15 -0
  172. package/src/components/stack/index.ts +1 -0
  173. package/src/components/stack/stack.stories.tsx +194 -0
  174. package/src/components/stack/stack.tsx +52 -0
  175. package/src/components/stepper/Stepper.tsx +190 -0
  176. package/src/components/stepper/context/stepper-context.tsx +11 -0
  177. package/src/components/stepper/index.ts +1 -0
  178. package/src/components/stepper/stepper.stories.tsx +130 -0
  179. package/src/components/stepper/stepper.test.tsx +91 -0
  180. package/src/components/switch/index.ts +1 -0
  181. package/src/components/switch/switch.stories.tsx +122 -0
  182. package/src/components/switch/switch.test.tsx +30 -0
  183. package/src/components/switch/switch.tsx +86 -0
  184. package/src/components/table/index.ts +3 -0
  185. package/src/components/table/table-primitives.tsx +122 -0
  186. package/src/components/table/table.model.ts +20 -0
  187. package/src/components/table/table.stories.tsx +169 -0
  188. package/src/components/table/table.test.tsx +91 -0
  189. package/src/components/table/table.tsx +109 -0
  190. package/src/components/table-pagination/index.ts +2 -0
  191. package/src/components/table-pagination/table-pagination.model.ts +2 -0
  192. package/src/components/table-pagination/table-pagination.stories.tsx +23 -0
  193. package/src/components/table-pagination/table-pagination.test.tsx +32 -0
  194. package/src/components/table-pagination/table-pagination.tsx +108 -0
  195. package/src/components/tabs/context/tabs-context.tsx +14 -0
  196. package/src/components/tabs/index.ts +1 -0
  197. package/src/components/tabs/tabs.stories.tsx +182 -0
  198. package/src/components/tabs/tabs.test.tsx +61 -0
  199. package/src/components/tabs/tabs.tsx +175 -0
  200. package/src/components/tag/index.ts +2 -0
  201. package/src/components/tag/tag.stories.tsx +170 -0
  202. package/src/components/tag/tag.test.tsx +18 -0
  203. package/src/components/tag/tag.tsx +99 -0
  204. package/src/components/tag/tag.variants.ts +31 -0
  205. package/src/components/textarea/index.ts +1 -0
  206. package/src/components/textarea/textarea.stories.tsx +73 -0
  207. package/src/components/textarea/textarea.tsx +105 -0
  208. package/src/components/timeline/index.ts +1 -0
  209. package/src/components/timeline/timeline-status.ts +5 -0
  210. package/src/components/timeline/timeline.stories.tsx +84 -0
  211. package/src/components/timeline/timeline.tsx +147 -0
  212. package/src/components/toast/index.ts +1 -0
  213. package/src/components/toast/toast.stories.tsx +392 -0
  214. package/src/components/toast/toast.test.tsx +50 -0
  215. package/src/components/toast/toast.tsx +411 -0
  216. package/src/components/tooltip/index.ts +1 -0
  217. package/src/components/tooltip/tooltip.stories.tsx +226 -0
  218. package/src/components/tooltip/tooltip.test.tsx +46 -0
  219. package/src/components/tooltip/tooltip.tsx +171 -0
  220. package/src/components/tree/hooks/use-controllable-tree-state.ts +80 -0
  221. package/src/components/tree/index.ts +2 -0
  222. package/src/components/tree/tree-primitives.tsx +126 -0
  223. package/src/components/tree/tree.stories.tsx +468 -0
  224. package/src/components/tree/tree.tsx +42 -0
  225. package/src/hooks/index.ts +26 -0
  226. package/src/hooks/useArray/__doc__/useArray.stories.tsx +100 -0
  227. package/src/hooks/useArray/__test__/useArray.test.tsx +88 -0
  228. package/src/hooks/useArray/index.ts +1 -0
  229. package/src/hooks/useArray/useArray.ts +76 -0
  230. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +149 -0
  231. package/src/hooks/useAsync/__test__/useAsync.test.tsx +68 -0
  232. package/src/hooks/useAsync/index.ts +1 -0
  233. package/src/hooks/useAsync/useAsync.ts +58 -0
  234. package/src/hooks/useClickOutside/__doc__/useClickOutside.stories.tsx +40 -0
  235. package/src/hooks/useClickOutside/__test__/useClickOutside.test.tsx +33 -0
  236. package/src/hooks/useClickOutside/index.ts +1 -0
  237. package/src/hooks/useClickOutside/useClickOutside.ts +26 -0
  238. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +45 -0
  239. package/src/hooks/useClipboard/__test__/useClipboard.test.tsx +19 -0
  240. package/src/hooks/useClipboard/index.ts +1 -0
  241. package/src/hooks/useClipboard/useClipboard.tsx +28 -0
  242. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +84 -0
  243. package/src/hooks/useDebounceCallback/index.ts +1 -0
  244. package/src/hooks/useDebounceCallback/useDebouncedCallback.ts +23 -0
  245. package/src/hooks/useDebounceValue/__doc__/useDebouncedValue.stories.tsx +75 -0
  246. package/src/hooks/useDebounceValue/index.ts +1 -0
  247. package/src/hooks/useDebounceValue/useDebouncedValue.ts +17 -0
  248. package/src/hooks/useDisclosure/__doc__/useDisclosure.stories.tsx +39 -0
  249. package/src/hooks/useDisclosure/__test__/useDisclosure.test.ts +43 -0
  250. package/src/hooks/useDisclosure/index.ts +1 -0
  251. package/src/hooks/useDisclosure/useDisclosure.ts +37 -0
  252. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +26 -0
  253. package/src/hooks/useDocumentTitle/index.ts +1 -0
  254. package/src/hooks/useDocumentTitle/useDocumentTitle.tsx +11 -0
  255. package/src/hooks/useEventListener/__doc__/useEventListener.stories.tsx +28 -0
  256. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +26 -0
  257. package/src/hooks/useEventListener/index.ts +1 -0
  258. package/src/hooks/useEventListener/useEventListener.ts +25 -0
  259. package/src/hooks/useFocusTrap/__doc__/useFocusTrap.stories.tsx +37 -0
  260. package/src/hooks/useFocusTrap/index.ts +1 -0
  261. package/src/hooks/useFocusTrap/scopeTab.ts +38 -0
  262. package/src/hooks/useFocusTrap/tabbable.ts +70 -0
  263. package/src/hooks/useFocusTrap/useFocusTrap.ts +78 -0
  264. package/src/hooks/useHotkey/__docs__/useHotkey.stories.tsx +116 -0
  265. package/src/hooks/useHotkey/__test__/useHotkey.test.tsx +105 -0
  266. package/src/hooks/useHotkey/__utils__/create-hotkey-listener.ts +25 -0
  267. package/src/hooks/useHotkey/__utils__/index.ts +3 -0
  268. package/src/hooks/useHotkey/__utils__/is-input-field.ts +14 -0
  269. package/src/hooks/useHotkey/__utils__/match-key-modifiers.ts +25 -0
  270. package/src/hooks/useHotkey/index.ts +1 -0
  271. package/src/hooks/useHotkey/useHotkey.ts +34 -0
  272. package/src/hooks/useHover/__doc__/useHover.stories.tsx +41 -0
  273. package/src/hooks/useHover/__test__/useHover.test.tsx +45 -0
  274. package/src/hooks/useHover/index.ts +1 -0
  275. package/src/hooks/useHover/useHover.tsx +40 -0
  276. package/src/hooks/useIsVisible/__doc__/useIsVisible.stories.tsx +60 -0
  277. package/src/hooks/useIsVisible/index.ts +1 -0
  278. package/src/hooks/useIsVisible/useIsVisible.tsx +50 -0
  279. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +86 -0
  280. package/src/hooks/useLocalStorage/__test__/useLocalStorage.test.ts +85 -0
  281. package/src/hooks/useLocalStorage/index.ts +1 -0
  282. package/src/hooks/useLocalStorage/useLocalStorage.ts +57 -0
  283. package/src/hooks/useMediaQuery/__doc__/useMediaQuery.stories.tsx +39 -0
  284. package/src/hooks/useMediaQuery/index.ts +1 -0
  285. package/src/hooks/useMediaQuery/useMediaQuery.ts +22 -0
  286. package/src/hooks/useMemoizedFn/index.ts +1 -0
  287. package/src/hooks/useMemoizedFn/useMemoizedFn.ts +32 -0
  288. package/src/hooks/useMutation/__doc__/useMutation.stories.tsx +111 -0
  289. package/src/hooks/useMutation/__test__/useMutation.test.tsx +83 -0
  290. package/src/hooks/useMutation/index.ts +1 -0
  291. package/src/hooks/useMutation/useMutation.tsx +60 -0
  292. package/src/hooks/useObject/__doc__/useObject.stories.tsx +119 -0
  293. package/src/hooks/useObject/__test__/useObject.test.tsx +87 -0
  294. package/src/hooks/useObject/index.ts +1 -0
  295. package/src/hooks/useObject/useObject.tsx +48 -0
  296. package/src/hooks/usePagination/__doc__/usePagination.stories.tsx +72 -0
  297. package/src/hooks/usePagination/__test__/usePagination.test.tsx +98 -0
  298. package/src/hooks/usePagination/index.ts +2 -0
  299. package/src/hooks/usePagination/usePagination.tsx +74 -0
  300. package/src/hooks/usePortal/__doc__/usePortal.stories.tsx +19 -0
  301. package/src/hooks/usePortal/__test__/usePortal.test.tsx +20 -0
  302. package/src/hooks/usePortal/index.ts +1 -0
  303. package/src/hooks/usePortal/usePortal.ts +40 -0
  304. package/src/hooks/usePreventCloseWindow/__doc__/usePreventCloseWindow.stories.tsx +32 -0
  305. package/src/hooks/usePreventCloseWindow/index.ts +1 -0
  306. package/src/hooks/usePreventCloseWindow/usePreventCloseWindow.ts +33 -0
  307. package/src/hooks/useRangePagination/__test__/useRangePagination.test.tsx +63 -0
  308. package/src/hooks/useRangePagination/index.ts +2 -0
  309. package/src/hooks/useRangePagination/useRangePagination.tsx +72 -0
  310. package/src/hooks/useSelection/__doc__/useSelection.stories.tsx +140 -0
  311. package/src/hooks/useSelection/__test__/useSelection.test.tsx +57 -0
  312. package/src/hooks/useSelection/index.ts +1 -0
  313. package/src/hooks/useSelection/useSelection.ts +121 -0
  314. package/src/hooks/useStep/__doc__/useStep.stories.tsx +98 -0
  315. package/src/hooks/useStep/__test__/useStep.test.ts +51 -0
  316. package/src/hooks/useStep/index.ts +1 -0
  317. package/src/hooks/useStep/useStep.ts +57 -0
  318. package/src/hooks/useToggle/__doc__/useToggle.stories.tsx +25 -0
  319. package/src/hooks/useToggle/__test__/useToggle.test.tsx +43 -0
  320. package/src/hooks/useToggle/index.ts +1 -0
  321. package/src/hooks/useToggle/useToggle.ts +16 -0
  322. package/src/index.ts +6 -0
  323. package/src/lib/cn.ts +8 -0
  324. package/src/lib/index.ts +1 -0
  325. package/src/models/Generic.model.ts +67 -0
  326. package/src/models/index.ts +1 -0
  327. package/src/providers/index.ts +2 -0
  328. package/src/providers/library-provider.tsx +44 -0
  329. package/src/providers/theme/ThemeProvider.tsx +25 -0
  330. package/src/providers/theme/index.ts +3 -0
  331. package/src/providers/theme/types.ts +11 -0
  332. package/src/providers/theme/useThemeProps.ts +25 -0
  333. package/src/stores/theme.store.ts +31 -0
  334. package/src/styles/components.css +4 -0
  335. package/src/styles/index.css +2 -0
  336. package/src/styles/library.css +2 -0
  337. package/src/styles/theme.css +232 -0
  338. package/src/utils/dates/parseDateRange.utility.ts +39 -0
  339. package/src/utils/form.tsx +91 -0
  340. package/src/utils/functions/createSafeContext.ts +17 -0
  341. package/src/utils/functions/ensureReactElement.tsx +30 -0
  342. package/src/utils/functions/getFormData.ts +19 -0
  343. package/src/utils/functions/index.ts +4 -0
  344. package/src/utils/functions/mergeRefs.ts +18 -0
  345. package/src/utils/index.ts +3 -0
  346. package/src/utils/strings/extractInitials.utility.ts +10 -0
  347. package/src/utils/strings/index.ts +1 -0
  348. package/src/utils/tests/click.ts +3 -0
  349. package/src/utils/tests/index.ts +2 -0
  350. package/src/utils/tests/keyboard.ts +21 -0
  351. package/src/utils/tests/type.ts +6 -0
  352. package/dist/components.css +0 -2
@@ -0,0 +1,209 @@
1
+ import { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { useState } from "react";
3
+ import { Field } from "../field";
4
+ import { Label } from "../label";
5
+ import {
6
+ NumberInput,
7
+ NumberInputCursorIcon,
8
+ NumberInputDecrement,
9
+ NumberInputGroup,
10
+ NumberInputIncrement,
11
+ NumberInputInput,
12
+ NumberInputRoot,
13
+ NumberInputScrubArea,
14
+ NumberInputScrubAreaCursor,
15
+ } from "./number-input";
16
+
17
+ // TODO: Mostrar borde rojo si es invalid
18
+ /**
19
+ * Input numérico construido sobre Base UI NumberField.
20
+ * Soporta incremento/decremento por botones, teclado y scrub (drag horizontal).
21
+ * Usá `hideControls` para renderizar sólo el input sin botones.
22
+ * Para composición avanzada, usá los primitivos `NumberInputRoot`, `NumberInputGroup`, etc.
23
+ */
24
+ const meta: Meta<typeof NumberInput> = {
25
+ title: "Components/NumberInput",
26
+ component: NumberInput,
27
+ parameters: { layout: "centered" },
28
+ args: {
29
+ name: "quantity",
30
+ defaultValue: 0,
31
+ },
32
+ };
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<typeof NumberInput>;
37
+
38
+ export const Default: Story = {};
39
+
40
+ export const Disabled: Story = {
41
+ args: {
42
+ disabled: true,
43
+ defaultValue: 42,
44
+ },
45
+ };
46
+
47
+ /**
48
+ * `hideControls` oculta los botones +/- y renderiza sólo el input.
49
+ * Útil cuando el espacio es limitado o los controles resultan innecesarios.
50
+ */
51
+ export const HideControls: Story = {
52
+ args: {
53
+ hideControls: true,
54
+ },
55
+ };
56
+
57
+ /**
58
+ * El `ScrubArea` permite cambiar el valor arrastrando horizontalmente sobre la etiqueta.
59
+ * Requiere composición manual con los primitivos exportados.
60
+ */
61
+ export const Scrub: Story = {
62
+ render: () => {
63
+ const [value, setValue] = useState(50);
64
+ return (
65
+ <NumberInputRoot
66
+ value={value}
67
+ onValueChange={(v) => v !== null && setValue(v)}
68
+ name="opacity"
69
+ allowWheelScrub
70
+ >
71
+ <div className="flex flex-col gap-2">
72
+ <NumberInputScrubArea>
73
+ <Label className="cursor-ew-resize" htmlFor="scrub-input">
74
+ Opacity
75
+ </Label>
76
+ <NumberInputScrubAreaCursor>
77
+ <NumberInputCursorIcon />
78
+ </NumberInputScrubAreaCursor>
79
+ </NumberInputScrubArea>
80
+ <NumberInputGroup>
81
+ <NumberInputDecrement />
82
+ <NumberInputInput id="scrub-input" />
83
+ <NumberInputIncrement />
84
+ </NumberInputGroup>
85
+ </div>
86
+ </NumberInputRoot>
87
+ );
88
+ },
89
+ };
90
+
91
+ /**
92
+ * Usá `value` + `onChange` para controlar el valor externamente.
93
+ * `onChange` recibe `number | null` — `null` cuando el campo está vacío o contiene un valor inválido.
94
+ */
95
+ export const Controlled: Story = {
96
+ render: () => {
97
+ const [value, setValue] = useState<number | null>(0);
98
+ return (
99
+ <div className="flex w-48 flex-col gap-2">
100
+ <NumberInput name="quantity" value={value} onChange={setValue} />
101
+ <p className="text-muted-foreground text-xs">
102
+ Valor: {value ?? "vacío"}
103
+ </p>
104
+ </div>
105
+ );
106
+ },
107
+ };
108
+
109
+ /**
110
+ * Usá `min` y `max` para restringir el rango de valores.
111
+ * Los botones se deshabilitan automáticamente al alcanzar los límites.
112
+ */
113
+ export const Range: Story = {
114
+ render: () => {
115
+ const [value, setValue] = useState(50);
116
+ return (
117
+ <div className="flex w-64 flex-col gap-2">
118
+ <NumberInput
119
+ name="progress"
120
+ value={value}
121
+ onChange={(v) => v !== null && setValue(v)}
122
+ min={0}
123
+ max={100}
124
+ step={10}
125
+ />
126
+ <p className="text-muted-foreground text-xs">
127
+ Valor: {value} (0 – 100, paso 10)
128
+ </p>
129
+ </div>
130
+ );
131
+ },
132
+ };
133
+
134
+ /**
135
+ * Pasá un objeto `format` compatible con `Intl.NumberFormat` para formatear el valor mostrado.
136
+ * El formato se aplica al mostrar y no interfiere con la edición ni con el valor interno.
137
+ *
138
+ * Para `style: "percent"`, el valor interno es decimal (0–1) y se muestra multiplicado por 100.
139
+ * Al tipear manualmente, incluí el símbolo "%" (ej: "80%") para que el parser lo interprete correctamente.
140
+ *
141
+ * Para campos tipo DNI, omitir `min`/`max` evita que el campo auto-corrija valores parciales mientras
142
+ * se escribe. La validación del rango se realiza en submit vía `Field` con el prop `error`.
143
+ *
144
+ * ```tsx
145
+ * <NumberInput
146
+ * name="dni"
147
+ * placeholder="Ej: 33.456.789"
148
+ * format={{ useGrouping: true, maximumFractionDigits: 0 }}
149
+ * hideControls
150
+ * />
151
+ * ```
152
+ */
153
+ export const FormattedValue: Story = {
154
+ render: () => (
155
+ <div className="flex w-64 flex-col gap-4">
156
+ <NumberInput
157
+ name="price"
158
+ defaultValue={1299}
159
+ locale="es-AR"
160
+ format={{ style: "currency", currency: "ARS" }}
161
+ />
162
+ <NumberInput
163
+ name="ratio"
164
+ defaultValue={0.75}
165
+ step={0.05}
166
+ min={0}
167
+ max={1}
168
+ format={{ style: "percent" }}
169
+ />
170
+ </div>
171
+ ),
172
+ };
173
+
174
+ /**
175
+ * Controlá la precisión y el incremento mínimo con `step`.
176
+ * `largeStep` (Page Up / Page Down) multiplica el paso por defecto.
177
+ */
178
+ export const Step: Story = {
179
+ args: {
180
+ name: "rating",
181
+ step: 0.5,
182
+ largeStep: 2,
183
+ min: 0,
184
+ max: 10,
185
+ defaultValue: 2.5,
186
+ format: { minimumFractionDigits: 1, maximumFractionDigits: 1 },
187
+ },
188
+ };
189
+
190
+ /**
191
+ * Integrá el `NumberInput` con el componente `Field` para obtener label, descripción y errores.
192
+ * Cuando `Field` tiene un `error`, el borde rojo se aplica automáticamente vía el contexto de Base UI.
193
+ * Para usarlo sin `Field`, pasá `invalid` directamente al componente.
194
+ */
195
+ export const FormIntegration: Story = {
196
+ render: () => {
197
+ return (
198
+ <div className="w-64">
199
+ <Field
200
+ label="Edad"
201
+ description="Ingresá tu edad en años."
202
+ error="El valor debe estar entre 18 y 99."
203
+ >
204
+ <NumberInput name="age" />
205
+ </Field>
206
+ </div>
207
+ );
208
+ },
209
+ };
@@ -0,0 +1,87 @@
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { createRef } from "react";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { NumberInput } from "../../components";
5
+
6
+ describe("NumberInput component", () => {
7
+ it("se renderiza correctamente", () => {
8
+ render(<NumberInput name="cantidad" />);
9
+ expect(screen.getByRole("textbox")).toBeInTheDocument();
10
+ });
11
+
12
+ it("acepta y muestra el valor inicial correctamente", () => {
13
+ render(<NumberInput name="cantidad" value={10} />);
14
+ const input = screen.getByRole("textbox") as HTMLInputElement;
15
+ expect(input.value).toBe("10");
16
+ });
17
+
18
+ it("deshabilita el input cuando la prop disabled es true", () => {
19
+ render(<NumberInput name="cantidad" disabled />);
20
+ expect(screen.getByRole("textbox")).toBeDisabled();
21
+ });
22
+
23
+ it("acepta la prop className en el contenedor visual", () => {
24
+ const TEST_CLASS = "mi-clase-personalizada";
25
+ render(<NumberInput name="cantidad" className={TEST_CLASS} />);
26
+ expect(screen.getByRole("group")).toHaveClass(TEST_CLASS);
27
+ });
28
+
29
+ it("acepta la prop ref al input visible via inputProps", () => {
30
+ const ref = createRef<HTMLInputElement>();
31
+ render(<NumberInput name="cantidad" inputProps={{ ref }} />);
32
+ expect(ref.current).not.toBeNull();
33
+ expect(ref.current).toBeInstanceOf(HTMLInputElement);
34
+ });
35
+
36
+ it("llama a onChange cuando el valor cambia", () => {
37
+ const handleChange = vi.fn();
38
+ render(<NumberInput name="cantidad" onChange={handleChange} />);
39
+ const input = screen.getByRole("textbox");
40
+
41
+ fireEvent.input(input, { target: { value: "5" } });
42
+ expect(handleChange).toHaveBeenCalledTimes(1);
43
+ });
44
+
45
+ it("respeta el valor mínimo", () => {
46
+ render(<NumberInput name="cantidad" min={0} />);
47
+ const input = screen.getByRole("textbox") as HTMLInputElement;
48
+
49
+ fireEvent.input(input, { target: { value: "-5" } });
50
+ fireEvent.blur(input);
51
+ expect(input.value).toBe("0");
52
+ });
53
+
54
+ it("respeta el valor máximo", () => {
55
+ render(<NumberInput name="cantidad" max={100} />);
56
+ const input = screen.getByRole("textbox") as HTMLInputElement;
57
+
58
+ fireEvent.input(input, { target: { value: "150" } });
59
+ fireEvent.blur(input);
60
+ expect(input.value).toBe("100");
61
+ });
62
+
63
+ it("incrementa el valor correctamente con los botones", () => {
64
+ render(<NumberInput name="cantidad" defaultValue={5} />);
65
+ const input = screen.getByRole("textbox") as HTMLInputElement;
66
+ const incrementButton = screen.getByTestId("increment-trigger");
67
+
68
+ fireEvent.click(incrementButton);
69
+ expect(input.value).toBe("6");
70
+ });
71
+
72
+ it("decrementa el valor correctamente con los botones", () => {
73
+ render(<NumberInput name="cantidad" defaultValue={5} />);
74
+ const input = screen.getByRole("textbox") as HTMLInputElement;
75
+ const decrementButton = screen.getByTestId("decrement-trigger");
76
+
77
+ fireEvent.click(decrementButton);
78
+ expect(input.value).toBe("4");
79
+ });
80
+
81
+ it("oculta los botones cuando hideControls es true", () => {
82
+ render(<NumberInput name="cantidad" hideControls />);
83
+ expect(screen.queryByTestId("increment-trigger")).not.toBeInTheDocument();
84
+ expect(screen.queryByTestId("decrement-trigger")).not.toBeInTheDocument();
85
+ expect(screen.getByRole("textbox")).toBeInTheDocument();
86
+ });
87
+ });
@@ -0,0 +1,232 @@
1
+ "use client";
2
+
3
+ import { NumberField as NumberFieldPrimitive } from "@base-ui/react/number-field";
4
+ import {
5
+ ChevronDownIcon,
6
+ ChevronUpIcon,
7
+ MinusIcon,
8
+ PlusIcon,
9
+ } from "lucide-react";
10
+ import { type ComponentProps } from "react";
11
+ import { cn } from "../../lib";
12
+
13
+ // ── Primitives ────────────────────────────────────────────────────────────────
14
+
15
+ export function NumberInputRoot({
16
+ className,
17
+ ...props
18
+ }: NumberFieldPrimitive.Root.Props) {
19
+ return (
20
+ <NumberFieldPrimitive.Root
21
+ className={cn("w-full", className)}
22
+ data-slot="number-input"
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ export function NumberInputGroup({
29
+ className,
30
+ ...props
31
+ }: NumberFieldPrimitive.Group.Props) {
32
+ return (
33
+ <NumberFieldPrimitive.Group
34
+ className={cn(
35
+ "flex h-10 w-full items-center overflow-hidden rounded-md bg-background text-sm transition-shadow",
36
+ "outline outline-1 outline-input [outline-offset:-1px]",
37
+ "focus-within:outline-ring",
38
+ "aria-invalid:outline-error focus-within:aria-invalid:ring-error/20",
39
+ "has-aria-invalid:outline-error focus-within:has-aria-invalid:ring-error/20",
40
+ "data-[invalid]:outline-error focus-within:data-[invalid]:ring-error/20",
41
+ "data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:pointer-events-none",
42
+ "[&_svg]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
43
+ className,
44
+ )}
45
+ data-slot="number-input-group"
46
+ {...props}
47
+ />
48
+ );
49
+ }
50
+
51
+ export function NumberInputInput({
52
+ className,
53
+ ...props
54
+ }: NumberFieldPrimitive.Input.Props) {
55
+ return (
56
+ <NumberFieldPrimitive.Input
57
+ className={cn(
58
+ "min-w-0 flex-1 bg-transparent px-3 py-2 text-left tabular-nums outline-none",
59
+ "placeholder:text-muted-foreground",
60
+ className,
61
+ )}
62
+ data-slot="number-input-input"
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ export function NumberInputDecrement({
69
+ className,
70
+ children,
71
+ ...props
72
+ }: NumberFieldPrimitive.Decrement.Props) {
73
+ return (
74
+ <NumberFieldPrimitive.Decrement
75
+ className={cn(
76
+ "flex shrink-0 cursor-pointer items-center justify-center border-r border-input px-2 py-2 text-muted-foreground transition-colors",
77
+ "hover:bg-accent hover:text-foreground",
78
+ "disabled:pointer-events-none disabled:opacity-50",
79
+ className,
80
+ )}
81
+ data-slot="number-input-decrement"
82
+ {...props}
83
+ >
84
+ {children ?? <MinusIcon />}
85
+ </NumberFieldPrimitive.Decrement>
86
+ );
87
+ }
88
+
89
+ export function NumberInputIncrement({
90
+ className,
91
+ children,
92
+ ...props
93
+ }: NumberFieldPrimitive.Increment.Props) {
94
+ return (
95
+ <NumberFieldPrimitive.Increment
96
+ className={cn(
97
+ "flex shrink-0 cursor-pointer items-center justify-center border-l border-input px-2 py-2 text-muted-foreground transition-colors",
98
+ "hover:bg-accent hover:text-foreground",
99
+ "disabled:pointer-events-none disabled:opacity-50",
100
+ className,
101
+ )}
102
+ data-slot="number-input-increment"
103
+ {...props}
104
+ >
105
+ {children ?? <PlusIcon />}
106
+ </NumberFieldPrimitive.Increment>
107
+ );
108
+ }
109
+
110
+ export function NumberInputScrubArea({
111
+ className,
112
+ ...props
113
+ }: NumberFieldPrimitive.ScrubArea.Props) {
114
+ return (
115
+ <NumberFieldPrimitive.ScrubArea
116
+ className={cn("inline-flex cursor-ew-resize items-center", className)}
117
+ data-slot="number-input-scrub-area"
118
+ {...props}
119
+ />
120
+ );
121
+ }
122
+
123
+ export function NumberInputScrubAreaCursor({
124
+ className,
125
+ ...props
126
+ }: NumberFieldPrimitive.ScrubAreaCursor.Props) {
127
+ return (
128
+ <NumberFieldPrimitive.ScrubAreaCursor
129
+ className={cn("drop-shadow-[0_1px_1px_#0008] filter", className)}
130
+ {...props}
131
+ />
132
+ );
133
+ }
134
+
135
+ export function NumberInputCursorIcon(props: ComponentProps<"svg">) {
136
+ return (
137
+ <svg
138
+ aria-hidden="true"
139
+ fill="black"
140
+ height="14"
141
+ stroke="white"
142
+ viewBox="0 0 24 14"
143
+ width="26"
144
+ xmlns="http://www.w3.org/2000/svg"
145
+ {...props}
146
+ >
147
+ <path d="M19.5 5.5L6.49737 5.51844V2L1 6.9999L6.5 12L6.49737 8.5L19.5 8.5V12L25 6.9999L19.5 2V5.5Z" />
148
+ </svg>
149
+ );
150
+ }
151
+
152
+ // ── Composite ─────────────────────────────────────────────────────────────────
153
+
154
+ export interface NumberInputProps extends NumberFieldPrimitive.Root.Props {
155
+ /** Hides the increment/decrement buttons. */
156
+ hideControls?: boolean;
157
+ /** Shorthand for `onValueChange` — receives only the value, without event details. */
158
+ onChange?: (value: number | null) => void;
159
+ /** Marks the field as invalid, adding the error border to the group. */
160
+ invalid?: boolean;
161
+ /** Forwarded to the underlying input element. */
162
+ placeholder?: string;
163
+ groupProps?: NumberFieldPrimitive.Group.Props;
164
+ inputProps?: NumberFieldPrimitive.Input.Props;
165
+ decrementProps?: NumberFieldPrimitive.Decrement.Props;
166
+ incrementProps?: NumberFieldPrimitive.Increment.Props;
167
+ }
168
+
169
+ export function NumberInput({
170
+ className,
171
+ hideControls = false,
172
+ onChange,
173
+ onValueChange,
174
+ invalid,
175
+ placeholder,
176
+ groupProps,
177
+ inputProps,
178
+ decrementProps,
179
+ incrementProps,
180
+ ...props
181
+ }: NumberInputProps) {
182
+ return (
183
+ <NumberInputRoot
184
+ onValueChange={(v, d) => {
185
+ onChange?.(v);
186
+ onValueChange?.(v, d);
187
+ }}
188
+ {...props}
189
+ >
190
+ <NumberInputGroup
191
+ aria-invalid={invalid || undefined}
192
+ {...groupProps}
193
+ className={cn(className, groupProps?.className)}
194
+ >
195
+ <NumberInputInput
196
+ {...(invalid ? { "aria-invalid": true } : {})}
197
+ placeholder={placeholder}
198
+ {...inputProps}
199
+ className={cn("self-stretch", inputProps?.className)}
200
+ />
201
+ {!hideControls && (
202
+ <div className="flex flex-col self-stretch divide-y divide-input border-l border-input">
203
+ <NumberInputIncrement
204
+ data-testid="increment-trigger"
205
+ {...incrementProps}
206
+ className={cn(
207
+ "flex-1 border-l-0 py-0 [&_svg]:size-3",
208
+ incrementProps?.className,
209
+ )}
210
+ >
211
+ {incrementProps?.children ?? <ChevronUpIcon />}
212
+ </NumberInputIncrement>
213
+ <NumberInputDecrement
214
+ data-testid="decrement-trigger"
215
+ {...decrementProps}
216
+ className={cn(
217
+ "flex-1 border-r-0 py-0 [&_svg]:size-3",
218
+ decrementProps?.className,
219
+ )}
220
+ >
221
+ {decrementProps?.children ?? <ChevronDownIcon />}
222
+ </NumberInputDecrement>
223
+ </div>
224
+ )}
225
+ </NumberInputGroup>
226
+ </NumberInputRoot>
227
+ );
228
+ }
229
+
230
+ // ── Primitive escape hatch ────────────────────────────────────────────────────
231
+
232
+ export { NumberFieldPrimitive as NumberInputPrimitive };
@@ -0,0 +1,27 @@
1
+ import { ButtonHTMLAttributes } from "react";
2
+ import { DOTS } from "../../../hooks";
3
+ import { cn } from "../../../lib";
4
+
5
+ interface PaginationOptionProps
6
+ extends ButtonHTMLAttributes<HTMLButtonElement> {
7
+ isActive?: boolean;
8
+ }
9
+
10
+ const PaginationOption = ({
11
+ isActive = false,
12
+ ...props
13
+ }: PaginationOptionProps) => (
14
+ <button
15
+ {...props}
16
+ data-slot="pagination-option"
17
+ data-active={isActive}
18
+ data-dots={props.children === DOTS}
19
+ className={cn(
20
+ "transition-colors border py-2 px-4 w-[50px] h-full data-[active=false]:hover:bg-accent",
21
+ "data-[active=true]:bg-primary data-[active=true]:text-primary-foreground data-[active=true]:border-transparent data-[active=true]:hover:bg-primary/90",
22
+ props.className,
23
+ )}
24
+ />
25
+ );
26
+
27
+ export { PaginationOption, type PaginationOptionProps };
@@ -0,0 +1 @@
1
+ export * from "./pagination";
@@ -0,0 +1,80 @@
1
+ import { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { ComponentProps } from "react";
3
+ import { action } from "storybook/actions";
4
+ import { Pagination } from "../../components";
5
+ import { cn } from "../../lib";
6
+
7
+ const meta: Meta<typeof Pagination> = {
8
+ title: "Data display/Pagination",
9
+ component: Pagination,
10
+ tags: ["autodocs"],
11
+ args: {
12
+ siblingCount: 1,
13
+ totalItems: 100,
14
+ pageSize: 10,
15
+ initialCurrentPage: 1,
16
+ onChange: action("onChange"),
17
+ },
18
+ };
19
+
20
+ export default meta;
21
+ type Story = StoryObj<typeof meta>;
22
+
23
+ export const Default: Story = {};
24
+
25
+ export const DotsVariants: Story = {
26
+ argTypes: {
27
+ totalItems: { control: false },
28
+ pageSize: { control: false },
29
+ initialCurrentPage: { control: false },
30
+ },
31
+ render: (args) => {
32
+ return (
33
+ <div className="space-y-2">
34
+ <h2>No hay suficientes elementos para mostrar puntos</h2>
35
+ <Pagination
36
+ {...(args as ComponentProps<typeof Pagination>)}
37
+ pageSize={20}
38
+ />
39
+ <h2>Puntos a la derecha</h2>
40
+ <Pagination
41
+ {...(args as ComponentProps<typeof Pagination>)}
42
+ initialCurrentPage={1}
43
+ />
44
+ <h2>Puntos de ambos lados</h2>
45
+ <Pagination
46
+ {...(args as ComponentProps<typeof Pagination>)}
47
+ initialCurrentPage={5}
48
+ />
49
+ <h2>Puntos a la izquierda</h2>
50
+ <Pagination
51
+ {...(args as ComponentProps<typeof Pagination>)}
52
+ initialCurrentPage={10}
53
+ />
54
+ </div>
55
+ );
56
+ },
57
+ };
58
+
59
+ /**
60
+ * Se exponen los siguientes atributos:
61
+ *
62
+ * | Atributo | Descripción |
63
+ * | --- | --- |
64
+ * | `data-active` | si el elemento es el activo |
65
+ * | `data-dots` | si el elemento son puntos|
66
+ **/
67
+ export const Custom: Story = {
68
+ args: {
69
+ className: "gap-x-4",
70
+ optionProps: {
71
+ className: cn(
72
+ "p-0 w-9 h-9 rounded-full flex justify-center items-center",
73
+ "data-[active=true]:bg-error data-[active=true]:text-error-foreground data-[active=true]:hover:bg-error/90",
74
+ "data-[dots=true]:border-none data-[dots=true]:bg-transparent",
75
+ "[&:nth-child(-n+2)]:border-none [&:nth-child(-n+2)]:bg-background",
76
+ "[&:nth-last-child(-n+2)]:border-none [&:nth-last-child(-n+2)]:bg-background",
77
+ ),
78
+ },
79
+ },
80
+ };