Files
lambada-fiesta-live/src/components/Lightbox.tsx
Antoni Nuñez Romeu a9d5bc10a1
All checks were successful
Deploy NPM app / Deploy NPM (push) Successful in 1m22s
New pictures & gallery lightbox
2026-05-18 15:09:32 +02:00

149 lines
4.7 KiB
TypeScript

import React, { useEffect, useState, useRef } from "react";
import { ImageIcon, X, ChevronLeft, ChevronRight, Play, Pause, ZoomIn, ZoomOut } from "lucide-react";
type ImageItem = { src?: string; alt?: string };
type LightboxProps = {
images: ImageItem[];
initialIndex?: number;
onClose?: () => void;
};
const Lightbox: React.FC<LightboxProps> = ({ images, initialIndex = 0, onClose }) => {
const [index, setIndex] = useState(initialIndex);
const [scale, setScale] = useState(1);
const [playing, setPlaying] = useState(false);
const timerRef = useRef<number | null>(null);
useEffect(() => {
setIndex(initialIndex);
}, [initialIndex]);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") handleClose();
if (e.key === "ArrowRight") next();
if (e.key === "ArrowLeft") prev();
if (e.key === "+") zoomIn();
if (e.key === "-") zoomOut();
if (e.key === " ") setPlaying((p) => !p);
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [index]);
useEffect(() => {
if (playing) {
timerRef.current = window.setInterval(() => setIndex((i) => (i + 1) % images.length), 3500);
} else if (timerRef.current) {
window.clearInterval(timerRef.current);
timerRef.current = null;
}
return () => {
if (timerRef.current) window.clearInterval(timerRef.current);
};
}, [playing, images.length]);
const prev = () => setIndex((i) => (i - 1 + images.length) % images.length);
const next = () => setIndex((i) => (i + 1) % images.length);
const handleClose = () => {
setPlaying(false);
setScale(1);
onClose?.();
};
const zoomIn = () => setScale((s) => Math.min(4, +(s + 0.25).toFixed(2)));
const zoomOut = () => setScale((s) => Math.max(0.5, +(s - 0.25).toFixed(2)));
if (!images || images.length === 0) return null;
const img = images[index];
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
<div className="absolute top-4 right-4">
<button
aria-label="Close"
onClick={handleClose}
className="p-2 rounded-full bg-white/10 hover:bg-white/20 text-white"
>
<X className="w-6 h-6" />
</button>
</div>
<div className="absolute left-4 inset-y-0 flex items-center">
<button
onClick={prev}
className="p-2 rounded-full bg-white/10 hover:bg-white/20 text-white"
aria-label="Previous"
>
<ChevronLeft className="w-8 h-8" />
</button>
</div>
<div className="absolute right-4 inset-y-0 flex items-center">
<button
onClick={next}
className="p-2 rounded-full bg-white/10 hover:bg-white/20 text-white"
aria-label="Next"
>
<ChevronRight className="w-8 h-8" />
</button>
</div>
<div className="max-w-[95%] max-h-[85%] flex items-center justify-center">
{img?.src ? (
<img
src={img.src}
alt={img.alt || "image"}
style={{ transform: `scale(${scale})` }}
className="max-w-full max-h-full object-contain transition-transform duration-200"
/>
) : (
<div className="text-white/70 flex flex-col items-center">
<ImageIcon className="w-20 h-20 mb-2" />
<div>{img?.alt}</div>
</div>
)}
</div>
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex items-center gap-3">
<button
onClick={zoomOut}
className="p-2 rounded-md bg-white/10 hover:bg-white/20 text-white flex items-center gap-2"
aria-label="Zoom out"
>
<ZoomOut className="w-5 h-5" />
</button>
<button
onClick={() => setScale(1)}
className="p-2 rounded-md bg-white/10 hover:bg-white/20 text-white"
aria-label="Reset zoom"
>
100%
</button>
<button
onClick={zoomIn}
className="p-2 rounded-md bg-white/10 hover:bg-white/20 text-white flex items-center gap-2"
aria-label="Zoom in"
>
<ZoomIn className="w-5 h-5" />
</button>
<button
onClick={() => setPlaying((p) => !p)}
className="p-2 rounded-md bg-white/10 hover:bg-white/20 text-white flex items-center gap-2"
aria-label="Play slideshow"
>
{playing ? <Pause className="w-5 h-5" /> : <Play className="w-5 h-5" />}
</button>
</div>
<div className="absolute bottom-4 right-4 text-white/80 text-sm">
{index + 1} / {images.length}
</div>
</div>
);
};
export default Lightbox;