All files / components ImageSlider.tsx

100% Statements 22/22
100% Branches 13/13
100% Functions 6/6
100% Lines 22/22

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