UNPKG

@amsterdam/design-system-react

Version:

All React components from the Amsterdam Design System. Use it to compose pages in your website or application.

98 lines (97 loc) 5.54 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { clsx } from 'clsx'; import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Image } from '../Image/Image'; import { ImageSliderContext } from './ImageSliderContext'; import { ImageSliderControls } from './ImageSliderControls'; import { ImageSliderItem } from './ImageSliderItem'; import { ImageSliderScroller } from './ImageSliderScroller'; import { ImageSliderThumbnails } from './ImageSliderThumbnails'; export const ImageSliderRoot = forwardRef(({ className, controls, imageLabel = 'Afbeelding', images, nextLabel = 'Volgende', previousLabel = 'Vorige', ...restProps }, ref) => { const [currentSlideId, setCurrentSlideId] = useState(0); const [isAtStart, setIsAtStart] = useState(true); const [isAtEnd, setIsAtEnd] = useState(false); const targetRef = useRef(null); const observerRef = useRef(null); const inView = useCallback((observations) => { const images = Array.from(targetRef.current?.children || []); observations.forEach((observation) => { if (observation.isIntersecting) { setCurrentSlideId(images.indexOf(observation.target)); } }); }, []); const observerOptions = useMemo(() => ({ root: targetRef.current, threshold: 0.6, }), []); const updateControls = useCallback(() => { const sliderScrollerElement = targetRef.current; if (!sliderScrollerElement) return; const { firstElementChild: firstElement, lastElementChild: lastElement } = sliderScrollerElement; setIsAtStart(firstElement === sliderScrollerElement?.children[currentSlideId]); setIsAtEnd(lastElement === sliderScrollerElement?.children[currentSlideId]); }, [currentSlideId]); useEffect(() => { if (targetRef.current) { observerRef.current = new IntersectionObserver(inView, observerOptions); const observer = observerRef.current; const slides = Array.from(targetRef.current.children); slides.forEach((slide) => observer.observe(slide)); targetRef.current.addEventListener('scrollend', synchronise); updateControls(); return () => { slides.forEach((slide) => observer.unobserve(slide)); targetRef.current?.removeEventListener('scrollend', synchronise); }; } return undefined; }, [inView, observerOptions, updateControls]); const synchronise = useCallback(() => updateControls(), [updateControls]); const goToSlide = useCallback((element) => { const sliderScrollerElement = targetRef.current; if (!sliderScrollerElement || !element) return; sliderScrollerElement.scrollTo({ left: sliderScrollerElement.scrollLeft + element.offsetLeft - sliderScrollerElement.scrollLeft, }); }, []); const goToSlideId = useCallback((id) => { const element = targetRef.current?.children[id]; if (element) goToSlide(element); }, [goToSlide]); const goToNextSlide = useCallback(() => { const element = targetRef.current?.children[currentSlideId]; const nextElement = element?.nextElementSibling; if (nextElement) goToSlide(nextElement); }, [currentSlideId, goToSlide]); const goToPreviousSlide = useCallback(() => { const element = targetRef.current?.children[currentSlideId]; const previousElement = element?.previousElementSibling; if (previousElement) goToSlide(previousElement); }, [currentSlideId, goToSlide]); useEffect(() => { const handleResize = () => { const sliderScrollerElement = targetRef.current; const currentSlideElement = targetRef.current?.children[currentSlideId]; if (!sliderScrollerElement || !currentSlideElement) return; const expectedScrollLeft = currentSlideElement.offsetLeft; if (Math.abs(sliderScrollerElement.scrollLeft - expectedScrollLeft) < 1) return; goToSlide(currentSlideElement); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [currentSlideId, goToSlide]); return (_jsx(ImageSliderContext.Provider, { value: { currentSlideId, goToNextSlide, goToPreviousSlide, goToSlideId, isAtEnd, isAtStart }, children: _jsxs("div", { ...restProps, "aria-roledescription": "carousel", className: clsx('ams-image-slider', controls && 'ams-image-slider--controls', className), ref: ref, tabIndex: -1, children: [controls && _jsx(ImageSliderControls, { nextLabel: nextLabel, previousLabel: previousLabel }), _jsx(ImageSliderScroller, { "aria-live": "polite", ref: targetRef, role: "group", tabIndex: 0, children: images.map(({ alt, aspectRatio, sizes, src, srcSet }, index) => (_jsx(ImageSliderItem, { slideId: index, children: _jsx(Image, { alt: alt, aspectRatio: aspectRatio, sizes: sizes, src: src, srcSet: srcSet }) }, index))) }), _jsx(ImageSliderThumbnails, { imageLabel: imageLabel, thumbnails: images })] }) })); }); ImageSliderRoot.displayName = 'ImageSlider'; /** * @see {@link https://designsystem.amsterdam/?path=/docs/components-media-image-slider--docs Image Slider docs at Amsterdam Design System} */ export const ImageSlider = Object.assign(ImageSliderRoot, { Item: ImageSliderItem });