{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"morph-modal","type":"registry:block","title":"Morph modal","description":"A reusable modal component that smoothly 'morphs' from the dimensions and position of its trigger element","dependencies":["motion"],"files":[{"path":"registry/100xui/blocks/morph-modal/components/morph-modal.tsx","content":"\"use client\";\r\n\r\nimport React from \"react\";\r\nimport {\r\n  AnimatePresence,\r\n  motion,\r\n  type HTMLMotionProps,\r\n  type Transition,\r\n} from \"motion/react\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\nfunction assertOnlyChild(children: React.ReactNode) {\r\n  if (Array.isArray(children))\r\n    throw new Error(\r\n      \"A single child is required, but received multiple siblings.\"\r\n    );\r\n  if (\r\n    children &&\r\n    React.isValidElement(children) &&\r\n    children.type === React.Fragment &&\r\n    Array.isArray((children.props as React.FragmentProps).children)\r\n  )\r\n    throw new Error(\r\n      \"A single child is required; fragments that render multiple siblings are not allowed.\"\r\n    );\r\n}\r\n\r\ntype MorphModalContextType = {\r\n  openModal: boolean;\r\n  setOpenModal: React.Dispatch<React.SetStateAction<boolean>>;\r\n  modalId: string;\r\n  transition: Transition;\r\n};\r\n\r\nconst MorphModalContext = React.createContext<\r\n  MorphModalContextType | undefined\r\n>(undefined);\r\n\r\nexport const useMorphModal = () => {\r\n  const ctx = React.useContext(MorphModalContext);\r\n  if (!ctx)\r\n    throw new Error(\"useMorphModal must be used within a <MorphModal />\");\r\n  return ctx;\r\n};\r\n\r\ninterface MorphModalProps {\r\n  children: React.ReactNode;\r\n  transition?: Omit<Transition, \"delay\">;\r\n}\r\n\r\nconst DEFAULT_DURATION = 0.3;\r\nconst DEFAULT_EASE = \"easeInOut\";\r\n\r\nexport function MorphModal({ children, transition }: MorphModalProps) {\r\n  const modalId = React.useId();\r\n  const [openModal, setOpenModal] = React.useState(false);\r\n\r\n  return (\r\n    <MorphModalContext.Provider\r\n      value={{\r\n        openModal,\r\n        setOpenModal,\r\n        modalId,\r\n        transition: {\r\n          duration: DEFAULT_DURATION,\r\n          ease: DEFAULT_EASE,\r\n          ...transition,\r\n        },\r\n      }}\r\n    >\r\n      {children}\r\n    </MorphModalContext.Provider>\r\n  );\r\n}\r\n\r\ninterface MorphModalTriggerProps extends HTMLMotionProps<\"button\"> {\r\n  children: React.ReactNode;\r\n}\r\n\r\nexport function MorphModalTrigger({\r\n  children,\r\n  className,\r\n  onClick,\r\n  ...rest\r\n}: MorphModalTriggerProps) {\r\n  assertOnlyChild(children);\r\n\r\n  const { openModal, setOpenModal, transition, modalId } = useMorphModal();\r\n  const { duration, ...restTransition } = transition;\r\n\r\n  return (\r\n    <div className=\"relative\">\r\n      <AnimatePresence initial={false}>\r\n        {!openModal && (\r\n          <motion.button\r\n            onClick={(e) => {\r\n              setOpenModal(true);\r\n              onClick?.(e);\r\n            }}\r\n            className={cn(\r\n              \"bg-primary text-primary-foreground hover:bg-accent/80 hover:text-accent-foreground/80 size-fit cursor-pointer overflow-hidden transition-colors duration-150 ease-out\",\r\n              className,\r\n              \"!absolute !inset-0\"\r\n            )}\r\n            {...rest}\r\n            transition={{ layout: { duration, ...restTransition } }}\r\n            layoutId={modalId + \"-morph-modal\"}\r\n          >\r\n            <motion.div\r\n              initial={{ opacity: 0 }}\r\n              animate={{\r\n                opacity: 1,\r\n                transition: { delay: duration, ...restTransition },\r\n              }}\r\n              exit={{ opacity: 0 }}\r\n              layoutId={modalId + \"morph-modal-trigger-child\"}\r\n              transition={{ duration, ...restTransition }}\r\n            >\r\n              {children}\r\n            </motion.div>\r\n          </motion.button>\r\n        )}\r\n      </AnimatePresence>\r\n      <div className={cn(\"invisible\", className)} aria-hidden>\r\n        {children}\r\n      </div>\r\n    </div>\r\n  );\r\n}\r\n\r\ninterface MorphModalContentProps extends HTMLMotionProps<\"div\"> {\r\n  children: React.ReactNode;\r\n}\r\n\r\nexport function MorphModalContent({\r\n  children,\r\n  className,\r\n  onClick,\r\n  ...rest\r\n}: MorphModalContentProps) {\r\n  assertOnlyChild(children);\r\n\r\n  const { transition, modalId } = useMorphModal();\r\n  const { duration, ...restTransition } = transition;\r\n\r\n  return (\r\n    <motion.div\r\n      layoutId={modalId + \"-morph-modal\"}\r\n      transition={{ layout: { duration, ...restTransition } }}\r\n      className={cn(\"size-fit overflow-hidden\", className)}\r\n      {...rest}\r\n    >\r\n      <motion.div\r\n        layoutId={modalId + \"morph-modal-content-child\"}\r\n        initial={{ opacity: 0 }}\r\n        animate={{\r\n          opacity: 1,\r\n          transition: { delay: duration, ...restTransition },\r\n        }}\r\n        exit={{ opacity: 0 }}\r\n        onClick={(e) => {\r\n          e.stopPropagation();\r\n          onClick?.(e);\r\n        }}\r\n        {...rest}\r\n      >\r\n        {children}\r\n      </motion.div>\r\n    </motion.div>\r\n  );\r\n}\r\n\r\ninterface MorphModalOverlayProps extends HTMLMotionProps<\"div\"> {\r\n  children: React.ReactNode;\r\n}\r\n\r\nexport function MorphModalOverlay({\r\n  children,\r\n  className,\r\n  onClick,\r\n  ...rest\r\n}: MorphModalOverlayProps) {\r\n  const { openModal, setOpenModal, transition } = useMorphModal();\r\n\r\n  return (\r\n    <AnimatePresence>\r\n      {openModal && (\r\n        <motion.div\r\n          initial={{ backdropFilter: \"blur(0px)\" }}\r\n          animate={{ backdropFilter: \"blur(2px)\" }}\r\n          exit={{ backdropFilter: \"blur(0px)\" }}\r\n          transition={transition}\r\n          className={cn(\r\n            \"fixed inset-0 isolate z-50 grid place-items-center\",\r\n            className\r\n          )}\r\n          onClick={(e) => {\r\n            setOpenModal(false);\r\n            onClick?.(e);\r\n          }}\r\n          {...rest}\r\n        >\r\n          {children}\r\n        </motion.div>\r\n      )}\r\n    </AnimatePresence>\r\n  );\r\n}\r\n","type":"registry:component"}]}