50 lines
1.3 KiB
TypeScript
50 lines
1.3 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
|
|
interface MarqueeProps {
|
|
items: string[];
|
|
separator?: string;
|
|
speed?: "slow" | "normal" | "fast";
|
|
reverse?: boolean;
|
|
className?: string;
|
|
itemClassName?: string;
|
|
separatorClassName?: string;
|
|
}
|
|
|
|
const speeds = { slow: "42s", normal: "28s", fast: "16s" };
|
|
|
|
export function Marquee({
|
|
items,
|
|
separator = "✦",
|
|
speed = "normal",
|
|
reverse = false,
|
|
className,
|
|
itemClassName,
|
|
separatorClassName,
|
|
}: MarqueeProps) {
|
|
// Double for seamless infinite loop
|
|
const doubled = [...items, ...items];
|
|
|
|
return (
|
|
<div className={cn("overflow-hidden whitespace-nowrap select-none", className)}>
|
|
<div
|
|
className="inline-flex"
|
|
style={{
|
|
animation: `marquee ${speeds[speed]} linear infinite${reverse ? " reverse" : ""}`,
|
|
willChange: "transform",
|
|
}}
|
|
>
|
|
{doubled.map((item, i) => (
|
|
<span key={i} className="inline-flex items-center">
|
|
<span className={cn("px-7 text-[11px] font-medium uppercase tracking-[0.18em]", itemClassName)}>
|
|
{item}
|
|
</span>
|
|
<span className={cn("opacity-50 text-sm", separatorClassName)}>
|
|
{separator}
|
|
</span>
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|