{
  "name": "marquee",
  "type": "registry:ui",
  "dependencies": [
    "radix-ui"
  ],
  "cssVars": {
    "theme": {
      "--animate-marquee-left": "marquee-left var(--marquee-duration) linear var(--marquee-loop-count)",
      "--animate-marquee-right": "marquee-right var(--marquee-duration) linear var(--marquee-loop-count)",
      "--animate-marquee-left-rtl": "marquee-left-rtl var(--marquee-duration) linear var(--marquee-loop-count)",
      "--animate-marquee-right-rtl": "marquee-right-rtl var(--marquee-duration) linear var(--marquee-loop-count)",
      "--animate-marquee-up": "marquee-up var(--marquee-duration) linear var(--marquee-loop-count)",
      "--animate-marquee-down": "marquee-down var(--marquee-duration) linear var(--marquee-loop-count)"
    }
  },
  "css": {
    "@keyframes marquee-left": {
      "0%": {
        "transform": "translateX(0%)"
      },
      "100%": {
        "transform": "translateX(calc(-100% - var(--marquee-gap)))"
      }
    },
    "@keyframes marquee-right": {
      "0%": {
        "transform": "translateX(calc(-100% - var(--marquee-gap)))"
      },
      "100%": {
        "transform": "translateX(0%)"
      }
    },
    "@keyframes marquee-up": {
      "0%": {
        "transform": "translateY(0%)"
      },
      "100%": {
        "transform": "translateY(calc(-100% - var(--marquee-gap)))"
      }
    },
    "@keyframes marquee-down": {
      "0%": {
        "transform": "translateY(calc(-100% - var(--marquee-gap)))"
      },
      "100%": {
        "transform": "translateY(0%)"
      }
    },
    "@keyframes marquee-left-rtl": {
      "0%": {
        "transform": "translateX(0%)"
      },
      "100%": {
        "transform": "translateX(calc(100% + var(--marquee-gap)))"
      }
    },
    "@keyframes marquee-right-rtl": {
      "0%": {
        "transform": "translateX(calc(100% + var(--marquee-gap)))"
      },
      "100%": {
        "transform": "translateX(0%)"
      }
    }
  },
  "files": [
    {
      "path": "ui/marquee.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n  Direction as DirectionPrimitive,\n  Slot as SlotPrimitive,\n} from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport { cn } from \"@/lib/utils\";\n\nconst ROOT_NAME = \"Marquee\";\nconst CONTENT_NAME = \"MarqueeContent\";\n\ntype Side = \"left\" | \"right\" | \"top\" | \"bottom\";\ntype Orientation = \"horizontal\" | \"vertical\";\ntype Direction = \"ltr\" | \"rtl\";\n\ntype RootElement = React.ComponentRef<typeof Marquee>;\ntype ContentElement = React.ComponentRef<typeof MarqueeContent>;\n\ninterface Dimensions {\n  width: number;\n  height: number;\n}\n\ninterface ElementDimensions {\n  rootSize: number;\n  contentSize: number;\n}\n\nfunction createResizeObserverStore() {\n  const listeners = new Set<() => void>();\n  let observer: ResizeObserver | null = null;\n  const elements = new Map<Element, Dimensions>();\n  const refCounts = new Map<Element, number>();\n  const isSupported = typeof ResizeObserver !== \"undefined\";\n  let notificationScheduled = false;\n\n  const snapshotCache = new WeakMap<\n    Element,\n    WeakMap<\n      Element,\n      { horizontal: ElementDimensions; vertical: ElementDimensions }\n    >\n  >();\n\n  function notify() {\n    if (notificationScheduled) return;\n    notificationScheduled = true;\n    queueMicrotask(() => {\n      notificationScheduled = false;\n      for (const callback of listeners) {\n        callback();\n      }\n    });\n  }\n\n  function cleanup() {\n    if (observer) {\n      observer.disconnect();\n      observer = null;\n    }\n    elements.clear();\n    refCounts.clear();\n  }\n\n  function subscribe(callback: () => void) {\n    listeners.add(callback);\n    return () => {\n      listeners.delete(callback);\n      if (listeners.size === 0) {\n        cleanup();\n      }\n    };\n  }\n\n  function getSnapshot(\n    rootElement: RootElement | null,\n    contentElement: ContentElement | null,\n    orientation: Orientation,\n  ): ElementDimensions | null {\n    if (!rootElement || !contentElement) return null;\n\n    const rootDims = elements.get(rootElement);\n    const contentDims = elements.get(contentElement);\n\n    if (!rootDims || !contentDims) return null;\n\n    const rootSize =\n      orientation === \"vertical\" ? rootDims.height : rootDims.width;\n    const contentSize =\n      orientation === \"vertical\" ? contentDims.height : contentDims.width;\n\n    let rootCache = snapshotCache.get(rootElement);\n    if (!rootCache) {\n      rootCache = new WeakMap();\n      snapshotCache.set(rootElement, rootCache);\n    }\n\n    let contentCache = rootCache.get(contentElement);\n    if (!contentCache) {\n      contentCache = {\n        horizontal: { rootSize: -1, contentSize: -1 },\n        vertical: { rootSize: -1, contentSize: -1 },\n      };\n      rootCache.set(contentElement, contentCache);\n    }\n\n    const cached = contentCache[orientation];\n    if (cached.rootSize === rootSize && cached.contentSize === contentSize) {\n      return cached;\n    }\n\n    const snapshot = { rootSize, contentSize };\n    contentCache[orientation] = snapshot;\n    return snapshot;\n  }\n\n  function observe(\n    rootElement: RootElement | null,\n    contentElement: Element | null,\n  ) {\n    if (!isSupported || !rootElement || !contentElement) return;\n\n    if (!observer) {\n      observer = new ResizeObserver((entries) => {\n        let hasChanged = false;\n\n        for (const entry of entries) {\n          const element = entry.target;\n          const { width, height } = entry.contentRect;\n\n          const currentData = elements.get(element);\n\n          if (\n            !currentData ||\n            currentData.width !== width ||\n            currentData.height !== height\n          ) {\n            elements.set(element, { width, height });\n            hasChanged = true;\n          }\n        }\n\n        if (hasChanged) {\n          notify();\n        }\n      });\n    }\n\n    refCounts.set(rootElement, (refCounts.get(rootElement) ?? 0) + 1);\n    refCounts.set(contentElement, (refCounts.get(contentElement) ?? 0) + 1);\n\n    observer.observe(rootElement);\n    observer.observe(contentElement);\n\n    const rootRect = rootElement.getBoundingClientRect();\n    const contentRect = contentElement.getBoundingClientRect();\n\n    const rootData = { width: rootRect.width, height: rootRect.height };\n    const contentData = {\n      width: contentRect.width,\n      height: contentRect.height,\n    };\n\n    elements.set(rootElement, rootData);\n    elements.set(contentElement, contentData);\n\n    if (\n      rootData.width > 0 &&\n      rootData.height > 0 &&\n      contentData.width > 0 &&\n      contentData.height > 0\n    ) {\n      notify();\n    }\n  }\n\n  function unobserve(\n    rootElement: RootElement | null,\n    contentElement: Element | null,\n  ) {\n    if (!observer || !rootElement || !contentElement) return;\n\n    const rootCount = (refCounts.get(rootElement) ?? 1) - 1;\n    const contentCount = (refCounts.get(contentElement) ?? 1) - 1;\n\n    if (rootCount <= 0) {\n      observer.unobserve(rootElement);\n      elements.delete(rootElement);\n      refCounts.delete(rootElement);\n    } else {\n      refCounts.set(rootElement, rootCount);\n    }\n\n    if (contentCount <= 0) {\n      observer.unobserve(contentElement);\n      elements.delete(contentElement);\n      refCounts.delete(contentElement);\n    } else {\n      refCounts.set(contentElement, contentCount);\n    }\n  }\n\n  return {\n    subscribe,\n    getSnapshot,\n    observe,\n    unobserve,\n  };\n}\n\nconst resizeObserverStore = createResizeObserverStore();\n\nfunction useResizeObserverStore(\n  rootRef: React.RefObject<RootElement | null>,\n  contentRef: React.RefObject<ContentElement | null>,\n  orientation: Orientation,\n) {\n  const onSubscribe = React.useCallback(\n    (callback: () => void) => resizeObserverStore.subscribe(callback),\n    [],\n  );\n\n  const getSnapshot = React.useCallback(\n    () =>\n      resizeObserverStore.getSnapshot(\n        rootRef.current,\n        contentRef.current,\n        orientation,\n      ),\n    [rootRef, contentRef, orientation],\n  );\n\n  return React.useSyncExternalStore(onSubscribe, getSnapshot, getSnapshot);\n}\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n  asChild?: boolean;\n}\n\ninterface MarqueeContextValue {\n  side: Side;\n  orientation: Orientation;\n  dir: Direction;\n  speed: number;\n  loopCount: number;\n  contentRef: React.RefObject<ContentElement | null>;\n  rootRef: React.RefObject<RootElement | null>;\n  autoFill: boolean;\n  pauseOnHover: boolean;\n  pauseOnKeyboard: boolean;\n  reverse: boolean;\n  paused: boolean;\n}\n\nconst MarqueeContext = React.createContext<MarqueeContextValue | null>(null);\n\nfunction useMarqueeContext(consumerName: string) {\n  const context = React.useContext(MarqueeContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n  }\n  return context;\n}\n\ninterface MarqueeProps extends DivProps {\n  side?: Side;\n  dir?: Direction;\n  speed?: number;\n  delay?: number;\n  loopCount?: number;\n  gap?: string | number;\n  autoFill?: boolean;\n  pauseOnHover?: boolean;\n  pauseOnKeyboard?: boolean;\n  reverse?: boolean;\n}\n\nfunction Marquee(props: MarqueeProps) {\n  const {\n    side = \"left\",\n    dir: dirProp,\n    speed = 50,\n    delay = 0,\n    loopCount = 0,\n    gap = \"1rem\",\n    asChild,\n    autoFill = false,\n    pauseOnHover = false,\n    pauseOnKeyboard = false,\n    reverse = false,\n    className,\n    style: styleProp,\n    ref,\n    ...marqueeProps\n  } = props;\n\n  const orientation: Orientation =\n    side === \"top\" || side === \"bottom\" ? \"vertical\" : \"horizontal\";\n\n  const dir = DirectionPrimitive.useDirection(dirProp);\n\n  const rootRef = React.useRef<RootElement>(null);\n  const contentRef = React.useRef<ContentElement>(null);\n  const composedRef = useComposedRefs(ref, rootRef);\n\n  const [paused, setPaused] = React.useState(false);\n\n  const onKeyDown = React.useCallback(\n    (event: React.KeyboardEvent) => {\n      if (pauseOnKeyboard && event.key === \" \") {\n        event.preventDefault();\n        setPaused((prev) => !prev);\n      }\n    },\n    [pauseOnKeyboard],\n  );\n\n  const dimensions = useResizeObserverStore(rootRef, contentRef, orientation);\n\n  const duration = React.useMemo(() => {\n    const safeSpeed = Math.max(0.001, speed);\n\n    if (!dimensions) {\n      const defaultDistance = autoFill ? 1000 : 2000;\n      return defaultDistance / safeSpeed;\n    }\n\n    const { rootSize, contentSize } = dimensions;\n\n    if (autoFill) {\n      const multiplier =\n        contentSize < rootSize ? Math.ceil(rootSize / contentSize) : 1;\n      return (contentSize * multiplier) / safeSpeed;\n    } else {\n      return contentSize < rootSize\n        ? rootSize / safeSpeed\n        : contentSize / safeSpeed;\n    }\n  }, [dimensions, speed, autoFill]);\n\n  const style = React.useMemo<React.CSSProperties>(\n    () => ({\n      \"--marquee-duration\": `${duration}s`,\n      \"--marquee-gap\": gap,\n      \"--marquee-delay\": `${delay}s`,\n      \"--marquee-loop-count\":\n        loopCount === 0 || loopCount === Infinity\n          ? \"infinite\"\n          : loopCount.toString(),\n      ...styleProp,\n    }),\n    [duration, gap, delay, loopCount, styleProp],\n  );\n\n  const contextValue = React.useMemo<MarqueeContextValue>(\n    () => ({\n      side,\n      orientation,\n      dir,\n      speed,\n      loopCount,\n      contentRef,\n      rootRef,\n      autoFill,\n      paused,\n      pauseOnHover,\n      pauseOnKeyboard,\n      reverse,\n    }),\n    [\n      side,\n      orientation,\n      dir,\n      speed,\n      loopCount,\n      autoFill,\n      paused,\n      pauseOnHover,\n      pauseOnKeyboard,\n      reverse,\n    ],\n  );\n\n  const MarqueePrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <MarqueeContext.Provider value={contextValue}>\n      <div data-slot=\"marquee-wrapper\" className=\"grid\">\n        <MarqueePrimitive\n          role=\"marquee\"\n          aria-live=\"off\"\n          data-orientation={orientation}\n          data-slot=\"marquee\"\n          dir={dir}\n          tabIndex={pauseOnKeyboard ? 0 : undefined}\n          {...marqueeProps}\n          ref={composedRef}\n          className={cn(\n            \"relative flex overflow-hidden motion-reduce:animate-none\",\n            orientation === \"vertical\" && \"h-full flex-col\",\n            orientation === \"horizontal\" && \"w-full\",\n            paused && \"[&_*]:[animation-play-state:paused]\",\n            pauseOnHover && \"group\",\n            pauseOnKeyboard &&\n              \"rounded-md focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none\",\n            className,\n          )}\n          style={style}\n          onKeyDown={pauseOnKeyboard ? onKeyDown : undefined}\n        />\n      </div>\n    </MarqueeContext.Provider>\n  );\n}\n\nconst marqueeContentVariants = cva(\n  \"flex min-w-full shrink-0 gap-(--marquee-gap)\",\n  {\n    variants: {\n      side: {\n        left: \"animate-marquee-left\",\n        right: \"animate-marquee-right\",\n        top: \"min-h-full min-w-auto animate-marquee-up flex-col\",\n        bottom: \"min-h-full min-w-auto animate-marquee-down flex-col\",\n      },\n      dir: {\n        ltr: \"\",\n        rtl: \"\",\n      },\n      pauseOnHover: {\n        true: \"group-hover:[animation-play-state:paused]\",\n        false: \"\",\n      },\n      reverse: {\n        true: \"[animation-direction:reverse]\",\n        false: \"\",\n      },\n    },\n    compoundVariants: [\n      {\n        side: \"left\",\n        dir: \"rtl\",\n        className: \"animate-marquee-left-rtl\",\n      },\n      {\n        side: \"right\",\n        dir: \"rtl\",\n        className: \"animate-marquee-right-rtl\",\n      },\n    ],\n    defaultVariants: {\n      side: \"left\",\n      dir: \"ltr\",\n      pauseOnHover: false,\n      reverse: false,\n    },\n  },\n);\n\nfunction MarqueeContent(props: DivProps) {\n  const {\n    className,\n    asChild,\n    ref,\n    children,\n    style: styleProp,\n    ...contentProps\n  } = props;\n\n  const context = useMarqueeContext(CONTENT_NAME);\n  const composedRef = useComposedRefs(ref, context.contentRef);\n\n  const isVertical = context.orientation === \"vertical\";\n  const isRtl = context.dir === \"rtl\";\n\n  const dimensions = useResizeObserverStore(\n    context.rootRef,\n    context.contentRef,\n    context.orientation,\n  );\n\n  React.useEffect(() => {\n    if (context.rootRef.current && context.contentRef.current) {\n      resizeObserverStore.observe(\n        context.rootRef.current,\n        context.contentRef.current,\n      );\n\n      return () => {\n        resizeObserverStore.unobserve(\n          context.rootRef.current,\n          context.contentRef.current,\n        );\n      };\n    }\n  }, [context.rootRef, context.contentRef]);\n\n  const multiplier = React.useMemo(() => {\n    if (!context.autoFill || !dimensions) return 1;\n\n    const { rootSize, contentSize } = dimensions;\n    if (contentSize === 0) return 1;\n\n    return contentSize < rootSize ? Math.ceil(rootSize / contentSize) : 1;\n  }, [context.autoFill, dimensions]);\n\n  const onMultipliedChildrenRender = React.useCallback(\n    (count: number) => {\n      return Array.from({ length: Math.max(0, count) }).map((_, i) => (\n        <React.Fragment key={i}>{children}</React.Fragment>\n      ));\n    },\n    [children],\n  );\n\n  const style = React.useMemo(\n    () => ({\n      ...styleProp,\n      animationDuration: \"var(--marquee-duration)\",\n      animationDelay: \"var(--marquee-delay)\",\n      animationIterationCount: \"var(--marquee-loop-count)\",\n      animationDirection: context.reverse ? \"reverse\" : \"normal\",\n    }),\n    [styleProp, context.reverse],\n  );\n\n  const ContentPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <>\n      <ContentPrimitive\n        data-orientation={context.orientation}\n        data-slot=\"marquee-content\"\n        {...contentProps}\n        style={style}\n        className={cn(\n          marqueeContentVariants({\n            side: context.side,\n            dir: context.dir,\n            pauseOnHover: context.pauseOnHover,\n            reverse: context.reverse,\n            className,\n          }),\n          isVertical && \"flex-col\",\n          isVertical\n            ? \"mb-(--marquee-gap)\"\n            : isRtl\n              ? \"ml-(--marquee-gap)\"\n              : \"mr-(--marquee-gap)\",\n        )}\n      >\n        <div\n          ref={composedRef}\n          className={cn(\n            \"flex shrink-0 gap-(--marquee-gap)\",\n            isVertical && \"flex-col\",\n          )}\n        >\n          {children}\n        </div>\n        {onMultipliedChildrenRender(multiplier - 1)}\n      </ContentPrimitive>\n      <ContentPrimitive\n        role=\"presentation\"\n        aria-hidden=\"true\"\n        {...contentProps}\n        style={style}\n        className={cn(\n          marqueeContentVariants({\n            side: context.side,\n            dir: context.dir,\n            pauseOnHover: context.pauseOnHover,\n            reverse: context.reverse,\n            className,\n          }),\n          isVertical && \"flex-col\",\n        )}\n      >\n        {onMultipliedChildrenRender(multiplier)}\n      </ContentPrimitive>\n    </>\n  );\n}\n\nfunction MarqueeItem(props: DivProps) {\n  const { className, asChild, ...itemProps } = props;\n\n  const ItemPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <ItemPrimitive\n      data-slot=\"marquee-item\"\n      {...itemProps}\n      className={cn(\"shrink-0\", className)}\n    />\n  );\n}\n\nconst marqueeEdgeVariants = cva(\"pointer-events-none absolute z-10\", {\n  variants: {\n    side: {\n      left: \"top-0 left-0 h-full bg-gradient-to-r from-background to-transparent\",\n      right:\n        \"top-0 right-0 h-full bg-gradient-to-l from-background to-transparent\",\n      top: \"top-0 left-0 w-full bg-gradient-to-b from-background to-transparent\",\n      bottom:\n        \"bottom-0 left-0 w-full bg-gradient-to-t from-background to-transparent\",\n    },\n    size: {\n      default: \"\",\n      sm: \"\",\n      lg: \"\",\n    },\n  },\n  compoundVariants: [\n    {\n      side: [\"left\", \"right\"],\n      size: \"default\",\n      className: \"w-1/4\",\n    },\n    {\n      side: [\"left\", \"right\"],\n      size: \"sm\",\n      className: \"w-1/6\",\n    },\n    {\n      side: [\"left\", \"right\"],\n      size: \"lg\",\n      className: \"w-1/3\",\n    },\n    {\n      side: [\"top\", \"bottom\"],\n      size: \"default\",\n      className: \"h-1/4\",\n    },\n    {\n      side: [\"top\", \"bottom\"],\n      size: \"sm\",\n      className: \"h-1/6\",\n    },\n    {\n      side: [\"top\", \"bottom\"],\n      size: \"lg\",\n      className: \"h-1/3\",\n    },\n  ],\n  defaultVariants: {\n    size: \"default\",\n  },\n});\n\ninterface MarqueeEdgeProps\n  extends VariantProps<typeof marqueeEdgeVariants>, DivProps {}\n\nfunction MarqueeEdge(props: MarqueeEdgeProps) {\n  const { side, size, className, asChild, ...edgeProps } = props;\n\n  const EdgePrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <EdgePrimitive\n      data-size={size}\n      data-slot=\"marquee-edge\"\n      {...edgeProps}\n      className={cn(marqueeEdgeVariants({ side, size, className }))}\n    />\n  );\n}\n\nexport { Marquee, MarqueeContent, MarqueeEdge, MarqueeItem, type MarqueeProps };\n",
      "target": ""
    },
    {
      "path": "lib/compose-refs.ts",
      "type": "registry:lib",
      "content": "/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx\n */\n\nimport * as React from \"react\";\n\ntype PossibleRef<T> = React.Ref<T> | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n  if (typeof ref === \"function\") {\n    return ref(value);\n  }\n\n  if (ref !== null && ref !== undefined) {\n    ref.current = value;\n  }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  return (node) => {\n    let hasCleanup = false;\n    const cleanups = refs.map((ref) => {\n      const cleanup = setRef(ref, node);\n      if (!hasCleanup && typeof cleanup === \"function\") {\n        hasCleanup = true;\n      }\n      return cleanup;\n    });\n\n    // React <19 will log an error to the console if a callback ref returns a\n    // value. We don't use ref cleanups internally so this will only happen if a\n    // user's ref callback returns a value, which we only expect if they are\n    // using the cleanup functionality added in React 19.\n    if (hasCleanup) {\n      return () => {\n        for (let i = 0; i < cleanups.length; i++) {\n          const cleanup = cleanups[i];\n          if (typeof cleanup === \"function\") {\n            cleanup();\n          } else {\n            setRef(refs[i], null);\n          }\n        }\n      };\n    }\n  };\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nfunction useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  // oxlint-disable-next-line react/exhaustive-deps -- we want to memoize by all values\n  return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n",
      "target": ""
    }
  ]
}