Repository Public
1// -----------------------------------------------------------------------------2// Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// Lightbox.tsx : visionneuse photos plein écran — clavier, gestes, compteur5// -----------------------------------------------------------------------------6import { useCallback, useEffect, useRef } from "react";78interface Props {9 images: string[];10 index: number;11 onClose: () => void;12 onIndex: (i: number) => void;13 alt: string;14}1516export default function Lightbox({ images, index, onClose, onIndex, alt }: Props) {17 const touchX = useRef<number | null>(null);1819 const prev = useCallback(20 () => onIndex((index - 1 + images.length) % images.length),21 [index, images.length, onIndex]22 );23 const next = useCallback(24 () => onIndex((index + 1) % images.length),25 [index, images.length, onIndex]26 );2728 useEffect(() => {29 const onKey = (e: KeyboardEvent) => {30 if (e.key === "Escape") onClose();31 else if (e.key === "ArrowLeft") prev();32 else if (e.key === "ArrowRight") next();33 };34 window.addEventListener("keydown", onKey);35 document.body.style.overflow = "hidden";36 return () => {37 window.removeEventListener("keydown", onKey);38 document.body.style.overflow = "";39 };40 }, [onClose, prev, next]);4142 // précharger les voisines pour une navigation instantanée43 useEffect(() => {44 [index - 1, index + 1].forEach((i) => {45 const u = images[(i + images.length) % images.length];46 if (u) new Image().src = u;47 });48 }, [index, images]);4950 return (51 <div52 className="lightbox"53 role="dialog"54 aria-label={`Photos — ${alt}`}55 onClick={onClose}56 onTouchStart={(e) => { touchX.current = e.touches[0].clientX; }}57 onTouchEnd={(e) => {58 if (touchX.current == null) return;59 const dx = e.changedTouches[0].clientX - touchX.current;60 touchX.current = null;61 if (dx > 48) prev();62 else if (dx < -48) next();63 }}64 >65 <button className="lb-close" aria-label="Fermer" onClick={onClose}>✕</button>66 <div className="lb-count mono">{index + 1} / {images.length}</div>67 {images.length > 1 && (68 <>69 <button70 className="lb-nav lb-prev"71 aria-label="Photo précédente"72 onClick={(e) => { e.stopPropagation(); prev(); }}73 >←</button>74 <button75 className="lb-nav lb-next"76 aria-label="Photo suivante"77 onClick={(e) => { e.stopPropagation(); next(); }}78 >→</button>79 </>80 )}81 <img82 src={images[index]}83 alt={`${alt} — photo ${index + 1}`}84 onClick={(e) => e.stopPropagation()}85 />86 </div>87 );88}89