@juspay/blend-design-system 0.0.37-beta.2 → 0.0.37-beta.3

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 (1123) hide show
  1. package/dist/components/Badge/Badge.d.ts +3 -0
  2. package/dist/components/Badge/Badge.types.d.ts +34 -0
  3. package/dist/components/Badge/badge.dark.tokens.d.ts +3 -0
  4. package/dist/components/Badge/badge.light.tokens.d.ts +3 -0
  5. package/dist/components/Badge/badge.tokens.d.ts +55 -0
  6. package/dist/components/Badge/badge.utils.d.ts +11 -0
  7. package/dist/components/Badge/index.d.ts +2 -0
  8. package/dist/components/ChartsV2/chartV2Options.d.ts +3 -0
  9. package/dist/components/ChatInput/ChatInput.d.ts +1 -1
  10. package/dist/components/CodeEditorV2/codeEditorV2.tokens.d.ts +2 -2
  11. package/dist/components/CodeEditorV2/utils.d.ts +9 -0
  12. package/dist/components/DataTable/DataTablePagination.d.ts +2 -1
  13. package/dist/components/DataTable/TableBody/types.d.ts +5 -1
  14. package/dist/components/DataTable/TableFooter/types.d.ts +1 -0
  15. package/dist/components/DataTable/TableHeader/FilterComponents.d.ts +7 -2
  16. package/dist/components/DataTable/TableHeader/handlers.d.ts +1 -1
  17. package/dist/components/DataTable/types.d.ts +5 -0
  18. package/dist/components/DataTable/utils.d.ts +8 -0
  19. package/dist/components/DateRangePicker/types.d.ts +6 -0
  20. package/dist/components/Directory/directory.tokens.d.ts +6 -0
  21. package/dist/components/DrawerV2/DrawerV2.d.ts +15 -0
  22. package/dist/components/InputsV2/ChatInputV2/AttachmentDropdown.d.ts +11 -0
  23. package/dist/components/InputsV2/ChatInputV2/ChatInputTagV2.d.ts +13 -0
  24. package/dist/components/InputsV2/ChatInputV2/ChatInputV2.d.ts +19 -0
  25. package/dist/components/InputsV2/ChatInputV2/ChatInputV2.dark.tokens.d.ts +4 -0
  26. package/dist/components/InputsV2/ChatInputV2/ChatInputV2.light.tokens.d.ts +3 -0
  27. package/dist/components/InputsV2/ChatInputV2/ChatInputV2.tokens.d.ts +125 -0
  28. package/dist/components/InputsV2/ChatInputV2/ChatInputV2.types.d.ts +54 -0
  29. package/dist/components/InputsV2/ChatInputV2/ChatInputV2AttachmentRow.d.ts +13 -0
  30. package/dist/components/InputsV2/ChatInputV2/ChatInputV2Mobile.dark.tokens.d.ts +4 -0
  31. package/dist/components/InputsV2/ChatInputV2/ChatInputV2Mobile.light.tokens.d.ts +3 -0
  32. package/dist/components/InputsV2/ChatInputV2/ChatInputV2Mobile.tokens.d.ts +60 -0
  33. package/dist/components/InputsV2/ChatInputV2/MobileChatInputV2.d.ts +17 -0
  34. package/dist/components/InputsV2/ChatInputV2/index.d.ts +4 -0
  35. package/dist/components/InputsV2/ChatInputV2/utils.d.ts +43 -0
  36. package/dist/components/InputsV2/MultiValueInputV2/MultiValueInputV2.d.ts +1 -1
  37. package/dist/components/InputsV2/NumberInputV2/NumberInputV2.d.ts +32 -0
  38. package/dist/components/InputsV2/NumberInputV2/NumberInputV2.dark.tokens.d.ts +3 -0
  39. package/dist/components/InputsV2/NumberInputV2/NumberInputV2.light.tokens.d.ts +3 -0
  40. package/dist/components/InputsV2/NumberInputV2/NumberInputV2Stepper.d.ts +14 -0
  41. package/dist/components/InputsV2/NumberInputV2/NumberInputV2Unit.d.ts +12 -0
  42. package/dist/components/InputsV2/NumberInputV2/StepperArrow.d.ts +2 -0
  43. package/dist/components/InputsV2/NumberInputV2/index.d.ts +3 -0
  44. package/dist/components/InputsV2/NumberInputV2/numberInputV2.tokens.d.ts +135 -0
  45. package/dist/components/InputsV2/NumberInputV2/numberInputV2.types.d.ts +41 -0
  46. package/dist/components/InputsV2/NumberInputV2/utils.d.ts +52 -0
  47. package/dist/components/InputsV2/OTPInputV2/OTPInputV2.d.ts +14 -0
  48. package/dist/components/InputsV2/OTPInputV2/OTPInputV2.dark.tokens.d.ts +3 -0
  49. package/dist/components/InputsV2/OTPInputV2/OTPInputV2.light.tokens.d.ts +3 -0
  50. package/dist/components/InputsV2/OTPInputV2/OTPInputV2.tokens.d.ts +34 -0
  51. package/dist/components/InputsV2/OTPInputV2/OTPInputV2.types.d.ts +13 -0
  52. package/dist/components/InputsV2/OTPInputV2/index.d.ts +3 -0
  53. package/dist/components/InputsV2/OTPInputV2/otpInputV2Utils.d.ts +47 -0
  54. package/dist/components/InputsV2/SearchInputV2/SearchInputV2.d.ts +14 -0
  55. package/dist/components/InputsV2/SearchInputV2/SearchInputV2.dark.tokens.d.ts +3 -0
  56. package/dist/components/InputsV2/SearchInputV2/SearchInputV2.light.tokens.d.ts +3 -0
  57. package/dist/components/InputsV2/SearchInputV2/SearchInputV2.tokens.d.ts +92 -0
  58. package/dist/components/InputsV2/SearchInputV2/SearchInputV2.types.d.ts +13 -0
  59. package/dist/components/InputsV2/SearchInputV2/index.d.ts +3 -0
  60. package/dist/components/InputsV2/TextAreaV2/TextAreaV2.d.ts +23 -0
  61. package/dist/components/InputsV2/TextAreaV2/TextAreaV2.dark.tokens.d.ts +3 -0
  62. package/dist/components/InputsV2/TextAreaV2/TextAreaV2.light.tokens.d.ts +3 -0
  63. package/dist/components/InputsV2/TextAreaV2/TextAreaV2.tokens.d.ts +63 -0
  64. package/dist/components/InputsV2/TextAreaV2/TextAreaV2.types.d.ts +22 -0
  65. package/dist/components/InputsV2/TextAreaV2/index.d.ts +3 -0
  66. package/dist/components/InputsV2/TextAreaV2/utils.d.ts +47 -0
  67. package/dist/components/InputsV2/TextInputV2/TextInputV2.d.ts +3 -1
  68. package/dist/components/InputsV2/TextInputV2/TextInputV2.types.d.ts +24 -1
  69. package/dist/components/InputsV2/TextInputV2/utils.d.ts +5 -3
  70. package/dist/components/InputsV2/utils/FloatingLabelsV2/FloatingLabelsV2.d.ts +3 -1
  71. package/dist/components/InputsV2/utils/utils.d.ts +10 -0
  72. package/dist/components/Primitives/Block/Block.d.ts +1 -1
  73. package/dist/components/ProgressBarV2/utils.d.ts +40 -0
  74. package/dist/components/SidebarV2/SecondarySidebar.d.ts +9 -0
  75. package/dist/components/SidebarV2/SidebarV2.d.ts +3 -0
  76. package/dist/components/SidebarV2/SidebarV2Footer.d.ts +10 -0
  77. package/dist/components/SidebarV2/SidebarV2Header.d.ts +24 -0
  78. package/dist/components/SidebarV2/SidebarV2MobileNavigation/MobileNavigationItem.d.ts +4 -0
  79. package/dist/components/SidebarV2/SidebarV2MobileNavigation/MoreButton.d.ts +3 -0
  80. package/dist/components/SidebarV2/SidebarV2MobileNavigation/PrimaryActionButton.d.ts +3 -0
  81. package/dist/components/SidebarV2/SidebarV2MobileNavigation/hooks.d.ts +6 -0
  82. package/dist/components/SidebarV2/SidebarV2MobileNavigation/index.d.ts +3 -0
  83. package/dist/components/SidebarV2/SidebarV2MobileNavigation/mobile.dark.tokens.d.ts +3 -0
  84. package/dist/components/SidebarV2/SidebarV2MobileNavigation/mobile.light.tokens.d.ts +3 -0
  85. package/dist/components/SidebarV2/SidebarV2MobileNavigation/mobile.tokens.d.ts +87 -0
  86. package/dist/components/SidebarV2/SidebarV2MobileNavigation/types.d.ts +28 -0
  87. package/dist/components/SidebarV2/SidebarV2MobileNavigation/utils.d.ts +25 -0
  88. package/dist/components/SidebarV2/SidebarV2Panel.d.ts +34 -0
  89. package/dist/components/SidebarV2/index.d.ts +5 -0
  90. package/dist/components/SidebarV2/sidebarV2.dark.tokens.d.ts +3 -0
  91. package/dist/components/SidebarV2/sidebarV2.light.tokens.d.ts +3 -0
  92. package/dist/components/SidebarV2/sidebarV2.tokens.d.ts +106 -0
  93. package/dist/components/SidebarV2/sidebarV2.types.d.ts +1 -0
  94. package/dist/components/SidebarV2/types.d.ts +51 -0
  95. package/dist/components/SidebarV2/utils.d.ts +8 -0
  96. package/dist/components/StepperV2/Stepper/StepStatusCircle.d.ts +11 -0
  97. package/dist/components/StepperV2/Stepper/StepperComponent.d.ts +9 -0
  98. package/dist/components/StepperV2/Stepper/Steps.d.ts +7 -0
  99. package/dist/components/StepperV2/Stepper/StepsHorizontalBody.d.ts +15 -0
  100. package/dist/components/StepperV2/Stepper/StepsSubstepList.d.ts +18 -0
  101. package/dist/components/StepperV2/Stepper/StepsVerticalSubstepRails.d.ts +12 -0
  102. package/dist/components/StepperV2/Stepper/VerticalLineV2.d.ts +5 -0
  103. package/dist/components/StepperV2/Stepper/stepsHelpers.d.ts +10 -0
  104. package/dist/components/StepperV2/StepperV2.d.ts +9 -0
  105. package/dist/components/StepperV2/index.d.ts +4 -0
  106. package/dist/components/StepperV2/stepperV2.dark.tokens.d.ts +3 -0
  107. package/dist/components/StepperV2/stepperV2.light.tokens.d.ts +3 -0
  108. package/dist/components/StepperV2/stepperV2.tokens.d.ts +99 -0
  109. package/dist/components/StepperV2/stepperV2.types.d.ts +61 -0
  110. package/dist/components/StepperV2/utils.d.ts +4 -0
  111. package/dist/components/Tabs/tabs.token.d.ts +1 -1
  112. package/dist/components/Tabs/types.d.ts +1 -0
  113. package/dist/components/TabsV2/tabsV2.types.d.ts +1 -0
  114. package/dist/components/TagV2/TagV2.d.ts +1 -1
  115. package/dist/components/TopbarV2/TopbarV2.d.ts +3 -0
  116. package/dist/components/TopbarV2/index.d.ts +3 -0
  117. package/dist/components/TopbarV2/topbarV2.dark.tokens.d.ts +3 -0
  118. package/dist/components/TopbarV2/topbarV2.light.tokens.d.ts +3 -0
  119. package/dist/components/TopbarV2/topbarV2.tokens.d.ts +83 -0
  120. package/dist/components/TopbarV2/types.d.ts +50 -0
  121. package/dist/components/common/TruncatedTextWithTooltipV2/TruncatedTextWithTooltipV2.d.ts +2 -0
  122. package/dist/components/common/TruncatedTextWithTooltipV2/index.d.ts +2 -0
  123. package/dist/components/common/TruncatedTextWithTooltipV2/types.d.ts +16 -0
  124. package/dist/components/common/TruncatedTextWithTooltipV2/utils.d.ts +1 -0
  125. package/dist/components/common/index.d.ts +1 -0
  126. package/dist/context/ThemeContext.d.ts +22 -0
  127. package/dist/context/useComponentToken.d.ts +12 -1
  128. package/dist/hooks/index.d.ts +1 -0
  129. package/dist/hooks/useResizeObserver.d.ts +1 -1
  130. package/dist/hooks/useSectionScroll.d.ts +24 -0
  131. package/dist/hooks/useTruncationDetection.d.ts +1 -1
  132. package/dist/main.d.ts +107 -33
  133. package/dist/main.js +118076 -108882
  134. package/dist/node-CRWdZOVN.js +14736 -0
  135. package/dist/node.d.ts +35 -0
  136. package/dist/node.js +31 -0
  137. package/dist/style.css +1 -0
  138. package/dist/token-engine-server.d.ts +9 -0
  139. package/dist/token-engine.d.ts +10 -0
  140. package/dist/tokens/unit.tokens.d.ts +3 -0
  141. package/dist/tokens-server.d.ts +2 -0
  142. package/dist/tokens-server.js +779 -0
  143. package/dist/tokens.d.ts +2 -0
  144. package/dist/tokens.js +233 -0
  145. package/lib/breakpoints/breakPoints.ts +8 -0
  146. package/lib/components/Accordion/Accordion.tsx +109 -0
  147. package/lib/components/Accordion/AccordionItem.tsx +612 -0
  148. package/lib/components/Accordion/accessibility/AccordionAccessibility.tsx +547 -0
  149. package/lib/components/Accordion/accessibility/AccordionAccessibilityReport.ts +373 -0
  150. package/lib/components/Accordion/accessibility/index.ts +2 -0
  151. package/lib/components/Accordion/accordion.tokens.ts +299 -0
  152. package/lib/components/Accordion/index.ts +4 -0
  153. package/lib/components/Accordion/types.ts +43 -0
  154. package/lib/components/AccordionV2/AccordionV2.tsx +129 -0
  155. package/lib/components/AccordionV2/AccordionV2Chevron.tsx +67 -0
  156. package/lib/components/AccordionV2/AccordionV2Item.tsx +288 -0
  157. package/lib/components/AccordionV2/AccordionV2TriggerContent.tsx +227 -0
  158. package/lib/components/AccordionV2/accordionV2.animations.ts +26 -0
  159. package/lib/components/AccordionV2/accordionV2.dark.tokens.ts +208 -0
  160. package/lib/components/AccordionV2/accordionV2.light.tokens.ts +208 -0
  161. package/lib/components/AccordionV2/accordionV2.tokens.ts +89 -0
  162. package/lib/components/AccordionV2/accordionV2.types.ts +39 -0
  163. package/lib/components/AccordionV2/index.ts +4 -0
  164. package/lib/components/Alert/Alert.tsx +383 -0
  165. package/lib/components/Alert/accessibility/AlertAccessibility.tsx +425 -0
  166. package/lib/components/Alert/accessibility/AlertAccessibilityReport.ts +549 -0
  167. package/lib/components/Alert/accessibility/index.ts +2 -0
  168. package/lib/components/Alert/alert.tokens.ts +385 -0
  169. package/lib/components/Alert/index.ts +3 -0
  170. package/lib/components/Alert/types.ts +41 -0
  171. package/lib/components/AlertV2/AlertV2.tsx +366 -0
  172. package/lib/components/AlertV2/alertV2.dark.tokens.ts +335 -0
  173. package/lib/components/AlertV2/alertV2.light.tokens.ts +335 -0
  174. package/lib/components/AlertV2/alertV2.tokens.ts +94 -0
  175. package/lib/components/AlertV2/alertV2.types.ts +63 -0
  176. package/lib/components/AlertV2/index.ts +3 -0
  177. package/lib/components/Avatar/Avatar.tsx +385 -0
  178. package/lib/components/Avatar/StyledAvatar.tsx +8 -0
  179. package/lib/components/Avatar/accessibility/AvatarAccessibility.tsx +784 -0
  180. package/lib/components/Avatar/accessibility/AvatarAccessibilityReport.ts +512 -0
  181. package/lib/components/Avatar/accessibility/index.ts +2 -0
  182. package/lib/components/Avatar/avatar.tokens.ts +450 -0
  183. package/lib/components/Avatar/avatarUtils.ts +102 -0
  184. package/lib/components/Avatar/index.ts +3 -0
  185. package/lib/components/Avatar/types.ts +46 -0
  186. package/lib/components/AvatarGroup/AvatarGroup.tsx +178 -0
  187. package/lib/components/AvatarGroup/StyledAvatarGroup.tsx +122 -0
  188. package/lib/components/AvatarGroup/accessibility/AvatarGroupAccessibility.tsx +517 -0
  189. package/lib/components/AvatarGroup/accessibility/AvatarGroupAccessibilityReport.ts +416 -0
  190. package/lib/components/AvatarGroup/accessibility/index.ts +2 -0
  191. package/lib/components/AvatarGroup/avatarGroup.tokens.ts +233 -0
  192. package/lib/components/AvatarGroup/avatarGroupUtils.tsx +68 -0
  193. package/lib/components/AvatarGroup/index.ts +3 -0
  194. package/lib/components/AvatarGroup/types.ts +39 -0
  195. package/lib/components/AvatarV2/AvatarV2.tsx +361 -0
  196. package/lib/components/AvatarV2/avatarV2.dark.tokens.ts +333 -0
  197. package/lib/components/AvatarV2/avatarV2.light.tokens.ts +333 -0
  198. package/lib/components/AvatarV2/avatarV2.tokens.ts +93 -0
  199. package/lib/components/AvatarV2/avatarV2.types.ts +96 -0
  200. package/lib/components/AvatarV2/avatarV2.utils.ts +223 -0
  201. package/lib/components/AvatarV2/index.ts +15 -0
  202. package/lib/components/Badge/Badge.tsx +169 -0
  203. package/lib/components/Badge/Badge.types.ts +47 -0
  204. package/lib/components/Badge/badge.dark.tokens.ts +149 -0
  205. package/lib/components/Badge/badge.light.tokens.ts +149 -0
  206. package/lib/components/Badge/badge.tokens.ts +71 -0
  207. package/lib/components/Badge/badge.utils.ts +113 -0
  208. package/lib/components/Badge/index.ts +2 -0
  209. package/lib/components/Breadcrumb/Breadcrumb.tsx +229 -0
  210. package/lib/components/Breadcrumb/BreadcrumbSkeleton.tsx +37 -0
  211. package/lib/components/Breadcrumb/accessibility/BreadcrumbAccessibility.tsx +629 -0
  212. package/lib/components/Breadcrumb/accessibility/BreadcrumbAccessibilityReport.ts +552 -0
  213. package/lib/components/Breadcrumb/accessibility/index.ts +6 -0
  214. package/lib/components/Breadcrumb/breadcrumb.tokens.ts +104 -0
  215. package/lib/components/Breadcrumb/index.ts +3 -0
  216. package/lib/components/Breadcrumb/types.ts +20 -0
  217. package/lib/components/BreadcrumbV2/BreadcrumbV2.tsx +93 -0
  218. package/lib/components/BreadcrumbV2/BreadcrumbV2Icon.tsx +18 -0
  219. package/lib/components/BreadcrumbV2/BreadcrumbV2Item.tsx +67 -0
  220. package/lib/components/BreadcrumbV2/BreadcrumbV2List.tsx +102 -0
  221. package/lib/components/BreadcrumbV2/BreadcrumbV2OverflowMenu.tsx +106 -0
  222. package/lib/components/BreadcrumbV2/BreadcrumbV2Page.tsx +27 -0
  223. package/lib/components/BreadcrumbV2/BreadcrumbV2Separator.tsx +24 -0
  224. package/lib/components/BreadcrumbV2/breadcrumbV2.dark.tokens.ts +61 -0
  225. package/lib/components/BreadcrumbV2/breadcrumbV2.light.tokens.ts +61 -0
  226. package/lib/components/BreadcrumbV2/breadcrumbV2.tokens.ts +47 -0
  227. package/lib/components/BreadcrumbV2/breadcrumbV2.types.ts +53 -0
  228. package/lib/components/BreadcrumbV2/index.ts +3 -0
  229. package/lib/components/BreadcrumbV2/utils.ts +196 -0
  230. package/lib/components/Button/ACCESSIBILITY_REPORT.md +740 -0
  231. package/lib/components/Button/Button.tsx +109 -0
  232. package/lib/components/Button/ButtonBase.tsx +329 -0
  233. package/lib/components/Button/accessibility/ButtonAccessibility.tsx +1043 -0
  234. package/lib/components/Button/accessibility/ButtonAccessibilityReport.ts +578 -0
  235. package/lib/components/Button/accessibility/index.ts +7 -0
  236. package/lib/components/Button/button.tokens.ts +1846 -0
  237. package/lib/components/Button/index.ts +3 -0
  238. package/lib/components/Button/types.ts +48 -0
  239. package/lib/components/ButtonGroup/ButtonGroup.tsx +53 -0
  240. package/lib/components/ButtonGroup/accessibility/ButtonGroupAccessibility.tsx +483 -0
  241. package/lib/components/ButtonGroup/accessibility/ButtonGroupAccessibilityReport.ts +159 -0
  242. package/lib/components/ButtonGroup/accessibility/index.ts +7 -0
  243. package/lib/components/ButtonGroup/index.ts +2 -0
  244. package/lib/components/ButtonGroup/types.ts +7 -0
  245. package/lib/components/ButtonV2/ButtonGroupV2/ButtonGroupV2.tsx +32 -0
  246. package/lib/components/ButtonV2/ButtonGroupV2/buttonGroupV2.types.ts +15 -0
  247. package/lib/components/ButtonV2/ButtonGroupV2/index.ts +6 -0
  248. package/lib/components/ButtonV2/ButtonGroupV2/utils.ts +19 -0
  249. package/lib/components/ButtonV2/ButtonV2.tsx +335 -0
  250. package/lib/components/ButtonV2/IconButton.tsx +38 -0
  251. package/lib/components/ButtonV2/LinkButton.tsx +179 -0
  252. package/lib/components/ButtonV2/VisuallyHidden.tsx +13 -0
  253. package/lib/components/ButtonV2/buttonV2.dark.tokens.ts +1407 -0
  254. package/lib/components/ButtonV2/buttonV2.light.tokens.ts +1493 -0
  255. package/lib/components/ButtonV2/buttonV2.tokens.ts +87 -0
  256. package/lib/components/ButtonV2/buttonV2.types.ts +86 -0
  257. package/lib/components/ButtonV2/index.ts +8 -0
  258. package/lib/components/ButtonV2/utils.ts +348 -0
  259. package/lib/components/Card/Card.tsx +141 -0
  260. package/lib/components/Card/CardComponents.tsx +627 -0
  261. package/lib/components/Card/CardSkeleton.tsx +27 -0
  262. package/lib/components/Card/accessibility/CardAccessibility.tsx +912 -0
  263. package/lib/components/Card/accessibility/CardAccessibilityReport.ts +351 -0
  264. package/lib/components/Card/card.tokens.ts +360 -0
  265. package/lib/components/Card/index.ts +3 -0
  266. package/lib/components/Card/types.ts +74 -0
  267. package/lib/components/Card/utils.ts +217 -0
  268. package/lib/components/Charts/BlendChart.tsx +167 -0
  269. package/lib/components/Charts/BlendChart.types.ts +39 -0
  270. package/lib/components/Charts/BlendChartContainer.tsx +23 -0
  271. package/lib/components/Charts/BlendChartHeader.tsx +26 -0
  272. package/lib/components/Charts/ChartContainer.tsx +30 -0
  273. package/lib/components/Charts/ChartHeader.tsx +190 -0
  274. package/lib/components/Charts/ChartLegend.tsx +756 -0
  275. package/lib/components/Charts/ChartUtils.tsx +530 -0
  276. package/lib/components/Charts/Charts.tsx +1002 -0
  277. package/lib/components/Charts/ChartsSkeleton.tsx +31 -0
  278. package/lib/components/Charts/CoreChart.tsx +73 -0
  279. package/lib/components/Charts/CustomTooltip.tsx +876 -0
  280. package/lib/components/Charts/DateTimeFormatter.ts +769 -0
  281. package/lib/components/Charts/SankeyChartWrapper.tsx +345 -0
  282. package/lib/components/Charts/SankeyLink.tsx +95 -0
  283. package/lib/components/Charts/SankeyNode.tsx +113 -0
  284. package/lib/components/Charts/accessibility/ChartsAccessibility.tsx +827 -0
  285. package/lib/components/Charts/accessibility/ChartsAccessibilityReport.ts +370 -0
  286. package/lib/components/Charts/chart.tokens.ts +144 -0
  287. package/lib/components/Charts/index.ts +15 -0
  288. package/lib/components/Charts/renderChart.tsx +1385 -0
  289. package/lib/components/Charts/types.tsx +309 -0
  290. package/lib/components/Charts/utils.tsx +60 -0
  291. package/lib/components/ChartsV2/ChartContainerV2.tsx +30 -0
  292. package/lib/components/ChartsV2/ChartHeaderV2.tsx +27 -0
  293. package/lib/components/ChartsV2/ChartV2.tsx +94 -0
  294. package/lib/components/ChartsV2/ChartV2Fullscreen.tsx +157 -0
  295. package/lib/components/ChartsV2/ChartV2Legend.tsx +174 -0
  296. package/lib/components/ChartsV2/ChartV2NoData.tsx +48 -0
  297. package/lib/components/ChartsV2/ChartV2Skeleton.tsx +37 -0
  298. package/lib/components/ChartsV2/chartV2.dark.tokens.ts +200 -0
  299. package/lib/components/ChartsV2/chartV2.light.tokens.ts +197 -0
  300. package/lib/components/ChartsV2/chartV2.sankey.ts +13 -0
  301. package/lib/components/ChartsV2/chartV2.tokens.ts +117 -0
  302. package/lib/components/ChartsV2/chartV2.types.ts +84 -0
  303. package/lib/components/ChartsV2/chartV2Options.ts +119 -0
  304. package/lib/components/ChartsV2/index.ts +9 -0
  305. package/lib/components/ChartsV2/useChartLegend.ts +25 -0
  306. package/lib/components/ChartsV2/useChartLegendHover.ts +116 -0
  307. package/lib/components/ChartsV2/useChartRefs.ts +29 -0
  308. package/lib/components/ChartsV2/utils.ts +92 -0
  309. package/lib/components/ChatInput/AttachmentFile.tsx +266 -0
  310. package/lib/components/ChatInput/ChatInput.tsx +577 -0
  311. package/lib/components/ChatInput/MobileChatInput.tsx +240 -0
  312. package/lib/components/ChatInput/accessibility/ChatInputAccessibility.tsx +597 -0
  313. package/lib/components/ChatInput/accessibility/ChatInputAccessibilityReport.ts +314 -0
  314. package/lib/components/ChatInput/accessibility/index.ts +7 -0
  315. package/lib/components/ChatInput/chatInput.tokens.ts +575 -0
  316. package/lib/components/ChatInput/index.tsx +3 -0
  317. package/lib/components/ChatInput/types.ts +121 -0
  318. package/lib/components/ChatInput/utils.ts +151 -0
  319. package/lib/components/Checkbox/Checkbox.tsx +308 -0
  320. package/lib/components/Checkbox/StyledCheckbox.tsx +100 -0
  321. package/lib/components/Checkbox/accessibility/CheckboxAccessibility.tsx +1080 -0
  322. package/lib/components/Checkbox/accessibility/CheckboxAccessibilityReport.ts +580 -0
  323. package/lib/components/Checkbox/accessibility/index.ts +6 -0
  324. package/lib/components/Checkbox/checkbox.animations.ts +85 -0
  325. package/lib/components/Checkbox/checkbox.token.ts +366 -0
  326. package/lib/components/Checkbox/checkboxUtils.ts +144 -0
  327. package/lib/components/Checkbox/index.ts +2 -0
  328. package/lib/components/Checkbox/types.ts +39 -0
  329. package/lib/components/CodeBlock/CodeBlock.tsx +358 -0
  330. package/lib/components/CodeBlock/CodeBlockDiffView/CodeBlockDiffView.tsx +488 -0
  331. package/lib/components/CodeBlock/CodeBlockDiffView/types.ts +18 -0
  332. package/lib/components/CodeBlock/CodeBlockDiffView/utils.ts +26 -0
  333. package/lib/components/CodeBlock/CodeBlockLineParts.tsx +300 -0
  334. package/lib/components/CodeBlock/accessibility/CodeBlockAccessibility.tsx +697 -0
  335. package/lib/components/CodeBlock/accessibility/CodeBlockAccessibilityReport.ts +350 -0
  336. package/lib/components/CodeBlock/codeBlock.token.ts +322 -0
  337. package/lib/components/CodeBlock/index.ts +2 -0
  338. package/lib/components/CodeBlock/types.ts +72 -0
  339. package/lib/components/CodeBlock/utils.ts +712 -0
  340. package/lib/components/CodeEditor/CodeEditor.tsx +101 -0
  341. package/lib/components/CodeEditor/CodeEditorHeader.tsx +125 -0
  342. package/lib/components/CodeEditor/MonacoEditorWrapper.tsx +621 -0
  343. package/lib/components/CodeEditor/index.ts +2 -0
  344. package/lib/components/CodeEditor/monaco-editor.css +1 -0
  345. package/lib/components/CodeEditor/types.ts +49 -0
  346. package/lib/components/CodeEditor/utils.ts +27 -0
  347. package/lib/components/CodeEditorV2/CodeEditorV2.tsx +156 -0
  348. package/lib/components/CodeEditorV2/CodeEditorV2Header.tsx +123 -0
  349. package/lib/components/CodeEditorV2/MonacoEditor/MonacoEditorWrapper.tsx +395 -0
  350. package/lib/components/CodeEditorV2/MonacoEditor/monaco-editor.css +1 -0
  351. package/lib/components/CodeEditorV2/MonacoEditor/monacoTheme.ts +133 -0
  352. package/lib/components/CodeEditorV2/codeEditorV2.dark.tokens.ts +168 -0
  353. package/lib/components/CodeEditorV2/codeEditorV2.light.token.ts +170 -0
  354. package/lib/components/CodeEditorV2/codeEditorV2.tokens.ts +99 -0
  355. package/lib/components/CodeEditorV2/codeEditorV2.types.ts +132 -0
  356. package/lib/components/CodeEditorV2/index.ts +3 -0
  357. package/lib/components/CodeEditorV2/utils.ts +504 -0
  358. package/lib/components/DataTable/ColumnFilter/index.tsx +284 -0
  359. package/lib/components/DataTable/ColumnManager.tsx +322 -0
  360. package/lib/components/DataTable/DataTable.tsx +2052 -0
  361. package/lib/components/DataTable/DataTableHeader/index.tsx +775 -0
  362. package/lib/components/DataTable/DataTableHeader/types.ts +31 -0
  363. package/lib/components/DataTable/DataTablePagination.tsx +602 -0
  364. package/lib/components/DataTable/MobileColumnDrawer/index.tsx +407 -0
  365. package/lib/components/DataTable/MobileColumnManagerDrawer/index.tsx +164 -0
  366. package/lib/components/DataTable/TableBody/BulkActionBar.tsx +309 -0
  367. package/lib/components/DataTable/TableBody/index.tsx +1619 -0
  368. package/lib/components/DataTable/TableBody/types.ts +57 -0
  369. package/lib/components/DataTable/TableCell/index.tsx +590 -0
  370. package/lib/components/DataTable/TableCell/types.ts +24 -0
  371. package/lib/components/DataTable/TableFooter/index.tsx +63 -0
  372. package/lib/components/DataTable/TableFooter/types.ts +15 -0
  373. package/lib/components/DataTable/TableHeader/DraggableColumnHeader.tsx +71 -0
  374. package/lib/components/DataTable/TableHeader/FilterComponents.tsx +1549 -0
  375. package/lib/components/DataTable/TableHeader/MobileFilterDrawer.tsx +772 -0
  376. package/lib/components/DataTable/TableHeader/handlers.ts +232 -0
  377. package/lib/components/DataTable/TableHeader/index.tsx +2143 -0
  378. package/lib/components/DataTable/TableHeader/types.ts +84 -0
  379. package/lib/components/DataTable/TableHeader/utils.ts +302 -0
  380. package/lib/components/DataTable/accessibility/DataTableAccessibility.tsx +579 -0
  381. package/lib/components/DataTable/accessibility/DataTableAccessibilityReport.ts +250 -0
  382. package/lib/components/DataTable/accessibility/index.ts +7 -0
  383. package/lib/components/DataTable/columnTypes.ts +359 -0
  384. package/lib/components/DataTable/dataTable.tokens.ts +802 -0
  385. package/lib/components/DataTable/hooks/index.ts +2 -0
  386. package/lib/components/DataTable/hooks/useMobileDataTable.ts +35 -0
  387. package/lib/components/DataTable/index.ts +23 -0
  388. package/lib/components/DataTable/types.ts +447 -0
  389. package/lib/components/DataTable/utils.ts +1358 -0
  390. package/lib/components/DateRangePicker/CalendarGrid.tsx +1012 -0
  391. package/lib/components/DateRangePicker/DateRangePicker.tsx +1278 -0
  392. package/lib/components/DateRangePicker/MobileDrawerPresets.tsx +174 -0
  393. package/lib/components/DateRangePicker/QuickRangeSelector.tsx +241 -0
  394. package/lib/components/DateRangePicker/TimeSelector.tsx +446 -0
  395. package/lib/components/DateRangePicker/accessibility/DateRangePickerAccessibility.tsx +488 -0
  396. package/lib/components/DateRangePicker/accessibility/DateRangePickerAccessibilityReport.ts +547 -0
  397. package/lib/components/DateRangePicker/accessibility/index.ts +4 -0
  398. package/lib/components/DateRangePicker/components/ActionButtons.tsx +76 -0
  399. package/lib/components/DateRangePicker/components/DatePickerComponent.tsx +291 -0
  400. package/lib/components/DateRangePicker/components/PresetItem.tsx +139 -0
  401. package/lib/components/DateRangePicker/components/ScrollablePicker.tsx +618 -0
  402. package/lib/components/DateRangePicker/components/mobile.tokens.ts +167 -0
  403. package/lib/components/DateRangePicker/constants.ts +42 -0
  404. package/lib/components/DateRangePicker/dateRangePicker.tokens.ts +492 -0
  405. package/lib/components/DateRangePicker/index.ts +4 -0
  406. package/lib/components/DateRangePicker/types.ts +420 -0
  407. package/lib/components/DateRangePicker/utils.ts +4391 -0
  408. package/lib/components/Directory/Directory.tsx +75 -0
  409. package/lib/components/Directory/NavItem.tsx +517 -0
  410. package/lib/components/Directory/Section.tsx +270 -0
  411. package/lib/components/Directory/directory.tokens.ts +283 -0
  412. package/lib/components/Directory/index.ts +3 -0
  413. package/lib/components/Directory/types.ts +52 -0
  414. package/lib/components/Directory/utils.ts +77 -0
  415. package/lib/components/Drawer/Drawer.tsx +31 -0
  416. package/lib/components/Drawer/accessibility/DrawerAccessibility.tsx +749 -0
  417. package/lib/components/Drawer/accessibility/DrawerAccessibilityReport.ts +394 -0
  418. package/lib/components/Drawer/components/Drawer.css +21 -0
  419. package/lib/components/Drawer/components/DrawerBase.tsx +738 -0
  420. package/lib/components/Drawer/components/NestedSelectDrawer.tsx +843 -0
  421. package/lib/components/Drawer/components/SelectDrawer.tsx +701 -0
  422. package/lib/components/Drawer/components/StatusDrawer.tsx +127 -0
  423. package/lib/components/Drawer/drawer.tokens.ts +141 -0
  424. package/lib/components/Drawer/index.ts +20 -0
  425. package/lib/components/Drawer/types.ts +424 -0
  426. package/lib/components/DrawerV2/DrawerV2.tsx +131 -0
  427. package/lib/components/DrawerV2/index.ts +16 -0
  428. package/lib/components/DrawerV2/types.ts +58 -0
  429. package/lib/components/GradientBlur/GradientBlur.css +107 -0
  430. package/lib/components/GradientBlur/GradientBlur.tsx +16 -0
  431. package/lib/components/Inputs/AutofillStyles/AutofillStyles.ts +23 -0
  432. package/lib/components/Inputs/DropdownInput/DropdownInput.tsx +419 -0
  433. package/lib/components/Inputs/DropdownInput/accessibility/DropdownInputAccessibility.tsx +674 -0
  434. package/lib/components/Inputs/DropdownInput/accessibility/DropdownInputAccessibilityReport.ts +444 -0
  435. package/lib/components/Inputs/DropdownInput/dropdownInput.tokens.ts +304 -0
  436. package/lib/components/Inputs/DropdownInput/index.ts +3 -0
  437. package/lib/components/Inputs/DropdownInput/types.ts +51 -0
  438. package/lib/components/Inputs/MultiValueInput/MultiValueInput.tsx +324 -0
  439. package/lib/components/Inputs/MultiValueInput/accessibility/MultiValueInputAccessibility.tsx +501 -0
  440. package/lib/components/Inputs/MultiValueInput/accessibility/MultiValueInputAccessibilityReport.ts +479 -0
  441. package/lib/components/Inputs/MultiValueInput/index.ts +3 -0
  442. package/lib/components/Inputs/MultiValueInput/multiValueInput.tokens.ts +302 -0
  443. package/lib/components/Inputs/MultiValueInput/types.ts +38 -0
  444. package/lib/components/Inputs/NumberInput/NumberInput.tsx +623 -0
  445. package/lib/components/Inputs/NumberInput/accessibility/NumberInputAccessibility.tsx +581 -0
  446. package/lib/components/Inputs/NumberInput/accessibility/NumberInputAccessibilityReport.ts +333 -0
  447. package/lib/components/Inputs/NumberInput/index.ts +3 -0
  448. package/lib/components/Inputs/NumberInput/numberInput.tokens.ts +382 -0
  449. package/lib/components/Inputs/NumberInput/types.ts +23 -0
  450. package/lib/components/Inputs/NumberInput/utils.ts +329 -0
  451. package/lib/components/Inputs/OTPInput/OTPInput.tsx +355 -0
  452. package/lib/components/Inputs/OTPInput/accessibility/OTPInputAccessibility.tsx +456 -0
  453. package/lib/components/Inputs/OTPInput/accessibility/OTPInputAccessibilityReport.ts +466 -0
  454. package/lib/components/Inputs/OTPInput/index.ts +3 -0
  455. package/lib/components/Inputs/OTPInput/otpInput.tokens.ts +255 -0
  456. package/lib/components/Inputs/OTPInput/types.ts +17 -0
  457. package/lib/components/Inputs/SearchInput/SearchInput.tsx +299 -0
  458. package/lib/components/Inputs/SearchInput/accessibility/SearchInputAccessibility.tsx +644 -0
  459. package/lib/components/Inputs/SearchInput/accessibility/SearchInputAccessibilityReport.ts +428 -0
  460. package/lib/components/Inputs/SearchInput/index.ts +3 -0
  461. package/lib/components/Inputs/SearchInput/searchInput.tokens.ts +238 -0
  462. package/lib/components/Inputs/SearchInput/types.ts +13 -0
  463. package/lib/components/Inputs/TextArea/TextArea.tsx +227 -0
  464. package/lib/components/Inputs/TextArea/accessibility/TextAreaAccessibility.tsx +466 -0
  465. package/lib/components/Inputs/TextArea/accessibility/TextAreaAccessibilityReport.ts +476 -0
  466. package/lib/components/Inputs/TextArea/index.ts +2 -0
  467. package/lib/components/Inputs/TextArea/textarea.token.ts +264 -0
  468. package/lib/components/Inputs/TextArea/types.ts +26 -0
  469. package/lib/components/Inputs/TextInput/TextInput.tsx +448 -0
  470. package/lib/components/Inputs/TextInput/accessibility/TextInputAccessibility.tsx +598 -0
  471. package/lib/components/Inputs/TextInput/accessibility/TextInputAccessibilityReport.ts +576 -0
  472. package/lib/components/Inputs/TextInput/index.ts +3 -0
  473. package/lib/components/Inputs/TextInput/textInput.tokens.ts +305 -0
  474. package/lib/components/Inputs/TextInput/types.ts +41 -0
  475. package/lib/components/Inputs/TextInput/utils.ts +43 -0
  476. package/lib/components/Inputs/UnitInput/UnitInput.tsx +379 -0
  477. package/lib/components/Inputs/UnitInput/accessibility/UnitInputAccessibility.tsx +587 -0
  478. package/lib/components/Inputs/UnitInput/accessibility/UnitInputAccessibilityReport.ts +322 -0
  479. package/lib/components/Inputs/UnitInput/index.ts +3 -0
  480. package/lib/components/Inputs/UnitInput/types.ts +33 -0
  481. package/lib/components/Inputs/UnitInput/unitInput.tokens.ts +380 -0
  482. package/lib/components/Inputs/index.ts +8 -0
  483. package/lib/components/Inputs/utils/FloatingLabels/FloatingLabels.tsx +47 -0
  484. package/lib/components/Inputs/utils/InputFooter/InputFooter.tsx +92 -0
  485. package/lib/components/Inputs/utils/InputLabels/InputLabels.tsx +133 -0
  486. package/lib/components/InputsV2/ChatInputV2/AttachmentDropdown.tsx +64 -0
  487. package/lib/components/InputsV2/ChatInputV2/ChatInputTagV2.tsx +93 -0
  488. package/lib/components/InputsV2/ChatInputV2/ChatInputV2.dark.tokens.ts +339 -0
  489. package/lib/components/InputsV2/ChatInputV2/ChatInputV2.light.tokens.ts +317 -0
  490. package/lib/components/InputsV2/ChatInputV2/ChatInputV2.tokens.ts +133 -0
  491. package/lib/components/InputsV2/ChatInputV2/ChatInputV2.tsx +466 -0
  492. package/lib/components/InputsV2/ChatInputV2/ChatInputV2.types.ts +61 -0
  493. package/lib/components/InputsV2/ChatInputV2/ChatInputV2AttachmentRow.tsx +260 -0
  494. package/lib/components/InputsV2/ChatInputV2/ChatInputV2Mobile.dark.tokens.ts +87 -0
  495. package/lib/components/InputsV2/ChatInputV2/ChatInputV2Mobile.light.tokens.ts +79 -0
  496. package/lib/components/InputsV2/ChatInputV2/ChatInputV2Mobile.tokens.ts +63 -0
  497. package/lib/components/InputsV2/ChatInputV2/MobileChatInputV2.tsx +346 -0
  498. package/lib/components/InputsV2/ChatInputV2/index.ts +4 -0
  499. package/lib/components/InputsV2/ChatInputV2/utils.ts +263 -0
  500. package/lib/components/InputsV2/MultiValueInputV2/MultiValueInputV2.dark.tokens.ts +409 -0
  501. package/lib/components/InputsV2/MultiValueInputV2/MultiValueInputV2.light.tokens.ts +409 -0
  502. package/lib/components/InputsV2/MultiValueInputV2/MultiValueInputV2.tokens.ts +82 -0
  503. package/lib/components/InputsV2/MultiValueInputV2/MultiValueInputV2.tsx +382 -0
  504. package/lib/components/InputsV2/MultiValueInputV2/MultiValueV2.types.ts +30 -0
  505. package/lib/components/InputsV2/MultiValueInputV2/index.ts +3 -0
  506. package/lib/components/InputsV2/NumberInputV2/NumberInputV2.dark.tokens.ts +404 -0
  507. package/lib/components/InputsV2/NumberInputV2/NumberInputV2.light.tokens.ts +532 -0
  508. package/lib/components/InputsV2/NumberInputV2/NumberInputV2.tsx +698 -0
  509. package/lib/components/InputsV2/NumberInputV2/NumberInputV2Stepper.tsx +135 -0
  510. package/lib/components/InputsV2/NumberInputV2/NumberInputV2Unit.tsx +75 -0
  511. package/lib/components/InputsV2/NumberInputV2/StepperArrow.tsx +25 -0
  512. package/lib/components/InputsV2/NumberInputV2/index.ts +3 -0
  513. package/lib/components/InputsV2/NumberInputV2/numberInputV2.tokens.ts +133 -0
  514. package/lib/components/InputsV2/NumberInputV2/numberInputV2.types.ts +47 -0
  515. package/lib/components/InputsV2/NumberInputV2/utils.ts +411 -0
  516. package/lib/components/InputsV2/OTPInputV2/OTPInputV2.dark.tokens.ts +299 -0
  517. package/lib/components/InputsV2/OTPInputV2/OTPInputV2.light.tokens.ts +301 -0
  518. package/lib/components/InputsV2/OTPInputV2/OTPInputV2.tokens.ts +47 -0
  519. package/lib/components/InputsV2/OTPInputV2/OTPInputV2.tsx +327 -0
  520. package/lib/components/InputsV2/OTPInputV2/OTPInputV2.types.ts +16 -0
  521. package/lib/components/InputsV2/OTPInputV2/index.ts +3 -0
  522. package/lib/components/InputsV2/OTPInputV2/otpInputV2Utils.ts +177 -0
  523. package/lib/components/InputsV2/SearchInputV2/SearchInputV2.dark.tokens.ts +257 -0
  524. package/lib/components/InputsV2/SearchInputV2/SearchInputV2.light.tokens.ts +257 -0
  525. package/lib/components/InputsV2/SearchInputV2/SearchInputV2.tokens.ts +106 -0
  526. package/lib/components/InputsV2/SearchInputV2/SearchInputV2.tsx +282 -0
  527. package/lib/components/InputsV2/SearchInputV2/SearchInputV2.types.ts +16 -0
  528. package/lib/components/InputsV2/SearchInputV2/index.ts +3 -0
  529. package/lib/components/InputsV2/SearchInputV2/utils.ts +164 -0
  530. package/lib/components/InputsV2/TextAreaV2/TextAreaV2.dark.tokens.ts +398 -0
  531. package/lib/components/InputsV2/TextAreaV2/TextAreaV2.light.tokens.ts +397 -0
  532. package/lib/components/InputsV2/TextAreaV2/TextAreaV2.tokens.ts +76 -0
  533. package/lib/components/InputsV2/TextAreaV2/TextAreaV2.tsx +251 -0
  534. package/lib/components/InputsV2/TextAreaV2/TextAreaV2.types.ts +26 -0
  535. package/lib/components/InputsV2/TextAreaV2/index.tsx +3 -0
  536. package/lib/components/InputsV2/TextAreaV2/utils.ts +140 -0
  537. package/lib/components/InputsV2/TextInputV2/TextInputV2.dark.tokens.ts +429 -0
  538. package/lib/components/InputsV2/TextInputV2/TextInputV2.light.tokens.ts +423 -0
  539. package/lib/components/InputsV2/TextInputV2/TextInputV2.tokens.ts +149 -0
  540. package/lib/components/InputsV2/TextInputV2/TextInputV2.tsx +433 -0
  541. package/lib/components/InputsV2/TextInputV2/TextInputV2.types.ts +56 -0
  542. package/lib/components/InputsV2/TextInputV2/index.ts +3 -0
  543. package/lib/components/InputsV2/TextInputV2/utils.ts +81 -0
  544. package/lib/components/InputsV2/inputV2.tokens.ts +93 -0
  545. package/lib/components/InputsV2/inputV2.types.ts +19 -0
  546. package/lib/components/InputsV2/utils/FloatingLabelsV2/FloatingLabelsV2.tsx +96 -0
  547. package/lib/components/InputsV2/utils/InputFooter/InputFooterV2.tsx +70 -0
  548. package/lib/components/InputsV2/utils/InputLabels/InputLabelsV2.tsx +95 -0
  549. package/lib/components/InputsV2/utils/InputSlots/InputSlots.tsx +43 -0
  550. package/lib/components/InputsV2/utils/utils.ts +38 -0
  551. package/lib/components/KeyValuePair/KeyValuePair.tokens.ts +111 -0
  552. package/lib/components/KeyValuePair/KeyValuePair.tsx +248 -0
  553. package/lib/components/KeyValuePair/accessibility/KeyValuePairAccessibility.tsx +514 -0
  554. package/lib/components/KeyValuePair/accessibility/KeyValuePairAccessibilityReport.ts +317 -0
  555. package/lib/components/KeyValuePair/accessibility/index.ts +2 -0
  556. package/lib/components/KeyValuePair/index.ts +3 -0
  557. package/lib/components/KeyValuePair/types.ts +45 -0
  558. package/lib/components/KeyValuePair/utils.ts +113 -0
  559. package/lib/components/KeyValuePairV2/KeyValuePairLayout.tsx +89 -0
  560. package/lib/components/KeyValuePairV2/KeyValuePairV2.tsx +132 -0
  561. package/lib/components/KeyValuePairV2/ResponsiveText.tsx +117 -0
  562. package/lib/components/KeyValuePairV2/index.ts +3 -0
  563. package/lib/components/KeyValuePairV2/keyValuePairV2.dark.tokens.ts +59 -0
  564. package/lib/components/KeyValuePairV2/keyValuePairV2.light.tokens.ts +59 -0
  565. package/lib/components/KeyValuePairV2/keyValuePairV2.tokens.ts +42 -0
  566. package/lib/components/KeyValuePairV2/keyValuePairV2.types.ts +35 -0
  567. package/lib/components/KeyValuePairV2/responsiveTextStyles.ts +65 -0
  568. package/lib/components/KeyValuePairV2/utils.ts +51 -0
  569. package/lib/components/Menu/Menu.tsx +502 -0
  570. package/lib/components/Menu/MenuGroupLabel.tsx +10 -0
  571. package/lib/components/Menu/MenuItem.tsx +246 -0
  572. package/lib/components/Menu/MenuSkeleton.tsx +36 -0
  573. package/lib/components/Menu/SubMenu.tsx +420 -0
  574. package/lib/components/Menu/accessibility/MenuAccessibility.tsx +598 -0
  575. package/lib/components/Menu/accessibility/MenuAccessibilityReport.ts +484 -0
  576. package/lib/components/Menu/accessibility/index.ts +2 -0
  577. package/lib/components/Menu/index.ts +3 -0
  578. package/lib/components/Menu/menu.animations.ts +146 -0
  579. package/lib/components/Menu/menu.styles.ts +17 -0
  580. package/lib/components/Menu/menu.tokens.ts +617 -0
  581. package/lib/components/Menu/types.tsx +101 -0
  582. package/lib/components/Menu/utils.ts +44 -0
  583. package/lib/components/MenuV2/MenuV2.tsx +133 -0
  584. package/lib/components/MenuV2/MenuV2Content.tsx +442 -0
  585. package/lib/components/MenuV2/MenuV2Item.tsx +194 -0
  586. package/lib/components/MenuV2/MenuV2SubMenu.tsx +288 -0
  587. package/lib/components/MenuV2/index.ts +7 -0
  588. package/lib/components/MenuV2/menuV2.animations.ts +83 -0
  589. package/lib/components/MenuV2/menuV2.dark.tokens.ts +401 -0
  590. package/lib/components/MenuV2/menuV2.light.tokens.ts +401 -0
  591. package/lib/components/MenuV2/menuV2.tokens.ts +126 -0
  592. package/lib/components/MenuV2/menuV2.types.ts +99 -0
  593. package/lib/components/MenuV2/menuV2.utils.ts +156 -0
  594. package/lib/components/Modal/MobileModal.tsx +235 -0
  595. package/lib/components/Modal/Modal.tsx +400 -0
  596. package/lib/components/Modal/ModalSkeleton.tsx +110 -0
  597. package/lib/components/Modal/accessibility/ModalAccessibility.tsx +518 -0
  598. package/lib/components/Modal/accessibility/ModalAccessibilityReport.ts +350 -0
  599. package/lib/components/Modal/index.ts +3 -0
  600. package/lib/components/Modal/modal.animations.ts +19 -0
  601. package/lib/components/Modal/modal.tokens.ts +207 -0
  602. package/lib/components/Modal/modal.utils.ts +24 -0
  603. package/lib/components/Modal/types.ts +42 -0
  604. package/lib/components/Modal/useModal.ts +66 -0
  605. package/lib/components/MultiSelect/MobileMultiSelect.tsx +999 -0
  606. package/lib/components/MultiSelect/MultiSelect.tsx +822 -0
  607. package/lib/components/MultiSelect/MultiSelectMenu.tsx +903 -0
  608. package/lib/components/MultiSelect/MultiSelectMenuItem.tsx +79 -0
  609. package/lib/components/MultiSelect/MultiSelectSkeleton.tsx +37 -0
  610. package/lib/components/MultiSelect/MultiSelectSubMenu.tsx +142 -0
  611. package/lib/components/MultiSelect/MultiSelectTrigger.tsx +322 -0
  612. package/lib/components/MultiSelect/SelectAllItem.tsx +108 -0
  613. package/lib/components/MultiSelect/accessibility/MultiSelectAccessibility.tsx +751 -0
  614. package/lib/components/MultiSelect/accessibility/MultiSelectAccessibilityReport.ts +604 -0
  615. package/lib/components/MultiSelect/accessibility/index.ts +7 -0
  616. package/lib/components/MultiSelect/index.ts +4 -0
  617. package/lib/components/MultiSelect/multiSelect.animations.ts +135 -0
  618. package/lib/components/MultiSelect/multiSelect.tokens.ts +777 -0
  619. package/lib/components/MultiSelect/types.ts +228 -0
  620. package/lib/components/MultiSelect/utils.ts +217 -0
  621. package/lib/components/MultiSelectGroup/MultiSelectGroup.tsx +35 -0
  622. package/lib/components/MultiSelectGroup/MultiselectGroupProps.types.ts +8 -0
  623. package/lib/components/MultiSelectGroup/index.tsx +4 -0
  624. package/lib/components/MultiSelectV2/MobileMultiSelectV2.tsx +1 -0
  625. package/lib/components/MultiSelectV2/MultiSelectV2.tsx +449 -0
  626. package/lib/components/MultiSelectV2/MultiSelectV2Menu.tsx +477 -0
  627. package/lib/components/MultiSelectV2/MultiSelectV2MenuActions.tsx +87 -0
  628. package/lib/components/MultiSelectV2/MultiSelectV2MenuHeader.tsx +91 -0
  629. package/lib/components/MultiSelectV2/MultiSelectV2MenuItem.tsx +76 -0
  630. package/lib/components/MultiSelectV2/MultiSelectV2MenuItems.tsx +114 -0
  631. package/lib/components/MultiSelectV2/MultiSelectV2MenuSearch.tsx +42 -0
  632. package/lib/components/MultiSelectV2/MultiSelectV2MenuVirtualList.tsx +139 -0
  633. package/lib/components/MultiSelectV2/MultiSelectV2SelectAllItem.tsx +114 -0
  634. package/lib/components/MultiSelectV2/MultiSelectV2Skeleton.tsx +48 -0
  635. package/lib/components/MultiSelectV2/MultiSelectV2SubMenu.tsx +153 -0
  636. package/lib/components/MultiSelectV2/MultiSelectV2Trigger.tsx +365 -0
  637. package/lib/components/MultiSelectV2/index.ts +14 -0
  638. package/lib/components/MultiSelectV2/mobile/MobileMultiSelectV2.tsx +708 -0
  639. package/lib/components/MultiSelectV2/mobile/mobileMultiSelectV2.utils.ts +72 -0
  640. package/lib/components/MultiSelectV2/multiSelectV2.dark.tokens.ts +459 -0
  641. package/lib/components/MultiSelectV2/multiSelectV2.light.tokens.ts +461 -0
  642. package/lib/components/MultiSelectV2/multiSelectV2.tokens.ts +251 -0
  643. package/lib/components/MultiSelectV2/multiSelectV2.types.ts +173 -0
  644. package/lib/components/MultiSelectV2/utils.ts +243 -0
  645. package/lib/components/Popover/MobilePopover.tsx +226 -0
  646. package/lib/components/Popover/Popover.tsx +226 -0
  647. package/lib/components/Popover/PopoverFooter.tsx +58 -0
  648. package/lib/components/Popover/PopoverHeader.tsx +125 -0
  649. package/lib/components/Popover/PopoverSkeleton.tsx +92 -0
  650. package/lib/components/Popover/accessibility/PopoverAccessibility.tsx +550 -0
  651. package/lib/components/Popover/accessibility/PopoverAccessibilityReport.ts +353 -0
  652. package/lib/components/Popover/index.ts +3 -0
  653. package/lib/components/Popover/popover.animations.ts +125 -0
  654. package/lib/components/Popover/popover.tokens.ts +277 -0
  655. package/lib/components/Popover/types.ts +54 -0
  656. package/lib/components/PopoverV2/MobilePopoverV2.tsx +219 -0
  657. package/lib/components/PopoverV2/PopoverV2.tsx +231 -0
  658. package/lib/components/PopoverV2/PopoverV2Footer.tsx +55 -0
  659. package/lib/components/PopoverV2/PopoverV2Header.tsx +133 -0
  660. package/lib/components/PopoverV2/PopoverV2Skeleton.tsx +89 -0
  661. package/lib/components/PopoverV2/index.tsx +3 -0
  662. package/lib/components/PopoverV2/popoverV2.dark.tokens.tsx +228 -0
  663. package/lib/components/PopoverV2/popoverV2.light.tokens.tsx +228 -0
  664. package/lib/components/PopoverV2/popoverV2.token.ts +85 -0
  665. package/lib/components/PopoverV2/popoverV2.types.ts +70 -0
  666. package/lib/components/Primitives/Block/Block.tsx +460 -0
  667. package/lib/components/Primitives/Group/Group.tsx +93 -0
  668. package/lib/components/Primitives/Group/index.ts +3 -0
  669. package/lib/components/Primitives/Group/types.ts +99 -0
  670. package/lib/components/Primitives/Group/utils.ts +76 -0
  671. package/lib/components/Primitives/PrimitiveButton/PrimitiveButton.tsx +376 -0
  672. package/lib/components/Primitives/PrimitiveInput/PrimitiveInput.tsx +482 -0
  673. package/lib/components/Primitives/PrimitiveLink.tsx +238 -0
  674. package/lib/components/Primitives/PrimitiveText/PrimitiveText.tsx +203 -0
  675. package/lib/components/Primitives/PrimitiveTextArea.tsx +398 -0
  676. package/lib/components/ProgressBar/ProgressBar.tsx +300 -0
  677. package/lib/components/ProgressBar/accessibility/ProgressBarAccessibility.tsx +751 -0
  678. package/lib/components/ProgressBar/accessibility/ProgressBarAccessibilityReport.ts +517 -0
  679. package/lib/components/ProgressBar/index.ts +3 -0
  680. package/lib/components/ProgressBar/progressbar.tokens.ts +313 -0
  681. package/lib/components/ProgressBar/types.ts +26 -0
  682. package/lib/components/ProgressBar/utils.ts +111 -0
  683. package/lib/components/ProgressBarV2/CircularProgressBarV2.tsx +118 -0
  684. package/lib/components/ProgressBarV2/LinearProgressBarV2.tsx +104 -0
  685. package/lib/components/ProgressBarV2/ProgressBarV2.tsx +85 -0
  686. package/lib/components/ProgressBarV2/index.ts +3 -0
  687. package/lib/components/ProgressBarV2/progressBarV2.dark.tokens.ts +203 -0
  688. package/lib/components/ProgressBarV2/progressBarV2.light.tokens.ts +203 -0
  689. package/lib/components/ProgressBarV2/progressBarV2.tokens.ts +80 -0
  690. package/lib/components/ProgressBarV2/progressBarV2.types.ts +50 -0
  691. package/lib/components/ProgressBarV2/utils.ts +154 -0
  692. package/lib/components/Radio/Radio.tsx +237 -0
  693. package/lib/components/Radio/RadioGroup.tsx +248 -0
  694. package/lib/components/Radio/StyledRadio.tsx +71 -0
  695. package/lib/components/Radio/accessibility/RadioAccessibility.tsx +1109 -0
  696. package/lib/components/Radio/accessibility/RadioAccessibilityReport.ts +581 -0
  697. package/lib/components/Radio/accessibility/index.ts +2 -0
  698. package/lib/components/Radio/index.ts +4 -0
  699. package/lib/components/Radio/radio.animations.ts +49 -0
  700. package/lib/components/Radio/radio.token.ts +287 -0
  701. package/lib/components/Radio/types.ts +39 -0
  702. package/lib/components/Radio/utils.ts +125 -0
  703. package/lib/components/Select/Select.tsx +378 -0
  704. package/lib/components/Select/SelectItem/SelectItem.tsx +3 -0
  705. package/lib/components/Select/SelectItem/index.tsx +384 -0
  706. package/lib/components/Select/SelectItem/types.ts +46 -0
  707. package/lib/components/Select/SelectItem/utils.ts +62 -0
  708. package/lib/components/Select/SelectMenu.tsx +518 -0
  709. package/lib/components/Select/index.ts +2 -0
  710. package/lib/components/Select/select.token.ts +80 -0
  711. package/lib/components/Select/selectUtils.ts +74 -0
  712. package/lib/components/Select/types.tsx +102 -0
  713. package/lib/components/SelectV2/SelectItemV2.tsx +328 -0
  714. package/lib/components/SelectV2/index.ts +10 -0
  715. package/lib/components/SelectV2/selectV2.constants.ts +7 -0
  716. package/lib/components/SelectV2/selectV2.shared.types.ts +113 -0
  717. package/lib/components/SelectV2/selectV2.tokenStates.ts +15 -0
  718. package/lib/components/SelectV2/types.ts +115 -0
  719. package/lib/components/SelectV2/useSelectV2MenuBehavior.ts +135 -0
  720. package/lib/components/SelectorV2/CheckboxV2/CheckboxV2.tsx +282 -0
  721. package/lib/components/SelectorV2/CheckboxV2/StyledCheckboxV2.tsx +100 -0
  722. package/lib/components/SelectorV2/CheckboxV2/checkboxV2.animations.ts +85 -0
  723. package/lib/components/SelectorV2/CheckboxV2/checkboxV2.dark.tokens.ts +327 -0
  724. package/lib/components/SelectorV2/CheckboxV2/checkboxV2.light.tokens.ts +324 -0
  725. package/lib/components/SelectorV2/CheckboxV2/checkboxV2.tokens.ts +98 -0
  726. package/lib/components/SelectorV2/CheckboxV2/checkboxV2.types.ts +70 -0
  727. package/lib/components/SelectorV2/CheckboxV2/index.ts +3 -0
  728. package/lib/components/SelectorV2/CheckboxV2/utils.ts +171 -0
  729. package/lib/components/SelectorV2/RadioV2/RadioV2.tsx +158 -0
  730. package/lib/components/SelectorV2/RadioV2/StyledRadioV2.tsx +72 -0
  731. package/lib/components/SelectorV2/RadioV2/index.ts +3 -0
  732. package/lib/components/SelectorV2/RadioV2/radioV2.dark.tokens.ts +278 -0
  733. package/lib/components/SelectorV2/RadioV2/radioV2.light.tokens.ts +281 -0
  734. package/lib/components/SelectorV2/RadioV2/radioV2.tokens.ts +90 -0
  735. package/lib/components/SelectorV2/RadioV2/radioV2.types.ts +45 -0
  736. package/lib/components/SelectorV2/SwitchV2/SwitchV2.tsx +239 -0
  737. package/lib/components/SelectorV2/SwitchV2/index.ts +3 -0
  738. package/lib/components/SelectorV2/SwitchV2/switchV2.dark.tokens.ts +232 -0
  739. package/lib/components/SelectorV2/SwitchV2/switchV2.light.tokens.ts +230 -0
  740. package/lib/components/SelectorV2/SwitchV2/switchV2.tokens.ts +80 -0
  741. package/lib/components/SelectorV2/SwitchV2/switchV2.types.ts +56 -0
  742. package/lib/components/SelectorV2/selectorV2.types.ts +13 -0
  743. package/lib/components/SelectorsContent/SelectorsContent.types.ts +55 -0
  744. package/lib/components/SelectorsContent/SelectorsLabel.tsx +68 -0
  745. package/lib/components/SelectorsContent/SelectorsSubLabel.tsx +52 -0
  746. package/lib/components/SelectorsContent/index.ts +3 -0
  747. package/lib/components/Sidebar/Sidebar.tsx +551 -0
  748. package/lib/components/Sidebar/SidebarContent.tsx +121 -0
  749. package/lib/components/Sidebar/SidebarFooter.tsx +46 -0
  750. package/lib/components/Sidebar/SidebarHeader.tsx +199 -0
  751. package/lib/components/Sidebar/SidebarMobile/MobileNavigationItem.tsx +99 -0
  752. package/lib/components/Sidebar/SidebarMobile/MoreButton.tsx +77 -0
  753. package/lib/components/Sidebar/SidebarMobile/PrimaryActionButton.tsx +60 -0
  754. package/lib/components/Sidebar/SidebarMobile/hooks.ts +87 -0
  755. package/lib/components/Sidebar/SidebarMobile/index.tsx +393 -0
  756. package/lib/components/Sidebar/SidebarMobile/mobile.tokens.ts +159 -0
  757. package/lib/components/Sidebar/SidebarMobile/utils.ts +167 -0
  758. package/lib/components/Sidebar/TenantPanel.tsx +255 -0
  759. package/lib/components/Sidebar/accessibility/SidebarAccessibility.tsx +715 -0
  760. package/lib/components/Sidebar/accessibility/SidebarAccessibilityReport.ts +446 -0
  761. package/lib/components/Sidebar/index.ts +3 -0
  762. package/lib/components/Sidebar/sidebar.tokens.ts +269 -0
  763. package/lib/components/Sidebar/types.ts +85 -0
  764. package/lib/components/Sidebar/utils.ts +371 -0
  765. package/lib/components/SidebarV2/SecondarySidebar.tsx +123 -0
  766. package/lib/components/SidebarV2/SidebarV2.tsx +474 -0
  767. package/lib/components/SidebarV2/SidebarV2Footer.tsx +45 -0
  768. package/lib/components/SidebarV2/SidebarV2Header.tsx +165 -0
  769. package/lib/components/SidebarV2/SidebarV2MobileNavigation/MobileNavigationItem.tsx +92 -0
  770. package/lib/components/SidebarV2/SidebarV2MobileNavigation/MoreButton.tsx +74 -0
  771. package/lib/components/SidebarV2/SidebarV2MobileNavigation/PrimaryActionButton.tsx +72 -0
  772. package/lib/components/SidebarV2/SidebarV2MobileNavigation/hooks.ts +91 -0
  773. package/lib/components/SidebarV2/SidebarV2MobileNavigation/index.tsx +385 -0
  774. package/lib/components/SidebarV2/SidebarV2MobileNavigation/mobile.dark.tokens.ts +85 -0
  775. package/lib/components/SidebarV2/SidebarV2MobileNavigation/mobile.light.tokens.ts +85 -0
  776. package/lib/components/SidebarV2/SidebarV2MobileNavigation/mobile.tokens.ts +99 -0
  777. package/lib/components/SidebarV2/SidebarV2MobileNavigation/types.ts +36 -0
  778. package/lib/components/SidebarV2/SidebarV2MobileNavigation/utils.ts +174 -0
  779. package/lib/components/SidebarV2/SidebarV2Panel.tsx +139 -0
  780. package/lib/components/SidebarV2/index.ts +10 -0
  781. package/lib/components/SidebarV2/sidebarV2.dark.tokens.ts +223 -0
  782. package/lib/components/SidebarV2/sidebarV2.light.tokens.ts +223 -0
  783. package/lib/components/SidebarV2/sidebarV2.tokens.ts +120 -0
  784. package/lib/components/SidebarV2/sidebarV2.types.ts +6 -0
  785. package/lib/components/SidebarV2/types.ts +63 -0
  786. package/lib/components/SidebarV2/utils.ts +121 -0
  787. package/lib/components/SingleSelect/MobileSingleSelect.tsx +628 -0
  788. package/lib/components/SingleSelect/SingleSelect.tsx +638 -0
  789. package/lib/components/SingleSelect/SingleSelectMenu.tsx +831 -0
  790. package/lib/components/SingleSelect/SingleSelectSkeleton.tsx +37 -0
  791. package/lib/components/SingleSelect/SingleSelectTrigger.tsx +242 -0
  792. package/lib/components/SingleSelect/accessibility/SingleSelectAccessibility.tsx +1375 -0
  793. package/lib/components/SingleSelect/accessibility/SingleSelectAccessibilityReport.ts +602 -0
  794. package/lib/components/SingleSelect/accessibility/index.ts +2 -0
  795. package/lib/components/SingleSelect/index.ts +3 -0
  796. package/lib/components/SingleSelect/singleSelect.animations.ts +149 -0
  797. package/lib/components/SingleSelect/singleSelect.tokens.ts +686 -0
  798. package/lib/components/SingleSelect/types.ts +141 -0
  799. package/lib/components/SingleSelect/utils.ts +236 -0
  800. package/lib/components/SingleSelectGroup/SingleSelectGroup.tsx +34 -0
  801. package/lib/components/SingleSelectGroup/SingleSelectGroupProps.types.ts +10 -0
  802. package/lib/components/SingleSelectGroup/index.ts +4 -0
  803. package/lib/components/SingleSelectV2/MobileSingleSelectV2.tsx +615 -0
  804. package/lib/components/SingleSelectV2/SingleSelectV2.tsx +297 -0
  805. package/lib/components/SingleSelectV2/SingleSelectV2List.tsx +90 -0
  806. package/lib/components/SingleSelectV2/SingleSelectV2Menu.tsx +355 -0
  807. package/lib/components/SingleSelectV2/SingleSelectV2MenuItems.tsx +228 -0
  808. package/lib/components/SingleSelectV2/SingleSelectV2Search.tsx +54 -0
  809. package/lib/components/SingleSelectV2/SingleSelectV2Skeleton.tsx +48 -0
  810. package/lib/components/SingleSelectV2/SingleSelectV2Trigger.tsx +308 -0
  811. package/lib/components/SingleSelectV2/SingleSelectV2VirtualList.tsx +161 -0
  812. package/lib/components/SingleSelectV2/index.ts +11 -0
  813. package/lib/components/SingleSelectV2/mobile/SingleSelectV2MobileItem.tsx +127 -0
  814. package/lib/components/SingleSelectV2/mobile/singleSelectV2.mobile.utils.ts +55 -0
  815. package/lib/components/SingleSelectV2/singleSelectV2.animations.ts +146 -0
  816. package/lib/components/SingleSelectV2/singleSelectV2.dark.tokens.ts +367 -0
  817. package/lib/components/SingleSelectV2/singleSelectV2.light.tokens.ts +367 -0
  818. package/lib/components/SingleSelectV2/singleSelectV2.tokens.ts +202 -0
  819. package/lib/components/SingleSelectV2/singleSelectV2.types.ts +203 -0
  820. package/lib/components/SingleSelectV2/utils.ts +384 -0
  821. package/lib/components/Skeleton/README.md +279 -0
  822. package/lib/components/Skeleton/Skeleton.tsx +192 -0
  823. package/lib/components/Skeleton/SkeletonAvatar.tsx +45 -0
  824. package/lib/components/Skeleton/SkeletonCard.tsx +137 -0
  825. package/lib/components/Skeleton/SkeletonCompound.tsx +64 -0
  826. package/lib/components/Skeleton/hooks/useSkeletonBase.ts +33 -0
  827. package/lib/components/Skeleton/index.ts +41 -0
  828. package/lib/components/Skeleton/skeleton.tokens.ts +143 -0
  829. package/lib/components/Skeleton/types.ts +38 -0
  830. package/lib/components/Skeleton/utils.ts +32 -0
  831. package/lib/components/Slider/Slider.tsx +296 -0
  832. package/lib/components/Slider/accessibility/SliderAccessibility.tsx +631 -0
  833. package/lib/components/Slider/accessibility/SliderAccessibilityReport.ts +246 -0
  834. package/lib/components/Slider/accessibility/index.ts +7 -0
  835. package/lib/components/Slider/index.ts +3 -0
  836. package/lib/components/Slider/types.ts +35 -0
  837. package/lib/components/Slider/utils.ts +370 -0
  838. package/lib/components/Snackbar/Snackbar.tsx +315 -0
  839. package/lib/components/Snackbar/accessibility/SnackbarAccessibility.tsx +525 -0
  840. package/lib/components/Snackbar/accessibility/SnackbarAccessibilityReport.ts +360 -0
  841. package/lib/components/Snackbar/accessibility/index.ts +2 -0
  842. package/lib/components/Snackbar/index.ts +4 -0
  843. package/lib/components/Snackbar/snackbar.tokens.ts +263 -0
  844. package/lib/components/Snackbar/types.ts +51 -0
  845. package/lib/components/SnackbarV2/SnackbarV2.tsx +474 -0
  846. package/lib/components/SnackbarV2/index.ts +4 -0
  847. package/lib/components/SnackbarV2/snackbarV2.dark.tokens.ts +161 -0
  848. package/lib/components/SnackbarV2/snackbarV2.light.tokens.ts +161 -0
  849. package/lib/components/SnackbarV2/snackbarV2.tokens.ts +88 -0
  850. package/lib/components/SnackbarV2/snackbarV2.types.ts +65 -0
  851. package/lib/components/SplitTag/SplitTag.tsx +85 -0
  852. package/lib/components/SplitTag/accessibility/SplitTagAccessibility.tsx +972 -0
  853. package/lib/components/SplitTag/accessibility/SplitTagAccessibilityReport.ts +441 -0
  854. package/lib/components/SplitTag/index.ts +2 -0
  855. package/lib/components/SplitTag/types.ts +8 -0
  856. package/lib/components/StatCard/StatCard.tsx +1620 -0
  857. package/lib/components/StatCard/StatCardSkeleton.tsx +24 -0
  858. package/lib/components/StatCard/accessibility/StatCardAccessibility.tsx +612 -0
  859. package/lib/components/StatCard/accessibility/StatCardAccessibilityReport.ts +400 -0
  860. package/lib/components/StatCard/index.ts +3 -0
  861. package/lib/components/StatCard/statcard.tokens.ts +468 -0
  862. package/lib/components/StatCard/types.ts +82 -0
  863. package/lib/components/StatCard/utils.ts +78 -0
  864. package/lib/components/StatCardV2/StatCardV2.chartConfig.ts +47 -0
  865. package/lib/components/StatCardV2/StatCardV2.tsx +307 -0
  866. package/lib/components/StatCardV2/StatCardV2Change.tsx +89 -0
  867. package/lib/components/StatCardV2/StatCardV2NoData.tsx +160 -0
  868. package/lib/components/StatCardV2/StatCardV2Skeleton.tsx +24 -0
  869. package/lib/components/StatCardV2/StatCardV2Subtitle.tsx +29 -0
  870. package/lib/components/StatCardV2/StatCardV2Title.tsx +83 -0
  871. package/lib/components/StatCardV2/StatCardV2Value.tsx +67 -0
  872. package/lib/components/StatCardV2/index.ts +3 -0
  873. package/lib/components/StatCardV2/statcardV2.dark.tokens.ts +184 -0
  874. package/lib/components/StatCardV2/statcardV2.light.tokens.ts +184 -0
  875. package/lib/components/StatCardV2/statcardV2.tokens.ts +95 -0
  876. package/lib/components/StatCardV2/statcardV2.types.ts +90 -0
  877. package/lib/components/StatCardV2/utils.ts +29 -0
  878. package/lib/components/Stepper/HorizonatlLine.tsx +23 -0
  879. package/lib/components/Stepper/HorizontalStepper.tsx +467 -0
  880. package/lib/components/Stepper/Stepper.tsx +56 -0
  881. package/lib/components/Stepper/StepperLine.tsx +19 -0
  882. package/lib/components/Stepper/VerticalLine.tsx +24 -0
  883. package/lib/components/Stepper/VerticalStepper.tsx +965 -0
  884. package/lib/components/Stepper/accessibility/StepperAccessibility.tsx +723 -0
  885. package/lib/components/Stepper/accessibility/StepperAccessibilityReport.ts +337 -0
  886. package/lib/components/Stepper/index.ts +3 -0
  887. package/lib/components/Stepper/stepper.tokens.ts +883 -0
  888. package/lib/components/Stepper/types.ts +55 -0
  889. package/lib/components/StepperV2/Stepper/StepStatusCircle.tsx +54 -0
  890. package/lib/components/StepperV2/Stepper/StepperComponent.tsx +177 -0
  891. package/lib/components/StepperV2/Stepper/Steps.tsx +562 -0
  892. package/lib/components/StepperV2/Stepper/StepsHorizontalBody.tsx +105 -0
  893. package/lib/components/StepperV2/Stepper/StepsSubstepList.tsx +198 -0
  894. package/lib/components/StepperV2/Stepper/StepsVerticalSubstepRails.tsx +72 -0
  895. package/lib/components/StepperV2/Stepper/VerticalLineV2.tsx +26 -0
  896. package/lib/components/StepperV2/Stepper/stepsHelpers.ts +59 -0
  897. package/lib/components/StepperV2/StepperV2.tsx +44 -0
  898. package/lib/components/StepperV2/index.ts +10 -0
  899. package/lib/components/StepperV2/stepperV2.dark.tokens.ts +1403 -0
  900. package/lib/components/StepperV2/stepperV2.light.tokens.ts +1389 -0
  901. package/lib/components/StepperV2/stepperV2.tokens.ts +116 -0
  902. package/lib/components/StepperV2/stepperV2.types.ts +72 -0
  903. package/lib/components/StepperV2/utils.ts +37 -0
  904. package/lib/components/Switch/StyledSwitch.tsx +97 -0
  905. package/lib/components/Switch/Switch.tsx +245 -0
  906. package/lib/components/Switch/SwitchGroup.tsx +115 -0
  907. package/lib/components/Switch/accessibility/SwitchAccessibility.tsx +819 -0
  908. package/lib/components/Switch/accessibility/SwitchAccessibilityReport.ts +579 -0
  909. package/lib/components/Switch/accessibility/index.ts +6 -0
  910. package/lib/components/Switch/index.ts +4 -0
  911. package/lib/components/Switch/switch.animations.ts +54 -0
  912. package/lib/components/Switch/switch.token.ts +363 -0
  913. package/lib/components/Switch/types.ts +37 -0
  914. package/lib/components/Switch/utils.ts +158 -0
  915. package/lib/components/Tabs/StyledTabs.tsx +153 -0
  916. package/lib/components/Tabs/Tabs.tsx +313 -0
  917. package/lib/components/Tabs/TabsContent.tsx +33 -0
  918. package/lib/components/Tabs/TabsList.tsx +590 -0
  919. package/lib/components/Tabs/TabsTrigger.tsx +271 -0
  920. package/lib/components/Tabs/accessibility/TabsAccessibility.tsx +735 -0
  921. package/lib/components/Tabs/accessibility/TabsAccessibilityReport.ts +290 -0
  922. package/lib/components/Tabs/accessibility/index.ts +2 -0
  923. package/lib/components/Tabs/index.ts +8 -0
  924. package/lib/components/Tabs/tabs.token.ts +758 -0
  925. package/lib/components/Tabs/types.ts +99 -0
  926. package/lib/components/Tabs/utils.ts +235 -0
  927. package/lib/components/TabsV2/StyledTabsV2.tsx +166 -0
  928. package/lib/components/TabsV2/TabsV2.tsx +202 -0
  929. package/lib/components/TabsV2/TabsV2Content.tsx +34 -0
  930. package/lib/components/TabsV2/TabsV2List.tsx +403 -0
  931. package/lib/components/TabsV2/TabsV2Trigger.tsx +260 -0
  932. package/lib/components/TabsV2/index.ts +12 -0
  933. package/lib/components/TabsV2/tabsV2.context.tsx +45 -0
  934. package/lib/components/TabsV2/tabsV2.dark.tokens.ts +680 -0
  935. package/lib/components/TabsV2/tabsV2.light.tokens.ts +680 -0
  936. package/lib/components/TabsV2/tabsV2.tokens.ts +111 -0
  937. package/lib/components/TabsV2/tabsV2.types.ts +85 -0
  938. package/lib/components/TabsV2/tabsV2.utils.ts +145 -0
  939. package/lib/components/TagGroupV2/TagGroupV2.tsx +32 -0
  940. package/lib/components/TagGroupV2/TagGroupV2.types.ts +12 -0
  941. package/lib/components/TagGroupV2/index.ts +2 -0
  942. package/lib/components/TagV2/TagSkeleton.tsx +102 -0
  943. package/lib/components/TagV2/TagV2.tsx +179 -0
  944. package/lib/components/TagV2/TagV2.types.ts +61 -0
  945. package/lib/components/TagV2/index.ts +2 -0
  946. package/lib/components/TagV2/tagV2.dark.tokens.ts +359 -0
  947. package/lib/components/TagV2/tagV2.light.tokens.ts +352 -0
  948. package/lib/components/TagV2/tagV2.tokens.ts +85 -0
  949. package/lib/components/TagV2/utils.ts +71 -0
  950. package/lib/components/Tags/Tag.tsx +115 -0
  951. package/lib/components/Tags/TagBase.tsx +227 -0
  952. package/lib/components/Tags/Tags.tsx +82 -0
  953. package/lib/components/Tags/accessibility/TagAccessibility.tsx +665 -0
  954. package/lib/components/Tags/accessibility/TagAccessibilityReport.ts +392 -0
  955. package/lib/components/Tags/index.ts +3 -0
  956. package/lib/components/Tags/tag.dark.tokens.ts +297 -0
  957. package/lib/components/Tags/tag.light.tokens.ts +297 -0
  958. package/lib/components/Tags/tag.tokens.ts +87 -0
  959. package/lib/components/Tags/types.ts +49 -0
  960. package/lib/components/Text/Text.tsx +147 -0
  961. package/lib/components/TextInputGroup/TextInputGroup.tsx +34 -0
  962. package/lib/components/TextInputGroup/TextInputGroupProps.types.ts +8 -0
  963. package/lib/components/TextInputGroup/index.ts +4 -0
  964. package/lib/components/Timeline/Timeline.tsx +106 -0
  965. package/lib/components/Timeline/TimelineHeader.tsx +120 -0
  966. package/lib/components/Timeline/TimelineLabel.tsx +61 -0
  967. package/lib/components/Timeline/TimelineNode.tsx +225 -0
  968. package/lib/components/Timeline/TimelineShowMore.tsx +36 -0
  969. package/lib/components/Timeline/TimelineSubstep.tsx +173 -0
  970. package/lib/components/Timeline/index.ts +9 -0
  971. package/lib/components/Timeline/timeline.dark.token.ts +149 -0
  972. package/lib/components/Timeline/timeline.light.token.ts +149 -0
  973. package/lib/components/Timeline/timeline.token.ts +152 -0
  974. package/lib/components/Timeline/types.ts +115 -0
  975. package/lib/components/Timeline/utils.ts +169 -0
  976. package/lib/components/Tooltip/Tooltip.tsx +170 -0
  977. package/lib/components/Tooltip/accessibility/TooltipAccessibility.tsx +592 -0
  978. package/lib/components/Tooltip/accessibility/TooltipAccessibilityReport.ts +356 -0
  979. package/lib/components/Tooltip/index.ts +3 -0
  980. package/lib/components/Tooltip/tooltip.animations.ts +82 -0
  981. package/lib/components/Tooltip/tooltip.tokens.ts +157 -0
  982. package/lib/components/Tooltip/types.ts +41 -0
  983. package/lib/components/TooltipV2/TooltipV2.tsx +214 -0
  984. package/lib/components/TooltipV2/index.ts +3 -0
  985. package/lib/components/TooltipV2/tooltipV2.animation.ts +82 -0
  986. package/lib/components/TooltipV2/tooltipV2.dark.tokens.ts +115 -0
  987. package/lib/components/TooltipV2/tooltipV2.light.tokens.ts +115 -0
  988. package/lib/components/TooltipV2/tooltipV2.tokens.ts +62 -0
  989. package/lib/components/TooltipV2/tooltipV2.types.ts +43 -0
  990. package/lib/components/Topbar/Topbar.tsx +370 -0
  991. package/lib/components/Topbar/index.ts +3 -0
  992. package/lib/components/Topbar/topbar.tokens.ts +304 -0
  993. package/lib/components/Topbar/types.ts +53 -0
  994. package/lib/components/TopbarV2/TopbarV2.tsx +363 -0
  995. package/lib/components/TopbarV2/index.ts +3 -0
  996. package/lib/components/TopbarV2/topbarV2.dark.tokens.ts +159 -0
  997. package/lib/components/TopbarV2/topbarV2.light.tokens.ts +159 -0
  998. package/lib/components/TopbarV2/topbarV2.tokens.ts +84 -0
  999. package/lib/components/TopbarV2/types.ts +52 -0
  1000. package/lib/components/Upload/Upload.tsx +675 -0
  1001. package/lib/components/Upload/accessibility/UploadAccessibility.tsx +507 -0
  1002. package/lib/components/Upload/accessibility/UploadAccessibilityReport.ts +495 -0
  1003. package/lib/components/Upload/accessibility/index.ts +3 -0
  1004. package/lib/components/Upload/components/DefaultState.tsx +101 -0
  1005. package/lib/components/Upload/components/ErrorState.tsx +124 -0
  1006. package/lib/components/Upload/components/FileListDisplay.tsx +112 -0
  1007. package/lib/components/Upload/components/MixedState.tsx +88 -0
  1008. package/lib/components/Upload/components/SuccessState.tsx +108 -0
  1009. package/lib/components/Upload/components/UploadingState.tsx +108 -0
  1010. package/lib/components/Upload/components/index.ts +6 -0
  1011. package/lib/components/Upload/index.ts +4 -0
  1012. package/lib/components/Upload/types.ts +103 -0
  1013. package/lib/components/Upload/upload.tokens.ts +288 -0
  1014. package/lib/components/Upload/utils.ts +929 -0
  1015. package/lib/components/VirtualList/VirtualList.tsx +269 -0
  1016. package/lib/components/VirtualList/index.ts +7 -0
  1017. package/lib/components/VirtualList/types.ts +31 -0
  1018. package/lib/components/VirtualList/utils.ts +169 -0
  1019. package/lib/components/animations/ChevronAnimation/ChevronAnimation.tsx +128 -0
  1020. package/lib/components/animations/ChevronAnimation/index.ts +3 -0
  1021. package/lib/components/animations/ChevronAnimation/types.ts +32 -0
  1022. package/lib/components/animations/ChevronAnimation/utils.ts +41 -0
  1023. package/lib/components/animations/Ripple/RippleContainer.tsx +70 -0
  1024. package/lib/components/animations/Ripple/index.ts +2 -0
  1025. package/lib/components/common/Seperator.tsx +30 -0
  1026. package/lib/components/common/TruncatedTextWithTooltip.tsx +120 -0
  1027. package/lib/components/common/TruncatedTextWithTooltipV2/TruncatedTextWithTooltipV2.tsx +88 -0
  1028. package/lib/components/common/TruncatedTextWithTooltipV2/index.ts +2 -0
  1029. package/lib/components/common/TruncatedTextWithTooltipV2/types.ts +21 -0
  1030. package/lib/components/common/TruncatedTextWithTooltipV2/utils.ts +6 -0
  1031. package/lib/components/common/error.animations.ts +28 -0
  1032. package/lib/components/common/index.ts +11 -0
  1033. package/lib/components/common/useErrorShake.ts +20 -0
  1034. package/lib/components/common/virtualViewport.ts +23 -0
  1035. package/lib/components/shared/accessibility/AccessibilityDashboard.tsx +348 -0
  1036. package/lib/components/shared/accessibility/LightHouse-components/AccordionLightHouse.tsx +328 -0
  1037. package/lib/components/shared/accessibility/LightHouse-components/AlertLightHouse.tsx +371 -0
  1038. package/lib/components/shared/accessibility/LightHouse-components/AvatarGroupLightHouse.tsx +386 -0
  1039. package/lib/components/shared/accessibility/LightHouse-components/AvatarLightHouse.tsx +248 -0
  1040. package/lib/components/shared/accessibility/LightHouse-components/BreadcrumbLightHouse.tsx +242 -0
  1041. package/lib/components/shared/accessibility/LightHouse-components/ButtonGroupLightHouse.tsx +342 -0
  1042. package/lib/components/shared/accessibility/LightHouse-components/ButtonLightHouse.tsx +185 -0
  1043. package/lib/components/shared/accessibility/LightHouse-components/CardLightHouse.tsx +542 -0
  1044. package/lib/components/shared/accessibility/LightHouse-components/ChartsLightHouse.tsx +349 -0
  1045. package/lib/components/shared/accessibility/LightHouse-components/ChatInputLightHouse.tsx +231 -0
  1046. package/lib/components/shared/accessibility/LightHouse-components/CheckboxLightHouse.tsx +236 -0
  1047. package/lib/components/shared/accessibility/LightHouse-components/CodeBlockLightHouse.tsx +180 -0
  1048. package/lib/components/shared/accessibility/LightHouse-components/DrawerLightHouse.tsx +254 -0
  1049. package/lib/components/shared/accessibility/LightHouse-components/DropdownInputLightHouse.tsx +204 -0
  1050. package/lib/components/shared/accessibility/LightHouse-components/KeyValuePairLightHouse.tsx +220 -0
  1051. package/lib/components/shared/accessibility/LightHouse-components/MenuLightHouse.tsx +441 -0
  1052. package/lib/components/shared/accessibility/LightHouse-components/ModalLightHouse.tsx +93 -0
  1053. package/lib/components/shared/accessibility/LightHouse-components/MultiSelectLightHouse.tsx +308 -0
  1054. package/lib/components/shared/accessibility/LightHouse-components/MultiValueInputLightHouse.tsx +144 -0
  1055. package/lib/components/shared/accessibility/LightHouse-components/NumberInputLightHouse.tsx +106 -0
  1056. package/lib/components/shared/accessibility/LightHouse-components/OTPInputLightHouse.tsx +97 -0
  1057. package/lib/components/shared/accessibility/LightHouse-components/PopoverLightHouse.tsx +97 -0
  1058. package/lib/components/shared/accessibility/LightHouse-components/ProgressBarLightHouse.tsx +280 -0
  1059. package/lib/components/shared/accessibility/LightHouse-components/RadioLightHouse.tsx +322 -0
  1060. package/lib/components/shared/accessibility/LightHouse-components/SearchInputLightHouse.tsx +195 -0
  1061. package/lib/components/shared/accessibility/LightHouse-components/SidebarLightHouse.tsx +403 -0
  1062. package/lib/components/shared/accessibility/LightHouse-components/SingleSelectLightHouse.tsx +278 -0
  1063. package/lib/components/shared/accessibility/LightHouse-components/SliderLightHouse.tsx +284 -0
  1064. package/lib/components/shared/accessibility/LightHouse-components/SnackbarLightHouse.tsx +35 -0
  1065. package/lib/components/shared/accessibility/LightHouse-components/SplitTagLightHouse.tsx +357 -0
  1066. package/lib/components/shared/accessibility/LightHouse-components/StatCardLightHouse.tsx +249 -0
  1067. package/lib/components/shared/accessibility/LightHouse-components/StepperLightHouse.tsx +208 -0
  1068. package/lib/components/shared/accessibility/LightHouse-components/SwitchLightHouse.tsx +355 -0
  1069. package/lib/components/shared/accessibility/LightHouse-components/TabsLightHouse.tsx +339 -0
  1070. package/lib/components/shared/accessibility/LightHouse-components/TagLightHouse.tsx +440 -0
  1071. package/lib/components/shared/accessibility/LightHouse-components/TextAreaLightHouse.tsx +141 -0
  1072. package/lib/components/shared/accessibility/LightHouse-components/TextInputLightHouse.tsx +95 -0
  1073. package/lib/components/shared/accessibility/LightHouse-components/TooltipLightHouse.tsx +218 -0
  1074. package/lib/components/shared/accessibility/LightHouse-components/UnitInputLightHouse.tsx +128 -0
  1075. package/lib/components/shared/accessibility/LightHouse-components/UploadLightHouse.tsx +269 -0
  1076. package/lib/components/shared/accessibility/index.ts +13 -0
  1077. package/lib/components/shared/accessibility/reportGenerator.ts +522 -0
  1078. package/lib/components/shared/accessibility/storybookParser.ts +93 -0
  1079. package/lib/components/shared/accessibility/testResultsParser.ts +75 -0
  1080. package/lib/context/ShadowAware.tsx +52 -0
  1081. package/lib/context/ThemeContext.tsx +436 -0
  1082. package/lib/context/ThemeProvider.tsx +59 -0
  1083. package/lib/context/index.ts +6 -0
  1084. package/lib/context/initComponentTokens.ts +271 -0
  1085. package/lib/context/theme.enum.ts +4 -0
  1086. package/lib/context/useComponentToken.ts +322 -0
  1087. package/lib/global-utils/GlobalUtils.ts +130 -0
  1088. package/lib/hooks/index.ts +8 -0
  1089. package/lib/hooks/useBreakPoints.ts +48 -0
  1090. package/lib/hooks/useClickOutside.ts +30 -0
  1091. package/lib/hooks/useDebounce.ts +19 -0
  1092. package/lib/hooks/useDropdownInteractionLock.ts +226 -0
  1093. package/lib/hooks/useInputSlotPadding.ts +50 -0
  1094. package/lib/hooks/usePreventParentScroll.ts +37 -0
  1095. package/lib/hooks/useResizeObserver.ts +33 -0
  1096. package/lib/hooks/useResponsiveTokens.ts +26 -0
  1097. package/lib/hooks/useRipple.ts +74 -0
  1098. package/lib/hooks/useScrollLock.ts +174 -0
  1099. package/lib/hooks/useSectionScroll.ts +90 -0
  1100. package/lib/hooks/useTruncationDetection.ts +87 -0
  1101. package/lib/main.ts +321 -0
  1102. package/lib/node.ts +39 -0
  1103. package/lib/pollyfills/resizeObserverPollyfill.ts +35 -0
  1104. package/lib/token-engine-server.ts +95 -0
  1105. package/lib/token-engine.ts +105 -0
  1106. package/lib/tokens/border.tokens.ts +58 -0
  1107. package/lib/tokens/color.tokens.ts +116 -0
  1108. package/lib/tokens/font.tokens.ts +312 -0
  1109. package/lib/tokens/index.ts +2 -0
  1110. package/lib/tokens/opacity.tokens.ts +34 -0
  1111. package/lib/tokens/shadows.tokens.ts +28 -0
  1112. package/lib/tokens/theme.token.ts +29 -0
  1113. package/lib/tokens/unit.tokens.ts +101 -0
  1114. package/lib/tokens/zIndex.tokens.ts +25 -0
  1115. package/lib/utils/accessibility/aria-helpers.ts +317 -0
  1116. package/lib/utils/accessibility/focus-helpers.ts +226 -0
  1117. package/lib/utils/accessibility/icon-helpers.ts +25 -0
  1118. package/lib/utils/accessibility/index.ts +25 -0
  1119. package/lib/utils/accessibility/keyboard-helpers.ts +342 -0
  1120. package/lib/utils/accessibility/visually-hidden.tsx +45 -0
  1121. package/lib/utils/prop-helpers.ts +10 -0
  1122. package/package.json +17 -3
  1123. package/dist/assets/main.css +0 -1
