A personal website powered by Astro and ATProto
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>