"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 >
) ;
}
Copy