Vokturz's picture
Improve modal animations and UI interactions
25e8265
raw
history blame
3.34 kB
import React, { useState, useEffect } from 'react'
import { X } from 'lucide-react'
interface ModalProps {
isOpen: boolean
onClose: () => void
title: React.ReactNode
children: React.ReactNode
maxWidth?:
| 'sm'
| 'md'
| 'lg'
| 'xl'
| '2xl'
| '3xl'
| '4xl'
| '5xl'
| '6xl'
| '7xl'
}
const Modal: React.FC<ModalProps> = ({
isOpen,
onClose,
title,
children,
maxWidth = '4xl'
}) => {
// State to control if the modal is in the DOM
const [isRendered, setIsRendered] = useState(isOpen)
// State to control the animation classes
const [isAnimating, setIsAnimating] = useState(false)
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose()
}
}
if (isOpen) {
setIsRendered(true)
document.body.style.overflow = 'hidden'
document.addEventListener('keydown', handleEscape)
const animationTimeout = setTimeout(() => setIsAnimating(true), 20)
return () => clearTimeout(animationTimeout)
} else {
setIsAnimating(false)
const unmountTimeout = setTimeout(() => {
setIsRendered(false)
document.body.style.overflow = 'unset'
document.removeEventListener('keydown', handleEscape)
}, 300)
return () => clearTimeout(unmountTimeout)
}
}, [isOpen, onClose])
// Unmount the component completely when not rendered
if (!isRendered) return null
const maxWidthClasses = {
sm: 'max-w-sm',
md: 'max-w-md',
lg: 'max-w-lg',
xl: 'max-w-xl',
'2xl': 'max-w-2xl',
'3xl': 'max-w-3xl',
'4xl': 'max-w-4xl',
'5xl': 'max-w-5xl',
'6xl': 'max-w-6xl',
'7xl': 'max-w-7xl'
}
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
{/* Backdrop */}
<div
className={`fixed inset-0 bg-black transition-opacity duration-300 ease-in-out ${
isAnimating ? 'opacity-50' : 'opacity-0'
}`}
onClick={onClose}
/>
{/* Modal */}
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
<div
className={`relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all duration-300 ease-in-out sm:my-8 sm:w-full ${
maxWidthClasses[maxWidth]
} ${
isAnimating
? 'opacity-100 translate-y-0 sm:scale-100'
: 'opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
}`}
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between border-b border-gray-200 px-6 py-4">
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
<button
onClick={onClose}
className="rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
<span className="sr-only">Close</span>
<X className="h-5 w-5" />
</button>
</div>
{/* Content */}
<div className="max-h-[calc(100vh-200px)] overflow-y-auto px-6 py-4">
{children}
</div>
</div>
</div>
</div>
)
}
export default Modal