@boxcustodia/library 2.0.0-alpha.11 → 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 (349) hide show
  1. package/dist/index.css +1 -1
  2. package/package.json +4 -3
  3. package/src/__doc__/Changelog.mdx +6 -0
  4. package/src/__doc__/Components.mdx +73 -0
  5. package/src/__doc__/Examples.tsx +69 -0
  6. package/src/__doc__/Icons.mdx +41 -0
  7. package/src/__doc__/Intro.mdx +138 -0
  8. package/src/__doc__/MCP.mdx +71 -0
  9. package/src/__doc__/Migration.mdx +475 -0
  10. package/src/__doc__/Theme.mdx +132 -0
  11. package/src/__doc__/Types.mdx +252 -0
  12. package/src/components/alert/alert.stories.tsx +142 -0
  13. package/src/components/alert/alert.tsx +109 -0
  14. package/src/components/alert/index.ts +7 -0
  15. package/src/components/alert-dialog/alert-dialog.stories.tsx +173 -0
  16. package/src/components/alert-dialog/alert-dialog.test.tsx +49 -0
  17. package/src/components/alert-dialog/alert-dialog.tsx +265 -0
  18. package/src/components/alert-dialog/index.ts +1 -0
  19. package/src/components/auto-complete/auto-complete-primitives.tsx +155 -0
  20. package/src/components/auto-complete/auto-complete.stories.tsx +241 -0
  21. package/src/components/auto-complete/auto-complete.tsx +82 -0
  22. package/src/components/auto-complete/index.ts +2 -0
  23. package/src/components/avatar/avatar.stories.tsx +84 -0
  24. package/src/components/avatar/avatar.test.tsx +61 -0
  25. package/src/components/avatar/avatar.tsx +104 -0
  26. package/src/components/avatar/index.ts +1 -0
  27. package/src/components/background-image/background-image.stories.tsx +21 -0
  28. package/src/components/background-image/background-image.test.tsx +29 -0
  29. package/src/components/background-image/background-image.tsx +23 -0
  30. package/src/components/background-image/index.ts +1 -0
  31. package/src/components/button/button.stories.tsx +396 -0
  32. package/src/components/button/button.test.tsx +58 -0
  33. package/src/components/button/button.tsx +31 -0
  34. package/src/components/button/button.variants.ts +44 -0
  35. package/src/components/button/components/base-button.tsx +86 -0
  36. package/src/components/button/components/loader-overlay.tsx +21 -0
  37. package/src/components/button/components/loading-icon.tsx +47 -0
  38. package/src/components/button/index.ts +3 -0
  39. package/src/components/calendar/calendar.model.ts +86 -0
  40. package/src/components/calendar/calendar.stories.tsx +155 -0
  41. package/src/components/calendar/calendar.test.tsx +12 -0
  42. package/src/components/calendar/calendar.tsx +185 -0
  43. package/src/components/calendar/components/calendar-navigation.tsx +141 -0
  44. package/src/components/calendar/components/day.tsx +61 -0
  45. package/src/components/calendar/components/decade-view.tsx +45 -0
  46. package/src/components/calendar/components/index.ts +6 -0
  47. package/src/components/calendar/components/month-view.tsx +58 -0
  48. package/src/components/calendar/components/week-days.tsx +27 -0
  49. package/src/components/calendar/components/year-view.tsx +29 -0
  50. package/src/components/calendar/hooks/index.ts +4 -0
  51. package/src/components/calendar/hooks/use-calendar-navigation.ts +79 -0
  52. package/src/components/calendar/hooks/use-calendar.ts +90 -0
  53. package/src/components/calendar/hooks/use-multiple-calendar.ts +34 -0
  54. package/src/components/calendar/hooks/use-range-calendar.ts +91 -0
  55. package/src/components/calendar/hooks/use-single-calendar.ts +18 -0
  56. package/src/components/calendar/index.ts +1 -0
  57. package/src/components/calendar/utils/typeguards.ts +7 -0
  58. package/src/components/card/card.stories.tsx +116 -0
  59. package/src/components/card/card.tsx +74 -0
  60. package/src/components/card/index.ts +1 -0
  61. package/src/components/center/center.stories.tsx +81 -0
  62. package/src/components/center/center.tsx +24 -0
  63. package/src/components/center/index.ts +1 -0
  64. package/src/components/checkbox/checkbox.stories.tsx +307 -0
  65. package/src/components/checkbox/checkbox.tsx +273 -0
  66. package/src/components/checkbox/index.ts +1 -0
  67. package/src/components/checkbox-group/checkbox-group.stories.tsx +104 -0
  68. package/src/components/checkbox-group/checkbox-group.tsx +16 -0
  69. package/src/components/checkbox-group/index.ts +1 -0
  70. package/src/components/combobox/combobox.stories.tsx +339 -0
  71. package/src/components/combobox/combobox.tsx +892 -0
  72. package/src/components/combobox/index.ts +1 -0
  73. package/src/components/date-picker/date-input.stories.tsx +158 -0
  74. package/src/components/date-picker/date-input.tsx +163 -0
  75. package/src/components/date-picker/date-picker.model.ts +90 -0
  76. package/src/components/date-picker/date-picker.stories.tsx +200 -0
  77. package/src/components/date-picker/date-picker.test.tsx +23 -0
  78. package/src/components/date-picker/date-picker.tsx +298 -0
  79. package/src/components/date-picker/date-picker.utils.ts +260 -0
  80. package/src/components/date-picker/index.ts +3 -0
  81. package/src/components/date-picker/use-date-input-popover.ts +48 -0
  82. package/src/components/date-picker/use-date-input.ts +125 -0
  83. package/src/components/dialog/dialog.stories.tsx +171 -0
  84. package/src/components/dialog/dialog.test.tsx +68 -0
  85. package/src/components/dialog/dialog.tsx +277 -0
  86. package/src/components/dialog/index.ts +1 -0
  87. package/src/components/divider/divider.stories.tsx +70 -0
  88. package/src/components/divider/divider.test.tsx +22 -0
  89. package/src/components/divider/divider.tsx +23 -0
  90. package/src/components/divider/index.ts +1 -0
  91. package/src/components/dropzone/dropzone.stories.tsx +210 -0
  92. package/src/components/dropzone/dropzone.tsx +154 -0
  93. package/src/components/dropzone/file-types.ts +64 -0
  94. package/src/components/dropzone/index.ts +3 -0
  95. package/src/components/dropzone/upload-primitives.tsx +310 -0
  96. package/src/components/dropzone/use-dropzone.ts +122 -0
  97. package/src/components/empty-state/empty-state.stories.tsx +56 -0
  98. package/src/components/empty-state/empty-state.tsx +39 -0
  99. package/src/components/empty-state/index.ts +1 -0
  100. package/src/components/field/field.stories.tsx +223 -0
  101. package/src/components/field/field.tsx +229 -0
  102. package/src/components/field/index.ts +1 -0
  103. package/src/components/form/form.stories.tsx +594 -0
  104. package/src/components/form/form.tsx +30 -0
  105. package/src/components/form/index.ts +1 -0
  106. package/src/components/heading/heading.stories.tsx +74 -0
  107. package/src/components/heading/heading.tsx +28 -0
  108. package/src/components/heading/heading.variants.ts +27 -0
  109. package/src/components/heading/index.ts +1 -0
  110. package/src/components/index.ts +46 -0
  111. package/src/components/input/index.ts +1 -0
  112. package/src/components/input/input.stories.tsx +104 -0
  113. package/src/components/input/input.tsx +75 -0
  114. package/src/components/kbd/index.ts +1 -0
  115. package/src/components/kbd/kbd.stories.tsx +40 -0
  116. package/src/components/kbd/kbd.tsx +31 -0
  117. package/src/components/kbd/kbd.variants.ts +26 -0
  118. package/src/components/label/index.ts +1 -0
  119. package/src/components/label/label.stories.tsx +68 -0
  120. package/src/components/label/label.test.tsx +61 -0
  121. package/src/components/label/label.tsx +62 -0
  122. package/src/components/loader/index.ts +1 -0
  123. package/src/components/loader/loader.stories.tsx +60 -0
  124. package/src/components/loader/loader.test.tsx +26 -0
  125. package/src/components/loader/loader.tsx +60 -0
  126. package/src/components/menu/index.ts +2 -0
  127. package/src/components/menu/menu-primitives.tsx +248 -0
  128. package/src/components/menu/menu.stories.tsx +203 -0
  129. package/src/components/menu/menu.tsx +100 -0
  130. package/src/components/menu/util/render-menu-item.tsx +54 -0
  131. package/src/components/multi-select/hooks/use-multi-select.ts +66 -0
  132. package/src/components/multi-select/index.ts +1 -0
  133. package/src/components/multi-select/multi-select.stories.tsx +294 -0
  134. package/src/components/multi-select/multi-select.tsx +300 -0
  135. package/src/components/multi-select/multi-select.variants.ts +22 -0
  136. package/src/components/number-input/index.ts +1 -0
  137. package/src/components/number-input/number-input.stories.tsx +209 -0
  138. package/src/components/number-input/number-input.test.tsx +87 -0
  139. package/src/components/number-input/number-input.tsx +230 -0
  140. package/src/components/pagination/components/pagination-option.tsx +27 -0
  141. package/src/components/pagination/index.ts +1 -0
  142. package/src/components/pagination/pagination.stories.tsx +80 -0
  143. package/src/components/pagination/pagination.test.tsx +76 -0
  144. package/src/components/pagination/pagination.tsx +102 -0
  145. package/src/components/password/index.ts +1 -0
  146. package/src/components/password/password.stories.tsx +104 -0
  147. package/src/components/password/password.tsx +71 -0
  148. package/src/components/popover/index.ts +1 -0
  149. package/src/components/popover/popover.stories.tsx +213 -0
  150. package/src/components/popover/popover.tsx +203 -0
  151. package/src/components/progress/index.ts +1 -0
  152. package/src/components/progress/progress.stories.tsx +124 -0
  153. package/src/components/progress/progress.test.tsx +25 -0
  154. package/src/components/progress/progress.tsx +124 -0
  155. package/src/components/scroll-area/index.ts +1 -0
  156. package/src/components/scroll-area/scroll-area.stories.tsx +166 -0
  157. package/src/components/scroll-area/scroll-area.tsx +64 -0
  158. package/src/components/select/index.ts +1 -0
  159. package/src/components/select/select.stories.tsx +253 -0
  160. package/src/components/select/select.tsx +430 -0
  161. package/src/components/show/index.ts +1 -0
  162. package/src/components/show/show.stories.tsx +197 -0
  163. package/src/components/show/show.test.tsx +41 -0
  164. package/src/components/show/show.tsx +16 -0
  165. package/src/components/skeleton/index.ts +1 -0
  166. package/src/components/skeleton/skeleton.stories.tsx +36 -0
  167. package/src/components/skeleton/skeleton.test.tsx +14 -0
  168. package/src/components/skeleton/skeleton.tsx +15 -0
  169. package/src/components/stack/index.ts +1 -0
  170. package/src/components/stack/stack.stories.tsx +194 -0
  171. package/src/components/stack/stack.tsx +52 -0
  172. package/src/components/stepper/Stepper.tsx +190 -0
  173. package/src/components/stepper/context/stepper-context.tsx +11 -0
  174. package/src/components/stepper/index.ts +1 -0
  175. package/src/components/stepper/stepper.stories.tsx +130 -0
  176. package/src/components/stepper/stepper.test.tsx +91 -0
  177. package/src/components/switch/index.ts +1 -0
  178. package/src/components/switch/switch.stories.tsx +122 -0
  179. package/src/components/switch/switch.test.tsx +30 -0
  180. package/src/components/switch/switch.tsx +86 -0
  181. package/src/components/table/index.ts +3 -0
  182. package/src/components/table/table-primitives.tsx +122 -0
  183. package/src/components/table/table.model.ts +20 -0
  184. package/src/components/table/table.stories.tsx +169 -0
  185. package/src/components/table/table.test.tsx +91 -0
  186. package/src/components/table/table.tsx +109 -0
  187. package/src/components/table-pagination/index.ts +2 -0
  188. package/src/components/table-pagination/table-pagination.model.ts +2 -0
  189. package/src/components/table-pagination/table-pagination.stories.tsx +23 -0
  190. package/src/components/table-pagination/table-pagination.test.tsx +32 -0
  191. package/src/components/table-pagination/table-pagination.tsx +108 -0
  192. package/src/components/tabs/context/tabs-context.tsx +14 -0
  193. package/src/components/tabs/index.ts +1 -0
  194. package/src/components/tabs/tabs.stories.tsx +182 -0
  195. package/src/components/tabs/tabs.test.tsx +61 -0
  196. package/src/components/tabs/tabs.tsx +175 -0
  197. package/src/components/tag/index.ts +2 -0
  198. package/src/components/tag/tag.stories.tsx +170 -0
  199. package/src/components/tag/tag.test.tsx +18 -0
  200. package/src/components/tag/tag.tsx +99 -0
  201. package/src/components/tag/tag.variants.ts +31 -0
  202. package/src/components/textarea/index.ts +1 -0
  203. package/src/components/textarea/textarea.stories.tsx +73 -0
  204. package/src/components/textarea/textarea.tsx +105 -0
  205. package/src/components/timeline/index.ts +1 -0
  206. package/src/components/timeline/timeline-status.ts +5 -0
  207. package/src/components/timeline/timeline.stories.tsx +84 -0
  208. package/src/components/timeline/timeline.tsx +147 -0
  209. package/src/components/toast/index.ts +1 -0
  210. package/src/components/toast/toast.stories.tsx +392 -0
  211. package/src/components/toast/toast.test.tsx +50 -0
  212. package/src/components/toast/toast.tsx +411 -0
  213. package/src/components/tooltip/index.ts +1 -0
  214. package/src/components/tooltip/tooltip.stories.tsx +226 -0
  215. package/src/components/tooltip/tooltip.test.tsx +46 -0
  216. package/src/components/tooltip/tooltip.tsx +171 -0
  217. package/src/components/tree/hooks/use-controllable-tree-state.ts +80 -0
  218. package/src/components/tree/index.ts +2 -0
  219. package/src/components/tree/tree-primitives.tsx +126 -0
  220. package/src/components/tree/tree.stories.tsx +468 -0
  221. package/src/components/tree/tree.tsx +42 -0
  222. package/src/hooks/index.ts +26 -0
  223. package/src/hooks/useArray/__doc__/useArray.stories.tsx +100 -0
  224. package/src/hooks/useArray/__test__/useArray.test.tsx +88 -0
  225. package/src/hooks/useArray/index.ts +1 -0
  226. package/src/hooks/useArray/useArray.ts +76 -0
  227. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +149 -0
  228. package/src/hooks/useAsync/__test__/useAsync.test.tsx +68 -0
  229. package/src/hooks/useAsync/index.ts +1 -0
  230. package/src/hooks/useAsync/useAsync.ts +58 -0
  231. package/src/hooks/useClickOutside/__doc__/useClickOutside.stories.tsx +40 -0
  232. package/src/hooks/useClickOutside/__test__/useClickOutside.test.tsx +33 -0
  233. package/src/hooks/useClickOutside/index.ts +1 -0
  234. package/src/hooks/useClickOutside/useClickOutside.ts +26 -0
  235. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +45 -0
  236. package/src/hooks/useClipboard/__test__/useClipboard.test.tsx +19 -0
  237. package/src/hooks/useClipboard/index.ts +1 -0
  238. package/src/hooks/useClipboard/useClipboard.tsx +28 -0
  239. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +84 -0
  240. package/src/hooks/useDebounceCallback/index.ts +1 -0
  241. package/src/hooks/useDebounceCallback/useDebouncedCallback.ts +23 -0
  242. package/src/hooks/useDebounceValue/__doc__/useDebouncedValue.stories.tsx +75 -0
  243. package/src/hooks/useDebounceValue/index.ts +1 -0
  244. package/src/hooks/useDebounceValue/useDebouncedValue.ts +17 -0
  245. package/src/hooks/useDisclosure/__doc__/useDisclosure.stories.tsx +39 -0
  246. package/src/hooks/useDisclosure/__test__/useDisclosure.test.ts +43 -0
  247. package/src/hooks/useDisclosure/index.ts +1 -0
  248. package/src/hooks/useDisclosure/useDisclosure.ts +37 -0
  249. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +26 -0
  250. package/src/hooks/useDocumentTitle/index.ts +1 -0
  251. package/src/hooks/useDocumentTitle/useDocumentTitle.tsx +11 -0
  252. package/src/hooks/useEventListener/__doc__/useEventListener.stories.tsx +28 -0
  253. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +26 -0
  254. package/src/hooks/useEventListener/index.ts +1 -0
  255. package/src/hooks/useEventListener/useEventListener.ts +25 -0
  256. package/src/hooks/useFocusTrap/__doc__/useFocusTrap.stories.tsx +37 -0
  257. package/src/hooks/useFocusTrap/index.ts +1 -0
  258. package/src/hooks/useFocusTrap/scopeTab.ts +38 -0
  259. package/src/hooks/useFocusTrap/tabbable.ts +70 -0
  260. package/src/hooks/useFocusTrap/useFocusTrap.ts +78 -0
  261. package/src/hooks/useHotkey/__docs__/useHotkey.stories.tsx +116 -0
  262. package/src/hooks/useHotkey/__test__/useHotkey.test.tsx +105 -0
  263. package/src/hooks/useHotkey/__utils__/create-hotkey-listener.ts +25 -0
  264. package/src/hooks/useHotkey/__utils__/index.ts +3 -0
  265. package/src/hooks/useHotkey/__utils__/is-input-field.ts +14 -0
  266. package/src/hooks/useHotkey/__utils__/match-key-modifiers.ts +25 -0
  267. package/src/hooks/useHotkey/index.ts +1 -0
  268. package/src/hooks/useHotkey/useHotkey.ts +34 -0
  269. package/src/hooks/useHover/__doc__/useHover.stories.tsx +41 -0
  270. package/src/hooks/useHover/__test__/useHover.test.tsx +45 -0
  271. package/src/hooks/useHover/index.ts +1 -0
  272. package/src/hooks/useHover/useHover.tsx +40 -0
  273. package/src/hooks/useIsVisible/__doc__/useIsVisible.stories.tsx +60 -0
  274. package/src/hooks/useIsVisible/index.ts +1 -0
  275. package/src/hooks/useIsVisible/useIsVisible.tsx +50 -0
  276. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +86 -0
  277. package/src/hooks/useLocalStorage/__test__/useLocalStorage.test.ts +85 -0
  278. package/src/hooks/useLocalStorage/index.ts +1 -0
  279. package/src/hooks/useLocalStorage/useLocalStorage.ts +57 -0
  280. package/src/hooks/useMediaQuery/__doc__/useMediaQuery.stories.tsx +39 -0
  281. package/src/hooks/useMediaQuery/index.ts +1 -0
  282. package/src/hooks/useMediaQuery/useMediaQuery.ts +22 -0
  283. package/src/hooks/useMemoizedFn/index.ts +1 -0
  284. package/src/hooks/useMemoizedFn/useMemoizedFn.ts +32 -0
  285. package/src/hooks/useMutation/__doc__/useMutation.stories.tsx +111 -0
  286. package/src/hooks/useMutation/__test__/useMutation.test.tsx +83 -0
  287. package/src/hooks/useMutation/index.ts +1 -0
  288. package/src/hooks/useMutation/useMutation.tsx +60 -0
  289. package/src/hooks/useObject/__doc__/useObject.stories.tsx +119 -0
  290. package/src/hooks/useObject/__test__/useObject.test.tsx +87 -0
  291. package/src/hooks/useObject/index.ts +1 -0
  292. package/src/hooks/useObject/useObject.tsx +48 -0
  293. package/src/hooks/usePagination/__doc__/usePagination.stories.tsx +72 -0
  294. package/src/hooks/usePagination/__test__/usePagination.test.tsx +98 -0
  295. package/src/hooks/usePagination/index.ts +2 -0
  296. package/src/hooks/usePagination/usePagination.tsx +74 -0
  297. package/src/hooks/usePortal/__doc__/usePortal.stories.tsx +19 -0
  298. package/src/hooks/usePortal/__test__/usePortal.test.tsx +20 -0
  299. package/src/hooks/usePortal/index.ts +1 -0
  300. package/src/hooks/usePortal/usePortal.ts +40 -0
  301. package/src/hooks/usePreventCloseWindow/__doc__/usePreventCloseWindow.stories.tsx +32 -0
  302. package/src/hooks/usePreventCloseWindow/index.ts +1 -0
  303. package/src/hooks/usePreventCloseWindow/usePreventCloseWindow.ts +33 -0
  304. package/src/hooks/useRangePagination/__test__/useRangePagination.test.tsx +63 -0
  305. package/src/hooks/useRangePagination/index.ts +2 -0
  306. package/src/hooks/useRangePagination/useRangePagination.tsx +72 -0
  307. package/src/hooks/useSelection/__doc__/useSelection.stories.tsx +140 -0
  308. package/src/hooks/useSelection/__test__/useSelection.test.tsx +57 -0
  309. package/src/hooks/useSelection/index.ts +1 -0
  310. package/src/hooks/useSelection/useSelection.ts +121 -0
  311. package/src/hooks/useStep/__doc__/useStep.stories.tsx +98 -0
  312. package/src/hooks/useStep/__test__/useStep.test.ts +51 -0
  313. package/src/hooks/useStep/index.ts +1 -0
  314. package/src/hooks/useStep/useStep.ts +57 -0
  315. package/src/hooks/useToggle/__doc__/useToggle.stories.tsx +25 -0
  316. package/src/hooks/useToggle/__test__/useToggle.test.tsx +43 -0
  317. package/src/hooks/useToggle/index.ts +1 -0
  318. package/src/hooks/useToggle/useToggle.ts +16 -0
  319. package/src/index.ts +6 -0
  320. package/src/lib/cn.ts +8 -0
  321. package/src/lib/index.ts +1 -0
  322. package/src/models/Generic.model.ts +67 -0
  323. package/src/models/index.ts +1 -0
  324. package/src/providers/index.ts +2 -0
  325. package/src/providers/library-provider.tsx +44 -0
  326. package/src/providers/theme/ThemeProvider.tsx +25 -0
  327. package/src/providers/theme/index.ts +3 -0
  328. package/src/providers/theme/types.ts +11 -0
  329. package/src/providers/theme/useThemeProps.ts +25 -0
  330. package/src/stores/theme.store.ts +31 -0
  331. package/src/styles/components.css +4 -0
  332. package/src/styles/index.css +2 -0
  333. package/src/styles/library.css +2 -0
  334. package/src/styles/theme.css +232 -0
  335. package/src/utils/dates/parseDateRange.utility.ts +39 -0
  336. package/src/utils/form.tsx +91 -0
  337. package/src/utils/functions/createSafeContext.ts +17 -0
  338. package/src/utils/functions/ensureReactElement.tsx +30 -0
  339. package/src/utils/functions/getFormData.ts +19 -0
  340. package/src/utils/functions/index.ts +4 -0
  341. package/src/utils/functions/mergeRefs.ts +18 -0
  342. package/src/utils/index.ts +3 -0
  343. package/src/utils/strings/extractInitials.utility.ts +10 -0
  344. package/src/utils/strings/index.ts +1 -0
  345. package/src/utils/tests/click.ts +3 -0
  346. package/src/utils/tests/index.ts +2 -0
  347. package/src/utils/tests/keyboard.ts +21 -0
  348. package/src/utils/tests/type.ts +6 -0
  349. package/dist/components.css +0 -2
