@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,86 @@
1
+ export type CalendarView = "month" | "year" | "decade";
2
+ export type Mode = "single" | "range" | "multiple";
3
+
4
+ export type DateSingle = Date | null;
5
+
6
+ export type DateRange = {
7
+ start: DateSingle;
8
+ end: DateSingle;
9
+ };
10
+
11
+ export type DateMultiple = Date[];
12
+
13
+ export type DateMatcher = DateSingle | DateRange | DateMultiple;
14
+
15
+ export type SingleDateValue = {
16
+ value?: DateSingle;
17
+ defaultValue?: DateSingle;
18
+ onChange?: (date: DateSingle) => void;
19
+ };
20
+
21
+ export type RangeDateValue = {
22
+ value?: DateRange;
23
+ defaultValue?: DateRange;
24
+ onChange?: (date: DateRange) => void;
25
+ };
26
+
27
+ export type MultipleDateValue = {
28
+ value?: Date[];
29
+ defaultValue?: Date[];
30
+ onChange?: (dates: Date[]) => void;
31
+ };
32
+
33
+ export type CalendarBaseProps<T extends Mode> = {
34
+ disabled?: (date: Date) => boolean;
35
+ mode: T;
36
+ };
37
+
38
+ export type CalendarModeProps<T extends Mode> = CalendarBaseProps<T> &
39
+ {
40
+ single: SingleDateValue;
41
+ range: RangeDateValue;
42
+ multiple: MultipleDateValue;
43
+ }[T];
44
+
45
+ export interface ViewProps {
46
+ value: Date;
47
+ onChange: (date: Date) => void;
48
+ }
49
+
50
+ export type CalendarState = {
51
+ defaultStartDate: Date;
52
+ mode: Mode;
53
+ isSingleMode: boolean;
54
+ isRangeMode: boolean;
55
+ selected: DateMatcher;
56
+ selectDate: (date: Date) => void;
57
+ isSelected: (day: Date) => boolean;
58
+ isInRange: (day: Date) => boolean;
59
+ isFirstDayFromRange: (day: Date) => boolean;
60
+ isLastDayFromRange: (day: Date) => boolean;
61
+ };
62
+
63
+ export const monthNames = [
64
+ "Enero",
65
+ "Febrero",
66
+ "Marzo",
67
+ "Abril",
68
+ "Mayo",
69
+ "Junio",
70
+ "Julio",
71
+ "Agosto",
72
+ "Septiembre",
73
+ "Octubre",
74
+ "Noviembre",
75
+ "Diciembre",
76
+ ] as const;
77
+
78
+ export const weeksDays: string[] = [
79
+ "Lunes",
80
+ "Martes",
81
+ "Miercoles",
82
+ "Jueves",
83
+ "Viernes",
84
+ "Sabado",
85
+ "Domingo",
86
+ ] as const;
@@ -0,0 +1,155 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { useState } from "react";
3
+ import type { DateRange } from "react-day-picker";
4
+ import { action } from "storybook/actions";
5
+ import { Calendar } from "./calendar";
6
+
7
+ /**
8
+ * Envoltorio estilizado sobre el `DayPicker` de react-day-picker. Soporta los
9
+ * modos de selección `single`, `range` y `multiple`. Acepta todas las props de `DayPicker`.
10
+ *
11
+ * Por defecto usa `captionLayout="dropdown"` con el componente `Select` de la librería
12
+ * para navegar por mes y año. El rango navegable cubre 100 años hacia atrás y 10 hacia
13
+ * adelante desde el año actual. Para volver al caption de texto, pasá `captionLayout="label"`.
14
+ *
15
+ * Sobreescribí `components.Dropdown` para reemplazar el dropdown por defecto con cualquier
16
+ * componente custom. Para apuntar solo al mes o al año, usá `components.MonthsDropdown`
17
+ * o `components.YearsDropdown`.
18
+ *
19
+ * Referencia: [react-day-picker](https://daypicker.dev/)
20
+ */
21
+ const meta: Meta<typeof Calendar> = {
22
+ title: "Components/Calendar",
23
+ component: Calendar,
24
+ parameters: { layout: "centered" },
25
+ argTypes: {
26
+ mode: {
27
+ control: "select",
28
+ options: ["single", "multiple", "range"],
29
+ description: "Modo de selección.",
30
+ table: { defaultValue: { summary: "single" } },
31
+ },
32
+ captionLayout: {
33
+ control: "select",
34
+ options: ["label", "dropdown", "dropdown-months", "dropdown-years"],
35
+ description:
36
+ 'Layout del encabezado. Usá `"dropdown"` para activar los dropdowns de mes/año.',
37
+ table: { defaultValue: { summary: "dropdown" } },
38
+ },
39
+ showOutsideDays: {
40
+ control: "boolean",
41
+ description: "Mostrar días de los meses adyacentes.",
42
+ table: { defaultValue: { summary: "true" } },
43
+ },
44
+ selected: { control: false },
45
+ onSelect: { control: false },
46
+ components: { control: false },
47
+ classNames: { control: false },
48
+ formatters: { control: false },
49
+ labels: { control: false },
50
+ startMonth: {
51
+ control: false,
52
+ description: "Primer mes navegable al usar la navegación por dropdown.",
53
+ },
54
+ endMonth: {
55
+ control: false,
56
+ description: "Último mes navegable al usar la navegación por dropdown.",
57
+ },
58
+ disabled: {
59
+ control: false,
60
+ description:
61
+ "Matcher o función `(date: Date) => boolean` para deshabilitar días específicos.",
62
+ },
63
+ hidden: {
64
+ control: false,
65
+ description: "Matcher para ocultar días específicos del calendario.",
66
+ },
67
+ modifiers: { control: false },
68
+ modifiersClassNames: { control: false },
69
+ modifiersStyles: { control: false },
70
+ },
71
+ };
72
+
73
+ export default meta;
74
+ type Story = StoryObj<typeof Calendar>;
75
+
76
+ export const Default: Story = {};
77
+
78
+ /**
79
+ * `mode="range"` habilita la selección de un rango contiguo. `selected` debe ser
80
+ * un objeto `DateRange` con forma `{ from: Date | undefined; to?: Date | undefined }`.
81
+ * Los rangos parciales (solo `from`) son válidos mientras el usuario selecciona.
82
+ */
83
+ export const Range: Story = {
84
+ render: () => {
85
+ const [range, setRange] = useState<DateRange | undefined>();
86
+ return (
87
+ <Calendar
88
+ mode="range"
89
+ selected={range}
90
+ onSelect={(value) => {
91
+ setRange(value);
92
+ action("onSelect")(value);
93
+ }}
94
+ />
95
+ );
96
+ },
97
+ };
98
+
99
+ /**
100
+ * `mode="multiple"` permite seleccionar cualquier cantidad de días individuales.
101
+ * `selected` es un `Date[]`.
102
+ */
103
+ export const Multiple: Story = {
104
+ render: () => {
105
+ const [dates, setDates] = useState<Date[] | undefined>();
106
+ return (
107
+ <Calendar
108
+ mode="multiple"
109
+ selected={dates}
110
+ onSelect={(value) => {
111
+ setDates(value);
112
+ action("onSelect")(value);
113
+ }}
114
+ />
115
+ );
116
+ },
117
+ };
118
+
119
+ /**
120
+ * `captionLayout="label"` muestra el mes y año como texto estático con botones
121
+ * de navegación. Útil cuando no se necesita saltar entre meses o años lejanos.
122
+ */
123
+ export const LabelCaption: Story = {
124
+ render: () => <Calendar captionLayout="label" />,
125
+ };
126
+
127
+ /**
128
+ * `disabled` acepta una función `(date: Date) => boolean` para deshabilitar
129
+ * días específicos. Los matchers de `date-fns` funcionan bien acá.
130
+ *
131
+ * ```tsx
132
+ * import { isWeekend } from "date-fns";
133
+ *
134
+ * <Calendar disabled={(date) => isWeekend(date)} />
135
+ * ```
136
+ */
137
+ export const Disabled: Story = {
138
+ render: () => (
139
+ <Calendar disabled={(date) => date.getDay() === 0 || date.getDay() === 6} />
140
+ ),
141
+ };
142
+
143
+ /**
144
+ * Tamaño de celda personalizado mediante la custom property `--cell-size`.
145
+ * Sobreescribila vía `className` para escalar toda la grilla del calendario.
146
+ *
147
+ * ```tsx
148
+ * <Calendar className="[--cell-size:--spacing(11)] sm:[--cell-size:--spacing(10)]" />
149
+ * ```
150
+ */
151
+ export const CustomCellSize: Story = {
152
+ render: () => (
153
+ <Calendar className="[--cell-size:--spacing(11)] sm:[--cell-size:--spacing(10)]" />
154
+ ),
155
+ };
@@ -0,0 +1,12 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+ import { Calendar } from "../../components";
4
+
5
+ describe("Calendar component", () => {
6
+ it.skip("should render correctly", () => {
7
+ render(<Calendar mode="single" />);
8
+ const component = screen.getByTestId("Calendar");
9
+
10
+ expect(component).toBeInTheDocument();
11
+ });
12
+ });
@@ -0,0 +1,185 @@
1
+ "use client";
2
+
3
+ import { es } from "date-fns/locale";
4
+ import {
5
+ ChevronLeftIcon,
6
+ ChevronRightIcon,
7
+ ChevronsUpDownIcon,
8
+ } from "lucide-react";
9
+ import type * as React from "react";
10
+ import type { DropdownProps } from "react-day-picker";
11
+ import { DayPicker } from "react-day-picker";
12
+ import { cn } from "@/lib";
13
+ import { Select } from "../select/select";
14
+
15
+ const _year = new Date().getFullYear();
16
+ const DEFAULT_START_MONTH = new Date(_year - 100, 0);
17
+ const DEFAULT_END_MONTH = new Date(_year + 10, 11);
18
+
19
+ type _Opt = { label: string; value: number };
20
+
21
+ function CalendarDropdown({
22
+ options = [],
23
+ value,
24
+ onChange,
25
+ }: DropdownProps): React.ReactElement {
26
+ const items: _Opt[] = options.map((o) => ({
27
+ label: o.label ?? "",
28
+ value: o.value ?? 0,
29
+ }));
30
+ const selected = items.find((o) => o.value === Number(value)) ?? null;
31
+
32
+ return (
33
+ <Select
34
+ items={items}
35
+ value={selected}
36
+ className="py-1"
37
+ getLabel={(o) => o.label}
38
+ getValue={(o) => String(o.value)}
39
+ onChange={(item) => {
40
+ if (!onChange || item === null) return;
41
+ onChange({
42
+ target: { value: String(item.value) },
43
+ } as React.ChangeEvent<HTMLSelectElement>);
44
+ }}
45
+ />
46
+ );
47
+ }
48
+
49
+ const buttonClassNames =
50
+ "relative flex size-(--cell-size) text-base sm:text-sm items-center justify-center rounded-lg text-foreground not-in-data-selected:hover:bg-accent disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0";
51
+
52
+ export function Calendar({
53
+ className,
54
+ classNames,
55
+ showOutsideDays = true,
56
+ components: userComponents,
57
+ mode = "single",
58
+ captionLayout = "dropdown",
59
+ startMonth = DEFAULT_START_MONTH,
60
+ endMonth = DEFAULT_END_MONTH,
61
+ ...props
62
+ }: React.ComponentProps<typeof DayPicker>): React.ReactElement {
63
+ const defaultClassNames = {
64
+ button_next: buttonClassNames,
65
+ button_previous: buttonClassNames,
66
+ caption_label:
67
+ "text-base sm:text-sm font-medium flex items-center gap-2 h-full",
68
+ day: "size-(--cell-size) text-sm py-px",
69
+ day_button: cn(
70
+ buttonClassNames,
71
+ "in-data-disabled:pointer-events-none in-[.range-middle]:rounded-none in-[.range-end:not(.range-start)]:rounded-s-none in-[.range-start:not(.range-end)]:rounded-e-none in-[.range-middle]:in-data-selected:bg-accent in-data-selected:bg-primary in-[.range-middle]:in-data-selected:text-foreground in-data-disabled:text-muted-foreground/72 in-data-outside:text-muted-foreground/72 in-data-selected:in-data-outside:text-primary-foreground in-data-selected:text-primary-foreground in-data-disabled:line-through outline-none in-[[data-selected]:not(.range-middle)]:transition-[color,background-color,border-radius,box-shadow] focus-visible:z-1 focus-visible:ring-[3px] focus-visible:ring-ring/50",
72
+ ),
73
+ dropdown: "absolute bg-popover inset-0 opacity-0",
74
+ dropdown_root:
75
+ "relative has-focus:border-ring has-focus:ring-ring/50 has-focus:ring-[3px] border border-input shadow-xs/5 rounded-lg px-[calc(--spacing(3)-1px)] h-9 sm:h-8 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-me-1",
76
+ dropdowns:
77
+ "w-full flex items-center text-base sm:text-sm justify-center h-(--cell-size) gap-1.5 *:[span]:font-medium",
78
+ hidden: "invisible",
79
+ month: "w-full",
80
+ month_caption:
81
+ "relative mx-(--cell-size) px-1 mb-1 flex h-(--cell-size) items-center justify-center z-2",
82
+ months: "relative flex flex-col sm:flex-row gap-2",
83
+ nav: "absolute top-0 flex w-full justify-between z-1",
84
+ outside:
85
+ "text-muted-foreground data-selected:bg-accent/50 data-selected:text-muted-foreground",
86
+ range_end: "range-end",
87
+ range_middle: "range-middle",
88
+ range_start: "range-start",
89
+ today:
90
+ "*:after:pointer-events-none *:after:absolute *:after:bottom-1 *:after:start-1/2 *:after:z-1 *:after:size-[3px] *:after:-translate-x-1/2 *:after:rounded-full *:after:bg-primary [&[data-selected]:not(.range-middle)>*]:after:bg-background [&[data-disabled]>*]:after:bg-foreground/30 *:after:transition-colors",
91
+ week_number:
92
+ "size-(--cell-size) p-0 text-xs font-medium text-muted-foreground/72",
93
+ weekday:
94
+ "size-(--cell-size) p-0 text-xs font-medium text-muted-foreground/72",
95
+ };
96
+ const mergedClassNames: typeof defaultClassNames = Object.keys(
97
+ defaultClassNames,
98
+ ).reduce(
99
+ (acc, key) => {
100
+ const userClass = classNames?.[key as keyof typeof classNames];
101
+ const baseClass =
102
+ defaultClassNames[key as keyof typeof defaultClassNames];
103
+
104
+ acc[key as keyof typeof defaultClassNames] = userClass
105
+ ? cn(baseClass, userClass)
106
+ : baseClass;
107
+
108
+ return acc;
109
+ },
110
+ { ...defaultClassNames } as typeof defaultClassNames,
111
+ );
112
+
113
+ const defaultComponents = {
114
+ Dropdown: CalendarDropdown,
115
+ Chevron: ({
116
+ className,
117
+ orientation,
118
+ ...props
119
+ }: {
120
+ className?: string;
121
+ orientation?: "left" | "right" | "up" | "down";
122
+ }): React.ReactElement => {
123
+ if (orientation === "left") {
124
+ return (
125
+ <ChevronLeftIcon
126
+ className={cn(className, "rtl:rotate-180")}
127
+ {...props}
128
+ aria-hidden="true"
129
+ />
130
+ );
131
+ }
132
+
133
+ if (orientation === "right") {
134
+ return (
135
+ <ChevronRightIcon
136
+ className={cn(className, "rtl:rotate-180")}
137
+ {...props}
138
+ aria-hidden="true"
139
+ />
140
+ );
141
+ }
142
+
143
+ return (
144
+ <ChevronsUpDownIcon
145
+ className={className}
146
+ {...props}
147
+ aria-hidden="true"
148
+ />
149
+ );
150
+ },
151
+ };
152
+
153
+ const mergedComponents = {
154
+ ...defaultComponents,
155
+ ...userComponents,
156
+ };
157
+
158
+ const dayPickerProps = {
159
+ className: cn(
160
+ "w-fit [--cell-size:--spacing(10)] sm:[--cell-size:--spacing(9)]",
161
+ className,
162
+ ),
163
+ classNames: mergedClassNames,
164
+ components: mergedComponents,
165
+ "data-slot": "calendar",
166
+ formatters: {
167
+ formatMonthDropdown: (date: Date) =>
168
+ date.toLocaleString("es", { month: "short" }),
169
+ } as React.ComponentProps<typeof DayPicker>["formatters"],
170
+ locale: es,
171
+ weekStartsOn: 1 as const,
172
+ captionLayout,
173
+ startMonth,
174
+ endMonth,
175
+ mode,
176
+ showOutsideDays,
177
+ ...props,
178
+ };
179
+
180
+ return (
181
+ <DayPicker
182
+ {...(dayPickerProps as React.ComponentProps<typeof DayPicker>)}
183
+ />
184
+ );
185
+ }
@@ -0,0 +1,141 @@
1
+ import {
2
+ eachYearOfInterval,
3
+ endOfDecade,
4
+ format,
5
+ startOfDecade,
6
+ } from "date-fns";
7
+ import { es } from "date-fns/locale";
8
+ import { first, last } from "lodash";
9
+ import { ChevronLeft, ChevronRight } from "lucide-react";
10
+ import { ComponentProps, HTMLProps, useMemo } from "react";
11
+ import { cn } from "../../../lib";
12
+ import { Button } from "../../button";
13
+ import { type CalendarView } from "../calendar.model";
14
+
15
+ type ButtonProps = ComponentProps<typeof Button>;
16
+
17
+ interface CalendarNavigationProps {
18
+ view: CalendarView;
19
+ setView: (view: CalendarView) => void;
20
+ value: Date;
21
+ previousMonth: () => void;
22
+ nextMonth: () => void;
23
+ previousYear: () => void;
24
+ nextYear: () => void;
25
+ previousDecade: () => void;
26
+ nextDecade: () => void;
27
+ goBackProps?: Omit<ButtonProps, "onClick">;
28
+ goNextProps?: Omit<ButtonProps, "onClick">;
29
+ changeViewProps?: Omit<HTMLProps<HTMLButtonElement>, "onClick">;
30
+ }
31
+
32
+ const CalendarNavigation = ({
33
+ view,
34
+ setView,
35
+ value,
36
+ previousMonth,
37
+ nextMonth,
38
+ previousYear,
39
+ nextYear,
40
+ previousDecade,
41
+ nextDecade,
42
+ goBackProps,
43
+ goNextProps,
44
+ changeViewProps,
45
+ }: CalendarNavigationProps) => {
46
+ const monthView = view === "month";
47
+ const yearView = view === "year";
48
+ const decadeView = view === "decade";
49
+
50
+ const navigateBack = (): void => {
51
+ if (decadeView) return previousDecade();
52
+ if (yearView) return previousYear();
53
+ previousMonth();
54
+ };
55
+
56
+ const navigateNext = (): void => {
57
+ if (decadeView) return nextDecade();
58
+ if (yearView) return nextYear();
59
+ nextMonth();
60
+ };
61
+
62
+ const changeView = (): void => {
63
+ if (monthView) return setView("year");
64
+ if (yearView) return setView("decade");
65
+ };
66
+
67
+ const getLabel = (): string => {
68
+ if (decadeView) return getDecadeLabel();
69
+ if (yearView) return format(value, "yyyy");
70
+ return format(value, "MMMM yyyy", { locale: es });
71
+ };
72
+
73
+ const getDecadeLabel = (): string => {
74
+ if (!value) return "";
75
+
76
+ const interval = eachYearOfInterval({
77
+ start: startOfDecade(value),
78
+ end: endOfDecade(value),
79
+ });
80
+
81
+ const startDate = first(interval)!;
82
+ const endDate = last(interval)!;
83
+
84
+ return `${format(startDate, "yyyy")} - ${format(endDate, "yyyy")}`;
85
+ };
86
+
87
+ const navigationLabel: string = useMemo(getLabel, [value, view]);
88
+
89
+ return (
90
+ <>
91
+ <div
92
+ className="flex items-center justify-between"
93
+ data-slot="calendar-navigation"
94
+ >
95
+ <Button
96
+ onClick={navigateBack}
97
+ size="icon"
98
+ variant="ghost"
99
+ className="border w-6 opacity-50 hover:opacity-100"
100
+ data-slot="calendar-navigation-back"
101
+ {...goBackProps}
102
+ >
103
+ <ChevronLeft
104
+ className="h-4 w-4"
105
+ data-slot="calendar-navigation-back-icon"
106
+ />
107
+ <span className="sr-only">Ir a anterior</span>
108
+ </Button>
109
+ <button
110
+ onClick={changeView}
111
+ disabled={decadeView}
112
+ {...changeViewProps}
113
+ type="button"
114
+ className={cn(
115
+ "font-semibold pb-px text-gray-900 text-lg text-blue-bell select-none min-w-[60%] capitalize",
116
+ changeViewProps?.className,
117
+ )}
118
+ data-slot="calendar-navigation-label"
119
+ >
120
+ {navigationLabel}
121
+ </button>
122
+ <Button
123
+ onClick={navigateNext}
124
+ size="icon"
125
+ variant="ghost"
126
+ className="border w-6 opacity-50 hover:opacity-100"
127
+ data-slot="calendar-navigation-next"
128
+ {...goNextProps}
129
+ >
130
+ <ChevronRight
131
+ className="h-4 w-4"
132
+ data-slot="calendar-navigation-next-icon"
133
+ />
134
+ <span className="sr-only">Ir a siguiente</span>
135
+ </Button>
136
+ </div>
137
+ </>
138
+ );
139
+ };
140
+
141
+ export { CalendarNavigation, type CalendarNavigationProps };
@@ -0,0 +1,61 @@
1
+ import { format, isSameMonth, isToday, isWeekend } from "date-fns";
2
+ import { HTMLProps, useContext, useMemo } from "react";
3
+ import { cn } from "../../../lib";
4
+ import { CalendarContext } from "../hooks/use-calendar";
5
+
6
+ type DayProps = {
7
+ day: Date;
8
+ navigationDate: Date;
9
+ } & HTMLProps<HTMLButtonElement>;
10
+
11
+ const Day = ({ day, navigationDate, className, ...props }: DayProps) => {
12
+ const {
13
+ isSelected: isDateSelected,
14
+ mode,
15
+ selected,
16
+ isRangeMode,
17
+ selectDate,
18
+ isFirstDayFromRange,
19
+ isLastDayFromRange,
20
+ } = useContext(CalendarContext);
21
+
22
+ const isSelected: boolean = useMemo(
23
+ () => isDateSelected(day),
24
+ [day, selected],
25
+ );
26
+
27
+ const isDifferentMonth = !isSelected && !isSameMonth(day, navigationDate);
28
+ const weekend = !isSelected && isWeekend(day);
29
+
30
+ return (
31
+ <button
32
+ {...props}
33
+ onClick={() => selectDate(day)}
34
+ type="button"
35
+ data-slot="calendar-day"
36
+ data-state-mode={mode}
37
+ data-state-today={isToday(day) || undefined}
38
+ data-state-selected={isSelected || undefined}
39
+ data-state-different-month={isDifferentMonth || undefined}
40
+ data-state-weekend={weekend || undefined}
41
+ data-state-range-mode={isRangeMode || undefined}
42
+ data-state-first-day={isFirstDayFromRange(day) || undefined}
43
+ data-state-last-day={isLastDayFromRange(day) || undefined}
44
+ data-state-in-range={(isRangeMode && isSelected) || undefined}
45
+ className={cn(
46
+ "mx-auto flex pt-px rounded-md items-center justify-center w-full aspect-square hover:bg-accent",
47
+ "data-[state-today]:text-error",
48
+ "data-[state-selected]:bg-primary data-[state-selected]:!text-primary-foreground data-[state-selected]:hover:bg-primary/80",
49
+ "data-[state-different-month]:opacity-50 data-[state-weekend=true]:opacity-50",
50
+ "data-[state-in-range]:not-data-[state-first-day]:not-data-[state-last-day]:rounded-none",
51
+ "data-[state-first-day]:rounded-l-full",
52
+ "data-[state-last-day]:rounded-r-full",
53
+ className,
54
+ )}
55
+ >
56
+ <time dateTime={format(day, "yyyy-MM-dd")}>{format(day, "d")}</time>
57
+ </button>
58
+ );
59
+ };
60
+
61
+ export { Day, type DayProps };
@@ -0,0 +1,45 @@
1
+ import {
2
+ eachYearOfInterval,
3
+ endOfDecade,
4
+ format,
5
+ setYear,
6
+ startOfDecade,
7
+ } from "date-fns";
8
+ import { useMemo } from "react";
9
+ import { Button } from "../../../components";
10
+ import { ViewProps } from "../calendar.model";
11
+
12
+ const DecadeView = ({ value, onChange }: ViewProps) => {
13
+ const getDecadeInterval = (): Date[] => {
14
+ return eachYearOfInterval({
15
+ start: startOfDecade(value),
16
+ end: endOfDecade(value),
17
+ });
18
+ };
19
+
20
+ const selectYear = (year: Date): void => {
21
+ const date = setYear(value, parseInt(format(year, "yyyy")));
22
+ onChange(date);
23
+ };
24
+
25
+ const years = useMemo(getDecadeInterval, [value]);
26
+
27
+ return (
28
+ <div className="grid grid-cols-3 grow" data-slot="calendar-decade-view">
29
+ {years.map((year: Date, index: number) => (
30
+ <Button
31
+ key={index}
32
+ onClick={() => selectYear(year)}
33
+ className="h-full rounded-none"
34
+ type="button"
35
+ variant="ghost"
36
+ data-slot="calendar-decade-view-year"
37
+ >
38
+ {format(year, "yyyy")}
39
+ </Button>
40
+ ))}
41
+ </div>
42
+ );
43
+ };
44
+
45
+ export { DecadeView, type ViewProps as DecadeViewProps };
@@ -0,0 +1,6 @@
1
+ export * from "./calendar-navigation";
2
+ export * from "./day";
3
+ export * from "./decade-view";
4
+ export * from "./month-view";
5
+ export * from "./week-days";
6
+ export * from "./year-view";