wrongmove/frontend/src/components/PhotoCarousel.tsx

89 lines
2.8 KiB
TypeScript
Raw Normal View History

import { useState, useCallback, useEffect } from 'react';
import useEmblaCarousel from 'embla-carousel-react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import type { ListingDetailPhoto } from '@/types';
interface PhotoCarouselProps {
photos: ListingDetailPhoto[];
}
export function PhotoCarousel({ photos }: PhotoCarouselProps) {
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true });
const [selectedIndex, setSelectedIndex] = useState(0);
const onSelect = useCallback(() => {
if (!emblaApi) return;
setSelectedIndex(emblaApi.selectedScrollSnap());
}, [emblaApi]);
useEffect(() => {
if (!emblaApi) return;
emblaApi.on('select', onSelect);
return () => { emblaApi.off('select', onSelect); };
}, [emblaApi, onSelect]);
if (photos.length === 0) {
return (
<div className="w-full h-48 bg-muted flex items-center justify-center text-muted-foreground">
No photos available
</div>
);
}
return (
<div className="relative">
<div className="overflow-hidden" ref={emblaRef}>
<div className="flex">
{photos.map((photo, i) => (
<div key={i} className="flex-[0_0_100%] min-w-0">
<img
src={photo.url}
alt={photo.caption || `Photo ${i + 1}`}
className="w-full h-64 object-cover"
loading="lazy"
/>
</div>
))}
</div>
</div>
{/* Prev/Next arrows */}
{photos.length > 1 && (
<>
<button
className="absolute left-1 top-1/2 -translate-y-1/2 bg-black/50 hover:bg-black/70 text-white rounded-full p-1 transition-colors"
onClick={() => emblaApi?.scrollPrev()}
aria-label="Previous photo"
>
<ChevronLeft className="w-5 h-5" />
</button>
<button
className="absolute right-1 top-1/2 -translate-y-1/2 bg-black/50 hover:bg-black/70 text-white rounded-full p-1 transition-colors"
onClick={() => emblaApi?.scrollNext()}
aria-label="Next photo"
>
<ChevronRight className="w-5 h-5" />
</button>
</>
)}
{/* Counter */}
<div className="absolute bottom-2 right-2 bg-black/60 text-white text-xs px-2 py-1 rounded">
{selectedIndex + 1} / {photos.length}
</div>
{/* Dots */}
{photos.length > 1 && photos.length <= 20 && (
<div className="flex justify-center gap-1 mt-2">
{photos.map((_, i) => (
<button
key={i}
className={`w-1.5 h-1.5 rounded-full transition-colors ${
i === selectedIndex ? 'bg-primary' : 'bg-muted-foreground/30'
}`}
onClick={() => emblaApi?.scrollTo(i)}
/>
))}
</div>
)}
</div>
);
}