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> ); } |