"use client";
import React from "react";
import { cn } from "@/lib/utils";
import { useMotionValue, animate, motion } from "motion/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 MarqueeEffect({
children,
gap = 16,
speed = 100,
speedOnHover,
direction = "horizontal",
reverse = false,
className
}: InfiniteSliderProps) {
const [currentSpeed, setCurrentSpeed] = React.useState(speed);
const [ref, { width, height }] = useMeasure();
const translation = useMotionValue(0);
const [isTransitioning, setIsTransitioning] = React.useState(false);
const [key, setKey] = React.useState(0);
React.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={cn("flex", { "w-max": direction !== "vertical" })}
style={{
...(direction === "horizontal" ? { x: translation } : { y: translation }),
gap: `${gap}px`,
flexDirection: direction === "horizontal" ? "row" : "column"
}}
ref={ref}
{...hoverProps}>
{children}
{children}
</motion.div>
</div>
);
}