re-init
This commit is contained in:
143
src/components/ImageLightbox.tsx
Normal file
143
src/components/ImageLightbox.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
|
||||
interface ImageLightboxProps {
|
||||
images: { src: string; alt: string }[];
|
||||
initialIndex: number;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function ImageLightbox({ images, initialIndex, onClose }: ImageLightboxProps) {
|
||||
const [currentIndex, setCurrentIndex] = useState(
|
||||
Math.max(0, Math.min(initialIndex, images.length - 1)),
|
||||
);
|
||||
|
||||
const goTo = useCallback(
|
||||
(i: number) => {
|
||||
setCurrentIndex(Math.max(0, Math.min(i, images.length - 1)));
|
||||
},
|
||||
[images.length],
|
||||
);
|
||||
|
||||
// Close on Escape key, navigate with arrows
|
||||
useEffect(() => {
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
if (e.key === "ArrowLeft") goTo(currentIndex - 1);
|
||||
if (e.key === "ArrowRight") goTo(currentIndex + 1);
|
||||
};
|
||||
window.addEventListener("keydown", handleKey);
|
||||
return () => window.removeEventListener("keydown", handleKey);
|
||||
}, [onClose, currentIndex, goTo]);
|
||||
|
||||
// Prevent body scroll while open
|
||||
useEffect(() => {
|
||||
document.body.style.overflow = "hidden";
|
||||
return () => {
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!images.length) return null;
|
||||
|
||||
const current = images[currentIndex];
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Image viewer"
|
||||
>
|
||||
{/* Faded backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Close button — top right */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 z-10 rounded-full p-2 text-white/70 hover:text-white transition-colors"
|
||||
aria-label="Close image"
|
||||
>
|
||||
<svg
|
||||
className="h-8 w-8"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Navigation — previous */}
|
||||
{images.length > 1 && currentIndex > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => goTo(currentIndex - 1)}
|
||||
className="absolute left-4 z-10 rounded-full p-2 text-white/70 hover:text-white transition-colors"
|
||||
aria-label="Previous image"
|
||||
>
|
||||
<svg
|
||||
className="h-8 w-8"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Navigation — next */}
|
||||
{images.length > 1 && currentIndex < images.length - 1 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => goTo(currentIndex + 1)}
|
||||
className="absolute right-4 z-10 rounded-full p-2 text-white/70 hover:text-white transition-colors"
|
||||
aria-label="Next image"
|
||||
>
|
||||
<svg
|
||||
className="h-8 w-8"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="9 18 15 12 9 6" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Full image */}
|
||||
<div className="relative z-0 max-w-[90vw] max-h-[85vh] flex flex-col items-center">
|
||||
<img
|
||||
src={current.src}
|
||||
alt={current.alt}
|
||||
className="max-w-full max-h-[80vh] object-contain rounded-lg shadow-2xl"
|
||||
/>
|
||||
<p className="mt-3 text-sm text-white/70 text-center max-w-lg">{current.alt}</p>
|
||||
|
||||
{/* Image counter */}
|
||||
{images.length > 1 && (
|
||||
<p className="mt-1 text-xs text-white/50">
|
||||
{currentIndex + 1} / {images.length}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user