{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "transition",
  "type": "registry:ui",
  "title": "Transition",
  "description": "Smooth page transitions with curved or slide effects.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["motion"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/transition.tsx",
      "content": "'use client';\n\nimport React, { useEffect, useRef, useState } from 'react';\nimport { useInView } from 'motion/react';\n\ntype Type = 'curved' | 'slide';\ntype Dir = 'top' | 'bottom' | 'left' | 'right';\n\nexport interface TransitionProps {\n  intro: React.ReactNode | ((triggerExit: () => void) => React.ReactNode);\n  children: React.ReactNode;\n  introDuration?: number;\n  transitionDuration?: number;\n  type?: Type;\n  direction?: Dir;\n  className?: string;\n  skip?: boolean;\n  autoExit?: boolean;\n  trigger?: boolean;\n  onFinished?: () => void;\n}\n\nconst easeInOutCubic = (t: number) =>\n  t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;\n\nexport const Transition: React.FC<TransitionProps> = ({\n  intro,\n  children,\n  introDuration = 1.5,\n  transitionDuration = 0.9,\n  type = 'curved',\n  direction = 'bottom',\n  className = 'bg-neutral-900 dark:bg-white',\n  skip = false,\n  autoExit = true,\n  trigger,\n  onFinished,\n}) => {\n  const [showIntro, setShowIntro] = useState(!skip);\n  const [animating, setAnimating] = useState(false);\n  const [progress, setProgress] = useState(0);\n\n  const ref = useRef<HTMLDivElement>(null);\n  const inView = useInView(ref, { margin: '-100px', once: true });\n\n  const rafRef = useRef<number | null>(null);\n  const timersRef = useRef<number[]>([]);\n\n  const propsRef = useRef({ transitionDuration, onFinished });\n  useEffect(() => {\n    propsRef.current = { transitionDuration, onFinished };\n  }, [transitionDuration, onFinished]);\n\n  const [triggerExit] = useState(() => {\n    return () => {\n      setAnimating(true);\n      let startTime: number | null = null;\n\n      const tick = (now: number) => {\n        if (!startTime) startTime = now;\n        const elapsed = (now - startTime) / 1000;\n        const raw = Math.min(elapsed / propsRef.current.transitionDuration, 1);\n        const eased = easeInOutCubic(raw);\n        setProgress(eased);\n\n        if (raw < 1) {\n          rafRef.current = requestAnimationFrame(tick);\n        } else {\n          setAnimating(false);\n          setShowIntro(false);\n          setProgress(0);\n          rafRef.current = null;\n          propsRef.current.onFinished?.();\n        }\n      };\n\n      rafRef.current = requestAnimationFrame(tick);\n    };\n  });\n\n  const startTransition = triggerExit;\n\n  useEffect(() => {\n    if (skip) {\n      onFinished?.();\n      return;\n    }\n\n    if (inView && autoExit) {\n      const t = window.setTimeout(\n        () => startTransition(),\n        introDuration * 1000,\n      );\n      timersRef.current.push(t);\n    }\n\n    const currentTimers = timersRef.current;\n    return () => {\n      currentTimers.forEach(clearTimeout);\n      if (rafRef.current) cancelAnimationFrame(rafRef.current);\n    };\n  }, [skip, inView, introDuration, autoExit, onFinished, startTransition]);\n\n  useEffect(() => {\n    if (!autoExit && trigger && showIntro) {\n      startTransition();\n    }\n  }, [trigger, autoExit, showIntro, startTransition]);\n\n  const getCurvedClip = (p: number) => {\n    const startRadius = 160;\n    const radius = Math.max(0, startRadius * (1 - p));\n    switch (direction) {\n      case 'top':\n        return `circle(${radius}% at 50% 0%)`;\n      case 'bottom':\n        return `circle(${radius}% at 50% 100%)`;\n      case 'left':\n        return `circle(${radius}% at 0% 50%)`;\n      case 'right':\n      default:\n        return `circle(${radius}% at 100% 50%)`;\n    }\n  };\n\n  const getSlideTransform = (p: number) => {\n    const pct = Math.round(p * 100);\n    switch (direction) {\n      case 'bottom':\n        return `translateY(${pct}%)`;\n      case 'top':\n        return `translateY(${-pct}%)`;\n      case 'left':\n        return `translateX(${-pct}%)`;\n      case 'right':\n      default:\n        return `translateX(${pct}%)`;\n    }\n  };\n\n  return (\n    <div ref={ref} className='relative w-full h-full min-h-full'>\n      <div className='relative z-0 w-full h-full'>{children}</div>\n\n      {showIntro && (\n        <div\n          className='absolute inset-0 z-40 flex items-center justify-center'\n          aria-hidden={!showIntro ? undefined : true}\n        >\n          <div\n            className='absolute inset-0'\n            style={\n              type === 'curved'\n                ? {\n                    clipPath: getCurvedClip(progress),\n                    transition: animating ? undefined : 'none',\n                  }\n                : { transform: getSlideTransform(progress) }\n            }\n          >\n            <div className={`absolute inset-0 ${className}`} />\n            <div className='absolute inset-0 flex items-center justify-center'>\n              {typeof intro === 'function' ? intro(triggerExit) : intro}\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default Transition;\n"
    }
  ]
}
