Files
thrilltrack-explorer/src-old/components/common/PhotoGrid.tsx

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';