1+ import { AnimatePresence , motion } from 'framer-motion' ;
2+ import { ChevronLeft , ChevronRight , X } from 'lucide-react' ;
3+ import React , { useEffect } from 'react' ;
4+
5+ interface ImageModalProps {
6+ isOpen : boolean ;
7+ onClose : ( ) => void ;
8+ images : string [ ] ;
9+ currentIndex : number ;
10+ onPrevious : ( ) => void ;
11+ onNext : ( ) => void ;
12+ onGoToIndex : ( index : number ) => void ;
13+ alt : string ;
14+ }
15+
16+ const ImageModal : React . FC < ImageModalProps > = ( {
17+ isOpen,
18+ onClose,
19+ images,
20+ currentIndex,
21+ onPrevious,
22+ onNext,
23+ onGoToIndex,
24+ alt
25+ } ) => {
26+ // Handle escape key
27+ useEffect ( ( ) => {
28+ const handleEscape = ( e : KeyboardEvent ) => {
29+ if ( e . key === 'Escape' ) {
30+ onClose ( ) ;
31+ }
32+ } ;
33+
34+ if ( isOpen ) {
35+ document . addEventListener ( 'keydown' , handleEscape ) ;
36+ document . body . style . overflow = 'hidden' ;
37+ }
38+
39+ return ( ) => {
40+ document . removeEventListener ( 'keydown' , handleEscape ) ;
41+ document . body . style . overflow = '' ;
42+ } ;
43+ } , [ isOpen , onClose ] ) ;
44+
45+ // Handle arrow keys
46+ useEffect ( ( ) => {
47+ const handleArrowKeys = ( e : KeyboardEvent ) => {
48+ if ( e . key === 'ArrowLeft' ) {
49+ onPrevious ( ) ;
50+ } else if ( e . key === 'ArrowRight' ) {
51+ onNext ( ) ;
52+ }
53+ } ;
54+
55+ if ( isOpen ) {
56+ document . addEventListener ( 'keydown' , handleArrowKeys ) ;
57+ }
58+
59+ return ( ) => {
60+ document . removeEventListener ( 'keydown' , handleArrowKeys ) ;
61+ } ;
62+ } , [ isOpen , onPrevious , onNext ] ) ;
63+
64+ if ( ! isOpen || images . length === 0 ) return null ;
65+
66+ return (
67+ < AnimatePresence >
68+ < motion . div
69+ initial = { { opacity : 0 } }
70+ animate = { { opacity : 1 } }
71+ exit = { { opacity : 0 } }
72+ transition = { { duration : 0.3 } }
73+ className = "fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-90"
74+ onClick = { onClose }
75+ >
76+ { /* Close button */ }
77+ < button
78+ onClick = { onClose }
79+ className = "absolute top-4 right-4 z-60 p-2 bg-black bg-opacity-50 text-white rounded-full hover:bg-opacity-70 transition-colors"
80+ >
81+ < X className = "h-6 w-6" />
82+ </ button >
83+
84+ { /* Image counter */ }
85+ { images . length > 1 && (
86+ < div className = "absolute top-4 left-4 z-60 px-3 py-1 bg-black bg-opacity-50 text-white text-sm rounded-full" >
87+ { currentIndex + 1 } of { images . length }
88+ </ div >
89+ ) }
90+
91+ { /* Previous button */ }
92+ { images . length > 1 && (
93+ < button
94+ onClick = { ( e ) => {
95+ e . stopPropagation ( ) ;
96+ onPrevious ( ) ;
97+ } }
98+ className = "absolute left-4 top-1/2 transform -translate-y-1/2 z-60 p-3 bg-black bg-opacity-50 text-white rounded-full hover:bg-opacity-70 transition-colors"
99+ >
100+ < ChevronLeft className = "h-6 w-6" />
101+ </ button >
102+ ) }
103+
104+ { /* Next button */ }
105+ { images . length > 1 && (
106+ < button
107+ onClick = { ( e ) => {
108+ e . stopPropagation ( ) ;
109+ onNext ( ) ;
110+ } }
111+ className = "absolute right-4 top-1/2 transform -translate-y-1/2 z-60 p-3 bg-black bg-opacity-50 text-white rounded-full hover:bg-opacity-70 transition-colors"
112+ >
113+ < ChevronRight className = "h-6 w-6" />
114+ </ button >
115+ ) }
116+
117+ { /* Image */ }
118+ < motion . div
119+ initial = { { scale : 0.8 } }
120+ animate = { { scale : 1 } }
121+ exit = { { scale : 0.8 } }
122+ transition = { { duration : 0.3 } }
123+ className = "max-w-4xl max-h-[90vh] mx-4"
124+ onClick = { ( e ) => e . stopPropagation ( ) }
125+ >
126+ < img
127+ src = { images [ currentIndex ] }
128+ alt = { `${ alt } - Image ${ currentIndex + 1 } ` }
129+ className = "w-full h-full object-contain rounded-lg"
130+ onError = { ( e ) => {
131+ const target = e . target as HTMLImageElement ;
132+ target . src = '/images/fallback-image.jpg' ; // You can add a fallback image
133+ } }
134+ />
135+ </ motion . div >
136+
137+ { /* Thumbnail navigation for multiple images */ }
138+ { images . length > 1 && (
139+ < div className = "absolute bottom-4 left-1/2 transform -translate-x-1/2 z-60" >
140+ < div className = "flex space-x-2 bg-black bg-opacity-50 rounded-lg p-2" >
141+ { images . map ( ( image , index ) => (
142+ < button
143+ key = { index }
144+ onClick = { ( e ) => {
145+ e . stopPropagation ( ) ;
146+ onGoToIndex ( index ) ;
147+ } }
148+ className = { `w-12 h-12 rounded overflow-hidden transition-opacity ${
149+ index === currentIndex ? 'opacity-100 ring-2 ring-white' : 'opacity-60 hover:opacity-80'
150+ } `}
151+ >
152+ < img
153+ src = { image }
154+ alt = { `Thumbnail ${ index + 1 } ` }
155+ className = "w-full h-full object-cover"
156+ />
157+ </ button >
158+ ) ) }
159+ </ div >
160+ </ div >
161+ ) }
162+ </ motion . div >
163+ </ AnimatePresence >
164+ ) ;
165+ } ;
166+
167+ export default ImageModal ;
0 commit comments