A personal website powered by Astro and ATProto
at main 113 lines 3.4 kB view raw
1--- 2import type { ProcessedGallery } from '../../lib/services/gallery-service'; 3 4interface Props { 5 gallery: ProcessedGallery; 6 showDescription?: boolean; 7 showTimestamp?: boolean; 8 columns?: number; 9 showType?: boolean; 10} 11 12const { 13 gallery, 14 showDescription = true, 15 showTimestamp = true, 16 columns = 3, 17 showType = false 18} = Astro.props; 19 20const formatDate = (dateString: string) => { 21 return new Date(dateString).toLocaleDateString('en-US', { 22 year: 'numeric', 23 month: 'long', 24 day: 'numeric', 25 }); 26}; 27 28const gridCols = { 29 1: 'grid-cols-1', 30 2: 'grid-cols-2', 31 3: 'grid-cols-3', 32 4: 'grid-cols-4', 33 5: 'grid-cols-5', 34 6: 'grid-cols-6', 35}[columns] || 'grid-cols-3'; 36 37// Determine image layout based on number of images 38const getImageLayout = (imageCount: number) => { 39 if (imageCount === 1) return 'grid-cols-1'; 40 if (imageCount === 2) return 'grid-cols-2'; 41 if (imageCount === 3) return 'grid-cols-3'; 42 if (imageCount === 4) return 'grid-cols-2 md:grid-cols-4'; 43 return gridCols; 44}; 45 46const imageLayout = getImageLayout(gallery.images.length); 47--- 48 49<article class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6 mb-6"> 50 <header class="mb-4"> 51 <h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2"> 52 {gallery.title} 53 </h2> 54 55 {showDescription && gallery.description && ( 56 <div class="text-gray-600 dark:text-gray-400 mb-3"> 57 {gallery.description} 58 </div> 59 )} 60 61 {gallery.text && ( 62 <div class="text-gray-900 dark:text-white mb-4"> 63 {gallery.text} 64 </div> 65 )} 66 67 <div class="flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400 mb-4"> 68 {showTimestamp && ( 69 <span> 70 Created on {formatDate(gallery.createdAt)} 71 </span> 72 )} 73 74 {showType && ( 75 <span class="bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-1 rounded text-xs"> 76 {gallery.$type} 77 </span> 78 )} 79 80 <span class="bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 px-2 py-1 rounded text-xs"> 81 {gallery.images.length} image{gallery.images.length !== 1 ? 's' : ''} 82 </span> 83 </div> 84 </header> 85 86 {gallery.images && gallery.images.length > 0 && ( 87 <div class={`grid ${imageLayout} gap-4`}> 88 {gallery.images.map((image, index) => ( 89 <div class="relative group"> 90 <img 91 src={image.url} 92 alt={image.alt || `Gallery image ${index + 1}`} 93 class="w-full h-48 object-cover rounded-lg transition-transform duration-200 group-hover:scale-105" 94 style={image.aspectRatio ? `aspect-ratio: ${image.aspectRatio.width} / ${image.aspectRatio.height}` : ''} 95 loading="lazy" 96 /> 97 {image.alt && ( 98 <div class="absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 text-white text-sm p-2 rounded-b-lg opacity-0 group-hover:opacity-100 transition-opacity duration-200"> 99 {image.alt} 100 </div> 101 )} 102 </div> 103 ))} 104 </div> 105 )} 106 107 <footer class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700"> 108 <div class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400"> 109 <span>Collection: {gallery.collection}</span> 110 <span>Type: {gallery.$type}</span> 111 </div> 112 </footer> 113</article>