{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "marquee",
  "title": "跑马灯",
  "description": "纯 CSS 无缝跑马灯，支持四向滚动、恒定速度、渐隐与斜向丝带。",
  "files": [
    {
      "path": "src/registry/ui/marquee/marquee.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst MARQUEE_CSS = `\n@keyframes tv-marquee-x { to { transform: translateX(-100%) } }\n@keyframes tv-marquee-y { to { transform: translateY(-100%) } }\n[data-slot=\"marquee\"] { display:flex; overflow:hidden; container-type:inline-size; rotate:var(--marquee-rotate) }\n[data-slot=\"marquee-group\"] { display:flex; flex:none; align-items:center; justify-content:space-around; gap:var(--marquee-gap); min-width:100%; padding-inline-end:var(--marquee-gap); animation:tv-marquee-x var(--marquee-duration,30s) linear infinite; animation-direction:var(--marquee-reverse) }\n[data-slot=\"marquee\"][data-vertical=\"true\"] { flex-direction:column }\n[data-vertical=\"true\"] > [data-slot=\"marquee-group\"] { flex-direction:column; min-width:0; width:100%; min-height:100%; padding-inline-end:0; padding-block-end:var(--marquee-gap); animation-name:tv-marquee-y }\n[data-slot=\"marquee\"][data-fade=\"true\"] { mask-image:linear-gradient(to right,transparent,var(--foreground) 8%,var(--foreground) 92%,transparent) }\n[data-slot=\"marquee\"][data-vertical=\"true\"][data-fade=\"true\"] { mask-image:linear-gradient(to bottom,transparent,var(--foreground) 8%,var(--foreground) 92%,transparent) }\n[data-slot=\"marquee\"]:focus-within > [data-slot=\"marquee-group\"], [data-slot=\"marquee\"][data-paused=\"true\"] > [data-slot=\"marquee-group\"], [data-slot=\"marquee\"][data-hover=\"true\"]:hover > [data-slot=\"marquee-group\"] { animation-play-state:paused }\n@media(prefers-reduced-motion:reduce) {\n [data-slot=\"marquee\"] { overflow:auto; mask-image:none!important; rotate:none }\n [data-slot=\"marquee-group\"] { animation:none!important; justify-content:flex-start }\n [data-slot=\"marquee-group\"][aria-hidden=\"true\"] { display:none }\n}`\n\n/** 文档调整覆盖项；不提供 Provider 时沿用组件属性。 */\ntype MarqueeReport = {\n  speed: number\n  reverse: boolean\n  gap: number\n  rotate: number\n  fade: boolean\n  pauseOnHover: boolean\n  paused: boolean\n}\ntype MarqueeTuning = Partial<MarqueeReport> & {\n  /** 回报覆盖前的示例取值。 */\n  onReport?: (report: MarqueeReport) => void\n}\nconst MarqueeTuningContext = React.createContext<MarqueeTuning>({})\n\ntype MarqueeDirection = \"left\" | \"right\" | \"up\" | \"down\"\ntype MarqueeProps = React.ComponentProps<\"div\"> & {\n  direction?: MarqueeDirection\n  /** 每秒移动的像素数；脚本只测量，不驱动动画。 */\n  speed?: number\n  pauseOnHover?: boolean\n  paused?: boolean\n  reverse?: boolean\n  /** 总内容组数，至少 2 组。 */\n  repeat?: number\n  fade?: boolean\n  /** 项目之间的像素间距。 */\n  gap?: number\n  /** 整条带子的旋转角度。 */\n  rotate?: number\n  label?: string\n}\n\nfunction Marquee({\n  direction = \"left\",\n  speed: speedProp = 48,\n  pauseOnHover: pauseOnHoverProp = true,\n  paused: pausedProp = false,\n  reverse: reverseProp = false,\n  repeat = 3,\n  fade: fadeProp = true,\n  gap: gapProp = 32,\n  rotate: rotateProp = 0,\n  label = \"跑马灯\",\n  children,\n  className,\n  style,\n  ...props\n}: MarqueeProps) {\n  const tuning = React.useContext(MarqueeTuningContext)\n  const { onReport } = tuning\n  const speed = tuning.speed ?? speedProp\n  const reverse = tuning.reverse ?? reverseProp\n  const gap = tuning.gap ?? gapProp\n  const rotate = tuning.rotate ?? rotateProp\n  const fade = tuning.fade ?? fadeProp\n  const pauseOnHover = tuning.pauseOnHover ?? pauseOnHoverProp\n  const paused = tuning.paused ?? pausedProp\n  React.useEffect(() => {\n    onReport?.({\n      speed: speedProp,\n      reverse: reverseProp,\n      gap: gapProp,\n      rotate: rotateProp,\n      fade: fadeProp,\n      pauseOnHover: pauseOnHoverProp,\n      paused: pausedProp,\n    })\n  }, [\n    onReport,\n    speedProp,\n    reverseProp,\n    gapProp,\n    rotateProp,\n    fadeProp,\n    pauseOnHoverProp,\n    pausedProp,\n  ])\n\n  const root = React.useRef<HTMLDivElement>(null)\n  const group = React.useRef<HTMLDivElement>(null)\n  const vertical = direction === \"up\" || direction === \"down\"\n  const backwards = (direction === \"right\" || direction === \"down\") !== reverse\n  const count = Math.min(20, Math.max(2, Math.floor(repeat) || 2))\n\n  React.useEffect(() => {\n    const element = group.current\n    const viewport = root.current\n    if (!element || !viewport) return\n    function measure() {\n      if (!element || !viewport) return\n      const distance = vertical ? element.offsetHeight : element.offsetWidth\n      viewport.style.setProperty(\n        \"--marquee-duration\",\n        `${distance / Math.max(1, speed)}s`,\n      )\n    }\n    const observer = new ResizeObserver(measure)\n    observer.observe(element)\n    observer.observe(viewport)\n    measure()\n    return () => observer.disconnect()\n  }, [speed, vertical])\n\n  return (\n    <div\n      {...props}\n      ref={root}\n      data-slot=\"marquee\"\n      data-vertical={vertical}\n      data-fade={fade}\n      data-hover={pauseOnHover}\n      data-paused={paused}\n      role=\"region\"\n      aria-label={label}\n      tabIndex={0}\n      className={cn(\n        \"relative w-full min-w-0 outline-none focus-visible:ring-3 focus-visible:ring-ring/50\",\n        className,\n      )}\n      style={\n        {\n          \"--marquee-gap\": `${Math.max(0, gap)}px`,\n          \"--marquee-rotate\": `${rotate}deg`,\n          \"--marquee-reverse\": backwards ? \"reverse\" : \"normal\",\n          ...style,\n        } as React.CSSProperties\n      }\n    >\n      <style href=\"travinsy-marquee\" precedence=\"default\">\n        {MARQUEE_CSS}\n      </style>\n      {Array.from({ length: count }, (_, index) => (\n        <div\n          key={index}\n          ref={index === 0 ? group : undefined}\n          data-slot=\"marquee-group\"\n          aria-hidden={index > 0 ? true : undefined}\n          inert={index > 0 ? true : undefined}\n        >\n          {children}\n        </div>\n      ))}\n    </div>\n  )\n}\n\nfunction MarqueeItem({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"marquee-item\"\n      className={cn(\n        \"flex shrink-0 items-center gap-3 whitespace-nowrap\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { MarqueeTuningContext, Marquee, MarqueeItem }\nexport type { MarqueeTuning, MarqueeReport, MarqueeProps, MarqueeDirection }\n",
      "type": "registry:ui",
      "target": "@ui/marquee/marquee.tsx"
    }
  ],
  "type": "registry:ui"
}
