Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | 17x 17x 2x 2x 2x 2x 17x 5x 5x 5x 5x 17x 1x 1x 17x 2x 15x 15x 1x 14x 42x 1x | import React from 'react';
import { useState, MouseEvent } from 'react';
import { Image } from '../types/property';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { getFullImageUrl } from '../config/api';
interface ImageSliderProps {
images: Image[];
}
export function ImageSlider({ images = [] }: ImageSliderProps) {
const [currentIndex, setCurrentIndex] = useState(0);
const goToPrevious = (e: MouseEvent) => {
e.stopPropagation();
const isFirstSlide = currentIndex === 0;
const newIndex = isFirstSlide ? images.length - 1 : currentIndex - 1;
setCurrentIndex(newIndex);
};
const goToNext = (e: MouseEvent) => {
e.stopPropagation();
const isLastSlide = currentIndex === images.length - 1;
const newIndex = isLastSlide ? 0 : currentIndex + 1;
setCurrentIndex(newIndex);
};
const goToSlide = (e: MouseEvent, slideIndex: number) => {
e.stopPropagation();
setCurrentIndex(slideIndex);
};
if (!Array.isArray(images) || images.length === 0) {
return (
<div className="w-full h-[480px] bg-gray-200 flex items-center justify-center text-gray-400 rounded-xl">
No Images Available
</div>
);
}
const imageUrl = images[currentIndex]?.url;
if (!imageUrl) {
return (
<div className="w-full h-[480px] bg-gray-200 flex items-center justify-center text-gray-400 rounded-xl">
Invalid Image URL
</div>
);
}
return (
<div className="relative w-full h-[480px] group">
<img
src={getFullImageUrl(imageUrl)}
alt={`Property ${currentIndex + 1}`}
className="w-full h-full object-cover rounded-xl"
/>
{/* Left Arrow */}
<button
className="absolute top-1/2 left-4 -translate-y-1/2 cursor-pointer bg-black/30 hover:bg-black/50 text-white p-2 rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
onClick={goToPrevious}
type="button"
aria-label="Previous image"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
</svg>
</button>
{/* Right Arrow */}
<button
className="absolute top-1/2 right-4 -translate-y-1/2 cursor-pointer bg-black/30 hover:bg-black/50 text-white p-2 rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
onClick={goToNext}
type="button"
aria-label="Next image"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
</button>
{/* Dots/Thumbnails */}
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
{images.map((_, slideIndex) => (
<button
key={slideIndex}
onClick={(e) => goToSlide(e, slideIndex)}
type="button"
aria-label={`Go to image ${slideIndex + 1}`}
className={`
w-3 h-3 rounded-full cursor-pointer transition-all
${currentIndex === slideIndex ? 'bg-white scale-125' : 'bg-white/50 hover:bg-white/75'}
`}
/>
))}
</div>
{/* Image Counter */}
<div className="absolute top-4 right-4 bg-black/50 text-white px-3 py-1 rounded-full text-sm">
{currentIndex + 1} / {images.length}
</div>
</div>
);
} |