ComponentsAboutContactPrivacyTerms

Built by Lacy Morrow

    Close file
    Close file
    Close file
    Close file
    'use client';
    import { cn } from '@/lib/utils';
    import { useMotionValue, animate, motion } from 'motion/react';
    import { useState, useEffect } from 'react';
    import useMeasure from 'react-use-measure';
    
    export type InfiniteSliderProps = {
      children: React.ReactNode;
      gap?: number;
      speed?: number;
      speedOnHover?: number;
      direction?: 'horizontal' | 'vertical';
      reverse?: boolean;
      className?: string;
    };
    
    export function InfiniteSlider({
      children,
      gap = 16,
      speed = 100,
      speedOnHover,
      direction = 'horizontal',
      reverse = false,
      className,
    }: InfiniteSliderProps) {
      const [currentSpeed, setCurrentSpeed] = useState(speed);
      const [ref, { width, height }] = useMeasure();
      const translation = useMotionValue(0);
      const [isTransitioning, setIsTransitioning] = useState(false);
      const [key, setKey] = useState(0);
    
      useEffect(() => {
        let controls;
        const size = direction === 'horizontal' ? width : height;
        const contentSize = size + gap;
        const from = reverse ? -contentSize / 2 : 0;
        const to = reverse ? 0 : -contentSize / 2;
    
        const distanceToTravel = Math.abs(to - from);
        const duration = distanceToTravel / currentSpeed;
    
        if (isTransitioning) {
          const remainingDistance = Math.abs(translation.get() - to);
          const transitionDuration = remainingDistance / currentSpeed;
    
          controls = animate(translation, [translation.get(), to], {
            ease: 'linear',
            duration: transitionDuration,
            onComplete: () => {
              setIsTransitioning(false);
              setKey((prevKey) => prevKey + 1);
            },
          });
        } else {
          controls = animate(translation, [from, to], {
            ease: 'linear',
            duration: duration,
            repeat: Infinity,
            repeatType: 'loop',
            repeatDelay: 0,
            onRepeat: () => {
              translation.set(from);
            },
          });
        }
    
        return controls?.stop;
      }, [
        key,
        translation,
        currentSpeed,
        width,
        height,
        gap,
        isTransitioning,
        direction,
        reverse,
      ]);
    
      const hoverProps = speedOnHover
        ? {
            onHoverStart: () => {
              setIsTransitioning(true);
              setCurrentSpeed(speedOnHover);
            },
            onHoverEnd: () => {
              setIsTransitioning(true);
              setCurrentSpeed(speed);
            },
          }
        : {};
    
      return (
        <div className={cn('overflow-hidden', className)}>
          <motion.div
            className='flex w-max'
            style={{
              ...(direction === 'horizontal'
                ? { x: translation }
                : { y: translation }),
              gap: `${gap}px`,
              flexDirection: direction === 'horizontal' ? 'row' : 'column',
            }}
            ref={ref}
            {...hoverProps}
          >
            {children}
            {children}
          </motion.div>
        </div>
      );
    }