{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "perspective-tilt",
  "title": "Perspective Tilt",
  "author": "ratneshc <ratneshchipre@gmail.com>",
  "description": "A 3D card with perspective tilt and blur transitions.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "src/registry/components/perspective-tilt/perspective-tilt.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype PerspectiveTiltContextType = {\n  /** Whether the dialog is currently open. */\n  open: boolean;\n  /** Function to update the open state. */\n  setOpen: (open: boolean) => void;\n};\n\nconst PerspectiveTiltContext =\n  React.createContext<PerspectiveTiltContextType | null>(null);\n\nfunction usePerspectiveTilt() {\n  const context = React.useContext(PerspectiveTiltContext);\n  if (!context) {\n    throw new Error(\n      \"PerspectiveTilt components must be wrapped in <PerspectiveTilt />\"\n    );\n  }\n  return context;\n}\n\n/** Hook to detect and respect the user's reduced motion system preference. */\nfunction useReducedMotion() {\n  const [prefersReducedMotion, setPrefersReducedMotion] = React.useState(false);\n\n  React.useEffect(() => {\n    const mediaQuery = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    setPrefersReducedMotion(mediaQuery.matches);\n\n    const handler = (event: MediaQueryListEvent) =>\n      setPrefersReducedMotion(event.matches);\n    mediaQuery.addEventListener(\"change\", handler);\n    return () => mediaQuery.removeEventListener(\"change\", handler);\n  }, []);\n\n  return prefersReducedMotion;\n}\n\nexport interface PerspectiveTiltProps {\n  /** Controlled open state of the dialog. */\n  open?: boolean;\n  /** Callback fired when the open state changes. */\n  onOpenChange?: (open: boolean) => void;\n  /** Children nodes to render within the root. */\n  children: React.ReactNode;\n}\n\nexport function PerspectiveTilt({\n  open: controlledOpen,\n  onOpenChange,\n  children,\n}: PerspectiveTiltProps) {\n  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);\n\n  const open = controlledOpen ?? uncontrolledOpen;\n  const setOpen = React.useCallback(\n    (value: boolean) => {\n      if (controlledOpen === undefined) {\n        setUncontrolledOpen(value);\n      }\n      onOpenChange?.(value);\n    },\n    [controlledOpen, onOpenChange]\n  );\n\n  return (\n    <PerspectiveTiltContext.Provider value={{ open, setOpen }}>\n      {children}\n    </PerspectiveTiltContext.Provider>\n  );\n}\n\nexport interface PerspectiveTiltTriggerProps {\n  /** Custom styling for the trigger element. */\n  className?: string;\n  /** Whether to merge props onto the child component. */\n  asChild?: boolean;\n  /** The element that triggers the dialog. */\n  children: React.ReactNode;\n}\n\nexport function PerspectiveTiltTrigger({\n  className,\n  asChild,\n  children,\n}: PerspectiveTiltTriggerProps) {\n  const { setOpen } = usePerspectiveTilt();\n\n  if (asChild && React.isValidElement(children)) {\n    const child = children as React.ReactElement<any>;\n    return React.cloneElement(child, {\n      className: cn(child.props.className, className),\n      onClick: (e: React.MouseEvent) => {\n        child.props.onClick?.(e);\n        setOpen(true);\n      },\n    });\n  }\n\n  return (\n    <button className={className} onClick={() => setOpen(true)}>\n      {children}\n    </button>\n  );\n}\n\nexport interface PerspectiveTiltOverlayProps {\n  /** Custom styling for the background backdrop. */\n  className?: string;\n  /** Callback fired when the overlay is clicked. */\n  onClick?: () => void;\n  /** The content to be rendered within the overlay. */\n  children: React.ReactNode;\n}\n\nexport function PerspectiveTiltOverlay({\n  className,\n  onClick,\n  children,\n}: PerspectiveTiltOverlayProps) {\n  return (\n    <motion.div\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1 }}\n      exit={{ opacity: 0 }}\n      transition={{ duration: 0.5, ease: [0.215, 0.61, 0.355, 1] }}\n      className={cn(\n        \"fixed inset-0 isolate z-50 flex items-center justify-center bg-black/10 supports-backdrop-filter:backdrop-blur-xs\",\n        className\n      )}\n      style={{ perspective: \"1200px\" }}\n      onClick={onClick}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nexport interface PerspectiveTiltContentProps {\n  /** Custom styling for the modal content card. */\n  className?: string;\n  /** The content to be rendered within the card. */\n  children: React.ReactNode;\n}\n\nexport function PerspectiveTiltContent({\n  className,\n  children,\n}: PerspectiveTiltContentProps) {\n  const { open, setOpen } = usePerspectiveTilt();\n  const prefersReducedMotion = useReducedMotion();\n\n  return (\n    <AnimatePresence mode=\"wait\">\n      {open && (\n        <PerspectiveTiltOverlay onClick={() => setOpen(false)}>\n          <motion.div\n            initial={{\n              filter: \"blur(8px)\",\n              scale: 0.96,\n              rotateX: 6,\n              rotateY: 25,\n              opacity: 0.8,\n            }}\n            animate={{\n              filter: \"blur(0px)\",\n              scale: 1,\n              rotateX: 0,\n              rotateY: 0,\n              opacity: 1,\n            }}\n            exit={{\n              filter: \"blur(8px)\",\n              scale: 0.96,\n              rotateX: 6,\n              rotateY: 25,\n              opacity: 0.8,\n            }}\n            transition={\n              prefersReducedMotion\n                ? { duration: 0 }\n                : {\n                    duration: 0.7,\n                    ease: [0.215, 0.61, 0.355, 1],\n                  }\n            }\n            onClick={(e) => e.stopPropagation()}\n            className={cn(\n              \"relative w-full max-w-[calc(100%-3rem)] rounded-xl bg-background p-6 shadow-lg ring-1 ring-foreground/10 outline-none sm:max-w-sm\",\n              className\n            )}\n            style={{\n              transformStyle: \"preserve-3d\",\n              transformOrigin: \"left left\",\n            }}\n          >\n            {children}\n          </motion.div>\n        </PerspectiveTiltOverlay>\n      )}\n    </AnimatePresence>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "https://ratneshc.com/components/perspective-tilt",
  "type": "registry:component"
}