Jade-Infra-test / src /components /ImageSlider.jsx
rushiljain's picture
Upload 29 files
691cdd0 verified
import React, { useState, useEffect, useRef } from 'react';
import ImageFlex from './ImageFlex.jsx';
export default function ImageSlider({ images, projectTitle }) {
const [currentIndex, setCurrentIndex] = useState(0);
const [isFullscreen, setIsFullscreen] = useState(false);
const intervalRef = useRef(null);
// Auto-advance every 4 seconds
useEffect(() => {
if (images.length <= 1) return;
if (intervalRef.current) clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % images.length);
}, 4000);
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, [images.length]);
// Pause on hover/focus
const pauseAutoPlay = () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
const resumeAutoPlay = () => {
if (images.length <= 1) return;
if (intervalRef.current) clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % images.length);
}, 4000);
};
const goToNext = () => {
pauseAutoPlay();
setCurrentIndex((prev) => (prev + 1) % images.length);
resumeAutoPlay();
};
const goToPrev = () => {
pauseAutoPlay();
setCurrentIndex((prev) => (prev - 1 + images.length) % images.length);
resumeAutoPlay();
};
const goToSlide = (index) => {
pauseAutoPlay();
setCurrentIndex(index);
resumeAutoPlay();
};
const openFullscreen = () => {
setIsFullscreen(true);
pauseAutoPlay();
};
const closeFullscreen = () => {
setIsFullscreen(false);
resumeAutoPlay();
};
// Keyboard navigation in fullscreen (auto-play stays paused in fullscreen)
useEffect(() => {
if (!isFullscreen) return;
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
setIsFullscreen(false);
resumeAutoPlay();
} else if (e.key === 'ArrowLeft') {
setCurrentIndex((prev) => (prev - 1 + images.length) % images.length);
} else if (e.key === 'ArrowRight') {
setCurrentIndex((prev) => (prev + 1) % images.length);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [isFullscreen, images.length]);
if (!images || images.length === 0) return null;
const currentImage = images[currentIndex];
return (
<>
<div
className="relative group overflow-hidden rounded-xl bg-slate-100"
onMouseEnter={pauseAutoPlay}
onMouseLeave={resumeAutoPlay}
>
<div className="aspect-video relative">
<ImageFlex
base={currentImage}
alt={`${projectTitle} - Image ${currentIndex + 1}`}
className="w-full h-full object-cover"
/>
{/* Click-to-fullscreen overlay */}
<button
type="button"
onClick={openFullscreen}
aria-label="View fullscreen"
className="absolute inset-0 cursor-zoom-in focus:outline-none"
/>
{/* Navigation arrows */}
{images.length > 1 && (
<>
<button
type="button"
onClick={goToPrev}
className="absolute left-4 top-1/2 -translate-y-1/2 bg-white/80 hover:bg-white rounded-full p-2
transition opacity-0 group-hover:opacity-100 focus:opacity-100"
aria-label="Previous image"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M15 18l-6-6 6-6" />
</svg>
</button>
<button
type="button"
onClick={goToNext}
className="absolute right-4 top-1/2 -translate-y-1/2 bg-white/80 hover:bg-white rounded-full p-2
transition opacity-0 group-hover:opacity-100 focus:opacity-100"
aria-label="Next image"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M9 18l6-6-6-6" />
</svg>
</button>
</>
)}
{/* Fullscreen button */}
<button
type="button"
onClick={openFullscreen}
className="absolute bottom-4 right-4 bg-white/80 hover:bg-white rounded-full p-2
transition opacity-0 group-hover:opacity-100 focus:opacity-100"
aria-label="View fullscreen"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3" />
</svg>
</button>
{/* Slide indicators */}
{images.length > 1 && (
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
{images.map((_, i) => (
<button
key={i}
type="button"
onClick={() => goToSlide(i)}
className={`h-2 rounded-full transition ${
i === currentIndex ? 'w-8 bg-white' : 'w-2 bg-white/50 hover:bg-white/75'
}`}
aria-label={`Go to slide ${i + 1}`}
/>
))}
</div>
)}
</div>
</div>
{/* Fullscreen modal */}
{isFullscreen && (
<div
className="fixed inset-0 z-[100] bg-black/95 flex items-center justify-center p-4"
onClick={closeFullscreen}
>
<button
type="button"
onClick={closeFullscreen}
className="absolute top-4 right-4 text-white hover:text-gray-300 p-2"
aria-label="Close fullscreen"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button>
<div className="relative max-w-7xl w-full h-full flex items-center" onClick={(e) => e.stopPropagation()}>
<button
type="button"
onClick={goToPrev}
className="absolute left-4 bg-white/20 hover:bg-white/30 text-white rounded-full p-3 z-10"
aria-label="Previous image"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M15 18l-6-6 6-6" />
</svg>
</button>
<div className="w-full h-full flex items-center justify-center">
<ImageFlex
base={currentImage}
alt={`${projectTitle} - Image ${currentIndex + 1}`}
className="max-w-full max-h-full object-contain"
/>
</div>
<button
type="button"
onClick={goToNext}
className="absolute right-4 bg-white/20 hover:bg-white/30 text-white rounded-full p-3 z-10"
aria-label="Next image"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M9 18l6-6-6-6" />
</svg>
</button>
{/* Fullscreen indicators */}
{images.length > 1 && (
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex gap-2">
{images.map((_, i) => (
<button
key={i}
type="button"
onClick={() => goToSlide(i)}
className={`h-2 rounded-full transition ${
i === currentIndex ? 'w-8 bg-white' : 'w-2 bg-white/50 hover:bg-white/75'
}`}
aria-label={`Go to slide ${i + 1}`}
/>
))}
</div>
)}
</div>
</div>
)}
</>
);
}