{
  "$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<any>(null);\n  const anim2Ref = useRef<any>(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      <ul className={`flex items-center gap-2 ${isVertical ? \"flex-col\" : \"\"}`}>\n        {items.map((item) => (\n          <li\n            key={item.id}\n            className={cn(\n              \"flex items-center gap-2 \",\n              \"border border-border bg-background px-4 py-2 rounded-full text-sm text-muted-foreground whitespace-nowrap\",\n              \"hover:bg-muted hover:border-foreground/20\",\n              isVertical ? \"w-full justify-center\" : \"\"\n            )}\n          >\n            {item.icon}\n            {item.label}\n          </li>\n        ))}\n      </ul>\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        <span key={i} aria-hidden=\"true\" className=\"shrink-0\">\n          {content}\n        </span>\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    <div\n      ref={containerRef}\n      className={cn(\n        \"flex shrink-0 grow-0 basis-auto overflow-hidden\",\n        isVertical ? \"flex-col h-full\" : \"w-full \",\n        className\n      )}\n      {...hoverProps}\n      {...touchProps}\n    >\n      <motion.div ref={scope1}>\n        <div\n          className={cn(\n            \"flex grow-0 shrink-0 basis-auto min-w-min\",\n            isVertical ? \"flex-col\" : \"\"\n          )}\n          style={{\n            marginRight: isVertical ? 0 : `${gap}px`,\n            marginBottom: isVertical ? `${gap}px` : 0,\n            gap: `${gap}px`,\n          }}\n        >\n          {/* to match the other span elements paddings */}\n          <span ref={marqueeRef} className=\"shrink-0\">\n            {content}\n          </span>\n          {copies(multiplier - 1)}\n        </div>\n      </motion.div>\n      <motion.div ref={scope2}>\n        <div\n          className={cn(\n            \"flex grow-0 shrink-0 basis-auto min-w-min\",\n            isVertical ? \"flex-col\" : \"\"\n          )}\n          aria-hidden=\"true\"\n          style={{\n            marginRight: isVertical ? 0 : `${gap}px`,\n            marginBottom: isVertical ? `${gap}px` : 0,\n            gap: `${gap}px`,\n          }}\n        >\n          {copies(multiplier)}\n        </div>\n      </motion.div>\n    </div>\n  );\n}\n\nfunction useMarquee(\n  { isVertical = false }: { isVertical?: boolean } = { isVertical: false }\n) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const marqueeRef = useRef<HTMLDivElement>(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      <div\n        className={cn(\n          \"absolute z-10 pointer-events-none\",\n          isVertical\n            ? \"inset-x-0 top-0 h-24 bg-gradient-to-b from-background to-transparent\"\n            : \"inset-y-0 left-0 w-24 bg-gradient-to-r from-background to-transparent\"\n        )}\n      />\n      <div\n        className={cn(\n          \"absolute z-10 pointer-events-none\",\n          isVertical\n            ? \"inset-x-0 bottom-0 h-24 bg-gradient-to-t from-background to-transparent\"\n            : \"inset-y-0 right-0 w-24 bg-gradient-to-l from-background to-transparent\"\n        )}\n      />\n    </>\n  );\n}\nexport { NativeMarquee, SideFadeGradients, useMarquee };\n",
      "type": "registry:component",
      "target": "components/uitripled/native-marquee-shadcnui.tsx"
    }
  ]
}