{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-list",
  "title": "Animated List",
  "description": "A live feed where items arrive and push the rest down.",
  "dependencies": [],
  "registryDependencies": [
    "utils",
    "https://ui.cuelark.com/r/theme.json",
    "https://ui.cuelark.com/r/motion.json"
  ],
  "files": [
    {
      "path": "src/registry/cuelark/ui/animated-list.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { gsap, motionScale } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\n\ntype AnimatedListProps = React.ComponentProps<\"ul\"> & {\n  /** Milliseconds between items appearing. */\n  interval?: number;\n  /** Keep cycling through the items forever (a live feed). */\n  loop?: boolean;\n  /** Most items visible at once. */\n  max?: number;\n};\n\n/**\n * A live-feed list: items arrive one by one at the top, pushing the rest down with a spring (GSAP Flip-style\n * position animation). Notifications, activity, transactions. Shows everything statically with motion off.\n */\nfunction AnimatedList({ className, interval = 1400, loop = true, max = 5, children, ...props }: AnimatedListProps) {\n  const all = React.Children.toArray(children);\n  const ref = React.useRef<HTMLUListElement>(null);\n  const [live, setLive] = React.useState(false);\n  const [tick, setTick] = React.useState(0);\n\n  React.useEffect(() => {\n    const el = ref.current;\n    if (!el || !motionScale(el) || !all.length) return;\n    setLive(true);\n    setTick(0);\n    const id = setInterval(() => setTick((t) => (loop || t < all.length - 1 ? t + 1 : t)), interval);\n    return () => clearInterval(id);\n  }, [interval, loop, all.length]);\n\n  const newest = loop ? tick : Math.min(tick, all.length - 1);\n  const shown = !live\n    ? all.slice(0, max).map((node, i) => ({ key: -1 - i, node }))\n    : Array.from({ length: Math.min(newest + 1, max) }, (_, i) => ({ key: newest - i, node: all[(newest - i) % all.length] }));\n\n  const positions = React.useRef(new Map<string, number>());\n  React.useLayoutEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const scale = motionScale(el);\n    const items = Array.from(el.children) as HTMLElement[];\n    items.forEach((item) => {\n      const key = item.dataset.key!;\n      const prev = positions.current.get(key);\n      const now = item.offsetTop;\n      if (!scale) return;\n      if (prev === undefined) gsap.fromTo(item, { opacity: 0, scale: 0.92, y: -12 }, { opacity: 1, scale: 1, y: 0, duration: 0.5, ease: \"cue-snap\" });\n      else if (prev !== now) gsap.fromTo(item, { y: prev - now }, { y: 0, duration: 0.5, ease: \"cue\" });\n    });\n    positions.current = new Map(items.map((i) => [i.dataset.key!, i.offsetTop]));\n  });\n\n  return (\n    <ul ref={ref} data-slot=\"animated-list\" aria-live=\"polite\" className={cn(\"grid gap-2\", className)} {...props}>\n      {shown.map(({ key, node }) => (\n        <li key={key} data-key={key}>\n          {node}\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nexport { AnimatedList };\nexport type { AnimatedListProps };\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Docs and examples: https://ui.cuelark.com/components/animated-list",
  "categories": [
    "effects"
  ],
  "type": "registry:ui"
}