keen-slider

mail@pastecode.io avatar
unknown
javascript
3 years ago
5.0 kB
456
Indexable
import Image from 'next/image'
import React, { MutableRefObject, useEffect, useRef, useState } from 'react'

import cn from 'clsx'
import {
  useKeenSlider,
  KeenSliderInstance,
  KeenSliderPlugin,
} from 'keen-slider/react'

import 'keen-slider/keen-slider.min.css'
import ProductSliderModal from './ProductSliderModal'

import { ProductImage } from '@commerce/types/product'

interface ProductSliderProps {
  productName: string
  media: ProductImage[]
}

function ThumbnailPlugin(
  mainRef: MutableRefObject<KeenSliderInstance | null>
): KeenSliderPlugin {
  return (slider) => {
    function removeActive() {
      slider.slides.forEach((slide) => {
        slide.classList.remove('active')
      })
    }
    function addActive(idx: number) {
      slider.slides[idx].classList.add('active')
    }

    function addClickEvents() {
      slider.slides.forEach((slide, idx) => {
        slide.addEventListener('click', () => {
          if (mainRef.current) mainRef.current.moveToIdx(idx)
        })
      })
    }

    slider.on('created', () => {
      if (!mainRef.current) return
      addActive(slider.track.details.rel)
      addClickEvents()
      mainRef.current.on('animationStarted', (main) => {
        removeActive()
        const next = main.animator.targetIdx || 0
        addActive(main.track.absToRel(next))
        slider.moveToIdx(next)
      })
    })
  }
}

const ProductSlider: React.FC<ProductSliderProps> = ({
  media,
  productName,
}) => {
  const [isMounted, setIsMounted] = useState(false)
  const [currentSlideIndex, setCurrentSlideIndex] = useState(0)
  const [isModalOpen, setModalOpen] = useState(false)
  const sliderContainerRef = useRef<HTMLDivElement>(null)

  const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({
    renderMode: 'performance',
    created: () => setIsMounted(true),
    initial: 0,
  })
  const [thumbnailRef, thumbnail] = useKeenSlider<HTMLDivElement>(
    {
      initial: 0,
      slides: {
        perView: 5,
        spacing: 10,
      },
    },
    [ThumbnailPlugin(slider)]
  )

  const hasThumbnail = media.length > 1

  const handleSlideClick = (idx: number) => {
    setCurrentSlideIndex(idx)
    setModalOpen(true)
  }

  // // Stop the history navigation gesture on touch devices
  useEffect(() => {
    const preventNavigation = (event: TouchEvent) => {
      // Center point of the touch area
      const touchXPosition = event.touches[0].pageX
      // Size of the touch area
      const touchXRadius = event.touches[0].radiusX || 0

      // We set a threshold (10px) on both sizes of the screen,
      // if the touch area overlaps with the screen edges
      // it's likely to trigger the navigation. We prevent the
      // touchstart event in that case.
      if (
        touchXPosition - touchXRadius < 10 ||
        touchXPosition + touchXRadius > window.innerWidth - 10
      )
        event.preventDefault()
    }

    const slider = sliderContainerRef.current!

    slider.addEventListener('touchstart', preventNavigation)

    return () => {
      if (slider) {
        console.log('removing touchstart')
        slider.removeEventListener('touchstart', preventNavigation)
      }
    }
  }, [])

  return (
    <>
      <div
        className="md:w-3/4 lg:w-full mb-8 md:mb-10 lg:mb-0"
        ref={sliderContainerRef}
      >
        <div
          ref={sliderRef}
          className={cn('keen-slider', {
            'mb-3': hasThumbnail,
          })}
        >
          {media.map(({ url, label }, index) => (
            <div
              key={url}
              className="keen-slider__slide"
              onClick={() => handleSlideClick(index)}
            >
              <div className="relative overflow-hidden rounded-lg h-[365px] lg:h-[582px]">
                <Image
                  src={url}
                  layout="fill"
                  objectFit="cover"
                  alt={label}
                  quality={100}
                />
              </div>
            </div>
          ))}
        </div>

        <div
          ref={thumbnailRef}
          className={cn('keen-slider thumbnail', {
            '!hidden': !hasThumbnail,
          })}
        >
          {hasThumbnail &&
            media.map(({ url, label }) => (
              <div key={url} className="keen-slider__slide rounded-lg">
                <div className="relative rounded-lg overflow-hidden h-14">
                  <Image
                    src={url}
                    layout="fill"
                    objectFit="cover"
                    alt={label}
                    quality={100}
                  />
                </div>
              </div>
            ))}
        </div>
      </div>

      {isModalOpen && (
        <ProductSliderModal
          productName={productName}
          isOpen={isModalOpen}
          onClose={() => setModalOpen(false)}
          media={media}
          initialSlide={currentSlideIndex}
        />
      )}
    </>
  )
}

export default ProductSlider