mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 10:51:13 -05:00
91 lines
3.1 KiB
TypeScript
91 lines
3.1 KiB
TypeScript
/**
|
|
* PhotoGrid Component
|
|
* Reusable photo grid display with modal support
|
|
*/
|
|
|
|
import { memo } from 'react';
|
|
import { Eye, AlertCircle } from 'lucide-react';
|
|
import { useIsMobile } from '@/hooks/use-mobile';
|
|
import type { PhotoItem } from '@/types/photos';
|
|
import { generatePhotoAlt } from '@/lib/photoHelpers';
|
|
import { LazyImage } from '@/components/common/LazyImage';
|
|
|
|
interface PhotoGridProps {
|
|
photos: PhotoItem[];
|
|
onPhotoClick?: (photos: PhotoItem[], index: number) => void;
|
|
maxDisplay?: number;
|
|
className?: string;
|
|
}
|
|
|
|
export const PhotoGrid = memo(({
|
|
photos,
|
|
onPhotoClick,
|
|
maxDisplay,
|
|
className = ''
|
|
}: PhotoGridProps) => {
|
|
const isMobile = useIsMobile();
|
|
const defaultMaxDisplay = isMobile ? 2 : 3;
|
|
const maxToShow = maxDisplay ?? defaultMaxDisplay;
|
|
const displayPhotos = photos.slice(0, maxToShow);
|
|
const remainingCount = Math.max(0, photos.length - maxToShow);
|
|
|
|
if (photos.length === 0) {
|
|
return (
|
|
<div className="text-sm text-muted-foreground flex items-center gap-2">
|
|
<AlertCircle className="w-4 h-4" />
|
|
No photos available
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={`grid gap-2 ${isMobile ? 'grid-cols-2' : 'grid-cols-3'} ${className}`}>
|
|
{displayPhotos.map((photo, index) => (
|
|
<div
|
|
key={photo.id}
|
|
className="relative cursor-pointer group overflow-hidden rounded-md border bg-muted/30 h-32"
|
|
onClick={() => onPhotoClick?.(photos, index)}
|
|
>
|
|
<LazyImage
|
|
src={photo.url}
|
|
alt={generatePhotoAlt(photo)}
|
|
className="w-full h-32"
|
|
onError={(e) => {
|
|
const target = e.target as HTMLImageElement;
|
|
target.style.display = 'none';
|
|
const parent = target.parentElement;
|
|
if (parent) {
|
|
const errorDiv = document.createElement('div');
|
|
errorDiv.className = 'absolute inset-0 flex flex-col items-center justify-center text-destructive text-xs p-2';
|
|
const icon = document.createElement('div');
|
|
icon.textContent = '⚠️';
|
|
icon.className = 'text-lg mb-1';
|
|
const text = document.createElement('div');
|
|
text.textContent = 'Failed to load';
|
|
errorDiv.appendChild(icon);
|
|
errorDiv.appendChild(text);
|
|
parent.appendChild(errorDiv);
|
|
}
|
|
}}
|
|
/>
|
|
<div className="absolute inset-0 flex items-center justify-center bg-black/50 text-white opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
|
|
<Eye className="w-5 h-5" />
|
|
</div>
|
|
{photo.caption && (
|
|
<div className="absolute bottom-0 left-0 right-0 bg-black/70 text-white text-xs p-1 truncate">
|
|
{photo.caption}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
{remainingCount > 0 && (
|
|
<div className="flex items-center justify-center bg-muted rounded-md text-sm text-muted-foreground font-medium">
|
|
+{remainingCount} more
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
});
|
|
|
|
PhotoGrid.displayName = 'PhotoGrid';
|