@@ -0,0 +1,4391 @@
1
+ import {
2
+ DateRange,
3
+ DateRangePreset,
4
+ DateFormatPreset,
5
+ DateFormatConfig,
6
+ CustomFormatFunction,
7
+ HapticFeedbackType,
8
+ CustomPresetConfig,
9
+ CustomPresetDefinition,
10
+ PresetsConfig,
11
+ CustomRangeConfig,
12
+ } from './types'
13
+ import { CalendarTokenType } from './dateRangePicker.tokens'
14
+ import { DATE_RANGE_PICKER_CONSTANTS } from './constants'
15
+
16
+ /**
17
+ * Gets the date components (year, month, day, etc.) for a given Date in a specific timezone
18
+ * @param date The date to get components for
19
+ * @param timezone IANA timezone string (e.g., "America/New_York")
20
+ * @returns Object with date components in the target timezone
21
+ */
22
+ export const getDatePartsInTimezone = (
23
+ date: Date,
24
+ timezone: string
25
+ ): {
26
+ year: number
27
+ month: number
28
+ day: number
29
+ hours: number
30
+ minutes: number
31
+ seconds: number
32
+ } => {
33
+ const formatter = new Intl.DateTimeFormat('en-US', {
34
+ timeZone: timezone,
35
+ year: 'numeric',
36
+ month: '2-digit',
37
+ day: '2-digit',
38
+ hour: '2-digit',
39
+ minute: '2-digit',
40
+ second: '2-digit',
41
+ hour12: false,
42
+ })
43
+
44
+ const parts = formatter.formatToParts(date)
45
+
46
+ return {
47
+ year: parseInt(parts.find((p) => p.type === 'year')?.value || '2024'),
48
+ month:
49
+ parseInt(parts.find((p) => p.type === 'month')?.value || '1') - 1,
50
+ day: parseInt(parts.find((p) => p.type === 'day')?.value || '1'),
51
+ hours: parseInt(parts.find((p) => p.type === 'hour')?.value || '0'),
52
+ minutes: parseInt(parts.find((p) => p.type === 'minute')?.value || '0'),
53
+ seconds: parseInt(parts.find((p) => p.type === 'second')?.value || '0'),
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Creates a Date object representing a specific moment in a timezone
59
+ * This interprets the given date/time as being in the target timezone and returns
60
+ * the equivalent JavaScript Date object (which is always in UTC internally)
61
+ *
62
+ * @param timezone IANA timezone string (e.g., "America/New_York")
63
+ * @param year Year
64
+ * @param month Month (0-11)
65
+ * @param day Day of month (1-31)
66
+ * @param hours Hours (0-23)
67
+ * @param minutes Minutes (0-59)
68
+ * @param seconds Seconds (0-59)
69
+ * @returns Date object
70
+ */
71
+ export const createDateInTimezone = (
72
+ timezone: string | undefined,
73
+ year: number,
74
+ month: number,
75
+ day: number,
76
+ hours: number = 0,
77
+ minutes: number = 0,
78
+ seconds: number = 0
79
+ ): Date => {
80
+ if (!timezone) {
81
+ return new Date(year, month, day, hours, minutes, seconds)
82
+ }
83
+
84
+ // Create an ISO string representing this moment in the target timezone
85
+ // Format: YYYY-MM-DDTHH:mm:ss
86
+ const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}T${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
87
+
88
+ // Try to parse this as a local time in the target timezone
89
+ // We'll create a date and then adjust it based on the timezone offset
90
+ const testDate = new Date(dateStr)
91
+ const testParts = getDatePartsInTimezone(testDate, timezone)
92
+
93
+ // Calculate the difference between what we want and what we got
94
+ const wantedTime = Date.UTC(year, month, day, hours, minutes, seconds)
95
+ const gotTime = Date.UTC(
96
+ testParts.year,
97
+ testParts.month,
98
+ testParts.day,
99
+ testParts.hours,
100
+ testParts.minutes,
101
+ testParts.seconds
102
+ )
103
+ const offset = wantedTime - gotTime
104
+
105
+ return new Date(testDate.getTime() + offset)
106
+ }
107
+
108
+ /**
109
+ * Gets the current date/time
110
+ * @param _timezone Timezone parameter (not used - now is the same moment everywhere)
111
+ * @returns Current Date object
112
+ */
113
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
114
+ export const getNowInTimezone = (_timezone: string | undefined): Date => {
115
+ return new Date()
116
+ }
117
+
118
+ /**
119
+ * Gets today's date (at midnight) in the specified timezone
120
+ * @param timezone IANA timezone string
121
+ * @returns Date object representing today at 00:00:00 in the specified timezone
122
+ */
123
+ export const getTodayInTimezone = (timezone: string | undefined): Date => {
124
+ const now = new Date()
125
+
126
+ if (!timezone) {
127
+ return now
128
+ }
129
+
130
+ // Get today's date in the target timezone
131
+ const parts = getDatePartsInTimezone(now, timezone)
132
+
133
+ // Create midnight for that date in that timezone
134
+ return new Date(
135
+ parts.year,
136
+ parts.month,
137
+ parts.day,
138
+ parts.hours,
139
+ parts.minutes,
140
+ parts.seconds
141
+ )
142
+ }
143
+
144
+ /**
145
+ * Formats a date according to the specified format
146
+ * @param date The date to format
147
+ * @param format The format string (e.g., "dd/MM/yyyy")
148
+ * @param timezone Optional IANA timezone string
149
+ * @returns The formatted date string or empty string if date is invalid
150
+ */
151
+ export const formatDate = (
152
+ date: Date,
153
+ format: string,
154
+ timezone?: string
155
+ ): string => {
156
+ if (!date || !isValidDate(date)) return ''
157
+
158
+ let day: number, month: number, year: number, hours: number, minutes: number
159
+
160
+ if (timezone) {
161
+ const formatter = new Intl.DateTimeFormat('en-US', {
162
+ timeZone: timezone,
163
+ year: 'numeric',
164
+ month: '2-digit',
165
+ day: '2-digit',
166
+ hour: '2-digit',
167
+ minute: '2-digit',
168
+ hour12: false,
169
+ })
170
+
171
+ const parts = formatter.formatToParts(date)
172
+ year = parseInt(parts.find((p) => p.type === 'year')?.value || '2024')
173
+ month = parseInt(parts.find((p) => p.type === 'month')?.value || '1')
174
+ day = parseInt(parts.find((p) => p.type === 'day')?.value || '1')
175
+ hours = parseInt(parts.find((p) => p.type === 'hour')?.value || '0')
176
+ minutes = parseInt(parts.find((p) => p.type === 'minute')?.value || '0')
177
+ } else {
178
+ day = date.getDate()
179
+ month = date.getMonth() + 1
180
+ year = date.getFullYear()
181
+ hours = date.getHours()
182
+ minutes = date.getMinutes()
183
+ }
184
+
185
+ return format
186
+ .replace('dd', day.toString().padStart(2, '0'))
187
+ .replace('MM', month.toString().padStart(2, '0'))
188
+ .replace('yyyy', year.toString())
189
+ .replace('HH', hours.toString().padStart(2, '0'))
190
+ .replace('mm', minutes.toString().padStart(2, '0'))
191
+ }
192
+
193
+ /**
194
+ * Parses a date string according to the specified format
195
+ * @param dateString The date string to parse
196
+ * @param format The format string
197
+ * @returns The parsed date or null if invalid
198
+ */
199
+ export const parseDate = (
200
+ dateString: string,
201
+ format: string,
202
+ hour: number,
203
+ minute: number
204
+ ): Date | null => {
205
+ try {
206
+ const formatParts = format.split(/[^a-zA-Z]/)
207
+ const dateParts = dateString.split(/[^0-9]/)
208
+
209
+ if (formatParts.length !== dateParts.length) return null
210
+
211
+ let day = 1,
212
+ month = 1,
213
+ year = new Date().getFullYear(),
214
+ hours = hour,
215
+ minutes = minute
216
+
217
+ formatParts.forEach((part, index) => {
218
+ const value = parseInt(dateParts[index])
219
+ if (isNaN(value)) return null
220
+
221
+ switch (part) {
222
+ case 'dd':
223
+ day = value
224
+ break
225
+ case 'MM':
226
+ month = value
227
+ break
228
+ case 'yyyy':
229
+ year = value
230
+ break
231
+ case 'hh':
232
+ hours = value
233
+ break
234
+ case 'mm':
235
+ minutes = value
236
+ break
237
+ default:
238
+ break
239
+ }
240
+ })
241
+
242
+ const date = new Date(year, month - 1, day, hours, minutes)
243
+ // createDateInTimezone(timezone, year, month - 1, day, hours, minutes)
244
+ return isValidDate(date) ? date : null
245
+ } catch {
246
+ return null
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Checks if a date is valid
252
+ * @param date The date to check
253
+ * @returns True if the date is valid
254
+ */
255
+ export const isValidDate = (date: Date): boolean => {
256
+ return date instanceof Date && !isNaN(date.getTime())
257
+ }
258
+
259
+ /**
260
+ * Formats time in 12-hour format
261
+ * @param date The date to format
262
+ * @returns The formatted time string
263
+ */
264
+ export const formatTimeIn12Hour = (date: Date): string => {
265
+ const hours = date.getHours()
266
+ const minutes = date.getMinutes()
267
+ const period = hours >= 12 ? 'PM' : 'AM'
268
+ const displayHours = hours % 12 === 0 ? 12 : hours % 12
269
+ return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`
270
+ }
271
+
272
+ /**
273
+ * Formats a date range for display
274
+ * @param range The date range to format
275
+ * @param showTime Whether to include time in the formatted string
276
+ * @returns The formatted date range string
277
+ */
278
+ export const formatDateRange = (
279
+ range: DateRange,
280
+ showTime: boolean = false
281
+ ): string => {
282
+ if (!range.startDate) {
283
+ return ''
284
+ }
285
+
286
+ const startFormat = showTime ? 'dd/MM/yyyy, HH:mm' : 'dd/MM/yyyy'
287
+ const endFormat = showTime ? 'dd/MM/yyyy, HH:mm' : 'dd/MM/yyyy'
288
+
289
+ const start = formatDate(range.startDate, startFormat)
290
+
291
+ if (!range.endDate) {
292
+ return start
293
+ }
294
+
295
+ const end = formatDate(range.endDate, endFormat)
296
+ return `${start} - ${end}`
297
+ }
298
+
299
+ /**
300
+ * Gets a date range based on a preset
301
+ * @param preset The preset to get the range for
302
+ * @param timezone Optional IANA timezone string for timezone-aware calculations
303
+ * @returns The date range for the preset
304
+ */
305
+ export const getPresetDateRange = (
306
+ preset: DateRangePreset,
307
+ timezone?: string
308
+ ): DateRange => {
309
+ const customDefinition = getCustomPresetDefinition(preset as string)
310
+ if (customDefinition) {
311
+ return customDefinition.getDateRange()
312
+ }
313
+
314
+ const now = getNowInTimezone(timezone)
315
+ const today = getTodayInTimezone(timezone)
316
+
317
+ switch (preset) {
318
+ case DateRangePreset.TODAY: {
319
+ // Today should be from 12:00 AM to current time
320
+ const startDate = createDateInTimezone(
321
+ timezone,
322
+ today.getFullYear(),
323
+ today.getMonth(),
324
+ today.getDate(),
325
+ 0,
326
+ 0,
327
+ 0
328
+ )
329
+ const endDate = now // Current time
330
+ return { startDate, endDate }
331
+ }
332
+
333
+ case DateRangePreset.YESTERDAY: {
334
+ // Yesterday: full day from 00:00:00 to 23:59:59 of the previous day
335
+ if (!timezone) {
336
+ const yesterday = new Date(today)
337
+ yesterday.setDate(yesterday.getDate() - 1)
338
+ return createSingleDateRange(yesterday, timezone)
339
+ }
340
+
341
+ // Get current date in target timezone and subtract 1 day
342
+ const nowInTz = new Date()
343
+ const parts = getDatePartsInTimezone(nowInTz, timezone)
344
+
345
+ // Subtract 1 day
346
+ const yesterdayDate = new Date(
347
+ parts.year,
348
+ parts.month,
349
+ parts.day - 1
350
+ )
351
+
352
+ // Create the full day range for yesterday in the target timezone
353
+ const startDate = createDateInTimezone(
354
+ timezone,
355
+ yesterdayDate.getFullYear(),
356
+ yesterdayDate.getMonth(),
357
+ yesterdayDate.getDate(),
358
+ 0,
359
+ 0,
360
+ 0
361
+ )
362
+ const endDate = createDateInTimezone(
363
+ timezone,
364
+ yesterdayDate.getFullYear(),
365
+ yesterdayDate.getMonth(),
366
+ yesterdayDate.getDate(),
367
+ 23,
368
+ 59,
369
+ 59
370
+ )
371
+
372
+ return { startDate, endDate }
373
+ }
374
+
375
+ case DateRangePreset.LAST_30_MINUTES: {
376
+ const thirtyMinsAgo = new Date(now.getTime() - 30 * 60 * 1000)
377
+ return { startDate: thirtyMinsAgo, endDate: now }
378
+ }
379
+
380
+ case DateRangePreset.TOMORROW: {
381
+ // Tomorrow: full day from 00:00:00 to 23:59:59 of the next day
382
+ if (!timezone) {
383
+ const tomorrow = new Date(today)
384
+ tomorrow.setDate(tomorrow.getDate() + 1)
385
+ return createSingleDateRange(tomorrow, timezone)
386
+ }
387
+
388
+ // Get current date in target timezone and add 1 day
389
+ const nowInTz = new Date()
390
+ const parts = getDatePartsInTimezone(nowInTz, timezone)
391
+
392
+ // Add 1 day
393
+ const tomorrowDate = new Date(
394
+ parts.year,
395
+ parts.month,
396
+ parts.day + 1
397
+ )
398
+
399
+ // Create the full day range for tomorrow in the target timezone
400
+ const startDate = createDateInTimezone(
401
+ timezone,
402
+ tomorrowDate.getFullYear(),
403
+ tomorrowDate.getMonth(),
404
+ tomorrowDate.getDate(),
405
+ 0,
406
+ 0,
407
+ 0
408
+ )
409
+ const endDate = createDateInTimezone(
410
+ timezone,
411
+ tomorrowDate.getFullYear(),
412
+ tomorrowDate.getMonth(),
413
+ tomorrowDate.getDate(),
414
+ 23,
415
+ 59,
416
+ 59
417
+ )
418
+
419
+ return { startDate, endDate }
420
+ }
421
+
422
+ case DateRangePreset.LAST_1_HOUR: {
423
+ const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000)
424
+ return { startDate: oneHourAgo, endDate: now }
425
+ }
426
+
427
+ case DateRangePreset.LAST_6_HOURS: {
428
+ const sixHoursAgo = new Date(now.getTime() - 6 * 60 * 60 * 1000)
429
+ return { startDate: sixHoursAgo, endDate: now }
430
+ }
431
+
432
+ case DateRangePreset.LAST_24_HOURS: {
433
+ const twentyFourHoursAgo = new Date(
434
+ now.getTime() - 24 * 60 * 60 * 1000
435
+ )
436
+ return { startDate: twentyFourHoursAgo, endDate: now }
437
+ }
438
+
439
+ case DateRangePreset.LAST_7_DAYS: {
440
+ // Last 7 days: from midnight 6 days ago to now
441
+ if (!timezone) {
442
+ const sevenDaysAgo = new Date(today)
443
+ sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 6)
444
+ sevenDaysAgo.setHours(0, 0, 0, 0)
445
+ return { startDate: sevenDaysAgo, endDate: now }
446
+ }
447
+
448
+ // Get current date in target timezone
449
+ const nowInTz = new Date()
450
+ const parts = getDatePartsInTimezone(nowInTz, timezone)
451
+
452
+ // Go back 6 days (6 days ago + today = 7 days)
453
+ const sevenDaysAgoDate = new Date(
454
+ parts.year,
455
+ parts.month,
456
+ parts.day - 6
457
+ )
458
+
459
+ const startDate = createDateInTimezone(
460
+ timezone,
461
+ sevenDaysAgoDate.getFullYear(),
462
+ sevenDaysAgoDate.getMonth(),
463
+ sevenDaysAgoDate.getDate(),
464
+ 0,
465
+ 0,
466
+ 0
467
+ )
468
+
469
+ return { startDate, endDate: now }
470
+ }
471
+
472
+ case DateRangePreset.LAST_30_DAYS: {
473
+ // Last 30 days: from midnight 29 days ago to now
474
+ if (!timezone) {
475
+ const thirtyDaysAgo = new Date(today)
476
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 29)
477
+ thirtyDaysAgo.setHours(0, 0, 0, 0)
478
+ return { startDate: thirtyDaysAgo, endDate: now }
479
+ }
480
+
481
+ // Get current date in target timezone
482
+ const nowInTz = new Date()
483
+ const parts = getDatePartsInTimezone(nowInTz, timezone)
484
+
485
+ // Go back 29 days (29 days ago + today = 30 days)
486
+ const thirtyDaysAgoDate = new Date(
487
+ parts.year,
488
+ parts.month,
489
+ parts.day - 29
490
+ )
491
+
492
+ const startDate = createDateInTimezone(
493
+ timezone,
494
+ thirtyDaysAgoDate.getFullYear(),
495
+ thirtyDaysAgoDate.getMonth(),
496
+ thirtyDaysAgoDate.getDate(),
497
+ 0,
498
+ 0,
499
+ 0
500
+ )
501
+
502
+ return { startDate, endDate: now }
503
+ }
504
+
505
+ case DateRangePreset.THIS_MONTH: {
506
+ // This month: Start of current month (1st day at 12:00 AM) to current time
507
+ const startOfMonth = createDateInTimezone(
508
+ timezone,
509
+ today.getFullYear(),
510
+ today.getMonth(),
511
+ 1,
512
+ 0,
513
+ 0,
514
+ 0
515
+ )
516
+ return { startDate: startOfMonth, endDate: now }
517
+ }
518
+
519
+ case DateRangePreset.LAST_MONTH: {
520
+ // Last month: Full previous month (1st day 12:00 AM to last day 11:59 PM)
521
+ const lastMonthDate = createDateInTimezone(
522
+ timezone,
523
+ today.getFullYear(),
524
+ today.getMonth() - 1,
525
+ 1
526
+ )
527
+ const startOfLastMonth = createDateInTimezone(
528
+ timezone,
529
+ lastMonthDate.getFullYear(),
530
+ lastMonthDate.getMonth(),
531
+ 1,
532
+ 0,
533
+ 0,
534
+ 0
535
+ )
536
+ // Get last day of last month (day 0 of current month)
537
+ const lastDayOfLastMonth = new Date(
538
+ lastMonthDate.getFullYear(),
539
+ lastMonthDate.getMonth() + 1,
540
+ 0
541
+ ).getDate()
542
+ const endOfLastMonth = createDateInTimezone(
543
+ timezone,
544
+ lastMonthDate.getFullYear(),
545
+ lastMonthDate.getMonth(),
546
+ lastDayOfLastMonth,
547
+ 23,
548
+ 59,
549
+ 59
550
+ )
551
+ return { startDate: startOfLastMonth, endDate: endOfLastMonth }
552
+ }
553
+
554
+ case DateRangePreset.LAST_3_MONTHS: {
555
+ // Last 3 months: Start from 12:00 AM of 3 months ago to current time
556
+ const threeMonthsAgoDate = new Date(now)
557
+ threeMonthsAgoDate.setMonth(threeMonthsAgoDate.getMonth() - 3)
558
+ const threeMonthsAgo = createDateInTimezone(
559
+ timezone,
560
+ threeMonthsAgoDate.getFullYear(),
561
+ threeMonthsAgoDate.getMonth(),
562
+ threeMonthsAgoDate.getDate(),
563
+ 0,
564
+ 0,
565
+ 0
566
+ )
567
+ return { startDate: threeMonthsAgo, endDate: now }
568
+ }
569
+
570
+ case DateRangePreset.LAST_12_MONTHS: {
571
+ // Last 12 months: Start from 12:00 AM of 12 months ago to current time
572
+ const twelveMonthsAgoDate = new Date(now)
573
+ twelveMonthsAgoDate.setFullYear(
574
+ twelveMonthsAgoDate.getFullYear() - 1
575
+ )
576
+ const twelveMonthsAgo = createDateInTimezone(
577
+ timezone,
578
+ twelveMonthsAgoDate.getFullYear(),
579
+ twelveMonthsAgoDate.getMonth(),
580
+ twelveMonthsAgoDate.getDate(),
581
+ 0,
582
+ 0,
583
+ 0
584
+ )
585
+ return { startDate: twelveMonthsAgo, endDate: now }
586
+ }
587
+
588
+ case DateRangePreset.NEXT_7_DAYS: {
589
+ // Next 7 days: from now to 23:59:59 of 6 days from today
590
+ if (!timezone) {
591
+ const sevenDaysLater = new Date(today)
592
+ sevenDaysLater.setDate(sevenDaysLater.getDate() + 6)
593
+ sevenDaysLater.setHours(23, 59, 59, 999)
594
+ return { startDate: now, endDate: sevenDaysLater }
595
+ }
596
+
597
+ // Get current date in target timezone
598
+ const nowInTz = new Date()
599
+ const parts = getDatePartsInTimezone(nowInTz, timezone)
600
+
601
+ // Go forward 6 days (today + 6 days = 7 days)
602
+ const sevenDaysLaterDate = new Date(
603
+ parts.year,
604
+ parts.month,
605
+ parts.day + 6
606
+ )
607
+
608
+ const endDate = createDateInTimezone(
609
+ timezone,
610
+ sevenDaysLaterDate.getFullYear(),
611
+ sevenDaysLaterDate.getMonth(),
612
+ sevenDaysLaterDate.getDate(),
613
+ 23,
614
+ 59,
615
+ 59
616
+ )
617
+
618
+ return { startDate: now, endDate }
619
+ }
620
+
621
+ case DateRangePreset.NEXT_30_DAYS: {
622
+ // Next 30 days: from now to 23:59:59 of 29 days from today
623
+ if (!timezone) {
624
+ const thirtyDaysLater = new Date(today)
625
+ thirtyDaysLater.setDate(thirtyDaysLater.getDate() + 29)
626
+ thirtyDaysLater.setHours(23, 59, 59, 999)
627
+ return { startDate: now, endDate: thirtyDaysLater }
628
+ }
629
+
630
+ // Get current date in target timezone
631
+ const nowInTz = new Date()
632
+ const parts = getDatePartsInTimezone(nowInTz, timezone)
633
+
634
+ // Go forward 29 days (today + 29 days = 30 days)
635
+ const thirtyDaysLaterDate = new Date(
636
+ parts.year,
637
+ parts.month,
638
+ parts.day + 29
639
+ )
640
+
641
+ const endDate = createDateInTimezone(
642
+ timezone,
643
+ thirtyDaysLaterDate.getFullYear(),
644
+ thirtyDaysLaterDate.getMonth(),
645
+ thirtyDaysLaterDate.getDate(),
646
+ 23,
647
+ 59,
648
+ 59
649
+ )
650
+
651
+ return { startDate: now, endDate }
652
+ }
653
+
654
+ case DateRangePreset.NEXT_3_MONTHS: {
655
+ // Next 3 months: From current time to 11:59 PM of 3 months later
656
+ const threeMonthsLaterDate = new Date(now)
657
+ threeMonthsLaterDate.setMonth(threeMonthsLaterDate.getMonth() + 3)
658
+ const threeMonthsLater = createDateInTimezone(
659
+ timezone,
660
+ threeMonthsLaterDate.getFullYear(),
661
+ threeMonthsLaterDate.getMonth(),
662
+ threeMonthsLaterDate.getDate(),
663
+ 23,
664
+ 59,
665
+ 59
666
+ )
667
+ return { startDate: now, endDate: threeMonthsLater }
668
+ }
669
+
670
+ case DateRangePreset.NEXT_12_MONTHS: {
671
+ // Next 12 months: From current time to 11:59 PM of 12 months later
672
+ const twelveMonthsLaterDate = new Date(now)
673
+ twelveMonthsLaterDate.setFullYear(
674
+ twelveMonthsLaterDate.getFullYear() + 1
675
+ )
676
+ const twelveMonthsLater = createDateInTimezone(
677
+ timezone,
678
+ twelveMonthsLaterDate.getFullYear(),
679
+ twelveMonthsLaterDate.getMonth(),
680
+ twelveMonthsLaterDate.getDate(),
681
+ 23,
682
+ 59,
683
+ 59
684
+ )
685
+ return { startDate: now, endDate: twelveMonthsLater }
686
+ }
687
+
688
+ default: {
689
+ return { startDate: today, endDate: today }
690
+ }
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Gets a label for a preset
696
+ * @param preset The preset to get the label for
697
+ * @returns The label for the preset
698
+ */
699
+ export const getPresetLabel = (preset: DateRangePreset): string => {
700
+ // Check if this is a custom preset
701
+ const customDefinition = getCustomPresetDefinition(preset as string)
702
+ if (customDefinition) {
703
+ return customDefinition.label
704
+ }
705
+
706
+ switch (preset) {
707
+ case DateRangePreset.TODAY:
708
+ return 'Today'
709
+ case DateRangePreset.YESTERDAY:
710
+ return 'Yesterday'
711
+ case DateRangePreset.TOMORROW:
712
+ return 'Tomorrow'
713
+ case DateRangePreset.LAST_30_MINUTES:
714
+ return 'Last 30 minutes'
715
+ case DateRangePreset.LAST_1_HOUR:
716
+ return 'Last 1 hour'
717
+ case DateRangePreset.LAST_6_HOURS:
718
+ return 'Last 6 hours'
719
+ case DateRangePreset.LAST_24_HOURS:
720
+ return 'Last 24 hours'
721
+ case DateRangePreset.LAST_7_DAYS:
722
+ return 'Last 7 days'
723
+ case DateRangePreset.LAST_30_DAYS:
724
+ return 'Last 30 days'
725
+ case DateRangePreset.THIS_MONTH:
726
+ return 'This month'
727
+ case DateRangePreset.LAST_MONTH:
728
+ return 'Last month'
729
+ case DateRangePreset.LAST_3_MONTHS:
730
+ return 'Last 3 months'
731
+ case DateRangePreset.LAST_12_MONTHS:
732
+ return 'Last 12 months'
733
+ case DateRangePreset.NEXT_7_DAYS:
734
+ return 'Next 7 days'
735
+ case DateRangePreset.NEXT_30_DAYS:
736
+ return 'Next 30 days'
737
+ case DateRangePreset.NEXT_3_MONTHS:
738
+ return 'Next 3 months'
739
+ case DateRangePreset.NEXT_12_MONTHS:
740
+ return 'Next 12 months'
741
+ case DateRangePreset.CUSTOM:
742
+ return 'Custom'
743
+ default:
744
+ return 'Select Range'
745
+ }
746
+ }
747
+
748
+ /**
749
+ * Formats time string to HH:MM format
750
+ * @param time The time string to format
751
+ * @returns The formatted time string
752
+ */
753
+ export const formatTime = (time: string): string => {
754
+ const [hours, minutes] = time.split(':')
755
+ const h = parseInt(hours) || 0
756
+ const m = parseInt(minutes) || 0
757
+
758
+ return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`
759
+ }
760
+
761
+ /**
762
+ * Validates a time string
763
+ * @param time The time string to validate
764
+ * @returns True if the time is valid
765
+ */
766
+ export const isValidTime = (time: string): boolean => {
767
+ const timeRegex = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/
768
+ return timeRegex.test(time)
769
+ }
770
+
771
+ /**
772
+ * Converts a date to string with optional time
773
+ * @param date The date to convert
774
+ * @param includeTime Whether to include time
775
+ * @param timeFormat The time format to use
776
+ * @returns The formatted date string
777
+ */
778
+ export const dateToString = (
779
+ date: Date,
780
+ includeTime?: boolean,
781
+ timeFormat?: string
782
+ ): string => {
783
+ const dateStr = formatDate(date, 'dd/MM/yyyy')
784
+
785
+ if (includeTime && timeFormat) {
786
+ const timeStr = formatDate(date, timeFormat)
787
+ return `${dateStr} ${timeStr}`
788
+ }
789
+
790
+ return dateStr
791
+ }
792
+
793
+ /**
794
+ * Checks if two dates are the same day
795
+ * @param date1 First date
796
+ * @param date2 Second date
797
+ * @returns True if dates are the same day
798
+ */
799
+ export const isSameDay = (date1: Date, date2: Date): boolean => {
800
+ return (
801
+ date1.getFullYear() === date2.getFullYear() &&
802
+ date1.getMonth() === date2.getMonth() &&
803
+ date1.getDate() === date2.getDate()
804
+ )
805
+ }
806
+
807
+ /**
808
+ * Checks if two dates are the same day
809
+ * @param parts1 Contains year, month, day of first date
810
+ * @param parts2 Contains year, month, day of second date
811
+ * @returns True if dates are the same day
812
+ */
813
+ export const isSameParts = (
814
+ part1: { year: number; month: number; day: number },
815
+ part2: { year: number; month: number; day: number }
816
+ ): boolean => {
817
+ return (
818
+ part1.year === part2.year &&
819
+ part1.month === part2.month &&
820
+ part1.day === part2.day
821
+ )
822
+ }
823
+
824
+ /**
825
+ * Checks if a date is within a range
826
+ * @param date The date to check
827
+ * @param startDate Range start date
828
+ * @param endDate Range end date
829
+ * @returns True if date is in range
830
+ */
831
+ export const isDateInRange = (
832
+ date: Date,
833
+ startDate: Date,
834
+ endDate: Date
835
+ ): boolean => {
836
+ const dateTime = date.getTime()
837
+ const startTime = startDate.getTime()
838
+ const endTime = endDate.getTime()
839
+
840
+ return dateTime >= startTime && dateTime <= endTime
841
+ }
842
+
843
+ /**
844
+ * Gets the number of days in a month
845
+ * @param year The year
846
+ * @param month The month (0-based)
847
+ * @returns The number of days in the month
848
+ */
849
+ export const getDaysInMonth = (year: number, month: number): number => {
850
+ return new Date(year, month + 1, 0).getDate()
851
+ }
852
+
853
+ /**
854
+ * Gets the first day of the month (0 = Sunday)
855
+ * @param year The year
856
+ * @param month The month (0-based)
857
+ * @returns The day of the week (0-6)
858
+ */
859
+ export const getFirstDayOfMonth = (year: number, month: number): number => {
860
+ return new Date(year, month, 1).getDay()
861
+ }
862
+
863
+ /**
864
+ * Generates a calendar grid for a month
865
+ * @param year The year
866
+ * @param month The month (0-based)
867
+ * @returns Array of weeks, each containing day numbers or null for empty cells
868
+ */
869
+ export const generateCalendarGrid = (
870
+ year: number,
871
+ month: number
872
+ ): (number | null)[][] => {
873
+ const daysInMonth = getDaysInMonth(year, month)
874
+ const firstDay = getFirstDayOfMonth(year, month)
875
+
876
+ const weeks: (number | null)[][] = []
877
+ let currentWeek: (number | null)[] = []
878
+
879
+ for (let i = 0; i < firstDay; i++) {
880
+ currentWeek.push(null)
881
+ }
882
+
883
+ for (let day = 1; day <= daysInMonth; day++) {
884
+ currentWeek.push(day)
885
+
886
+ if (currentWeek.length === 7) {
887
+ weeks.push(currentWeek)
888
+ currentWeek = []
889
+ }
890
+ }
891
+
892
+ while (currentWeek.length > 0 && currentWeek.length < 7) {
893
+ currentWeek.push(null)
894
+ }
895
+
896
+ if (currentWeek.length > 0) {
897
+ weeks.push(currentWeek)
898
+ }
899
+
900
+ return weeks
901
+ }
902
+
903
+ /**
904
+ * Checks if a date is the start date of a range
905
+ * @param date The date to check
906
+ * @param selectedRange The selected date range
907
+ * @param timezone The timezone for the date range
908
+ * @returns True if the date is the start date
909
+ */
910
+ export const isStartDate = (
911
+ date: Date,
912
+ startDate: Date,
913
+ timezone?: string
914
+ ): boolean => {
915
+ if (!startDate) return false
916
+ const parts = timezone
917
+ ? getDatePartsInTimezone(startDate, timezone)
918
+ : {
919
+ year: startDate.getFullYear(),
920
+ month: startDate.getMonth(),
921
+ day: startDate.getDate(),
922
+ }
923
+
924
+ return (
925
+ date.getDate() === parts.day &&
926
+ date.getMonth() === parts.month &&
927
+ date.getFullYear() === parts.year
928
+ )
929
+ }
930
+
931
+ /**
932
+ * Checks if a date is the end date of a range (but not if it's the same as start date)
933
+ * @param date The date to check
934
+ * @param selectedRange The selected date range
935
+ * @returns True if the date is the end date and different from start date
936
+ */
937
+ export const isEndDate = (
938
+ date: Date,
939
+ startDate: Date,
940
+ endDate: Date,
941
+ timezone?: string
942
+ ): boolean => {
943
+ if (!endDate || !startDate) return false
944
+
945
+ const partsEnd = timezone
946
+ ? getDatePartsInTimezone(endDate, timezone)
947
+ : {
948
+ year: endDate.getFullYear(),
949
+ month: endDate.getMonth(),
950
+ day: endDate.getDate(),
951
+ }
952
+
953
+ return (
954
+ date.getDate() === partsEnd.day &&
955
+ date.getMonth() === partsEnd.month &&
956
+ date.getFullYear() === partsEnd.year
957
+ )
958
+ }
959
+
960
+ /**
961
+ * Checks if a date is within a selected range (not including start/end)
962
+ * @param date The date to check
963
+ * @param selectedRange The selected date range
964
+ * @returns True if the date is in the range
965
+ */
966
+ export const isInSelectedRange = (
967
+ date: Date,
968
+ selectedRange: DateRange,
969
+ timezone?: string
970
+ ): boolean => {
971
+ if (!selectedRange.startDate || !selectedRange.endDate) return false
972
+ const startDate = convertLocalDateToTimezoneDate(
973
+ selectedRange.startDate,
974
+ timezone
975
+ )
976
+ const endDate = convertLocalDateToTimezoneDate(
977
+ selectedRange.endDate,
978
+ timezone
979
+ )
980
+ return date > startDate && date < endDate
981
+ }
982
+
983
+ /**
984
+ * Checks if a date is today
985
+ * @param date The date to check
986
+ * @param today Today's date
987
+ * @returns True if the date is today
988
+ */
989
+ export const isDateToday = (date: Date, today: Date): boolean => {
990
+ return (
991
+ date.getDate() === today.getDate() &&
992
+ date.getMonth() === today.getMonth() &&
993
+ date.getFullYear() === today.getFullYear()
994
+ )
995
+ }
996
+
997
+ /**
998
+ * Creates a proper single date range (24-hour selection within the same day)
999
+ * @param date The date to create a range for
1000
+ * @returns DateRange spanning the entire day (same date, different times)
1001
+ */
1002
+ export const createSingleDateRange = (
1003
+ date: Date,
1004
+ timezone?: string,
1005
+ disableFutureDates: boolean = false,
1006
+ disablePastDates: boolean = false,
1007
+ today?: Date
1008
+ ): DateRange => {
1009
+ const startDateTimeCheck =
1010
+ disablePastDates && today && isDateToday(date, today)
1011
+ // Start of day (00:00:00)
1012
+ const startDate = createDateInTimezone(
1013
+ timezone,
1014
+ date.getFullYear(),
1015
+ date.getMonth(),
1016
+ date.getDate(),
1017
+ startDateTimeCheck ? today.getHours() : 0,
1018
+ startDateTimeCheck ? today.getMinutes() : 0,
1019
+ startDateTimeCheck ? today.getSeconds() : 0
1020
+ )
1021
+ const endDateTimeCheck =
1022
+ disableFutureDates && today && isDateToday(date, today)
1023
+
1024
+ // End of day (23:59:59.999) - same date but end of day
1025
+ const endDate = createDateInTimezone(
1026
+ timezone,
1027
+ date.getFullYear(),
1028
+ date.getMonth(),
1029
+ date.getDate(),
1030
+ endDateTimeCheck ? today.getHours() : 23,
1031
+ endDateTimeCheck ? today.getMinutes() : 59,
1032
+ endDateTimeCheck ? today.getSeconds() : 59
1033
+ )
1034
+
1035
+ return { startDate, endDate }
1036
+ }
1037
+
1038
+ /**
1039
+ * Creates a date at start of day (00:00:00)
1040
+ */
1041
+ const createStartOfDay = (
1042
+ date: Date,
1043
+ disablePastDates: boolean = false,
1044
+ today?: Date,
1045
+ timezone?: string
1046
+ ): Date => {
1047
+ const startDateTimeCheck =
1048
+ disablePastDates && today && isDateToday(date, today)
1049
+ return createDateInTimezone(
1050
+ timezone,
1051
+ date.getFullYear(),
1052
+ date.getMonth(),
1053
+ date.getDate(),
1054
+ startDateTimeCheck ? today.getHours() : 0,
1055
+ startDateTimeCheck ? today.getMinutes() : 0,
1056
+ startDateTimeCheck ? today.getSeconds() : 0
1057
+ )
1058
+ }
1059
+
1060
+ /**
1061
+ * Creates a date at end of day (23:59:59.999 same day)
1062
+ */
1063
+ const createEndOfDay = (
1064
+ date: Date,
1065
+ disableFutureDates: boolean = false,
1066
+ today?: Date,
1067
+ timezone?: string
1068
+ ): Date => {
1069
+ const endDateTimeCheck =
1070
+ disableFutureDates && today && isDateToday(date, today)
1071
+ return createDateInTimezone(
1072
+ timezone,
1073
+ date.getFullYear(),
1074
+ date.getMonth(),
1075
+ date.getDate(),
1076
+ endDateTimeCheck ? today.getHours() : 23,
1077
+ endDateTimeCheck ? today.getMinutes() : 59,
1078
+ endDateTimeCheck ? today.getSeconds() : 59
1079
+ )
1080
+ }
1081
+
1082
+ /**
1083
+ * Checks if we have a complete range (both start and end dates that are different)
1084
+ */
1085
+ const hasCompleteRange = (range: DateRange): boolean => {
1086
+ return !!(
1087
+ range.startDate &&
1088
+ range.endDate &&
1089
+ !isSameDay(range.startDate, range.endDate)
1090
+ )
1091
+ }
1092
+
1093
+ /**
1094
+ * Handles date click logic for calendar with clean first click = start, second click = end pattern
1095
+ * @param clickedDate The date that was clicked
1096
+ * @param selectedRange Current selected range
1097
+ * @param allowSingleDateSelection Whether single date selection is allowed
1098
+ * @param today Today's date for validation
1099
+ * @param disableFutureDates Whether future dates are disabled
1100
+ * @param disablePastDates Whether past dates are disabled
1101
+ * @param isDoubleClick Whether this is a double-click event
1102
+ * @returns New date range or null if click should be ignored
1103
+ */
1104
+ export const handleCalendarDateClick = (
1105
+ clickedDate: Date,
1106
+ allowSingleDateSelection: boolean = false,
1107
+ today: Date,
1108
+ disableFutureDates: boolean = false,
1109
+ disablePastDates: boolean = false,
1110
+ isDoubleClick: boolean = false,
1111
+ timezone?: string,
1112
+ selectedRange?: DateRange,
1113
+ isSingleDatePicker?: boolean
1114
+ ): DateRange | null => {
1115
+ // Validate date is not disabled
1116
+ if (
1117
+ (disableFutureDates && clickedDate > today) ||
1118
+ (disablePastDates && clickedDate < today)
1119
+ ) {
1120
+ return null
1121
+ }
1122
+
1123
+ // Create clean date without time components for comparison
1124
+ const clickedDateOnly = new Date(
1125
+ clickedDate.getFullYear(),
1126
+ clickedDate.getMonth(),
1127
+ clickedDate.getDate()
1128
+ )
1129
+
1130
+ // Handle double click - always create single date range if allowed
1131
+ if (isDoubleClick && allowSingleDateSelection) {
1132
+ return createSingleDateRange(
1133
+ clickedDateOnly,
1134
+ timezone,
1135
+ disableFutureDates,
1136
+ disablePastDates,
1137
+ today
1138
+ )
1139
+ }
1140
+
1141
+ // Case 1: No selection - first click sets start date
1142
+ if (
1143
+ isSingleDatePicker ||
1144
+ !selectedRange ||
1145
+ (selectedRange.startDate && selectedRange.endDate)
1146
+ ) {
1147
+ const startDate = createStartOfDay(
1148
+ clickedDateOnly,
1149
+ disablePastDates,
1150
+ today,
1151
+ timezone
1152
+ )
1153
+ return { startDate }
1154
+ }
1155
+
1156
+ const endDate = createEndOfDay(
1157
+ clickedDateOnly,
1158
+ disableFutureDates,
1159
+ today,
1160
+ timezone
1161
+ )
1162
+
1163
+ if (endDate > selectedRange.startDate) {
1164
+ return {
1165
+ startDate: selectedRange.startDate,
1166
+ endDate: createEndOfDay(
1167
+ clickedDateOnly,
1168
+ disableFutureDates,
1169
+ today,
1170
+ timezone
1171
+ ),
1172
+ }
1173
+ } else {
1174
+ const startDate = createStartOfDay(
1175
+ clickedDateOnly,
1176
+ disablePastDates,
1177
+ today,
1178
+ timezone
1179
+ )
1180
+ return { startDate }
1181
+ }
1182
+ }
1183
+
1184
+ /**
1185
+ * Generates calendar weeks for a specific month with consistent alignment
1186
+ * @param year The year
1187
+ * @param month The month (0-based)
1188
+ * @returns Array of weeks with day numbers or null for empty cells
1189
+ */
1190
+ export const generateMonthWeeks = (
1191
+ year: number,
1192
+ month: number
1193
+ ): (number | null)[][] => {
1194
+ const daysInMonth = getDaysInMonth(year, month)
1195
+ const firstDayOfMonth = getFirstDayOfMonth(year, month)
1196
+
1197
+ const firstDayAdjusted = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1
1198
+
1199
+ const weeks = []
1200
+ let week = Array(7).fill(null)
1201
+ let dayCounter = 1
1202
+
1203
+ for (let i = firstDayAdjusted; i < 7 && dayCounter <= daysInMonth; i++) {
1204
+ week[i] = dayCounter++
1205
+ }
1206
+ weeks.push(week)
1207
+
1208
+ while (dayCounter <= daysInMonth) {
1209
+ week = Array(7).fill(null)
1210
+ for (let i = 0; i < 7 && dayCounter <= daysInMonth; i++) {
1211
+ week[i] = dayCounter++
1212
+ }
1213
+ weeks.push(week)
1214
+ }
1215
+
1216
+ return weeks
1217
+ }
1218
+
1219
+ /**
1220
+ * Generates the list of months to display in calendar
1221
+ * @param startYear Starting year
1222
+ * @param startMonth Starting month (0-based)
1223
+ * @param endYear Ending year
1224
+ * @returns Array of month/year objects
1225
+ */
1226
+ export const generateCalendarMonths = (
1227
+ startYear: number = DATE_RANGE_PICKER_CONSTANTS.MIN_YEAR,
1228
+ startMonth: number = 0,
1229
+ endYear?: number
1230
+ ): { month: number; year: number }[] => {
1231
+ const months = []
1232
+ const currentDate = new Date()
1233
+ const finalEndYear = endYear || currentDate.getFullYear() + 5
1234
+
1235
+ for (let year = startYear; year <= finalEndYear; year++) {
1236
+ const monthStart = year === startYear ? startMonth : 0
1237
+ for (let month = monthStart; month <= 11; month++) {
1238
+ months.push({ month, year })
1239
+ }
1240
+ }
1241
+
1242
+ return months
1243
+ }
1244
+
1245
+ /**
1246
+ * Generates initial months around current date (4-5 months)
1247
+ * @param today Current date
1248
+ * @returns Array of initial months to display
1249
+ */
1250
+ export const generateInitialMonths = (
1251
+ today: Date
1252
+ ): { month: number; year: number }[] => {
1253
+ const months = []
1254
+ const currentYear = today.getFullYear()
1255
+ const currentMonth = today.getMonth()
1256
+
1257
+ for (let i = -2; i <= 2; i++) {
1258
+ const date = new Date(currentYear, currentMonth + i, 1)
1259
+ months.push({
1260
+ month: date.getMonth(),
1261
+ year: date.getFullYear(),
1262
+ })
1263
+ }
1264
+
1265
+ return months
1266
+ }
1267
+
1268
+ /**
1269
+ * Generates a chunk of months for progressive loading
1270
+ * @param startYear Starting year
1271
+ * @param startMonth Starting month (0-based)
1272
+ * @param endYear Ending year for this chunk
1273
+ * @param endMonth Ending month for this chunk (0-based)
1274
+ * @returns Array of months for the chunk
1275
+ */
1276
+ export const generateMonthChunk = (
1277
+ startYear: number,
1278
+ startMonth: number,
1279
+ endYear: number,
1280
+ endMonth: number = 11
1281
+ ): { month: number; year: number }[] => {
1282
+ const months = []
1283
+
1284
+ for (let year = startYear; year <= endYear; year++) {
1285
+ const monthStart = year === startYear ? startMonth : 0
1286
+ const monthEnd = year === endYear ? endMonth : 11
1287
+
1288
+ for (let month = monthStart; month <= monthEnd; month++) {
1289
+ months.push({ month, year })
1290
+ }
1291
+ }
1292
+
1293
+ return months
1294
+ }
1295
+
1296
+ /**
1297
+ * Calculates the next chunk to load based on current data
1298
+ * @param currentMonths Currently loaded months
1299
+ * @param direction Direction to load ('past' or 'future')
1300
+ * @returns Next chunk parameters or null if reached bounds
1301
+ */
1302
+ export const getNextChunkParams = (
1303
+ currentMonths: { month: number; year: number }[],
1304
+ direction: 'past' | 'future'
1305
+ ): { startYear: number; startMonth: number } | null => {
1306
+ const MIN_YEAR = DATE_RANGE_PICKER_CONSTANTS.MIN_YEAR
1307
+ const MAX_YEAR = new Date().getFullYear() + 10
1308
+
1309
+ if (direction === 'past') {
1310
+ const firstMonth = currentMonths[0]
1311
+
1312
+ if (firstMonth.year <= MIN_YEAR && firstMonth.month === 0) {
1313
+ return null
1314
+ }
1315
+
1316
+ const targetYear = Math.max(MIN_YEAR, firstMonth.year - 3)
1317
+ return {
1318
+ startYear: targetYear,
1319
+ startMonth: targetYear === MIN_YEAR ? 0 : 0,
1320
+ }
1321
+ } else {
1322
+ const lastMonth = currentMonths[currentMonths.length - 1]
1323
+
1324
+ if (lastMonth.year >= MAX_YEAR) {
1325
+ return null
1326
+ }
1327
+
1328
+ const nextMonth = lastMonth.month === 11 ? 0 : lastMonth.month + 1
1329
+ const nextYear =
1330
+ lastMonth.month === 11 ? lastMonth.year + 1 : lastMonth.year
1331
+ return { startYear: nextYear, startMonth: nextMonth }
1332
+ }
1333
+ }
1334
+
1335
+ /**
1336
+ * Gets month name from month index
1337
+ * @param monthIndex Month index (0-based)
1338
+ * @returns Month name
1339
+ */
1340
+ export const getMonthName = (monthIndex: number): string => {
1341
+ const monthNames = [
1342
+ 'January',
1343
+ 'February',
1344
+ 'March',
1345
+ 'April',
1346
+ 'May',
1347
+ 'June',
1348
+ 'July',
1349
+ 'August',
1350
+ 'September',
1351
+ 'October',
1352
+ 'November',
1353
+ 'December',
1354
+ ]
1355
+ return monthNames[monthIndex]
1356
+ }
1357
+
1358
+ /**
1359
+ * Gets day names for calendar header
1360
+ * @returns Array of day names
1361
+ */
1362
+ export const getDayNames = (): string[] => {
1363
+ return ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']
1364
+ }
1365
+
1366
+ /**
1367
+ * Calculates the height of a single month in the calendar
1368
+ * @param year The year of the month
1369
+ * @param month The month (0-based)
1370
+ * @returns Height in pixels
1371
+ */
1372
+ export const getMonthHeight = (year?: number, month?: number): number => {
1373
+ if (year !== undefined && month !== undefined) {
1374
+ const weeks = generateMonthWeeks(year, month)
1375
+ const numberOfWeeks = weeks.length
1376
+ // Month header height (32px) + consistent margin (16px) + actual weeks * 40px per week + bottom padding (16px)
1377
+ return 32 + 16 + numberOfWeeks * 40 + 16
1378
+ }
1379
+ // Default fallback for when year/month not provided (6 weeks max)
1380
+ return 32 + 16 + 6 * 40 + 16
1381
+ }
1382
+
1383
+ /**
1384
+ * Calculates which months should be visible in the viewport
1385
+ * @param scrollTop Current scroll position
1386
+ * @param containerHeight Height of the scrollable container
1387
+ * @param months Array of all months
1388
+ * @param monthHeight Height of each month
1389
+ * @param buffer Number of months to render outside viewport for smooth scrolling
1390
+ * @returns Object with start/end indices and visible months
1391
+ */
1392
+ export const getVisibleMonths = (
1393
+ scrollTop: number,
1394
+ containerHeight: number,
1395
+ months: { month: number; year: number }[],
1396
+ monthHeight: number,
1397
+ buffer: number = 12
1398
+ ): {
1399
+ startIndex: number
1400
+ endIndex: number
1401
+ visibleMonths: { month: number; year: number; index: number }[]
1402
+ totalHeight: number
1403
+ } => {
1404
+ const totalHeight = months.length * monthHeight
1405
+
1406
+ const startIndex = Math.max(0, Math.floor(scrollTop / monthHeight) - buffer)
1407
+ const endIndex = Math.min(
1408
+ months.length - 1,
1409
+ Math.ceil((scrollTop + containerHeight) / monthHeight) + buffer
1410
+ )
1411
+
1412
+ const visibleMonths = months
1413
+ .slice(startIndex, endIndex + 1)
1414
+ .map((month, i) => ({
1415
+ ...month,
1416
+ index: startIndex + i,
1417
+ }))
1418
+
1419
+ return {
1420
+ startIndex,
1421
+ endIndex,
1422
+ visibleMonths,
1423
+ totalHeight,
1424
+ }
1425
+ }
1426
+
1427
+ /**
1428
+ * Throttle function to limit how often a function can be called
1429
+ * @param func Function to throttle
1430
+ * @param limit Time limit in milliseconds
1431
+ * @returns Throttled function
1432
+ */
1433
+ export const throttle = <T extends (...args: unknown[]) => unknown>(
1434
+ func: T,
1435
+ limit: number
1436
+ ): ((...args: Parameters<T>) => void) => {
1437
+ let inThrottle: boolean
1438
+ return function (this: unknown, ...args: Parameters<T>) {
1439
+ if (!inThrottle) {
1440
+ func.apply(this, args)
1441
+ inThrottle = true
1442
+ setTimeout(() => (inThrottle = false), limit)
1443
+ }
1444
+ }
1445
+ }
1446
+
1447
+ /**
1448
+ * Calculates the top offset for a month at a given index
1449
+ * @param index Month index
1450
+ * @param monthHeight Height of each month
1451
+ * @returns Top offset in pixels
1452
+ */
1453
+ export const getMonthOffset = (index: number, monthHeight: number): number => {
1454
+ return index * monthHeight
1455
+ }
1456
+
1457
+ /**
1458
+ * Finds the month that contains today's date
1459
+ * @param months Array of months
1460
+ * @param today Today's date
1461
+ * @returns Index of the month containing today
1462
+ */
1463
+ export const findCurrentMonthIndex = (
1464
+ months: { month: number; year: number }[],
1465
+ targetMonth: number,
1466
+ targetYear: number
1467
+ ): number => {
1468
+ return months.findIndex(
1469
+ ({ month, year }) => month === targetMonth && year === targetYear
1470
+ )
1471
+ }
1472
+
1473
+ /**
1474
+ * Scrolls to a specific month
1475
+ * @param monthIndex Index of the month to scroll to
1476
+ * @param monthHeight Height of each month
1477
+ * @returns Scroll position
1478
+ */
1479
+ export const getScrollToMonth = (
1480
+ monthIndex: number,
1481
+ monthHeight: number
1482
+ ): number => {
1483
+ return monthIndex * monthHeight
1484
+ }
1485
+
1486
+ /**
1487
+ * Gets all the states for a date cell
1488
+ * @param date The date to check
1489
+ * @param selectedRange Current selected range
1490
+ * @param today Today's date
1491
+ * @param disableFutureDates Whether future dates are disabled
1492
+ * @param disablePastDates Whether past dates are disabled
1493
+ * @param customDisableDates Custom function to disable specific dates
1494
+ * @returns Object with all date states
1495
+ */
1496
+ export const getDateCellStates = (
1497
+ date: Date,
1498
+ selectedRange: DateRange | undefined,
1499
+ today: Date,
1500
+ disableFutureDates: boolean = false,
1501
+ disablePastDates: boolean = false,
1502
+ customDisableDates?: (date: Date) => boolean,
1503
+ timezone?: string,
1504
+ isSingleDatePicker?: boolean
1505
+ ) => {
1506
+ const isTodayDay = isDateToday(date, today)
1507
+ const isDisabled = Boolean(
1508
+ (disableFutureDates && date > today) ||
1509
+ (disablePastDates && date < today) ||
1510
+ (customDisableDates && customDisableDates(date))
1511
+ )
1512
+
1513
+ if (!selectedRange) {
1514
+ return {
1515
+ isStart: false,
1516
+ isEnd: false,
1517
+ isRangeDay: false,
1518
+ isTodayDay: isTodayDay,
1519
+ isSingleDate: false,
1520
+ isDisabled: isDisabled,
1521
+ }
1522
+ }
1523
+
1524
+ const isStart =
1525
+ selectedRange && isStartDate(date, selectedRange.startDate, timezone)
1526
+ const isEnd =
1527
+ !!selectedRange.endDate &&
1528
+ isEndDate(
1529
+ date,
1530
+ selectedRange.startDate,
1531
+ selectedRange.endDate,
1532
+ timezone
1533
+ )
1534
+ const isRangeDay =
1535
+ !!selectedRange.endDate &&
1536
+ isInSelectedRange(date, selectedRange as DateRange, timezone)
1537
+
1538
+ // For single date selection, only show as single date if start and end are the same day
1539
+ const isSingleDate =
1540
+ (isSingleDatePicker && isStart) ||
1541
+ (isStart &&
1542
+ selectedRange.startDate &&
1543
+ !!selectedRange.endDate &&
1544
+ isSameDay(
1545
+ convertLocalDateToTimezoneDate(
1546
+ selectedRange.startDate,
1547
+ timezone
1548
+ ),
1549
+ convertLocalDateToTimezoneDate(selectedRange.endDate, timezone)
1550
+ ))
1551
+
1552
+ return {
1553
+ isStart,
1554
+ isEnd,
1555
+ isRangeDay,
1556
+ isTodayDay,
1557
+ isSingleDate,
1558
+ isDisabled,
1559
+ }
1560
+ }
1561
+
1562
+ /**
1563
+ * Determines if a today indicator should be shown
1564
+ * @param dateStates Object containing all date states
1565
+ * @returns Boolean indicating if today indicator should be shown
1566
+ */
1567
+ export const shouldShowTodayIndicator = (
1568
+ dateStates: ReturnType<typeof getDateCellStates>
1569
+ ): boolean => {
1570
+ const { isTodayDay, isStart, isEnd, isRangeDay } = dateStates
1571
+ return isTodayDay && !isStart && !isEnd && !isRangeDay
1572
+ }
1573
+
1574
+ /**
1575
+ * Validation result for date input
1576
+ */
1577
+ export type DateValidationResult = {
1578
+ isValid: boolean
1579
+ error: 'none' | 'format' | 'invalid-date' | 'out-of-range'
1580
+ message?: string
1581
+ }
1582
+
1583
+ /**
1584
+ * Interface for date range picker tokens used in styling functions
1585
+ */
1586
+ export type DateRangePickerTokens = {
1587
+ calendar: {
1588
+ dayCell: Record<string, unknown>
1589
+ singleDate: Record<string, unknown>
1590
+ startDate: Record<string, unknown>
1591
+ endDate: Record<string, unknown>
1592
+ rangeDay: Record<string, unknown>
1593
+ todayDay: Record<string, unknown>
1594
+ }
1595
+ states: {
1596
+ disabledDay: Record<string, unknown>
1597
+ }
1598
+ text: {
1599
+ selectedDay: {
1600
+ color?: string | unknown
1601
+ }
1602
+ todayDay: {
1603
+ color?: string | unknown
1604
+ }
1605
+ dayNumber: {
1606
+ color?: string | unknown
1607
+ }
1608
+ }
1609
+ }
1610
+
1611
+ /**
1612
+ * Validates date format and date values
1613
+ * @param value The input value to validate
1614
+ * @param format The expected format (e.g., 'dd/MM/yyyy')
1615
+ * @param disableFutureDates Whether future dates should be disabled
1616
+ * @param disablePastDates Whether past dates should be disabled
1617
+ * @returns Validation result with specific error type
1618
+ */
1619
+ export const validateDateInput = (
1620
+ value: string,
1621
+ format: string,
1622
+ disableFutureDates: boolean = false,
1623
+ disablePastDates: boolean = false,
1624
+ timezone?: string
1625
+ ): DateValidationResult => {
1626
+ if (!value || value.length === 0) {
1627
+ return { isValid: true, error: 'none' }
1628
+ }
1629
+
1630
+ if (format === 'dd/MM/yyyy') {
1631
+ const dateRegex = /^(\d{2})\/(\d{2})\/(\d{4})$/
1632
+ const match = value.match(dateRegex)
1633
+
1634
+ if (!match) {
1635
+ return {
1636
+ isValid: false,
1637
+ error: 'format',
1638
+ message: 'Invalid date',
1639
+ }
1640
+ }
1641
+
1642
+ const day = parseInt(match[1], 10)
1643
+ const month = parseInt(match[2], 10)
1644
+ const year = parseInt(match[3], 10)
1645
+
1646
+ if (year < 2001 || year > 2100) {
1647
+ return {
1648
+ isValid: false,
1649
+ error: 'out-of-range',
1650
+ message: 'Date not in range',
1651
+ }
1652
+ }
1653
+
1654
+ if (month < 1 || month > 12) {
1655
+ return {
1656
+ isValid: false,
1657
+ error: 'invalid-date',
1658
+ message: 'Invalid date',
1659
+ }
1660
+ }
1661
+
1662
+ if (day < 1 || day > 31) {
1663
+ return {
1664
+ isValid: false,
1665
+ error: 'invalid-date',
1666
+ message: 'Invalid date',
1667
+ }
1668
+ }
1669
+
1670
+ const date = new Date(year, month - 1, day)
1671
+
1672
+ // Check future/past date restrictions
1673
+ const today = getTodayInTimezone(timezone)
1674
+ today.setHours(0, 0, 0, 0) // Compare dates only, not times
1675
+ date.setHours(0, 0, 0, 0)
1676
+
1677
+ if (disableFutureDates && date > today) {
1678
+ return {
1679
+ isValid: false,
1680
+ error: 'out-of-range',
1681
+ message: 'Future dates are not allowed',
1682
+ }
1683
+ }
1684
+
1685
+ if (disablePastDates && date < today) {
1686
+ return {
1687
+ isValid: false,
1688
+ error: 'out-of-range',
1689
+ message: 'Past dates are not allowed',
1690
+ }
1691
+ }
1692
+
1693
+ return { isValid: true, error: 'none' }
1694
+ }
1695
+
1696
+ return { isValid: true, error: 'none' }
1697
+ }
1698
+
1699
+ /**
1700
+ * Formats date input as user types, adding slashes automatically
1701
+ * @param value The input value to format
1702
+ * @param format The target format (e.g., 'dd/MM/yyyy')
1703
+ * @returns Formatted input value
1704
+ */
1705
+ export const formatDateInput = (value: string, format: string): string => {
1706
+ if (format === 'dd/MM/yyyy') {
1707
+ // Allow existing slashes to remain, only add where needed
1708
+ const cleaned = value.replace(/[^\d/]/g, '')
1709
+
1710
+ // Remove multiple consecutive slashes
1711
+ const singleSlash = cleaned.replace(/\/+/g, '/')
1712
+
1713
+ // Split by slashes to work with parts
1714
+ const parts = singleSlash.split('/')
1715
+
1716
+ if (parts.length === 1) {
1717
+ // Only day part
1718
+ const day = parts[0]
1719
+ if (day.length === 0) return ''
1720
+ if (day.length <= 2) return day
1721
+ if (day.length <= 4) return day.slice(0, 2) + '/' + day.slice(2)
1722
+ return (
1723
+ day.slice(0, 2) + '/' + day.slice(2, 4) + '/' + day.slice(4, 8)
1724
+ )
1725
+ } else if (parts.length === 2) {
1726
+ // Day and month parts
1727
+ const day = parts[0].slice(0, 2)
1728
+ const month = parts[1]
1729
+ if (month.length === 0) return day + '/'
1730
+ if (month.length <= 2) return day + '/' + month
1731
+ return day + '/' + month.slice(0, 2) + '/' + month.slice(2, 6)
1732
+ } else if (parts.length >= 3) {
1733
+ // All parts
1734
+ const day = parts[0].slice(0, 2)
1735
+ const month = parts[1].slice(0, 2)
1736
+ const year = parts[2].slice(0, 4)
1737
+ return day + '/' + month + (year ? '/' + year : '')
1738
+ }
1739
+ }
1740
+
1741
+ return value
1742
+ }
1743
+
1744
+ /**
1745
+ * Checks if date input is complete (full format length)
1746
+ * @param value The input value to check
1747
+ * @param format The expected format
1748
+ * @returns True if input is complete
1749
+ */
1750
+ export const isDateInputComplete = (value: string, format: string): boolean => {
1751
+ if (format === 'dd/MM/yyyy') {
1752
+ return value.length === 10
1753
+ }
1754
+ return true
1755
+ }
1756
+
1757
+ /**
1758
+ * Formats date display for the trigger button
1759
+ * @param selectedRange Current selected date range
1760
+ * @param allowSingleDateSelection Whether single date selection is allowed
1761
+ * @returns Formatted display string
1762
+ */
1763
+ export const formatDateDisplay = (
1764
+ selectedRange: DateRange | undefined,
1765
+ allowSingleDateSelection: boolean = false,
1766
+ timezone?: string,
1767
+ isSingleDatePicker?: boolean
1768
+ ): string => {
1769
+ if (!selectedRange || !selectedRange.startDate) {
1770
+ return isSingleDatePicker ? 'Select date' : 'Select date range'
1771
+ }
1772
+
1773
+ const formatOptions: Intl.DateTimeFormatOptions = {
1774
+ month: 'short',
1775
+ day: 'numeric',
1776
+ year: 'numeric',
1777
+ ...(timezone && { timeZone: timezone }),
1778
+ }
1779
+
1780
+ const timeFormatOptions: Intl.DateTimeFormatOptions = {
1781
+ hour: 'numeric',
1782
+ minute: '2-digit',
1783
+ hour12: true,
1784
+ ...(timezone && { timeZone: timezone }),
1785
+ }
1786
+
1787
+ const startDateStr = selectedRange.startDate.toLocaleDateString(
1788
+ 'en-US',
1789
+ formatOptions
1790
+ )
1791
+ const startTimeStr = selectedRange.startDate.toLocaleTimeString(
1792
+ 'en-US',
1793
+ timeFormatOptions
1794
+ )
1795
+
1796
+ if (
1797
+ !selectedRange.endDate ||
1798
+ (allowSingleDateSelection &&
1799
+ selectedRange.startDate.getTime() ===
1800
+ selectedRange.endDate.getTime())
1801
+ ) {
1802
+ return `${startDateStr}, ${startTimeStr}`
1803
+ }
1804
+
1805
+ const endDateStr = selectedRange.endDate.toLocaleDateString(
1806
+ 'en-US',
1807
+ formatOptions
1808
+ )
1809
+ const endTimeStr = selectedRange.endDate.toLocaleTimeString(
1810
+ 'en-US',
1811
+ timeFormatOptions
1812
+ )
1813
+
1814
+ return `${startDateStr}, ${startTimeStr} - ${endDateStr}, ${endTimeStr}`
1815
+ }
1816
+
1817
+ /**
1818
+ * Handles date input change with formatting and validation
1819
+ * @param value Input value
1820
+ * @param dateFormat Date format string
1821
+ * @param currentRange Current selected range
1822
+ * @param timeValue Current time value (HH:mm)
1823
+ * @param isStartDate Whether this is start date or end date
1824
+ * @param disableFutureDates Whether future dates should be disabled
1825
+ * @param disablePastDates Whether past dates should be disabled
1826
+ * @returns Object with formatted value, validation result, and updated range
1827
+ */
1828
+ export const handleDateInputChange = (
1829
+ value: string,
1830
+ dateFormat: string,
1831
+ currentRange: DateRange | undefined,
1832
+ timeValue?: string,
1833
+ isStartDate: boolean = true,
1834
+ disableFutureDates: boolean = false,
1835
+ disablePastDates: boolean = false,
1836
+ timezone?: string
1837
+ ): {
1838
+ formattedValue: string
1839
+ validation: DateValidationResult
1840
+ updatedRange?: DateRange
1841
+ } => {
1842
+ const formattedValue = formatDateInput(value, dateFormat)
1843
+ const validation = validateDateInput(
1844
+ formattedValue,
1845
+ dateFormat,
1846
+ disableFutureDates,
1847
+ disablePastDates,
1848
+ timezone
1849
+ )
1850
+
1851
+ let updatedRange: DateRange | undefined
1852
+
1853
+ if (validation.isValid && isDateInputComplete(formattedValue, dateFormat)) {
1854
+ const today = getTodayInTimezone(timezone)
1855
+ const [day, month, year] = '18/02/2026'.split('/')
1856
+ const date = new Date(+year, +month - 1, +day)
1857
+ const endDateTimeCheck =
1858
+ disableFutureDates && today && isDateToday(date, today)
1859
+ const startDateTimeCheck =
1860
+ disablePastDates && today && isDateToday(date, today)
1861
+ const [startHours, startMinutes] = [
1862
+ startDateTimeCheck ? today.getHours() : 0,
1863
+ startDateTimeCheck ? today.getMinutes() : 0,
1864
+ ]
1865
+ const [endHours, endMinutes] = [
1866
+ endDateTimeCheck ? today.getHours() : 23,
1867
+ endDateTimeCheck ? today.getMinutes() : 59,
1868
+ ]
1869
+ const currentStartDate =
1870
+ currentRange &&
1871
+ (timezone
1872
+ ? convertLocalDateToTimezoneDate(
1873
+ currentRange.startDate,
1874
+ timezone
1875
+ )
1876
+ : currentRange.startDate)
1877
+ const currentEndDate =
1878
+ currentRange?.endDate &&
1879
+ (timezone
1880
+ ? convertLocalDateToTimezoneDate(currentRange.endDate, timezone)
1881
+ : currentRange.endDate)
1882
+
1883
+ const intermediateStartHours =
1884
+ currentStartDate?.getHours() ?? startHours
1885
+ const intermediateStartMinutes =
1886
+ currentStartDate?.getMinutes() ?? startMinutes
1887
+ const intermediateEndHours = currentEndDate?.getHours() ?? endHours
1888
+ const intermediateEndMinutes =
1889
+ currentEndDate?.getMinutes() ?? endMinutes
1890
+
1891
+ const [hour, minute] = isStartDate
1892
+ ? [
1893
+ intermediateStartHours < startHours
1894
+ ? startHours
1895
+ : intermediateStartHours,
1896
+ intermediateStartHours <= startHours &&
1897
+ intermediateStartMinutes < startMinutes
1898
+ ? startMinutes
1899
+ : intermediateStartMinutes,
1900
+ ]
1901
+ : [
1902
+ intermediateEndHours > endHours
1903
+ ? endHours
1904
+ : intermediateEndHours,
1905
+ intermediateEndHours >= endHours &&
1906
+ intermediateEndMinutes > endMinutes
1907
+ ? endMinutes
1908
+ : intermediateEndMinutes,
1909
+ ]
1910
+
1911
+ const parsedDate = parseDate(formattedValue, dateFormat, hour, minute)
1912
+ if (timeValue && parsedDate !== null && isValidDate(parsedDate)) {
1913
+ updatedRange = isStartDate
1914
+ ? currentRange
1915
+ ? { ...currentRange, startDate: parsedDate }
1916
+ : { startDate: parsedDate }
1917
+ : currentRange
1918
+ ? { ...currentRange, endDate: parsedDate }
1919
+ : undefined
1920
+ }
1921
+ }
1922
+
1923
+ return {
1924
+ formattedValue,
1925
+ validation,
1926
+ updatedRange,
1927
+ }
1928
+ }
1929
+
1930
+ /**
1931
+ * Handles time change for date range
1932
+ * @param time New time value (HH:mm)
1933
+ * @param currentRange Current selected range
1934
+ * @param isStartTime Whether this is start time or end time
1935
+ * @returns Updated date range
1936
+ */
1937
+ export const handleTimeChange = (
1938
+ time: string,
1939
+ currentRange: DateRange,
1940
+ timezone?: string,
1941
+ isStartTime: boolean = true
1942
+ ): DateRange => {
1943
+ const targetDate = isStartTime
1944
+ ? currentRange.startDate
1945
+ : currentRange.endDate
1946
+
1947
+ if (targetDate) {
1948
+ const [hours, minutes] = time.split(':').map(Number)
1949
+
1950
+ let newDate
1951
+ if (timezone) {
1952
+ const parts = getDatePartsInTimezone(targetDate, timezone)
1953
+ newDate = createDateInTimezone(
1954
+ timezone,
1955
+ parts.year,
1956
+ parts.month,
1957
+ parts.day,
1958
+ hours,
1959
+ minutes,
1960
+ parts.seconds
1961
+ )
1962
+ } else {
1963
+ newDate = new Date(targetDate)
1964
+ newDate.setHours(hours, minutes)
1965
+ }
1966
+
1967
+ return isStartTime
1968
+ ? { ...currentRange, startDate: newDate }
1969
+ : { ...currentRange, endDate: newDate }
1970
+ }
1971
+
1972
+ return currentRange
1973
+ }
1974
+
1975
+ /**
1976
+ * Handles date selection from calendar
1977
+ * @param range Selected date range from calendar
1978
+ * @param startTime Current start time
1979
+ * @param endTime Current end time
1980
+ * @param dateFormat Date format string
1981
+ * @returns Object with updated range and formatted date strings
1982
+ */
1983
+ export const handleCalendarDateSelect = (
1984
+ range: DateRange,
1985
+ startTime: string,
1986
+ dateFormat: string,
1987
+ timezone?: string,
1988
+ endTime?: string
1989
+ ): {
1990
+ updatedRange: DateRange
1991
+ formattedStartDate: string
1992
+ formattedEndDate?: string
1993
+ } => {
1994
+ // Check if this is a single date range (same day for start and end)
1995
+ const isSingleDateRange =
1996
+ range.startDate &&
1997
+ range.endDate &&
1998
+ isSameDay(range.startDate, range.endDate)
1999
+
2000
+ if (range.startDate) {
2001
+ const [startHour, startMinute] = startTime.split(':').map(Number)
2002
+ if (isSingleDateRange) {
2003
+ // For single date ranges, preserve the 00:00:00 time for start date
2004
+ // Only override if it's not already set to start of day
2005
+ if (startHour !== 0 || startMinute !== 0) {
2006
+ if (timezone) {
2007
+ const parts = getDatePartsInTimezone(
2008
+ range.startDate,
2009
+ timezone
2010
+ )
2011
+ range.startDate = createDateInTimezone(
2012
+ timezone,
2013
+ parts.year,
2014
+ parts.month,
2015
+ parts.day,
2016
+ startHour,
2017
+ startMinute,
2018
+ parts.seconds
2019
+ )
2020
+ } else {
2021
+ range.startDate.setHours(startHour, startMinute)
2022
+ }
2023
+ }
2024
+ } else {
2025
+ if (timezone) {
2026
+ const parts = getDatePartsInTimezone(range.startDate, timezone)
2027
+ range.startDate = createDateInTimezone(
2028
+ timezone,
2029
+ parts.year,
2030
+ parts.month,
2031
+ parts.day,
2032
+ startHour,
2033
+ startMinute,
2034
+ parts.seconds
2035
+ )
2036
+ } else {
2037
+ range.startDate.setHours(startHour, startMinute)
2038
+ }
2039
+ }
2040
+ }
2041
+
2042
+ if (range.endDate && endTime) {
2043
+ const [endHour, endMinute] = endTime
2044
+ ? endTime.split(':').map(Number)
2045
+ : []
2046
+ if (isSingleDateRange) {
2047
+ // For single date ranges, preserve the 23:59:59 time for end date
2048
+ // Only override if it's not already set to end of day
2049
+ if (endHour !== 23 || endMinute !== 59) {
2050
+ if (timezone) {
2051
+ const parts = getDatePartsInTimezone(
2052
+ range.endDate,
2053
+ timezone
2054
+ )
2055
+ range.endDate = createDateInTimezone(
2056
+ timezone,
2057
+ parts.year,
2058
+ parts.month,
2059
+ parts.day,
2060
+ endHour,
2061
+ endMinute,
2062
+ parts.seconds
2063
+ )
2064
+ } else {
2065
+ range.endDate.setHours(endHour, endMinute)
2066
+ }
2067
+ }
2068
+ } else {
2069
+ if (timezone) {
2070
+ const parts = getDatePartsInTimezone(range.endDate, timezone)
2071
+ range.endDate = createDateInTimezone(
2072
+ timezone,
2073
+ parts.year,
2074
+ parts.month,
2075
+ parts.day,
2076
+ endHour,
2077
+ endMinute,
2078
+ parts.seconds
2079
+ )
2080
+ } else {
2081
+ range.endDate.setHours(endHour, endMinute)
2082
+ }
2083
+ }
2084
+ }
2085
+
2086
+ return {
2087
+ updatedRange: range,
2088
+ formattedStartDate: formatDate(range.startDate, dateFormat, timezone),
2089
+ formattedEndDate:
2090
+ range.endDate && formatDate(range.endDate, dateFormat, timezone),
2091
+ }
2092
+ }
2093
+
2094
+ /**
2095
+ * Handles preset selection
2096
+ * @param preset Selected preset
2097
+ * @param dateFormat Date format string
2098
+ * @returns Object with updated range, formatted dates, and times
2099
+ */
2100
+ export const handlePresetSelection = (
2101
+ preset: DateRangePreset,
2102
+ dateFormat: string,
2103
+ timezone?: string
2104
+ ): {
2105
+ updatedRange: DateRange
2106
+ formattedStartDate: string
2107
+ formattedEndDate: string
2108
+ formattedStartTime: string
2109
+ formattedEndTime: string
2110
+ } | null => {
2111
+ const range = getPresetDateRange(preset, timezone)
2112
+ if (!range.endDate) return null
2113
+
2114
+ return {
2115
+ updatedRange: range,
2116
+ formattedStartDate: formatDate(range.startDate, dateFormat, timezone),
2117
+ formattedEndDate: formatDate(range.endDate, dateFormat, timezone),
2118
+ formattedStartTime: formatDate(range.startDate, 'HH:mm', timezone),
2119
+ formattedEndTime: formatDate(range.endDate, 'HH:mm', timezone),
2120
+ }
2121
+ }
2122
+
2123
+ /**
2124
+ * Handles loading more months in calendar
2125
+ * @param months Current months array
2126
+ * @param direction Direction to load ('past' or 'future')
2127
+ * @param isLoadingPast Current loading state for past
2128
+ * @param isLoadingFuture Current loading state for future
2129
+ * @returns Promise that resolves when loading is complete
2130
+ */
2131
+ export const handleLoadMoreMonths = async (
2132
+ months: { month: number; year: number }[],
2133
+ direction: 'past' | 'future',
2134
+ isLoadingPast: boolean,
2135
+ isLoadingFuture: boolean
2136
+ ): Promise<{ month: number; year: number }[] | null> => {
2137
+ if (
2138
+ (direction === 'past' && isLoadingPast) ||
2139
+ (direction === 'future' && isLoadingFuture)
2140
+ ) {
2141
+ return null
2142
+ }
2143
+
2144
+ const chunkParams = getNextChunkParams(months, direction)
2145
+ if (!chunkParams) {
2146
+ return null
2147
+ }
2148
+
2149
+ await new Promise((resolve) => setTimeout(resolve, 500))
2150
+
2151
+ const { startYear, startMonth } = chunkParams
2152
+ let newChunk: { month: number; year: number }[]
2153
+
2154
+ if (direction === 'past') {
2155
+ const firstMonth = months[0]
2156
+ const endMonth = firstMonth.month === 0 ? 11 : firstMonth.month - 1
2157
+ const adjustedEndYear =
2158
+ firstMonth.month === 0 ? firstMonth.year - 1 : firstMonth.year
2159
+
2160
+ newChunk = generateMonthChunk(
2161
+ startYear,
2162
+ startMonth,
2163
+ adjustedEndYear,
2164
+ endMonth
2165
+ )
2166
+ } else {
2167
+ const endYear = startYear + 2
2168
+ newChunk = generateMonthChunk(startYear, startMonth, endYear)
2169
+ }
2170
+
2171
+ return newChunk
2172
+ }
2173
+
2174
+ /**
2175
+ * Handles scroll position updates for calendar
2176
+ * @param scrollTop Current scroll position
2177
+ * @param scrollHeight Total scroll height
2178
+ * @param clientHeight Client height
2179
+ * @param loadThreshold Threshold for triggering loads
2180
+ * @returns Object indicating what should be loaded
2181
+ */
2182
+ export const handleCalendarScroll = (
2183
+ scrollTop: number,
2184
+ scrollHeight: number,
2185
+ clientHeight: number,
2186
+ loadThreshold: number = 100
2187
+ ): {
2188
+ shouldLoadPast: boolean
2189
+ shouldLoadFuture: boolean
2190
+ } => {
2191
+ const shouldLoadPast = scrollTop < loadThreshold
2192
+ const shouldLoadFuture =
2193
+ scrollTop + clientHeight > scrollHeight - loadThreshold
2194
+
2195
+ return {
2196
+ shouldLoadPast,
2197
+ shouldLoadFuture,
2198
+ }
2199
+ }
2200
+
2201
+ /**
2202
+ * Creates calendar month data structure for rendering
2203
+ * @param year Year of the month
2204
+ * @param month Month (0-based)
2205
+ * @param monthIndex Index in the months array
2206
+ * @param monthHeight Height of each month
2207
+ * @returns Month data for rendering
2208
+ */
2209
+ export const createCalendarMonthData = (
2210
+ year: number,
2211
+ month: number,
2212
+ monthIndex: number,
2213
+ monthHeight: number
2214
+ ) => {
2215
+ const weeks = generateMonthWeeks(year, month)
2216
+ const topOffset = getMonthOffset(monthIndex, monthHeight)
2217
+
2218
+ return {
2219
+ key: `month-${year}-${month}`,
2220
+ year,
2221
+ month,
2222
+ weeks,
2223
+ topOffset,
2224
+ monthHeight,
2225
+ monthName: getMonthName(month),
2226
+ }
2227
+ }
2228
+
2229
+ /**
2230
+ * Calculates day cell props for rendering
2231
+ * @param date Date object
2232
+ * @param selectedRange Current selected range
2233
+ * @param today Today's date
2234
+ * @param disableFutureDates Whether future dates are disabled
2235
+ * @param disablePastDates Whether past dates are disabled
2236
+ * @param calendarToken Calendar token for styling
2237
+ * @param customDisableDates Custom function to disable specific dates
2238
+ * @returns Day cell props
2239
+ */
2240
+ export const calculateDayCellProps = (
2241
+ date: Date,
2242
+ selectedRange: DateRange | undefined,
2243
+ today: Date,
2244
+ disableFutureDates: boolean,
2245
+ disablePastDates: boolean,
2246
+ calendarToken: CalendarTokenType,
2247
+ customDisableDates?: (date: Date) => boolean,
2248
+ timezone?: string,
2249
+ isSingleDatePicker?: boolean
2250
+ ): {
2251
+ dateStates: ReturnType<typeof getDateCellStates>
2252
+ styles: Record<string, unknown>
2253
+ textColor: string | unknown
2254
+ showTodayIndicator: boolean
2255
+ } => {
2256
+ const dateStates = getDateCellStates(
2257
+ date,
2258
+ selectedRange,
2259
+ today,
2260
+ disableFutureDates,
2261
+ disablePastDates,
2262
+ customDisableDates,
2263
+ timezone,
2264
+ isSingleDatePicker
2265
+ )
2266
+
2267
+ const getCellStyles = () => {
2268
+ // Base cell styles - hardcoded values for consistent styling
2269
+ let styles: Record<string, unknown> = {
2270
+ cursor: 'pointer',
2271
+ textAlign: 'center',
2272
+ padding: '10px 8px',
2273
+ position: 'relative',
2274
+ fontWeight: '500',
2275
+ boxSizing: 'border-box',
2276
+ outline: '1px solid transparent',
2277
+ fontSize: '16px',
2278
+ lineHeight: '20px',
2279
+ display: 'flex',
2280
+ alignItems: 'center',
2281
+ justifyContent: 'center',
2282
+ }
2283
+
2284
+ // Apply state-specific styles with hardcoded values
2285
+ if (dateStates.isSingleDate) {
2286
+ styles = {
2287
+ ...styles,
2288
+ backgroundColor: '#3b82f6',
2289
+ borderRadius: '8px',
2290
+ }
2291
+ } else if (dateStates.isStart) {
2292
+ styles = {
2293
+ ...styles,
2294
+ backgroundColor: '#3b82f6',
2295
+ borderTopLeftRadius: '8px',
2296
+ borderBottomLeftRadius: '8px',
2297
+ }
2298
+ } else if (dateStates.isEnd) {
2299
+ styles = {
2300
+ ...styles,
2301
+ backgroundColor: '#3b82f6',
2302
+ borderTopRightRadius: '8px',
2303
+ borderBottomRightRadius: '8px',
2304
+ }
2305
+ } else if (dateStates.isRangeDay) {
2306
+ styles = {
2307
+ ...styles,
2308
+ backgroundColor: '#dbeafe',
2309
+ }
2310
+ }
2311
+
2312
+ // Apply disabled state last to override everything
2313
+ if (dateStates.isDisabled) {
2314
+ styles = {
2315
+ ...styles,
2316
+ opacity: 0.4,
2317
+ cursor: 'not-allowed',
2318
+ pointerEvents: 'none',
2319
+ }
2320
+ }
2321
+
2322
+ return styles
2323
+ }
2324
+
2325
+ const getTextColor = () => {
2326
+ if (dateStates.isDisabled) {
2327
+ return calendarToken.calendar.calendarGrid.day.text.disabledDate
2328
+ .color
2329
+ } else if (
2330
+ dateStates.isStart ||
2331
+ dateStates.isEnd ||
2332
+ dateStates.isSingleDate
2333
+ ) {
2334
+ return calendarToken.calendar.calendarGrid.day.text.selectedDay
2335
+ .color
2336
+ } else if (dateStates.isTodayDay) {
2337
+ return calendarToken.calendar.calendarGrid.day.text.todayDay.color
2338
+ } else if (dateStates.isRangeDay) {
2339
+ return calendarToken.calendar.calendarGrid.day.text.rangeDay.color
2340
+ }
2341
+ return calendarToken.calendar.calendarGrid.day.text.dayNumber.color
2342
+ }
2343
+
2344
+ return {
2345
+ dateStates,
2346
+ styles: getCellStyles(),
2347
+ textColor: getTextColor(),
2348
+ showTodayIndicator: shouldShowTodayIndicator(dateStates),
2349
+ }
2350
+ }
2351
+
2352
+ const getPickerYearRange = (
2353
+ selectedRange: DateRange | undefined,
2354
+ maxYearOffset?: number
2355
+ ) => {
2356
+ const { MIN_YEAR, MAX_YEAR_OFFSET } = DATE_RANGE_PICKER_CONSTANTS
2357
+ const currentYear = new Date().getFullYear()
2358
+ const defaultMaxYear =
2359
+ currentYear +
2360
+ (maxYearOffset && maxYearOffset >= 0 ? maxYearOffset : MAX_YEAR_OFFSET)
2361
+
2362
+ const selectedYears: number[] = []
2363
+ if (selectedRange && isValidDate(selectedRange.startDate)) {
2364
+ selectedYears.push(selectedRange.startDate.getFullYear())
2365
+ }
2366
+ if (selectedRange?.endDate && isValidDate(selectedRange.endDate)) {
2367
+ selectedYears.push(selectedRange.endDate.getFullYear())
2368
+ }
2369
+
2370
+ const hasSelectedYears = selectedYears.length > 0
2371
+ const earliestYear = hasSelectedYears
2372
+ ? Math.min(...selectedYears)
2373
+ : currentYear
2374
+ const latestYear = hasSelectedYears
2375
+ ? Math.max(...selectedYears)
2376
+ : currentYear
2377
+
2378
+ const minYear = Math.min(MIN_YEAR, earliestYear)
2379
+ const maxYear = Math.max(defaultMaxYear, latestYear)
2380
+
2381
+ return {
2382
+ minYear: Math.max(0, minYear),
2383
+ maxYear: Math.max(minYear, maxYear),
2384
+ }
2385
+ }
2386
+
2387
+ const buildYearOptions = (minYear: number, maxYear: number): number[] => {
2388
+ if (maxYear < minYear) {
2389
+ return [minYear]
2390
+ }
2391
+
2392
+ const totalYears = maxYear - minYear + 1
2393
+ return Array.from({ length: totalYears }, (_, index) => minYear + index)
2394
+ }
2395
+
2396
+ /**
2397
+ * Generates picker data for date/time selection
2398
+ * @param tabType Whether this is for start or end date
2399
+ * @param selectedRange Current selected date range
2400
+ * @param startTime Current start time
2401
+ * @param endTime Current end time
2402
+ * @returns Object with picker data for all columns
2403
+ */
2404
+ export const generatePickerData = (
2405
+ tabType: 'start' | 'end',
2406
+ selectedRange?: DateRange,
2407
+ startTime?: string,
2408
+ endTime?: string,
2409
+ maxYearOffset?: number
2410
+ ) => {
2411
+ const rawDate =
2412
+ tabType === 'start' ? selectedRange?.startDate : selectedRange?.endDate
2413
+ const targetTime = tabType === 'start' ? startTime : endTime
2414
+
2415
+ const now = new Date()
2416
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
2417
+
2418
+ const safeDate = rawDate && isValidDate(rawDate) ? new Date(rawDate) : today
2419
+
2420
+ const { minYear, maxYear } = getPickerYearRange(
2421
+ selectedRange,
2422
+ maxYearOffset
2423
+ )
2424
+ const yearOptions = buildYearOptions(minYear, maxYear)
2425
+ const monthIndex = safeDate.getMonth()
2426
+ const daysInMonth = new Date(
2427
+ safeDate.getFullYear(),
2428
+ monthIndex + 1,
2429
+ 0
2430
+ ).getDate()
2431
+ const dateOptions = Array.from({ length: daysInMonth }, (_, i) => i + 1)
2432
+ const monthOptions = Array.from({ length: 12 }, (_, i) => i)
2433
+
2434
+ const allTimes = []
2435
+ for (let h = 0; h < 24; h++) {
2436
+ for (let m = 0; m < 60; m += 15) {
2437
+ allTimes.push(
2438
+ `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`
2439
+ )
2440
+ }
2441
+ }
2442
+
2443
+ const yearIndex = yearOptions.indexOf(safeDate.getFullYear())
2444
+ const resolvedYearIndex =
2445
+ yearIndex >= 0
2446
+ ? yearIndex
2447
+ : clampPickerIndex(yearIndex, yearOptions.length)
2448
+ const dateIndex = dateOptions.indexOf(safeDate.getDate())
2449
+ const resolvedDateIndex =
2450
+ dateIndex >= 0
2451
+ ? dateIndex
2452
+ : clampPickerIndex(dateIndex, dateOptions.length)
2453
+
2454
+ return {
2455
+ years: {
2456
+ items: yearOptions,
2457
+ selectedIndex: resolvedYearIndex,
2458
+ },
2459
+ months: {
2460
+ items: monthOptions.map((m) => getMonthName(m).slice(0, 3)),
2461
+ selectedIndex: monthIndex,
2462
+ },
2463
+ dates: {
2464
+ items: dateOptions,
2465
+ selectedIndex: resolvedDateIndex,
2466
+ },
2467
+ times: {
2468
+ items: allTimes,
2469
+ selectedIndex: targetTime
2470
+ ? Math.max(0, allTimes.indexOf(targetTime))
2471
+ : 0,
2472
+ },
2473
+ }
2474
+ }
2475
+
2476
+ /**
2477
+ * Creates selection handler for picker columns
2478
+ * @param tabType Whether this is for start or end date
2479
+ * @param type The type of selection (year, month, date, time)
2480
+ * @param selectedRange Current selected range
2481
+ * @param dateFormat Date format string
2482
+ * @param handleStartTimeChange Start time change handler
2483
+ * @param handleEndTimeChange End time change handler
2484
+ * @param setSelectedRange Range setter function
2485
+ * @param setStartDate Start date setter function
2486
+ * @param setEndDate End date setter function
2487
+ * @returns Selection handler function
2488
+ */
2489
+ export const createSelectionHandler = (
2490
+ tabType: 'start' | 'end',
2491
+ type: 'year' | 'month' | 'date' | 'time',
2492
+ dateFormat: string,
2493
+ handleStartTimeChange: (time: string) => void,
2494
+ handleEndTimeChange: (time: string) => void,
2495
+ setSelectedRange: (range: DateRange) => void,
2496
+ setStartDate: (date: string) => void,
2497
+ setEndDate: (date: string) => void,
2498
+ selectedRange?: DateRange,
2499
+ maxYearOffset?: number
2500
+ ) => {
2501
+ return (index: number) => {
2502
+ const now = new Date()
2503
+ const baselineDate =
2504
+ tabType === 'start'
2505
+ ? selectedRange?.startDate
2506
+ : selectedRange?.endDate
2507
+ const safeBaseDate =
2508
+ baselineDate && isValidDate(baselineDate)
2509
+ ? baselineDate
2510
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate())
2511
+ const newDate = new Date(safeBaseDate)
2512
+
2513
+ switch (type) {
2514
+ case 'year': {
2515
+ const { minYear, maxYear } = getPickerYearRange(
2516
+ selectedRange,
2517
+ maxYearOffset
2518
+ )
2519
+ const years = buildYearOptions(minYear, maxYear)
2520
+ const safeIndex = clampPickerIndex(index, years.length)
2521
+ const newYear = years[safeIndex]
2522
+ const currentMonth = newDate.getMonth()
2523
+ const currentDay = newDate.getDate()
2524
+ const daysInTargetMonth = new Date(
2525
+ newYear,
2526
+ currentMonth + 1,
2527
+ 0
2528
+ ).getDate()
2529
+ const clampedDay = Math.min(currentDay, daysInTargetMonth)
2530
+ newDate.setFullYear(newYear, currentMonth, clampedDay)
2531
+ break
2532
+ }
2533
+ case 'month': {
2534
+ const newMonth = Math.max(0, Math.min(11, index))
2535
+ const currentDay = newDate.getDate()
2536
+ const daysInTargetMonth = new Date(
2537
+ newDate.getFullYear(),
2538
+ newMonth + 1,
2539
+ 0
2540
+ ).getDate()
2541
+ const clampedDay = Math.min(currentDay, daysInTargetMonth)
2542
+ newDate.setMonth(newMonth, clampedDay)
2543
+ break
2544
+ }
2545
+ case 'date': {
2546
+ const daysInMonth = new Date(
2547
+ newDate.getFullYear(),
2548
+ newDate.getMonth() + 1,
2549
+ 0
2550
+ ).getDate()
2551
+ const clampedIndex = Math.max(
2552
+ 0,
2553
+ Math.min(daysInMonth - 1, index)
2554
+ )
2555
+ const dates = Array.from(
2556
+ { length: daysInMonth },
2557
+ (_, i) => i + 1
2558
+ )
2559
+ newDate.setDate(dates[clampedIndex])
2560
+ break
2561
+ }
2562
+ case 'time': {
2563
+ const times = []
2564
+ for (let h = 0; h < 24; h++) {
2565
+ for (let m = 0; m < 60; m += 15) {
2566
+ times.push(
2567
+ `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`
2568
+ )
2569
+ }
2570
+ }
2571
+ const time = times[index]
2572
+ if (tabType === 'start') {
2573
+ handleStartTimeChange(time)
2574
+ } else {
2575
+ handleEndTimeChange(time)
2576
+ }
2577
+ return
2578
+ }
2579
+ }
2580
+
2581
+ if (tabType === 'start') {
2582
+ setSelectedRange({ ...selectedRange, startDate: newDate })
2583
+ setStartDate(formatDate(newDate, dateFormat))
2584
+ } else if (selectedRange) {
2585
+ setSelectedRange({ ...selectedRange, endDate: newDate })
2586
+ setEndDate(formatDate(newDate, dateFormat))
2587
+ }
2588
+ }
2589
+ }
2590
+
2591
+ /**
2592
+ * Gets preset display label with custom mappings
2593
+ * @param preset The preset to get label for
2594
+ * @returns Display label for the preset
2595
+ */
2596
+ export const getPresetDisplayLabel = (preset: DateRangePreset): string => {
2597
+ switch (preset) {
2598
+ case DateRangePreset.LAST_1_HOUR:
2599
+ return 'Last 6 hours'
2600
+ case DateRangePreset.LAST_6_HOURS:
2601
+ return 'Last 6 hours'
2602
+ case DateRangePreset.LAST_7_DAYS:
2603
+ return 'Last 2 Days'
2604
+ default:
2605
+ return getPresetLabel(preset)
2606
+ }
2607
+ }
2608
+
2609
+ /**
2610
+ * Enhanced date range formatting with preset patterns
2611
+ * @param range The date range to format
2612
+ * @param config Format configuration
2613
+ * @returns Formatted date range string
2614
+ */
2615
+ export const formatDateRangeWithConfig = (
2616
+ range: DateRange,
2617
+ config: DateFormatConfig = {},
2618
+ timezone?: string
2619
+ ): string => {
2620
+ if (!range.startDate) {
2621
+ return ''
2622
+ }
2623
+
2624
+ const {
2625
+ preset = DateFormatPreset.MEDIUM_RANGE,
2626
+ customFormat,
2627
+ includeTime = false,
2628
+ includeYear = true,
2629
+ separator = ' - ',
2630
+ locale = 'en-US',
2631
+ timeFormat = '12h',
2632
+ } = config
2633
+
2634
+ if (preset === DateFormatPreset.CUSTOM && customFormat) {
2635
+ return customFormat(range, {
2636
+ includeTime,
2637
+ includeYear,
2638
+ separator,
2639
+ locale,
2640
+ })
2641
+ }
2642
+
2643
+ const startDate = range.startDate
2644
+ const endDate = range.endDate || range.startDate
2645
+ const isSameDate = isSameDay(startDate, endDate)
2646
+
2647
+ // Helper function to format time
2648
+ const formatTimeString = (date: Date): string => {
2649
+ if (!includeTime) return ''
2650
+
2651
+ if (timeFormat === '12h') {
2652
+ return formatTimeIn12Hour(date)
2653
+ } else {
2654
+ return formatDate(date, 'HH:mm', timezone)
2655
+ }
2656
+ }
2657
+
2658
+ // Helper function to get month abbreviation
2659
+ const getMonthAbbr = (date: Date): string => {
2660
+ const options: Intl.DateTimeFormatOptions = {
2661
+ month: 'short',
2662
+ ...(timezone && { timeZone: timezone }),
2663
+ }
2664
+ return date.toLocaleDateString(locale, options)
2665
+ }
2666
+
2667
+ // Helper function to get full month name
2668
+ const getMonthFull = (date: Date): string => {
2669
+ const options: Intl.DateTimeFormatOptions = {
2670
+ month: 'long',
2671
+ ...(timezone && { timeZone: timezone }),
2672
+ }
2673
+ return date.toLocaleDateString(locale, options)
2674
+ }
2675
+
2676
+ // Helper function to get ordinal suffix
2677
+ const getOrdinalSuffix = (day: number): string => {
2678
+ if (day > 3 && day < 21) return 'th'
2679
+ switch (day % 10) {
2680
+ case 1:
2681
+ return 'st'
2682
+ case 2:
2683
+ return 'nd'
2684
+ case 3:
2685
+ return 'rd'
2686
+ default:
2687
+ return 'th'
2688
+ }
2689
+ }
2690
+
2691
+ switch (preset) {
2692
+ case DateFormatPreset.SHORT_RANGE: {
2693
+ const startMonth = getMonthAbbr(startDate)
2694
+ const startDay = startDate.getDate()
2695
+ const endDay = endDate.getDate()
2696
+ const year = includeYear ? ` ${startDate.getFullYear()}` : ''
2697
+
2698
+ if (isSameDate && !includeTime) {
2699
+ return `${startMonth} ${startDay}${year}`
2700
+ }
2701
+
2702
+ if (isSameDate && includeTime) {
2703
+ const startTimeStr = formatTimeString(startDate)
2704
+ const endTimeStr = formatTimeString(endDate)
2705
+
2706
+ if (startTimeStr === endTimeStr) {
2707
+ return `${startMonth} ${startDay}${year}, ${startTimeStr}`
2708
+ } else {
2709
+ return `${startMonth} ${startDay}${year}, ${startTimeStr} - ${endTimeStr}`
2710
+ }
2711
+ }
2712
+
2713
+ if (
2714
+ startDate.getMonth() === endDate.getMonth() &&
2715
+ startDate.getFullYear() === endDate.getFullYear()
2716
+ ) {
2717
+ const timeStr = includeTime
2718
+ ? `, ${formatTimeString(startDate)} - ${formatTimeString(endDate)}`
2719
+ : ''
2720
+ return `${startMonth} ${startDay}-${endDay}${year}${timeStr}`
2721
+ }
2722
+
2723
+ const endMonth = getMonthAbbr(endDate)
2724
+ const endYear =
2725
+ includeYear && endDate.getFullYear() !== startDate.getFullYear()
2726
+ ? ` ${endDate.getFullYear()}`
2727
+ : ''
2728
+ const timeStr = includeTime
2729
+ ? `, ${formatTimeString(startDate)} - ${formatTimeString(endDate)}`
2730
+ : ''
2731
+ return `${startMonth} ${startDay}${year} - ${endMonth} ${endDay}${endYear}${timeStr}`
2732
+ }
2733
+
2734
+ case DateFormatPreset.MEDIUM_RANGE: {
2735
+ const startMonth = getMonthAbbr(startDate)
2736
+ const startDay = startDate.getDate()
2737
+ const year = includeYear ? ` ${startDate.getFullYear()}` : ''
2738
+
2739
+ if (isSameDate) {
2740
+ if (!includeTime) {
2741
+ return `${startMonth} ${startDay}${year}`
2742
+ } else {
2743
+ const startTimeStr = formatTimeString(startDate)
2744
+ const endTimeStr = formatTimeString(endDate)
2745
+
2746
+ if (startTimeStr === endTimeStr) {
2747
+ return `${startMonth} ${startDay}${year}, ${startTimeStr}`
2748
+ } else {
2749
+ return `${startMonth} ${startDay}${year}, ${startTimeStr} - ${endTimeStr}`
2750
+ }
2751
+ }
2752
+ }
2753
+
2754
+ const endMonth = getMonthAbbr(endDate)
2755
+ const endDay = endDate.getDate()
2756
+ const endYear =
2757
+ includeYear && endDate.getFullYear() !== startDate.getFullYear()
2758
+ ? ` ${endDate.getFullYear()}`
2759
+ : ''
2760
+ const timeStr = includeTime
2761
+ ? `, ${formatTimeString(startDate)} - ${formatTimeString(endDate)}`
2762
+ : ''
2763
+ return `${startMonth} ${startDay}${separator}${endMonth} ${endDay}${endYear || year}${timeStr}`
2764
+ }
2765
+
2766
+ case DateFormatPreset.LONG_RANGE: {
2767
+ const startMonth = getMonthAbbr(startDate)
2768
+ const startDay = startDate.getDate()
2769
+ const startYear = includeYear ? ` ${startDate.getFullYear()}` : ''
2770
+
2771
+ if (isSameDate && !includeTime) {
2772
+ return `${startMonth} ${startDay}${startYear}`
2773
+ }
2774
+
2775
+ if (isSameDate && includeTime) {
2776
+ const startTimeStr = formatTimeString(startDate)
2777
+ const endTimeStr = formatTimeString(endDate)
2778
+
2779
+ if (startTimeStr === endTimeStr) {
2780
+ return `${startMonth} ${startDay}${startYear}, ${startTimeStr}`
2781
+ } else {
2782
+ return `${startMonth} ${startDay}${startYear}, ${startTimeStr}${separator}${startMonth} ${startDay}${startYear}, ${endTimeStr}`
2783
+ }
2784
+ }
2785
+
2786
+ const endMonth = getMonthAbbr(endDate)
2787
+ const endDay = endDate.getDate()
2788
+ const endYear = includeYear ? ` ${endDate.getFullYear()}` : ''
2789
+
2790
+ if (includeTime) {
2791
+ const startTimeStr = formatTimeString(startDate)
2792
+ const endTimeStr = formatTimeString(endDate)
2793
+ return `${startMonth} ${startDay}${startYear}, ${startTimeStr}${separator}${endMonth} ${endDay}${endYear}, ${endTimeStr}`
2794
+ }
2795
+
2796
+ return `${startMonth} ${startDay}${startYear}${separator}${endMonth} ${endDay}${endYear}`
2797
+ }
2798
+
2799
+ case DateFormatPreset.SHORT_SINGLE: {
2800
+ const month = getMonthAbbr(startDate)
2801
+ const day = startDate.getDate()
2802
+ const year = includeYear ? ` ${startDate.getFullYear()}` : ''
2803
+ const timeStr = includeTime
2804
+ ? `, ${formatTimeString(startDate)}`
2805
+ : ''
2806
+ return `${month} ${day}${year}${timeStr}`
2807
+ }
2808
+
2809
+ case DateFormatPreset.MEDIUM_SINGLE: {
2810
+ const month = getMonthFull(startDate)
2811
+ const day = startDate.getDate()
2812
+ const year = includeYear ? `, ${startDate.getFullYear()}` : ''
2813
+ const timeStr = includeTime
2814
+ ? `, ${formatTimeString(startDate)}`
2815
+ : ''
2816
+ return `${month} ${day}${year}${timeStr}`
2817
+ }
2818
+
2819
+ case DateFormatPreset.LONG_SINGLE: {
2820
+ const month = getMonthFull(startDate)
2821
+ const day = startDate.getDate()
2822
+ const ordinal = getOrdinalSuffix(day)
2823
+ const year = includeYear ? `, ${startDate.getFullYear()}` : ''
2824
+ const timeStr = includeTime
2825
+ ? `, ${formatTimeString(startDate)}`
2826
+ : ''
2827
+ return `${month} ${day}${ordinal}${year}${timeStr}`
2828
+ }
2829
+
2830
+ case DateFormatPreset.ISO_RANGE: {
2831
+ const formatISODate = (date: Date): string => {
2832
+ const year = date.getFullYear()
2833
+ const month = (date.getMonth() + 1).toString().padStart(2, '0')
2834
+ const day = date.getDate().toString().padStart(2, '0')
2835
+ return `${year}-${month}-${day}`
2836
+ }
2837
+
2838
+ const startISO = formatISODate(startDate)
2839
+
2840
+ if (isSameDate) {
2841
+ const timeStr = includeTime
2842
+ ? ` ${formatDate(startDate, 'HH:mm')}`
2843
+ : ''
2844
+ return `${startISO}${timeStr}`
2845
+ }
2846
+
2847
+ const endISO = formatISODate(endDate)
2848
+ const timeStr = includeTime
2849
+ ? ` ${formatDate(startDate, 'HH:mm')} - ${formatDate(endDate, 'HH:mm')}`
2850
+ : ''
2851
+ return `${startISO}${separator}${endISO}${timeStr}`
2852
+ }
2853
+
2854
+ case DateFormatPreset.US_RANGE: {
2855
+ const startUS = formatDate(startDate, 'MM/dd/yyyy')
2856
+
2857
+ if (isSameDate) {
2858
+ const timeStr = includeTime
2859
+ ? ` ${formatTimeString(startDate)}`
2860
+ : ''
2861
+ return `${startUS}${timeStr}`
2862
+ }
2863
+
2864
+ const endUS = formatDate(endDate, 'MM/dd/yyyy')
2865
+ const timeStr = includeTime
2866
+ ? ` ${formatTimeString(startDate)} - ${formatTimeString(endDate)}`
2867
+ : ''
2868
+ return `${startUS}${separator}${endUS}${timeStr}`
2869
+ }
2870
+
2871
+ default:
2872
+ return formatDateRange(range, includeTime)
2873
+ }
2874
+ }
2875
+
2876
+ /**
2877
+ * Creates a custom trigger element with enhanced formatting
2878
+ * @param range Current selected date range
2879
+ * @param config Format configuration
2880
+ * @param placeholder Placeholder text when no range is selected
2881
+ * @returns Formatted display string for trigger
2882
+ */
2883
+ export const formatTriggerDisplay = (
2884
+ range: DateRange | undefined,
2885
+ config: DateFormatConfig = {},
2886
+ isSingleDatePicker?: boolean,
2887
+ placeholder: string = isSingleDatePicker
2888
+ ? 'Select date'
2889
+ : 'Select date range',
2890
+ timezone?: string
2891
+ ): string => {
2892
+ if (!range || !range.startDate) {
2893
+ return placeholder
2894
+ }
2895
+
2896
+ return formatDateRangeWithConfig(range, config, timezone)
2897
+ }
2898
+
2899
+ /**
2900
+ * Predefined format configurations for common use cases
2901
+ */
2902
+ export const FORMAT_PRESETS = {
2903
+ COMPACT_NO_TIME: {
2904
+ preset: DateFormatPreset.SHORT_RANGE,
2905
+ includeTime: false,
2906
+ includeYear: true,
2907
+ } as DateFormatConfig,
2908
+
2909
+ COMPACT_NO_YEAR: {
2910
+ preset: DateFormatPreset.SHORT_RANGE,
2911
+ includeTime: false,
2912
+ includeYear: false,
2913
+ } as DateFormatConfig,
2914
+
2915
+ MEDIUM_NO_TIME: {
2916
+ preset: DateFormatPreset.MEDIUM_RANGE,
2917
+ includeTime: false,
2918
+ includeYear: true,
2919
+ } as DateFormatConfig,
2920
+
2921
+ MEDIUM_WITH_TIME: {
2922
+ preset: DateFormatPreset.MEDIUM_RANGE,
2923
+ includeTime: true,
2924
+ includeYear: true,
2925
+ timeFormat: '12h' as const,
2926
+ } as DateFormatConfig,
2927
+
2928
+ VERBOSE_NO_TIME: {
2929
+ preset: DateFormatPreset.LONG_RANGE,
2930
+ includeTime: false,
2931
+ includeYear: true,
2932
+ } as DateFormatConfig,
2933
+
2934
+ VERBOSE_WITH_TIME: {
2935
+ preset: DateFormatPreset.LONG_RANGE,
2936
+ includeTime: true,
2937
+ includeYear: true,
2938
+ timeFormat: '12h' as const,
2939
+ } as DateFormatConfig,
2940
+
2941
+ ISO_FORMAT: {
2942
+ preset: DateFormatPreset.ISO_RANGE,
2943
+ includeTime: false,
2944
+ includeYear: true,
2945
+ } as DateFormatConfig,
2946
+
2947
+ ISO_WITH_TIME: {
2948
+ preset: DateFormatPreset.ISO_RANGE,
2949
+ includeTime: true,
2950
+ includeYear: true,
2951
+ timeFormat: '24h' as const,
2952
+ } as DateFormatConfig,
2953
+
2954
+ US_FORMAT: {
2955
+ preset: DateFormatPreset.US_RANGE,
2956
+ includeTime: false,
2957
+ includeYear: true,
2958
+ } as DateFormatConfig,
2959
+
2960
+ US_WITH_TIME: {
2961
+ preset: DateFormatPreset.US_RANGE,
2962
+ includeTime: true,
2963
+ includeYear: true,
2964
+ timeFormat: '12h' as const,
2965
+ } as DateFormatConfig,
2966
+ } as const
2967
+
2968
+ /**
2969
+ * Helper function to create custom format functions
2970
+ * @param formatFn Custom formatting function
2971
+ * @returns DateFormatConfig with custom format
2972
+ */
2973
+ export const createCustomFormat = (
2974
+ formatFn: CustomFormatFunction
2975
+ ): DateFormatConfig => ({
2976
+ preset: DateFormatPreset.CUSTOM,
2977
+ customFormat: formatFn,
2978
+ })
2979
+
2980
+ /**
2981
+ * Example custom format functions
2982
+ */
2983
+ export const CUSTOM_FORMAT_EXAMPLES = {
2984
+ RELATIVE: createCustomFormat((range) => {
2985
+ const now = new Date()
2986
+ const startDiff = Math.floor(
2987
+ (now.getTime() - range.startDate.getTime()) / (1000 * 60 * 60 * 24)
2988
+ )
2989
+ const endDiff = range.endDate
2990
+ ? Math.floor(
2991
+ (now.getTime() - range.endDate.getTime()) /
2992
+ (1000 * 60 * 60 * 24)
2993
+ )
2994
+ : 0
2995
+
2996
+ const formatRelative = (diff: number): string => {
2997
+ if (diff === 0) return 'today'
2998
+ if (diff === 1) return 'yesterday'
2999
+ if (diff === -1) return 'tomorrow'
3000
+ if (diff > 0) return `${diff} days ago`
3001
+ return `in ${Math.abs(diff)} days`
3002
+ }
3003
+
3004
+ if (range.endDate && isSameDay(range.startDate, range.endDate)) {
3005
+ return formatRelative(startDiff)
3006
+ }
3007
+
3008
+ return `${formatRelative(startDiff)} - ${formatRelative(endDiff)}`
3009
+ }),
3010
+
3011
+ BUSINESS: createCustomFormat((range) => {
3012
+ const startDate = range.startDate
3013
+ const endDate = range.endDate
3014
+
3015
+ const startQuarter = Math.floor(startDate.getMonth() / 3) + 1
3016
+ const endQuarter = endDate
3017
+ ? Math.floor(endDate.getMonth() / 3) + 1
3018
+ : null
3019
+
3020
+ if (
3021
+ startQuarter === endQuarter &&
3022
+ startDate.getFullYear() === endDate?.getFullYear()
3023
+ ) {
3024
+ return `Q${startQuarter} ${startDate.getFullYear()}`
3025
+ }
3026
+
3027
+ const startMonth = startDate.toLocaleDateString('en-US', {
3028
+ month: 'short',
3029
+ })
3030
+ const endMonth = endDate
3031
+ ? endDate.toLocaleDateString('en-US', { month: 'short' })
3032
+ : ''
3033
+ const year = startDate.getFullYear()
3034
+
3035
+ return `${startMonth} - ${endMonth} ${year}`
3036
+ }),
3037
+
3038
+ MINIMAL: createCustomFormat((range, options = {}) => {
3039
+ const { separator = '-' } = options
3040
+ const startDate = range.startDate
3041
+ const endDate = range.endDate
3042
+
3043
+ if (endDate && isSameDay(startDate, endDate)) {
3044
+ return `${startDate.getDate()} ${startDate.toLocaleDateString('en-US', { month: 'short' })}`
3045
+ }
3046
+
3047
+ const startDay = startDate.getDate()
3048
+ const endDay = endDate ? endDate.getDate() : null
3049
+ const month = startDate.toLocaleDateString('en-US', { month: 'short' })
3050
+
3051
+ if (
3052
+ startDate.getMonth() === endDate?.getMonth() &&
3053
+ startDate.getFullYear() === endDate?.getFullYear()
3054
+ ) {
3055
+ return `${startDay}${separator}${endDay} ${month}`
3056
+ }
3057
+
3058
+ const endMonth = endDate
3059
+ ? endDate.toLocaleDateString('en-US', { month: 'short' })
3060
+ : ''
3061
+ return `${startDay} ${month} ${separator} ${endDay} ${endMonth}`
3062
+ }),
3063
+ }
3064
+
3065
+ const HAPTIC_PATTERNS = {
3066
+ selection: [5], // Light feedback for scroll selection
3067
+ impact: [10], // Subtle impact for direct selection
3068
+ notification: [15, 30, 15], // Softer pattern for notifications
3069
+ } as const
3070
+
3071
+ /**
3072
+ * Enhanced haptic feedback utility with better error handling
3073
+ * @param type The type of haptic feedback to trigger
3074
+ */
3075
+ export const triggerHapticFeedback = (
3076
+ type: HapticFeedbackType = HapticFeedbackType.SELECTION
3077
+ ): void => {
3078
+ if (typeof window === 'undefined' || typeof navigator === 'undefined') {
3079
+ return
3080
+ }
3081
+
3082
+ const pattern = HAPTIC_PATTERNS[type] || HAPTIC_PATTERNS.selection
3083
+ let hapticTriggered = false
3084
+
3085
+ // Method 1: Standard Vibration API
3086
+ try {
3087
+ if (navigator.vibrate && typeof navigator.vibrate === 'function') {
3088
+ const success = navigator.vibrate(pattern)
3089
+ if (success) {
3090
+ hapticTriggered = true
3091
+ }
3092
+ }
3093
+ } catch {
3094
+ // Silent fail, continue to next method
3095
+ }
3096
+
3097
+ // Method 2: Vendor-prefixed vibration APIs
3098
+ if (!hapticTriggered) {
3099
+ try {
3100
+ // @ts-expect-error - Vendor prefixed APIs
3101
+ const vibrate = navigator.webkitVibrate || navigator.mozVibrate
3102
+ if (vibrate && typeof vibrate === 'function') {
3103
+ vibrate.call(navigator, pattern)
3104
+ hapticTriggered = true
3105
+ }
3106
+ } catch {
3107
+ // Silent fail
3108
+ }
3109
+ }
3110
+
3111
+ if (!hapticTriggered && /iPhone|iPad|iPod/i.test(navigator.userAgent)) {
3112
+ try {
3113
+ if (
3114
+ window.DeviceMotionEvent &&
3115
+ // @ts-expect-error - iOS 13+ requires permission
3116
+ typeof window.DeviceMotionEvent.requestPermission === 'function'
3117
+ ) {
3118
+ if (navigator.vibrate) {
3119
+ navigator.vibrate(pattern)
3120
+ hapticTriggered = true
3121
+ }
3122
+ }
3123
+ } catch {
3124
+ // Silent fail
3125
+ }
3126
+ }
3127
+ }
3128
+
3129
+ export class calendarHapticManager {
3130
+ private lastHapticTime = 0
3131
+ private lastHapticIndex = -1
3132
+ private readonly hapticCooldown = 100
3133
+ private isDestroyed = false
3134
+
3135
+ /**
3136
+ * Triggers haptic feedback when scrolling to a new item
3137
+ * @param currentIndex The current selected index
3138
+ */
3139
+ triggerScrollHaptic(currentIndex: number): void {
3140
+ if (this.isDestroyed || !Number.isInteger(currentIndex)) {
3141
+ return
3142
+ }
3143
+
3144
+ const now = performance.now()
3145
+
3146
+ if (
3147
+ currentIndex !== this.lastHapticIndex &&
3148
+ now - this.lastHapticTime >= this.hapticCooldown &&
3149
+ currentIndex >= 0
3150
+ ) {
3151
+ triggerHapticFeedback(HapticFeedbackType.SELECTION)
3152
+ this.lastHapticTime = now
3153
+ this.lastHapticIndex = currentIndex
3154
+ }
3155
+ }
3156
+
3157
+ triggerSelectionHaptic(): void {
3158
+ if (this.isDestroyed) {
3159
+ return
3160
+ }
3161
+
3162
+ triggerHapticFeedback(HapticFeedbackType.IMPACT)
3163
+ this.lastHapticTime = performance.now()
3164
+ }
3165
+
3166
+ reset(): void {
3167
+ this.lastHapticTime = 0
3168
+ this.lastHapticIndex = -1
3169
+ }
3170
+
3171
+ destroy(): void {
3172
+ this.isDestroyed = true
3173
+ this.reset()
3174
+ }
3175
+ }
3176
+
3177
+ export const MOBILE_CALENDAR_CONSTANTS = {
3178
+ // Scroll behavior - optimized for smoothness and control
3179
+ SNAP_DURATION: 340, // Controlled snap similar to native pickers
3180
+ MOMENTUM_THRESHOLD: 0.01, // Lower threshold for steadier momentum
3181
+ DECELERATION_RATE: 0.95, // Slower deceleration for smoother glide
3182
+ MIN_VELOCITY: 0.005, // Prevent tiny jitters
3183
+ MAX_MOMENTUM_DISTANCE: 50, // Allow momentum to scroll through many items
3184
+ VELOCITY_MULTIPLIER: 0.8, // Impact from swipe speed
3185
+ VELOCITY_SMOOTHING: 0.7, // Balanced velocity curve
3186
+ SCROLL_RESISTANCE: 0.95, // Lower friction for Apple-like feel
3187
+
3188
+ // Visual feedback - refined values
3189
+ SCALE_SELECTED: 1.02, // More subtle scaling
3190
+ SCALE_UNSELECTED: 0.98, // Gentler unselected scaling
3191
+ OPACITY_SELECTED: 1, // Full opacity for selected
3192
+ OPACITY_UNSELECTED: 0.9, // Better visibility
3193
+
3194
+ TRANSITION_DURATION: '220ms',
3195
+ EASING: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
3196
+
3197
+ ANIMATION_FRAME_LIMIT: 60, // Limit animation frames
3198
+ VELOCITY_HISTORY_SIZE: 3, // Smaller history for better performance
3199
+ } as const
3200
+
3201
+ export const MOBILE_PICKER_CONSTANTS = {
3202
+ ITEM_HEIGHT: 44,
3203
+ VISIBLE_ITEMS: 3,
3204
+ SCROLL_DEBOUNCE: 130,
3205
+ } as const
3206
+
3207
+ /**
3208
+ * Safely gets an item from an array with comprehensive bounds checking
3209
+ * @param items The array of items
3210
+ * @param index The index to access
3211
+ * @returns The item at the index or empty string if out of bounds
3212
+ */
3213
+ export const safeGetPickerItem = (
3214
+ items: (string | number)[],
3215
+ index: number
3216
+ ): string => {
3217
+ // Comprehensive validation
3218
+ if (!Array.isArray(items) || items.length === 0) {
3219
+ return ''
3220
+ }
3221
+
3222
+ if (!Number.isInteger(index) || index < 0 || index >= items.length) {
3223
+ return ''
3224
+ }
3225
+
3226
+ const item = items[index]
3227
+ if (item === undefined || item === null) {
3228
+ return ''
3229
+ }
3230
+
3231
+ return String(item)
3232
+ }
3233
+
3234
+ /**
3235
+ * Calculates the visible items for the picker with robust bounds checking
3236
+ * @param items The array of items
3237
+ * @param selectedIndex The currently selected index
3238
+ * @returns Object with top, center, and bottom items plus availability flags
3239
+ */
3240
+ export const getPickerVisibleItems = (
3241
+ items: (string | number)[],
3242
+ selectedIndex: number
3243
+ ) => {
3244
+ if (!Array.isArray(items) || items.length === 0) {
3245
+ return {
3246
+ topItem: '',
3247
+ centerItem: '',
3248
+ bottomItem: '',
3249
+ hasTopItem: false,
3250
+ hasBottomItem: false,
3251
+ }
3252
+ }
3253
+
3254
+ const clampedIndex = clampPickerIndex(selectedIndex, items.length)
3255
+
3256
+ return {
3257
+ topItem: safeGetPickerItem(items, clampedIndex - 1),
3258
+ centerItem: safeGetPickerItem(items, clampedIndex),
3259
+ bottomItem: safeGetPickerItem(items, clampedIndex + 1),
3260
+ hasTopItem: clampedIndex > 0 && items.length > 1,
3261
+ hasBottomItem: clampedIndex < items.length - 1 && items.length > 1,
3262
+ }
3263
+ }
3264
+
3265
+ /**
3266
+ * Validates and clamps an index to array bounds with safety checks
3267
+ * @param index The index to validate
3268
+ * @param arrayLength The length of the array
3269
+ * @returns Clamped index within bounds
3270
+ */
3271
+ export const clampPickerIndex = (
3272
+ index: number,
3273
+ arrayLength: number
3274
+ ): number => {
3275
+ if (!Number.isInteger(arrayLength) || arrayLength <= 0) {
3276
+ return 0
3277
+ }
3278
+
3279
+ if (!Number.isFinite(index)) {
3280
+ return 0
3281
+ }
3282
+
3283
+ return Math.max(0, Math.min(arrayLength - 1, Math.floor(index)))
3284
+ }
3285
+
3286
+ /**
3287
+ * Calculates scroll position for a given index with validation
3288
+ * @param index The target index
3289
+ * @param itemHeight Height of each item
3290
+ * @returns Scroll position in pixels
3291
+ */
3292
+ export const calculateScrollPosition = (
3293
+ index: number,
3294
+ itemHeight: number
3295
+ ): number => {
3296
+ if (
3297
+ !Number.isFinite(index) ||
3298
+ !Number.isFinite(itemHeight) ||
3299
+ itemHeight <= 0
3300
+ ) {
3301
+ return 0
3302
+ }
3303
+
3304
+ const clampedIndex = Math.max(0, Math.floor(index))
3305
+ return clampedIndex * itemHeight
3306
+ }
3307
+
3308
+ /**
3309
+ * Calculates index from scroll position with bounds checking
3310
+ * @param scrollTop Current scroll position
3311
+ * @param itemHeight Height of each item
3312
+ * @returns Calculated index
3313
+ */
3314
+ export const calculateIndexFromScroll = (
3315
+ scrollTop: number,
3316
+ itemHeight: number
3317
+ ): number => {
3318
+ if (
3319
+ !Number.isFinite(scrollTop) ||
3320
+ !Number.isFinite(itemHeight) ||
3321
+ itemHeight <= 0
3322
+ ) {
3323
+ return 0
3324
+ }
3325
+
3326
+ const calculatedIndex = Math.round(Math.max(0, scrollTop) / itemHeight)
3327
+ return Math.max(0, calculatedIndex)
3328
+ }
3329
+
3330
+ /**
3331
+ * Validates time input with comprehensive checks
3332
+ * @param input The input string to validate
3333
+ * @returns True if input is valid
3334
+ */
3335
+ export const isValidTimeInput = (input: string): boolean => {
3336
+ if (typeof input !== 'string') {
3337
+ return false
3338
+ }
3339
+
3340
+ return /^[0-9:.\s]*$/.test(input) && input.length <= 8
3341
+ }
3342
+
3343
+ /**
3344
+ * Formats time input with better validation
3345
+ * @param input The input string to format
3346
+ * @returns Formatted time string
3347
+ */
3348
+ export const formatTimeInput = (input: string): string => {
3349
+ if (!isValidTimeInput(input)) {
3350
+ return ''
3351
+ }
3352
+
3353
+ const cleaned = input.replace(/[^0-9:]/g, '')
3354
+
3355
+ if (cleaned.length === 0) {
3356
+ return ''
3357
+ }
3358
+
3359
+ if (cleaned.length <= 2) {
3360
+ return cleaned
3361
+ }
3362
+
3363
+ if (cleaned.length === 3 && !cleaned.includes(':')) {
3364
+ return cleaned.slice(0, 2) + ':' + cleaned.slice(2)
3365
+ }
3366
+
3367
+ if (cleaned.length >= 4) {
3368
+ const parts = cleaned.split(':')
3369
+ if (parts.length >= 2) {
3370
+ const hours = parts[0].slice(0, 2)
3371
+ const minutes = parts[1].slice(0, 2)
3372
+ return hours + ':' + minutes
3373
+ }
3374
+ }
3375
+
3376
+ return cleaned.slice(0, 5)
3377
+ }
3378
+
3379
+ /**
3380
+ * Validates if a date range has valid date/time combinations
3381
+ * @param range The date range to validate
3382
+ * @param allowSingleDateSelection Whether single date selection is allowed
3383
+ * @returns Validation result with specific error information
3384
+ */
3385
+ export const validateDateTimeRange = (
3386
+ range: DateRange,
3387
+ isSingleDatePicker: boolean
3388
+ ): {
3389
+ isValid: boolean
3390
+ error:
3391
+ | 'none'
3392
+ | 'invalid-time-order'
3393
+ | 'missing-dates'
3394
+ | 'invalid-single-date'
3395
+ message?: string
3396
+ } => {
3397
+ if (!range.startDate || (!isSingleDatePicker && !range.endDate)) {
3398
+ return {
3399
+ isValid: false,
3400
+ error: 'missing-dates',
3401
+ message: isSingleDatePicker
3402
+ ? 'A valid date is required for single date selection'
3403
+ : 'Both start and end dates are required',
3404
+ }
3405
+ }
3406
+
3407
+ if (range.endDate && range.endDate.getTime() < range.startDate.getTime()) {
3408
+ return {
3409
+ isValid: false,
3410
+ error: 'invalid-time-order',
3411
+ message:
3412
+ 'End time must be after or equal to start time on the same day',
3413
+ }
3414
+ }
3415
+
3416
+ return { isValid: true, error: 'none' }
3417
+ }
3418
+
3419
+ /**
3420
+ * Checks if a date should be hidden from calendar view
3421
+ * @param date The date to check
3422
+ * @param today Today's date
3423
+ * @param disableFutureDates Whether future dates should be hidden
3424
+ * @param disablePastDates Whether past dates should be hidden
3425
+ * @param hideFutureDates Whether to completely hide future dates (not just disable)
3426
+ * @param hidePastDates Whether to completely hide past dates (not just disable)
3427
+ * @returns True if the date should be hidden
3428
+ */
3429
+ export const shouldHideDateFromCalendar = (
3430
+ date: Date,
3431
+ today: Date,
3432
+ hideFutureDates: boolean = false,
3433
+ hidePastDates: boolean = false
3434
+ ): boolean => {
3435
+ const dateOnly = new Date(
3436
+ date.getFullYear(),
3437
+ date.getMonth(),
3438
+ date.getDate()
3439
+ )
3440
+ const todayOnly = new Date(
3441
+ today.getFullYear(),
3442
+ today.getMonth(),
3443
+ today.getDate()
3444
+ )
3445
+
3446
+ if (hideFutureDates && dateOnly > todayOnly) {
3447
+ return true
3448
+ }
3449
+
3450
+ if (hidePastDates && dateOnly < todayOnly) {
3451
+ return true
3452
+ }
3453
+
3454
+ return false
3455
+ }
3456
+
3457
+ /**
3458
+ * Enhanced calendar date click handler with proper single date selection
3459
+ * @param clickedDate The date that was clicked
3460
+ * @param selectedRange Current selected range
3461
+ * @param allowSingleDateSelection Whether single date selection is allowed
3462
+ * @param today Today's date for validation
3463
+ * @param disableFutureDates Whether future dates are disabled
3464
+ * @param disablePastDates Whether past dates are disabled
3465
+ * @param isDoubleClick Whether this is a double-click event
3466
+ * @returns New date range or null if click should be ignored
3467
+ */
3468
+ export const handleEnhancedCalendarDateClick = (
3469
+ clickedDate: Date,
3470
+ selectedRange: DateRange,
3471
+ allowSingleDateSelection: boolean = false,
3472
+ today: Date,
3473
+ disableFutureDates: boolean = false,
3474
+ disablePastDates: boolean = false,
3475
+ isDoubleClick: boolean = false
3476
+ ): DateRange | null => {
3477
+ // Validate date is not disabled
3478
+ if (
3479
+ (disableFutureDates && clickedDate > today) ||
3480
+ (disablePastDates && clickedDate < today)
3481
+ ) {
3482
+ return null
3483
+ }
3484
+
3485
+ // Create clean date without time components for comparison
3486
+ const clickedDateOnly = new Date(
3487
+ clickedDate.getFullYear(),
3488
+ clickedDate.getMonth(),
3489
+ clickedDate.getDate()
3490
+ )
3491
+
3492
+ // Handle double click - always create single date range if allowed
3493
+ if (isDoubleClick && allowSingleDateSelection) {
3494
+ return createSingleDateRange(clickedDateOnly)
3495
+ }
3496
+
3497
+ // For single date selection mode, always create single date ranges
3498
+ if (allowSingleDateSelection) {
3499
+ return createSingleDateRange(clickedDateOnly)
3500
+ }
3501
+
3502
+ // Regular range selection logic
3503
+ // Case 1: No selection - first click sets start date
3504
+ if (!selectedRange.startDate) {
3505
+ const startDate = createStartOfDay(clickedDateOnly)
3506
+ return { startDate, endDate: startDate }
3507
+ }
3508
+
3509
+ // Case 2: We have only start date (same as start date) - second click on same date
3510
+ if (
3511
+ selectedRange.startDate &&
3512
+ selectedRange.endDate &&
3513
+ isSameDay(selectedRange.startDate, selectedRange.endDate)
3514
+ ) {
3515
+ // If clicking the same day as start date, keep as single point
3516
+ if (isSameDay(clickedDateOnly, selectedRange.startDate)) {
3517
+ return {
3518
+ startDate: selectedRange.startDate,
3519
+ endDate: selectedRange.startDate,
3520
+ }
3521
+ }
3522
+
3523
+ // Clicking different date - set as end date
3524
+ if (clickedDateOnly > selectedRange.startDate) {
3525
+ // Normal range: start to end
3526
+ return {
3527
+ startDate: selectedRange.startDate,
3528
+ endDate: createEndOfDay(clickedDateOnly),
3529
+ }
3530
+ } else {
3531
+ // Clicked date is before start date - make it the new start
3532
+ const startDate = createStartOfDay(clickedDateOnly)
3533
+ return { startDate, endDate: startDate }
3534
+ }
3535
+ }
3536
+
3537
+ // Case 3: We have a complete range - start over with new start date
3538
+ if (hasCompleteRange(selectedRange)) {
3539
+ const startDate = createStartOfDay(clickedDateOnly)
3540
+ return { startDate, endDate: startDate }
3541
+ }
3542
+
3543
+ // Fallback - should not reach here, but handle gracefully
3544
+ const startDate = createStartOfDay(clickedDateOnly)
3545
+ return { startDate, endDate: startDate }
3546
+ }
3547
+
3548
+ /**
3549
+ * Validates custom range configuration
3550
+ * @param config The custom range configuration to validate
3551
+ * @returns Validation result
3552
+ */
3553
+ export const validateCustomRangeConfig = (
3554
+ config: CustomRangeConfig
3555
+ ): { isValid: boolean; error?: string } => {
3556
+ if (!config.calculateEndDate && !config.referenceRange) {
3557
+ return {
3558
+ isValid: false,
3559
+ error: 'Either calculateEndDate or referenceRange must be provided',
3560
+ }
3561
+ }
3562
+
3563
+ if (config.backwardDays !== undefined && config.backwardDays < 0) {
3564
+ return {
3565
+ isValid: false,
3566
+ error: 'backwardDays must be a non-negative number',
3567
+ }
3568
+ }
3569
+
3570
+ return { isValid: true }
3571
+ }
3572
+
3573
+ /**
3574
+ * Handles calendar date click with custom range configuration support
3575
+ * @param clickedDate The date that was clicked
3576
+ * @param selectedRange Current selected range
3577
+ * @param allowSingleDateSelection Whether single date selection is allowed
3578
+ * @param today Today's date for validation
3579
+ * @param disableFutureDates Whether future dates are disabled
3580
+ * @param disablePastDates Whether past dates are disabled
3581
+ * @param customRangeConfig Custom range configuration
3582
+ * @param isDoubleClick Whether this is a double-click event
3583
+ * @returns New date range or null if click should be ignored
3584
+ */
3585
+ export const handleCustomRangeCalendarDateClick = (
3586
+ clickedDate: Date,
3587
+ allowSingleDateSelection: boolean = false,
3588
+ today: Date,
3589
+ disableFutureDates: boolean = false,
3590
+ disablePastDates: boolean = false,
3591
+ customRangeConfig?: CustomRangeConfig,
3592
+ isDoubleClick: boolean = false,
3593
+ timezone?: string,
3594
+ selectedRange?: DateRange,
3595
+ isSingleDatePicker?: boolean
3596
+ ): DateRange | null => {
3597
+ // Validate date is not disabled
3598
+ if (
3599
+ (disableFutureDates && clickedDate > today) ||
3600
+ (disablePastDates && clickedDate < today)
3601
+ ) {
3602
+ return null
3603
+ }
3604
+
3605
+ // If no custom range config, use standard logic
3606
+ if (!customRangeConfig) {
3607
+ return handleCalendarDateClick(
3608
+ clickedDate,
3609
+ allowSingleDateSelection,
3610
+ today,
3611
+ disableFutureDates,
3612
+ disablePastDates,
3613
+ isDoubleClick,
3614
+ timezone,
3615
+ selectedRange,
3616
+ isSingleDatePicker
3617
+ )
3618
+ }
3619
+
3620
+ // Custom range logic
3621
+ const clickedDateOnly = new Date(
3622
+ clickedDate.getFullYear(),
3623
+ clickedDate.getMonth(),
3624
+ clickedDate.getDate()
3625
+ )
3626
+
3627
+ // Handle double click - always create single date range if allowed
3628
+ if (isDoubleClick && allowSingleDateSelection) {
3629
+ return createSingleDateRange(
3630
+ clickedDateOnly,
3631
+ timezone,
3632
+ disableFutureDates,
3633
+ disablePastDates,
3634
+ today
3635
+ )
3636
+ }
3637
+
3638
+ // Calculate end date based on custom config
3639
+ let endDate: Date
3640
+
3641
+ if (
3642
+ customRangeConfig.referenceRange &&
3643
+ customRangeConfig.referenceRange.endDate
3644
+ ) {
3645
+ // Use reference range to calculate duration
3646
+ const duration =
3647
+ customRangeConfig.referenceRange.endDate.getTime() -
3648
+ customRangeConfig.referenceRange.startDate.getTime()
3649
+ endDate = new Date(clickedDateOnly.getTime() + duration)
3650
+ } else if (customRangeConfig.calculateEndDate) {
3651
+ const calculatedDate =
3652
+ customRangeConfig.calculateEndDate(clickedDateOnly)
3653
+ if (!calculatedDate) {
3654
+ // Fallback to standard logic if calculation returns null
3655
+ return handleCalendarDateClick(
3656
+ clickedDate,
3657
+ allowSingleDateSelection,
3658
+ today,
3659
+ disableFutureDates,
3660
+ disablePastDates,
3661
+ isDoubleClick,
3662
+ timezone,
3663
+ selectedRange,
3664
+ isSingleDatePicker
3665
+ )
3666
+ }
3667
+ endDate = calculatedDate
3668
+ } else {
3669
+ // Fallback to standard logic
3670
+ return handleCalendarDateClick(
3671
+ clickedDate,
3672
+ allowSingleDateSelection,
3673
+ today,
3674
+ disableFutureDates,
3675
+ disablePastDates,
3676
+ isDoubleClick,
3677
+ timezone,
3678
+ selectedRange,
3679
+ isSingleDatePicker
3680
+ )
3681
+ }
3682
+
3683
+ // Handle backward days if specified
3684
+ if (customRangeConfig.backwardDays !== undefined) {
3685
+ const startDate = new Date(clickedDateOnly)
3686
+ startDate.setDate(startDate.getDate() - customRangeConfig.backwardDays)
3687
+ startDate.setHours(0, 0, 0, 0)
3688
+
3689
+ const calculatedEndDate = new Date(clickedDateOnly)
3690
+ calculatedEndDate.setHours(23, 59, 59, 999)
3691
+
3692
+ return { startDate, endDate: calculatedEndDate }
3693
+ }
3694
+
3695
+ // Set start date to beginning of clicked day
3696
+ const startDate = createStartOfDay(
3697
+ clickedDateOnly,
3698
+ disablePastDates,
3699
+ today,
3700
+ timezone
3701
+ )
3702
+
3703
+ // If manual end date selection is allowed and we already have a start date
3704
+ if (
3705
+ customRangeConfig.allowManualEndDateSelection &&
3706
+ selectedRange &&
3707
+ !(
3708
+ selectedRange.endDate &&
3709
+ isSameDay(selectedRange.startDate, selectedRange.endDate)
3710
+ )
3711
+ ) {
3712
+ // Allow second click to set end date
3713
+ if (clickedDateOnly > selectedRange.startDate) {
3714
+ return {
3715
+ startDate: selectedRange.startDate,
3716
+ endDate: createEndOfDay(
3717
+ clickedDateOnly,
3718
+ disableFutureDates,
3719
+ today,
3720
+ timezone
3721
+ ),
3722
+ }
3723
+ }
3724
+ }
3725
+
3726
+ // Ensure end date is at end of day
3727
+ if (isSameDay(startDate, endDate)) {
3728
+ endDate = createEndOfDay(endDate, disableFutureDates, today, timezone)
3729
+ }
3730
+
3731
+ return { startDate, endDate }
3732
+ }
3733
+
3734
+ // =============================================================================
3735
+ // PRESET DETECTION UTILITIES
3736
+ // =============================================================================
3737
+
3738
+ /**
3739
+ * Checks if two dates represent the same calendar day (ignoring time and timezone)
3740
+ */
3741
+ export const isSameCalendarDay = (date1: Date, date2: Date): boolean => {
3742
+ // Compare both in local timezone and UTC to handle timezone differences
3743
+ const localSame =
3744
+ date1.getFullYear() === date2.getFullYear() &&
3745
+ date1.getMonth() === date2.getMonth() &&
3746
+ date1.getDate() === date2.getDate()
3747
+
3748
+ const utcSame =
3749
+ date1.getUTCFullYear() === date2.getUTCFullYear() &&
3750
+ date1.getUTCMonth() === date2.getUTCMonth() &&
3751
+ date1.getUTCDate() === date2.getUTCDate()
3752
+
3753
+ return localSame || utcSame
3754
+ }
3755
+
3756
+ /**
3757
+ * Checks if a date range represents a full day in any timezone
3758
+ */
3759
+ export const isFullDayRange = (startDate: Date, endDate: Date): boolean => {
3760
+ // Check if it's a full day in local timezone
3761
+ const localStartIsStartOfDay =
3762
+ startDate.getHours() === 0 &&
3763
+ startDate.getMinutes() === 0 &&
3764
+ startDate.getSeconds() === 0 &&
3765
+ startDate.getMilliseconds() === 0
3766
+
3767
+ const localEndIsEndOfDay =
3768
+ endDate.getHours() === 23 &&
3769
+ endDate.getMinutes() === 59 &&
3770
+ endDate.getSeconds() === 59 &&
3771
+ endDate.getMilliseconds() === 999
3772
+
3773
+ // Check if it's a full day in UTC
3774
+ const utcStartIsStartOfDay =
3775
+ startDate.getUTCHours() === 0 &&
3776
+ startDate.getUTCMinutes() === 0 &&
3777
+ startDate.getUTCSeconds() === 0 &&
3778
+ startDate.getUTCMilliseconds() === 0
3779
+
3780
+ const utcEndIsEndOfDay =
3781
+ endDate.getUTCHours() === 23 &&
3782
+ endDate.getUTCMinutes() === 59 &&
3783
+ endDate.getUTCSeconds() === 59 &&
3784
+ endDate.getUTCMilliseconds() === 999
3785
+
3786
+ const isLocalFullDay =
3787
+ localStartIsStartOfDay &&
3788
+ localEndIsEndOfDay &&
3789
+ isSameCalendarDay(startDate, endDate)
3790
+ const isUtcFullDay =
3791
+ utcStartIsStartOfDay &&
3792
+ utcEndIsEndOfDay &&
3793
+ isSameCalendarDay(startDate, endDate)
3794
+
3795
+ return isLocalFullDay || isUtcFullDay
3796
+ }
3797
+
3798
+ export const convertLocalDateToTimezoneDate = (
3799
+ date: Date,
3800
+ timezone?: string
3801
+ ): Date => {
3802
+ if (timezone) {
3803
+ const parts = getDatePartsInTimezone(date, timezone)
3804
+ return new Date(
3805
+ parts.year,
3806
+ parts.month,
3807
+ parts.day,
3808
+ parts.hours,
3809
+ parts.minutes,
3810
+ parts.seconds
3811
+ )
3812
+ } else {
3813
+ return date
3814
+ }
3815
+ }
3816
+
3817
+ /**
3818
+ * Checks if a date range matches "Today" preset
3819
+ */
3820
+ export const matchesTodayPreset = (
3821
+ range: DateRange,
3822
+ timezone?: string
3823
+ ): boolean => {
3824
+ if (!range.endDate) return false
3825
+ const now = new Date()
3826
+
3827
+ const startDate = convertLocalDateToTimezoneDate(range.startDate, timezone)
3828
+ const endDate = convertLocalDateToTimezoneDate(range.endDate, timezone)
3829
+ const today = convertLocalDateToTimezoneDate(now, timezone)
3830
+
3831
+ // Check if start date is today at midnight
3832
+ const startIsToday = isSameCalendarDay(startDate, today)
3833
+ const startIsStartOfDay =
3834
+ startDate.getHours() === 0 &&
3835
+ startDate.getMinutes() === 0 &&
3836
+ startDate.getSeconds() === 0 &&
3837
+ startDate.getMilliseconds() === 0
3838
+
3839
+ // End date should be sometime today (not necessarily current time)
3840
+ const endIsToday = isSameCalendarDay(endDate, today)
3841
+
3842
+ return startIsToday && startIsStartOfDay && endIsToday
3843
+ }
3844
+
3845
+ /**
3846
+ * Checks if a date range matches "Yesterday" preset
3847
+ */
3848
+ export const matchesYesterdayPreset = (
3849
+ range: DateRange,
3850
+ timezone?: string
3851
+ ): boolean => {
3852
+ if (!range.endDate) return false
3853
+ const yesterday_ = new Date()
3854
+ yesterday_.setDate(yesterday_.getDate() - 1)
3855
+
3856
+ const startDate = convertLocalDateToTimezoneDate(range.startDate, timezone)
3857
+ const endDate = convertLocalDateToTimezoneDate(range.endDate, timezone)
3858
+ const yesterday = convertLocalDateToTimezoneDate(yesterday_, timezone)
3859
+
3860
+ // Check if it's a full day range for yesterday
3861
+ const startIsYesterday = isSameCalendarDay(startDate, yesterday)
3862
+ const endIsYesterday = isSameCalendarDay(endDate, yesterday)
3863
+
3864
+ // More flexible check for full day - allow for slight variations in milliseconds
3865
+ const isFullDayOrClose =
3866
+ isFullDayRange(startDate, endDate) ||
3867
+ (startIsYesterday &&
3868
+ endIsYesterday &&
3869
+ startDate.getHours() === 0 &&
3870
+ startDate.getMinutes() === 0 &&
3871
+ endDate.getHours() === 23 &&
3872
+ endDate.getMinutes() === 59)
3873
+
3874
+ return startIsYesterday && endIsYesterday && isFullDayOrClose
3875
+ }
3876
+
3877
+ /**
3878
+ * Checks if a date range matches "Tomorrow" preset
3879
+ */
3880
+ export const matchesTomorrowPreset = (range: DateRange): boolean => {
3881
+ if (!range.endDate) return false
3882
+ const now = new Date()
3883
+ const tomorrow = new Date(
3884
+ now.getFullYear(),
3885
+ now.getMonth(),
3886
+ now.getDate() + 1
3887
+ )
3888
+
3889
+ // Check if it's a full day range for tomorrow
3890
+ const startIsTomorrow = isSameCalendarDay(range.startDate, tomorrow)
3891
+ const endIsTomorrow = isSameCalendarDay(range.endDate, tomorrow)
3892
+ const isFullDay = isFullDayRange(range.startDate, range.endDate)
3893
+
3894
+ return startIsTomorrow && endIsTomorrow && isFullDay
3895
+ }
3896
+
3897
+ /**
3898
+ * Checks if a date range matches "This Month" preset
3899
+ */
3900
+ export const matchesThisMonthPreset = (range: DateRange): boolean => {
3901
+ if (!range.endDate) return false
3902
+ const now = new Date()
3903
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
3904
+
3905
+ const startOfMonth = new Date(
3906
+ today.getFullYear(),
3907
+ today.getMonth(),
3908
+ 1,
3909
+ 0,
3910
+ 0,
3911
+ 0,
3912
+ 0
3913
+ )
3914
+
3915
+ const startMatches =
3916
+ Math.abs(range.startDate.getTime() - startOfMonth.getTime()) <=
3917
+ DATE_RANGE_PICKER_CONSTANTS.TIMEZONE_TOLERANCE_HOURS * 60 * 60 * 1000
3918
+
3919
+ const endInCurrentMonth =
3920
+ range.endDate.getFullYear() === now.getFullYear() &&
3921
+ range.endDate.getMonth() === now.getMonth()
3922
+
3923
+ return startMatches && endInCurrentMonth
3924
+ }
3925
+
3926
+ /**
3927
+ * Checks if a date range matches "Last Month" preset
3928
+ */
3929
+ export const matchesLastMonthPreset = (range: DateRange): boolean => {
3930
+ if (!range.endDate) return false
3931
+ const now = new Date()
3932
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
3933
+
3934
+ const lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1)
3935
+
3936
+ const startOfLastMonth = new Date(
3937
+ lastMonth.getFullYear(),
3938
+ lastMonth.getMonth(),
3939
+ 1,
3940
+ 0,
3941
+ 0,
3942
+ 0,
3943
+ 0
3944
+ )
3945
+
3946
+ const endOfLastMonth = new Date(
3947
+ lastMonth.getFullYear(),
3948
+ lastMonth.getMonth() + 1,
3949
+ 0,
3950
+ 23,
3951
+ 59,
3952
+ 59,
3953
+ 999
3954
+ )
3955
+
3956
+ const startMatches =
3957
+ Math.abs(range.startDate.getTime() - startOfLastMonth.getTime()) <=
3958
+ DATE_RANGE_PICKER_CONSTANTS.TIMEZONE_TOLERANCE_HOURS * 60 * 60 * 1000
3959
+
3960
+ const endMatches =
3961
+ Math.abs(range.endDate.getTime() - endOfLastMonth.getTime()) <=
3962
+ DATE_RANGE_PICKER_CONSTANTS.TIMEZONE_TOLERANCE_HOURS * 60 * 60 * 1000
3963
+
3964
+ return startMatches && endMatches
3965
+ }
3966
+
3967
+ /**
3968
+ * Robust preset detection that works with both UTC and local timezone dates
3969
+ */
3970
+ export const detectPresetFromRange = (
3971
+ range: DateRange,
3972
+ timezone?: string
3973
+ ): DateRangePreset => {
3974
+ if (!range.startDate || !range.endDate) {
3975
+ return DateRangePreset.CUSTOM
3976
+ }
3977
+
3978
+ const matchRange: DateRange = {
3979
+ startDate: range.startDate,
3980
+ endDate: range.endDate,
3981
+ }
3982
+
3983
+ // Check specific day presets first
3984
+ if (matchesTodayPreset(matchRange, timezone)) {
3985
+ return DateRangePreset.TODAY
3986
+ }
3987
+
3988
+ if (matchesYesterdayPreset(matchRange, timezone)) {
3989
+ return DateRangePreset.YESTERDAY
3990
+ }
3991
+
3992
+ if (matchesTomorrowPreset(matchRange)) {
3993
+ return DateRangePreset.TOMORROW
3994
+ }
3995
+
3996
+ if (matchesThisMonthPreset(matchRange)) {
3997
+ return DateRangePreset.THIS_MONTH
3998
+ }
3999
+
4000
+ if (matchesLastMonthPreset(matchRange)) {
4001
+ return DateRangePreset.LAST_MONTH
4002
+ }
4003
+
4004
+ // Check time-based presets with tolerance
4005
+ const now = new Date()
4006
+ const timeDiff = Math.abs(range.endDate.getTime() - now.getTime())
4007
+ const tolerance = DATE_RANGE_PICKER_CONSTANTS.PRESET_DETECTION_TOLERANCE_MS
4008
+
4009
+ if (timeDiff <= tolerance) {
4010
+ const durationMs = range.endDate.getTime() - range.startDate.getTime()
4011
+
4012
+ // Check minute and hour-based presets
4013
+ const thirtyMinutes = 30 * 60 * 1000
4014
+ const oneHour = 60 * 60 * 1000
4015
+ const sixHours = 6 * 60 * 60 * 1000
4016
+ const twentyFourHours = 24 * 60 * 60 * 1000
4017
+
4018
+ if (Math.abs(durationMs - thirtyMinutes) <= tolerance) {
4019
+ return DateRangePreset.LAST_30_MINUTES
4020
+ }
4021
+
4022
+ if (Math.abs(durationMs - oneHour) <= tolerance) {
4023
+ return DateRangePreset.LAST_1_HOUR
4024
+ }
4025
+
4026
+ if (Math.abs(durationMs - sixHours) <= tolerance) {
4027
+ return DateRangePreset.LAST_6_HOURS
4028
+ }
4029
+
4030
+ if (Math.abs(durationMs - twentyFourHours) <= tolerance) {
4031
+ return DateRangePreset.LAST_24_HOURS
4032
+ }
4033
+ }
4034
+
4035
+ // Check multi-day presets
4036
+ const daysDiff = Math.round(
4037
+ (range.endDate.getTime() - range.startDate.getTime()) /
4038
+ (24 * 60 * 60 * 1000)
4039
+ )
4040
+
4041
+ if (daysDiff === 6 || daysDiff === 7) {
4042
+ // Allow for timezone differences
4043
+ // Check if start is 7 days ago at midnight
4044
+ const sevenDaysAgo = new Date(
4045
+ now.getFullYear(),
4046
+ now.getMonth(),
4047
+ now.getDate() - 6,
4048
+ 0,
4049
+ 0,
4050
+ 0,
4051
+ 0
4052
+ )
4053
+ if (
4054
+ Math.abs(range.startDate.getTime() - sevenDaysAgo.getTime()) <=
4055
+ DATE_RANGE_PICKER_CONSTANTS.TIMEZONE_TOLERANCE_HOURS *
4056
+ 60 *
4057
+ 60 *
4058
+ 1000
4059
+ ) {
4060
+ return DateRangePreset.LAST_7_DAYS
4061
+ }
4062
+ }
4063
+
4064
+ if (daysDiff >= 29 && daysDiff <= 30) {
4065
+ // Allow for timezone differences
4066
+ // Check if start is 30 days ago at midnight
4067
+ const thirtyDaysAgo = new Date(
4068
+ now.getFullYear(),
4069
+ now.getMonth(),
4070
+ now.getDate() - 29,
4071
+ 0,
4072
+ 0,
4073
+ 0,
4074
+ 0
4075
+ )
4076
+ if (
4077
+ Math.abs(range.startDate.getTime() - thirtyDaysAgo.getTime()) <=
4078
+ DATE_RANGE_PICKER_CONSTANTS.TIMEZONE_TOLERANCE_HOURS *
4079
+ 60 *
4080
+ 60 *
4081
+ 1000
4082
+ ) {
4083
+ return DateRangePreset.LAST_30_DAYS
4084
+ }
4085
+ }
4086
+
4087
+ // For future presets, check if start is today and end is appropriate days later
4088
+ const startIsToday = isSameCalendarDay(range.startDate, now)
4089
+
4090
+ if (startIsToday && (daysDiff === 6 || daysDiff === 7)) {
4091
+ return DateRangePreset.NEXT_7_DAYS
4092
+ }
4093
+
4094
+ if (startIsToday && daysDiff >= 29 && daysDiff <= 30) {
4095
+ return DateRangePreset.NEXT_30_DAYS
4096
+ }
4097
+
4098
+ // Check month-based presets (approximate)
4099
+ const monthsDiff = Math.round(daysDiff / 30)
4100
+
4101
+ if (monthsDiff === 3) {
4102
+ return DateRangePreset.LAST_3_MONTHS
4103
+ }
4104
+
4105
+ if (monthsDiff === 12) {
4106
+ return DateRangePreset.LAST_12_MONTHS
4107
+ }
4108
+
4109
+ if (startIsToday && monthsDiff === 3) {
4110
+ return DateRangePreset.NEXT_3_MONTHS
4111
+ }
4112
+
4113
+ if (startIsToday && monthsDiff === 12) {
4114
+ return DateRangePreset.NEXT_12_MONTHS
4115
+ }
4116
+
4117
+ return DateRangePreset.CUSTOM
4118
+ }
4119
+
4120
+ // =============================================================================
4121
+ // CUSTOM PRESET CONFIGURATION UTILITIES
4122
+ // =============================================================================
4123
+
4124
+ /**
4125
+ * Default preset configuration for the DateRangePicker
4126
+ */
4127
+ export const DEFAULT_PRESET_CONFIG: DateRangePreset[] = [
4128
+ DateRangePreset.LAST_30_MINUTES,
4129
+ DateRangePreset.LAST_1_HOUR,
4130
+ DateRangePreset.LAST_6_HOURS,
4131
+ DateRangePreset.LAST_24_HOURS,
4132
+ DateRangePreset.TODAY,
4133
+ DateRangePreset.YESTERDAY,
4134
+ DateRangePreset.LAST_7_DAYS,
4135
+ DateRangePreset.LAST_30_DAYS,
4136
+ DateRangePreset.THIS_MONTH,
4137
+ DateRangePreset.LAST_MONTH,
4138
+ ]
4139
+
4140
+ /**
4141
+ * Store for custom preset definitions
4142
+ * Maps custom preset IDs to their definitions
4143
+ */
4144
+ const customPresetDefinitions = new Map<string, CustomPresetDefinition>()
4145
+
4146
+ /**
4147
+ * Get custom preset definition by ID
4148
+ */
4149
+ export const getCustomPresetDefinition = (
4150
+ id: string
4151
+ ): CustomPresetDefinition | undefined => {
4152
+ return customPresetDefinitions.get(id)
4153
+ }
4154
+
4155
+ /**
4156
+ * Processes custom presets configuration and returns normalized preset configs
4157
+ * @param customPresets User-provided presets configuration
4158
+ * @returns Array of normalized CustomPresetConfig objects
4159
+ */
4160
+ export const processCustomPresets = (
4161
+ customPresets?: PresetsConfig
4162
+ ): CustomPresetConfig[] => {
4163
+ if (!customPresets || customPresets.length === 0) {
4164
+ return DEFAULT_PRESET_CONFIG.map((preset) => ({
4165
+ preset,
4166
+ visible: true,
4167
+ }))
4168
+ }
4169
+
4170
+ // If first item is a DateRangePreset, treat as simple array
4171
+ if (typeof customPresets[0] === 'string') {
4172
+ return (customPresets as DateRangePreset[]).map((preset) => ({
4173
+ preset,
4174
+ visible: true,
4175
+ }))
4176
+ }
4177
+
4178
+ // Process array that may contain DateRangePreset, CustomPresetConfig, or CustomPresetDefinition
4179
+ return customPresets.map((item) => {
4180
+ // If it's a string, it's a DateRangePreset
4181
+ if (typeof item === 'string') {
4182
+ return {
4183
+ preset: item as DateRangePreset,
4184
+ visible: true,
4185
+ }
4186
+ }
4187
+
4188
+ // Check if it's a CustomPresetDefinition (has id and getDateRange)
4189
+ if ('id' in item && 'getDateRange' in item) {
4190
+ const definition = item as CustomPresetDefinition
4191
+ // Store the definition for later use
4192
+ customPresetDefinitions.set(definition.id, definition)
4193
+
4194
+ // Convert to CustomPresetConfig using the id as the preset value
4195
+ return {
4196
+ preset: definition.id as DateRangePreset,
4197
+ label: definition.label,
4198
+ visible: definition.visible !== false,
4199
+ }
4200
+ }
4201
+
4202
+ // Otherwise, it's already a CustomPresetConfig
4203
+ return item as CustomPresetConfig
4204
+ })
4205
+ }
4206
+
4207
+ /**
4208
+ * Filters presets based on visibility and other criteria
4209
+ * @param presetConfigs Array of preset configurations
4210
+ * @param disableFutureDates Whether future date presets should be excluded
4211
+ * @param disablePastDates Whether past date presets should be excluded
4212
+ * @returns Array of visible and enabled presets
4213
+ */
4214
+ export const getFilteredPresets = (
4215
+ presetConfigs: CustomPresetConfig[],
4216
+ disableFutureDates: boolean = false,
4217
+ disablePastDates: boolean = false
4218
+ ): DateRangePreset[] => {
4219
+ const futurePresets = [
4220
+ DateRangePreset.TOMORROW,
4221
+ DateRangePreset.NEXT_7_DAYS,
4222
+ DateRangePreset.NEXT_30_DAYS,
4223
+ DateRangePreset.NEXT_3_MONTHS,
4224
+ DateRangePreset.NEXT_12_MONTHS,
4225
+ ]
4226
+
4227
+ const pastPresets = [
4228
+ DateRangePreset.LAST_30_MINUTES,
4229
+ DateRangePreset.LAST_1_HOUR,
4230
+ DateRangePreset.LAST_6_HOURS,
4231
+ DateRangePreset.LAST_24_HOURS,
4232
+ DateRangePreset.YESTERDAY,
4233
+ DateRangePreset.LAST_7_DAYS,
4234
+ DateRangePreset.LAST_30_DAYS,
4235
+ DateRangePreset.LAST_MONTH,
4236
+ DateRangePreset.LAST_3_MONTHS,
4237
+ DateRangePreset.LAST_12_MONTHS,
4238
+ ]
4239
+
4240
+ return presetConfigs
4241
+ .filter((config) => {
4242
+ // Filter out invisible presets
4243
+ if (config.visible === false) {
4244
+ return false
4245
+ }
4246
+
4247
+ // Filter out future presets if disabled
4248
+ if (disableFutureDates && futurePresets.includes(config.preset)) {
4249
+ return false
4250
+ }
4251
+
4252
+ // Filter out past presets if disabled
4253
+ if (disablePastDates && pastPresets.includes(config.preset)) {
4254
+ return false
4255
+ }
4256
+
4257
+ return true
4258
+ })
4259
+ .map((config) => config.preset)
4260
+ }
4261
+
4262
+ /**
4263
+ * Gets the label for a preset, using custom label if provided
4264
+ * @param preset The preset to get label for
4265
+ * @param presetConfigs Array of preset configurations with potential custom labels
4266
+ * @returns The label for the preset
4267
+ */
4268
+ export const getPresetLabelWithCustom = (
4269
+ preset: DateRangePreset,
4270
+ presetConfigs?: CustomPresetConfig[]
4271
+ ): string => {
4272
+ if (presetConfigs) {
4273
+ const config = presetConfigs.find((config) => config.preset === preset)
4274
+ if (config?.label) {
4275
+ return config.label
4276
+ }
4277
+ }
4278
+
4279
+ return getPresetLabel(preset)
4280
+ }
4281
+
4282
+ /**
4283
+ * Creates a preset configuration with custom label and visibility
4284
+ * @param preset The preset enum value
4285
+ * @param label Optional custom label
4286
+ * @param visible Whether the preset should be visible (default: true)
4287
+ * @returns CustomPresetConfig object
4288
+ */
4289
+ export const createPresetConfig = (
4290
+ preset: DateRangePreset,
4291
+ label?: string,
4292
+ visible: boolean = true
4293
+ ): CustomPresetConfig => ({
4294
+ preset,
4295
+ label,
4296
+ visible,
4297
+ })
4298
+
4299
+ /**
4300
+ * Helper function to create common preset configurations
4301
+ */
4302
+ export const PRESET_HELPERS = {
4303
+ /**
4304
+ * Creates a configuration for time-based presets only
4305
+ */
4306
+ timeBasedOnly: (): CustomPresetConfig[] => [
4307
+ createPresetConfig(DateRangePreset.LAST_30_MINUTES),
4308
+ createPresetConfig(DateRangePreset.LAST_1_HOUR),
4309
+ createPresetConfig(DateRangePreset.LAST_6_HOURS),
4310
+ createPresetConfig(DateRangePreset.LAST_24_HOURS),
4311
+ ],
4312
+
4313
+ /**
4314
+ * Creates a configuration for day-based presets only
4315
+ */
4316
+ dayBasedOnly: (): CustomPresetConfig[] => [
4317
+ createPresetConfig(DateRangePreset.TODAY),
4318
+ createPresetConfig(DateRangePreset.YESTERDAY),
4319
+ createPresetConfig(DateRangePreset.LAST_7_DAYS),
4320
+ createPresetConfig(DateRangePreset.LAST_30_DAYS),
4321
+ ],
4322
+
4323
+ /**
4324
+ * Creates a configuration for month-based presets only
4325
+ */
4326
+ monthBasedOnly: (): CustomPresetConfig[] => [
4327
+ createPresetConfig(DateRangePreset.THIS_MONTH),
4328
+ createPresetConfig(DateRangePreset.LAST_MONTH),
4329
+ createPresetConfig(DateRangePreset.LAST_3_MONTHS),
4330
+ createPresetConfig(DateRangePreset.LAST_12_MONTHS),
4331
+ ],
4332
+
4333
+ /**
4334
+ * Creates a minimal preset configuration
4335
+ */
4336
+ minimal: (): CustomPresetConfig[] => [
4337
+ createPresetConfig(DateRangePreset.TODAY),
4338
+ createPresetConfig(DateRangePreset.LAST_7_DAYS),
4339
+ createPresetConfig(DateRangePreset.LAST_30_DAYS),
4340
+ ],
4341
+
4342
+ /**
4343
+ * Creates a comprehensive preset configuration
4344
+ */
4345
+ comprehensive: (): CustomPresetConfig[] => [
4346
+ createPresetConfig(DateRangePreset.LAST_30_MINUTES),
4347
+ createPresetConfig(DateRangePreset.LAST_1_HOUR),
4348
+ createPresetConfig(DateRangePreset.LAST_6_HOURS),
4349
+ createPresetConfig(DateRangePreset.LAST_24_HOURS),
4350
+ createPresetConfig(DateRangePreset.TODAY),
4351
+ createPresetConfig(DateRangePreset.YESTERDAY),
4352
+ createPresetConfig(DateRangePreset.LAST_7_DAYS),
4353
+ createPresetConfig(DateRangePreset.LAST_30_DAYS),
4354
+ createPresetConfig(DateRangePreset.THIS_MONTH),
4355
+ createPresetConfig(DateRangePreset.LAST_MONTH),
4356
+ createPresetConfig(DateRangePreset.LAST_3_MONTHS),
4357
+ createPresetConfig(DateRangePreset.LAST_12_MONTHS),
4358
+ ],
4359
+
4360
+ /**
4361
+ * Creates a configuration with custom labels
4362
+ */
4363
+ withCustomLabels: (): CustomPresetConfig[] => [
4364
+ createPresetConfig(DateRangePreset.LAST_30_MINUTES, 'Last 30 min'),
4365
+ createPresetConfig(DateRangePreset.LAST_1_HOUR, 'Last hour'),
4366
+ createPresetConfig(DateRangePreset.LAST_6_HOURS, 'Last 6 hours'),
4367
+ createPresetConfig(DateRangePreset.LAST_24_HOURS, 'Last 24 hours'),
4368
+ createPresetConfig(DateRangePreset.TODAY, 'Today'),
4369
+ createPresetConfig(DateRangePreset.YESTERDAY, 'Yesterday'),
4370
+ createPresetConfig(DateRangePreset.LAST_7_DAYS, 'Last 7 days'),
4371
+ createPresetConfig(DateRangePreset.LAST_30_DAYS, 'Last 30 days'),
4372
+ createPresetConfig(DateRangePreset.THIS_MONTH, 'This month'),
4373
+ createPresetConfig(DateRangePreset.LAST_MONTH, 'Last month'),
4374
+ ],
4375
+
4376
+ /**
4377
+ * Creates a configuration with some presets hidden
4378
+ */
4379
+ selectiveVisibility: (): CustomPresetConfig[] => [
4380
+ createPresetConfig(DateRangePreset.LAST_30_MINUTES),
4381
+ createPresetConfig(DateRangePreset.LAST_1_HOUR),
4382
+ createPresetConfig(DateRangePreset.LAST_6_HOURS),
4383
+ createPresetConfig(DateRangePreset.LAST_24_HOURS, undefined, false), // Hidden
4384
+ createPresetConfig(DateRangePreset.TODAY),
4385
+ createPresetConfig(DateRangePreset.YESTERDAY),
4386
+ createPresetConfig(DateRangePreset.LAST_7_DAYS),
4387
+ createPresetConfig(DateRangePreset.LAST_30_DAYS),
4388
+ createPresetConfig(DateRangePreset.THIS_MONTH),
4389
+ createPresetConfig(DateRangePreset.LAST_MONTH),
4390
+ ],
4391
+ }