166 lines
4.6 KiB
TypeScript
166 lines
4.6 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
|
|
interface GalleryItem {
|
|
id: number;
|
|
imageUrl: string;
|
|
alt: string;
|
|
}
|
|
|
|
// Gallery photos from community events
|
|
const galleryData: GalleryItem[] = [
|
|
{
|
|
id: 1,
|
|
imageUrl: '/photos/0001.jpg',
|
|
alt: 'MCDi community gathering',
|
|
},
|
|
{
|
|
id: 2,
|
|
imageUrl: '/photos/0002.jpg',
|
|
alt: 'MCDi community event photo',
|
|
},
|
|
{
|
|
id: 3,
|
|
imageUrl: '/photos/0003.jpg',
|
|
alt: 'MCDi community event photo',
|
|
},
|
|
{
|
|
id: 4,
|
|
imageUrl: '/photos/0004.jpg',
|
|
alt: 'MCDi community event photo',
|
|
},
|
|
{
|
|
id: 5,
|
|
imageUrl: '/photos/0005.jpg',
|
|
alt: 'MCDi community event photo',
|
|
},
|
|
{
|
|
id: 6,
|
|
imageUrl: '/photos/0006.jpg',
|
|
alt: 'MCDi community event photo',
|
|
},
|
|
{
|
|
id: 7,
|
|
imageUrl: '/photos/0007.jpg',
|
|
alt: 'MCDi community event photo',
|
|
},
|
|
{
|
|
id: 8,
|
|
imageUrl: '/photos/0008.jpg',
|
|
alt: 'MCDi community event photo',
|
|
},
|
|
{
|
|
id: 9,
|
|
imageUrl: '/photos/0009.jpg',
|
|
alt: 'MCDi community event photo',
|
|
},
|
|
{
|
|
id: 10,
|
|
imageUrl: '/photos/0010.jpg',
|
|
alt: 'MCDi community event photo',
|
|
},
|
|
];
|
|
|
|
const Gallery: React.FC = () => {
|
|
const [selectedImage, setSelectedImage] = useState<GalleryItem | null>(null);
|
|
|
|
const openImage = (item: GalleryItem) => {
|
|
setSelectedImage(item);
|
|
};
|
|
|
|
const closeImage = () => {
|
|
setSelectedImage(null);
|
|
};
|
|
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
>
|
|
<h1 className="text-4xl font-bold text-[#8C1D40] mb-8">Photo Gallery</h1>
|
|
|
|
{/* Photo Grid */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
|
{galleryData.map((item) => (
|
|
<motion.div
|
|
key={item.id}
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ duration: 0.3 }}
|
|
whileHover={{ scale: 1.05 }}
|
|
className="cursor-pointer"
|
|
onClick={() => openImage(item)}
|
|
>
|
|
<div className="bg-white rounded-lg shadow-lg overflow-hidden hover:shadow-xl transition-shadow duration-300">
|
|
<div className="aspect-square overflow-hidden">
|
|
<img
|
|
src={item.imageUrl}
|
|
alt={item.alt}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Image Modal */}
|
|
<AnimatePresence>
|
|
{selectedImage && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4"
|
|
onClick={closeImage}
|
|
>
|
|
<motion.div
|
|
initial={{ scale: 0.8, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
exit={{ scale: 0.8, opacity: 0 }}
|
|
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
|
|
className="relative max-w-5xl max-h-[90vh] flex flex-col"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{/* Close Button */}
|
|
<button
|
|
onClick={closeImage}
|
|
className="absolute -top-12 right-0 text-white hover:text-yellow-400 transition-colors duration-200 z-10"
|
|
aria-label="Close image"
|
|
>
|
|
<svg
|
|
className="w-10 h-10"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
|
|
{/* Image Container */}
|
|
<div className="bg-white rounded-lg overflow-hidden shadow-2xl">
|
|
<img
|
|
src={selectedImage.imageUrl}
|
|
alt={selectedImage.alt}
|
|
className="max-w-full max-h-[80vh] w-auto h-auto mx-auto object-contain"
|
|
/>
|
|
</div>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Gallery;
|