{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "native-marquee-shadcnui", "type": "registry:component", "title": "Native Marquee", "description": "A smooth, infinite scrolling marquee for logos or text.", "dependencies": [ "framer-motion", "react" ], "files": [ { "path": "@uitripled/react-shadcn/src/components/native/native-marquee-shadcnui.tsx", "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { motion, useAnimate } from \"framer-motion\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\ninterface NativeMarqueeItem {\n id: string;\n icon?: React.ReactNode;\n label: string;\n}\n\ninterface NativeMarqueeProps {\n className?: string;\n speed?: number;\n items: NativeMarqueeItem[];\n gap?: number;\n isVertical?: boolean;\n reverse?: boolean;\n pauseOnHover?: boolean;\n pauseOnTouch?: boolean;\n}\n\nfunction NativeMarquee({\n items,\n speed = 10,\n gap = 2,\n isVertical = false,\n reverse = false,\n pauseOnHover = false,\n pauseOnTouch = false,\n className = \"\",\n}: NativeMarqueeProps) {\n const { containerRef, marqueeRef, multiplier, isMounted } = useMarquee({\n isVertical,\n });\n\n const [scope1, animate1] = useAnimate();\n const [scope2, animate2] = useAnimate();\n const anim1Ref = useRef(null);\n const anim2Ref = useRef(null);\n\n const duration = 100 / speed;\n\n useEffect(() => {\n if (!isMounted || !scope1.current || !scope2.current) return;\n\n const axis = isVertical ? \"y\" : \"x\";\n const from = reverse ? -100 : 0;\n const to = reverse ? 0 : -100;\n\n const start = () => {\n anim1Ref.current = animate1(\n scope1.current,\n { [axis]: [from + \"%\", to + \"%\"] },\n { duration, ease: \"linear\", repeat: Infinity }\n );\n anim2Ref.current = animate2(\n scope2.current,\n { [axis]: [from + \"%\", to + \"%\"] },\n { duration, ease: \"linear\", repeat: Infinity }\n );\n };\n\n const animationFrame = requestAnimationFrame(start);\n\n return () => cancelAnimationFrame(animationFrame);\n }, [isMounted, isVertical, speed, reverse]);\n\n const content = useMemo(\n () => (\n \n ),\n [items, isVertical]\n );\n\n const copies = useCallback(\n (multiplier: number) => {\n const arraySize = multiplier >= 0 ? multiplier : 0;\n return [...Array(arraySize)].map((_, i) => (\n \n {content}\n \n ));\n },\n [items]\n );\n\n const pause = () => {\n anim1Ref.current?.pause();\n anim2Ref.current?.pause();\n };\n\n const resume = () => {\n anim1Ref.current?.play();\n anim2Ref.current?.play();\n };\n\n const hoverProps = pauseOnHover\n ? { onMouseEnter: pause, onMouseLeave: resume }\n : {};\n const touchProps = pauseOnTouch\n ? { onTouchStart: pause, onTouchEnd: resume }\n : {};\n\n if (!isMounted) {\n return null;\n }\n\n return (\n \n \n \n {/* to match the other span elements paddings */}\n \n {content}\n \n {copies(multiplier - 1)}\n \n \n \n \n {copies(multiplier)}\n \n \n \n );\n}\n\nfunction useMarquee(\n { isVertical = false }: { isVertical?: boolean } = { isVertical: false }\n) {\n const containerRef = useRef(null);\n const marqueeRef = useRef(null);\n const [multiplier, setMultiplier] = useState(1);\n const [isMounted, setIsMounted] = useState(false);\n\n const calculateMultiplier = useCallback(() => {\n if (!containerRef.current || !marqueeRef.current) return;\n const containerRect = containerRef.current.getBoundingClientRect();\n const marqueeRect = marqueeRef.current.getBoundingClientRect();\n\n let marqueeSize: number;\n let containerSize: number;\n let scale: number;\n if (isVertical) {\n containerSize = containerRect.height;\n marqueeSize = marqueeRect.height;\n scale = Math.ceil(containerSize / marqueeSize);\n } else {\n containerSize = containerRect.width;\n marqueeSize = marqueeRect.width;\n scale = Math.min(Math.ceil(containerSize / marqueeSize), 20);\n }\n setMultiplier(marqueeSize < containerSize ? scale : 1);\n }, [isVertical]);\n\n // ensure the marquee is mounted on the client\n useEffect(() => {\n setIsMounted(true);\n }, []);\n\n useEffect(() => {\n if (!isMounted) return;\n calculateMultiplier();\n if (marqueeRef.current && containerRef.current) {\n const resizeObserver = new ResizeObserver(() => {\n calculateMultiplier();\n });\n resizeObserver.observe(marqueeRef.current);\n return () => resizeObserver.disconnect();\n }\n }, [calculateMultiplier, isMounted]);\n\n return { containerRef, marqueeRef, multiplier, isMounted };\n}\n\nfunction SideFadeGradients({ isVertical = false }: { isVertical?: boolean }) {\n return (\n <>\n \n \n \n );\n}\nexport { NativeMarquee, SideFadeGradients, useMarquee };\n", "type": "registry:component", "target": "components/uitripled/native-marquee-shadcnui.tsx" } ] }