{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "author": "DesignX ", "name": "morphing-modal", "type": "registry:component", "title": "Morphing Modal", "description": "A card that morphs into a full modal.", "dependencies": [ "motion@^14.0.0" ], "registryDependencies": [ "@dx/lib-ease", "@dx/lib-presence-gate", "@dx/utils" ], "files": [ { "path": "components/motion/morphing-modal.tsx", "type": "registry:component", "target": "components/motion/morphing-modal.tsx", "content": "import {\n AnimatePresence,\n motion,\n useReducedMotion,\n} from \"motion/react\";\nimport { type ReactNode, useEffect } from \"react\";\nimport { EASE_OUT, SPRING_PANEL } from \"@/lib/ease\";\nimport { PresenceGate } from \"@/lib/presence-gate\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface MorphingModalProps {\n /** Which view is currently shown. `null` closes the modal. */\n viewId: string | null;\n onClose: () => void;\n children: ReactNode;\n /** \"bottom\" anchors to the viewport bottom (mobile-like). \"center\" centers vertically. */\n placement?: \"bottom\" | \"center\";\n className?: string;\n}\n\nexport function MorphingModal({\n viewId,\n onClose,\n children,\n placement = \"bottom\",\n className,\n}: MorphingModalProps) {\n const open = viewId !== null;\n const reduce = useReducedMotion();\n const enterY = reduce ? 0 : placement === \"bottom\" ? 40 : 20;\n const enterScale = reduce ? 1 : 0.97;\n\n useEffect(() => {\n if (!open) return;\n const prev = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = prev;\n };\n }, [open]);\n\n // Mounted only while open, and while open the chrome is two fixed siblings\n // rather than one wrapper: the backdrop spans the viewport edges but carries\n // the scrim colour, and the layer positioning the panel sits inset off every\n // edge (`inset-4`, with the bottom placement's `pb-4` on top of it). Both hang\n // off `PresenceGate`, so interaction releases in the same commit that starts\n // the exit rather than when it ends`open` is already false for those\n // frames.\n return (\n \n {open ? (\n \n {({ gate }) => (\n \n )}\n \n ) : null}\n\n {open ? (\n \n {({ isPresent, gate }) => (\n // The layer itself never takes pointer events, so it carries\n // `inert` alone rather than the gate's pointer-events value.\n \n \n \n \n \n {children}\n \n \n \n \n \n )}\n \n ) : null}\n \n );\n}\n" } ], "categories": [ "motion-kit" ], "meta": { "group": "motion", "docs": "https://dxuireact.com/docs/components/morphing-modal" } }