@@ -0,0 +1,125 @@
1
+ import { ChangeEvent, KeyboardEvent, useEffect, useRef, useState } from "react";
2
+ import {
3
+ DateMaskContext,
4
+ formatDate,
5
+ maskDateInput,
6
+ parseInputToDate,
7
+ } from "./date-picker.utils";
8
+
9
+ type UseDateInputOptions = {
10
+ date: Date | null;
11
+ onDateChange: (value: Date | null) => void;
12
+ disabledDate?: (date: Date) => boolean;
13
+ };
14
+
15
+ type UseDateInputResult = {
16
+ inputValue: string;
17
+ handleChange: (event: ChangeEvent<HTMLInputElement>) => void;
18
+ handleBlur: () => void;
19
+ handleKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void;
20
+ clear: () => void;
21
+ setFromExternalDate: (date: Date | null) => void;
22
+ };
23
+
24
+ export const useDateInput = (
25
+ options: UseDateInputOptions,
26
+ ): UseDateInputResult => {
27
+ const { date, onDateChange, disabledDate } = options;
28
+
29
+ const [inputValue, setInputValue] = useState<string>("");
30
+ const lastKeyRef = useRef<string | null>(null);
31
+ const previousValueRef = useRef<string>("");
32
+
33
+ useEffect(() => {
34
+ const formatted = formatDate(date ?? null);
35
+ setInputValue(formatted);
36
+ previousValueRef.current = formatted;
37
+ }, [date]);
38
+
39
+ const commitInputValue = () => {
40
+ const current = inputValue.trim();
41
+
42
+ if (!current) {
43
+ onDateChange(null);
44
+ setInputValue("");
45
+ previousValueRef.current = "";
46
+ return;
47
+ }
48
+
49
+ const nextDate = parseInputToDate(current);
50
+
51
+ if (!nextDate) {
52
+ onDateChange(null);
53
+ setInputValue("");
54
+ previousValueRef.current = "";
55
+ return;
56
+ }
57
+
58
+ if (disabledDate?.(nextDate)) {
59
+ const fallback = formatDate(date ?? null);
60
+ setInputValue(fallback);
61
+ previousValueRef.current = fallback;
62
+ return;
63
+ }
64
+
65
+ onDateChange(nextDate);
66
+ const formatted = formatDate(nextDate);
67
+ setInputValue(formatted);
68
+ previousValueRef.current = formatted;
69
+ };
70
+
71
+ const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
72
+ const raw = event.target.value;
73
+ const context: DateMaskContext = {
74
+ lastKey: lastKeyRef.current,
75
+ previousValue: previousValueRef.current,
76
+ };
77
+
78
+ const masked = maskDateInput(raw, context);
79
+ setInputValue(masked);
80
+ previousValueRef.current = masked;
81
+ };
82
+
83
+ const handleBlur = () => {
84
+ commitInputValue();
85
+ };
86
+
87
+ const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
88
+ lastKeyRef.current = event.key;
89
+
90
+ if (event.key === "Enter") {
91
+ event.preventDefault();
92
+ commitInputValue();
93
+ return;
94
+ }
95
+
96
+ if (event.key === "Escape") {
97
+ event.preventDefault();
98
+ const formatted = formatDate(date ?? null);
99
+ setInputValue(formatted);
100
+ previousValueRef.current = formatted;
101
+ }
102
+ };
103
+
104
+ const clear = () => {
105
+ onDateChange(null);
106
+ setInputValue("");
107
+ previousValueRef.current = "";
108
+ };
109
+
110
+ const setFromExternalDate = (next: Date | null) => {
111
+ onDateChange(next);
112
+ const formatted = formatDate(next ?? null);
113
+ setInputValue(formatted);
114
+ previousValueRef.current = formatted;
115
+ };
116
+
117
+ return {
118
+ inputValue,
119
+ handleChange,
120
+ handleBlur,
121
+ handleKeyDown,
122
+ clear,
123
+ setFromExternalDate,
124
+ };
125
+ };
@@ -0,0 +1,171 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { action } from "storybook/actions";
3
+ import {
4
+ Button,
5
+ Dialog,
6
+ DialogClose,
7
+ DialogFooter,
8
+ DialogHeader,
9
+ DialogPopup,
10
+ DialogRoot,
11
+ DialogTitle,
12
+ DialogTrigger,
13
+ Input,
14
+ Label,
15
+ Tooltip,
16
+ } from "../../components";
17
+
18
+ /**
19
+ * General-purpose dialog for displaying content that requires user attention
20
+ * or interaction — forms, detail views, confirmations with custom layouts.
21
+ *
22
+ * Use `open` + `onOpenChange` for programmatic control when there is no static trigger.
23
+ * For anything beyond the standard layout, drop down to the exported primitives.
24
+ */
25
+ const meta: Meta<typeof Dialog> = {
26
+ title: "Components/Dialog",
27
+ component: Dialog,
28
+ parameters: {
29
+ layout: "centered",
30
+ },
31
+ args: {
32
+ trigger: <Button variant="outline">Open dialog</Button>,
33
+ title: "Dialog title",
34
+ description:
35
+ "Supporting text that provides more context about this dialog.",
36
+ onOpenChange: action("onOpenChange"),
37
+ },
38
+ argTypes: {
39
+ trigger: { control: false },
40
+ title: { control: "text" },
41
+ description: { control: "text" },
42
+ hideClose: { control: "boolean" },
43
+ footer: { control: false },
44
+ children: { control: false },
45
+ },
46
+ };
47
+
48
+ export default meta;
49
+ type Story = StoryObj<typeof Dialog>;
50
+
51
+ export const Default: Story = {};
52
+
53
+ /**
54
+ * The primary use case: a dialog containing a form.
55
+ * `children` renders between the description and the footer.
56
+ */
57
+ export const WithForm: Story = {
58
+ args: {
59
+ trigger: <Button variant="outline">Edit profile</Button>,
60
+ title: "Edit profile",
61
+ description: "Update your account details below.",
62
+ footer: (
63
+ <>
64
+ <DialogClose>
65
+ <Button variant="secondary">Cancel</Button>
66
+ </DialogClose>
67
+ <Button type="submit">Save changes</Button>
68
+ </>
69
+ ),
70
+ children: (
71
+ <div className="flex flex-col gap-4">
72
+ <div className="flex flex-col gap-1.5">
73
+ <Label htmlFor="name">Name</Label>
74
+ <Input id="name" defaultValue="Franco Baigorria" />
75
+ </div>
76
+ <div className="flex flex-col gap-1.5">
77
+ <Label htmlFor="email">Email</Label>
78
+ <Input id="email" defaultValue="franco@example.com" />
79
+ </div>
80
+ </div>
81
+ ),
82
+ },
83
+ };
84
+
85
+ /**
86
+ * Use `hideClose` when el dismiss lo manejás vos desde el footer.
87
+ */
88
+ export const HideClose: Story = {
89
+ args: {
90
+ trigger: <Button variant="outline">Open</Button>,
91
+ title: "No close button",
92
+ description:
93
+ "The ✕ button is hidden. Dismiss via the footer or backdrop click.",
94
+ hideClose: true,
95
+ footer: (
96
+ <DialogClose>
97
+ <Button variant="secondary">Dismiss</Button>
98
+ </DialogClose>
99
+ ),
100
+ },
101
+ };
102
+
103
+ /**
104
+ * Use `open` and `onOpenChange` when the dialog is triggered programmatically —
105
+ * from a table row action, a keyboard shortcut, or a side-effect — without a
106
+ * static trigger element rendered next to the content.
107
+ */
108
+ export const Controlled: Story = {
109
+ render: (args) => (
110
+ <div className="flex items-center gap-4">
111
+ <Dialog {...args} />
112
+ <span className="text-sm text-muted-foreground">
113
+ State managed externally
114
+ </span>
115
+ </div>
116
+ ),
117
+ args: {
118
+ trigger: <Button variant="outline">Open (controlled)</Button>,
119
+ title: "Controlled dialog",
120
+ description:
121
+ "This dialog's open state is managed by the parent. Useful for programmatic flows.",
122
+ },
123
+ };
124
+
125
+ /**
126
+ * Pass a `<Tooltip>` as the `trigger` to show a tooltip on hover and open the dialog
127
+ * on click — both bound to the same DOM element, no wrapper added.
128
+ */
129
+ export const WithTooltip: Story = {
130
+ args: {
131
+ trigger: (
132
+ <Tooltip content="Manage account settings">
133
+ <Button variant="outline">Settings</Button>
134
+ </Tooltip>
135
+ ),
136
+ title: "Account settings",
137
+ description: "Manage your preferences and security options.",
138
+ },
139
+ };
140
+
141
+ /**
142
+ * Direct composition with primitives for full structural control.
143
+ * Use this when the composite layout doesn't fit — multi-step flows,
144
+ * scrollable panels, or anything with a non-standard structure.
145
+ */
146
+ export const Primitive: Story = {
147
+ render: () => (
148
+ <DialogRoot>
149
+ <DialogTrigger>
150
+ <Button variant="outline">Open</Button>
151
+ </DialogTrigger>
152
+ <DialogPopup>
153
+ <DialogHeader>
154
+ <DialogTitle>Built with primitives</DialogTitle>
155
+ </DialogHeader>
156
+ <div className="flex flex-col gap-4">
157
+ <div className="flex flex-col gap-1.5">
158
+ <Label htmlFor="prim-email">Email</Label>
159
+ <Input id="prim-email" defaultValue="franco@example.com" />
160
+ </div>
161
+ </div>
162
+ <DialogFooter>
163
+ <DialogClose>
164
+ <Button variant="secondary">Cancel</Button>
165
+ </DialogClose>
166
+ <Button>Save</Button>
167
+ </DialogFooter>
168
+ </DialogPopup>
169
+ </DialogRoot>
170
+ ),
171
+ };
@@ -0,0 +1,68 @@
1
+ import { render, waitFor } from "@testing-library/react";
2
+ import { ComponentProps } from "react";
3
+ import { describe, expect, it } from "vitest";
4
+ import {
5
+ DialogClose,
6
+ DialogDescription,
7
+ DialogFooter,
8
+ DialogHeader,
9
+ DialogPopup,
10
+ DialogRoot,
11
+ DialogTitle,
12
+ DialogTrigger,
13
+ } from "../../components";
14
+ import { click } from "../../utils";
15
+
16
+ describe("Dialog component", () => {
17
+ const setup = (
18
+ dialogProps?: Omit<ComponentProps<typeof DialogRoot>, "ref">,
19
+ ) =>
20
+ render(
21
+ <DialogRoot {...dialogProps}>
22
+ <DialogTrigger render={<button>Trigger</button>} />
23
+ <DialogPopup>
24
+ <DialogHeader>
25
+ <DialogTitle>Title</DialogTitle>
26
+ <DialogDescription>Description</DialogDescription>
27
+ </DialogHeader>
28
+ <DialogFooter>
29
+ <DialogClose render={<button>Cancelar</button>} />
30
+ </DialogFooter>
31
+ </DialogPopup>
32
+ </DialogRoot>,
33
+ );
34
+
35
+ it("should render correctly", () => setup());
36
+
37
+ it("should show dialog on trigger click", async () => {
38
+ const { getByText } = setup();
39
+
40
+ click(getByText("Trigger"));
41
+
42
+ await waitFor(() => {
43
+ expect(getByText("Title")).toBeInTheDocument();
44
+ expect(getByText("Description")).toBeInTheDocument();
45
+ });
46
+ });
47
+
48
+ it("should close dialog on close button click", async () => {
49
+ const { getByText, queryByText } = setup();
50
+
51
+ click(getByText("Trigger"));
52
+
53
+ await waitFor(() => expect(getByText("Cancelar")).toBeInTheDocument());
54
+
55
+ click(getByText("Cancelar"));
56
+
57
+ await waitFor(() => {
58
+ expect(queryByText("Title")).not.toBeInTheDocument();
59
+ });
60
+ });
61
+
62
+ it("should be controlled by open prop", () => {
63
+ const { getByText } = setup({ open: true });
64
+
65
+ expect(getByText("Title")).toBeInTheDocument();
66
+ expect(getByText("Description")).toBeInTheDocument();
67
+ });
68
+ });
@@ -0,0 +1,277 @@
1
+ import { Dialog as DialogBase } from "@base-ui/react/dialog";
2
+ import { X } from "lucide-react";
3
+ import type { HTMLAttributes, ReactElement, ReactNode } from "react";
4
+ import { cn } from "../../lib";
5
+ import { Button } from "../button";
6
+
7
+ // ── Primitives ────────────────────────────────────────────────────────────────
8
+
9
+ /** Context root. Holds open state and wraps all dialog parts. */
10
+ export function DialogRoot(props: DialogBase.Root.Props) {
11
+ return <DialogBase.Root {...props} />;
12
+ }
13
+
14
+ /**
15
+ * Renders as its `render` element (defaults to `<button>`).
16
+ * Composes cleanly with `<Tooltip>` and other Base UI triggers on the same DOM element.
17
+ */
18
+ export function DialogTrigger({
19
+ className,
20
+ ...props
21
+ }: DialogBase.Trigger.Props) {
22
+ return (
23
+ <DialogBase.Trigger
24
+ data-slot="dialog-trigger"
25
+ className={cn(className)}
26
+ {...props}
27
+ />
28
+ );
29
+ }
30
+
31
+ /** Semi-transparent overlay rendered behind the popup. */
32
+ export function DialogBackdrop({
33
+ className,
34
+ ...props
35
+ }: DialogBase.Backdrop.Props) {
36
+ return (
37
+ <DialogBase.Backdrop
38
+ data-slot="dialog-backdrop"
39
+ forceRender
40
+ className={cn(
41
+ "fixed inset-0 z-overlay bg-black/50",
42
+ "transition-opacity duration-200",
43
+ "data-starting-style:opacity-0 data-ending-style:opacity-0",
44
+ className,
45
+ )}
46
+ {...props}
47
+ />
48
+ );
49
+ }
50
+
51
+ /** Full-screen container that centers the popup. */
52
+ export function DialogViewport({
53
+ className,
54
+ ...props
55
+ }: DialogBase.Viewport.Props) {
56
+ return (
57
+ <DialogBase.Viewport
58
+ data-slot="dialog-viewport"
59
+ className={cn(
60
+ "fixed inset-0 z-overlay flex items-center justify-center p-4",
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Dialog content container. Includes Portal, Backdrop and Viewport internally.
70
+ * Pass `portalProps` to configure the portal (e.g. a custom `container`).
71
+ * Pass `closeProps` to customise the close button.
72
+ */
73
+ export function DialogPopup({
74
+ className,
75
+ children,
76
+ hideClose,
77
+ closeProps,
78
+ portalProps,
79
+ ...props
80
+ }: DialogBase.Popup.Props & {
81
+ hideClose?: boolean;
82
+ closeProps?: DialogBase.Close.Props;
83
+ portalProps?: DialogBase.Portal.Props;
84
+ }) {
85
+ return (
86
+ <DialogBase.Portal {...portalProps}>
87
+ <DialogBackdrop />
88
+ <DialogViewport>
89
+ <DialogBase.Popup
90
+ data-slot="dialog-popup"
91
+ className={cn(
92
+ "relative flex w-full max-w-lg flex-col gap-4 rounded-lg border bg-background p-6 shadow-lg",
93
+ "transition-[scale,opacity] duration-200 ease-out",
94
+ "data-starting-style:scale-95 data-starting-style:opacity-0",
95
+ "data-ending-style:scale-95 data-ending-style:opacity-0",
96
+ className,
97
+ )}
98
+ {...props}
99
+ >
100
+ {children}
101
+ {!hideClose && (
102
+ <DialogBase.Close
103
+ aria-label="Close"
104
+ className="absolute right-2 top-2"
105
+ render={<Button size="icon" variant="ghost" />}
106
+ {...closeProps}
107
+ >
108
+ <X className="size-4" />
109
+ </DialogBase.Close>
110
+ )}
111
+ </DialogBase.Popup>
112
+ </DialogViewport>
113
+ </DialogBase.Portal>
114
+ );
115
+ }
116
+
117
+ /** Closes the dialog and merges an optional `onClick` into the `render` element. */
118
+ export function DialogClose({ className, ...props }: DialogBase.Close.Props) {
119
+ return (
120
+ <DialogBase.Close
121
+ data-slot="dialog-close"
122
+ className={cn(className)}
123
+ {...props}
124
+ />
125
+ );
126
+ }
127
+
128
+ /** Accessible title rendered as `<h2>`. Required for a11y. */
129
+ export function DialogTitle({ className, ...props }: DialogBase.Title.Props) {
130
+ return (
131
+ <DialogBase.Title
132
+ data-slot="dialog-title"
133
+ className={cn(
134
+ "text-lg font-semibold leading-none tracking-tight",
135
+ className,
136
+ )}
137
+ {...props}
138
+ />
139
+ );
140
+ }
141
+
142
+ /** Supplementary description rendered below the title. */
143
+ export function DialogDescription({
144
+ className,
145
+ ...props
146
+ }: DialogBase.Description.Props) {
147
+ return (
148
+ <DialogBase.Description
149
+ data-slot="dialog-description"
150
+ className={cn("text-sm text-muted-foreground", className)}
151
+ {...props}
152
+ />
153
+ );
154
+ }
155
+
156
+ /** Layout wrapper for title and description. */
157
+ export function DialogHeader({
158
+ className,
159
+ ...props
160
+ }: HTMLAttributes<HTMLDivElement>) {
161
+ return (
162
+ <div
163
+ data-slot="dialog-header"
164
+ className={cn("flex flex-col gap-2", className)}
165
+ {...props}
166
+ />
167
+ );
168
+ }
169
+
170
+ /** Layout wrapper for action buttons. */
171
+ export function DialogFooter({
172
+ className,
173
+ ...props
174
+ }: HTMLAttributes<HTMLDivElement>) {
175
+ return (
176
+ <div
177
+ data-slot="dialog-footer"
178
+ className={cn(
179
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
180
+ className,
181
+ )}
182
+ {...props}
183
+ />
184
+ );
185
+ }
186
+
187
+ // ── Composite ─────────────────────────────────────────────────────────────────
188
+
189
+ export type DialogProps = Omit<DialogBase.Root.Props, "children"> & {
190
+ /** Element that opens the dialog on click. Passed as Base UI `render` prop — no DOM wrapper added. */
191
+ trigger?: ReactElement;
192
+ /** Dialog heading. */
193
+ title?: ReactNode;
194
+ /** Supporting text rendered below the title. */
195
+ description?: ReactNode;
196
+ /** Body content rendered between the description and the footer. */
197
+ children?: ReactNode;
198
+ /** Content rendered inside the footer wrapper. */
199
+ footer?: ReactNode;
200
+ /** Called when the dialog closes. */
201
+ onClose?: () => void;
202
+ /** Hides the close (×) button inside the popup. */
203
+ hideClose?: boolean;
204
+ /** Props forwarded to the close button primitive. */
205
+ closeProps?: DialogBase.Close.Props;
206
+ /** Props forwarded to the portal. */
207
+ portalProps?: DialogBase.Portal.Props;
208
+ /** Additional className forwarded to the popup panel. */
209
+ className?: string;
210
+ };
211
+
212
+ /**
213
+ * General-purpose dialog with optional trigger, title, description, body, and footer.
214
+ *
215
+ * The `trigger` prop uses Base UI's `render` prop — no wrapper element is added —
216
+ * making it composable with `<Tooltip>` at the primitive level.
217
+ * For full structural control, use the exported primitives directly.
218
+ *
219
+ * @example
220
+ * ```tsx
221
+ * <Dialog
222
+ * trigger={<Button>Open settings</Button>}
223
+ * title="Settings"
224
+ * description="Manage your account preferences."
225
+ * footer={<Button onClick={handleSave}>Save</Button>}
226
+ * >
227
+ * <SettingsForm />
228
+ * </Dialog>
229
+ * ```
230
+ */
231
+ export function Dialog({
232
+ trigger,
233
+ title,
234
+ description,
235
+ children,
236
+ footer,
237
+ onClose,
238
+ hideClose,
239
+ closeProps,
240
+ portalProps,
241
+ className,
242
+ onOpenChange,
243
+ ...props
244
+ }: DialogProps) {
245
+ return (
246
+ <DialogRoot
247
+ onOpenChange={(open, event) => {
248
+ if (!open) onClose?.();
249
+ onOpenChange?.(open, event);
250
+ }}
251
+ {...props}
252
+ >
253
+ {trigger && <DialogTrigger render={trigger} />}
254
+ <DialogPopup
255
+ className={className}
256
+ hideClose={hideClose}
257
+ closeProps={closeProps}
258
+ portalProps={portalProps}
259
+ >
260
+ {(title || description) && (
261
+ <DialogHeader>
262
+ {title && <DialogTitle>{title}</DialogTitle>}
263
+ {description && (
264
+ <DialogDescription>{description}</DialogDescription>
265
+ )}
266
+ </DialogHeader>
267
+ )}
268
+ {children}
269
+ {footer && <DialogFooter>{footer}</DialogFooter>}
270
+ </DialogPopup>
271
+ </DialogRoot>
272
+ );
273
+ }
274
+
275
+ // ── Primitive escape hatch ────────────────────────────────────────────────────
276
+
277
+ export { DialogBase as DialogPrimitive };
@@ -0,0 +1 @@
1
+ export * from "./dialog";