Dialog

An animated dialog component powered by Framer Motion, offering smooth transitions and interactive visual effects for modal windows

Installation

npm install framer-motion
modal.tsx
import React, {
  createContext,
  useContext,
  useState,
  useEffect,
  ReactNode,
} from 'react'
import { AnimatePresence, motion } from 'framer-motion'
import { X } from 'lucide-react'
 
interface ModalContextProps {
  open: boolean
  setOpen: (open: boolean) => void
}
 
const ModalContext = createContext<ModalContextProps | undefined>(undefined)
 
const useModal = () => {
  const context = useContext(ModalContext)
  if (!context) {
    throw new Error('useModal must be used within a ModalProvider')
  }
  return context
}
 
interface FramerModalProps {
  children: ReactNode
  open?: boolean
  setOpen?: (open: boolean) => void
}
 
export function FramerModal({
  children,
  open: controlledOpen,
  setOpen: controlledSetOpen,
}: FramerModalProps) {
  const [internalOpen, setInternalOpen] = useState(false)
  const open = controlledOpen !== undefined ? controlledOpen : internalOpen
  const setOpen =
    controlledSetOpen !== undefined ? controlledSetOpen : setInternalOpen
  useEffect(() => {
    if (open) {
      document.body.classList.add('overflow-hidden')
    } else {
      document.body.classList.remove('overflow-hidden')
    }
 
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        setOpen(false)
      }
    }
 
    document.addEventListener('keydown', handleKeyDown)
    return () => {
      document.removeEventListener('keydown', handleKeyDown)
    }
  }, [open])
  return (
    <ModalContext.Provider value={{ open, setOpen }}>
      <AnimatePresence>
        {open && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 z-20 top-0 left-0 right-0 bottom-0 flex flex-col items-center w-full h-screen justify-center dark:bg-black/90 bg-white/90 backdrop-blur-sm cursor-zoom-out border"
            onClick={() => setOpen(false)}
          >
            <motion.div
              initial={{ opacity: 0, y: 8 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: 8 }}
              onClick={(e) => e.stopPropagation()}
              className=" w-full max-w-md rounded-xl bg-white/5 p-6 backdrop-blur-2xl border"
            >
              <button
                className="absolute top-2 right-2"
                onClick={() => setOpen(false)}
              >
                <X />
              </button>
              {children}
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>
    </ModalContext.Provider>
  )
}
 
export function ModalContent({ children }: { children: ReactNode }) {
  return <>{children}</>
}

Props

FramerModal Props

PropTypeDefaultDescription
childrenReact.ReactNode-The content inside the modal.
openbooleanundefinedControls the visibility of the modal.
setOpen(open: boolean) => voidundefinedFunction to set the modal's visibility state.

ModalContent Props

PropTypeDefaultDescription
childrenReact.ReactNode-The content inside the modal content area.