{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flip-grid",
  "title": "Flip Grid",
  "description": "A grid whose items glide to new places when filtered or sorted.",
  "dependencies": [
    "gsap"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.cuelark.com/r/theme.json",
    "https://ui.cuelark.com/r/motion.json"
  ],
  "files": [
    {
      "path": "src/registry/cuelark/ui/flip-grid.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Flip } from \"gsap/Flip\";\n\nimport { gsap, motionScale } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\n\nif (typeof window !== \"undefined\") gsap.registerPlugin(Flip);\n\ntype FlipGridProps = React.ComponentProps<\"div\"> & {\n  /** Change this (the active filter, the sort order) to animate the next layout. */\n  layoutKey: string | number;\n};\n\n/**\n * A grid that animates its own layout changes with GSAP Flip. Wrap items in `FlipGridItem` and toggle `hidden`\n * instead of unmounting them: items glide to new cells, leaving ones shrink out, entering ones grow in, and the\n * container eases to its new height so nothing below jumps.\n */\nfunction FlipGrid({ className, layoutKey, children, ...props }: FlipGridProps) {\n  const ref = React.useRef<HTMLDivElement>(null);\n  const snapshot = React.useRef<{ state: Flip.FlipState; height: number } | null>(null);\n  const lastKey = React.useRef(layoutKey);\n\n  // Record the \"before\" layout during render, before React commits the change.\n  if (lastKey.current !== layoutKey) {\n    lastKey.current = layoutKey;\n    const el = ref.current;\n    if (el) snapshot.current = { state: Flip.getState(el.children, { props: \"opacity\" }), height: el.offsetHeight };\n  }\n\n  React.useLayoutEffect(() => {\n    const el = ref.current;\n    const snap = snapshot.current;\n    snapshot.current = null;\n    if (!el || !snap || !motionScale(el)) return;\n    const height = el.offsetHeight;\n    if (height !== snap.height) gsap.fromTo(el, { height: snap.height }, { height, duration: 0.5, ease: \"cue\", clearProps: \"height\" });\n    Flip.from(snap.state, {\n      duration: 0.5,\n      ease: \"cue\",\n      // Only leaving items are pulled out of flow; staying items animate in place, so nothing snaps at the end.\n      absoluteOnLeave: true,\n      stagger: 0.015,\n      onEnter: (els) => gsap.fromTo(els, { opacity: 0, scale: 0.85 }, { opacity: 1, scale: 1, duration: 0.4, ease: \"cue\", delay: 0.12, clearProps: \"opacity,scale\" }),\n      onLeave: (els) => gsap.to(els, { opacity: 0, scale: 0.85, duration: 0.25, ease: \"cue\" }),\n    });\n  }, [layoutKey]);\n\n  return (\n    <div ref={ref} data-slot=\"flip-grid\" className={cn(\"relative grid gap-3\", className)} {...props}>\n      {children}\n    </div>\n  );\n}\n\n/** A grid item; set `hidden` to filter it out (it stays mounted so it can animate away and back). */\nfunction FlipGridItem({ className, hidden, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"flip-grid-item\" data-flip-id={props.id} hidden={hidden} className={className} style={hidden ? { display: \"none\" } : undefined} {...props} />;\n}\n\nexport { FlipGrid, FlipGridItem };\nexport type { FlipGridProps };\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Docs and examples: https://ui.cuelark.com/components/flip-grid",
  "categories": [
    "effects"
  ],
  "type": "registry:ui"
}