199 lines
8.7 KiB
TypeScript
199 lines
8.7 KiB
TypeScript
import { useState, useRef, useEffect } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import { FaChevronLeft, FaChevronRight, FaExpand, FaTimes, FaArrowLeft } from 'react-icons/fa';
|
|
|
|
interface Photo {
|
|
filePath: string;
|
|
}
|
|
|
|
interface PremiumGalleryProps {
|
|
photos: Photo[];
|
|
isAdActive: boolean;
|
|
onFavoriteToggle: () => void;
|
|
isFavorite: boolean;
|
|
statusBadge: React.ReactNode;
|
|
featuredBadge: React.ReactNode;
|
|
locationBadge: React.ReactNode;
|
|
}
|
|
|
|
export default function PremiumGallery({
|
|
photos,
|
|
isAdActive,
|
|
onFavoriteToggle,
|
|
isFavorite,
|
|
statusBadge,
|
|
featuredBadge,
|
|
locationBadge
|
|
}: PremiumGalleryProps) {
|
|
const [activePhoto, setActivePhoto] = useState(0);
|
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const touchStartX = useRef<number | null>(null);
|
|
|
|
const getImageUrl = (path: string) => {
|
|
if (!path) return "/placeholder-car.png";
|
|
return path.startsWith('http') ? path : `${import.meta.env.VITE_STATIC_BASE_URL}${path}`;
|
|
};
|
|
|
|
const handleTouchStart = (e: React.TouchEvent) => {
|
|
touchStartX.current = e.touches[0].clientX;
|
|
};
|
|
|
|
const handleTouchEnd = (e: React.TouchEvent) => {
|
|
if (touchStartX.current === null) return;
|
|
const touchEndX = e.changedTouches[0].clientX;
|
|
const diff = touchStartX.current - touchEndX;
|
|
|
|
if (Math.abs(diff) > 50) {
|
|
if (diff > 0) nextPhoto();
|
|
else prevPhoto();
|
|
}
|
|
touchStartX.current = null;
|
|
};
|
|
|
|
const nextPhoto = () => setActivePhoto((prev) => (prev + 1) % photos.length);
|
|
const prevPhoto = () => setActivePhoto((prev) => (prev - 1 + photos.length) % photos.length);
|
|
|
|
useEffect(() => {
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (isFullscreen) {
|
|
if (e.key === 'ArrowRight') nextPhoto();
|
|
if (e.key === 'ArrowLeft') prevPhoto();
|
|
if (e.key === 'Escape') setIsFullscreen(false);
|
|
}
|
|
};
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
}, [isFullscreen]);
|
|
|
|
if (!photos || photos.length === 0) return null;
|
|
|
|
return (
|
|
<div className="space-y-4 select-none">
|
|
{/* CONTENEDOR PRINCIPAL */}
|
|
<div
|
|
ref={containerRef}
|
|
className="glass rounded-2xl md:rounded-[3rem] overflow-hidden border border-white/5 relative h-[350px] md:h-[550px] shadow-2xl group bg-black/40 cursor-zoom-in"
|
|
onTouchStart={handleTouchStart}
|
|
onTouchEnd={handleTouchEnd}
|
|
onClick={() => setIsFullscreen(true)}
|
|
>
|
|
<div className="relative w-full h-full overflow-hidden flex items-center justify-center">
|
|
<img
|
|
src={getImageUrl(photos[activePhoto].filePath)}
|
|
className={`max-h-full max-w-full object-contain transition-transform duration-500 ${!isAdActive ? 'grayscale' : ''}`}
|
|
alt="Vehículo"
|
|
/>
|
|
</div>
|
|
|
|
{/* HUD DE INFO */}
|
|
<div className="absolute top-4 md:top-8 left-4 md:left-8 flex flex-col items-start gap-2 z-10">
|
|
{statusBadge}
|
|
{featuredBadge}
|
|
{locationBadge}
|
|
</div>
|
|
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); onFavoriteToggle(); }}
|
|
className={`absolute top-4 md:top-8 right-4 md:right-8 w-12 h-12 md:w-14 md:h-14 rounded-xl md:rounded-2xl flex items-center justify-center text-xl md:text-2xl transition-all shadow-xl backdrop-blur-xl border z-10 ${isFavorite ? 'bg-red-600 border-red-500/50 text-white' : 'bg-black/40 border-white/10 text-white hover:bg-white hover:text-red-500'}`}
|
|
>
|
|
{isFavorite ? '❤️' : '🤍'}
|
|
</button>
|
|
|
|
{photos.length > 1 && (
|
|
<>
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); prevPhoto(); }}
|
|
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 md:w-12 h-10 md:h-12 bg-black/40 hover:bg-white hover:text-black backdrop-blur-md rounded-full border border-white/10 flex items-center justify-center transition-all opacity-0 group-hover:opacity-100 translate-x-[-10px] group-hover:translate-x-0 z-10"
|
|
>
|
|
<FaChevronLeft />
|
|
</button>
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); nextPhoto(); }}
|
|
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 md:w-12 h-10 md:h-12 bg-black/40 hover:bg-white hover:text-black backdrop-blur-md rounded-full border border-white/10 flex items-center justify-center transition-all opacity-0 group-hover:opacity-100 translate-x-[10px] group-hover:translate-x-0 z-10"
|
|
>
|
|
<FaChevronRight />
|
|
</button>
|
|
</>
|
|
)}
|
|
|
|
<div className="absolute bottom-6 right-6 opacity-0 group-hover:opacity-100 transition-opacity z-10 hidden md:block">
|
|
<div className="bg-black/60 backdrop-blur-md p-3 rounded-xl border border-white/10 text-white/70">
|
|
<FaExpand size={18} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 bg-black/40 backdrop-blur-md px-4 py-1.5 rounded-full border border-white/10 text-[10px] font-black tracking-[0.2em] text-white/80 z-10">
|
|
{activePhoto + 1} / {photos.length}
|
|
</div>
|
|
</div>
|
|
|
|
{/* MINIATURAS (THUMBNAILS) */}
|
|
{photos.length > 1 && (
|
|
<div className="flex gap-3 md:gap-4 overflow-x-auto pt-4 pb-4 px-1 scrollbar-hide no-scrollbar items-center justify-center">
|
|
{photos.map((p, idx) => (
|
|
<button
|
|
key={idx}
|
|
onClick={() => setActivePhoto(idx)}
|
|
className={`relative w-24 md:w-32 h-16 md:h-20 rounded-xl md:rounded-2xl overflow-hidden flex-shrink-0 border-2 transition-all duration-300 ${activePhoto === idx ? 'border-blue-500 scale-105 shadow-lg ring-4 ring-blue-500/20' : 'border-white/5 opacity-40 hover:opacity-100 hover:border-white/20'}`}
|
|
>
|
|
<img src={getImageUrl(p.filePath)} className="w-full h-full object-cover" alt="" />
|
|
{activePhoto === idx && <div className="absolute inset-0 bg-blue-500/10 pointer-events-none" />}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* LIGHTBOX CON PORTAL */}
|
|
{isFullscreen && createPortal(
|
|
<div className="fixed inset-0 z-[99999] bg-black/98 backdrop-blur-3xl flex flex-col items-center justify-center animate-fade-in" onClick={() => setIsFullscreen(false)}>
|
|
|
|
{/* Botón Cerrar Desktop (X Minimalista) */}
|
|
<button
|
|
className="absolute top-8 right-8 text-white/40 hover:text-white transition-all hidden md:flex items-center gap-2 group z-[100000]"
|
|
onClick={() => setIsFullscreen(false)}
|
|
>
|
|
<span className="text-[10px] font-black tracking-widest opacity-0 group-hover:opacity-100 transition-opacity">CERRAR</span>
|
|
<FaTimes size={24} />
|
|
</button>
|
|
|
|
<div
|
|
className="relative w-full h-full flex flex-col items-center justify-center p-4 md:p-12"
|
|
onClick={(e) => e.stopPropagation()}
|
|
onTouchStart={handleTouchStart}
|
|
onTouchEnd={handleTouchEnd}
|
|
>
|
|
<img
|
|
src={getImageUrl(photos[activePhoto].filePath)}
|
|
className="max-h-[80vh] max-w-full object-contain animate-scale-up shadow-2xl"
|
|
alt="Vista amplia"
|
|
/>
|
|
|
|
{photos.length > 1 && (
|
|
<>
|
|
<button onClick={(e) => { e.stopPropagation(); prevPhoto(); }} className="absolute left-4 md:left-12 top-1/2 -translate-y-1/2 w-16 h-16 bg-white/5 hover:bg-white hover:text-black rounded-full border border-white/5 flex items-center justify-center transition-all text-2xl hidden md:flex"><FaChevronLeft /></button>
|
|
<button onClick={(e) => { e.stopPropagation(); nextPhoto(); }} className="absolute right-4 md:right-12 top-1/2 -translate-y-1/2 w-16 h-16 bg-white/5 hover:bg-white hover:text-black rounded-full border border-white/5 flex items-center justify-center transition-all text-2xl hidden md:flex"><FaChevronRight /></button>
|
|
</>
|
|
)}
|
|
|
|
{/* Info inferior (Contador) */}
|
|
<div className="mt-8 text-white/30 font-black tracking-[0.4em] text-[10px] uppercase">
|
|
Foto {activePhoto + 1} de {photos.length}
|
|
</div>
|
|
|
|
{/* BOTÓN CERRAR MÓVIL (Píldora ergonómica) */}
|
|
<button
|
|
className="mt-12 md:hidden bg-white/10 border border-white/20 text-white px-8 py-4 rounded-full font-black text-xs tracking-widest flex items-center gap-2 hover:bg-white/20 active:scale-95 transition-all shadow-xl"
|
|
onClick={() => setIsFullscreen(false)}
|
|
>
|
|
<FaArrowLeft size={10} /> VOLVER AL AVISO
|
|
</button>
|
|
</div>
|
|
</div>,
|
|
document.body
|
|
)}
|
|
</div>
|
|
);
|
|
}
|