@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.
- package/dist/components/Badge/Badge.d.ts +3 -0
- package/dist/components/Badge/Badge.types.d.ts +34 -0
- package/dist/components/Badge/badge.dark.tokens.d.ts +3 -0
- package/dist/components/Badge/badge.light.tokens.d.ts +3 -0
- package/dist/components/Badge/badge.tokens.d.ts +55 -0
- package/dist/components/Badge/badge.utils.d.ts +11 -0
- package/dist/components/Badge/index.d.ts +2 -0
- package/dist/components/ChartsV2/chartV2Options.d.ts +3 -0
- package/dist/components/ChatInput/ChatInput.d.ts +1 -1
- package/dist/components/CodeEditorV2/codeEditorV2.tokens.d.ts +2 -2
- package/dist/components/CodeEditorV2/utils.d.ts +9 -0
- package/dist/components/DataTable/DataTablePagination.d.ts +2 -1
- package/dist/components/DataTable/TableBody/types.d.ts +5 -1
- package/dist/components/DataTable/TableFooter/types.d.ts +1 -0
- package/dist/components/DataTable/TableHeader/FilterComponents.d.ts +7 -2
- package/dist/components/DataTable/TableHeader/handlers.d.ts +1 -1
- package/dist/components/DataTable/types.d.ts +5 -0
- package/dist/components/DataTable/utils.d.ts +8 -0
- package/dist/components/DateRangePicker/types.d.ts +6 -0
- package/dist/components/Directory/directory.tokens.d.ts +6 -0
- package/dist/components/DrawerV2/DrawerV2.d.ts +15 -0
- package/dist/components/InputsV2/ChatInputV2/AttachmentDropdown.d.ts +11 -0
- package/dist/components/InputsV2/ChatInputV2/ChatInputTagV2.d.ts +13 -0
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2.d.ts +19 -0
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2.dark.tokens.d.ts +4 -0
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2.light.tokens.d.ts +3 -0
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2.tokens.d.ts +125 -0
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2.types.d.ts +54 -0
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2AttachmentRow.d.ts +13 -0
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2Mobile.dark.tokens.d.ts +4 -0
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2Mobile.light.tokens.d.ts +3 -0
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2Mobile.tokens.d.ts +60 -0
- package/dist/components/InputsV2/ChatInputV2/MobileChatInputV2.d.ts +17 -0
- package/dist/components/InputsV2/ChatInputV2/index.d.ts +4 -0
- package/dist/components/InputsV2/ChatInputV2/utils.d.ts +43 -0
- package/dist/components/InputsV2/MultiValueInputV2/MultiValueInputV2.d.ts +1 -1
- package/dist/components/InputsV2/NumberInputV2/NumberInputV2.d.ts +32 -0
- package/dist/components/InputsV2/NumberInputV2/NumberInputV2.dark.tokens.d.ts +3 -0
- package/dist/components/InputsV2/NumberInputV2/NumberInputV2.light.tokens.d.ts +3 -0
- package/dist/components/InputsV2/NumberInputV2/NumberInputV2Stepper.d.ts +14 -0
- package/dist/components/InputsV2/NumberInputV2/NumberInputV2Unit.d.ts +12 -0
- package/dist/components/InputsV2/NumberInputV2/StepperArrow.d.ts +2 -0
- package/dist/components/InputsV2/NumberInputV2/index.d.ts +3 -0
- package/dist/components/InputsV2/NumberInputV2/numberInputV2.tokens.d.ts +135 -0
- package/dist/components/InputsV2/NumberInputV2/numberInputV2.types.d.ts +41 -0
- package/dist/components/InputsV2/NumberInputV2/utils.d.ts +52 -0
- package/dist/components/InputsV2/OTPInputV2/OTPInputV2.d.ts +14 -0
- package/dist/components/InputsV2/OTPInputV2/OTPInputV2.dark.tokens.d.ts +3 -0
- package/dist/components/InputsV2/OTPInputV2/OTPInputV2.light.tokens.d.ts +3 -0
- package/dist/components/InputsV2/OTPInputV2/OTPInputV2.tokens.d.ts +34 -0
- package/dist/components/InputsV2/OTPInputV2/OTPInputV2.types.d.ts +13 -0
- package/dist/components/InputsV2/OTPInputV2/index.d.ts +3 -0
- package/dist/components/InputsV2/OTPInputV2/otpInputV2Utils.d.ts +47 -0
- package/dist/components/InputsV2/SearchInputV2/SearchInputV2.d.ts +14 -0
- package/dist/components/InputsV2/SearchInputV2/SearchInputV2.dark.tokens.d.ts +3 -0
- package/dist/components/InputsV2/SearchInputV2/SearchInputV2.light.tokens.d.ts +3 -0
- package/dist/components/InputsV2/SearchInputV2/SearchInputV2.tokens.d.ts +92 -0
- package/dist/components/InputsV2/SearchInputV2/SearchInputV2.types.d.ts +13 -0
- package/dist/components/InputsV2/SearchInputV2/index.d.ts +3 -0
- package/dist/components/InputsV2/TextAreaV2/TextAreaV2.d.ts +23 -0
- package/dist/components/InputsV2/TextAreaV2/TextAreaV2.dark.tokens.d.ts +3 -0
- package/dist/components/InputsV2/TextAreaV2/TextAreaV2.light.tokens.d.ts +3 -0
- package/dist/components/InputsV2/TextAreaV2/TextAreaV2.tokens.d.ts +63 -0
- package/dist/components/InputsV2/TextAreaV2/TextAreaV2.types.d.ts +22 -0
- package/dist/components/InputsV2/TextAreaV2/index.d.ts +3 -0
- package/dist/components/InputsV2/TextAreaV2/utils.d.ts +47 -0
- package/dist/components/InputsV2/TextInputV2/TextInputV2.d.ts +3 -1
- package/dist/components/InputsV2/TextInputV2/TextInputV2.types.d.ts +24 -1
- package/dist/components/InputsV2/TextInputV2/utils.d.ts +5 -3
- package/dist/components/InputsV2/utils/FloatingLabelsV2/FloatingLabelsV2.d.ts +3 -1
- package/dist/components/InputsV2/utils/utils.d.ts +10 -0
- package/dist/components/Primitives/Block/Block.d.ts +1 -1
- package/dist/components/ProgressBarV2/utils.d.ts +40 -0
- package/dist/components/SidebarV2/SecondarySidebar.d.ts +9 -0
- package/dist/components/SidebarV2/SidebarV2.d.ts +3 -0
- package/dist/components/SidebarV2/SidebarV2Footer.d.ts +10 -0
- package/dist/components/SidebarV2/SidebarV2Header.d.ts +24 -0
- package/dist/components/SidebarV2/SidebarV2MobileNavigation/MobileNavigationItem.d.ts +4 -0
- package/dist/components/SidebarV2/SidebarV2MobileNavigation/MoreButton.d.ts +3 -0
- package/dist/components/SidebarV2/SidebarV2MobileNavigation/PrimaryActionButton.d.ts +3 -0
- package/dist/components/SidebarV2/SidebarV2MobileNavigation/hooks.d.ts +6 -0
- package/dist/components/SidebarV2/SidebarV2MobileNavigation/index.d.ts +3 -0
- package/dist/components/SidebarV2/SidebarV2MobileNavigation/mobile.dark.tokens.d.ts +3 -0
- package/dist/components/SidebarV2/SidebarV2MobileNavigation/mobile.light.tokens.d.ts +3 -0
- package/dist/components/SidebarV2/SidebarV2MobileNavigation/mobile.tokens.d.ts +87 -0
- package/dist/components/SidebarV2/SidebarV2MobileNavigation/types.d.ts +28 -0
- package/dist/components/SidebarV2/SidebarV2MobileNavigation/utils.d.ts +25 -0
- package/dist/components/SidebarV2/SidebarV2Panel.d.ts +34 -0
- package/dist/components/SidebarV2/index.d.ts +5 -0
- package/dist/components/SidebarV2/sidebarV2.dark.tokens.d.ts +3 -0
- package/dist/components/SidebarV2/sidebarV2.light.tokens.d.ts +3 -0
- package/dist/components/SidebarV2/sidebarV2.tokens.d.ts +106 -0
- package/dist/components/SidebarV2/sidebarV2.types.d.ts +1 -0
- package/dist/components/SidebarV2/types.d.ts +51 -0
- package/dist/components/SidebarV2/utils.d.ts +8 -0
- package/dist/components/StepperV2/Stepper/StepStatusCircle.d.ts +11 -0
- package/dist/components/StepperV2/Stepper/StepperComponent.d.ts +9 -0
- package/dist/components/StepperV2/Stepper/Steps.d.ts +7 -0
- package/dist/components/StepperV2/Stepper/StepsHorizontalBody.d.ts +15 -0
- package/dist/components/StepperV2/Stepper/StepsSubstepList.d.ts +18 -0
- package/dist/components/StepperV2/Stepper/StepsVerticalSubstepRails.d.ts +12 -0
- package/dist/components/StepperV2/Stepper/VerticalLineV2.d.ts +5 -0
- package/dist/components/StepperV2/Stepper/stepsHelpers.d.ts +10 -0
- package/dist/components/StepperV2/StepperV2.d.ts +9 -0
- package/dist/components/StepperV2/index.d.ts +4 -0
- package/dist/components/StepperV2/stepperV2.dark.tokens.d.ts +3 -0
- package/dist/components/StepperV2/stepperV2.light.tokens.d.ts +3 -0
- package/dist/components/StepperV2/stepperV2.tokens.d.ts +99 -0
- package/dist/components/StepperV2/stepperV2.types.d.ts +61 -0
- package/dist/components/StepperV2/utils.d.ts +4 -0
- package/dist/components/Tabs/tabs.token.d.ts +1 -1
- package/dist/components/Tabs/types.d.ts +1 -0
- package/dist/components/TabsV2/tabsV2.types.d.ts +1 -0
- package/dist/components/TagV2/TagV2.d.ts +1 -1
- package/dist/components/TopbarV2/TopbarV2.d.ts +3 -0
- package/dist/components/TopbarV2/index.d.ts +3 -0
- package/dist/components/TopbarV2/topbarV2.dark.tokens.d.ts +3 -0
- package/dist/components/TopbarV2/topbarV2.light.tokens.d.ts +3 -0
- package/dist/components/TopbarV2/topbarV2.tokens.d.ts +83 -0
- package/dist/components/TopbarV2/types.d.ts +50 -0
- package/dist/components/common/TruncatedTextWithTooltipV2/TruncatedTextWithTooltipV2.d.ts +2 -0
- package/dist/components/common/TruncatedTextWithTooltipV2/index.d.ts +2 -0
- package/dist/components/common/TruncatedTextWithTooltipV2/types.d.ts +16 -0
- package/dist/components/common/TruncatedTextWithTooltipV2/utils.d.ts +1 -0
- package/dist/components/common/index.d.ts +1 -0
- package/dist/context/ThemeContext.d.ts +22 -0
- package/dist/context/useComponentToken.d.ts +12 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useResizeObserver.d.ts +1 -1
- package/dist/hooks/useSectionScroll.d.ts +24 -0
- package/dist/hooks/useTruncationDetection.d.ts +1 -1
- package/dist/main.d.ts +107 -33
- package/dist/main.js +118076 -108882
- package/dist/node-CRWdZOVN.js +14736 -0
- package/dist/node.d.ts +35 -0
- package/dist/node.js +31 -0
- package/dist/style.css +1 -0
- package/dist/token-engine-server.d.ts +9 -0
- package/dist/token-engine.d.ts +10 -0
- package/dist/tokens/unit.tokens.d.ts +3 -0
- package/dist/tokens-server.d.ts +2 -0
- package/dist/tokens-server.js +779 -0
- package/dist/tokens.d.ts +2 -0
- package/dist/tokens.js +233 -0
- package/lib/breakpoints/breakPoints.ts +8 -0
- package/lib/components/Accordion/Accordion.tsx +109 -0
- package/lib/components/Accordion/AccordionItem.tsx +612 -0
- package/lib/components/Accordion/accessibility/AccordionAccessibility.tsx +547 -0
- package/lib/components/Accordion/accessibility/AccordionAccessibilityReport.ts +373 -0
- package/lib/components/Accordion/accessibility/index.ts +2 -0
- package/lib/components/Accordion/accordion.tokens.ts +299 -0
- package/lib/components/Accordion/index.ts +4 -0
- package/lib/components/Accordion/types.ts +43 -0
- package/lib/components/AccordionV2/AccordionV2.tsx +129 -0
- package/lib/components/AccordionV2/AccordionV2Chevron.tsx +67 -0
- package/lib/components/AccordionV2/AccordionV2Item.tsx +288 -0
- package/lib/components/AccordionV2/AccordionV2TriggerContent.tsx +227 -0
- package/lib/components/AccordionV2/accordionV2.animations.ts +26 -0
- package/lib/components/AccordionV2/accordionV2.dark.tokens.ts +208 -0
- package/lib/components/AccordionV2/accordionV2.light.tokens.ts +208 -0
- package/lib/components/AccordionV2/accordionV2.tokens.ts +89 -0
- package/lib/components/AccordionV2/accordionV2.types.ts +39 -0
- package/lib/components/AccordionV2/index.ts +4 -0
- package/lib/components/Alert/Alert.tsx +383 -0
- package/lib/components/Alert/accessibility/AlertAccessibility.tsx +425 -0
- package/lib/components/Alert/accessibility/AlertAccessibilityReport.ts +549 -0
- package/lib/components/Alert/accessibility/index.ts +2 -0
- package/lib/components/Alert/alert.tokens.ts +385 -0
- package/lib/components/Alert/index.ts +3 -0
- package/lib/components/Alert/types.ts +41 -0
- package/lib/components/AlertV2/AlertV2.tsx +366 -0
- package/lib/components/AlertV2/alertV2.dark.tokens.ts +335 -0
- package/lib/components/AlertV2/alertV2.light.tokens.ts +335 -0
- package/lib/components/AlertV2/alertV2.tokens.ts +94 -0
- package/lib/components/AlertV2/alertV2.types.ts +63 -0
- package/lib/components/AlertV2/index.ts +3 -0
- package/lib/components/Avatar/Avatar.tsx +385 -0
- package/lib/components/Avatar/StyledAvatar.tsx +8 -0
- package/lib/components/Avatar/accessibility/AvatarAccessibility.tsx +784 -0
- package/lib/components/Avatar/accessibility/AvatarAccessibilityReport.ts +512 -0
- package/lib/components/Avatar/accessibility/index.ts +2 -0
- package/lib/components/Avatar/avatar.tokens.ts +450 -0
- package/lib/components/Avatar/avatarUtils.ts +102 -0
- package/lib/components/Avatar/index.ts +3 -0
- package/lib/components/Avatar/types.ts +46 -0
- package/lib/components/AvatarGroup/AvatarGroup.tsx +178 -0
- package/lib/components/AvatarGroup/StyledAvatarGroup.tsx +122 -0
- package/lib/components/AvatarGroup/accessibility/AvatarGroupAccessibility.tsx +517 -0
- package/lib/components/AvatarGroup/accessibility/AvatarGroupAccessibilityReport.ts +416 -0
- package/lib/components/AvatarGroup/accessibility/index.ts +2 -0
- package/lib/components/AvatarGroup/avatarGroup.tokens.ts +233 -0
- package/lib/components/AvatarGroup/avatarGroupUtils.tsx +68 -0
- package/lib/components/AvatarGroup/index.ts +3 -0
- package/lib/components/AvatarGroup/types.ts +39 -0
- package/lib/components/AvatarV2/AvatarV2.tsx +361 -0
- package/lib/components/AvatarV2/avatarV2.dark.tokens.ts +333 -0
- package/lib/components/AvatarV2/avatarV2.light.tokens.ts +333 -0
- package/lib/components/AvatarV2/avatarV2.tokens.ts +93 -0
- package/lib/components/AvatarV2/avatarV2.types.ts +96 -0
- package/lib/components/AvatarV2/avatarV2.utils.ts +223 -0
- package/lib/components/AvatarV2/index.ts +15 -0
- package/lib/components/Badge/Badge.tsx +169 -0
- package/lib/components/Badge/Badge.types.ts +47 -0
- package/lib/components/Badge/badge.dark.tokens.ts +149 -0
- package/lib/components/Badge/badge.light.tokens.ts +149 -0
- package/lib/components/Badge/badge.tokens.ts +71 -0
- package/lib/components/Badge/badge.utils.ts +113 -0
- package/lib/components/Badge/index.ts +2 -0
- package/lib/components/Breadcrumb/Breadcrumb.tsx +229 -0
- package/lib/components/Breadcrumb/BreadcrumbSkeleton.tsx +37 -0
- package/lib/components/Breadcrumb/accessibility/BreadcrumbAccessibility.tsx +629 -0
- package/lib/components/Breadcrumb/accessibility/BreadcrumbAccessibilityReport.ts +552 -0
- package/lib/components/Breadcrumb/accessibility/index.ts +6 -0
- package/lib/components/Breadcrumb/breadcrumb.tokens.ts +104 -0
- package/lib/components/Breadcrumb/index.ts +3 -0
- package/lib/components/Breadcrumb/types.ts +20 -0
- package/lib/components/BreadcrumbV2/BreadcrumbV2.tsx +93 -0
- package/lib/components/BreadcrumbV2/BreadcrumbV2Icon.tsx +18 -0
- package/lib/components/BreadcrumbV2/BreadcrumbV2Item.tsx +67 -0
- package/lib/components/BreadcrumbV2/BreadcrumbV2List.tsx +102 -0
- package/lib/components/BreadcrumbV2/BreadcrumbV2OverflowMenu.tsx +106 -0
- package/lib/components/BreadcrumbV2/BreadcrumbV2Page.tsx +27 -0
- package/lib/components/BreadcrumbV2/BreadcrumbV2Separator.tsx +24 -0
- package/lib/components/BreadcrumbV2/breadcrumbV2.dark.tokens.ts +61 -0
- package/lib/components/BreadcrumbV2/breadcrumbV2.light.tokens.ts +61 -0
- package/lib/components/BreadcrumbV2/breadcrumbV2.tokens.ts +47 -0
- package/lib/components/BreadcrumbV2/breadcrumbV2.types.ts +53 -0
- package/lib/components/BreadcrumbV2/index.ts +3 -0
- package/lib/components/BreadcrumbV2/utils.ts +196 -0
- package/lib/components/Button/ACCESSIBILITY_REPORT.md +740 -0
- package/lib/components/Button/Button.tsx +109 -0
- package/lib/components/Button/ButtonBase.tsx +329 -0
- package/lib/components/Button/accessibility/ButtonAccessibility.tsx +1043 -0
- package/lib/components/Button/accessibility/ButtonAccessibilityReport.ts +578 -0
- package/lib/components/Button/accessibility/index.ts +7 -0
- package/lib/components/Button/button.tokens.ts +1846 -0
- package/lib/components/Button/index.ts +3 -0
- package/lib/components/Button/types.ts +48 -0
- package/lib/components/ButtonGroup/ButtonGroup.tsx +53 -0
- package/lib/components/ButtonGroup/accessibility/ButtonGroupAccessibility.tsx +483 -0
- package/lib/components/ButtonGroup/accessibility/ButtonGroupAccessibilityReport.ts +159 -0
- package/lib/components/ButtonGroup/accessibility/index.ts +7 -0
- package/lib/components/ButtonGroup/index.ts +2 -0
- package/lib/components/ButtonGroup/types.ts +7 -0
- package/lib/components/ButtonV2/ButtonGroupV2/ButtonGroupV2.tsx +32 -0
- package/lib/components/ButtonV2/ButtonGroupV2/buttonGroupV2.types.ts +15 -0
- package/lib/components/ButtonV2/ButtonGroupV2/index.ts +6 -0
- package/lib/components/ButtonV2/ButtonGroupV2/utils.ts +19 -0
- package/lib/components/ButtonV2/ButtonV2.tsx +335 -0
- package/lib/components/ButtonV2/IconButton.tsx +38 -0
- package/lib/components/ButtonV2/LinkButton.tsx +179 -0
- package/lib/components/ButtonV2/VisuallyHidden.tsx +13 -0
- package/lib/components/ButtonV2/buttonV2.dark.tokens.ts +1407 -0
- package/lib/components/ButtonV2/buttonV2.light.tokens.ts +1493 -0
- package/lib/components/ButtonV2/buttonV2.tokens.ts +87 -0
- package/lib/components/ButtonV2/buttonV2.types.ts +86 -0
- package/lib/components/ButtonV2/index.ts +8 -0
- package/lib/components/ButtonV2/utils.ts +348 -0
- package/lib/components/Card/Card.tsx +141 -0
- package/lib/components/Card/CardComponents.tsx +627 -0
- package/lib/components/Card/CardSkeleton.tsx +27 -0
- package/lib/components/Card/accessibility/CardAccessibility.tsx +912 -0
- package/lib/components/Card/accessibility/CardAccessibilityReport.ts +351 -0
- package/lib/components/Card/card.tokens.ts +360 -0
- package/lib/components/Card/index.ts +3 -0
- package/lib/components/Card/types.ts +74 -0
- package/lib/components/Card/utils.ts +217 -0
- package/lib/components/Charts/BlendChart.tsx +167 -0
- package/lib/components/Charts/BlendChart.types.ts +39 -0
- package/lib/components/Charts/BlendChartContainer.tsx +23 -0
- package/lib/components/Charts/BlendChartHeader.tsx +26 -0
- package/lib/components/Charts/ChartContainer.tsx +30 -0
- package/lib/components/Charts/ChartHeader.tsx +190 -0
- package/lib/components/Charts/ChartLegend.tsx +756 -0
- package/lib/components/Charts/ChartUtils.tsx +530 -0
- package/lib/components/Charts/Charts.tsx +1002 -0
- package/lib/components/Charts/ChartsSkeleton.tsx +31 -0
- package/lib/components/Charts/CoreChart.tsx +73 -0
- package/lib/components/Charts/CustomTooltip.tsx +876 -0
- package/lib/components/Charts/DateTimeFormatter.ts +769 -0
- package/lib/components/Charts/SankeyChartWrapper.tsx +345 -0
- package/lib/components/Charts/SankeyLink.tsx +95 -0
- package/lib/components/Charts/SankeyNode.tsx +113 -0
- package/lib/components/Charts/accessibility/ChartsAccessibility.tsx +827 -0
- package/lib/components/Charts/accessibility/ChartsAccessibilityReport.ts +370 -0
- package/lib/components/Charts/chart.tokens.ts +144 -0
- package/lib/components/Charts/index.ts +15 -0
- package/lib/components/Charts/renderChart.tsx +1385 -0
- package/lib/components/Charts/types.tsx +309 -0
- package/lib/components/Charts/utils.tsx +60 -0
- package/lib/components/ChartsV2/ChartContainerV2.tsx +30 -0
- package/lib/components/ChartsV2/ChartHeaderV2.tsx +27 -0
- package/lib/components/ChartsV2/ChartV2.tsx +94 -0
- package/lib/components/ChartsV2/ChartV2Fullscreen.tsx +157 -0
- package/lib/components/ChartsV2/ChartV2Legend.tsx +174 -0
- package/lib/components/ChartsV2/ChartV2NoData.tsx +48 -0
- package/lib/components/ChartsV2/ChartV2Skeleton.tsx +37 -0
- package/lib/components/ChartsV2/chartV2.dark.tokens.ts +200 -0
- package/lib/components/ChartsV2/chartV2.light.tokens.ts +197 -0
- package/lib/components/ChartsV2/chartV2.sankey.ts +13 -0
- package/lib/components/ChartsV2/chartV2.tokens.ts +117 -0
- package/lib/components/ChartsV2/chartV2.types.ts +84 -0
- package/lib/components/ChartsV2/chartV2Options.ts +119 -0
- package/lib/components/ChartsV2/index.ts +9 -0
- package/lib/components/ChartsV2/useChartLegend.ts +25 -0
- package/lib/components/ChartsV2/useChartLegendHover.ts +116 -0
- package/lib/components/ChartsV2/useChartRefs.ts +29 -0
- package/lib/components/ChartsV2/utils.ts +92 -0
- package/lib/components/ChatInput/AttachmentFile.tsx +266 -0
- package/lib/components/ChatInput/ChatInput.tsx +577 -0
- package/lib/components/ChatInput/MobileChatInput.tsx +240 -0
- package/lib/components/ChatInput/accessibility/ChatInputAccessibility.tsx +597 -0
- package/lib/components/ChatInput/accessibility/ChatInputAccessibilityReport.ts +314 -0
- package/lib/components/ChatInput/accessibility/index.ts +7 -0
- package/lib/components/ChatInput/chatInput.tokens.ts +575 -0
- package/lib/components/ChatInput/index.tsx +3 -0
- package/lib/components/ChatInput/types.ts +121 -0
- package/lib/components/ChatInput/utils.ts +151 -0
- package/lib/components/Checkbox/Checkbox.tsx +308 -0
- package/lib/components/Checkbox/StyledCheckbox.tsx +100 -0
- package/lib/components/Checkbox/accessibility/CheckboxAccessibility.tsx +1080 -0
- package/lib/components/Checkbox/accessibility/CheckboxAccessibilityReport.ts +580 -0
- package/lib/components/Checkbox/accessibility/index.ts +6 -0
- package/lib/components/Checkbox/checkbox.animations.ts +85 -0
- package/lib/components/Checkbox/checkbox.token.ts +366 -0
- package/lib/components/Checkbox/checkboxUtils.ts +144 -0
- package/lib/components/Checkbox/index.ts +2 -0
- package/lib/components/Checkbox/types.ts +39 -0
- package/lib/components/CodeBlock/CodeBlock.tsx +358 -0
- package/lib/components/CodeBlock/CodeBlockDiffView/CodeBlockDiffView.tsx +488 -0
- package/lib/components/CodeBlock/CodeBlockDiffView/types.ts +18 -0
- package/lib/components/CodeBlock/CodeBlockDiffView/utils.ts +26 -0
- package/lib/components/CodeBlock/CodeBlockLineParts.tsx +300 -0
- package/lib/components/CodeBlock/accessibility/CodeBlockAccessibility.tsx +697 -0
- package/lib/components/CodeBlock/accessibility/CodeBlockAccessibilityReport.ts +350 -0
- package/lib/components/CodeBlock/codeBlock.token.ts +322 -0
- package/lib/components/CodeBlock/index.ts +2 -0
- package/lib/components/CodeBlock/types.ts +72 -0
- package/lib/components/CodeBlock/utils.ts +712 -0
- package/lib/components/CodeEditor/CodeEditor.tsx +101 -0
- package/lib/components/CodeEditor/CodeEditorHeader.tsx +125 -0
- package/lib/components/CodeEditor/MonacoEditorWrapper.tsx +621 -0
- package/lib/components/CodeEditor/index.ts +2 -0
- package/lib/components/CodeEditor/monaco-editor.css +1 -0
- package/lib/components/CodeEditor/types.ts +49 -0
- package/lib/components/CodeEditor/utils.ts +27 -0
- package/lib/components/CodeEditorV2/CodeEditorV2.tsx +156 -0
- package/lib/components/CodeEditorV2/CodeEditorV2Header.tsx +123 -0
- package/lib/components/CodeEditorV2/MonacoEditor/MonacoEditorWrapper.tsx +395 -0
- package/lib/components/CodeEditorV2/MonacoEditor/monaco-editor.css +1 -0
- package/lib/components/CodeEditorV2/MonacoEditor/monacoTheme.ts +133 -0
- package/lib/components/CodeEditorV2/codeEditorV2.dark.tokens.ts +168 -0
- package/lib/components/CodeEditorV2/codeEditorV2.light.token.ts +170 -0
- package/lib/components/CodeEditorV2/codeEditorV2.tokens.ts +99 -0
- package/lib/components/CodeEditorV2/codeEditorV2.types.ts +132 -0
- package/lib/components/CodeEditorV2/index.ts +3 -0
- package/lib/components/CodeEditorV2/utils.ts +504 -0
- package/lib/components/DataTable/ColumnFilter/index.tsx +284 -0
- package/lib/components/DataTable/ColumnManager.tsx +322 -0
- package/lib/components/DataTable/DataTable.tsx +2052 -0
- package/lib/components/DataTable/DataTableHeader/index.tsx +775 -0
- package/lib/components/DataTable/DataTableHeader/types.ts +31 -0
- package/lib/components/DataTable/DataTablePagination.tsx +602 -0
- package/lib/components/DataTable/MobileColumnDrawer/index.tsx +407 -0
- package/lib/components/DataTable/MobileColumnManagerDrawer/index.tsx +164 -0
- package/lib/components/DataTable/TableBody/BulkActionBar.tsx +309 -0
- package/lib/components/DataTable/TableBody/index.tsx +1619 -0
- package/lib/components/DataTable/TableBody/types.ts +57 -0
- package/lib/components/DataTable/TableCell/index.tsx +590 -0
- package/lib/components/DataTable/TableCell/types.ts +24 -0
- package/lib/components/DataTable/TableFooter/index.tsx +63 -0
- package/lib/components/DataTable/TableFooter/types.ts +15 -0
- package/lib/components/DataTable/TableHeader/DraggableColumnHeader.tsx +71 -0
- package/lib/components/DataTable/TableHeader/FilterComponents.tsx +1549 -0
- package/lib/components/DataTable/TableHeader/MobileFilterDrawer.tsx +772 -0
- package/lib/components/DataTable/TableHeader/handlers.ts +232 -0
- package/lib/components/DataTable/TableHeader/index.tsx +2143 -0
- package/lib/components/DataTable/TableHeader/types.ts +84 -0
- package/lib/components/DataTable/TableHeader/utils.ts +302 -0
- package/lib/components/DataTable/accessibility/DataTableAccessibility.tsx +579 -0
- package/lib/components/DataTable/accessibility/DataTableAccessibilityReport.ts +250 -0
- package/lib/components/DataTable/accessibility/index.ts +7 -0
- package/lib/components/DataTable/columnTypes.ts +359 -0
- package/lib/components/DataTable/dataTable.tokens.ts +802 -0
- package/lib/components/DataTable/hooks/index.ts +2 -0
- package/lib/components/DataTable/hooks/useMobileDataTable.ts +35 -0
- package/lib/components/DataTable/index.ts +23 -0
- package/lib/components/DataTable/types.ts +447 -0
- package/lib/components/DataTable/utils.ts +1358 -0
- package/lib/components/DateRangePicker/CalendarGrid.tsx +1012 -0
- package/lib/components/DateRangePicker/DateRangePicker.tsx +1278 -0
- package/lib/components/DateRangePicker/MobileDrawerPresets.tsx +174 -0
- package/lib/components/DateRangePicker/QuickRangeSelector.tsx +241 -0
- package/lib/components/DateRangePicker/TimeSelector.tsx +446 -0
- package/lib/components/DateRangePicker/accessibility/DateRangePickerAccessibility.tsx +488 -0
- package/lib/components/DateRangePicker/accessibility/DateRangePickerAccessibilityReport.ts +547 -0
- package/lib/components/DateRangePicker/accessibility/index.ts +4 -0
- package/lib/components/DateRangePicker/components/ActionButtons.tsx +76 -0
- package/lib/components/DateRangePicker/components/DatePickerComponent.tsx +291 -0
- package/lib/components/DateRangePicker/components/PresetItem.tsx +139 -0
- package/lib/components/DateRangePicker/components/ScrollablePicker.tsx +618 -0
- package/lib/components/DateRangePicker/components/mobile.tokens.ts +167 -0
- package/lib/components/DateRangePicker/constants.ts +42 -0
- package/lib/components/DateRangePicker/dateRangePicker.tokens.ts +492 -0
- package/lib/components/DateRangePicker/index.ts +4 -0
- package/lib/components/DateRangePicker/types.ts +420 -0
- package/lib/components/DateRangePicker/utils.ts +4391 -0
- package/lib/components/Directory/Directory.tsx +75 -0
- package/lib/components/Directory/NavItem.tsx +517 -0
- package/lib/components/Directory/Section.tsx +270 -0
- package/lib/components/Directory/directory.tokens.ts +283 -0
- package/lib/components/Directory/index.ts +3 -0
- package/lib/components/Directory/types.ts +52 -0
- package/lib/components/Directory/utils.ts +77 -0
- package/lib/components/Drawer/Drawer.tsx +31 -0
- package/lib/components/Drawer/accessibility/DrawerAccessibility.tsx +749 -0
- package/lib/components/Drawer/accessibility/DrawerAccessibilityReport.ts +394 -0
- package/lib/components/Drawer/components/Drawer.css +21 -0
- package/lib/components/Drawer/components/DrawerBase.tsx +738 -0
- package/lib/components/Drawer/components/NestedSelectDrawer.tsx +843 -0
- package/lib/components/Drawer/components/SelectDrawer.tsx +701 -0
- package/lib/components/Drawer/components/StatusDrawer.tsx +127 -0
- package/lib/components/Drawer/drawer.tokens.ts +141 -0
- package/lib/components/Drawer/index.ts +20 -0
- package/lib/components/Drawer/types.ts +424 -0
- package/lib/components/DrawerV2/DrawerV2.tsx +131 -0
- package/lib/components/DrawerV2/index.ts +16 -0
- package/lib/components/DrawerV2/types.ts +58 -0
- package/lib/components/GradientBlur/GradientBlur.css +107 -0
- package/lib/components/GradientBlur/GradientBlur.tsx +16 -0
- package/lib/components/Inputs/AutofillStyles/AutofillStyles.ts +23 -0
- package/lib/components/Inputs/DropdownInput/DropdownInput.tsx +419 -0
- package/lib/components/Inputs/DropdownInput/accessibility/DropdownInputAccessibility.tsx +674 -0
- package/lib/components/Inputs/DropdownInput/accessibility/DropdownInputAccessibilityReport.ts +444 -0
- package/lib/components/Inputs/DropdownInput/dropdownInput.tokens.ts +304 -0
- package/lib/components/Inputs/DropdownInput/index.ts +3 -0
- package/lib/components/Inputs/DropdownInput/types.ts +51 -0
- package/lib/components/Inputs/MultiValueInput/MultiValueInput.tsx +324 -0
- package/lib/components/Inputs/MultiValueInput/accessibility/MultiValueInputAccessibility.tsx +501 -0
- package/lib/components/Inputs/MultiValueInput/accessibility/MultiValueInputAccessibilityReport.ts +479 -0
- package/lib/components/Inputs/MultiValueInput/index.ts +3 -0
- package/lib/components/Inputs/MultiValueInput/multiValueInput.tokens.ts +302 -0
- package/lib/components/Inputs/MultiValueInput/types.ts +38 -0
- package/lib/components/Inputs/NumberInput/NumberInput.tsx +623 -0
- package/lib/components/Inputs/NumberInput/accessibility/NumberInputAccessibility.tsx +581 -0
- package/lib/components/Inputs/NumberInput/accessibility/NumberInputAccessibilityReport.ts +333 -0
- package/lib/components/Inputs/NumberInput/index.ts +3 -0
- package/lib/components/Inputs/NumberInput/numberInput.tokens.ts +382 -0
- package/lib/components/Inputs/NumberInput/types.ts +23 -0
- package/lib/components/Inputs/NumberInput/utils.ts +329 -0
- package/lib/components/Inputs/OTPInput/OTPInput.tsx +355 -0
- package/lib/components/Inputs/OTPInput/accessibility/OTPInputAccessibility.tsx +456 -0
- package/lib/components/Inputs/OTPInput/accessibility/OTPInputAccessibilityReport.ts +466 -0
- package/lib/components/Inputs/OTPInput/index.ts +3 -0
- package/lib/components/Inputs/OTPInput/otpInput.tokens.ts +255 -0
- package/lib/components/Inputs/OTPInput/types.ts +17 -0
- package/lib/components/Inputs/SearchInput/SearchInput.tsx +299 -0
- package/lib/components/Inputs/SearchInput/accessibility/SearchInputAccessibility.tsx +644 -0
- package/lib/components/Inputs/SearchInput/accessibility/SearchInputAccessibilityReport.ts +428 -0
- package/lib/components/Inputs/SearchInput/index.ts +3 -0
- package/lib/components/Inputs/SearchInput/searchInput.tokens.ts +238 -0
- package/lib/components/Inputs/SearchInput/types.ts +13 -0
- package/lib/components/Inputs/TextArea/TextArea.tsx +227 -0
- package/lib/components/Inputs/TextArea/accessibility/TextAreaAccessibility.tsx +466 -0
- package/lib/components/Inputs/TextArea/accessibility/TextAreaAccessibilityReport.ts +476 -0
- package/lib/components/Inputs/TextArea/index.ts +2 -0
- package/lib/components/Inputs/TextArea/textarea.token.ts +264 -0
- package/lib/components/Inputs/TextArea/types.ts +26 -0
- package/lib/components/Inputs/TextInput/TextInput.tsx +448 -0
- package/lib/components/Inputs/TextInput/accessibility/TextInputAccessibility.tsx +598 -0
- package/lib/components/Inputs/TextInput/accessibility/TextInputAccessibilityReport.ts +576 -0
- package/lib/components/Inputs/TextInput/index.ts +3 -0
- package/lib/components/Inputs/TextInput/textInput.tokens.ts +305 -0
- package/lib/components/Inputs/TextInput/types.ts +41 -0
- package/lib/components/Inputs/TextInput/utils.ts +43 -0
- package/lib/components/Inputs/UnitInput/UnitInput.tsx +379 -0
- package/lib/components/Inputs/UnitInput/accessibility/UnitInputAccessibility.tsx +587 -0
- package/lib/components/Inputs/UnitInput/accessibility/UnitInputAccessibilityReport.ts +322 -0
- package/lib/components/Inputs/UnitInput/index.ts +3 -0
- package/lib/components/Inputs/UnitInput/types.ts +33 -0
- package/lib/components/Inputs/UnitInput/unitInput.tokens.ts +380 -0
- package/lib/components/Inputs/index.ts +8 -0
- package/lib/components/Inputs/utils/FloatingLabels/FloatingLabels.tsx +47 -0
- package/lib/components/Inputs/utils/InputFooter/InputFooter.tsx +92 -0
- package/lib/components/Inputs/utils/InputLabels/InputLabels.tsx +133 -0
- package/lib/components/InputsV2/ChatInputV2/AttachmentDropdown.tsx +64 -0
- package/lib/components/InputsV2/ChatInputV2/ChatInputTagV2.tsx +93 -0
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2.dark.tokens.ts +339 -0
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2.light.tokens.ts +317 -0
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2.tokens.ts +133 -0
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2.tsx +466 -0
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2.types.ts +61 -0
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2AttachmentRow.tsx +260 -0
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2Mobile.dark.tokens.ts +87 -0
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2Mobile.light.tokens.ts +79 -0
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2Mobile.tokens.ts +63 -0
- package/lib/components/InputsV2/ChatInputV2/MobileChatInputV2.tsx +346 -0
- package/lib/components/InputsV2/ChatInputV2/index.ts +4 -0
- package/lib/components/InputsV2/ChatInputV2/utils.ts +263 -0
- package/lib/components/InputsV2/MultiValueInputV2/MultiValueInputV2.dark.tokens.ts +409 -0
- package/lib/components/InputsV2/MultiValueInputV2/MultiValueInputV2.light.tokens.ts +409 -0
- package/lib/components/InputsV2/MultiValueInputV2/MultiValueInputV2.tokens.ts +82 -0
- package/lib/components/InputsV2/MultiValueInputV2/MultiValueInputV2.tsx +382 -0
- package/lib/components/InputsV2/MultiValueInputV2/MultiValueV2.types.ts +30 -0
- package/lib/components/InputsV2/MultiValueInputV2/index.ts +3 -0
- package/lib/components/InputsV2/NumberInputV2/NumberInputV2.dark.tokens.ts +404 -0
- package/lib/components/InputsV2/NumberInputV2/NumberInputV2.light.tokens.ts +532 -0
- package/lib/components/InputsV2/NumberInputV2/NumberInputV2.tsx +698 -0
- package/lib/components/InputsV2/NumberInputV2/NumberInputV2Stepper.tsx +135 -0
- package/lib/components/InputsV2/NumberInputV2/NumberInputV2Unit.tsx +75 -0
- package/lib/components/InputsV2/NumberInputV2/StepperArrow.tsx +25 -0
- package/lib/components/InputsV2/NumberInputV2/index.ts +3 -0
- package/lib/components/InputsV2/NumberInputV2/numberInputV2.tokens.ts +133 -0
- package/lib/components/InputsV2/NumberInputV2/numberInputV2.types.ts +47 -0
- package/lib/components/InputsV2/NumberInputV2/utils.ts +411 -0
- package/lib/components/InputsV2/OTPInputV2/OTPInputV2.dark.tokens.ts +299 -0
- package/lib/components/InputsV2/OTPInputV2/OTPInputV2.light.tokens.ts +301 -0
- package/lib/components/InputsV2/OTPInputV2/OTPInputV2.tokens.ts +47 -0
- package/lib/components/InputsV2/OTPInputV2/OTPInputV2.tsx +327 -0
- package/lib/components/InputsV2/OTPInputV2/OTPInputV2.types.ts +16 -0
- package/lib/components/InputsV2/OTPInputV2/index.ts +3 -0
- package/lib/components/InputsV2/OTPInputV2/otpInputV2Utils.ts +177 -0
- package/lib/components/InputsV2/SearchInputV2/SearchInputV2.dark.tokens.ts +257 -0
- package/lib/components/InputsV2/SearchInputV2/SearchInputV2.light.tokens.ts +257 -0
- package/lib/components/InputsV2/SearchInputV2/SearchInputV2.tokens.ts +106 -0
- package/lib/components/InputsV2/SearchInputV2/SearchInputV2.tsx +282 -0
- package/lib/components/InputsV2/SearchInputV2/SearchInputV2.types.ts +16 -0
- package/lib/components/InputsV2/SearchInputV2/index.ts +3 -0
- package/lib/components/InputsV2/SearchInputV2/utils.ts +164 -0
- package/lib/components/InputsV2/TextAreaV2/TextAreaV2.dark.tokens.ts +398 -0
- package/lib/components/InputsV2/TextAreaV2/TextAreaV2.light.tokens.ts +397 -0
- package/lib/components/InputsV2/TextAreaV2/TextAreaV2.tokens.ts +76 -0
- package/lib/components/InputsV2/TextAreaV2/TextAreaV2.tsx +251 -0
- package/lib/components/InputsV2/TextAreaV2/TextAreaV2.types.ts +26 -0
- package/lib/components/InputsV2/TextAreaV2/index.tsx +3 -0
- package/lib/components/InputsV2/TextAreaV2/utils.ts +140 -0
- package/lib/components/InputsV2/TextInputV2/TextInputV2.dark.tokens.ts +429 -0
- package/lib/components/InputsV2/TextInputV2/TextInputV2.light.tokens.ts +423 -0
- package/lib/components/InputsV2/TextInputV2/TextInputV2.tokens.ts +149 -0
- package/lib/components/InputsV2/TextInputV2/TextInputV2.tsx +433 -0
- package/lib/components/InputsV2/TextInputV2/TextInputV2.types.ts +56 -0
- package/lib/components/InputsV2/TextInputV2/index.ts +3 -0
- package/lib/components/InputsV2/TextInputV2/utils.ts +81 -0
- package/lib/components/InputsV2/inputV2.tokens.ts +93 -0
- package/lib/components/InputsV2/inputV2.types.ts +19 -0
- package/lib/components/InputsV2/utils/FloatingLabelsV2/FloatingLabelsV2.tsx +96 -0
- package/lib/components/InputsV2/utils/InputFooter/InputFooterV2.tsx +70 -0
- package/lib/components/InputsV2/utils/InputLabels/InputLabelsV2.tsx +95 -0
- package/lib/components/InputsV2/utils/InputSlots/InputSlots.tsx +43 -0
- package/lib/components/InputsV2/utils/utils.ts +38 -0
- package/lib/components/KeyValuePair/KeyValuePair.tokens.ts +111 -0
- package/lib/components/KeyValuePair/KeyValuePair.tsx +248 -0
- package/lib/components/KeyValuePair/accessibility/KeyValuePairAccessibility.tsx +514 -0
- package/lib/components/KeyValuePair/accessibility/KeyValuePairAccessibilityReport.ts +317 -0
- package/lib/components/KeyValuePair/accessibility/index.ts +2 -0
- package/lib/components/KeyValuePair/index.ts +3 -0
- package/lib/components/KeyValuePair/types.ts +45 -0
- package/lib/components/KeyValuePair/utils.ts +113 -0
- package/lib/components/KeyValuePairV2/KeyValuePairLayout.tsx +89 -0
- package/lib/components/KeyValuePairV2/KeyValuePairV2.tsx +132 -0
- package/lib/components/KeyValuePairV2/ResponsiveText.tsx +117 -0
- package/lib/components/KeyValuePairV2/index.ts +3 -0
- package/lib/components/KeyValuePairV2/keyValuePairV2.dark.tokens.ts +59 -0
- package/lib/components/KeyValuePairV2/keyValuePairV2.light.tokens.ts +59 -0
- package/lib/components/KeyValuePairV2/keyValuePairV2.tokens.ts +42 -0
- package/lib/components/KeyValuePairV2/keyValuePairV2.types.ts +35 -0
- package/lib/components/KeyValuePairV2/responsiveTextStyles.ts +65 -0
- package/lib/components/KeyValuePairV2/utils.ts +51 -0
- package/lib/components/Menu/Menu.tsx +502 -0
- package/lib/components/Menu/MenuGroupLabel.tsx +10 -0
- package/lib/components/Menu/MenuItem.tsx +246 -0
- package/lib/components/Menu/MenuSkeleton.tsx +36 -0
- package/lib/components/Menu/SubMenu.tsx +420 -0
- package/lib/components/Menu/accessibility/MenuAccessibility.tsx +598 -0
- package/lib/components/Menu/accessibility/MenuAccessibilityReport.ts +484 -0
- package/lib/components/Menu/accessibility/index.ts +2 -0
- package/lib/components/Menu/index.ts +3 -0
- package/lib/components/Menu/menu.animations.ts +146 -0
- package/lib/components/Menu/menu.styles.ts +17 -0
- package/lib/components/Menu/menu.tokens.ts +617 -0
- package/lib/components/Menu/types.tsx +101 -0
- package/lib/components/Menu/utils.ts +44 -0
- package/lib/components/MenuV2/MenuV2.tsx +133 -0
- package/lib/components/MenuV2/MenuV2Content.tsx +442 -0
- package/lib/components/MenuV2/MenuV2Item.tsx +194 -0
- package/lib/components/MenuV2/MenuV2SubMenu.tsx +288 -0
- package/lib/components/MenuV2/index.ts +7 -0
- package/lib/components/MenuV2/menuV2.animations.ts +83 -0
- package/lib/components/MenuV2/menuV2.dark.tokens.ts +401 -0
- package/lib/components/MenuV2/menuV2.light.tokens.ts +401 -0
- package/lib/components/MenuV2/menuV2.tokens.ts +126 -0
- package/lib/components/MenuV2/menuV2.types.ts +99 -0
- package/lib/components/MenuV2/menuV2.utils.ts +156 -0
- package/lib/components/Modal/MobileModal.tsx +235 -0
- package/lib/components/Modal/Modal.tsx +400 -0
- package/lib/components/Modal/ModalSkeleton.tsx +110 -0
- package/lib/components/Modal/accessibility/ModalAccessibility.tsx +518 -0
- package/lib/components/Modal/accessibility/ModalAccessibilityReport.ts +350 -0
- package/lib/components/Modal/index.ts +3 -0
- package/lib/components/Modal/modal.animations.ts +19 -0
- package/lib/components/Modal/modal.tokens.ts +207 -0
- package/lib/components/Modal/modal.utils.ts +24 -0
- package/lib/components/Modal/types.ts +42 -0
- package/lib/components/Modal/useModal.ts +66 -0
- package/lib/components/MultiSelect/MobileMultiSelect.tsx +999 -0
- package/lib/components/MultiSelect/MultiSelect.tsx +822 -0
- package/lib/components/MultiSelect/MultiSelectMenu.tsx +903 -0
- package/lib/components/MultiSelect/MultiSelectMenuItem.tsx +79 -0
- package/lib/components/MultiSelect/MultiSelectSkeleton.tsx +37 -0
- package/lib/components/MultiSelect/MultiSelectSubMenu.tsx +142 -0
- package/lib/components/MultiSelect/MultiSelectTrigger.tsx +322 -0
- package/lib/components/MultiSelect/SelectAllItem.tsx +108 -0
- package/lib/components/MultiSelect/accessibility/MultiSelectAccessibility.tsx +751 -0
- package/lib/components/MultiSelect/accessibility/MultiSelectAccessibilityReport.ts +604 -0
- package/lib/components/MultiSelect/accessibility/index.ts +7 -0
- package/lib/components/MultiSelect/index.ts +4 -0
- package/lib/components/MultiSelect/multiSelect.animations.ts +135 -0
- package/lib/components/MultiSelect/multiSelect.tokens.ts +777 -0
- package/lib/components/MultiSelect/types.ts +228 -0
- package/lib/components/MultiSelect/utils.ts +217 -0
- package/lib/components/MultiSelectGroup/MultiSelectGroup.tsx +35 -0
- package/lib/components/MultiSelectGroup/MultiselectGroupProps.types.ts +8 -0
- package/lib/components/MultiSelectGroup/index.tsx +4 -0
- package/lib/components/MultiSelectV2/MobileMultiSelectV2.tsx +1 -0
- package/lib/components/MultiSelectV2/MultiSelectV2.tsx +449 -0
- package/lib/components/MultiSelectV2/MultiSelectV2Menu.tsx +477 -0
- package/lib/components/MultiSelectV2/MultiSelectV2MenuActions.tsx +87 -0
- package/lib/components/MultiSelectV2/MultiSelectV2MenuHeader.tsx +91 -0
- package/lib/components/MultiSelectV2/MultiSelectV2MenuItem.tsx +76 -0
- package/lib/components/MultiSelectV2/MultiSelectV2MenuItems.tsx +114 -0
- package/lib/components/MultiSelectV2/MultiSelectV2MenuSearch.tsx +42 -0
- package/lib/components/MultiSelectV2/MultiSelectV2MenuVirtualList.tsx +139 -0
- package/lib/components/MultiSelectV2/MultiSelectV2SelectAllItem.tsx +114 -0
- package/lib/components/MultiSelectV2/MultiSelectV2Skeleton.tsx +48 -0
- package/lib/components/MultiSelectV2/MultiSelectV2SubMenu.tsx +153 -0
- package/lib/components/MultiSelectV2/MultiSelectV2Trigger.tsx +365 -0
- package/lib/components/MultiSelectV2/index.ts +14 -0
- package/lib/components/MultiSelectV2/mobile/MobileMultiSelectV2.tsx +708 -0
- package/lib/components/MultiSelectV2/mobile/mobileMultiSelectV2.utils.ts +72 -0
- package/lib/components/MultiSelectV2/multiSelectV2.dark.tokens.ts +459 -0
- package/lib/components/MultiSelectV2/multiSelectV2.light.tokens.ts +461 -0
- package/lib/components/MultiSelectV2/multiSelectV2.tokens.ts +251 -0
- package/lib/components/MultiSelectV2/multiSelectV2.types.ts +173 -0
- package/lib/components/MultiSelectV2/utils.ts +243 -0
- package/lib/components/Popover/MobilePopover.tsx +226 -0
- package/lib/components/Popover/Popover.tsx +226 -0
- package/lib/components/Popover/PopoverFooter.tsx +58 -0
- package/lib/components/Popover/PopoverHeader.tsx +125 -0
- package/lib/components/Popover/PopoverSkeleton.tsx +92 -0
- package/lib/components/Popover/accessibility/PopoverAccessibility.tsx +550 -0
- package/lib/components/Popover/accessibility/PopoverAccessibilityReport.ts +353 -0
- package/lib/components/Popover/index.ts +3 -0
- package/lib/components/Popover/popover.animations.ts +125 -0
- package/lib/components/Popover/popover.tokens.ts +277 -0
- package/lib/components/Popover/types.ts +54 -0
- package/lib/components/PopoverV2/MobilePopoverV2.tsx +219 -0
- package/lib/components/PopoverV2/PopoverV2.tsx +231 -0
- package/lib/components/PopoverV2/PopoverV2Footer.tsx +55 -0
- package/lib/components/PopoverV2/PopoverV2Header.tsx +133 -0
- package/lib/components/PopoverV2/PopoverV2Skeleton.tsx +89 -0
- package/lib/components/PopoverV2/index.tsx +3 -0
- package/lib/components/PopoverV2/popoverV2.dark.tokens.tsx +228 -0
- package/lib/components/PopoverV2/popoverV2.light.tokens.tsx +228 -0
- package/lib/components/PopoverV2/popoverV2.token.ts +85 -0
- package/lib/components/PopoverV2/popoverV2.types.ts +70 -0
- package/lib/components/Primitives/Block/Block.tsx +460 -0
- package/lib/components/Primitives/Group/Group.tsx +93 -0
- package/lib/components/Primitives/Group/index.ts +3 -0
- package/lib/components/Primitives/Group/types.ts +99 -0
- package/lib/components/Primitives/Group/utils.ts +76 -0
- package/lib/components/Primitives/PrimitiveButton/PrimitiveButton.tsx +376 -0
- package/lib/components/Primitives/PrimitiveInput/PrimitiveInput.tsx +482 -0
- package/lib/components/Primitives/PrimitiveLink.tsx +238 -0
- package/lib/components/Primitives/PrimitiveText/PrimitiveText.tsx +203 -0
- package/lib/components/Primitives/PrimitiveTextArea.tsx +398 -0
- package/lib/components/ProgressBar/ProgressBar.tsx +300 -0
- package/lib/components/ProgressBar/accessibility/ProgressBarAccessibility.tsx +751 -0
- package/lib/components/ProgressBar/accessibility/ProgressBarAccessibilityReport.ts +517 -0
- package/lib/components/ProgressBar/index.ts +3 -0
- package/lib/components/ProgressBar/progressbar.tokens.ts +313 -0
- package/lib/components/ProgressBar/types.ts +26 -0
- package/lib/components/ProgressBar/utils.ts +111 -0
- package/lib/components/ProgressBarV2/CircularProgressBarV2.tsx +118 -0
- package/lib/components/ProgressBarV2/LinearProgressBarV2.tsx +104 -0
- package/lib/components/ProgressBarV2/ProgressBarV2.tsx +85 -0
- package/lib/components/ProgressBarV2/index.ts +3 -0
- package/lib/components/ProgressBarV2/progressBarV2.dark.tokens.ts +203 -0
- package/lib/components/ProgressBarV2/progressBarV2.light.tokens.ts +203 -0
- package/lib/components/ProgressBarV2/progressBarV2.tokens.ts +80 -0
- package/lib/components/ProgressBarV2/progressBarV2.types.ts +50 -0
- package/lib/components/ProgressBarV2/utils.ts +154 -0
- package/lib/components/Radio/Radio.tsx +237 -0
- package/lib/components/Radio/RadioGroup.tsx +248 -0
- package/lib/components/Radio/StyledRadio.tsx +71 -0
- package/lib/components/Radio/accessibility/RadioAccessibility.tsx +1109 -0
- package/lib/components/Radio/accessibility/RadioAccessibilityReport.ts +581 -0
- package/lib/components/Radio/accessibility/index.ts +2 -0
- package/lib/components/Radio/index.ts +4 -0
- package/lib/components/Radio/radio.animations.ts +49 -0
- package/lib/components/Radio/radio.token.ts +287 -0
- package/lib/components/Radio/types.ts +39 -0
- package/lib/components/Radio/utils.ts +125 -0
- package/lib/components/Select/Select.tsx +378 -0
- package/lib/components/Select/SelectItem/SelectItem.tsx +3 -0
- package/lib/components/Select/SelectItem/index.tsx +384 -0
- package/lib/components/Select/SelectItem/types.ts +46 -0
- package/lib/components/Select/SelectItem/utils.ts +62 -0
- package/lib/components/Select/SelectMenu.tsx +518 -0
- package/lib/components/Select/index.ts +2 -0
- package/lib/components/Select/select.token.ts +80 -0
- package/lib/components/Select/selectUtils.ts +74 -0
- package/lib/components/Select/types.tsx +102 -0
- package/lib/components/SelectV2/SelectItemV2.tsx +328 -0
- package/lib/components/SelectV2/index.ts +10 -0
- package/lib/components/SelectV2/selectV2.constants.ts +7 -0
- package/lib/components/SelectV2/selectV2.shared.types.ts +113 -0
- package/lib/components/SelectV2/selectV2.tokenStates.ts +15 -0
- package/lib/components/SelectV2/types.ts +115 -0
- package/lib/components/SelectV2/useSelectV2MenuBehavior.ts +135 -0
- package/lib/components/SelectorV2/CheckboxV2/CheckboxV2.tsx +282 -0
- package/lib/components/SelectorV2/CheckboxV2/StyledCheckboxV2.tsx +100 -0
- package/lib/components/SelectorV2/CheckboxV2/checkboxV2.animations.ts +85 -0
- package/lib/components/SelectorV2/CheckboxV2/checkboxV2.dark.tokens.ts +327 -0
- package/lib/components/SelectorV2/CheckboxV2/checkboxV2.light.tokens.ts +324 -0
- package/lib/components/SelectorV2/CheckboxV2/checkboxV2.tokens.ts +98 -0
- package/lib/components/SelectorV2/CheckboxV2/checkboxV2.types.ts +70 -0
- package/lib/components/SelectorV2/CheckboxV2/index.ts +3 -0
- package/lib/components/SelectorV2/CheckboxV2/utils.ts +171 -0
- package/lib/components/SelectorV2/RadioV2/RadioV2.tsx +158 -0
- package/lib/components/SelectorV2/RadioV2/StyledRadioV2.tsx +72 -0
- package/lib/components/SelectorV2/RadioV2/index.ts +3 -0
- package/lib/components/SelectorV2/RadioV2/radioV2.dark.tokens.ts +278 -0
- package/lib/components/SelectorV2/RadioV2/radioV2.light.tokens.ts +281 -0
- package/lib/components/SelectorV2/RadioV2/radioV2.tokens.ts +90 -0
- package/lib/components/SelectorV2/RadioV2/radioV2.types.ts +45 -0
- package/lib/components/SelectorV2/SwitchV2/SwitchV2.tsx +239 -0
- package/lib/components/SelectorV2/SwitchV2/index.ts +3 -0
- package/lib/components/SelectorV2/SwitchV2/switchV2.dark.tokens.ts +232 -0
- package/lib/components/SelectorV2/SwitchV2/switchV2.light.tokens.ts +230 -0
- package/lib/components/SelectorV2/SwitchV2/switchV2.tokens.ts +80 -0
- package/lib/components/SelectorV2/SwitchV2/switchV2.types.ts +56 -0
- package/lib/components/SelectorV2/selectorV2.types.ts +13 -0
- package/lib/components/SelectorsContent/SelectorsContent.types.ts +55 -0
- package/lib/components/SelectorsContent/SelectorsLabel.tsx +68 -0
- package/lib/components/SelectorsContent/SelectorsSubLabel.tsx +52 -0
- package/lib/components/SelectorsContent/index.ts +3 -0
- package/lib/components/Sidebar/Sidebar.tsx +551 -0
- package/lib/components/Sidebar/SidebarContent.tsx +121 -0
- package/lib/components/Sidebar/SidebarFooter.tsx +46 -0
- package/lib/components/Sidebar/SidebarHeader.tsx +199 -0
- package/lib/components/Sidebar/SidebarMobile/MobileNavigationItem.tsx +99 -0
- package/lib/components/Sidebar/SidebarMobile/MoreButton.tsx +77 -0
- package/lib/components/Sidebar/SidebarMobile/PrimaryActionButton.tsx +60 -0
- package/lib/components/Sidebar/SidebarMobile/hooks.ts +87 -0
- package/lib/components/Sidebar/SidebarMobile/index.tsx +393 -0
- package/lib/components/Sidebar/SidebarMobile/mobile.tokens.ts +159 -0
- package/lib/components/Sidebar/SidebarMobile/utils.ts +167 -0
- package/lib/components/Sidebar/TenantPanel.tsx +255 -0
- package/lib/components/Sidebar/accessibility/SidebarAccessibility.tsx +715 -0
- package/lib/components/Sidebar/accessibility/SidebarAccessibilityReport.ts +446 -0
- package/lib/components/Sidebar/index.ts +3 -0
- package/lib/components/Sidebar/sidebar.tokens.ts +269 -0
- package/lib/components/Sidebar/types.ts +85 -0
- package/lib/components/Sidebar/utils.ts +371 -0
- package/lib/components/SidebarV2/SecondarySidebar.tsx +123 -0
- package/lib/components/SidebarV2/SidebarV2.tsx +474 -0
- package/lib/components/SidebarV2/SidebarV2Footer.tsx +45 -0
- package/lib/components/SidebarV2/SidebarV2Header.tsx +165 -0
- package/lib/components/SidebarV2/SidebarV2MobileNavigation/MobileNavigationItem.tsx +92 -0
- package/lib/components/SidebarV2/SidebarV2MobileNavigation/MoreButton.tsx +74 -0
- package/lib/components/SidebarV2/SidebarV2MobileNavigation/PrimaryActionButton.tsx +72 -0
- package/lib/components/SidebarV2/SidebarV2MobileNavigation/hooks.ts +91 -0
- package/lib/components/SidebarV2/SidebarV2MobileNavigation/index.tsx +385 -0
- package/lib/components/SidebarV2/SidebarV2MobileNavigation/mobile.dark.tokens.ts +85 -0
- package/lib/components/SidebarV2/SidebarV2MobileNavigation/mobile.light.tokens.ts +85 -0
- package/lib/components/SidebarV2/SidebarV2MobileNavigation/mobile.tokens.ts +99 -0
- package/lib/components/SidebarV2/SidebarV2MobileNavigation/types.ts +36 -0
- package/lib/components/SidebarV2/SidebarV2MobileNavigation/utils.ts +174 -0
- package/lib/components/SidebarV2/SidebarV2Panel.tsx +139 -0
- package/lib/components/SidebarV2/index.ts +10 -0
- package/lib/components/SidebarV2/sidebarV2.dark.tokens.ts +223 -0
- package/lib/components/SidebarV2/sidebarV2.light.tokens.ts +223 -0
- package/lib/components/SidebarV2/sidebarV2.tokens.ts +120 -0
- package/lib/components/SidebarV2/sidebarV2.types.ts +6 -0
- package/lib/components/SidebarV2/types.ts +63 -0
- package/lib/components/SidebarV2/utils.ts +121 -0
- package/lib/components/SingleSelect/MobileSingleSelect.tsx +628 -0
- package/lib/components/SingleSelect/SingleSelect.tsx +638 -0
- package/lib/components/SingleSelect/SingleSelectMenu.tsx +831 -0
- package/lib/components/SingleSelect/SingleSelectSkeleton.tsx +37 -0
- package/lib/components/SingleSelect/SingleSelectTrigger.tsx +242 -0
- package/lib/components/SingleSelect/accessibility/SingleSelectAccessibility.tsx +1375 -0
- package/lib/components/SingleSelect/accessibility/SingleSelectAccessibilityReport.ts +602 -0
- package/lib/components/SingleSelect/accessibility/index.ts +2 -0
- package/lib/components/SingleSelect/index.ts +3 -0
- package/lib/components/SingleSelect/singleSelect.animations.ts +149 -0
- package/lib/components/SingleSelect/singleSelect.tokens.ts +686 -0
- package/lib/components/SingleSelect/types.ts +141 -0
- package/lib/components/SingleSelect/utils.ts +236 -0
- package/lib/components/SingleSelectGroup/SingleSelectGroup.tsx +34 -0
- package/lib/components/SingleSelectGroup/SingleSelectGroupProps.types.ts +10 -0
- package/lib/components/SingleSelectGroup/index.ts +4 -0
- package/lib/components/SingleSelectV2/MobileSingleSelectV2.tsx +615 -0
- package/lib/components/SingleSelectV2/SingleSelectV2.tsx +297 -0
- package/lib/components/SingleSelectV2/SingleSelectV2List.tsx +90 -0
- package/lib/components/SingleSelectV2/SingleSelectV2Menu.tsx +355 -0
- package/lib/components/SingleSelectV2/SingleSelectV2MenuItems.tsx +228 -0
- package/lib/components/SingleSelectV2/SingleSelectV2Search.tsx +54 -0
- package/lib/components/SingleSelectV2/SingleSelectV2Skeleton.tsx +48 -0
- package/lib/components/SingleSelectV2/SingleSelectV2Trigger.tsx +308 -0
- package/lib/components/SingleSelectV2/SingleSelectV2VirtualList.tsx +161 -0
- package/lib/components/SingleSelectV2/index.ts +11 -0
- package/lib/components/SingleSelectV2/mobile/SingleSelectV2MobileItem.tsx +127 -0
- package/lib/components/SingleSelectV2/mobile/singleSelectV2.mobile.utils.ts +55 -0
- package/lib/components/SingleSelectV2/singleSelectV2.animations.ts +146 -0
- package/lib/components/SingleSelectV2/singleSelectV2.dark.tokens.ts +367 -0
- package/lib/components/SingleSelectV2/singleSelectV2.light.tokens.ts +367 -0
- package/lib/components/SingleSelectV2/singleSelectV2.tokens.ts +202 -0
- package/lib/components/SingleSelectV2/singleSelectV2.types.ts +203 -0
- package/lib/components/SingleSelectV2/utils.ts +384 -0
- package/lib/components/Skeleton/README.md +279 -0
- package/lib/components/Skeleton/Skeleton.tsx +192 -0
- package/lib/components/Skeleton/SkeletonAvatar.tsx +45 -0
- package/lib/components/Skeleton/SkeletonCard.tsx +137 -0
- package/lib/components/Skeleton/SkeletonCompound.tsx +64 -0
- package/lib/components/Skeleton/hooks/useSkeletonBase.ts +33 -0
- package/lib/components/Skeleton/index.ts +41 -0
- package/lib/components/Skeleton/skeleton.tokens.ts +143 -0
- package/lib/components/Skeleton/types.ts +38 -0
- package/lib/components/Skeleton/utils.ts +32 -0
- package/lib/components/Slider/Slider.tsx +296 -0
- package/lib/components/Slider/accessibility/SliderAccessibility.tsx +631 -0
- package/lib/components/Slider/accessibility/SliderAccessibilityReport.ts +246 -0
- package/lib/components/Slider/accessibility/index.ts +7 -0
- package/lib/components/Slider/index.ts +3 -0
- package/lib/components/Slider/types.ts +35 -0
- package/lib/components/Slider/utils.ts +370 -0
- package/lib/components/Snackbar/Snackbar.tsx +315 -0
- package/lib/components/Snackbar/accessibility/SnackbarAccessibility.tsx +525 -0
- package/lib/components/Snackbar/accessibility/SnackbarAccessibilityReport.ts +360 -0
- package/lib/components/Snackbar/accessibility/index.ts +2 -0
- package/lib/components/Snackbar/index.ts +4 -0
- package/lib/components/Snackbar/snackbar.tokens.ts +263 -0
- package/lib/components/Snackbar/types.ts +51 -0
- package/lib/components/SnackbarV2/SnackbarV2.tsx +474 -0
- package/lib/components/SnackbarV2/index.ts +4 -0
- package/lib/components/SnackbarV2/snackbarV2.dark.tokens.ts +161 -0
- package/lib/components/SnackbarV2/snackbarV2.light.tokens.ts +161 -0
- package/lib/components/SnackbarV2/snackbarV2.tokens.ts +88 -0
- package/lib/components/SnackbarV2/snackbarV2.types.ts +65 -0
- package/lib/components/SplitTag/SplitTag.tsx +85 -0
- package/lib/components/SplitTag/accessibility/SplitTagAccessibility.tsx +972 -0
- package/lib/components/SplitTag/accessibility/SplitTagAccessibilityReport.ts +441 -0
- package/lib/components/SplitTag/index.ts +2 -0
- package/lib/components/SplitTag/types.ts +8 -0
- package/lib/components/StatCard/StatCard.tsx +1620 -0
- package/lib/components/StatCard/StatCardSkeleton.tsx +24 -0
- package/lib/components/StatCard/accessibility/StatCardAccessibility.tsx +612 -0
- package/lib/components/StatCard/accessibility/StatCardAccessibilityReport.ts +400 -0
- package/lib/components/StatCard/index.ts +3 -0
- package/lib/components/StatCard/statcard.tokens.ts +468 -0
- package/lib/components/StatCard/types.ts +82 -0
- package/lib/components/StatCard/utils.ts +78 -0
- package/lib/components/StatCardV2/StatCardV2.chartConfig.ts +47 -0
- package/lib/components/StatCardV2/StatCardV2.tsx +307 -0
- package/lib/components/StatCardV2/StatCardV2Change.tsx +89 -0
- package/lib/components/StatCardV2/StatCardV2NoData.tsx +160 -0
- package/lib/components/StatCardV2/StatCardV2Skeleton.tsx +24 -0
- package/lib/components/StatCardV2/StatCardV2Subtitle.tsx +29 -0
- package/lib/components/StatCardV2/StatCardV2Title.tsx +83 -0
- package/lib/components/StatCardV2/StatCardV2Value.tsx +67 -0
- package/lib/components/StatCardV2/index.ts +3 -0
- package/lib/components/StatCardV2/statcardV2.dark.tokens.ts +184 -0
- package/lib/components/StatCardV2/statcardV2.light.tokens.ts +184 -0
- package/lib/components/StatCardV2/statcardV2.tokens.ts +95 -0
- package/lib/components/StatCardV2/statcardV2.types.ts +90 -0
- package/lib/components/StatCardV2/utils.ts +29 -0
- package/lib/components/Stepper/HorizonatlLine.tsx +23 -0
- package/lib/components/Stepper/HorizontalStepper.tsx +467 -0
- package/lib/components/Stepper/Stepper.tsx +56 -0
- package/lib/components/Stepper/StepperLine.tsx +19 -0
- package/lib/components/Stepper/VerticalLine.tsx +24 -0
- package/lib/components/Stepper/VerticalStepper.tsx +965 -0
- package/lib/components/Stepper/accessibility/StepperAccessibility.tsx +723 -0
- package/lib/components/Stepper/accessibility/StepperAccessibilityReport.ts +337 -0
- package/lib/components/Stepper/index.ts +3 -0
- package/lib/components/Stepper/stepper.tokens.ts +883 -0
- package/lib/components/Stepper/types.ts +55 -0
- package/lib/components/StepperV2/Stepper/StepStatusCircle.tsx +54 -0
- package/lib/components/StepperV2/Stepper/StepperComponent.tsx +177 -0
- package/lib/components/StepperV2/Stepper/Steps.tsx +562 -0
- package/lib/components/StepperV2/Stepper/StepsHorizontalBody.tsx +105 -0
- package/lib/components/StepperV2/Stepper/StepsSubstepList.tsx +198 -0
- package/lib/components/StepperV2/Stepper/StepsVerticalSubstepRails.tsx +72 -0
- package/lib/components/StepperV2/Stepper/VerticalLineV2.tsx +26 -0
- package/lib/components/StepperV2/Stepper/stepsHelpers.ts +59 -0
- package/lib/components/StepperV2/StepperV2.tsx +44 -0
- package/lib/components/StepperV2/index.ts +10 -0
- package/lib/components/StepperV2/stepperV2.dark.tokens.ts +1403 -0
- package/lib/components/StepperV2/stepperV2.light.tokens.ts +1389 -0
- package/lib/components/StepperV2/stepperV2.tokens.ts +116 -0
- package/lib/components/StepperV2/stepperV2.types.ts +72 -0
- package/lib/components/StepperV2/utils.ts +37 -0
- package/lib/components/Switch/StyledSwitch.tsx +97 -0
- package/lib/components/Switch/Switch.tsx +245 -0
- package/lib/components/Switch/SwitchGroup.tsx +115 -0
- package/lib/components/Switch/accessibility/SwitchAccessibility.tsx +819 -0
- package/lib/components/Switch/accessibility/SwitchAccessibilityReport.ts +579 -0
- package/lib/components/Switch/accessibility/index.ts +6 -0
- package/lib/components/Switch/index.ts +4 -0
- package/lib/components/Switch/switch.animations.ts +54 -0
- package/lib/components/Switch/switch.token.ts +363 -0
- package/lib/components/Switch/types.ts +37 -0
- package/lib/components/Switch/utils.ts +158 -0
- package/lib/components/Tabs/StyledTabs.tsx +153 -0
- package/lib/components/Tabs/Tabs.tsx +313 -0
- package/lib/components/Tabs/TabsContent.tsx +33 -0
- package/lib/components/Tabs/TabsList.tsx +590 -0
- package/lib/components/Tabs/TabsTrigger.tsx +271 -0
- package/lib/components/Tabs/accessibility/TabsAccessibility.tsx +735 -0
- package/lib/components/Tabs/accessibility/TabsAccessibilityReport.ts +290 -0
- package/lib/components/Tabs/accessibility/index.ts +2 -0
- package/lib/components/Tabs/index.ts +8 -0
- package/lib/components/Tabs/tabs.token.ts +758 -0
- package/lib/components/Tabs/types.ts +99 -0
- package/lib/components/Tabs/utils.ts +235 -0
- package/lib/components/TabsV2/StyledTabsV2.tsx +166 -0
- package/lib/components/TabsV2/TabsV2.tsx +202 -0
- package/lib/components/TabsV2/TabsV2Content.tsx +34 -0
- package/lib/components/TabsV2/TabsV2List.tsx +403 -0
- package/lib/components/TabsV2/TabsV2Trigger.tsx +260 -0
- package/lib/components/TabsV2/index.ts +12 -0
- package/lib/components/TabsV2/tabsV2.context.tsx +45 -0
- package/lib/components/TabsV2/tabsV2.dark.tokens.ts +680 -0
- package/lib/components/TabsV2/tabsV2.light.tokens.ts +680 -0
- package/lib/components/TabsV2/tabsV2.tokens.ts +111 -0
- package/lib/components/TabsV2/tabsV2.types.ts +85 -0
- package/lib/components/TabsV2/tabsV2.utils.ts +145 -0
- package/lib/components/TagGroupV2/TagGroupV2.tsx +32 -0
- package/lib/components/TagGroupV2/TagGroupV2.types.ts +12 -0
- package/lib/components/TagGroupV2/index.ts +2 -0
- package/lib/components/TagV2/TagSkeleton.tsx +102 -0
- package/lib/components/TagV2/TagV2.tsx +179 -0
- package/lib/components/TagV2/TagV2.types.ts +61 -0
- package/lib/components/TagV2/index.ts +2 -0
- package/lib/components/TagV2/tagV2.dark.tokens.ts +359 -0
- package/lib/components/TagV2/tagV2.light.tokens.ts +352 -0
- package/lib/components/TagV2/tagV2.tokens.ts +85 -0
- package/lib/components/TagV2/utils.ts +71 -0
- package/lib/components/Tags/Tag.tsx +115 -0
- package/lib/components/Tags/TagBase.tsx +227 -0
- package/lib/components/Tags/Tags.tsx +82 -0
- package/lib/components/Tags/accessibility/TagAccessibility.tsx +665 -0
- package/lib/components/Tags/accessibility/TagAccessibilityReport.ts +392 -0
- package/lib/components/Tags/index.ts +3 -0
- package/lib/components/Tags/tag.dark.tokens.ts +297 -0
- package/lib/components/Tags/tag.light.tokens.ts +297 -0
- package/lib/components/Tags/tag.tokens.ts +87 -0
- package/lib/components/Tags/types.ts +49 -0
- package/lib/components/Text/Text.tsx +147 -0
- package/lib/components/TextInputGroup/TextInputGroup.tsx +34 -0
- package/lib/components/TextInputGroup/TextInputGroupProps.types.ts +8 -0
- package/lib/components/TextInputGroup/index.ts +4 -0
- package/lib/components/Timeline/Timeline.tsx +106 -0
- package/lib/components/Timeline/TimelineHeader.tsx +120 -0
- package/lib/components/Timeline/TimelineLabel.tsx +61 -0
- package/lib/components/Timeline/TimelineNode.tsx +225 -0
- package/lib/components/Timeline/TimelineShowMore.tsx +36 -0
- package/lib/components/Timeline/TimelineSubstep.tsx +173 -0
- package/lib/components/Timeline/index.ts +9 -0
- package/lib/components/Timeline/timeline.dark.token.ts +149 -0
- package/lib/components/Timeline/timeline.light.token.ts +149 -0
- package/lib/components/Timeline/timeline.token.ts +152 -0
- package/lib/components/Timeline/types.ts +115 -0
- package/lib/components/Timeline/utils.ts +169 -0
- package/lib/components/Tooltip/Tooltip.tsx +170 -0
- package/lib/components/Tooltip/accessibility/TooltipAccessibility.tsx +592 -0
- package/lib/components/Tooltip/accessibility/TooltipAccessibilityReport.ts +356 -0
- package/lib/components/Tooltip/index.ts +3 -0
- package/lib/components/Tooltip/tooltip.animations.ts +82 -0
- package/lib/components/Tooltip/tooltip.tokens.ts +157 -0
- package/lib/components/Tooltip/types.ts +41 -0
- package/lib/components/TooltipV2/TooltipV2.tsx +214 -0
- package/lib/components/TooltipV2/index.ts +3 -0
- package/lib/components/TooltipV2/tooltipV2.animation.ts +82 -0
- package/lib/components/TooltipV2/tooltipV2.dark.tokens.ts +115 -0
- package/lib/components/TooltipV2/tooltipV2.light.tokens.ts +115 -0
- package/lib/components/TooltipV2/tooltipV2.tokens.ts +62 -0
- package/lib/components/TooltipV2/tooltipV2.types.ts +43 -0
- package/lib/components/Topbar/Topbar.tsx +370 -0
- package/lib/components/Topbar/index.ts +3 -0
- package/lib/components/Topbar/topbar.tokens.ts +304 -0
- package/lib/components/Topbar/types.ts +53 -0
- package/lib/components/TopbarV2/TopbarV2.tsx +363 -0
- package/lib/components/TopbarV2/index.ts +3 -0
- package/lib/components/TopbarV2/topbarV2.dark.tokens.ts +159 -0
- package/lib/components/TopbarV2/topbarV2.light.tokens.ts +159 -0
- package/lib/components/TopbarV2/topbarV2.tokens.ts +84 -0
- package/lib/components/TopbarV2/types.ts +52 -0
- package/lib/components/Upload/Upload.tsx +675 -0
- package/lib/components/Upload/accessibility/UploadAccessibility.tsx +507 -0
- package/lib/components/Upload/accessibility/UploadAccessibilityReport.ts +495 -0
- package/lib/components/Upload/accessibility/index.ts +3 -0
- package/lib/components/Upload/components/DefaultState.tsx +101 -0
- package/lib/components/Upload/components/ErrorState.tsx +124 -0
- package/lib/components/Upload/components/FileListDisplay.tsx +112 -0
- package/lib/components/Upload/components/MixedState.tsx +88 -0
- package/lib/components/Upload/components/SuccessState.tsx +108 -0
- package/lib/components/Upload/components/UploadingState.tsx +108 -0
- package/lib/components/Upload/components/index.ts +6 -0
- package/lib/components/Upload/index.ts +4 -0
- package/lib/components/Upload/types.ts +103 -0
- package/lib/components/Upload/upload.tokens.ts +288 -0
- package/lib/components/Upload/utils.ts +929 -0
- package/lib/components/VirtualList/VirtualList.tsx +269 -0
- package/lib/components/VirtualList/index.ts +7 -0
- package/lib/components/VirtualList/types.ts +31 -0
- package/lib/components/VirtualList/utils.ts +169 -0
- package/lib/components/animations/ChevronAnimation/ChevronAnimation.tsx +128 -0
- package/lib/components/animations/ChevronAnimation/index.ts +3 -0
- package/lib/components/animations/ChevronAnimation/types.ts +32 -0
- package/lib/components/animations/ChevronAnimation/utils.ts +41 -0
- package/lib/components/animations/Ripple/RippleContainer.tsx +70 -0
- package/lib/components/animations/Ripple/index.ts +2 -0
- package/lib/components/common/Seperator.tsx +30 -0
- package/lib/components/common/TruncatedTextWithTooltip.tsx +120 -0
- package/lib/components/common/TruncatedTextWithTooltipV2/TruncatedTextWithTooltipV2.tsx +88 -0
- package/lib/components/common/TruncatedTextWithTooltipV2/index.ts +2 -0
- package/lib/components/common/TruncatedTextWithTooltipV2/types.ts +21 -0
- package/lib/components/common/TruncatedTextWithTooltipV2/utils.ts +6 -0
- package/lib/components/common/error.animations.ts +28 -0
- package/lib/components/common/index.ts +11 -0
- package/lib/components/common/useErrorShake.ts +20 -0
- package/lib/components/common/virtualViewport.ts +23 -0
- package/lib/components/shared/accessibility/AccessibilityDashboard.tsx +348 -0
- package/lib/components/shared/accessibility/LightHouse-components/AccordionLightHouse.tsx +328 -0
- package/lib/components/shared/accessibility/LightHouse-components/AlertLightHouse.tsx +371 -0
- package/lib/components/shared/accessibility/LightHouse-components/AvatarGroupLightHouse.tsx +386 -0
- package/lib/components/shared/accessibility/LightHouse-components/AvatarLightHouse.tsx +248 -0
- package/lib/components/shared/accessibility/LightHouse-components/BreadcrumbLightHouse.tsx +242 -0
- package/lib/components/shared/accessibility/LightHouse-components/ButtonGroupLightHouse.tsx +342 -0
- package/lib/components/shared/accessibility/LightHouse-components/ButtonLightHouse.tsx +185 -0
- package/lib/components/shared/accessibility/LightHouse-components/CardLightHouse.tsx +542 -0
- package/lib/components/shared/accessibility/LightHouse-components/ChartsLightHouse.tsx +349 -0
- package/lib/components/shared/accessibility/LightHouse-components/ChatInputLightHouse.tsx +231 -0
- package/lib/components/shared/accessibility/LightHouse-components/CheckboxLightHouse.tsx +236 -0
- package/lib/components/shared/accessibility/LightHouse-components/CodeBlockLightHouse.tsx +180 -0
- package/lib/components/shared/accessibility/LightHouse-components/DrawerLightHouse.tsx +254 -0
- package/lib/components/shared/accessibility/LightHouse-components/DropdownInputLightHouse.tsx +204 -0
- package/lib/components/shared/accessibility/LightHouse-components/KeyValuePairLightHouse.tsx +220 -0
- package/lib/components/shared/accessibility/LightHouse-components/MenuLightHouse.tsx +441 -0
- package/lib/components/shared/accessibility/LightHouse-components/ModalLightHouse.tsx +93 -0
- package/lib/components/shared/accessibility/LightHouse-components/MultiSelectLightHouse.tsx +308 -0
- package/lib/components/shared/accessibility/LightHouse-components/MultiValueInputLightHouse.tsx +144 -0
- package/lib/components/shared/accessibility/LightHouse-components/NumberInputLightHouse.tsx +106 -0
- package/lib/components/shared/accessibility/LightHouse-components/OTPInputLightHouse.tsx +97 -0
- package/lib/components/shared/accessibility/LightHouse-components/PopoverLightHouse.tsx +97 -0
- package/lib/components/shared/accessibility/LightHouse-components/ProgressBarLightHouse.tsx +280 -0
- package/lib/components/shared/accessibility/LightHouse-components/RadioLightHouse.tsx +322 -0
- package/lib/components/shared/accessibility/LightHouse-components/SearchInputLightHouse.tsx +195 -0
- package/lib/components/shared/accessibility/LightHouse-components/SidebarLightHouse.tsx +403 -0
- package/lib/components/shared/accessibility/LightHouse-components/SingleSelectLightHouse.tsx +278 -0
- package/lib/components/shared/accessibility/LightHouse-components/SliderLightHouse.tsx +284 -0
- package/lib/components/shared/accessibility/LightHouse-components/SnackbarLightHouse.tsx +35 -0
- package/lib/components/shared/accessibility/LightHouse-components/SplitTagLightHouse.tsx +357 -0
- package/lib/components/shared/accessibility/LightHouse-components/StatCardLightHouse.tsx +249 -0
- package/lib/components/shared/accessibility/LightHouse-components/StepperLightHouse.tsx +208 -0
- package/lib/components/shared/accessibility/LightHouse-components/SwitchLightHouse.tsx +355 -0
- package/lib/components/shared/accessibility/LightHouse-components/TabsLightHouse.tsx +339 -0
- package/lib/components/shared/accessibility/LightHouse-components/TagLightHouse.tsx +440 -0
- package/lib/components/shared/accessibility/LightHouse-components/TextAreaLightHouse.tsx +141 -0
- package/lib/components/shared/accessibility/LightHouse-components/TextInputLightHouse.tsx +95 -0
- package/lib/components/shared/accessibility/LightHouse-components/TooltipLightHouse.tsx +218 -0
- package/lib/components/shared/accessibility/LightHouse-components/UnitInputLightHouse.tsx +128 -0
- package/lib/components/shared/accessibility/LightHouse-components/UploadLightHouse.tsx +269 -0
- package/lib/components/shared/accessibility/index.ts +13 -0
- package/lib/components/shared/accessibility/reportGenerator.ts +522 -0
- package/lib/components/shared/accessibility/storybookParser.ts +93 -0
- package/lib/components/shared/accessibility/testResultsParser.ts +75 -0
- package/lib/context/ShadowAware.tsx +52 -0
- package/lib/context/ThemeContext.tsx +436 -0
- package/lib/context/ThemeProvider.tsx +59 -0
- package/lib/context/index.ts +6 -0
- package/lib/context/initComponentTokens.ts +271 -0
- package/lib/context/theme.enum.ts +4 -0
- package/lib/context/useComponentToken.ts +322 -0
- package/lib/global-utils/GlobalUtils.ts +130 -0
- package/lib/hooks/index.ts +8 -0
- package/lib/hooks/useBreakPoints.ts +48 -0
- package/lib/hooks/useClickOutside.ts +30 -0
- package/lib/hooks/useDebounce.ts +19 -0
- package/lib/hooks/useDropdownInteractionLock.ts +226 -0
- package/lib/hooks/useInputSlotPadding.ts +50 -0
- package/lib/hooks/usePreventParentScroll.ts +37 -0
- package/lib/hooks/useResizeObserver.ts +33 -0
- package/lib/hooks/useResponsiveTokens.ts +26 -0
- package/lib/hooks/useRipple.ts +74 -0
- package/lib/hooks/useScrollLock.ts +174 -0
- package/lib/hooks/useSectionScroll.ts +90 -0
- package/lib/hooks/useTruncationDetection.ts +87 -0
- package/lib/main.ts +321 -0
- package/lib/node.ts +39 -0
- package/lib/pollyfills/resizeObserverPollyfill.ts +35 -0
- package/lib/token-engine-server.ts +95 -0
- package/lib/token-engine.ts +105 -0
- package/lib/tokens/border.tokens.ts +58 -0
- package/lib/tokens/color.tokens.ts +116 -0
- package/lib/tokens/font.tokens.ts +312 -0
- package/lib/tokens/index.ts +2 -0
- package/lib/tokens/opacity.tokens.ts +34 -0
- package/lib/tokens/shadows.tokens.ts +28 -0
- package/lib/tokens/theme.token.ts +29 -0
- package/lib/tokens/unit.tokens.ts +101 -0
- package/lib/tokens/zIndex.tokens.ts +25 -0
- package/lib/utils/accessibility/aria-helpers.ts +317 -0
- package/lib/utils/accessibility/focus-helpers.ts +226 -0
- package/lib/utils/accessibility/icon-helpers.ts +25 -0
- package/lib/utils/accessibility/index.ts +25 -0
- package/lib/utils/accessibility/keyboard-helpers.ts +342 -0
- package/lib/utils/accessibility/visually-hidden.tsx +45 -0
- package/lib/utils/prop-helpers.ts +10 -0
- package/package.json +17 -3
- 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
|
+
}
|