@evolve.ui/core-components 1.0.0 → 1.1.0
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/Checkbox.d.cts +1 -1
- package/Checkbox.d.mts +1 -1
- package/{DateInput-B8XQZPTx.d.cts → DateInput-CFxTZu3m.d.mts} +2 -2
- package/{DateInput-B8XQZPTx.d.cts.map → DateInput-CFxTZu3m.d.mts.map} +1 -1
- package/{DateInput-CfNmywqQ.d.mts → DateInput-DstMICTl.d.cts} +2 -2
- package/{DateInput-CfNmywqQ.d.mts.map → DateInput-DstMICTl.d.cts.map} +1 -1
- package/{FavoriteButton-CowOPoqo.d.cts → FavoriteButton-BGF-3dze.d.cts} +2 -2
- package/{FavoriteButton-BRY8FVpV.d.mts.map → FavoriteButton-BGF-3dze.d.cts.map} +1 -1
- package/{FavoriteButton-BRY8FVpV.d.mts → FavoriteButton-BZEGWrRo.d.mts} +2 -2
- package/{FavoriteButton-CowOPoqo.d.cts.map → FavoriteButton-BZEGWrRo.d.mts.map} +1 -1
- package/Input.d.cts +1 -1
- package/Input.d.mts +1 -1
- package/Radio.d.cts +1 -1
- package/Radio.d.mts +1 -1
- package/{TextInput-BhkeQZh4.d.cts → TextInput-B7lqQ1gh.d.cts} +3 -3
- package/{TextInput-BhkeQZh4.d.cts.map → TextInput-B7lqQ1gh.d.cts.map} +1 -1
- package/{TextInput-CWCeR18b.d.mts → TextInput-CxoT3np_.d.mts} +3 -3
- package/{TextInput-CWCeR18b.d.mts.map → TextInput-CxoT3np_.d.mts.map} +1 -1
- package/dateInput.d.cts +1 -1
- package/dateInput.d.mts +1 -1
- package/datePicker.d.cts +1 -1
- package/datePicker.d.mts +1 -1
- package/evolve-ui.entries.json +556 -0
- package/favoriteButton.d.cts +1 -1
- package/favoriteButton.d.mts +1 -1
- package/featureFlags/hooks.d.cts +1 -1
- package/featureFlags/hooks.d.mts +1 -1
- package/fileUpload/testUtils/mockFilesAndImages.d.cts +3 -3
- package/fileUpload/testUtils/mockFilesAndImages.d.cts.map +1 -1
- package/fileUpload/testUtils/mockFilesAndImages.d.mts +3 -3
- package/fileUpload/testUtils/mockFilesAndImages.d.mts.map +1 -1
- package/fileUpload/testUtils/testUtils.d.cts +1 -1
- package/fileUpload/testUtils/testUtils.d.cts.map +1 -1
- package/fileUpload/testUtils/testUtils.d.mts +1 -1
- package/fileUpload/testUtils/testUtils.d.mts.map +1 -1
- package/{index-BVb-FJDo.d.mts → index-BYPklw8m.d.mts} +2 -2
- package/{index-BVb-FJDo.d.mts.map → index-BYPklw8m.d.mts.map} +1 -1
- package/{index-3V0Y3AU3.d.mts → index-Bvn6svaT.d.cts} +2 -2
- package/{index-3V0Y3AU3.d.mts.map → index-Bvn6svaT.d.cts.map} +1 -1
- package/{index-Bu7c6X7O.d.mts → index-C2zPeUs9.d.mts} +5 -5
- package/{index-Bu7c6X7O.d.mts.map → index-C2zPeUs9.d.mts.map} +1 -1
- package/{index-BXz3rMMk.d.cts → index-Cmz2OD6x.d.cts} +2 -2
- package/{index-BXz3rMMk.d.cts.map → index-Cmz2OD6x.d.cts.map} +1 -1
- package/{index-kchg551L.d.cts → index-DsZAxqnF.d.mts} +2 -2
- package/{index-kchg551L.d.cts.map → index-DsZAxqnF.d.mts.map} +1 -1
- package/{index-BzN9RaXe.d.cts → index-QtMvgJcp.d.cts} +5 -5
- package/{index-BzN9RaXe.d.cts.map → index-QtMvgJcp.d.cts.map} +1 -1
- package/{index-dpfLYhgI.d.mts → index-hrztez-b.d.mts} +2 -2
- package/{index-DBYqF9Sr.d.cts.map → index-hrztez-b.d.mts.map} +1 -1
- package/{index-DBYqF9Sr.d.cts → index-o9dwzLLE.d.cts} +2 -2
- package/{index-dpfLYhgI.d.mts.map → index-o9dwzLLE.d.cts.map} +1 -1
- package/index.d.cts +7 -7
- package/index.d.mts +7 -7
- package/package.json +2 -2
- package/providers.d.cts +1 -1
- package/providers.d.mts +1 -1
- package/src/Accordion/Accordion.tsx +233 -0
- package/src/AddressDisplay/AddressDisplay.tsx +43 -0
- package/src/AddressDisplay/components/addressComponents.tsx +111 -0
- package/src/AddressDisplay/components/addressField.tsx +25 -0
- package/src/AddressDisplay/components/addressSection.tsx +8 -0
- package/src/AddressDisplay/components/getCountryValue.tsx +31 -0
- package/src/AddressDisplay/types.ts +26 -0
- package/src/AutomatedCarousel/AutomatedCarousel.tsx +163 -0
- package/src/AutomatedCarousel/automatedCarousel.types.ts +7 -0
- package/src/AutomatedCarousel/components/CarouselProgressBar/CarouselProgressBar.tsx +139 -0
- package/src/AutomatedCarousel/components/CarouselProgressBar/components/CarouselProgressBarUnit/CarouselProgressBarUnit.tsx +165 -0
- package/src/Badge/Badge.tsx +104 -0
- package/src/Badge/Badge.types.ts +58 -0
- package/src/Badge/ItemBadge.tsx +97 -0
- package/src/Badge/LiveStatusBadge.tsx +93 -0
- package/src/BannerNotification/BannerNotification.tsx +157 -0
- package/src/BannerNotification/components/BannerNotificationSkeleton.tsx +39 -0
- package/src/BannerNotification/components/CalloutIcon/BannerIcon.tsx +48 -0
- package/src/BannerNotification/utils/getRandomTooltipId.ts +3 -0
- package/src/BottomSheet/BottomSheet.tsx +476 -0
- package/src/BottomSheet/components/BottomSheetBackdrop/BottomSheetBackdrop.tsx +47 -0
- package/src/BottomSheet/components/BottomSheetContentArea/BottomSheetContentArea.tsx +92 -0
- package/src/BottomSheet/components/BottomSheetOverlay/BottomSheetOverlayTranslate3d.tsx +196 -0
- package/src/BottomSheet/components/SheetDescription/SheetDescription.tsx +22 -0
- package/src/BottomSheet/components/SheetTitle/SheetTitle.tsx +21 -0
- package/src/BottomSheet/components/index.tsx +5 -0
- package/src/BottomSheet/hooks/index.tsx +13 -0
- package/src/BottomSheet/hooks/useAriaHider.tsx +91 -0
- package/src/BottomSheet/hooks/useBottomSheetServices.tsx +224 -0
- package/src/BottomSheet/hooks/useDragHandler.tsx +282 -0
- package/src/BottomSheet/hooks/useFocusTrap.tsx +113 -0
- package/src/BottomSheet/hooks/useLayoutEffect.tsx +4 -0
- package/src/BottomSheet/hooks/useOptimizedResizeObserver.tsx +94 -0
- package/src/BottomSheet/hooks/useOptimizedScrollState.tsx +87 -0
- package/src/BottomSheet/hooks/useReady.tsx +34 -0
- package/src/BottomSheet/hooks/useReducedMotion.tsx +22 -0
- package/src/BottomSheet/hooks/useScrollLock.tsx +143 -0
- package/src/BottomSheet/hooks/useSnapPoints.tsx +355 -0
- package/src/BottomSheet/hooks/useSpring.tsx +47 -0
- package/src/BottomSheet/hooks/useSpringInterpolationsTranslate3d.tsx +101 -0
- package/src/BottomSheet/index.tsx +237 -0
- package/src/BottomSheet/state/atoms.ts +147 -0
- package/src/BottomSheet/state/useBottomSheetStateMachine.ts +246 -0
- package/src/BottomSheet/types.ts +172 -0
- package/src/BottomSheet/utils.ts +65 -0
- package/src/Breadcrumbs/Breadcrumbs.tsx +126 -0
- package/src/Button/Button.tsx +164 -0
- package/src/Button/Button.types.ts +80 -0
- package/src/Button/buttonColorClasses.ts +53 -0
- package/src/Callout/Callout.tsx +136 -0
- package/src/Callout/Callout.types.ts +29 -0
- package/src/Callout/components/CalloutIcon/CalloutIcon.tsx +56 -0
- package/src/Card/CardGrid.tsx +263 -0
- package/src/Card/SingleCard.tsx +22 -0
- package/src/Card/cardFactory.tsx +176 -0
- package/src/Card/components/createBadge.tsx +34 -0
- package/src/Card/components/createFavoriteButton.tsx +40 -0
- package/src/Card/components/createImage.tsx +64 -0
- package/src/Card/components/createInfo.tsx +84 -0
- package/src/Card/components/createName.tsx +63 -0
- package/src/Card/components/createPriceStatus.tsx +100 -0
- package/src/Card/components/types.ts +4 -0
- package/src/Card/hooks/useCardVisibilityTracking.ts +82 -0
- package/src/Card/sharedProps.ts +12 -0
- package/src/Card/types.ts +62 -0
- package/src/Carousel/Carousel.tsx +513 -0
- package/src/Carousel/Carousel.types.ts +43 -0
- package/src/Carousel/Carousel.utils.ts +63 -0
- package/src/Carousel/components/DotButton/DotButton.tsx +42 -0
- package/src/Carousel/components/NavigationButtons/NavigationButtons.tsx +77 -0
- package/src/Carousel/components/TitleSection/TitleSection.tsx +61 -0
- package/src/Carousel/components/TitleSection/components/SubHeaderLeft/SubHeaderLeft.tsx +53 -0
- package/src/Carousel/components/TitleSection/components/Title/Title.tsx +46 -0
- package/src/Carousel/index.ts +2 -0
- package/src/Carousel/testUtils.ts +10 -0
- package/src/Chips/Chip.tsx +153 -0
- package/src/Chips/ChipGroup.tsx +173 -0
- package/src/Chips/SuggestionChip.tsx +15 -0
- package/src/Chips/chipTypes.ts +113 -0
- package/src/Chips/index.ts +4 -0
- package/src/ChoiceListItem/Checkbox.tsx +69 -0
- package/src/ChoiceListItem/Radio.tsx +61 -0
- package/src/ChoiceListItem/choiceListItem.tsx +99 -0
- package/src/ChoiceListItem/common.tsx +148 -0
- package/src/ChoiceListItem/index.ts +3 -0
- package/src/Collapsible/Collapsible.tsx +58 -0
- package/src/Confetti/Confetti.tsx +270 -0
- package/src/Confetti/index.ts +2 -0
- package/src/ContentPager/ContentPager.tsx +189 -0
- package/src/ContentPager/components/Page.tsx +24 -0
- package/src/CookieConsent/components/CookieCategoryRow/CookieCategoryRow.tsx +30 -0
- package/src/CookieConsent/components/CookieConsent/CookieConsent.tsx +74 -0
- package/src/CookieConsent/components/CookieConsentBanner/CookieConsentBanner.tsx +51 -0
- package/src/CookieConsent/components/CookieConsentModal/CookieConsentModal.tsx +64 -0
- package/src/CookieConsent/hooks/useCookieConsent.ts +170 -0
- package/src/CookieConsent/hooks/useCookieConsentState.ts +142 -0
- package/src/CookieConsent/index.ts +16 -0
- package/src/CookieConsent/types/cookieConsent.types.ts +79 -0
- package/src/CookieConsent/types/index.ts +7 -0
- package/src/Countdown/Countdown.tsx +161 -0
- package/src/Countdown/Countdown.types.ts +4 -0
- package/src/Countdown/hooks/useGetCountdown.ts +103 -0
- package/src/DateTimeInput/dateInput/DateInput.tsx +199 -0
- package/src/DateTimeInput/datePicker/DatePicker.tsx +138 -0
- package/src/DateTimeInput/datePicker/components/calendar/Calendar.tsx +72 -0
- package/src/DateTimeInput/datePicker/index.ts +2 -0
- package/src/DateTimeInput/timeInput/TimeInput.tsx +128 -0
- package/src/DateTimeInput/timePicker/TimePicker.tsx +118 -0
- package/src/DateTimeInput/timePicker/components/TimePopover/AmPmSwitch.tsx +103 -0
- package/src/DateTimeInput/timePicker/components/TimePopover/TimeFieldInput.tsx +26 -0
- package/src/DateTimeInput/timePicker/components/TimePopover/TimePopover.tsx +84 -0
- package/src/DateTimeInput/timePicker/components/TimePopover/TimePopover.utils.ts +67 -0
- package/src/DateTimeInput/timePicker/components/TimePopover/TimePopoverActions.tsx +26 -0
- package/src/DateTimeInput/timePicker/index.ts +2 -0
- package/src/DateTimeInput/utils/getDisplayDate.ts +15 -0
- package/src/DateTimeInput/utils/getDisplayTime.ts +15 -0
- package/src/DateTimeInput/utils/index.ts +2 -0
- package/src/DateTimeInput/utils/parseTimeValue.ts +54 -0
- package/src/Drawer/Drawer.tsx +446 -0
- package/src/Drawer/atoms.ts +4 -0
- package/src/Drawer/components/DialogActions/DialogActions.tsx +33 -0
- package/src/Drawer/components/DialogContent/DialogContent.tsx +80 -0
- package/src/Drawer/components/DialogHeader/DialogHeader.tsx +68 -0
- package/src/EducationalPopover/EducationalPopover.tsx +174 -0
- package/src/FavoriteButton/FavoriteButton.tsx +64 -0
- package/src/FavoriteButton/components/IconOnlyFavoriteButton/IconOnlyFavoriteButton.tsx +80 -0
- package/src/FavoriteButton/components/LabeledAnimatedFavoriteButton/LabeledAnimatedFavoriteButton.tsx +179 -0
- package/src/FavoriteButton/components/LabeledFavoriteButton/LabeledFavoriteButton.tsx +71 -0
- package/src/FavoriteButton/components/SmallHeartStraightIcon/SmallHeartStraightIcon.tsx +14 -0
- package/src/FavoriteButton/hooks/useFavoriteButtonState.ts +30 -0
- package/src/FavoriteButton/index.ts +2 -0
- package/src/FavoriteButton/types/favoriteButton.ts +87 -0
- package/src/FeatureFlags/devTools/FeatureFlagDevToolsUI.tsx +226 -0
- package/src/FeatureFlags/devTools/getFeatureFlagCookieOverrides.ts +30 -0
- package/src/FeatureFlags/devTools/utils/cookies.ts +169 -0
- package/src/FeatureFlags/hooks/index.ts +2 -0
- package/src/FeatureFlags/hooks/useFeatureFlag.tsx +19 -0
- package/src/FeatureFlags/hooks/useFeatureFlags.tsx +15 -0
- package/src/FileUpload/components/FileCard/FileCard.tsx +50 -0
- package/src/FileUpload/components/FileDropzone/FileDropzone.tsx +221 -0
- package/src/FileUpload/components/FileDropzone/components/DropzoneContent/DropzoneContent.tsx +28 -0
- package/src/FileUpload/components/FileDropzone/components/DropzoneContent/DropzoneText.tsx +81 -0
- package/src/FileUpload/components/FileDropzone/components/FileDropzoneDebugger/FileDropzoneDebugger.tsx +40 -0
- package/src/FileUpload/components/FileDropzone/components/FileDropzoneDebugger/FileRejectionsDisplay.tsx +110 -0
- package/src/FileUpload/components/FileDropzoneOverlay/FileDropzoneOverlay.tsx +183 -0
- package/src/FileUpload/components/FileDropzoneOverlay/components/OverlayDropzoneContent.tsx +146 -0
- package/src/FileUpload/components/FileDropzoneOverlay/components/OverlayDropzoneDragText.tsx +42 -0
- package/src/FileUpload/components/FileDropzoneOverlay/styles.ts +79 -0
- package/src/FileUpload/components/FilePreviewList/FilePreviewList.tsx +251 -0
- package/src/FileUpload/components/FilePreviewList/virtualizerComponents/FilePreviewVirtualizedList/FilePreviewVirtualizedList.tsx +88 -0
- package/src/FileUpload/components/FileUpload/FileUpload.tsx +63 -0
- package/src/FileUpload/components/FileUploadCanvas/FileUploadCanvas.tsx +125 -0
- package/src/FileUpload/components/GridFileCard/GridFileCard.tsx +364 -0
- package/src/FileUpload/components/GridFileCard/utils/sortable.ts +48 -0
- package/src/FileUpload/components/ListFileCard/ListFileCard.tsx +259 -0
- package/src/FileUpload/components/types.ts +78 -0
- package/src/FileUpload/hooks/filePreview/useImageFilePreviewUrl.ts +62 -0
- package/src/FileUpload/hooks/index.ts +11 -0
- package/src/FileUpload/hooks/upload/useHandleDragEnter/useHandleDragEnter.ts +154 -0
- package/src/FileUpload/hooks/upload/useHandleDrop/createFileRejectionHelpers.ts +273 -0
- package/src/FileUpload/hooks/upload/useHandleDrop/useHandleDrop.ts +153 -0
- package/src/FileUpload/hooks/upload/useUploadAvailability/useUploadAvailability.ts +44 -0
- package/src/FileUpload/hooks/validation/createDropzoneValidator.ts +41 -0
- package/src/FileUpload/hooks/validation/useFileValidation.ts +25 -0
- package/src/FileUpload/hooks/validation/useFileValidationLogic.ts +107 -0
- package/src/FileUpload/hooks/validation/useValidationMessages.ts +92 -0
- package/src/FileUpload/hooks/validation/useValidationState.ts +171 -0
- package/src/FileUpload/index.ts +57 -0
- package/src/FileUpload/providers/FileUploadProvider.tsx +219 -0
- package/src/FileUpload/providers/UploadValidationProvider.tsx +159 -0
- package/src/FileUpload/testUtils/mockFilesAndImages.ts +64 -0
- package/src/FileUpload/testUtils/testUtils.ts +111 -0
- package/src/FileUpload/types/commonTypes.ts +267 -0
- package/src/FileUpload/types/index.ts +31 -0
- package/src/FileUpload/types/sharedTypes.ts +159 -0
- package/src/FileUpload/utils/constants.ts +79 -0
- package/src/FileUpload/utils/createMessageContext.ts +180 -0
- package/src/FileUpload/utils/getFileExtensionsFromAcceptedTypes.ts +73 -0
- package/src/FileUpload/utils/getFilePluralizationText.ts +68 -0
- package/src/FileUpload/utils/getMimeTypes.ts +26 -0
- package/src/FileUpload/utils/messageUtils.ts +41 -0
- package/src/FileUpload/utils/styles.ts +68 -0
- package/src/FormLabel/FormLabel.tsx +212 -0
- package/src/IconButton/IconButton.tsx +154 -0
- package/src/Icons/CreditCardIcon/CreditCardIcon.tsx +73 -0
- package/src/Icons/CreditCardIcon/cardIcons.tsx +270 -0
- package/src/Icons/IconBase.tsx +133 -0
- package/src/Icons/anyIcon.ts +3 -0
- package/src/Icons/generatedIcons/Archive.tsx +48 -0
- package/src/Icons/generatedIcons/ArrowClockwise.tsx +48 -0
- package/src/Icons/generatedIcons/ArrowDown.tsx +48 -0
- package/src/Icons/generatedIcons/ArrowLeft.tsx +48 -0
- package/src/Icons/generatedIcons/ArrowRight.tsx +48 -0
- package/src/Icons/generatedIcons/ArrowSquareOut.tsx +51 -0
- package/src/Icons/generatedIcons/ArrowSquareRight.tsx +51 -0
- package/src/Icons/generatedIcons/ArrowUp.tsx +48 -0
- package/src/Icons/generatedIcons/ArrowsCounterClockwise.tsx +48 -0
- package/src/Icons/generatedIcons/ArrowsLeftRight.tsx +48 -0
- package/src/Icons/generatedIcons/ArrowsOutCardinal.tsx +48 -0
- package/src/Icons/generatedIcons/Bank.tsx +48 -0
- package/src/Icons/generatedIcons/Bell.tsx +51 -0
- package/src/Icons/generatedIcons/BookBookmark.tsx +51 -0
- package/src/Icons/generatedIcons/BookmarksSimple.tsx +51 -0
- package/src/Icons/generatedIcons/Broadcast.tsx +48 -0
- package/src/Icons/generatedIcons/CalendarBlank.tsx +48 -0
- package/src/Icons/generatedIcons/CalendarStar.tsx +51 -0
- package/src/Icons/generatedIcons/Camera.tsx +51 -0
- package/src/Icons/generatedIcons/CaretDown.tsx +48 -0
- package/src/Icons/generatedIcons/CaretLeft.tsx +48 -0
- package/src/Icons/generatedIcons/CaretRight.tsx +48 -0
- package/src/Icons/generatedIcons/CaretUp.tsx +48 -0
- package/src/Icons/generatedIcons/Certificate.tsx +51 -0
- package/src/Icons/generatedIcons/ChartBar.tsx +48 -0
- package/src/Icons/generatedIcons/Check.tsx +51 -0
- package/src/Icons/generatedIcons/CheckCircle.tsx +48 -0
- package/src/Icons/generatedIcons/CirclesFour.tsx +51 -0
- package/src/Icons/generatedIcons/Clock.tsx +48 -0
- package/src/Icons/generatedIcons/ClockCountdown.tsx +48 -0
- package/src/Icons/generatedIcons/Close.tsx +51 -0
- package/src/Icons/generatedIcons/CloudArrowUp.tsx +51 -0
- package/src/Icons/generatedIcons/CloudWarning.tsx +51 -0
- package/src/Icons/generatedIcons/Code.tsx +48 -0
- package/src/Icons/generatedIcons/Compass.tsx +51 -0
- package/src/Icons/generatedIcons/Confetti.tsx +51 -0
- package/src/Icons/generatedIcons/Copy.tsx +48 -0
- package/src/Icons/generatedIcons/CornersOut.tsx +48 -0
- package/src/Icons/generatedIcons/CreditCard.tsx +48 -0
- package/src/Icons/generatedIcons/Crown.tsx +51 -0
- package/src/Icons/generatedIcons/CrownSimple.tsx +51 -0
- package/src/Icons/generatedIcons/CurrencyCircleDollar.tsx +48 -0
- package/src/Icons/generatedIcons/CurrencyDollarSimple.tsx +51 -0
- package/src/Icons/generatedIcons/Desktop.tsx +51 -0
- package/src/Icons/generatedIcons/Detective.tsx +51 -0
- package/src/Icons/generatedIcons/DotOutline.tsx +46 -0
- package/src/Icons/generatedIcons/DotsSixVertical.tsx +51 -0
- package/src/Icons/generatedIcons/DotsThreeOutline.tsx +51 -0
- package/src/Icons/generatedIcons/DotsThreeOutlineVertical.tsx +51 -0
- package/src/Icons/generatedIcons/DownloadSimple.tsx +51 -0
- package/src/Icons/generatedIcons/EnvelopeSimple.tsx +48 -0
- package/src/Icons/generatedIcons/Eraser.tsx +51 -0
- package/src/Icons/generatedIcons/Eye.tsx +51 -0
- package/src/Icons/generatedIcons/EyeSlash.tsx +51 -0
- package/src/Icons/generatedIcons/FacebookLogo.tsx +48 -0
- package/src/Icons/generatedIcons/FileArchive.tsx +48 -0
- package/src/Icons/generatedIcons/FileArrowDown.tsx +48 -0
- package/src/Icons/generatedIcons/FileCsv.tsx +48 -0
- package/src/Icons/generatedIcons/FilePdf.tsx +48 -0
- package/src/Icons/generatedIcons/FileText.tsx +48 -0
- package/src/Icons/generatedIcons/Files.tsx +51 -0
- package/src/Icons/generatedIcons/Fire.tsx +51 -0
- package/src/Icons/generatedIcons/Flag.tsx +51 -0
- package/src/Icons/generatedIcons/FlagBannerFold.tsx +48 -0
- package/src/Icons/generatedIcons/FloppyDisk.tsx +51 -0
- package/src/Icons/generatedIcons/Gauge.tsx +51 -0
- package/src/Icons/generatedIcons/Gavel.tsx +51 -0
- package/src/Icons/generatedIcons/Gear.tsx +51 -0
- package/src/Icons/generatedIcons/GlobeHemisphereWest.tsx +51 -0
- package/src/Icons/generatedIcons/GoogleLogo.tsx +48 -0
- package/src/Icons/generatedIcons/GridNine.tsx +51 -0
- package/src/Icons/generatedIcons/HandCoins.tsx +51 -0
- package/src/Icons/generatedIcons/HandbagSimple.tsx +51 -0
- package/src/Icons/generatedIcons/Headset.tsx +51 -0
- package/src/Icons/generatedIcons/HeartStraight.tsx +51 -0
- package/src/Icons/generatedIcons/HourglassLow.tsx +48 -0
- package/src/Icons/generatedIcons/House.tsx +51 -0
- package/src/Icons/generatedIcons/Image.tsx +51 -0
- package/src/Icons/generatedIcons/ImageSquare.tsx +51 -0
- package/src/Icons/generatedIcons/ImagesSquare.tsx +51 -0
- package/src/Icons/generatedIcons/Info.tsx +48 -0
- package/src/Icons/generatedIcons/InstagramLogo.tsx +51 -0
- package/src/Icons/generatedIcons/Lightning.tsx +48 -0
- package/src/Icons/generatedIcons/Link.tsx +51 -0
- package/src/Icons/generatedIcons/LinkedinLogo.tsx +51 -0
- package/src/Icons/generatedIcons/List.tsx +48 -0
- package/src/Icons/generatedIcons/Lock.tsx +51 -0
- package/src/Icons/generatedIcons/LockOpen.tsx +51 -0
- package/src/Icons/generatedIcons/MagnifyingGlass.tsx +48 -0
- package/src/Icons/generatedIcons/MapPin.tsx +51 -0
- package/src/Icons/generatedIcons/MapPinSimpleArea.tsx +48 -0
- package/src/Icons/generatedIcons/Medal.tsx +48 -0
- package/src/Icons/generatedIcons/Megaphone.tsx +48 -0
- package/src/Icons/generatedIcons/Minus.tsx +45 -0
- package/src/Icons/generatedIcons/Money.tsx +51 -0
- package/src/Icons/generatedIcons/Notches.tsx +48 -0
- package/src/Icons/generatedIcons/NotePencil.tsx +48 -0
- package/src/Icons/generatedIcons/Package.tsx +51 -0
- package/src/Icons/generatedIcons/PaintBrush.tsx +51 -0
- package/src/Icons/generatedIcons/PaperPlaneTilt.tsx +51 -0
- package/src/Icons/generatedIcons/Paperclip.tsx +51 -0
- package/src/Icons/generatedIcons/Pause.tsx +51 -0
- package/src/Icons/generatedIcons/PencilLine.tsx +51 -0
- package/src/Icons/generatedIcons/PencilRuler.tsx +51 -0
- package/src/Icons/generatedIcons/PencilSimple.tsx +51 -0
- package/src/Icons/generatedIcons/Phone.tsx +51 -0
- package/src/Icons/generatedIcons/PinterestLogo.tsx +51 -0
- package/src/Icons/generatedIcons/Play.tsx +51 -0
- package/src/Icons/generatedIcons/Plus.tsx +51 -0
- package/src/Icons/generatedIcons/PlusCircle.tsx +48 -0
- package/src/Icons/generatedIcons/Printer.tsx +51 -0
- package/src/Icons/generatedIcons/Question.tsx +48 -0
- package/src/Icons/generatedIcons/Receipt.tsx +51 -0
- package/src/Icons/generatedIcons/Rows.tsx +51 -0
- package/src/Icons/generatedIcons/SealCheck.tsx +51 -0
- package/src/Icons/generatedIcons/SelectionSlash.tsx +48 -0
- package/src/Icons/generatedIcons/ShareNetwork.tsx +51 -0
- package/src/Icons/generatedIcons/ShieldCheck.tsx +51 -0
- package/src/Icons/generatedIcons/ShieldStar.tsx +51 -0
- package/src/Icons/generatedIcons/ShoppingCart.tsx +48 -0
- package/src/Icons/generatedIcons/ShuffleAngular.tsx +48 -0
- package/src/Icons/generatedIcons/SidebarSimple.tsx +48 -0
- package/src/Icons/generatedIcons/SlidersHorizontal.tsx +51 -0
- package/src/Icons/generatedIcons/Smiley.tsx +48 -0
- package/src/Icons/generatedIcons/Sparkle.tsx +51 -0
- package/src/Icons/generatedIcons/SpinnerGap.tsx +48 -0
- package/src/Icons/generatedIcons/StackPlus.tsx +48 -0
- package/src/Icons/generatedIcons/Star.tsx +51 -0
- package/src/Icons/generatedIcons/Storefront.tsx +51 -0
- package/src/Icons/generatedIcons/Swap.tsx +51 -0
- package/src/Icons/generatedIcons/Tag.tsx +51 -0
- package/src/Icons/generatedIcons/ThumbsDown.tsx +48 -0
- package/src/Icons/generatedIcons/ThumbsUp.tsx +48 -0
- package/src/Icons/generatedIcons/Toolbox.tsx +48 -0
- package/src/Icons/generatedIcons/Trash.tsx +48 -0
- package/src/Icons/generatedIcons/TrendUp.tsx +48 -0
- package/src/Icons/generatedIcons/Trophy.tsx +51 -0
- package/src/Icons/generatedIcons/Truck.tsx +51 -0
- package/src/Icons/generatedIcons/UploadSimple.tsx +51 -0
- package/src/Icons/generatedIcons/User.tsx +48 -0
- package/src/Icons/generatedIcons/Users.tsx +48 -0
- package/src/Icons/generatedIcons/UsersThree.tsx +51 -0
- package/src/Icons/generatedIcons/VideoCamera.tsx +51 -0
- package/src/Icons/generatedIcons/Warning.tsx +51 -0
- package/src/Icons/generatedIcons/WarningCircle.tsx +48 -0
- package/src/Icons/generatedIcons/WifiHigh.tsx +51 -0
- package/src/Icons/generatedIcons/XCircle.tsx +48 -0
- package/src/Icons/generatedIcons/XLogo.tsx +48 -0
- package/src/Icons/generatedIcons/YoutubeLogo.tsx +51 -0
- package/src/Icons/importedIcons.ts +152 -0
- package/src/Icons/index.ts +149 -0
- package/src/Icons/socialIcons/appleSocialIcon.tsx +50 -0
- package/src/Icons/socialIcons/facebookSocialIcon.tsx +41 -0
- package/src/Icons/socialIcons/googleSocialIcon.tsx +134 -0
- package/src/ImageCluster/ImageCluster.tsx +125 -0
- package/src/ImageSlider/ImageSlider.tsx +582 -0
- package/src/ImageSlider/ImageSlider.types.ts +90 -0
- package/src/ImageSlider/components/IndexIndicator/IndexIndicator.tsx +21 -0
- package/src/ImageSlider/components/NavigationButtons/NavigationButtons.tsx +113 -0
- package/src/ImageSlider/components/ResponsiveImage/ResponsiveImage.tsx +114 -0
- package/src/ImageSlider/components/ResponsiveImage/index.ts +1 -0
- package/src/ImageSlider/components/Thumbnails/Thumbnails.tsx +184 -0
- package/src/ImageSlider/constants.ts +59 -0
- package/src/ImageSlider/hooks/index.ts +3 -0
- package/src/ImageSlider/hooks/useImageSlider.ts +57 -0
- package/src/ImageSlider/hooks/useImageSliderNavigation.ts +133 -0
- package/src/ImageSlider/hooks/useImageSliderScrollHandler.ts +171 -0
- package/src/ImageSlider/index.ts +3 -0
- package/src/ImageSlider/utils/imageOptimization.ts +97 -0
- package/src/ImageSlider/utils/index.ts +1 -0
- package/src/InlineNotification/InlineNotification.tsx +95 -0
- package/src/JumpMenu/JumpMenu.tsx +395 -0
- package/src/JumpMenu/components/JumpMenuItem/JumpMenuItem.tsx +62 -0
- package/src/JumpMenu/hooks/useContainerTracking.ts +171 -0
- package/src/JumpMenu/hooks/useIntersectionObserver.ts +77 -0
- package/src/JumpMenu/hooks/useKeyboardNavigation.ts +102 -0
- package/src/JumpMenu/hooks/useResizeObserver.ts +53 -0
- package/src/JumpMenu/hooks/useSectionObserver.ts +102 -0
- package/src/JumpMenu/index.ts +2 -0
- package/src/JumpMenu/types/index.ts +19 -0
- package/src/Loading/Loading.tsx +39 -0
- package/src/Loading/components/LoadingDot/LoadingDot.tsx +16 -0
- package/src/LoadingSpinner/LoadingSpinner.tsx +20 -0
- package/src/Menu/Menu.tsx +372 -0
- package/src/Menu/components/MenuDivider/MenuDivider.tsx +5 -0
- package/src/Menu/components/MenuItem/MenuItem.tsx +298 -0
- package/src/Menu/context/menuContext.tsx +20 -0
- package/src/Menu/hooks/useMenu.tsx +18 -0
- package/src/Menu/index.ts +3 -0
- package/src/Modal/ComposableModal.tsx +313 -0
- package/src/Modal/Modal.tsx +231 -0
- package/src/Modal/components/BackButton/BackButton.tsx +43 -0
- package/src/Modal/components/Backdrop/Backdrop.tsx +62 -0
- package/src/Modal/components/CloseButton/CloseButton.tsx +55 -0
- package/src/Modal/components/ModalActions/ModalActions.tsx +135 -0
- package/src/Modal/components/ModalContent/ModalContent.tsx +163 -0
- package/src/Modal/components/ModalHeader/ModalHeader.tsx +84 -0
- package/src/Modal/components/ModalHeader/hooks/useHeaderAnimation.tsx +49 -0
- package/src/Modal/components/ModalProgressSteps/ModalProgressSteps.tsx +26 -0
- package/src/Modal/components/ModalSubTitle/ModalSubTitle.tsx +29 -0
- package/src/Modal/components/ModalTitle/ModalTitle.tsx +22 -0
- package/src/Modal/components/PreventModalScroll/PreventModalScroll.tsx +106 -0
- package/src/Modal/components/index.ts +12 -0
- package/src/Modal/context.ts +33 -0
- package/src/Modal/hooks/useCloseModal.ts +133 -0
- package/src/NotificationBadge/NotificationBadge.tsx +34 -0
- package/src/Pagination/AdvancedPagination.tsx +48 -0
- package/src/Pagination/Pagination.tsx +216 -0
- package/src/Pagination/components/AdvancedPaginator/AdvancedPaginator.tsx +52 -0
- package/src/Pagination/components/AdvancedPaginator/components/PageSelect/PageSelect.tsx +43 -0
- package/src/Pagination/components/ItemCountDisplay/ItemCountDisplay.tsx +32 -0
- package/src/Pagination/components/ItemCountSelect/ItemCountSelect.tsx +33 -0
- package/src/Pagination/index.tsx +3 -0
- package/src/Pagination/paginationProvider.tsx +234 -0
- package/src/Pagination/utils.tsx +36 -0
- package/src/PhoneInput/PhoneInput.tsx +509 -0
- package/src/PhoneInput/useDropdownLogic.tsx +163 -0
- package/src/ProgressCircle/ProgressCircle.tsx +246 -0
- package/src/ProgressCircle/hooks/useProgressStatusTransitions.ts +49 -0
- package/src/ProgressCircle/utils/progressCircle.constants.ts +27 -0
- package/src/ProgressSteps/ProgressSteps.tsx +160 -0
- package/src/SaveConfetti/SaveConfetti.tsx +277 -0
- package/src/SaveConfetti/index.ts +2 -0
- package/src/SearchInput/SearchInput.tsx +236 -0
- package/src/SegmentedControl/SegmentedControl.tsx +111 -0
- package/src/SegmentedControl/SegmentedControlGroup.tsx +94 -0
- package/src/SegmentedControl/index.ts +4 -0
- package/src/Select/Select.tsx +154 -0
- package/src/Select/SelectAsync.tsx +13 -0
- package/src/Select/SelectAsyncCreatable.tsx +14 -0
- package/src/Select/SelectCreatable.tsx +12 -0
- package/src/Select/reexportedComponents.tsx +5 -0
- package/src/Select/selectComponents.tsx +306 -0
- package/src/Skeleton/Skeleton.tsx +13 -0
- package/src/SplitButton/SplitButton.tsx +298 -0
- package/src/SplitButton/SplitButtonTrigger.tsx +55 -0
- package/src/StarRating/StarRating.tsx +165 -0
- package/src/StarRating/components/StarComponent/StarComponent.tsx +44 -0
- package/src/Table/Table.tsx +474 -0
- package/src/Table/components/TableHeader/TableHeader.tsx +166 -0
- package/src/Table/components/TablePagination/TablePagination.tsx +18 -0
- package/src/Table/components/TablePagination/components/ItemCountDisplay/ItemCountDisplay.tsx +17 -0
- package/src/Table/components/TablePagination/components/ItemCountSelect/ItemCountSelect.tsx +31 -0
- package/src/Table/components/TablePagination/components/TablePaginator/TablePaginator.tsx +37 -0
- package/src/Table/components/TablePagination/components/TablePaginator/components/PageSelect/PageSelect.tsx +32 -0
- package/src/Table/components/TableRow/TableRows.tsx +147 -0
- package/src/Table/components/TableRow/components/TableRowCell/TableRowCell.tsx +49 -0
- package/src/Table/components/TableShadow/TableShadow.tsx +46 -0
- package/src/Table/index.ts +2 -0
- package/src/Table/sharedState.ts +30 -0
- package/src/Table/sharedStyles.ts +1 -0
- package/src/Table/types.ts +55 -0
- package/src/Tabs/Tabs.tsx +358 -0
- package/src/TextArea/TextArea.tsx +157 -0
- package/src/TextInput/TextInput.tsx +173 -0
- package/src/Toast/Toast.tsx +64 -0
- package/src/Toast/components/ToastContainer/ToastContainer.tsx +48 -0
- package/src/Toast/index.ts +2 -0
- package/src/Toast/types/role.ts +1 -0
- package/src/Toast/utils/getIcon.tsx +41 -0
- package/src/ToggleSwitch/ToggleSwitch.tsx +188 -0
- package/src/Tooltip/InfoIconTooltip.tsx +47 -0
- package/src/Tooltip/Tooltip.tsx +41 -0
- package/src/Typography/InlineButton/InlineButton.tsx +166 -0
- package/src/Typography/Links/Links.tsx +113 -0
- package/src/Typography/Text/Text.tsx +239 -0
- package/src/ViewMore/ViewMore.tsx +226 -0
- package/src/WebRTCPlayer/WebRTCPlayer.tsx +325 -0
- package/src/WebRTCPlayer/adaptor/NativeWebRTCAdaptor.ts +588 -0
- package/src/WebRTCPlayer/hooks/useWebRTCStream.ts +571 -0
- package/src/WebRTCPlayer/index.ts +22 -0
- package/src/WebRTCPlayer/types.ts +95 -0
- package/src/breakpoints/index.tsx +1 -0
- package/src/breakpoints/utils.tsx +50 -0
- package/src/declarations.d.ts +7 -0
- package/src/grid/gridHighlighter.tsx +83 -0
- package/src/grid/index.ts +3 -0
- package/src/grid/pageGrid.tsx +37 -0
- package/src/grid/useSubGridCSS.tsx +214 -0
- package/src/hooks/useHorizontalScrollFade.ts +44 -0
- package/src/hooks/usePrevious.ts +14 -0
- package/src/index.ts +351 -0
- package/src/intl/index.ts +4 -0
- package/src/intl/utils.ts +67 -0
- package/src/mixins/horizontalScrollFadeMixin.ts +56 -0
- package/src/mixins/index.ts +18 -0
- package/src/mixins/propMixins.ts +389 -0
- package/src/providers/createContextProvider.ts +22 -0
- package/src/providers/designSystemProvider.tsx +218 -0
- package/src/providers/errorBoundaryProvider.tsx +179 -0
- package/src/providers/featureFlagProvider.tsx +18 -0
- package/src/providers/gridSizeProvider.tsx +23 -0
- package/src/providers/index.ts +12 -0
- package/src/providers/linkProvider.tsx +32 -0
- package/src/providers/normalize.tsx +93 -0
- package/src/providers/targets/DatePickerTarget.tsx +10 -0
- package/src/providers/targets/ModalTarget.tsx +14 -0
- package/src/providers/targets/TimePickerTarget.tsx +10 -0
- package/src/providers/themeContext.tsx +24 -0
- package/src/providers/windowSizeProvider.tsx +125 -0
- package/src/spacing/index.ts +1 -0
- package/src/spacing/spacing.ts +11 -0
- package/src/utils/countriesWithISOCodes.ts +1419 -0
- package/src/utils/dateTimeFormat.ts +257 -0
- package/src/utils/effectEventListener.ts +11 -0
- package/src/utils/formatFileSize.ts +18 -0
- package/src/utils/isClientSide.ts +1 -0
- package/src/utils/keepTransition.ts +15 -0
- package/src/utils/nodeEnv.ts +9 -0
- package/src/utils/smoothScrollTo.ts +47 -0
- package/src/utils/utilityTypes.ts +13 -0
- package/textInput.d.cts +1 -1
- package/textInput.d.mts +1 -1
- package/timePicker.d.cts +1 -1
- package/timePicker.d.mts +1 -1
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { cn } from '@evolve.ui/theme-system';
|
|
2
|
+
import { debounce } from 'es-toolkit';
|
|
3
|
+
import {
|
|
4
|
+
KeyboardEventHandler,
|
|
5
|
+
MouseEvent,
|
|
6
|
+
ReactNode,
|
|
7
|
+
UIEventHandler,
|
|
8
|
+
useCallback,
|
|
9
|
+
useEffect,
|
|
10
|
+
useMemo,
|
|
11
|
+
useRef,
|
|
12
|
+
useState,
|
|
13
|
+
} from 'react';
|
|
14
|
+
import useMeasure from 'react-use-measure';
|
|
15
|
+
|
|
16
|
+
import { CaretRight } from '../Icons';
|
|
17
|
+
import { useWindowIsDesktop } from '../providers';
|
|
18
|
+
import { SubtleLink } from '../Typography/Links/Links';
|
|
19
|
+
import {
|
|
20
|
+
CAROUSEL_CARD_GAP_MOBILE_NUM,
|
|
21
|
+
CAROUSEL_CARD_GAP_NUM,
|
|
22
|
+
CarouselCard,
|
|
23
|
+
CarouselGoToPageDirection,
|
|
24
|
+
CarouselGoToPageInfo,
|
|
25
|
+
CarouselPageInfo,
|
|
26
|
+
} from './Carousel.types';
|
|
27
|
+
import { calculatePageBreakpoints } from './Carousel.utils';
|
|
28
|
+
import { DotButton } from './components/DotButton/DotButton';
|
|
29
|
+
import { NavigationButtons } from './components/NavigationButtons/NavigationButtons';
|
|
30
|
+
import { TitleSection } from './components/TitleSection/TitleSection';
|
|
31
|
+
|
|
32
|
+
const SCROLL_DEBOUNCE_TIME = 50;
|
|
33
|
+
|
|
34
|
+
type Props = {
|
|
35
|
+
/**
|
|
36
|
+
* A Link element that a user can interact with.
|
|
37
|
+
* Usually, this will be something like a "View all" that links to a page with all the items that the carousel represents.
|
|
38
|
+
*
|
|
39
|
+
* It will appear on the right side, opposite of the title, if one exists
|
|
40
|
+
*
|
|
41
|
+
* @property {string} path - Where the user will be taken upon clicking the action
|
|
42
|
+
* @property {string} text="View All" - The text to be displayed in the action element
|
|
43
|
+
*/
|
|
44
|
+
action?: {
|
|
45
|
+
hideIcon?: boolean;
|
|
46
|
+
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Where the user will be taken upon clicking the action
|
|
49
|
+
*/
|
|
50
|
+
path: string;
|
|
51
|
+
/**
|
|
52
|
+
* The text to be displayed in the action element
|
|
53
|
+
*
|
|
54
|
+
* @default "View All"
|
|
55
|
+
*/
|
|
56
|
+
text?: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* If provided, the cards, barring the first card, will be offset so that some of the previous card will be shown on the left.
|
|
60
|
+
* This is best used for mobile carousels so that people can better see that they're in the middle of a carousel.
|
|
61
|
+
* It should be a positive number, representing a pixel amount.
|
|
62
|
+
* Gap size will be added to it, so make sure you aren't including it.
|
|
63
|
+
*
|
|
64
|
+
* @default 0
|
|
65
|
+
*/
|
|
66
|
+
cardOffset?: number;
|
|
67
|
+
/**
|
|
68
|
+
* The scrollable content of the carousel.
|
|
69
|
+
* Should be individual elements, likes cards or images.
|
|
70
|
+
*
|
|
71
|
+
* @property {ReactNode} card - The rendered content
|
|
72
|
+
* @property {string} key - A unique identifier for the card
|
|
73
|
+
*/
|
|
74
|
+
cards: CarouselCard[];
|
|
75
|
+
/**
|
|
76
|
+
* Are the cards taking up a full page of the carousel?
|
|
77
|
+
* This is used to use card counts to render the page-based sections of the carousel for SSR.
|
|
78
|
+
*/
|
|
79
|
+
fullPage?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Bring the slider to the selected page. Only one property will be defined, so components using this function should check
|
|
82
|
+
* which one is populated.
|
|
83
|
+
*
|
|
84
|
+
* @property {CarouselGoToPageDirection} buttonDirection - If populated, refers to which button was pressed: Previous or Next
|
|
85
|
+
* @property {number} oneBasedPageNumber - If populated, refers to which page the user is specifically going to
|
|
86
|
+
*
|
|
87
|
+
* @see CarouselGoToPageInfo
|
|
88
|
+
*/
|
|
89
|
+
goToPage?: (goToPageInfo: CarouselGoToPageInfo) => void;
|
|
90
|
+
/**
|
|
91
|
+
* Callback triggered when the user scrolls through the carousel manually (either through mouse wheel or dragging)
|
|
92
|
+
*/
|
|
93
|
+
onScroll?: (currentScrollPage: number) => void;
|
|
94
|
+
/**
|
|
95
|
+
* @returns The number of pages for the carousel
|
|
96
|
+
*/
|
|
97
|
+
providePageCount?: (numberOfPages: number) => void;
|
|
98
|
+
/**
|
|
99
|
+
* The selected page to scroll to directly
|
|
100
|
+
*/
|
|
101
|
+
selectedPage?: number;
|
|
102
|
+
/**
|
|
103
|
+
* If the user scrolls, they're automatically brought to a specific page breakpoint.
|
|
104
|
+
* Set this to `false` to avoid further scrolling after the user has stopped scrolling through the carousel.
|
|
105
|
+
*
|
|
106
|
+
* @default true
|
|
107
|
+
*/
|
|
108
|
+
shouldGoToPageBreakpointsOnScrolling?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Displays dots at the bottom of the carousel that represent a page of content.
|
|
111
|
+
* Clicking a dot will bring the user to the corresponding page.
|
|
112
|
+
* Mobile devices will NEVER show the page selector.
|
|
113
|
+
*
|
|
114
|
+
* @default false
|
|
115
|
+
*/
|
|
116
|
+
showPagination?: boolean;
|
|
117
|
+
subHeaderDescription?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Left side content for the carousel subheader - can be either a link or plain text.
|
|
120
|
+
*/
|
|
121
|
+
subHeaderLeft?:
|
|
122
|
+
| {
|
|
123
|
+
link: {
|
|
124
|
+
ariaLabel?: string;
|
|
125
|
+
href: string;
|
|
126
|
+
label: string;
|
|
127
|
+
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
128
|
+
variant?: 'default' | 'subtle';
|
|
129
|
+
};
|
|
130
|
+
type: 'link';
|
|
131
|
+
}
|
|
132
|
+
| { text: string; type: 'text' };
|
|
133
|
+
/**
|
|
134
|
+
* The title of the carousel, display above and to the left of the carousel.
|
|
135
|
+
* Even though it's a ReactNode, this is for formatMessage/other intl-based libraries
|
|
136
|
+
* to pass in populated values. You should pass in a value that is string-based, due
|
|
137
|
+
* to the carousel variant formatting the title as an h2 or h5.
|
|
138
|
+
*
|
|
139
|
+
* Optional.
|
|
140
|
+
*
|
|
141
|
+
* @variation primary - If carousel is primary, title is rendered as an H2
|
|
142
|
+
* @variation secondary - If carousel is secondary, title is rendered as an H5
|
|
143
|
+
*
|
|
144
|
+
* @see {variant}
|
|
145
|
+
*/
|
|
146
|
+
title?: ReactNode;
|
|
147
|
+
/**
|
|
148
|
+
* Link configuration for the title - when provided, title becomes clickable with hover underline
|
|
149
|
+
*/
|
|
150
|
+
titleLink?: {
|
|
151
|
+
/** Accessible label for the title link */
|
|
152
|
+
ariaLabel?: string;
|
|
153
|
+
/** URL for the title link */
|
|
154
|
+
href: string;
|
|
155
|
+
/** Click handler for the title link */
|
|
156
|
+
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* If true, the carousel will use overlay controls to navigate to the next and previous pages.
|
|
160
|
+
*
|
|
161
|
+
* @default false
|
|
162
|
+
*/
|
|
163
|
+
useOverlayControls?: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* @variation {primary} - A primary carousel displays important content relevant to the page. Should only have one per page.
|
|
166
|
+
* @variation {secondary} - A secondary carousel display content of lesser importance to the user. Can have multiple per page.
|
|
167
|
+
*
|
|
168
|
+
* @default primary
|
|
169
|
+
*/
|
|
170
|
+
variant?: 'primary' | 'secondary';
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Displays a scrollable list of cards or elements horizontally.
|
|
175
|
+
* Allows navigation via buttons, dots (pagination), or touch gestures (on mobile).
|
|
176
|
+
*/
|
|
177
|
+
export const Carousel = ({
|
|
178
|
+
action,
|
|
179
|
+
cardOffset = 0,
|
|
180
|
+
cards,
|
|
181
|
+
fullPage = false,
|
|
182
|
+
goToPage,
|
|
183
|
+
onScroll,
|
|
184
|
+
providePageCount,
|
|
185
|
+
selectedPage,
|
|
186
|
+
shouldGoToPageBreakpointsOnScrolling = true,
|
|
187
|
+
showPagination = false,
|
|
188
|
+
subHeaderDescription,
|
|
189
|
+
subHeaderLeft,
|
|
190
|
+
title,
|
|
191
|
+
titleLink,
|
|
192
|
+
useOverlayControls,
|
|
193
|
+
variant = 'primary',
|
|
194
|
+
}: Props) => {
|
|
195
|
+
const isDesktop = useWindowIsDesktop();
|
|
196
|
+
const [containerRef, { width }] = useMeasure();
|
|
197
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
198
|
+
|
|
199
|
+
const actionElement = useMemo(() => {
|
|
200
|
+
if (action) {
|
|
201
|
+
const { hideIcon = false, onClick, path, text = 'View All' } = action;
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<SubtleLink
|
|
205
|
+
className="flex items-center gap-2xs"
|
|
206
|
+
href={path}
|
|
207
|
+
onClick={onClick}
|
|
208
|
+
>
|
|
209
|
+
{text} {!hideIcon && <CaretRight size="sm" />}
|
|
210
|
+
</SubtleLink>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return undefined;
|
|
214
|
+
}, [action]);
|
|
215
|
+
|
|
216
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
217
|
+
const [shouldGoToPageBreakpoint, setShouldGoToPageBreakpoint] = useState(false);
|
|
218
|
+
// Prevents onScroll prop from firing during programmatic scrollTo (e.g. AutomatedCarousel autoplay),
|
|
219
|
+
// which would otherwise pause autoplay by triggering the parent's scroll handler.
|
|
220
|
+
const isProgrammaticScroll = useRef(false);
|
|
221
|
+
const cardRefs = useRef<(HTMLDivElement | null)[]>([]);
|
|
222
|
+
const [pageInfo, setPageInfo] = useState<CarouselPageInfo>({
|
|
223
|
+
breakpoints: [0],
|
|
224
|
+
cardsPerPage: 0,
|
|
225
|
+
numberOfPages: 1,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Ensure the ref array has the correct size when the number of cards changes.
|
|
230
|
+
*/
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
cardRefs.current = cardRefs.current.slice(0, cards.length);
|
|
233
|
+
}, [cards.length]);
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* These cards are wrapped in a div.
|
|
237
|
+
* The div is guaranteed to have a unique key.
|
|
238
|
+
* The div also has a ref used to control the scrolling animation.
|
|
239
|
+
*/
|
|
240
|
+
const carouselCards = useMemo(() => {
|
|
241
|
+
return cards.map(({ card, key }, index) => (
|
|
242
|
+
<div
|
|
243
|
+
className={cn('backface-hidden transform-gpu will-change-transform xsMax:shrink-0', {
|
|
244
|
+
'xsMax:pl-sm': index === 0,
|
|
245
|
+
'xsMax:pr-sm': index === cards.length - 1,
|
|
246
|
+
})}
|
|
247
|
+
key={key}
|
|
248
|
+
ref={(el) => {
|
|
249
|
+
// Directly assign the element or null to the ref array at the correct index
|
|
250
|
+
cardRefs.current[index] = el;
|
|
251
|
+
}}
|
|
252
|
+
role="listitem"
|
|
253
|
+
>
|
|
254
|
+
{card}
|
|
255
|
+
</div>
|
|
256
|
+
));
|
|
257
|
+
}, [cards]);
|
|
258
|
+
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
const allRefsPopulated =
|
|
261
|
+
cardRefs.current.length === cards.length && cardRefs.current.every((ref) => ref !== null);
|
|
262
|
+
|
|
263
|
+
if (cards.length > 0 && contentRef.current && allRefsPopulated) {
|
|
264
|
+
const gap = isDesktop ? CAROUSEL_CARD_GAP_NUM : CAROUSEL_CARD_GAP_MOBILE_NUM;
|
|
265
|
+
let contentTotalWidth = 0;
|
|
266
|
+
|
|
267
|
+
const cumulativeWidths = cards.map((_, index) => {
|
|
268
|
+
const ref = cardRefs.current[index];
|
|
269
|
+
if (!ref) {
|
|
270
|
+
return contentTotalWidth;
|
|
271
|
+
}
|
|
272
|
+
const isLastCard = index === cards.length - 1;
|
|
273
|
+
contentTotalWidth += ref.offsetWidth + (isLastCard ? 0 : gap);
|
|
274
|
+
return contentTotalWidth;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const scrollAreaWidth = contentRef.current.offsetWidth;
|
|
278
|
+
const newPageInfo = calculatePageBreakpoints(cumulativeWidths, scrollAreaWidth, contentTotalWidth, gap);
|
|
279
|
+
setPageInfo(newPageInfo);
|
|
280
|
+
providePageCount?.(newPageInfo.numberOfPages);
|
|
281
|
+
} else if (cards.length === 0 || !contentRef.current) {
|
|
282
|
+
setPageInfo({ breakpoints: [0], cardsPerPage: 0, numberOfPages: 1 });
|
|
283
|
+
providePageCount?.(1);
|
|
284
|
+
}
|
|
285
|
+
}, [width, cards, providePageCount, isDesktop]);
|
|
286
|
+
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
if (currentPage > pageInfo.numberOfPages) {
|
|
289
|
+
setCurrentPage(pageInfo.numberOfPages);
|
|
290
|
+
setShowPreviousPageButton(pageInfo.numberOfPages > 1);
|
|
291
|
+
}
|
|
292
|
+
}, [currentPage, pageInfo.numberOfPages]);
|
|
293
|
+
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
if (!shouldGoToPageBreakpoint || !contentRef.current || typeof contentRef.current.scrollTo !== 'function') {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const scrollToPoint = pageInfo.breakpoints[currentPage - 1];
|
|
299
|
+
if (scrollToPoint === undefined) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
let adjustedPoint = scrollToPoint;
|
|
303
|
+
if (cardOffset) {
|
|
304
|
+
const gapSize = isDesktop ? CAROUSEL_CARD_GAP_NUM : CAROUSEL_CARD_GAP_MOBILE_NUM;
|
|
305
|
+
adjustedPoint -= cardOffset + gapSize;
|
|
306
|
+
}
|
|
307
|
+
requestAnimationFrame(() => {
|
|
308
|
+
contentRef.current?.scrollTo({
|
|
309
|
+
behavior: 'smooth',
|
|
310
|
+
left: adjustedPoint,
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
}, [cardOffset, currentPage, isDesktop, pageInfo.breakpoints, shouldGoToPageBreakpoint]);
|
|
314
|
+
|
|
315
|
+
const onClickPageDotButton = useCallback((newPage: number) => {
|
|
316
|
+
setShouldGoToPageBreakpoint(true);
|
|
317
|
+
setCurrentPage(newPage);
|
|
318
|
+
setShowPreviousPageButton(newPage > 1);
|
|
319
|
+
}, []);
|
|
320
|
+
|
|
321
|
+
const pageDots = useMemo(() => {
|
|
322
|
+
if (pageInfo.numberOfPages === 1) {
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
const buttons = [];
|
|
326
|
+
for (let i = 1; i <= pageInfo.numberOfPages; i++) {
|
|
327
|
+
buttons.push(
|
|
328
|
+
<DotButton
|
|
329
|
+
isActive={currentPage === i}
|
|
330
|
+
key={i}
|
|
331
|
+
onClick={() => {
|
|
332
|
+
onClickPageDotButton(i);
|
|
333
|
+
}}
|
|
334
|
+
pageNumber={i}
|
|
335
|
+
/>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return buttons;
|
|
339
|
+
}, [currentPage, onClickPageDotButton, pageInfo.numberOfPages]);
|
|
340
|
+
|
|
341
|
+
// Manually synced in goToNextPage, goToPreviousPage, onClickPageDotButton,
|
|
342
|
+
// selectedPage effect, and onScrollCards. This duplication is intentional:
|
|
343
|
+
// deriving it purely from currentPage would lose the scroll-position-based
|
|
344
|
+
// "show back button when any manual scroll has happened" behavior, and deriving
|
|
345
|
+
// it purely from the scroll handler would cause button flicker during the 50ms
|
|
346
|
+
// debounce window between setCurrentPage and the resulting scroll event.
|
|
347
|
+
const [showPreviousPageButton, setShowPreviousPageButton] = useState(false);
|
|
348
|
+
|
|
349
|
+
const goToPreviousPage = useCallback(() => {
|
|
350
|
+
if (currentPage > 1) {
|
|
351
|
+
setShouldGoToPageBreakpoint(true);
|
|
352
|
+
setCurrentPage(currentPage - 1);
|
|
353
|
+
goToPage?.({
|
|
354
|
+
buttonDirection: CarouselGoToPageDirection.Previous,
|
|
355
|
+
});
|
|
356
|
+
} else if (contentRef.current) {
|
|
357
|
+
contentRef.current.scrollLeft = 0;
|
|
358
|
+
goToPage?.({ oneBasedPageNumber: 1 });
|
|
359
|
+
}
|
|
360
|
+
setShowPreviousPageButton(currentPage - 1 > 1);
|
|
361
|
+
}, [currentPage, goToPage]);
|
|
362
|
+
|
|
363
|
+
const goToNextPage = useCallback(() => {
|
|
364
|
+
if (currentPage >= pageInfo.numberOfPages) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
setShouldGoToPageBreakpoint(true);
|
|
368
|
+
setCurrentPage(currentPage + 1);
|
|
369
|
+
setShowPreviousPageButton(true);
|
|
370
|
+
goToPage?.({
|
|
371
|
+
buttonDirection: CarouselGoToPageDirection.Next,
|
|
372
|
+
});
|
|
373
|
+
}, [currentPage, goToPage, pageInfo.numberOfPages]);
|
|
374
|
+
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
if (selectedPage) {
|
|
377
|
+
isProgrammaticScroll.current = true;
|
|
378
|
+
setShouldGoToPageBreakpoint(true);
|
|
379
|
+
setCurrentPage(selectedPage);
|
|
380
|
+
setShowPreviousPageButton(selectedPage > 1);
|
|
381
|
+
}
|
|
382
|
+
}, [selectedPage]);
|
|
383
|
+
|
|
384
|
+
const clearProgrammaticScrollFlag = useCallback(() => {
|
|
385
|
+
isProgrammaticScroll.current = false;
|
|
386
|
+
}, []);
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Handles keyboard navigation for the carousel.
|
|
390
|
+
* Allows using Left and Right arrow keys to navigate between pages.
|
|
391
|
+
*/
|
|
392
|
+
const onKeyNavigation: KeyboardEventHandler = useCallback(
|
|
393
|
+
(event) => {
|
|
394
|
+
if (event.key === 'ArrowRight') {
|
|
395
|
+
goToNextPage();
|
|
396
|
+
} else if (event.key === 'ArrowLeft') {
|
|
397
|
+
goToPreviousPage();
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
[goToNextPage, goToPreviousPage]
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Handles scroll events within the carousel content area.
|
|
405
|
+
* Updates the current page and visibility of navigation buttons based on scroll position.
|
|
406
|
+
*/
|
|
407
|
+
const onScrollCards: UIEventHandler<HTMLDivElement> = useCallback(() => {
|
|
408
|
+
if (!contentRef.current || isProgrammaticScroll.current) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const { clientWidth, scrollLeft, scrollWidth } = contentRef.current;
|
|
412
|
+
setShowPreviousPageButton(scrollLeft > 0);
|
|
413
|
+
setShouldGoToPageBreakpoint(false);
|
|
414
|
+
|
|
415
|
+
const isShowingAllOfLastPage = scrollWidth - scrollLeft <= clientWidth;
|
|
416
|
+
|
|
417
|
+
let resolvedPage: number;
|
|
418
|
+
if (isShowingAllOfLastPage) {
|
|
419
|
+
resolvedPage = pageInfo.numberOfPages;
|
|
420
|
+
} else {
|
|
421
|
+
// Find the last breakpoint the scroll position has reached or passed.
|
|
422
|
+
// breakpoints are sorted ascending; walk backwards for simplicity.
|
|
423
|
+
let pageIndex = 0;
|
|
424
|
+
for (let i = pageInfo.breakpoints.length - 1; i >= 0; i--) {
|
|
425
|
+
if (pageInfo.breakpoints[i] <= scrollLeft) {
|
|
426
|
+
pageIndex = i;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
resolvedPage = pageIndex + 1;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (shouldGoToPageBreakpointsOnScrolling) {
|
|
434
|
+
setCurrentPage(resolvedPage);
|
|
435
|
+
goToPage?.({ oneBasedPageNumber: resolvedPage });
|
|
436
|
+
} else {
|
|
437
|
+
onScroll?.(resolvedPage);
|
|
438
|
+
}
|
|
439
|
+
}, [goToPage, onScroll, pageInfo, shouldGoToPageBreakpointsOnScrolling]);
|
|
440
|
+
|
|
441
|
+
const debouncedOnScrollCards = useMemo(() => debounce(onScrollCards, SCROLL_DEBOUNCE_TIME), [onScrollCards]);
|
|
442
|
+
|
|
443
|
+
useEffect(() => {
|
|
444
|
+
return () => debouncedOnScrollCards.cancel();
|
|
445
|
+
}, [debouncedOnScrollCards]);
|
|
446
|
+
|
|
447
|
+
const showNextPageButton =
|
|
448
|
+
currentPage < pageInfo.numberOfPages ||
|
|
449
|
+
// only use fullPage if the pageInfo is not yet populated
|
|
450
|
+
(fullPage && pageInfo.numberOfPages === 1 && cards.length > 1);
|
|
451
|
+
|
|
452
|
+
return (
|
|
453
|
+
<section
|
|
454
|
+
className={cn('relative w-full')}
|
|
455
|
+
ref={containerRef}
|
|
456
|
+
>
|
|
457
|
+
<div className={cn('flex items-end justify-between')}>
|
|
458
|
+
<TitleSection
|
|
459
|
+
subHeaderDescription={subHeaderDescription}
|
|
460
|
+
subHeaderLeft={subHeaderLeft}
|
|
461
|
+
title={title}
|
|
462
|
+
titleLink={titleLink}
|
|
463
|
+
variant={variant}
|
|
464
|
+
/>
|
|
465
|
+
<div className="flex h-full items-center gap-xs">
|
|
466
|
+
{actionElement}
|
|
467
|
+
<NavigationButtons
|
|
468
|
+
goToNextPage={goToNextPage}
|
|
469
|
+
goToPreviousPage={goToPreviousPage}
|
|
470
|
+
showNextPageButton={showNextPageButton}
|
|
471
|
+
showPreviousPageButton={showPreviousPageButton}
|
|
472
|
+
useOverlayControls={useOverlayControls}
|
|
473
|
+
variant={variant}
|
|
474
|
+
/>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
{/* IF YOU CHANGE GAP BETWEEN CARDS HERE, YOU MUST ALSO ADJUST THE BREAKPOINT CALCULATION FORMULA TO ACCOUNT FOR THAT GAP */}
|
|
478
|
+
<div className={cn('mt-sm w-full overflow-hidden xsMax:-mx-sm xsMax:w-[calc(100%+32px)]')}>
|
|
479
|
+
{/* onKeyUp is used to arrow key right and left to pages */}
|
|
480
|
+
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
|
|
481
|
+
<div
|
|
482
|
+
aria-label={`Slide ${currentPage} of ${pageInfo.numberOfPages}`}
|
|
483
|
+
aria-live="polite"
|
|
484
|
+
className={cn(
|
|
485
|
+
'flex gap-md overflow-y-hidden overflow-x-scroll xsMax:gap-sm',
|
|
486
|
+
'xsMax:snap-x xsMax:snap-mandatory xsMax:scroll-pl-sm xsMax:scroll-pr-sm',
|
|
487
|
+
'backface-hidden transform-gpu will-change-scroll contain-layout'
|
|
488
|
+
)}
|
|
489
|
+
onKeyUp={onKeyNavigation}
|
|
490
|
+
onScroll={debouncedOnScrollCards}
|
|
491
|
+
onTouchMoveCapture={clearProgrammaticScrollFlag}
|
|
492
|
+
onWheelCapture={clearProgrammaticScrollFlag}
|
|
493
|
+
ref={contentRef}
|
|
494
|
+
role="list"
|
|
495
|
+
style={{
|
|
496
|
+
scrollbarWidth: 'none',
|
|
497
|
+
}}
|
|
498
|
+
>
|
|
499
|
+
{carouselCards}
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
{showPagination && (
|
|
503
|
+
<div
|
|
504
|
+
aria-label={`${title} carousel page selector`}
|
|
505
|
+
className={cn('mt-sm flex justify-center gap-xs')}
|
|
506
|
+
role="tablist"
|
|
507
|
+
>
|
|
508
|
+
{pageDots}
|
|
509
|
+
</div>
|
|
510
|
+
)}
|
|
511
|
+
</section>
|
|
512
|
+
);
|
|
513
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type CarouselCard = {
|
|
4
|
+
/**
|
|
5
|
+
* The rendered content of the card
|
|
6
|
+
*/
|
|
7
|
+
card: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* The unique identifier for the card
|
|
10
|
+
*/
|
|
11
|
+
key: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type CarouselPageInfo = {
|
|
15
|
+
breakpoints: number[];
|
|
16
|
+
cardsPerPage: number;
|
|
17
|
+
numberOfPages: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// this is the px size of the MD gap
|
|
21
|
+
export const CAROUSEL_CARD_GAP_NUM = 24;
|
|
22
|
+
export const CAROUSEL_CARD_GAP_MOBILE_NUM = 16;
|
|
23
|
+
|
|
24
|
+
export enum CarouselGoToPageDirection {
|
|
25
|
+
Previous = -1,
|
|
26
|
+
Next = 1,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Page navigation information based on the action the user took.
|
|
31
|
+
*
|
|
32
|
+
* @property {CarouselGoToPageDirection} buttonDirection - Populated if the user clicked the Next or Previous buttons
|
|
33
|
+
* @property {number} oneBasedPageNumber - The user navigated between cards by scrolling/dragging manually
|
|
34
|
+
*/
|
|
35
|
+
export type CarouselGoToPageInfo =
|
|
36
|
+
| {
|
|
37
|
+
buttonDirection: CarouselGoToPageDirection;
|
|
38
|
+
oneBasedPageNumber?: never;
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
buttonDirection?: never;
|
|
42
|
+
oneBasedPageNumber: number;
|
|
43
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CarouselPageInfo } from './Carousel.types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure function that calculates page breakpoints from cumulative card positions.
|
|
5
|
+
*
|
|
6
|
+
* @param cumulativeWidths - Array of cumulative widths (including inter-card gaps) for each card.
|
|
7
|
+
* Each entry is the total width from the start through that card, including a trailing gap
|
|
8
|
+
* for every card except the last.
|
|
9
|
+
* @param scrollAreaWidth - The visible width of the scroll container.
|
|
10
|
+
* @param contentTotalWidth - The total width of all cards and gaps combined.
|
|
11
|
+
* @param gap - The gap size (in px) between cards.
|
|
12
|
+
*/
|
|
13
|
+
export const calculatePageBreakpoints = (
|
|
14
|
+
cumulativeWidths: number[],
|
|
15
|
+
scrollAreaWidth: number,
|
|
16
|
+
contentTotalWidth: number,
|
|
17
|
+
gap: number
|
|
18
|
+
): CarouselPageInfo => {
|
|
19
|
+
if (cumulativeWidths.length === 0 || scrollAreaWidth <= 0) {
|
|
20
|
+
return { breakpoints: [0], cardsPerPage: 0, numberOfPages: 1 };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const cardsPerPageCounts: number[] = [];
|
|
24
|
+
// Starts at 1 because the first card (index 0) is already on the current page;
|
|
25
|
+
// the loop below begins at index 1.
|
|
26
|
+
let currentCardsPerPage = 1;
|
|
27
|
+
let numberOfPages = 1;
|
|
28
|
+
const pageBreakpoints = [0];
|
|
29
|
+
let pageStartCumulative = 0;
|
|
30
|
+
|
|
31
|
+
for (let index = 1; index < cumulativeWidths.length; index++) {
|
|
32
|
+
// Subtract the trailing gap so we measure only the visual width of cards + inner gaps on this page.
|
|
33
|
+
// The last card in the input never has a trailing gap, but intermediate cards do.
|
|
34
|
+
const isLastCard = index === cumulativeWidths.length - 1;
|
|
35
|
+
const trailingGap = isLastCard ? 0 : gap;
|
|
36
|
+
const pageWidth = cumulativeWidths[index] - pageStartCumulative - trailingGap;
|
|
37
|
+
|
|
38
|
+
if (pageWidth > scrollAreaWidth) {
|
|
39
|
+
numberOfPages++;
|
|
40
|
+
pageBreakpoints.push(cumulativeWidths[index - 1]);
|
|
41
|
+
cardsPerPageCounts.push(currentCardsPerPage);
|
|
42
|
+
pageStartCumulative = cumulativeWidths[index - 1];
|
|
43
|
+
currentCardsPerPage = 1;
|
|
44
|
+
} else {
|
|
45
|
+
currentCardsPerPage++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Include the last page's card count in the max calculation
|
|
50
|
+
cardsPerPageCounts.push(currentCardsPerPage);
|
|
51
|
+
|
|
52
|
+
const lastBreakpoint = pageBreakpoints[pageBreakpoints.length - 1] || 0;
|
|
53
|
+
if (contentTotalWidth - lastBreakpoint > scrollAreaWidth) {
|
|
54
|
+
numberOfPages++;
|
|
55
|
+
pageBreakpoints.push(contentTotalWidth - scrollAreaWidth);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
breakpoints: pageBreakpoints,
|
|
60
|
+
cardsPerPage: Math.max(...cardsPerPageCounts),
|
|
61
|
+
numberOfPages,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cn } from '@evolve.ui/theme-system';
|
|
2
|
+
|
|
3
|
+
import { Tooltip } from '../../../Tooltip/Tooltip';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
pageNumber: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const DotButton = ({ isActive, onClick, pageNumber }: Props) => {
|
|
12
|
+
const baseStyles = cn('h-xs w-xs rounded-full border-none hover:cursor-pointer');
|
|
13
|
+
const styles = isActive
|
|
14
|
+
? cn(baseStyles, 'bg-text-primary hover:bg-text-secondary')
|
|
15
|
+
: cn(baseStyles, 'bg-text-inactive hover:bg-text-secondary');
|
|
16
|
+
// Generate deterministic ID from pageNumber for SSR compatibility
|
|
17
|
+
const tooltipId = `carousel-dot-page-${pageNumber}`;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
{/*
|
|
22
|
+
this uses a regular HTML button because our Button component doesn't get small enough to be completely circular.
|
|
23
|
+
this is solving an edgecase and this pattern should be avoided for any other kind of button
|
|
24
|
+
*/}
|
|
25
|
+
<button
|
|
26
|
+
aria-controls="carousel-track"
|
|
27
|
+
aria-label={`Page ${pageNumber}`}
|
|
28
|
+
aria-selected={isActive}
|
|
29
|
+
className={styles}
|
|
30
|
+
data-testid="dot-button"
|
|
31
|
+
data-tooltip-id={tooltipId}
|
|
32
|
+
onClick={onClick}
|
|
33
|
+
role="tab"
|
|
34
|
+
/>
|
|
35
|
+
<Tooltip
|
|
36
|
+
content={`Page ${pageNumber}`}
|
|
37
|
+
delayShow={50}
|
|
38
|
+
id={tooltipId}
|
|
39
|
+
/>
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
};
|