@@ -4,82 +4,89 @@ import Image from "next/image";
44
55export default function Gallery ( { images } ) {
66 const [ activeImage , setActiveImage ] = useState ( null ) ;
7- const [ imageDimensions , setImageDimensions ] = useState ( {
8- width : 0 ,
9- height : 0 ,
10- } ) ;
11- const imageRef = useRef ( null ) ;
7+ const modalRef = useRef ( null ) ;
128
13- const openModal = ( image ) => {
14- setActiveImage ( image ) ;
15- document . body . style . overflow = "hidden" ;
16- } ;
9+ // Navegación por teclado
10+ const handleKeyNavigation = useCallback (
11+ ( e ) => {
12+ if ( ! activeImage ) return ;
13+ const currentIndex = images . findIndex (
14+ ( img ) => img . src === activeImage . src
15+ ) ;
1716
18- const closeModal = ( ) => {
19- setActiveImage ( null ) ;
20- document . body . style . overflow = "auto" ;
21- } ;
22-
23- // Esta función calculará las dimensiones reales de la imagen renderizada
24- const updateImageDimensions = useCallback ( ( ) => {
25- if ( imageRef . current && activeImage ) {
26- const img = imageRef . current ;
27- const rect = img . getBoundingClientRect ( ) ;
28- setImageDimensions ( {
29- width : rect . width ,
30- height : rect . height ,
31- top : rect . top ,
32- left : rect . left ,
33- } ) ;
34- }
35- } , [ activeImage ] ) ;
17+ if ( e . key === "ArrowLeft" && currentIndex > 0 ) {
18+ setActiveImage ( images [ currentIndex - 1 ] ) ;
19+ }
20+ if ( e . key === "ArrowRight" && currentIndex < images . length - 1 ) {
21+ setActiveImage ( images [ currentIndex + 1 ] ) ;
22+ }
23+ if ( e . key === "Escape" ) {
24+ setActiveImage ( null ) ;
25+ document . body . style . overflow = "auto" ;
26+ }
27+ } ,
28+ [ activeImage , images ]
29+ ) ;
3630
37- // Actualizar dimensiones cuando cambia la imagen activa o en resize
3831 useEffect ( ( ) => {
39- if ( activeImage ) {
40- const timer = setTimeout ( updateImageDimensions , 100 ) ;
41- window . addEventListener ( "resize" , updateImageDimensions ) ;
32+ window . addEventListener ( "keydown" , handleKeyNavigation ) ;
33+ return ( ) => window . removeEventListener ( "keydown" , handleKeyNavigation ) ;
34+ } , [ handleKeyNavigation ] ) ;
4235
43- return ( ) => {
44- clearTimeout ( timer ) ;
45- window . removeEventListener ( "resize" , updateImageDimensions ) ;
46- } ;
47- }
48- } , [ activeImage , updateImageDimensions ] ) ;
36+ // Cargar placeholder dinámico
37+ const getBlurData = ( width , height ) =>
38+ `data:image/svg+xml;base64,${ btoa (
39+ `<svg width="${ width } " height="${ height } " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${ width } ${ height } " fill="#3D8B3720"/>`
40+ ) } `;
4941
5042 return (
5143 < div >
5244 < div className = "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" >
53- { images . map ( ( image , index ) => (
54- < div
55- key = { index }
56- className = "aspect-square relative rounded-lg overflow-hidden cursor-pointer group"
57- onClick = { ( ) => openModal ( image ) }
58- >
59- < Image
60- src = { image . src }
61- alt = { image . alt || "Imagen de PyDay Chile" }
62- fill
63- className = "object-cover transition-transform duration-300 group-hover:scale-110"
64- />
65- < div className = "absolute inset-0 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end p-4" >
66- < span className = "text-white text-sm font-medium" >
67- { image . caption || `PyDay ${ image . year || "2024" } ` }
68- </ span >
45+ { images . map ( ( image , index ) => {
46+ const isPriority = index < 6 ;
47+ return (
48+ < div
49+ key = { image . src }
50+ className = "aspect-square relative rounded-lg overflow-hidden cursor-pointer group"
51+ onClick = { ( ) => {
52+ setActiveImage ( image ) ;
53+ document . body . style . overflow = "hidden" ;
54+ } }
55+ >
56+ < Image
57+ src = { image . src }
58+ alt = { image . alt || `Imagen ${ index + 1 } de PyDay Chile` }
59+ fill
60+ sizes = "(max-width: 768px) 50vw, 33vw"
61+ className = "object-cover transition-transform duration-300 group-hover:scale-105"
62+ loading = { isPriority ? "eager" : "lazy" }
63+ priority = { isPriority }
64+ placeholder = "blur"
65+ blurDataURL = { getBlurData ( 1600 , 900 ) }
66+ />
67+ < div className = "absolute inset-0 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end p-3" >
68+ < span className = "text-white text-sm font-medium line-clamp-2" >
69+ { image . caption }
70+ </ span >
71+ </ div >
6972 </ div >
70- </ div >
71- ) ) }
73+ ) ;
74+ } ) }
7275 </ div >
7376
74- { /* Modal */ }
7577 { activeImage && (
7678 < div
79+ ref = { modalRef }
7780 className = "fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4"
78- onClick = { closeModal }
81+ onClick = { ( e ) => e . target === modalRef . current && setActiveImage ( null ) }
7982 >
8083 < button
81- className = "absolute top-4 right-4 text-white p-2 hover:bg-white/10 rounded-full transition z-50"
82- onClick = { closeModal }
84+ className = "absolute top-4 right-4 text-white p-2 hover:bg-white/10 rounded-full z-50"
85+ onClick = { ( ) => {
86+ setActiveImage ( null ) ;
87+ document . body . style . overflow = "auto" ;
88+ } }
89+ aria-label = "Cerrar modal"
8390 >
8491 < svg
8592 xmlns = "http://www.w3.org/2000/svg"
@@ -97,47 +104,26 @@ export default function Gallery({ images }) {
97104 </ svg >
98105 </ button >
99106
100- < div className = "flex flex-col items-center justify-center relative max-w-3xl w-full" >
101- { /* Contenedor de la imagen */ }
102- < div className = "relative flex items-center justify-center w-full h-[60vh] md:h-[70vh] lg:h-[80vh]" >
103- < div className = "relative h-full flex items-center justify-center" >
104- < Image
105- ref = { imageRef }
106- src = { activeImage . src }
107- alt = { activeImage . alt || "Imagen de PyDay Chile" }
108- width = { 1200 }
109- height = { 900 }
110- style = { {
111- maxHeight : "100%" ,
112- width : "auto" ,
113- maxWidth : "100%" ,
114- objectFit : "contain" ,
115- } }
116- onLoad = { updateImageDimensions }
117- priority
118- />
107+ < div className = "relative max-w-5xl w-full" >
108+ < Image
109+ src = { activeImage . src }
110+ alt = { activeImage . alt }
111+ width = { 1600 }
112+ height = { 900 }
113+ className = "max-h-[90vh] w-auto mx-auto object-contain"
114+ priority
115+ quality = { 90 }
116+ />
119117
120- { /* Caption*/ }
121- { activeImage . caption && imageDimensions . width > 0 && (
122- < div
123- className = "absolute bg-black/60 p-3 md:p-4 text-center"
124- style = { {
125- bottom : 0 ,
126- width : imageDimensions . width ,
127- maxWidth : "100%" ,
128- } }
129- >
130- < p className = "text-white text-sm md:text-base" >
131- { activeImage . caption }
132- </ p >
133- { activeImage . location && (
134- < p className = "text-white text-xs md:text-sm opacity-70 mt-1" >
135- { activeImage . location }
136- </ p >
137- ) }
138- </ div >
139- ) }
140- </ div >
118+ < div className = "absolute bottom-0 left-0 right-0 bg-black/70 p-4 text-center backdrop-blur-md" >
119+ < p className = "text-white text-lg font-medium" >
120+ { activeImage . caption }
121+ </ p >
122+ { activeImage . location && (
123+ < p className = "text-gray-300 text-sm mt-1" >
124+ { activeImage . location }
125+ </ p >
126+ ) }
141127 </ div >
142128 </ div >
143129 </ div >
0 commit comments