deafmissoula-website/client/src/components/Gallery.tsx

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;