skyflytravel.hu/components/gallery-dialog.tsx

177 lines
6.4 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import Image from 'next/image';
import { motion, AnimatePresence } from 'framer-motion';
import { X, ChevronLeft, ChevronRight, Maximize2, ImageIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
interface GalleryDialogProps {
isOpen: boolean;
onClose: () => void;
images: string[];
vehicleName: string;
categoryName: string;
}
export function GalleryDialog({ isOpen, onClose, images, vehicleName, categoryName }: GalleryDialogProps) {
const [currentIndex, setCurrentIndex] = useState(0);
const [direction, setDirection] = useState(0);
// Lock scroll when open
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isOpen]);
if (!isOpen) return null;
const nextImage = () => {
setDirection(1);
setCurrentIndex((prev) => (prev + 1) % images.length);
};
const prevImage = () => {
setDirection(-1);
setCurrentIndex((prev) => (prev - 1 + images.length) % images.length);
};
const variants = {
enter: (direction: number) => ({
x: direction > 0 ? 1000 : -1000,
opacity: 0,
scale: 0.9,
}),
center: {
zIndex: 1,
x: 0,
opacity: 1,
scale: 1,
},
exit: (direction: number) => ({
zIndex: 0,
x: direction < 0 ? 1000 : -1000,
opacity: 0,
scale: 0.9,
}),
};
return (
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/95 backdrop-blur-xl"
>
{/* Close Button */}
<button
onClick={onClose}
className="absolute top-8 right-8 z-[110] p-4 text-white/50 hover:text-white hover:bg-white/10 rounded-full transition-all"
>
<X className="w-8 h-8" />
</button>
{/* Header Info */}
<div className="absolute top-8 left-8 z-[110] flex flex-col gap-2 pr-20">
<h3 className="text-lg lg:text-2xl font-black uppercase tracking-[0.15em] lg:tracking-[0.3em] text-white leading-tight">
<span className="lg:hidden block">{categoryName}</span>
<span className="hidden lg:block">{vehicleName}</span>
</h3>
<div className="flex items-center gap-4 text-primary text-[10px] font-black tracking-widest uppercase">
<ImageIcon className="w-4 h-4" />
<span>{currentIndex + 1} / {images.length} Kép</span>
</div>
</div>
{/* Main Stage */}
<div className="relative w-full h-full max-w-7xl px-4 md:px-12 flex items-center justify-center overflow-hidden">
<AnimatePresence initial={false} custom={direction}>
{images.length > 0 && (
<motion.div
key={currentIndex}
custom={direction}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{
x: { type: "spring", stiffness: 300, damping: 30 },
opacity: { duration: 0.3 },
scale: { duration: 0.4 }
}}
className="absolute inset-0 flex items-center justify-center p-4 md:p-12"
>
<div className="relative w-full h-full shadow-2xl shadow-primary/10 rounded-[2rem] overflow-hidden border border-white/10">
<Image
src={images[currentIndex]}
alt={`${vehicleName} view ${currentIndex + 1}`}
fill
className="object-cover"
priority
quality={100}
/>
{/* Vignette Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none" />
</div>
</motion.div>
)}
</AnimatePresence>
{/* Navigation Handles */}
<button
onClick={prevImage}
className="absolute left-8 z-[110] w-16 h-16 rounded-full border border-white/10 bg-black/20 backdrop-blur-md flex items-center justify-center text-white hover:bg-primary hover:border-primary transition-all group active:scale-95"
>
<ChevronLeft className="w-8 h-8 group-hover:-translate-x-1 transition-transform" />
</button>
<button
onClick={nextImage}
className="absolute right-8 z-[110] w-16 h-16 rounded-full border border-white/10 bg-black/20 backdrop-blur-md flex items-center justify-center text-white hover:bg-primary hover:border-primary transition-all group active:scale-95"
>
<ChevronRight className="w-8 h-8 group-hover:translate-x-1 transition-transform" />
</button>
</div>
{/* Thumbnails Sidebar/Bottom Bar */}
<div className="absolute bottom-8 left-0 right-0 z-[110] flex justify-center gap-4 px-8 overflow-x-auto pb-4 scrollbar-hide">
{images.map((img, idx) => (
<button
key={idx}
onClick={() => {
setDirection(idx > currentIndex ? 1 : -1);
setCurrentIndex(idx);
}}
className={cn(
"relative w-32 h-20 rounded-xl overflow-hidden border-2 transition-all flex-shrink-0 active:scale-95",
currentIndex === idx
? "border-primary scale-110 shadow-lg shadow-primary/20"
: "border-white/10 opacity-40 hover:opacity-100"
)}
>
<Image
src={img}
alt="Thumbnail"
fill
className="object-cover"
/>
</button>
))}
</div>
{/* Subtle noise/texture overlay for premium feel */}
<div className="fixed inset-0 pointer-events-none opacity-[0.03] bg-[url('/images/noise.png')] mix-blend-overlay" />
</motion.div>
)}
</AnimatePresence>
);
}