cyb/src/components/Tabs/Carousel/Carousel.tsx

/* eslint-disable react-hooks/rules-of-hooks */
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useMediaQuery from '../../../hooks/useMediaQuery';
import TabItem, { Position } from '../TabItem/TabItem';
import styles from './Carousel.module.scss';

const cx = require('classnames');

interface Slide {
  step?: number;
  title?: string | JSX.Element;
}

type Color = 'green' | 'blue';

type CarouselProps = {
  slides: Slide[];
  // speed?: number;
  activeStep?: number;
  // setStep: (step: number) => void;
  // transitionSpeed?: number;
  // slideWidth?: number;
  heightSlide?: string;
  // disableNext?: boolean;
  disableMode?: boolean;
  displaySlide?: number;
  color?: Color;
  noAnimation?: boolean;
  onChange: (index: number) => void;
};

function Carousel({
  slides = [],
  speed = 3000,
  activeStep = 1,
  setStep,
  transitionSpeed = 500,
  slideWidth = 200,
  heightSlide,
  disableNext,
  onChange,
  noAnimation,
  color = 'green',
  disableMode,
  displaySlide = 3,
}: CarouselProps) {
  const query = useMediaQuery('(min-width: 768px)');
  const [itemWidth, setItemWidth] = useState(0);
  const [displaySlideState, setDisplaySlideState] = useState(displaySlide);
  const [visibleSlide, setVisibleSlide] = useState(1);
  const [hasTransitionClass, setHasTransitionClass] = useState(!noAnimation);
  const changeDisplay = useRef(false);

  useEffect(() => {
    if (displaySlide > 3) {
      if (!query) {
        setDisplaySlideState(3);
        changeDisplay.current = true;
      }
      if (query && changeDisplay.current) {
        setDisplaySlideState(displaySlide);
        changeDisplay.current = false;
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [query, displaySlide]);

  if (slides.length < 2) {
    console.error('Please provide more slides');
    return null;
  }

  useEffect(() => {
    setVisibleSlide(slides.length * 2 + activeStep);

    const resizeCarousel = () => {
      const { clientWidth } = document.getElementById('containerCarousel');

      setItemWidth(clientWidth / displaySlideState);
    };

    resizeCarousel();
    window.addEventListener('resize', resizeCarousel);

    return () => {
      window.removeEventListener('resize', resizeCarousel);
    };
  }, [activeStep, slides, displaySlideState]);

  const calculateLeftMargin = useMemo(() => {
    if (itemWidth !== 0) {
      return `-${visibleSlide * itemWidth - itemWidth * Math.floor(displaySlideState / 2)}px`;
    }
    return '0px';
  }, [visibleSlide, itemWidth, displaySlideState]);

  useEffect(() => {
    if (noAnimation) {
      return;
    }

    // make carousel infinite
    if (visibleSlide > slides.length * 2) {
      // keep index near the middle of the list when moving left
      // setNavDisabled(true); // disable nav buttons while silently moving to different card
      setTimeout(() => {
        // disable transition animation after card slides, then snap to same card prior in the list
        setHasTransitionClass(false);
        setVisibleSlide(visibleSlide - slides.length);
      }, 500);
    }
    // eslint-disable-next-line eqeqeq
    if (visibleSlide === slides.length) {
      // keep index near the middle of the list when moving right
      // setNavDisabled(true);
      setTimeout(() => {
        setHasTransitionClass(false);
        setVisibleSlide(visibleSlide + slides.length);
      }, 500);
    }
    if (hasTransitionClass === false) {
      // turn transition animation back on after 25ms
      setTimeout(() => {
        setHasTransitionClass(true);
        // setNavDisabled(false);
      }, 500 / 10);
    }
  }, [visibleSlide, noAnimation, hasTransitionClass, slides.length]);

  const setActiveItem = useCallback(
    (index: number) => {
      if (!disableMode) {
        setVisibleSlide(index);
      }
    },
    [disableMode]
  );

  const newItemList = slides.concat(slides, slides, slides);

  return (
    <div
      className={cx(styles.carousel, styles[`color_${color}`])}
      id="containerCarousel"
      style={{
        height: heightSlide || '42px',
      }}
    >
      <div className={styles.slidesContainer} style={{ width: `${itemWidth}px` }}>
        <div
          id="slides"
          className={cx(styles.slides, {
            [styles.transition]: hasTransitionClass,
          })}
          style={{ left: calculateLeftMargin }}
        >
          {newItemList.map((slide, index) => {
            let typeTab: Position | undefined;
            if (index + Math.floor(displaySlideState / 2) === visibleSlide) {
              typeTab = Position.Left;
            }
            if (index - Math.floor(displaySlideState / 2) === visibleSlide) {
              typeTab = Position.Right;
            }
            return (
              // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
              <TabItem
                type={typeTab}
                key={index}
                disable={disableMode}
                onClick={() => {
                  setActiveItem(index);
                  onChange?.(slides.indexOf(slide));
                }}
                isSelected={index === visibleSlide}
                text={slide.title || ''}
                style={{
                  width: `${itemWidth}px`,
                  height: heightSlide || '42px',
                }}
              />
            );
          })}
        </div>
      </div>
    </div>
  );
}

export default Carousel;

Synonyms

bostrom.network/src/components/ui/carousel.tsx
pussy-ts/src/components/Tabs/Carousel/Carousel.tsx

Neighbours