Media Modals
A media modal component that opens images or videos with a smooth animation
A media modal component that opens images or videos with a smooth animation
npm install framer-motion
1// @ts-nocheck2'use client';3import React, { useEffect, useId, useState } from 'react';4import { AnimatePresence, motion, MotionConfig } from 'framer-motion';5import { useMediaQuery } from '@/hooks/use-media-query';6import { XIcon } from 'lucide-react';78interface IMediaModal {9imgSrc?: string;10videoSrc?: string;11className?: string;12}13const transition = {14type: 'spring',15duration: 0.4,16};17export function MediaModal({ imgSrc, videoSrc, className }: IMediaModal) {18const [isMediaModalOpen, setIsMediaModalOpen] = useState(false);19const isDesktop = useMediaQuery('(min-width:768px)');20const uniqueId = useId();2122useEffect(() => {23if (isMediaModalOpen) {24document.body.classList.add('overflow-hidden');25} else {26document.body.classList.remove('overflow-hidden');27}2829const handleKeyDown = (event: KeyboardEvent) => {30if (event.key === 'Escape') {31setIsMediaModalOpen(false);32}33};3435document.addEventListener('keydown', handleKeyDown);36return () => {37document.removeEventListener('keydown', handleKeyDown);38};39}, [isMediaModalOpen]);40return (41<>42<MotionConfig transition={transition}>43<>44<motion.div45// @ts-ignore46className='w-full h-full flex relative flex-col overflow-hidden border dark:bg-black bg-gray-300 hover:bg-gray-200 dark:hover:bg-gray-950'47layoutId={`dialog-${uniqueId}`}48style={{49borderRadius: '12px',50}}51onClick={() => {52setIsMediaModalOpen(true);53}}54>55{imgSrc && (56<motion.div57layoutId={`dialog-img-${uniqueId}`}58className='w-full h-full'59>60{/* eslint-disable-next-line @next/next/no-img-element */}61<img62src={imgSrc}63alt='A desk lamp designed by Edouard Wilfrid Buquet in 1925. It features a double-arm design and is made from nickel-plated brass, aluminium and varnished wood.'64className=' w-full object-cover h-full'65/>66</motion.div>67)}68{videoSrc && (69<motion.div70layoutId={`dialog-video-${uniqueId}`}71className='w-full h-full'72>73<video74autoPlay75muted76loop77className='h-full w-full object-cover rounded-sm'78>79<source src={videoSrc!} type='video/mp4' />80</video>81</motion.div>82)}83</motion.div>84</>85<AnimatePresence initial={false} mode='sync'>86{isMediaModalOpen && (87<>88<motion.div89key={`backdrop-${uniqueId}`}90className='fixed inset-0 h-full w-full dark:bg-black/25 bg-white/95 backdrop-blur-sm '91variants={{ open: { opacity: 1 }, closed: { opacity: 0 } }}92initial='closed'93animate='open'94exit='closed'95onClick={() => {96setIsMediaModalOpen(false);97}}98/>99<motion.div100key='dialog'101className='pointer-events-none fixed inset-0 flex items-center justify-center z-50'102>103<motion.div104className='pointer-events-auto relative flex flex-col overflow-hidden dark:bg-gray-950 bg-gray-200 border w-[80%] h-[90%] '105layoutId={`dialog-${uniqueId}`}106tabIndex={-1}107style={{108borderRadius: '24px',109}}110>111{imgSrc && (112<motion.div113layoutId={`dialog-img-${uniqueId}`}114className='w-full h-full'115>116{/* eslint-disable-next-line @next/next/no-img-element */}117<img118src={imgSrc}119alt=''120className='h-full w-full object-cover'121/>122</motion.div>123)}124{videoSrc && (125<motion.div126layoutId={`dialog-video-${uniqueId}`}127className='w-full h-full'128>129<video130autoPlay131muted132loop133controls134className='h-full w-full object-cover rounded-sm'135>136<source src={videoSrc!} type='video/mp4' />137</video>138</motion.div>139)}140141<button142onClick={() => setIsMediaModalOpen(false)}143className='absolute right-6 top-6 p-3 text-zinc-50 cursor-pointer dark:bg-gray-900 bg-gray-400 hover:bg-gray-500 rounded-full dark:hover:bg-gray-800'144type='button'145aria-label='Close dialog'146>147<XIcon size={24} />148</button>149</motion.div>150</motion.div>151</>152)}153</AnimatePresence>154</MotionConfig>155</>156);157}
Prop | Type | Default | Description |
---|---|---|---|
imgSrc | string | undefined | Optional source URL for an image to display in the modal. |
videoSrc | string | undefined | Optional source URL for a video to display in the modal. |
className | string | undefined | Optional CSS class for styling the modal component. |
Sometimes, we don't need reusable components because they are most useful when a component is used 2-3 times or more. However, in a single-page application, reusable components aren't always necessary. In such cases, you can use this component instead, and it will give you the same effect.