pixelize-design-library 2.4.2-beta.55 → 2.4.2-beta.57
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/CHANGELOG.md +64 -0
- package/COMPONENT-GOTCHAS.md +50 -22
- package/DESIGN-GUIDE.md +19 -2
- package/dist/Components/AddressCard/AddressCard.d.ts +4 -0
- package/dist/Components/AddressCard/AddressCard.js +25 -0
- package/dist/Components/AddressCard/AddressCardGroup.d.ts +4 -0
- package/dist/Components/AddressCard/AddressCardGroup.js +7 -0
- package/dist/Components/AddressCard/AddressCardProps.d.ts +43 -0
- package/dist/Components/AddressCard/AddressCardProps.js +2 -0
- package/dist/Components/BagBar/BagBar.d.ts +4 -0
- package/dist/Components/BagBar/BagBar.js +32 -0
- package/dist/Components/BagBar/BagBarProps.d.ts +30 -0
- package/dist/Components/BagBar/BagBarProps.js +2 -0
- package/dist/Components/CartItem/CartItem.d.ts +4 -0
- package/dist/Components/CartItem/CartItem.js +60 -0
- package/dist/Components/CartItem/CartItemProps.d.ts +64 -0
- package/dist/Components/CartItem/CartItemProps.js +2 -0
- package/dist/Components/CartItem/cartItemOptions.d.ts +3 -0
- package/dist/Components/CartItem/cartItemOptions.js +13 -0
- package/dist/Components/CheckoutKit/selectable.d.ts +58 -0
- package/dist/Components/CheckoutKit/selectable.js +120 -0
- package/dist/Components/Commerce/commerceTypes.d.ts +52 -0
- package/dist/Components/Commerce/commerceTypes.js +2 -0
- package/dist/Components/Commerce/commerceVariants.d.ts +13 -0
- package/dist/Components/Commerce/commerceVariants.js +51 -0
- package/dist/Components/Commerce/index.d.ts +2 -0
- package/dist/Components/Commerce/index.js +18 -0
- package/dist/Components/Commerce/useContainerWidth.d.ts +3 -0
- package/dist/Components/Commerce/useContainerWidth.js +25 -0
- package/dist/Components/CouponField/CouponField.d.ts +4 -0
- package/dist/Components/CouponField/CouponField.js +72 -0
- package/dist/Components/CouponField/CouponFieldProps.d.ts +49 -0
- package/dist/Components/CouponField/CouponFieldProps.js +2 -0
- package/dist/Components/DatePicker/DatePickerProps.d.ts +1 -1
- package/dist/Components/DatePicker/PickerPortal.d.ts +11 -0
- package/dist/Components/DatePicker/PickerPortal.js +26 -0
- package/dist/Components/DatePicker/RangeDatePicker.d.ts +1 -1
- package/dist/Components/DatePicker/RangeDatePicker.js +67 -32
- package/dist/Components/DatePicker/SingleDatePicker.js +49 -13
- package/dist/Components/DatePicker/TimeOnlyPicker.js +39 -4
- package/dist/Components/DetailSections/DetailSections.d.ts +4 -0
- package/dist/Components/DetailSections/DetailSections.js +112 -0
- package/dist/Components/DetailSections/DetailSectionsProps.d.ts +58 -0
- package/dist/Components/DetailSections/DetailSectionsProps.js +2 -0
- package/dist/Components/Input/PhoneNumberInputProps.d.ts +1 -1
- package/dist/Components/MediaGallery/MediaFrame.d.ts +31 -0
- package/dist/Components/MediaGallery/MediaFrame.js +55 -0
- package/dist/Components/MediaGallery/MediaGallery.d.ts +4 -0
- package/dist/Components/MediaGallery/MediaGallery.js +206 -0
- package/dist/Components/MediaGallery/MediaGalleryProps.d.ts +76 -0
- package/dist/Components/MediaGallery/MediaGalleryProps.js +20 -0
- package/dist/Components/MediaGallery/MediaLightbox.d.ts +4 -0
- package/dist/Components/MediaGallery/MediaLightbox.js +116 -0
- package/dist/Components/MediaGallery/MediaLightboxProps.d.ts +40 -0
- package/dist/Components/MediaGallery/MediaLightboxProps.js +15 -0
- package/dist/Components/MediaGallery/MediaThumb.d.ts +18 -0
- package/dist/Components/MediaGallery/MediaThumb.js +25 -0
- package/dist/Components/MediaGallery/glass.d.ts +31 -0
- package/dist/Components/MediaGallery/glass.js +47 -0
- package/dist/Components/MediaGallery/mediaGalleryModel.d.ts +22 -0
- package/dist/Components/MediaGallery/mediaGalleryModel.js +83 -0
- package/dist/Components/MediaGallery/motion.d.ts +5 -0
- package/dist/Components/MediaGallery/motion.js +12 -0
- package/dist/Components/MediaGallery/useMatchMedia.d.ts +2 -0
- package/dist/Components/MediaGallery/useMatchMedia.js +20 -0
- package/dist/Components/MultiSelect/MultiSelectProps.d.ts +1 -1
- package/dist/Components/OffersList/OffersList.d.ts +4 -0
- package/dist/Components/OffersList/OffersList.js +83 -0
- package/dist/Components/OffersList/OffersListProps.d.ts +34 -0
- package/dist/Components/OffersList/OffersListProps.js +2 -0
- package/dist/Components/OptionChip/OptionChip.d.ts +4 -0
- package/dist/Components/OptionChip/OptionChip.js +97 -0
- package/dist/Components/OptionChip/OptionChipProps.d.ts +33 -0
- package/dist/Components/OptionChip/OptionChipProps.js +2 -0
- package/dist/Components/OptionChip/optionInk.d.ts +12 -0
- package/dist/Components/OptionChip/optionInk.js +25 -0
- package/dist/Components/OrderStatusTimeline/OrderStatusTimeline.d.ts +4 -0
- package/dist/Components/OrderStatusTimeline/OrderStatusTimeline.js +57 -0
- package/dist/Components/OrderStatusTimeline/OrderStatusTimelineProps.d.ts +29 -0
- package/dist/Components/OrderStatusTimeline/OrderStatusTimelineProps.js +2 -0
- package/dist/Components/OrderSummary/OrderSummary.d.ts +4 -0
- package/dist/Components/OrderSummary/OrderSummary.js +63 -0
- package/dist/Components/OrderSummary/OrderSummaryProps.d.ts +53 -0
- package/dist/Components/OrderSummary/OrderSummaryProps.js +2 -0
- package/dist/Components/PaymentOption/PaymentOption.d.ts +4 -0
- package/dist/Components/PaymentOption/PaymentOption.js +24 -0
- package/dist/Components/PaymentOption/PaymentOptionGroup.d.ts +4 -0
- package/dist/Components/PaymentOption/PaymentOptionGroup.js +7 -0
- package/dist/Components/PaymentOption/PaymentOptionProps.d.ts +32 -0
- package/dist/Components/PaymentOption/PaymentOptionProps.js +2 -0
- package/dist/Components/PinInput/PinInputProps.d.ts +1 -1
- package/dist/Components/PincodeChecker/PincodeChecker.d.ts +4 -0
- package/dist/Components/PincodeChecker/PincodeChecker.js +175 -0
- package/dist/Components/PincodeChecker/PincodeCheckerProps.d.ts +57 -0
- package/dist/Components/PincodeChecker/PincodeCheckerProps.js +2 -0
- package/dist/Components/PriceBlock/PriceBlock.d.ts +4 -0
- package/dist/Components/PriceBlock/PriceBlock.js +41 -0
- package/dist/Components/PriceBlock/PriceBlockProps.d.ts +39 -0
- package/dist/Components/PriceBlock/PriceBlockProps.js +2 -0
- package/dist/Components/ProductBadges/ProductBadges.d.ts +4 -0
- package/dist/Components/ProductBadges/ProductBadges.js +61 -0
- package/dist/Components/ProductBadges/ProductBadgesProps.d.ts +18 -0
- package/dist/Components/ProductBadges/ProductBadgesProps.js +2 -0
- package/dist/Components/ProductCard/ProductCard.d.ts +3 -2
- package/dist/Components/ProductCard/ProductCard.js +103 -52
- package/dist/Components/ProductCard/ProductCardAction.d.ts +17 -0
- package/dist/Components/ProductCard/ProductCardAction.js +65 -0
- package/dist/Components/ProductCard/ProductCardGrid.d.ts +4 -0
- package/dist/Components/ProductCard/ProductCardGrid.js +20 -0
- package/dist/Components/ProductCard/ProductCardMedia.d.ts +18 -0
- package/dist/Components/ProductCard/ProductCardMedia.js +35 -0
- package/dist/Components/ProductCard/ProductCardProps.d.ts +138 -56
- package/dist/Components/ProductCard/ProductCardSkeleton.d.ts +4 -0
- package/dist/Components/ProductCard/ProductCardSkeleton.js +17 -0
- package/dist/Components/ProductCard/ProductCardSwatches.d.ts +15 -0
- package/dist/Components/ProductCard/ProductCardSwatches.js +37 -0
- package/dist/Components/ProductCard/QuickAddSheet.d.ts +4 -0
- package/dist/Components/ProductCard/QuickAddSheet.js +109 -0
- package/dist/Components/ProductCard/QuickAddSheetProps.d.ts +38 -0
- package/dist/Components/ProductCard/QuickAddSheetProps.js +2 -0
- package/dist/Components/ProductCard/cardLabels.d.ts +2 -0
- package/dist/Components/ProductCard/cardLabels.js +16 -0
- package/dist/Components/ProductCard/cardMetrics.d.ts +27 -0
- package/dist/Components/ProductCard/cardMetrics.js +55 -0
- package/dist/Components/ProductCard/productCardFixtures.d.ts +7 -0
- package/dist/Components/ProductCard/productCardFixtures.js +61 -0
- package/dist/Components/ProductCard/productCardModel.d.ts +22 -0
- package/dist/Components/ProductCard/productCardModel.js +79 -0
- package/dist/Components/ProductCard/useMinWidth.d.ts +2 -0
- package/dist/Components/ProductCard/useMinWidth.js +20 -0
- package/dist/Components/ProductDetails/ProductDetails.d.ts +3 -2
- package/dist/Components/ProductDetails/ProductDetails.js +209 -33
- package/dist/Components/ProductDetails/ProductDetailsProps.d.ts +143 -41
- package/dist/Components/ProductDetails/productDetailsModel.d.ts +14 -0
- package/dist/Components/ProductDetails/productDetailsModel.js +45 -0
- package/dist/Components/ProductDetails/useInViewport.d.ts +3 -0
- package/dist/Components/ProductDetails/useInViewport.js +18 -0
- package/dist/Components/QuantityStepper/QuantityStepper.d.ts +4 -0
- package/dist/Components/QuantityStepper/QuantityStepper.js +59 -0
- package/dist/Components/QuantityStepper/QuantityStepperProps.d.ts +24 -0
- package/dist/Components/QuantityStepper/QuantityStepperProps.js +2 -0
- package/dist/Components/Rating/Rating.d.ts +4 -0
- package/dist/Components/Rating/Rating.js +81 -0
- package/dist/Components/Rating/RatingProps.d.ts +27 -0
- package/dist/Components/Rating/RatingProps.js +2 -0
- package/dist/Components/ReviewList/ReviewList.d.ts +4 -0
- package/dist/Components/ReviewList/ReviewList.js +94 -0
- package/dist/Components/ReviewList/ReviewListProps.d.ts +68 -0
- package/dist/Components/ReviewList/ReviewListProps.js +2 -0
- package/dist/Components/ReviewSummary/ReviewSummary.d.ts +4 -0
- package/dist/Components/ReviewSummary/ReviewSummary.js +47 -0
- package/dist/Components/ReviewSummary/ReviewSummaryProps.d.ts +31 -0
- package/dist/Components/ReviewSummary/ReviewSummaryProps.js +2 -0
- package/dist/Components/Search/SearchProps.d.ts +1 -1
- package/dist/Components/SearchSelect/SearchSelectProps.d.ts +1 -1
- package/dist/Components/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/Components/SegmentedControl/SegmentedControl.js +17 -5
- package/dist/Components/SegmentedControl/SegmentedControlProps.d.ts +6 -0
- package/dist/Components/SegmentedControl/segmentedInk.d.ts +2 -0
- package/dist/Components/SegmentedControl/segmentedInk.js +4 -1
- package/dist/Components/SegmentedControl/useSlidingPill.d.ts +2 -0
- package/dist/Components/SegmentedControl/useSlidingPill.js +6 -1
- package/dist/Components/Select/SelectProps.d.ts +1 -1
- package/dist/Components/SelectSearch/SelectSearchProps.d.ts +1 -1
- package/dist/Components/SelectV2/SelectV2Props.d.ts +1 -1
- package/dist/Components/SideBar/SecondaryBar.js +13 -8
- package/dist/Components/SideBar/SideBarProps.d.ts +3 -1
- package/dist/Components/SideBar/components/MenuItemBox.js +47 -10
- package/dist/Components/SideBar/components/MenuPopoverContent.js +4 -3
- package/dist/Components/SpecTable/SpecTable.d.ts +4 -0
- package/dist/Components/SpecTable/SpecTable.js +39 -0
- package/dist/Components/SpecTable/SpecTableProps.d.ts +25 -0
- package/dist/Components/SpecTable/SpecTableProps.js +2 -0
- package/dist/Components/Spinner/Spinner.d.ts +1 -1
- package/dist/Components/Spinner/Spinner.js +2 -2
- package/dist/Components/Spinner/SpinnerProps.d.ts +2 -0
- package/dist/Components/StageProgress/StepperStage.js +2 -1
- package/dist/Components/StatCard/StatCard.d.ts +1 -1
- package/dist/Components/StatCard/StatCard.js +2 -2
- package/dist/Components/StatCard/StatCardProps.d.ts +4 -0
- package/dist/Components/StickyBuyBar/StickyBuyBar.d.ts +3 -0
- package/dist/Components/StickyBuyBar/StickyBuyBar.js +85 -0
- package/dist/Components/StickyBuyBar/StickyBuyBarProps.d.ts +54 -0
- package/dist/Components/StickyBuyBar/StickyBuyBarProps.js +2 -0
- package/dist/Components/StockStatus/StockStatus.d.ts +4 -0
- package/dist/Components/StockStatus/StockStatus.js +31 -0
- package/dist/Components/StockStatus/StockStatusProps.d.ts +14 -0
- package/dist/Components/StockStatus/StockStatusProps.js +2 -0
- package/dist/Components/Table/Table.js +2 -1
- package/dist/Components/Table/TableProps.d.ts +5 -2
- package/dist/Components/Table/columnLabel.d.ts +6 -0
- package/dist/Components/Table/columnLabel.js +27 -0
- package/dist/Components/Table/components/TableBody.js +5 -1
- package/dist/Components/Table/components/TableHeader.js +3 -2
- package/dist/Components/Table/filters/ActiveFilters.js +2 -1
- package/dist/Components/Table/filters/TableFilters.js +2 -1
- package/dist/Components/Table/hooks/useTable.js +3 -2
- package/dist/Components/Table/settings/ManageColumns.js +4 -3
- package/dist/Components/Table/settings/TableSettings.js +2 -1
- package/dist/Components/TrustBadges/TrustBadges.d.ts +4 -0
- package/dist/Components/TrustBadges/TrustBadges.js +24 -0
- package/dist/Components/TrustBadges/TrustBadgesProps.d.ts +20 -0
- package/dist/Components/TrustBadges/TrustBadgesProps.js +2 -0
- package/dist/Components/VariantSelector/VariantSelector.d.ts +4 -0
- package/dist/Components/VariantSelector/VariantSelector.js +123 -0
- package/dist/Components/VariantSelector/VariantSelectorProps.d.ts +27 -0
- package/dist/Components/VariantSelector/VariantSelectorProps.js +2 -0
- package/dist/Components/VariantSelector/variantSelection.d.ts +6 -0
- package/dist/Components/VariantSelector/variantSelection.js +25 -0
- package/dist/Components/WishlistToggle/WishlistToggle.d.ts +4 -0
- package/dist/Components/WishlistToggle/WishlistToggle.js +65 -0
- package/dist/Components/WishlistToggle/WishlistToggleProps.d.ts +19 -0
- package/dist/Components/WishlistToggle/WishlistToggleProps.js +2 -0
- package/dist/Theme/assertPalette.d.ts +1 -1
- package/dist/Theme/assertPalette.js +1 -1
- package/dist/Theme/tokens/brands/index.js +2 -0
- package/dist/Theme/tokens/brands/zinc/palette.dark.d.ts +1 -0
- package/dist/Theme/tokens/builders/buildDarkPalette.js +2 -0
- package/dist/Theme/tokens/builders/index.d.ts +1 -0
- package/dist/Theme/tokens/builders/index.js +1 -0
- package/dist/Theme/tokens/builders/rechartsTheme.d.ts +56 -0
- package/dist/Theme/tokens/builders/rechartsTheme.js +43 -0
- package/dist/Theme/tokens/builders/star.d.ts +3 -0
- package/dist/Theme/tokens/builders/star.js +8 -0
- package/dist/Theme/tokens/primitives/controlSizes.js +1 -1
- package/dist/Theme/tokens/types.d.ts +3 -1
- package/dist/Theme/useChartTheme.d.ts +4 -0
- package/dist/Theme/useChartTheme.js +12 -0
- package/dist/Utils/price.d.ts +16 -0
- package/dist/Utils/price.js +34 -0
- package/dist/esm/Components/AddressCard/AddressCard.js +20 -0
- package/dist/esm/Components/AddressCard/AddressCardGroup.js +5 -0
- package/dist/esm/Components/AddressCard/AddressCardProps.js +1 -0
- package/dist/esm/Components/BagBar/BagBar.js +27 -0
- package/dist/esm/Components/BagBar/BagBarProps.js +1 -0
- package/dist/esm/Components/CartItem/CartItem.js +55 -0
- package/dist/esm/Components/CartItem/CartItemProps.js +1 -0
- package/dist/esm/Components/CartItem/cartItemOptions.js +9 -0
- package/dist/esm/Components/CheckoutKit/selectable.js +112 -0
- package/dist/esm/Components/Commerce/commerceTypes.js +1 -0
- package/dist/esm/Components/Commerce/commerceVariants.js +41 -0
- package/dist/esm/Components/Commerce/index.js +2 -0
- package/dist/esm/Components/Commerce/useContainerWidth.js +21 -0
- package/dist/esm/Components/CouponField/CouponField.js +67 -0
- package/dist/esm/Components/CouponField/CouponFieldProps.js +1 -0
- package/dist/esm/Components/DatePicker/PickerPortal.js +21 -0
- package/dist/esm/Components/DatePicker/RangeDatePicker.js +34 -32
- package/dist/esm/Components/DatePicker/SingleDatePicker.js +16 -13
- package/dist/esm/Components/DatePicker/TimeOnlyPicker.js +6 -4
- package/dist/esm/Components/DetailSections/DetailSections.js +107 -0
- package/dist/esm/Components/DetailSections/DetailSectionsProps.js +1 -0
- package/dist/esm/Components/MediaGallery/MediaFrame.js +48 -0
- package/dist/esm/Components/MediaGallery/MediaGallery.js +201 -0
- package/dist/esm/Components/MediaGallery/MediaGalleryProps.js +17 -0
- package/dist/esm/Components/MediaGallery/MediaLightbox.js +78 -0
- package/dist/esm/Components/MediaGallery/MediaLightboxProps.js +12 -0
- package/dist/esm/Components/MediaGallery/MediaThumb.js +23 -0
- package/dist/esm/Components/MediaGallery/glass.js +42 -0
- package/dist/esm/Components/MediaGallery/mediaGalleryModel.js +71 -0
- package/dist/esm/Components/MediaGallery/motion.js +8 -0
- package/dist/esm/Components/MediaGallery/useMatchMedia.js +16 -0
- package/dist/esm/Components/OffersList/OffersList.js +81 -0
- package/dist/esm/Components/OffersList/OffersListProps.js +1 -0
- package/dist/esm/Components/OptionChip/OptionChip.js +95 -0
- package/dist/esm/Components/OptionChip/OptionChipProps.js +1 -0
- package/dist/esm/Components/OptionChip/optionInk.js +21 -0
- package/dist/esm/Components/OrderStatusTimeline/OrderStatusTimeline.js +55 -0
- package/dist/esm/Components/OrderStatusTimeline/OrderStatusTimelineProps.js +1 -0
- package/dist/esm/Components/OrderSummary/OrderSummary.js +58 -0
- package/dist/esm/Components/OrderSummary/OrderSummaryProps.js +1 -0
- package/dist/esm/Components/PaymentOption/PaymentOption.js +19 -0
- package/dist/esm/Components/PaymentOption/PaymentOptionGroup.js +5 -0
- package/dist/esm/Components/PaymentOption/PaymentOptionProps.js +1 -0
- package/dist/esm/Components/PincodeChecker/PincodeChecker.js +170 -0
- package/dist/esm/Components/PincodeChecker/PincodeCheckerProps.js +1 -0
- package/dist/esm/Components/PriceBlock/PriceBlock.js +39 -0
- package/dist/esm/Components/PriceBlock/PriceBlockProps.js +1 -0
- package/dist/esm/Components/ProductBadges/ProductBadges.js +59 -0
- package/dist/esm/Components/ProductBadges/ProductBadgesProps.js +1 -0
- package/dist/esm/Components/ProductCard/ProductCard.js +105 -54
- package/dist/esm/Components/ProductCard/ProductCardAction.js +60 -0
- package/dist/esm/Components/ProductCard/ProductCardGrid.js +18 -0
- package/dist/esm/Components/ProductCard/ProductCardMedia.js +33 -0
- package/dist/esm/Components/ProductCard/ProductCardSkeleton.js +12 -0
- package/dist/esm/Components/ProductCard/ProductCardSwatches.js +35 -0
- package/dist/esm/Components/ProductCard/QuickAddSheet.js +71 -0
- package/dist/esm/Components/ProductCard/QuickAddSheetProps.js +1 -0
- package/dist/esm/Components/ProductCard/cardLabels.js +13 -0
- package/dist/esm/Components/ProductCard/cardMetrics.js +52 -0
- package/dist/esm/Components/ProductCard/productCardFixtures.js +58 -0
- package/dist/esm/Components/ProductCard/productCardModel.js +65 -0
- package/dist/esm/Components/ProductCard/useMinWidth.js +16 -0
- package/dist/esm/Components/ProductDetails/ProductDetails.js +210 -34
- package/dist/esm/Components/ProductDetails/productDetailsModel.js +35 -0
- package/dist/esm/Components/ProductDetails/useInViewport.js +14 -0
- package/dist/esm/Components/QuantityStepper/QuantityStepper.js +57 -0
- package/dist/esm/Components/QuantityStepper/QuantityStepperProps.js +1 -0
- package/dist/esm/Components/Rating/Rating.js +79 -0
- package/dist/esm/Components/Rating/RatingProps.js +1 -0
- package/dist/esm/Components/ReviewList/ReviewList.js +89 -0
- package/dist/esm/Components/ReviewList/ReviewListProps.js +1 -0
- package/dist/esm/Components/ReviewSummary/ReviewSummary.js +42 -0
- package/dist/esm/Components/ReviewSummary/ReviewSummaryProps.js +1 -0
- package/dist/esm/Components/SegmentedControl/SegmentedControl.js +19 -7
- package/dist/esm/Components/SegmentedControl/segmentedInk.js +2 -0
- package/dist/esm/Components/SegmentedControl/useSlidingPill.js +6 -1
- package/dist/esm/Components/SideBar/SecondaryBar.js +14 -9
- package/dist/esm/Components/SideBar/components/MenuItemBox.js +14 -10
- package/dist/esm/Components/SideBar/components/MenuPopoverContent.js +4 -3
- package/dist/esm/Components/SpecTable/SpecTable.js +34 -0
- package/dist/esm/Components/SpecTable/SpecTableProps.js +1 -0
- package/dist/esm/Components/Spinner/Spinner.js +2 -2
- package/dist/esm/Components/StageProgress/StepperStage.js +2 -1
- package/dist/esm/Components/StatCard/StatCard.js +2 -2
- package/dist/esm/Components/StickyBuyBar/StickyBuyBar.js +80 -0
- package/dist/esm/Components/StickyBuyBar/StickyBuyBarProps.js +1 -0
- package/dist/esm/Components/StockStatus/StockStatus.js +29 -0
- package/dist/esm/Components/StockStatus/StockStatusProps.js +1 -0
- package/dist/esm/Components/Table/Table.js +2 -1
- package/dist/esm/Components/Table/columnLabel.js +21 -0
- package/dist/esm/Components/Table/components/TableBody.js +5 -1
- package/dist/esm/Components/Table/components/TableHeader.js +3 -2
- package/dist/esm/Components/Table/filters/ActiveFilters.js +2 -1
- package/dist/esm/Components/Table/filters/TableFilters.js +2 -1
- package/dist/esm/Components/Table/hooks/useTable.js +3 -2
- package/dist/esm/Components/Table/settings/ManageColumns.js +4 -3
- package/dist/esm/Components/Table/settings/TableSettings.js +2 -1
- package/dist/esm/Components/TrustBadges/TrustBadges.js +22 -0
- package/dist/esm/Components/TrustBadges/TrustBadgesProps.js +1 -0
- package/dist/esm/Components/VariantSelector/VariantSelector.js +118 -0
- package/dist/esm/Components/VariantSelector/VariantSelectorProps.js +1 -0
- package/dist/esm/Components/VariantSelector/variantSelection.js +19 -0
- package/dist/esm/Components/WishlistToggle/WishlistToggle.js +63 -0
- package/dist/esm/Components/WishlistToggle/WishlistToggleProps.js +1 -0
- package/dist/esm/Theme/assertPalette.js +1 -1
- package/dist/esm/Theme/tokens/brands/index.js +2 -0
- package/dist/esm/Theme/tokens/builders/buildDarkPalette.js +2 -0
- package/dist/esm/Theme/tokens/builders/index.js +1 -0
- package/dist/esm/Theme/tokens/builders/rechartsTheme.js +37 -0
- package/dist/esm/Theme/tokens/builders/star.js +4 -0
- package/dist/esm/Theme/tokens/primitives/controlSizes.js +1 -1
- package/dist/esm/Theme/useChartTheme.js +8 -0
- package/dist/esm/Utils/price.js +27 -0
- package/dist/esm/index.js +36 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +86 -4
- package/package.json +1 -1
- package/dist/Components/ProductCard/ProductLabel.d.ts +0 -7
- package/dist/Components/ProductCard/ProductLabel.js +0 -20
- package/dist/Components/ProductCard/ProductPrice.d.ts +0 -10
- package/dist/Components/ProductCard/ProductPrice.js +0 -126
- package/dist/Components/ProductCard/ProductReview.d.ts +0 -8
- package/dist/Components/ProductCard/ProductReview.js +0 -30
- package/dist/Components/ProductCard/ProductTags.d.ts +0 -8
- package/dist/Components/ProductCard/ProductTags.js +0 -53
- package/dist/Components/ProductDetails/ProductDtlPrice.d.ts +0 -7
- package/dist/Components/ProductDetails/ProductDtlPrice.js +0 -24
- package/dist/Components/ProductDetails/ProductImageSlider.d.ts +0 -13
- package/dist/Components/ProductDetails/ProductImageSlider.js +0 -240
- package/dist/esm/Components/ProductCard/ProductLabel.js +0 -18
- package/dist/esm/Components/ProductCard/ProductPrice.js +0 -121
- package/dist/esm/Components/ProductCard/ProductReview.js +0 -28
- package/dist/esm/Components/ProductCard/ProductTags.js +0 -48
- package/dist/esm/Components/ProductDetails/ProductDtlPrice.js +0 -22
- package/dist/esm/Components/ProductDetails/ProductImageSlider.js +0 -204
|
@@ -10,6 +10,7 @@ import { useFieldIds } from "../Common/useFieldIds.js";
|
|
|
10
10
|
import { resolvePickerAliases } from "./pickerField.js";
|
|
11
11
|
import TimePicker from "./TimePicker.js";
|
|
12
12
|
import { TimePickerInput } from "./TimePickerInput.js";
|
|
13
|
+
import PickerPortal, { usePickerPortal } from "./PickerPortal.js";
|
|
13
14
|
import { useExclusiveOverlay } from "../../Hooks/useExclusiveOverlay.js";
|
|
14
15
|
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
15
16
|
import { DEFAULT_CONTROL_SIZE } from "../../Theme/tokens/primitives/controlSizes.js";
|
|
@@ -22,6 +23,7 @@ export function TimeOnlyPicker(props) {
|
|
|
22
23
|
const theme = useCustomTheme();
|
|
23
24
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
|
24
25
|
const requestExclusiveOpen = useExclusiveOverlay(isOpen, onClose);
|
|
26
|
+
const pickerPortal = usePickerPortal(isOpen);
|
|
25
27
|
const openPicker = () => {
|
|
26
28
|
requestExclusiveOpen();
|
|
27
29
|
onOpen();
|
|
@@ -90,13 +92,13 @@ export function TimeOnlyPicker(props) {
|
|
|
90
92
|
};
|
|
91
93
|
}, [isOpen, onClose]);
|
|
92
94
|
const resolvedTempDate = tempDate !== null && tempDate !== void 0 ? tempDate : new Date((disableToday ? tomorrow : today).getFullYear(), (disableToday ? tomorrow : today).getMonth(), (disableToday ? tomorrow : today).getDate(), 0, 0, 0, 0);
|
|
93
|
-
return (_jsxs(FormControl, { id: ids.inputId, isDisabled: disabled, isInvalid: error, isRequired: isRequired, width: width, children: [label && (_jsx(TextLabel, { label: label, id: ids.inputId, isRequired: isRequired, isInformation: isInformation, informationMessage: informationMessage })), _jsxs(Popover, { isLazy: true, isOpen:
|
|
95
|
+
return (_jsxs(FormControl, { id: ids.inputId, isDisabled: disabled, isInvalid: error, isRequired: isRequired, width: width, children: [label && (_jsx(TextLabel, { label: label, id: ids.inputId, isRequired: isRequired, isInformation: isInformation, informationMessage: informationMessage })), _jsxs(Popover, { isLazy: true, isOpen: pickerPortal.isPopoverOpen, onClose: onClose, placement: "bottom-start", closeOnBlur: false, returnFocusOnClose: false, children: [_jsx(PopoverAnchor, { children: _jsx(Box, { ref: triggerRef, as: "div", children: _jsx(Box, { ref: inputWrapperRef, children: _jsx(TimePickerInput, { value: tempDate, onChange: (date) => {
|
|
94
96
|
handleTimeInputChange(date);
|
|
95
97
|
}, dateFormat: dateFormat, isDisabled: disabled, placeholder: placeholder, id: ids.inputId, name: name, isRequired: isRequired, describedBy: ids.describedBy, onFocus: () => {
|
|
96
98
|
if (!disabled && !isOpen)
|
|
97
99
|
openPicker();
|
|
98
|
-
}, error: error, size: sizeKey }) }) }) }), _jsx(PopoverContent, { width: "auto", p: 4, ref: popoverRef, boxShadow: "lg", borderRadius: "xl", bg: theme.colors.surfaceRaised, children: _jsxs(PopoverBody, { children: [_jsx(TimePicker, { date: resolvedTempDate, dateFormat: dateFormat, onChange: (updatedDate) => {
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
}, error: error, size: sizeKey }) }) }) }), _jsx(PickerPortal, { isMounted: pickerPortal.isMounted, children: _jsx(PopoverContent, { width: "auto", p: 4, ref: popoverRef, boxShadow: "lg", borderRadius: "xl", bg: theme.colors.surfaceRaised, children: _jsxs(PopoverBody, { children: [_jsx(TimePicker, { date: resolvedTempDate, dateFormat: dateFormat, onChange: (updatedDate) => {
|
|
101
|
+
handleTimePickerChange(updatedDate);
|
|
102
|
+
} }), _jsxs(Box, { display: "flex", justifyContent: "space-between", mt: 4, pt: 4, borderTop: "1px solid", borderColor: theme.colors.boxborder[200], children: [_jsx(ChakraButton, { size: "sm", variant: "ghost", onClick: handleClear, children: "Clear" }), _jsx(ChakraButton, { size: "sm", colorScheme: "primary", onClick: handleOk, children: "OK" })] })] }) }) })] }), _jsx(FieldMessage, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }));
|
|
101
103
|
}
|
|
102
104
|
export default TimeOnlyPicker;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Flex, Text } from "@chakra-ui/react";
|
|
3
|
+
import { Check, Dot } from "lucide-react";
|
|
4
|
+
import Accordion from "../Accordion/Accordion.js";
|
|
5
|
+
import Tabs from "../Tabs/Tabs.js";
|
|
6
|
+
import SpecTable from "../SpecTable/SpecTable.js";
|
|
7
|
+
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
8
|
+
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
9
|
+
const DEFAULT_LABELS = {
|
|
10
|
+
description: "Description",
|
|
11
|
+
highlights: "Highlights",
|
|
12
|
+
specifications: "Specifications",
|
|
13
|
+
care: "Care",
|
|
14
|
+
shipping: "Shipping & returns",
|
|
15
|
+
legal: "Legal & manufacturer info",
|
|
16
|
+
netQuantity: "Net quantity",
|
|
17
|
+
manufacturer: "Manufactured / packed by",
|
|
18
|
+
originCountry: "Country of origin",
|
|
19
|
+
mrp: "MRP",
|
|
20
|
+
customerCare: "Customer care",
|
|
21
|
+
mrpIncludesTaxes: "Maximum retail price, inclusive of all taxes",
|
|
22
|
+
};
|
|
23
|
+
const hasContent = (node) => node !== undefined && node !== null && node !== false && node !== "";
|
|
24
|
+
const legalRows = (legal, text) => {
|
|
25
|
+
const fields = [legal.netQuantity, legal.manufacturer, legal.originCountry, legal.mrpNote, legal.customerCare];
|
|
26
|
+
if (!fields.some(Boolean))
|
|
27
|
+
return [];
|
|
28
|
+
const rows = [
|
|
29
|
+
[text.netQuantity, legal.netQuantity],
|
|
30
|
+
[text.manufacturer, legal.manufacturer],
|
|
31
|
+
[text.originCountry, legal.originCountry],
|
|
32
|
+
[text.mrp, legal.mrpNote || text.mrpIncludesTaxes],
|
|
33
|
+
[text.customerCare, legal.customerCare],
|
|
34
|
+
];
|
|
35
|
+
return rows.flatMap(([label, value]) => (value ? [{ label, value }] : []));
|
|
36
|
+
};
|
|
37
|
+
const BulletList = ({ items, icon }) => {
|
|
38
|
+
const { colors } = useCustomTheme();
|
|
39
|
+
return (_jsx(Flex, { as: "ul", direction: "column", gap: 2, m: 0, p: 0, listStyleType: "none", children: items.map((item, i) => (_jsxs(Flex, { as: "li", align: "flex-start", gap: 2, color: colors.text[800], children: [_jsx(Box, { as: "span", "aria-hidden": true, display: "inline-flex", flexShrink: 0, mt: "0.1875rem", color: colors.accentText, children: icon === "check" ? _jsx(Check, { size: 16, strokeWidth: 2.25 }) : _jsx(Dot, { size: 16, strokeWidth: 6 }) }), _jsx(Box, { as: "span", minW: 0, overflowWrap: "anywhere", children: item })] }, `${item}-${i}`))) }));
|
|
40
|
+
};
|
|
41
|
+
/** Long-form product information: description, highlights, specifications, care, shipping and returns, and legal-metrology info. */
|
|
42
|
+
const DetailSections = ({ description, highlights, specs, care, shipping, legal, sections, layout = "accordion", defaultOpenIds, specCollapsedCount, labels, "data-testid": testId, }) => {
|
|
43
|
+
const { colors } = useCustomTheme();
|
|
44
|
+
const text = { ...DEFAULT_LABELS, ...labels };
|
|
45
|
+
const body = { fontSize: "sm", lineHeight: 1.7, color: colors.text[800] };
|
|
46
|
+
const careList = typeof care === "string" ? null : care;
|
|
47
|
+
const legalSpecs = legal ? legalRows(legal, text) : [];
|
|
48
|
+
const builtIn = [
|
|
49
|
+
description ? { id: "description", title: text.description, content: _jsx(Text, { ...body, children: description }) } : null,
|
|
50
|
+
(highlights === null || highlights === void 0 ? void 0 : highlights.length) ? { id: "highlights", title: text.highlights, content: _jsx(BulletList, { items: highlights, icon: "check" }) } : null,
|
|
51
|
+
(specs === null || specs === void 0 ? void 0 : specs.length)
|
|
52
|
+
? { id: "specs", title: text.specifications, content: _jsx(SpecTable, { items: specs, collapsedCount: specCollapsedCount }) }
|
|
53
|
+
: null,
|
|
54
|
+
(care === null || care === void 0 ? void 0 : care.length)
|
|
55
|
+
? {
|
|
56
|
+
id: "care",
|
|
57
|
+
title: text.care,
|
|
58
|
+
content: careList ? _jsx(BulletList, { items: careList, icon: "dot" }) : _jsx(Text, { ...body, children: care }),
|
|
59
|
+
}
|
|
60
|
+
: null,
|
|
61
|
+
hasContent(shipping) ? { id: "shipping", title: text.shipping, content: _jsx(Box, { ...body, children: shipping }) } : null,
|
|
62
|
+
legalSpecs.length ? { id: "legal", title: text.legal, content: _jsx(SpecTable, { items: legalSpecs }) } : null,
|
|
63
|
+
];
|
|
64
|
+
const BUILT_IN_IDS = ["description", "highlights", "specs", "care", "shipping", "legal"];
|
|
65
|
+
const overrides = new Map((sections !== null && sections !== void 0 ? sections : []).map((s) => [s.id, s]));
|
|
66
|
+
const merged = [
|
|
67
|
+
...builtIn.flatMap((section, i) => {
|
|
68
|
+
const replacement = overrides.get(BUILT_IN_IDS[i]);
|
|
69
|
+
const picked = replacement !== null && replacement !== void 0 ? replacement : section;
|
|
70
|
+
return picked ? [picked] : [];
|
|
71
|
+
}),
|
|
72
|
+
...(sections !== null && sections !== void 0 ? sections : []).filter((s) => !BUILT_IN_IDS.includes(s.id)),
|
|
73
|
+
];
|
|
74
|
+
if (merged.length === 0)
|
|
75
|
+
return null;
|
|
76
|
+
if (layout === "tabs") {
|
|
77
|
+
return (_jsx(Box, { "data-testid": testId, children: _jsx(Tabs, { variant: "line", "aria-label": "Product details", items: merged.map((s) => ({
|
|
78
|
+
id: s.id,
|
|
79
|
+
label: s.title,
|
|
80
|
+
content: (_jsx(Box, { id: `detail-section-${s.id}`, "data-section-id": s.id, pt: 4, children: s.content })),
|
|
81
|
+
})) }) }));
|
|
82
|
+
}
|
|
83
|
+
const openIds = defaultOpenIds !== null && defaultOpenIds !== void 0 ? defaultOpenIds : [merged[0].id];
|
|
84
|
+
const defaultIndex = merged.flatMap((s, i) => (openIds.includes(s.id) ? [i] : []));
|
|
85
|
+
return (_jsx(Box, { "data-testid": testId, children: _jsx(Accordion, { allowMultiple: true, defaultIndex: defaultIndex, items: merged.map((s) => ({
|
|
86
|
+
header: (_jsx(Text, { as: "span", color: colors.header[900], children: s.title })),
|
|
87
|
+
content: s.content,
|
|
88
|
+
itemProps: {
|
|
89
|
+
id: `detail-section-${s.id}`,
|
|
90
|
+
"data-section-id": s.id,
|
|
91
|
+
border: "none",
|
|
92
|
+
borderRadius: "xl",
|
|
93
|
+
bg: colors.background[100],
|
|
94
|
+
mb: 2,
|
|
95
|
+
},
|
|
96
|
+
buttonProps: {
|
|
97
|
+
px: 4,
|
|
98
|
+
py: 3.5,
|
|
99
|
+
fontSize: "md",
|
|
100
|
+
bg: "transparent",
|
|
101
|
+
transition: transitionFor(["background-color"]),
|
|
102
|
+
_hover: { bg: colors.background[200] },
|
|
103
|
+
},
|
|
104
|
+
panelProps: { px: 4, pt: 1, pb: 5, color: colors.text[800] },
|
|
105
|
+
})) }) }));
|
|
106
|
+
};
|
|
107
|
+
export default DetailSections;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { Box, Flex, Text } from "@chakra-ui/react";
|
|
4
|
+
import { ImageOff, Play, Video } from "lucide-react";
|
|
5
|
+
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
6
|
+
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
7
|
+
import { reducedMotionSx } from "../../Theme/chakra/motion.js";
|
|
8
|
+
import Skeletons from "../Skeletons/Skeletons.js";
|
|
9
|
+
import { GlassIconButton } from "./glass.js";
|
|
10
|
+
import { frameKind, framePoster, parseEmbed } from "./mediaGalleryModel.js";
|
|
11
|
+
const CENTER = { position: "absolute", inset: 0 };
|
|
12
|
+
/** One frame: skeleton, then image fade-in with the ratio already reserved; broken images become a tile. */
|
|
13
|
+
export const FrameImage = ({ src, alt, fit, eager, zoom, unavailableLabel, }) => {
|
|
14
|
+
const { colors } = useCustomTheme();
|
|
15
|
+
const [state, setState] = useState("loading");
|
|
16
|
+
const settle = useCallback((node) => {
|
|
17
|
+
if (node && node.complete && node.naturalWidth > 0)
|
|
18
|
+
setState("loaded");
|
|
19
|
+
}, []);
|
|
20
|
+
if (state === "failed") {
|
|
21
|
+
return (_jsxs(Flex, { ...CENTER, direction: "column", align: "center", justify: "center", gap: 2, color: colors.textMuted, role: "img", "aria-label": `${unavailableLabel}: ${alt}`, children: [_jsx(ImageOff, { size: 24, "aria-hidden": true }), _jsx(Text, { as: "span", fontSize: "xs", children: unavailableLabel })] }));
|
|
22
|
+
}
|
|
23
|
+
return (_jsxs(_Fragment, { children: [state === "loading" && (_jsx(Box, { ...CENTER, "data-testid": "media-skeleton", "aria-hidden": true, children: _jsx(Skeletons, { type: "skeleton", height: "100%", width: "100%", borderRadius: "0" }) })), _jsx(Box, { as: "img", ref: settle, src: src, alt: alt, loading: eager ? "eager" : "lazy", decoding: "async", draggable: false, onLoad: () => setState("loaded"), onError: () => setState("failed"), ...CENTER, w: "100%", h: "100%", objectFit: fit, opacity: state === "loaded" ? 1 : 0, transform: zoom ? `scale(${zoom.scale})` : undefined, transformOrigin: zoom ? `${zoom.x}% ${zoom.y}%` : undefined, transition: transitionFor(["opacity", "transform"], "base"), sx: reducedMotionSx() })] }));
|
|
24
|
+
};
|
|
25
|
+
/** Image, or a video that shows its poster behind a play button until asked to play. */
|
|
26
|
+
const MediaFrame = ({ media, alt, fit = "cover", active = true, eager = true, autoPlay = false, zoom, surface, playLabel, unavailableLabel, }) => {
|
|
27
|
+
const { colors } = useCustomTheme();
|
|
28
|
+
const kind = frameKind(media);
|
|
29
|
+
const [playing, setPlaying] = useState(autoPlay);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!active)
|
|
32
|
+
setPlaying(false);
|
|
33
|
+
}, [active]);
|
|
34
|
+
const poster = framePoster(media);
|
|
35
|
+
let content;
|
|
36
|
+
if (kind === "image") {
|
|
37
|
+
content = _jsx(FrameImage, { src: media.src, alt: alt, fit: fit, eager: eager, zoom: zoom, unavailableLabel: unavailableLabel }, media.src);
|
|
38
|
+
}
|
|
39
|
+
else if (playing) {
|
|
40
|
+
const embed = parseEmbed(media.src);
|
|
41
|
+
content = embed ? (_jsx(Box, { as: "iframe", ...CENTER, w: "100%", h: "100%", border: "0", src: embed.src, title: alt, allow: "autoplay; encrypted-media; picture-in-picture; fullscreen", allowFullScreen: true })) : (_jsx(Box, { as: "video", ...CENTER, w: "100%", h: "100%", objectFit: "contain", src: media.src, poster: media.poster, controls: true, autoPlay: true, playsInline: true, "aria-label": alt }));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
content = (_jsxs(_Fragment, { children: [poster ? (_jsx(FrameImage, { src: poster, alt: alt, fit: fit, eager: eager, unavailableLabel: unavailableLabel }, poster)) : (_jsx(Flex, { ...CENTER, align: "center", justify: "center", color: colors.textMuted, "aria-hidden": true, children: _jsx(Video, { size: 32 }) })), _jsx(GlassIconButton, { label: playLabel, icon: _jsx(Play, { size: 20, "aria-hidden": true, fill: "currentColor" }), onClick: () => setPlaying(true), size: 56, left: "calc(50% - 28px)", top: "calc(50% - 28px)", tabIndex: active ? 0 : -1 })] }));
|
|
45
|
+
}
|
|
46
|
+
return (_jsx(Box, { position: "relative", w: "100%", h: "100%", overflow: "hidden", bg: surface !== null && surface !== void 0 ? surface : colors.background[100], "data-frame-kind": kind, children: content }));
|
|
47
|
+
};
|
|
48
|
+
export default MediaFrame;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { Box, Flex, Text } from "@chakra-ui/react";
|
|
4
|
+
import { ChevronLeft, ChevronRight, ZoomIn } from "lucide-react";
|
|
5
|
+
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
6
|
+
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
7
|
+
import { scrollbarStyles } from "../../Theme/tokens/builders/scrollbar.js";
|
|
8
|
+
import { reducedMotionSx, usePrefersReducedMotion } from "../../Theme/chakra/motion.js";
|
|
9
|
+
import { focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
|
|
10
|
+
import Skeletons from "../Skeletons/Skeletons.js";
|
|
11
|
+
import MediaFrame from "./MediaFrame.js";
|
|
12
|
+
import MediaThumb from "./MediaThumb.js";
|
|
13
|
+
import MediaLightbox from "./MediaLightbox.js";
|
|
14
|
+
import { GlassIconButton, GlassPill, glassStyles } from "./glass.js";
|
|
15
|
+
import { CROSSFADE_MS, fadeIn } from "./motion.js";
|
|
16
|
+
import { useContainerWidth } from "../Commerce/useContainerWidth.js";
|
|
17
|
+
import { useMatchMedia } from "./useMatchMedia.js";
|
|
18
|
+
import { clampIndex, frameKind, frameSignature, indexForKey, keyBelongsToTarget, resolveFrames } from "./mediaGalleryModel.js";
|
|
19
|
+
import { defaultMediaGalleryLabels } from "./MediaGalleryProps.js";
|
|
20
|
+
const WIDE_QUERY = "(min-width: 768px)";
|
|
21
|
+
const WIDE_MIN_WIDTH = 560;
|
|
22
|
+
const FINE_POINTER_QUERY = "(hover: hover) and (pointer: fine)";
|
|
23
|
+
const MAX_DOTS = 8;
|
|
24
|
+
const INNER_ZOOM = 2;
|
|
25
|
+
const SETTLE_MS = 700;
|
|
26
|
+
const HIDDEN_SCROLLBAR = { scrollbarWidth: "none", "&::-webkit-scrollbar": { display: "none" } };
|
|
27
|
+
const revealWithin = (container, child, axis) => {
|
|
28
|
+
const [start, size, view] = axis === "x"
|
|
29
|
+
? [child.offsetLeft, child.offsetWidth, container.clientWidth]
|
|
30
|
+
: [child.offsetTop, child.offsetHeight, container.clientHeight];
|
|
31
|
+
const key = axis === "x" ? "scrollLeft" : "scrollTop";
|
|
32
|
+
const at = container[key];
|
|
33
|
+
if (start < at)
|
|
34
|
+
container[key] = start;
|
|
35
|
+
else if (start + size > at + view)
|
|
36
|
+
container[key] = start + size - view;
|
|
37
|
+
};
|
|
38
|
+
const GallerySkeleton = ({ arrangement, aspectRatio, rounded, label }) => {
|
|
39
|
+
const rail = arrangement !== "swipe";
|
|
40
|
+
const vertical = arrangement === "thumbs-left";
|
|
41
|
+
return (_jsxs(Flex, { role: "status", "aria-label": label, direction: vertical ? "row" : "column", gap: 4, w: "100%", "data-testid": "media-gallery-skeleton", children: [rail && vertical && (_jsx(Flex, { direction: "column", gap: 3, w: "72px", flexShrink: 0, children: [0, 1, 2, 3].map((n) => (_jsx(Skeletons, { type: "skeleton", height: "90px", width: "72px", borderRadius: "xl" }, n))) })), _jsx(Box, { flex: 1, minW: 0, sx: { aspectRatio }, borderRadius: rounded ? "2xl" : 0, overflow: "hidden", children: _jsx(Skeletons, { type: "skeleton", height: "100%", width: "100%", borderRadius: "0" }) }), rail && !vertical && (_jsx(Flex, { gap: 3, children: [0, 1, 2, 3].map((n) => (_jsx(Skeletons, { type: "skeleton", height: "80px", width: "64px", borderRadius: "xl" }, n))) }))] }));
|
|
42
|
+
};
|
|
43
|
+
/** Product media carousel: scroll-snap track, thumbnail rail or dots, lightbox, colour-linked frame sets. */
|
|
44
|
+
const MediaGallery = ({ media, layout = "auto", aspectRatio = "4/5", zoom = "lightbox", index, defaultIndex = 0, onIndexChange, activeMediaIds, badges, actions, sticky = false, stickyTop = 24, rounded, isLoading = false, labels: labelOverrides, lightboxLabels, "aria-label": ariaLabel, "data-testid": testId, }) => {
|
|
45
|
+
const { colors } = useCustomTheme();
|
|
46
|
+
const reduced = usePrefersReducedMotion();
|
|
47
|
+
const rootRef = useRef(null);
|
|
48
|
+
const containerWidth = useContainerWidth(rootRef);
|
|
49
|
+
const windowWide = useMatchMedia(WIDE_QUERY, false);
|
|
50
|
+
const wide = containerWidth === undefined ? windowWide : containerWidth >= WIDE_MIN_WIDTH;
|
|
51
|
+
const finePointer = useMatchMedia(FINE_POINTER_QUERY, false);
|
|
52
|
+
const labels = { ...defaultMediaGalleryLabels, ...labelOverrides };
|
|
53
|
+
const arrangement = layout === "auto" ? (wide ? "thumbs-left" : "swipe") : layout;
|
|
54
|
+
const isSwipe = arrangement === "swipe";
|
|
55
|
+
const squared = rounded === undefined ? isSwipe : !rounded;
|
|
56
|
+
const activeKey = JSON.stringify(activeMediaIds !== null && activeMediaIds !== void 0 ? activeMediaIds : null);
|
|
57
|
+
const frames = useMemo(() => { var _a; return resolveFrames(media, (_a = JSON.parse(activeKey)) !== null && _a !== void 0 ? _a : undefined); }, [media, activeKey]);
|
|
58
|
+
const signature = frameSignature(frames);
|
|
59
|
+
const count = frames.length;
|
|
60
|
+
const isControlled = index !== undefined;
|
|
61
|
+
const [ownIndex, setOwnIndex] = useState(defaultIndex);
|
|
62
|
+
const current = clampIndex(isControlled ? index : ownIndex, count);
|
|
63
|
+
const latest = useRef(current);
|
|
64
|
+
latest.current = current;
|
|
65
|
+
const onChangeRef = useRef(onIndexChange);
|
|
66
|
+
onChangeRef.current = onIndexChange;
|
|
67
|
+
const commit = (next) => {
|
|
68
|
+
var _a;
|
|
69
|
+
const target = clampIndex(next, count);
|
|
70
|
+
if (target === latest.current)
|
|
71
|
+
return;
|
|
72
|
+
latest.current = target;
|
|
73
|
+
if (!isControlled)
|
|
74
|
+
setOwnIndex(target);
|
|
75
|
+
(_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, target);
|
|
76
|
+
};
|
|
77
|
+
const commitRef = useRef(commit);
|
|
78
|
+
commitRef.current = commit;
|
|
79
|
+
const trackRef = useRef(null);
|
|
80
|
+
const railRef = useRef(null);
|
|
81
|
+
const settling = useRef(null);
|
|
82
|
+
const settleTimer = useRef();
|
|
83
|
+
const alignedTrack = useRef(null);
|
|
84
|
+
const lightboxReturn = useRef(null);
|
|
85
|
+
const [lightboxOpen, setLightboxOpen] = useState(false);
|
|
86
|
+
const [inner, setInner] = useState({ on: false, x: 50, y: 50 });
|
|
87
|
+
const [fadeKey, setFadeKey] = useState(0);
|
|
88
|
+
const seenSignature = useRef(signature);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (seenSignature.current === signature)
|
|
91
|
+
return;
|
|
92
|
+
seenSignature.current = signature;
|
|
93
|
+
setFadeKey((key) => key + 1);
|
|
94
|
+
commitRef.current(0);
|
|
95
|
+
}, [signature]);
|
|
96
|
+
useEffect(() => setInner((state) => (state.on ? { ...state, on: false } : state)), [current, signature]);
|
|
97
|
+
useEffect(() => () => window.clearTimeout(settleTimer.current), []);
|
|
98
|
+
useLayoutEffect(() => {
|
|
99
|
+
var _a;
|
|
100
|
+
const track = trackRef.current;
|
|
101
|
+
const width = (_a = track === null || track === void 0 ? void 0 : track.clientWidth) !== null && _a !== void 0 ? _a : 0;
|
|
102
|
+
if (!track || !width)
|
|
103
|
+
return;
|
|
104
|
+
const firstPass = alignedTrack.current !== track;
|
|
105
|
+
alignedTrack.current = track;
|
|
106
|
+
const left = current * width;
|
|
107
|
+
if (Math.abs(track.scrollLeft - left) < 2)
|
|
108
|
+
return;
|
|
109
|
+
settling.current = current;
|
|
110
|
+
window.clearTimeout(settleTimer.current);
|
|
111
|
+
settleTimer.current = window.setTimeout(() => {
|
|
112
|
+
settling.current = null;
|
|
113
|
+
}, SETTLE_MS);
|
|
114
|
+
if (firstPass || reduced || typeof track.scrollTo !== "function")
|
|
115
|
+
track.scrollLeft = left;
|
|
116
|
+
else
|
|
117
|
+
track.scrollTo({ left, behavior: "smooth" });
|
|
118
|
+
}, [current, fadeKey, arrangement, reduced]);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const rail = railRef.current;
|
|
121
|
+
const selected = rail === null || rail === void 0 ? void 0 : rail.querySelector('[aria-current="true"]');
|
|
122
|
+
if (rail && selected)
|
|
123
|
+
revealWithin(rail, selected, arrangement === "thumbs-left" ? "y" : "x");
|
|
124
|
+
}, [current, fadeKey, arrangement]);
|
|
125
|
+
const onScroll = () => {
|
|
126
|
+
const track = trackRef.current;
|
|
127
|
+
if (!track || !track.clientWidth)
|
|
128
|
+
return;
|
|
129
|
+
const at = clampIndex(Math.round(track.scrollLeft / track.clientWidth), count);
|
|
130
|
+
if (settling.current !== null) {
|
|
131
|
+
if (at === settling.current)
|
|
132
|
+
settling.current = null;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
commit(at);
|
|
136
|
+
};
|
|
137
|
+
const onKeyDown = (event) => {
|
|
138
|
+
if (event.ctrlKey || event.metaKey || event.altKey || keyBelongsToTarget(event.target))
|
|
139
|
+
return;
|
|
140
|
+
const next = indexForKey(event.key, latest.current, count);
|
|
141
|
+
if (next === undefined)
|
|
142
|
+
return;
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
commit(next);
|
|
145
|
+
};
|
|
146
|
+
const openLightbox = (event) => {
|
|
147
|
+
lightboxReturn.current = event.currentTarget;
|
|
148
|
+
setLightboxOpen(true);
|
|
149
|
+
};
|
|
150
|
+
const toggleInner = (event) => {
|
|
151
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
152
|
+
const x = box.width ? ((event.clientX - box.left) / box.width) * 100 : 50;
|
|
153
|
+
const y = box.height ? ((event.clientY - box.top) / box.height) * 100 : 50;
|
|
154
|
+
setInner((state) => ({ on: !state.on, x, y }));
|
|
155
|
+
};
|
|
156
|
+
const panInner = (event) => {
|
|
157
|
+
if (!inner.on || event.pointerType !== "mouse")
|
|
158
|
+
return;
|
|
159
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
160
|
+
if (!box.width || !box.height)
|
|
161
|
+
return;
|
|
162
|
+
setInner({ on: true, x: ((event.clientX - box.left) / box.width) * 100, y: ((event.clientY - box.top) / box.height) * 100 });
|
|
163
|
+
};
|
|
164
|
+
const ratio = aspectRatio.replace("/", " / ");
|
|
165
|
+
const radius = squared ? 0 : "2xl";
|
|
166
|
+
const showArrows = count > 1 && (finePointer || !isSwipe);
|
|
167
|
+
const showRail = count > 1 && !isSwipe;
|
|
168
|
+
const arrowSize = isSwipe ? 36 : 44;
|
|
169
|
+
const arrowInset = isSwipe ? 12 : 16;
|
|
170
|
+
const currentFrame = frames[current];
|
|
171
|
+
const zoomable = zoom !== "none" && currentFrame !== undefined && frameKind(currentFrame) === "image";
|
|
172
|
+
if (isLoading)
|
|
173
|
+
return _jsx(GallerySkeleton, { arrangement: arrangement, aspectRatio: ratio, rounded: !squared, label: labels.loading });
|
|
174
|
+
const thumbRing = { gap: colors.background[50], ink: colors.header[900] };
|
|
175
|
+
const thumbs = (width, height) => frames.map((frame, position) => (_jsx(MediaThumb, { media: frame, label: labels.frame(position + 1, count, frame.alt), selected: position === current, onSelect: () => commit(position), width: width, height: height, ring: thumbRing }, frame.id)));
|
|
176
|
+
const glassDots = glassStyles(colors);
|
|
177
|
+
const focusInset = { outline: "2px solid transparent", boxShadow: `inset 0 0 0 2px ${colors.focusRing.halo}, inset 0 0 0 4px ${colors.focusRing.ring}` };
|
|
178
|
+
const well = (_jsxs(Box, { position: "relative", flex: 1, minW: 0, overflow: "hidden", isolation: "isolate", borderRadius: radius, bg: colors.background[100], sx: { aspectRatio: ratio }, "data-testid": "media-gallery-well", children: [_jsxs(Flex, { ref: trackRef, position: "absolute", inset: 0, tabIndex: 0, role: "group", "aria-label": labels.region, overflowX: "auto", overflowY: "hidden", animation: fadeKey > 0 ? fadeIn(CROSSFADE_MS) : undefined, onScroll: onScroll, onPointerDown: () => {
|
|
179
|
+
settling.current = null;
|
|
180
|
+
}, outline: "none", _focusVisible: focusInset, sx: {
|
|
181
|
+
scrollSnapType: "x mandatory",
|
|
182
|
+
overscrollBehaviorX: "contain",
|
|
183
|
+
...HIDDEN_SCROLLBAR,
|
|
184
|
+
...reducedMotionSx(),
|
|
185
|
+
}, "data-testid": "media-gallery-track", children: [frames.map((frame, position) => {
|
|
186
|
+
var _a;
|
|
187
|
+
const isCurrent = position === current;
|
|
188
|
+
const kind = frameKind(frame);
|
|
189
|
+
const alt = (_a = frame.alt) !== null && _a !== void 0 ? _a : labels.image(position + 1, count);
|
|
190
|
+
const view = (_jsx(MediaFrame, { media: frame, alt: alt, active: isCurrent, eager: Math.abs(position - current) <= 1, zoom: zoom === "inner" && isCurrent && inner.on ? { scale: INNER_ZOOM, x: inner.x, y: inner.y } : undefined, playLabel: labels.playVideo, unavailableLabel: labels.imageUnavailable }));
|
|
191
|
+
const interactive = zoom !== "none" && kind === "image";
|
|
192
|
+
return (_jsx(Box, { role: "group", "aria-roledescription": "slide", "aria-label": labels.slide(position + 1, count), "aria-hidden": isCurrent ? undefined : true, flex: "0 0 100%", h: "100%", position: "relative", opacity: reduced && !isCurrent ? 0 : 1, transition: reduced ? transitionFor(["opacity"], "fast") : undefined, sx: { scrollSnapAlign: "start", scrollSnapStop: "always" }, children: interactive ? (_jsx(Box, { as: "button", type: "button", w: "100%", h: "100%", p: 0, border: "0", bg: "transparent", display: "block", overflow: "hidden", cursor: "zoom-in", tabIndex: isCurrent ? 0 : -1, "aria-label": zoom === "lightbox" ? labels.openLightbox(position + 1, count) : labels.zoomImage(position + 1, count), "aria-haspopup": zoom === "lightbox" ? "dialog" : undefined, "aria-pressed": zoom === "inner" ? isCurrent && inner.on : undefined, onClick: zoom === "lightbox" ? openLightbox : toggleInner, onPointerMove: zoom === "inner" ? panInner : undefined, outline: "none", _focusVisible: focusInset, sx: { cursor: zoom === "inner" && inner.on ? "zoom-out" : "zoom-in" }, children: view })) : (view) }, frame.id));
|
|
193
|
+
}), count === 0 && _jsx(Box, { flex: "0 0 100%", bg: colors.background[100] })] }, fadeKey), badges && (_jsx(Box, { position: "absolute", top: 3, left: 3, zIndex: 2, pointerEvents: "none", "data-testid": "media-gallery-badges", children: badges })), actions && (_jsx(Box, { position: "absolute", top: 3, right: 3, zIndex: 3, "data-testid": "media-gallery-actions", children: actions })), showArrows && (_jsxs(_Fragment, { children: [_jsx(GlassIconButton, { label: labels.previous, icon: _jsx(ChevronLeft, { size: arrowSize / 2, "aria-hidden": true }), onClick: () => commit(current - 1), isDisabled: current === 0, size: arrowSize, left: `${arrowInset}px`, top: `calc(50% - ${arrowSize / 2}px)`, zIndex: 2 }), _jsx(GlassIconButton, { label: labels.next, icon: _jsx(ChevronRight, { size: arrowSize / 2, "aria-hidden": true }), onClick: () => commit(current + 1), isDisabled: current === count - 1, size: arrowSize, right: `${arrowInset}px`, top: `calc(50% - ${arrowSize / 2}px)`, zIndex: 2 })] })), count > 1 && isSwipe && count <= MAX_DOTS && (_jsx(Flex, { role: "group", "aria-label": labels.dots, position: "absolute", left: "50%", bottom: 4, transform: "translateX(-50%)", zIndex: 2, h: "28px", px: 1.5, align: "center", borderRadius: "full", ...glassDots, "data-testid": "media-gallery-dots", children: frames.map((frame, position) => {
|
|
194
|
+
const selected = position === current;
|
|
195
|
+
return (_jsx(Box, { as: "button", type: "button", onClick: () => commit(position), "aria-label": labels.image(position + 1, count), "aria-current": selected ? "true" : undefined, display: "inline-flex", alignItems: "center", justifyContent: "center", h: "28px", w: selected ? "30px" : "20px", p: 0, border: "0", bg: "transparent", cursor: "pointer", transition: transitionFor(["width"]), borderRadius: "full", sx: { ...focusVisibleStyles(colors), ...reducedMotionSx() }, children: _jsx(Box, { as: "span", boxSize: "6px", borderRadius: "full", bg: "currentColor", opacity: selected ? 0.92 : 0.3, transform: selected ? "scaleX(2.4)" : "scaleX(1)", transition: transitionFor(["transform", "opacity"]) }) }, frame.id));
|
|
196
|
+
}) })), count > 1 && (_jsx(GlassPill, { ...(isSwipe ? { right: 4, bottom: "19px", height: 22 } : { left: 4, bottom: 4, height: 32 }), zIndex: 2, children: _jsx("span", { "aria-live": "polite", "data-testid": "media-gallery-counter", children: labels.counter(current + 1, count) }) })), !isSwipe && finePointer && zoomable && zoom === "lightbox" && (_jsxs(GlassPill, { right: 4, bottom: 4, height: 32, zIndex: 2, children: [_jsx(ZoomIn, { size: 14, "aria-hidden": true }), _jsx(Text, { as: "span", children: labels.zoomHint })] }))] }));
|
|
197
|
+
const rail = showRail &&
|
|
198
|
+
(arrangement === "thumbs-left" ? (_jsx(Box, { position: "relative", w: "72px", flexShrink: 0, alignSelf: "stretch", "data-testid": "media-gallery-rail", children: _jsx(Flex, { ref: railRef, role: "group", "aria-label": labels.thumbnails, position: "absolute", inset: "-4px", p: "4px", direction: "column", gap: 3, overflowY: "auto", overflowX: "hidden", animation: fadeKey > 0 ? fadeIn(CROSSFADE_MS) : undefined, sx: { ...scrollbarStyles(colors, "hover"), ...reducedMotionSx() }, children: thumbs(72, 90) }, fadeKey) })) : (_jsx(Flex, { ref: railRef, role: "group", "aria-label": labels.thumbnails, gap: 3, m: "-4px", p: "4px", overflowX: "auto", animation: fadeKey > 0 ? fadeIn(CROSSFADE_MS) : undefined, sx: { ...scrollbarStyles(colors, "hover"), ...reducedMotionSx() }, "data-testid": "media-gallery-rail", children: thumbs(64, 80) }, fadeKey)));
|
|
199
|
+
return (_jsxs(_Fragment, { children: [_jsx(Box, { ref: rootRef, role: "region", "aria-roledescription": "carousel", "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : labels.region, onKeyDown: onKeyDown, w: "100%", position: sticky ? "sticky" : "relative", top: sticky ? `${stickyTop}px` : undefined, alignSelf: sticky ? "flex-start" : undefined, "data-testid": testId, children: _jsxs(Flex, { direction: arrangement === "thumbs-left" ? "row" : "column", gap: 4, align: arrangement === "thumbs-left" ? "stretch" : undefined, children: [arrangement === "thumbs-left" && rail, well, arrangement === "thumbs-below" && rail] }) }), _jsx(MediaLightbox, { isOpen: lightboxOpen, onClose: () => setLightboxOpen(false), items: frames, index: current, onIndexChange: commit, finalFocusRef: lightboxReturn, labels: lightboxLabels })] }));
|
|
200
|
+
};
|
|
201
|
+
export default MediaGallery;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const defaultMediaGalleryLabels = {
|
|
2
|
+
region: "Product images",
|
|
3
|
+
slide: (position, total) => `${position} of ${total}`,
|
|
4
|
+
image: (position, total) => `Image ${position} of ${total}`,
|
|
5
|
+
frame: (position, total, alt) => `Image ${position} of ${total}${alt ? `: ${alt}` : ""}`,
|
|
6
|
+
previous: "Previous image",
|
|
7
|
+
next: "Next image",
|
|
8
|
+
dots: "Choose an image",
|
|
9
|
+
thumbnails: "Image thumbnails",
|
|
10
|
+
counter: (position, total) => `${position} / ${total}`,
|
|
11
|
+
zoomHint: "Click to zoom",
|
|
12
|
+
openLightbox: (position, total) => `View image ${position} of ${total} full screen`,
|
|
13
|
+
zoomImage: (position, total) => `Zoom image ${position} of ${total}`,
|
|
14
|
+
playVideo: "Play video",
|
|
15
|
+
imageUnavailable: "Image unavailable",
|
|
16
|
+
loading: "Loading product images",
|
|
17
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Box, Flex, Text } from "@chakra-ui/react";
|
|
4
|
+
import { ChevronLeft, ChevronRight, X } from "lucide-react";
|
|
5
|
+
import Modal, { ModalBody } from "../Modal/Modal.js";
|
|
6
|
+
import ViewerIconButton from "../MediaViewer/components/ViewerIconButton.js";
|
|
7
|
+
import { useViewerChrome } from "../MediaViewer/useViewerChrome.js";
|
|
8
|
+
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
9
|
+
import { overlayScrim } from "../../Theme/tokens/builders/scrim.js";
|
|
10
|
+
import { fullViewportHeight } from "../../Theme/tokens/builders/viewportHeight.js";
|
|
11
|
+
import { reducedMotionSx, usePrefersReducedMotion } from "../../Theme/chakra/motion.js";
|
|
12
|
+
import MediaFrame from "./MediaFrame.js";
|
|
13
|
+
import MediaThumb from "./MediaThumb.js";
|
|
14
|
+
import { fadeIn } from "./motion.js";
|
|
15
|
+
import { clampIndex, indexForKey, keyBelongsToTarget } from "./mediaGalleryModel.js";
|
|
16
|
+
import { defaultMediaLightboxLabels } from "./MediaLightboxProps.js";
|
|
17
|
+
const SWIPE_MIN_PX = 50;
|
|
18
|
+
/** Full-screen product media viewer: counter and close on top, arrows, thumbnail row, Esc and arrow keys. */
|
|
19
|
+
const MediaLightbox = ({ isOpen, onClose, items, index, defaultIndex = 0, onIndexChange, finalFocusRef, labels: labelOverrides, }) => {
|
|
20
|
+
var _a;
|
|
21
|
+
const { colors } = useCustomTheme();
|
|
22
|
+
const chrome = useViewerChrome();
|
|
23
|
+
const reduced = usePrefersReducedMotion();
|
|
24
|
+
const labels = { ...defaultMediaLightboxLabels, ...labelOverrides };
|
|
25
|
+
const isControlled = index !== undefined;
|
|
26
|
+
const [own, setOwn] = useState(defaultIndex);
|
|
27
|
+
const total = items.length;
|
|
28
|
+
const current = clampIndex(isControlled ? index : own, total);
|
|
29
|
+
const latest = useRef(current);
|
|
30
|
+
latest.current = current;
|
|
31
|
+
const stageRef = useRef(null);
|
|
32
|
+
const stripRef = useRef(null);
|
|
33
|
+
const touchStartX = useRef(null);
|
|
34
|
+
const go = (next) => {
|
|
35
|
+
const target = clampIndex(next, total);
|
|
36
|
+
if (target === latest.current)
|
|
37
|
+
return;
|
|
38
|
+
latest.current = target;
|
|
39
|
+
if (!isControlled)
|
|
40
|
+
setOwn(target);
|
|
41
|
+
onIndexChange === null || onIndexChange === void 0 ? void 0 : onIndexChange(target);
|
|
42
|
+
};
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const strip = stripRef.current;
|
|
45
|
+
const selected = strip === null || strip === void 0 ? void 0 : strip.querySelector('[aria-current="true"]');
|
|
46
|
+
if (!strip || !selected)
|
|
47
|
+
return;
|
|
48
|
+
const left = selected.offsetLeft - strip.clientWidth / 2 + selected.offsetWidth / 2;
|
|
49
|
+
strip.scrollLeft = Math.max(0, left);
|
|
50
|
+
}, [current, isOpen]);
|
|
51
|
+
const onKeyDown = (event) => {
|
|
52
|
+
if (event.ctrlKey || event.metaKey || event.altKey || keyBelongsToTarget(event.target))
|
|
53
|
+
return;
|
|
54
|
+
const next = indexForKey(event.key, latest.current, total);
|
|
55
|
+
if (next === undefined)
|
|
56
|
+
return;
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
go(next);
|
|
59
|
+
};
|
|
60
|
+
const onTouchEnd = (event) => {
|
|
61
|
+
const start = touchStartX.current;
|
|
62
|
+
touchStartX.current = null;
|
|
63
|
+
if (start === null)
|
|
64
|
+
return;
|
|
65
|
+
const dx = event.changedTouches[0].clientX - start;
|
|
66
|
+
if (Math.abs(dx) > SWIPE_MIN_PX)
|
|
67
|
+
go(latest.current + (dx < 0 ? 1 : -1));
|
|
68
|
+
};
|
|
69
|
+
const item = items[current];
|
|
70
|
+
if (!item)
|
|
71
|
+
return null;
|
|
72
|
+
const alt = (_a = item.alt) !== null && _a !== void 0 ? _a : labels.image(current + 1, total);
|
|
73
|
+
const bar = { bg: overlayScrim(colors), color: chrome.ink };
|
|
74
|
+
return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, size: "full", hideCloseButton: true, motionPreset: reduced ? "none" : "scale", initialFocusRef: stageRef, finalFocusRef: finalFocusRef, overlaybg: overlayScrim(colors), contentProps: { bg: chrome.stage, m: 0, borderRadius: 0, "aria-label": labels.dialog }, children: _jsxs(ModalBody, { p: 0, display: "flex", flexDirection: "column", sx: fullViewportHeight, children: [_jsxs(Flex, { align: "center", justify: "space-between", gap: 2, px: 4, py: 2, ...bar, "data-testid": "media-lightbox-bar", children: [_jsx(Text, { as: "span", fontSize: "sm", fontWeight: 600, "aria-live": "polite", sx: { fontVariantNumeric: "tabular-nums" }, children: labels.counter(current + 1, total) }), _jsx(ViewerIconButton, { label: labels.close, icon: _jsx(X, { size: 20, "aria-hidden": true }), onClick: onClose, testId: "media-lightbox-close" })] }), _jsxs(Box, { ref: stageRef, tabIndex: -1, flex: 1, minH: 0, position: "relative", outline: "none", onKeyDown: onKeyDown, onTouchStart: (event) => {
|
|
75
|
+
touchStartX.current = event.touches.length === 1 ? event.touches[0].clientX : null;
|
|
76
|
+
}, onTouchEnd: onTouchEnd, "data-testid": "media-lightbox-stage", children: [_jsx(Box, { position: "absolute", inset: { base: 0, md: 6 }, animation: fadeIn(), sx: reducedMotionSx(), children: _jsx(MediaFrame, { media: item, alt: alt, fit: "contain", autoPlay: true, surface: "transparent", playLabel: labels.playVideo, unavailableLabel: labels.imageUnavailable }) }, item.id), total > 1 && (_jsxs(_Fragment, { children: [_jsx(Box, { position: "absolute", left: { base: 2, md: 4 }, top: "calc(50% - 20px)", zIndex: 2, children: _jsx(ViewerIconButton, { label: labels.previous, icon: _jsx(ChevronLeft, { size: 24, "aria-hidden": true }), onClick: () => go(current - 1), isDisabled: current === 0, round: true, testId: "media-lightbox-prev" }) }), _jsx(Box, { position: "absolute", right: { base: 2, md: 4 }, top: "calc(50% - 20px)", zIndex: 2, children: _jsx(ViewerIconButton, { label: labels.next, icon: _jsx(ChevronRight, { size: 24, "aria-hidden": true }), onClick: () => go(current + 1), isDisabled: current === total - 1, round: true, testId: "media-lightbox-next" }) })] }))] }), total > 1 && (_jsx(Flex, { as: "nav", "aria-label": labels.thumbnails, ...bar, py: 3, px: 4, justify: "center", children: _jsx(Flex, { ref: stripRef, gap: 3, p: "4px", maxW: "100%", overflowX: "auto", onKeyDown: onKeyDown, "data-testid": "media-lightbox-thumbs", children: items.map((frame, position) => (_jsx(MediaThumb, { media: frame, label: labels.frame(position + 1, total, frame.alt), selected: position === current, onSelect: () => go(position), width: 56, height: 70, ring: { gap: chrome.stage, ink: chrome.ink }, surface: chrome.raised }, frame.id))) }) }))] }) }));
|
|
77
|
+
};
|
|
78
|
+
export default MediaLightbox;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const defaultMediaLightboxLabels = {
|
|
2
|
+
dialog: "Product images",
|
|
3
|
+
close: "Close",
|
|
4
|
+
previous: "Previous image",
|
|
5
|
+
next: "Next image",
|
|
6
|
+
counter: (position, total) => `${position} / ${total}`,
|
|
7
|
+
thumbnails: "Image thumbnails",
|
|
8
|
+
frame: (position, total, alt) => `Image ${position} of ${total}${alt ? `: ${alt}` : ""}`,
|
|
9
|
+
image: (position, total) => `Image ${position} of ${total}`,
|
|
10
|
+
playVideo: "Play video",
|
|
11
|
+
imageUnavailable: "Image unavailable",
|
|
12
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
|
+
import { Box, Flex } from "@chakra-ui/react";
|
|
4
|
+
import { Play, Video } from "lucide-react";
|
|
5
|
+
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
6
|
+
import { overlayScrim } from "../../Theme/tokens/builders/scrim.js";
|
|
7
|
+
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
8
|
+
import { reducedMotionSx } from "../../Theme/chakra/motion.js";
|
|
9
|
+
import { focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
|
|
10
|
+
import { frameThumb } from "./mediaGalleryModel.js";
|
|
11
|
+
/** Selectable thumbnail: ring and full opacity when current, `aria-current` for assistive tech. */
|
|
12
|
+
const MediaThumb = ({ media, label, selected, onSelect, width, height, ring, surface }) => {
|
|
13
|
+
const { colors } = useCustomTheme();
|
|
14
|
+
const [loaded, setLoaded] = useState(false);
|
|
15
|
+
const [failed, setFailed] = useState(false);
|
|
16
|
+
const src = frameThumb(media);
|
|
17
|
+
const settle = useCallback((node) => {
|
|
18
|
+
if (node && node.complete && node.naturalWidth > 0)
|
|
19
|
+
setLoaded(true);
|
|
20
|
+
}, []);
|
|
21
|
+
return (_jsxs(Box, { as: "button", type: "button", onClick: onSelect, "aria-label": label, "aria-current": selected ? "true" : undefined, position: "relative", flexShrink: 0, w: `${width}px`, h: `${height}px`, p: 0, border: "0", borderRadius: "xl", overflow: "hidden", cursor: "pointer", bg: surface !== null && surface !== void 0 ? surface : colors.background[100], opacity: selected ? 1 : 0.72, boxShadow: selected ? `0 0 0 2px ${ring.gap}, 0 0 0 4px ${ring.ink}` : "none", transition: transitionFor(["box-shadow", "opacity", "transform"]), _hover: { opacity: 1, transform: "translateY(-1px)" }, sx: { ...focusVisibleStyles(colors), ...reducedMotionSx({ transform: "none" }) }, children: [src && !failed ? (_jsx(Box, { as: "img", ref: settle, src: src, alt: "", loading: "lazy", decoding: "async", draggable: false, onLoad: () => setLoaded(true), onError: () => setFailed(true), position: "absolute", inset: 0, w: "100%", h: "100%", objectFit: "cover", opacity: loaded ? 1 : 0, transition: transitionFor(["opacity"], "base") })) : (_jsx(Flex, { position: "absolute", inset: 0, align: "center", justify: "center", color: colors.textMuted, "aria-hidden": true, children: _jsx(Video, { size: 20 }) })), media.type === "video" && (_jsx(Flex, { position: "absolute", inset: 0, align: "center", justify: "center", color: colors.white, "aria-hidden": true, children: _jsx(Flex, { boxSize: 6, align: "center", justify: "center", borderRadius: "full", bg: overlayScrim(colors), children: _jsx(Play, { size: 12, fill: "currentColor" }) }) }))] }));
|
|
22
|
+
};
|
|
23
|
+
export default MediaThumb;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { Box, Flex } from "@chakra-ui/react";
|
|
4
|
+
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
5
|
+
import { hexToRgb } from "../../Theme/tokens/builders/color.js";
|
|
6
|
+
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
7
|
+
import { reducedMotionSx } from "../../Theme/chakra/motion.js";
|
|
8
|
+
import { focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
|
|
9
|
+
const GLASS_ALPHA = 0.72;
|
|
10
|
+
const GLASS_ALPHA_HOVER = 0.9;
|
|
11
|
+
const BLUR = "blur(20px) saturate(180%)";
|
|
12
|
+
const NO_BLUR_SUPPORT = "@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)))";
|
|
13
|
+
const tint = (colors, alpha) => {
|
|
14
|
+
const { r, g, b } = hexToRgb(colors.background[50]);
|
|
15
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
16
|
+
};
|
|
17
|
+
/** Translucent surface over a photo: the page canvas at 72%, blurred, opaque where blur is unsupported. */
|
|
18
|
+
export const glassStyles = (colors, hover = false) => ({
|
|
19
|
+
bg: tint(colors, hover ? GLASS_ALPHA_HOVER : GLASS_ALPHA),
|
|
20
|
+
color: colors.header[900],
|
|
21
|
+
backdropFilter: BLUR,
|
|
22
|
+
sx: { WebkitBackdropFilter: BLUR, [NO_BLUR_SUPPORT]: { backgroundColor: colors.background[50] } },
|
|
23
|
+
});
|
|
24
|
+
/** 36 or 44 px glass circle with a 44 px hit area; fades out instead of unmounting when disabled. */
|
|
25
|
+
export const GlassIconButton = forwardRef(({ label, icon, onClick, size = 36, isDisabled = false, tabIndex, ...placement }, ref) => {
|
|
26
|
+
const { colors } = useCustomTheme();
|
|
27
|
+
const glass = glassStyles(colors);
|
|
28
|
+
const reach = Math.max(0, (44 - size) / 2);
|
|
29
|
+
return (_jsx(Box, { as: "button", ref: ref, type: "button", "aria-label": label, onClick: onClick, disabled: isDisabled, tabIndex: tabIndex, display: "inline-flex", alignItems: "center", justifyContent: "center", w: `${size}px`, h: `${size}px`, borderRadius: "full", position: "absolute", flexShrink: 0, cursor: "pointer", opacity: isDisabled ? 0 : 1, pointerEvents: isDisabled ? "none" : "auto", transition: transitionFor(["opacity", "background-color"]), _hover: { bg: tint(colors, GLASS_ALPHA_HOVER) }, ...glass, sx: {
|
|
30
|
+
...glass.sx,
|
|
31
|
+
...focusVisibleStyles(colors),
|
|
32
|
+
"&::after": { content: '""', position: "absolute", inset: `-${reach}px` },
|
|
33
|
+
...reducedMotionSx(),
|
|
34
|
+
}, ...placement, children: icon }));
|
|
35
|
+
});
|
|
36
|
+
GlassIconButton.displayName = "GlassIconButton";
|
|
37
|
+
/** Non-interactive glass capsule for the counter and zoom hint. */
|
|
38
|
+
export const GlassPill = ({ children, height = 28, ...placement }) => {
|
|
39
|
+
const { colors } = useCustomTheme();
|
|
40
|
+
const glass = glassStyles(colors);
|
|
41
|
+
return (_jsx(Flex, { align: "center", gap: 1.5, h: `${height}px`, px: 3, borderRadius: "full", position: "absolute", fontSize: "xs", fontWeight: 600, pointerEvents: "none", whiteSpace: "nowrap", ...glass, sx: { ...glass.sx, fontVariantNumeric: "tabular-nums" }, ...placement, children: children }));
|
|
42
|
+
};
|