{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "marquee",
  "title": "Marquee",
  "description": "An endless CSS-driven row that stops when someone reaches for it, and turns into a still, scrollable row for people who turned motion off.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/marquee.tsx",
      "content": "import type * as React from \"react\"\nimport { createContext, useContext, useMemo } from \"react\"\nimport { useReducedMotion } from \"motion/react\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype MarqueeVariant = \"plain\" | \"fade\" | \"cards\" | \"logos\"\ntype MarqueeOrientation = \"horizontal\" | \"vertical\"\n\nconst DEFAULT_DURATION_SECONDS = 30\n\n/**\n * Lives with the component so an install needs no global CSS. React 19 hoists a style with `href` into the head\n * once however many marquees render. The sign flips the travel in right-to-left pages, where the copies run the other way.\n */\nconst KEYFRAMES =\n  \"@keyframes mise-marquee-x{to{transform:translateX(calc(-100% * var(--mise-marquee-sign,1)))}}@keyframes mise-marquee-y{to{transform:translateY(-100%)}}\"\n\nconst GAP_CLASS: Record<MarqueeVariant, string> = {\n  plain: \"[--mise-marquee-gap:2rem]\",\n  fade: \"[--mise-marquee-gap:3rem]\",\n  cards: \"[--mise-marquee-gap:0.75rem]\",\n  logos: \"[--mise-marquee-gap:3rem]\",\n}\n\nconst FADE_MASK: Record<MarqueeOrientation, string> = {\n  horizontal: \"[mask-image:linear-gradient(to_right,transparent,black_3rem,black_calc(100%-3rem),transparent)]\",\n  vertical: \"[mask-image:linear-gradient(to_bottom,transparent,black_3rem,black_calc(100%-3rem),transparent)]\",\n}\n\nconst ITEM_CLASS: Record<MarqueeVariant, string> = {\n  plain: \"text-sm whitespace-nowrap text-muted-foreground\",\n  fade: \"text-[15px] font-medium whitespace-nowrap\",\n  cards: \"rounded-lg border bg-card p-4 text-sm text-card-foreground\",\n  logos: \"text-base font-semibold tracking-tight whitespace-nowrap text-muted-foreground\",\n}\n\nconst ROW_CLASS: Record<MarqueeOrientation, string> = {\n  horizontal: \"flex-row items-center pr-(--mise-marquee-gap)\",\n  vertical: \"flex-col items-stretch pb-(--mise-marquee-gap)\",\n}\n\nconst TRAVEL_CLASS: Record<MarqueeOrientation, string> = {\n  horizontal: \"min-w-full animate-[mise-marquee-x_var(--mise-marquee-duration)_linear_infinite]\",\n  vertical: \"min-h-full animate-[mise-marquee-y_var(--mise-marquee-duration)_linear_infinite]\",\n}\n\nconst PAUSE_ON_ATTENTION =\n  \"group-hover/marquee:[animation-play-state:paused] group-focus-within/marquee:[animation-play-state:paused]\"\n\nconst MarqueeContext = createContext<{ variant: MarqueeVariant; orientation: MarqueeOrientation }>({\n  variant: \"plain\",\n  orientation: \"horizontal\",\n})\n\ninterface CopyProps {\n  orientation: MarqueeOrientation\n  hidden?: boolean\n  reverse: boolean\n  paused: boolean\n  children: React.ReactNode\n}\n\nfunction MarqueeCopy({ orientation, hidden = false, reverse, paused, children }: CopyProps) {\n  return (\n    <div\n      data-slot=\"marquee-copy\"\n      // The repeat is scenery. Inert also keeps any links in it out of the tab order, so nobody tabs through the row twice.\n      aria-hidden={hidden || undefined}\n      inert={hidden || undefined}\n      className={cn(\n        \"flex shrink-0 justify-around gap-(--mise-marquee-gap) motion-reduce:animate-none\",\n        ROW_CLASS[orientation],\n        TRAVEL_CLASS[orientation],\n        PAUSE_ON_ATTENTION,\n        reverse && \"[animation-direction:reverse]\",\n        paused && \"[animation-play-state:paused]\",\n      )}\n    >\n      {children}\n    </div>\n  )\n}\n\ntype MarqueeProps = React.ComponentProps<\"div\"> & {\n  /** `plain` a quiet line of text, `fade` larger with masked edges, `cards` a row of surfaces, `logos` text wordmarks. */\n  variant?: MarqueeVariant\n  /** Runs top to bottom. Give the root a height; the default is 192 px. */\n  vertical?: boolean\n  reverse?: boolean\n  /** Seconds for one full pass. A longer row needs more, to keep the same speed. */\n  duration?: number\n  /** Holds it still, for a pause button of your own. Hover and focus already pause it. */\n  paused?: boolean\n}\n\nfunction Marquee({\n  variant = \"plain\",\n  vertical = false,\n  reverse = false,\n  duration = DEFAULT_DURATION_SECONDS,\n  paused = false,\n  className,\n  style,\n  children,\n  onBlur,\n  ...props\n}: MarqueeProps) {\n  const reduceMotion = useReducedMotion() ?? false\n  const orientation: MarqueeOrientation = vertical ? \"vertical\" : \"horizontal\"\n  const context = useMemo(() => ({ variant, orientation }), [variant, orientation])\n  const root = {\n    ...props,\n    \"data-slot\": \"marquee\",\n    \"data-variant\": variant,\n    \"data-orientation\": orientation,\n  }\n  const base = cn(\"group/marquee flex w-full min-w-0 rtl:[--mise-marquee-sign:-1]\", vertical && \"h-48 flex-col\", GAP_CLASS[variant])\n  // People who turned motion off still get the whole row, by scrolling it themselves.\n  const still = (\n    <div\n      {...root}\n      data-static=\"true\"\n      tabIndex={0}\n      style={style}\n      className={cn(\n        base,\n        \"gap-(--mise-marquee-gap) outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n        vertical ? \"overflow-y-auto\" : \"items-center overflow-x-auto\",\n        className,\n      )}\n    >\n      {children}\n    </div>\n  )\n  const moving = (\n    <div\n      {...root}\n      data-paused={paused ? \"true\" : undefined}\n      style={{ \"--mise-marquee-duration\": `${duration}s`, ...style } as React.CSSProperties}\n      className={cn(base, \"overflow-hidden\", variant === \"fade\" && FADE_MASK[orientation], className)}\n      onBlur={(event) => {\n        onBlur?.(event)\n        // Focusing an off-screen link scrolls this clipped box. Undo it, or the loop runs on with a permanent offset.\n        if (event.currentTarget.contains(event.relatedTarget)) return\n        event.currentTarget.scrollTo?.(0, 0)\n      }}\n    >\n      <MarqueeCopy {...{ orientation, reverse, paused }}>{children}</MarqueeCopy>\n      <MarqueeCopy {...{ orientation, reverse, paused }} hidden>\n        {children}\n      </MarqueeCopy>\n    </div>\n  )\n  return (\n    <MarqueeContext.Provider value={context}>\n      {reduceMotion ? null : (\n        <style href=\"mise-marquee\" precedence=\"default\">\n          {KEYFRAMES}\n        </style>\n      )}\n      {reduceMotion ? still : moving}\n    </MarqueeContext.Provider>\n  )\n}\n\nfunction MarqueeItem({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { variant, orientation } = useContext(MarqueeContext)\n  return (\n    <div\n      data-slot=\"marquee-item\"\n      className={cn(\n        \"shrink-0\",\n        ITEM_CLASS[variant],\n        variant === \"cards\" && orientation === \"horizontal\" && \"w-64 max-w-[80vw]\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Marquee, MarqueeItem, type MarqueeProps, type MarqueeVariant }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "display"
  ],
  "type": "registry:ui"
}