11import { useState , useEffect } from 'react' ;
2+ import { motion , AnimatePresence } from 'framer-motion' ;
23
34interface ImageLightboxProps {
45 images : { src : string ; alt ?: string } [ ] ;
@@ -28,7 +29,11 @@ export default function ImageLightbox({
2829
2930 if ( isOpen ) {
3031 document . addEventListener ( 'keydown' , handleKeyDown ) ;
31- return ( ) => document . removeEventListener ( 'keydown' , handleKeyDown ) ;
32+ document . body . style . overflow = 'hidden' ;
33+ return ( ) => {
34+ document . removeEventListener ( 'keydown' , handleKeyDown ) ;
35+ document . body . style . overflow = 'unset' ;
36+ } ;
3237 }
3338 } , [ isOpen , currentIndex ] ) ;
3439
@@ -44,69 +49,87 @@ export default function ImageLightbox({
4449 } ;
4550
4651 return (
47- < div
48- className = "fixed inset-0 z-50 bg-black/90 flex items-center justify-center"
49- onClick = { onClose }
50- role = "dialog"
51- aria-modal = "true"
52- aria-label = "Image gallery"
53- >
54- < button
55- onClick = { handleClose }
56- className = "absolute top-4 right-4 text-white text-2xl hover:text-gray-300 z-10"
57- aria-label = "Close gallery"
58- >
59- ×
60- </ button >
52+ < AnimatePresence >
53+ { isOpen && (
54+ < motion . div
55+ initial = { { opacity : 0 } }
56+ animate = { { opacity : 1 } }
57+ exit = { { opacity : 0 } }
58+ transition = { { duration : 0.2 } }
59+ className = "fixed inset-0 z-50 bg-black/95 flex items-center justify-center"
60+ onClick = { onClose }
61+ role = "dialog"
62+ aria-modal = "true"
63+ aria-label = "Image gallery"
64+ >
65+ < button
66+ onClick = { handleClose }
67+ className = "absolute top-4 right-4 text-white/80 hover:text-white text-3xl w-10 h-10 flex items-center justify-center rounded-full hover:bg-white/10 transition-colors z-10"
68+ aria-label = "Close gallery"
69+ >
70+ ×
71+ </ button >
6172
62- < div className = "relative w-full h-full flex items-center justify-center p-8" >
63- < img
64- src = { images [ currentIndex ] . src }
65- alt = { images [ currentIndex ] . alt || '' }
66- className = "max-w-full max-h-full object-contain"
67- onClick = { ( e ) => e . stopPropagation ( ) }
68- />
73+ < div className = "relative w-full h-full flex items-center justify-center p-4 md:p-8" >
74+ < AnimatePresence mode = "wait" custom = { currentIndex } >
75+ < motion . img
76+ key = { currentIndex }
77+ src = { images [ currentIndex ] . src }
78+ alt = { images [ currentIndex ] . alt || '' }
79+ initial = { { opacity : 0 , scale : 0.9 } }
80+ animate = { { opacity : 1 , scale : 1 } }
81+ exit = { { opacity : 0 , scale : 0.9 } }
82+ transition = { { duration : 0.3 , ease : 'easeOut' } }
83+ className = "max-w-full max-h-full w-auto h-auto object-contain rounded-lg"
84+ onClick = { ( e ) => e . stopPropagation ( ) }
85+ style = { { maxWidth : '90vw' , maxHeight : '90vh' } }
86+ />
87+ </ AnimatePresence >
6988
70- { images . length > 1 && (
71- < >
72- < button
73- onClick = { ( e ) => {
74- e . stopPropagation ( ) ;
75- goToPrev ( ) ;
76- } }
77- className = "absolute left-4 text-white text-4xl hover:text-gray-300"
78- aria-label = "Previous image"
79- >
80- ‹
81- </ button >
82- < button
83- onClick = { ( e ) => {
84- e . stopPropagation ( ) ;
85- goToNext ( ) ;
86- } }
87- className = "absolute right-4 text-white text-4xl hover:text-gray-300"
88- aria-label = "Next image"
89- >
90- ›
91- </ button >
92- < div className = "absolute bottom-4 flex gap-2" >
93- { images . map ( ( _ , idx ) => (
89+ { images . length > 1 && (
90+ < >
9491 < button
95- key = { images [ idx ] . src }
9692 onClick = { ( e ) => {
9793 e . stopPropagation ( ) ;
98- setCurrentIndex ( idx ) ;
94+ goToPrev ( ) ;
9995 } }
100- className = { `w-2 h-2 rounded-full ${
101- idx === currentIndex ? 'bg-white' : 'bg-white/50'
102- } `}
103- aria-label = { `Go to image ${ idx + 1 } ` }
104- />
105- ) ) }
106- </ div >
107- </ >
108- ) }
109- </ div >
110- </ div >
96+ className = "absolute left-4 text-white/80 hover:text-white text-5xl w-12 h-12 flex items-center justify-center rounded-full hover:bg-white/10 transition-colors"
97+ aria-label = "Previous image"
98+ >
99+ ‹
100+ </ button >
101+ < button
102+ onClick = { ( e ) => {
103+ e . stopPropagation ( ) ;
104+ goToNext ( ) ;
105+ } }
106+ className = "absolute right-4 text-white/80 hover:text-white text-5xl w-12 h-12 flex items-center justify-center rounded-full hover:bg-white/10 transition-colors"
107+ aria-label = "Next image"
108+ >
109+ ›
110+ </ button >
111+ < div className = "absolute bottom-6 flex gap-2" >
112+ { images . map ( ( _ , idx ) => (
113+ < button
114+ key = { images [ idx ] . src }
115+ onClick = { ( e ) => {
116+ e . stopPropagation ( ) ;
117+ setCurrentIndex ( idx ) ;
118+ } }
119+ className = { `w-2 h-2 rounded-full transition-all ${
120+ idx === currentIndex
121+ ? 'bg-white w-6'
122+ : 'bg-white/50 hover:bg-white/70'
123+ } `}
124+ aria-label = { `Go to image ${ idx + 1 } ` }
125+ />
126+ ) ) }
127+ </ div >
128+ </ >
129+ ) }
130+ </ div >
131+ </ motion . div >
132+ ) }
133+ </ AnimatePresence >
111134 ) ;
112135}
0 commit comments