UNPKG

@shopgate/engage

Version:
171 lines (163 loc) 6.03 kB
import { useMemo } from 'react'; import { useWidget } from '@shopgate/engage/page/hooks'; import { useTheme } from '@shopgate/engage/styles'; import { resolveBorderRadiusFromWidgetConfig } from "../../helpers"; /** * @typedef {import('swiper/react').SwiperProps} SwiperCmpProps */ /** * @typedef {Object} ImageSliderImageData * @property {string} url The image URL. * @property {string} [altText] The image alt text. */ /** * @typedef {Object} ImageSliderImage * @property {ImageSliderImageData} image The image data object. * @property {string} [link] The link URL. */ /** * @typedef {Object} ImageSliderWidgetConfig * @property {ImageSliderImage[]} images The image objects. * @property {boolean} slideAutomatic Whether the slider should automatically slide. * @property {boolean} endlessSlider Whether the slider should loop endlessly. * @property {number} sliderSpeed The speed (in ms) for the slider autoplay. * @property {"default"|"dense"|"custom"} slidesPerView * @property {number} slidesPerViewCustomSmall Slides per view for small screens. * @property {number} slidesPerViewCustomMedium Slides per view for medium screens. * @property {number} slidesPerViewCustomLarge Slides per view for large screens. * @property {number} imageSpacing Optional gap between image slides (in pixels). * @property {"default"|"off"|"bullets"|"progressbar"|"fraction"} paginationStyle * @property {"default"|"none"|"rounded"|"custom"} borderRadius The border radius option. * @property {number} [borderRadiusCustom] The custom border radius value. * the pagination type for the slider. */ /** * @typedef {ReturnType<typeof import('@shopgate/engage/page/hooks') * .useWidget<ImageSliderWidgetConfig> >} UseWidgetReturnType */ // eslint-disable-next-line valid-jsdoc /** * Hook to access the ImageSlider widget configuration and data. */ export const useImageSliderWidget = () => { /** @type {UseWidgetReturnType} */ const { config, isPreview, layout } = useWidget(); const theme = useTheme(); const { images, slideAutomatic, endlessSlider, sliderSpeed, slidesPerView, slidesPerViewCustomSmall, slidesPerViewCustomMedium, slidesPerViewCustomLarge, imageSpacing, paginationStyle = 'bullets', borderRadius, borderRadiusCustom } = config; const borderRadiusResolved = resolveBorderRadiusFromWidgetConfig({ borderRadius, borderRadiusCustom }); const paginationType = useMemo(() => paginationStyle === 'default' ? 'bullets' : paginationStyle.toLowerCase(), [paginationStyle]); const imagesWithUrls = useMemo(() => images.filter(img => img?.image?.url), [images]); /** * @type {SwiperCmpProps} */ const swiperProps = useMemo(() => { let slidesPerViewSmall = 1.0; let slidesPerViewMedium = 1.3; let slidesPerViewLarge = 1.6; if (slidesPerView === 'dense') { slidesPerViewSmall = 1.3; slidesPerViewMedium = 1.8; slidesPerViewLarge = 2.3; } else if (slidesPerView === 'custom') { slidesPerViewSmall = slidesPerViewCustomSmall; slidesPerViewMedium = slidesPerViewCustomMedium; slidesPerViewLarge = slidesPerViewCustomLarge; } /** * Special image spacing for slides with a SINGLE slide per view. * * Needs to be at least as large as the highest horizontal layout margin (when set) to avoid * showing of more than one slide. * @type {number} */ const imageSpacingForSingleSlide = Math.max(layout?.marginLeft ?? 0, layout?.marginRight ?? 0, imageSpacing); const breakpoints = { [theme.breakpoints.values.sm]: { slidesPerView: slidesPerViewMedium, ...(slidesPerViewMedium === 1 && imageSpacingForSingleSlide ? { spaceBetween: imageSpacingForSingleSlide } : { spaceBetween: imageSpacing }) }, [theme.breakpoints.values.md]: { slidesPerView: slidesPerViewLarge, ...(slidesPerViewLarge === 1 && imageSpacingForSingleSlide ? { spaceBetween: imageSpacingForSingleSlide } : { spaceBetween: imageSpacing }) } }; const showPagination = paginationType !== 'off' && imagesWithUrls.length > 1; // Create a key that changes when relevant config changes, to force remount of Swiper const componentKey = isPreview ? JSON.stringify({ slidesPerView, spaceBetween: imageSpacing, paginationType, showPagination, ...breakpoints }) : null; return { autoplay: slideAutomatic ? { delay: sliderSpeed } : false, loop: endlessSlider, slidesPerView: slidesPerViewSmall, breakpoints, pagination: showPagination ? { type: paginationType, clickable: true, dynamicBullets: true } : false, // Prevent cut-off sliders when margins are used in the layout ...(layout.marginLeft || layout.marginRight ? { style: { ...(layout.marginLeft ? { marginLeft: layout.marginLeft * -1, paddingLeft: layout.marginLeft } : {}), ...(layout.marginRight ? { marginRight: layout.marginRight * -1, paddingRight: layout.marginRight } : {}) } } : null), ...(slidesPerViewSmall === 1 && imageSpacingForSingleSlide ? { spaceBetween: imageSpacingForSingleSlide } : { spaceBetween: imageSpacing }), ...(isPreview ? { key: componentKey, // Improves interaction with the slider in the CMS preview iframe touchStartPreventDefault: true } : {}) }; }, [slidesPerView, theme.breakpoints.values.sm, theme.breakpoints.values.md, paginationType, imagesWithUrls.length, imageSpacing, slideAutomatic, sliderSpeed, endlessSlider, isPreview, slidesPerViewCustomSmall, slidesPerViewCustomMedium, slidesPerViewCustomLarge, layout]); return { slides: imagesWithUrls, swiperProps, borderRadius: borderRadiusResolved }; };