|
3 | 3 | import { useQuery } from '@tanstack/react-query' |
4 | 4 | import { useLocale, useTranslations } from 'next-intl' |
5 | 5 | import { useEffect, useState } from 'react' |
| 6 | +import Masonry, { ResponsiveMasonry } from 'react-responsive-masonry' |
6 | 7 |
|
7 | 8 | import { API_URL } from '~/constants/env' |
8 | 9 |
|
@@ -152,109 +153,113 @@ export const GalleryShowcase = () => { |
152 | 153 | )} |
153 | 154 |
|
154 | 155 | {!isLoading && !error && galleries.length > 0 && ( |
155 | | - <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> |
156 | | - {galleries.map((gallery) => ( |
157 | | - <a |
158 | | - key={gallery.id} |
159 | | - href={buildGalleryUrl(gallery)} |
160 | | - target="_blank" |
161 | | - rel="noopener noreferrer" |
162 | | - className="group relative overflow-hidden rounded-3xl border border-white/10 bg-linear-to-br from-white/8 to-transparent p-6 transition hover:border-white/30 hover:bg-white/10" |
163 | | - > |
164 | | - {/* Author Avatar & Info */} |
165 | | - {gallery.author && ( |
166 | | - <div className="mb-4 flex items-center gap-3"> |
167 | | - <div className="relative size-10 shrink-0 overflow-hidden rounded-full border border-white/10 bg-white/5"> |
168 | | - {gallery.author.avatar ? ( |
169 | | - <img |
170 | | - src={gallery.author.avatar} |
171 | | - alt={gallery.author.name} |
172 | | - className="h-full w-full object-cover" |
173 | | - onError={(e) => { |
174 | | - const target = e.target as HTMLImageElement |
175 | | - target.style.display = 'none' |
176 | | - }} |
177 | | - /> |
178 | | - ) : null} |
179 | | - {(!gallery.author.avatar || |
180 | | - gallery.author.avatar === '') && ( |
181 | | - <div className="bg-accent-20 text-accent flex h-full w-full items-center justify-center"> |
182 | | - <span className="text-sm font-medium"> |
183 | | - {gallery.author.name.charAt(0).toUpperCase()} |
184 | | - </span> |
185 | | - </div> |
186 | | - )} |
187 | | - </div> |
188 | | - <div className="min-w-0 flex-1"> |
189 | | - <p className="truncate text-sm font-medium text-white"> |
190 | | - {gallery.author.name} |
191 | | - </p> |
192 | | - <p className="truncate text-xs text-white/50"> |
193 | | - {getDisplayUrl(gallery)} |
194 | | - </p> |
| 156 | + <ResponsiveMasonry |
| 157 | + columnsCountBreakPoints={{ 350: 1, 640: 2, 1024: 3 }} |
| 158 | + > |
| 159 | + <Masonry gutter="1rem"> |
| 160 | + {galleries.map((gallery) => ( |
| 161 | + <a |
| 162 | + key={gallery.id} |
| 163 | + href={buildGalleryUrl(gallery)} |
| 164 | + target="_blank" |
| 165 | + rel="noopener noreferrer" |
| 166 | + className="group relative block w-full overflow-hidden rounded-3xl border border-white/10 bg-linear-to-br from-white/8 to-transparent p-6 transition hover:border-white/30 hover:bg-white/10" |
| 167 | + > |
| 168 | + {/* Author Avatar & Info */} |
| 169 | + {gallery.author && ( |
| 170 | + <div className="mb-4 flex items-center gap-3"> |
| 171 | + <div className="relative size-10 shrink-0 overflow-hidden rounded-full border border-white/10 bg-white/5"> |
| 172 | + {gallery.author.avatar ? ( |
| 173 | + <img |
| 174 | + src={gallery.author.avatar} |
| 175 | + alt={gallery.author.name} |
| 176 | + className="h-full w-full object-cover" |
| 177 | + onError={(e) => { |
| 178 | + const target = e.target as HTMLImageElement |
| 179 | + target.style.display = 'none' |
| 180 | + }} |
| 181 | + /> |
| 182 | + ) : null} |
| 183 | + {(!gallery.author.avatar || |
| 184 | + gallery.author.avatar === '') && ( |
| 185 | + <div className="bg-accent-20 text-accent flex h-full w-full items-center justify-center"> |
| 186 | + <span className="text-sm font-medium"> |
| 187 | + {gallery.author.name.charAt(0).toUpperCase()} |
| 188 | + </span> |
| 189 | + </div> |
| 190 | + )} |
| 191 | + </div> |
| 192 | + <div className="min-w-0 flex-1"> |
| 193 | + <p className="truncate text-sm font-medium text-white"> |
| 194 | + {gallery.author.name} |
| 195 | + </p> |
| 196 | + <p className="truncate text-xs text-white/50"> |
| 197 | + {getDisplayUrl(gallery)} |
| 198 | + </p> |
| 199 | + </div> |
195 | 200 | </div> |
196 | | - </div> |
197 | | - )} |
| 201 | + )} |
198 | 202 |
|
199 | | - {/* Site Name */} |
200 | | - <h3 className="group-hover:text-accent mb-2 font-serif text-xl text-white transition"> |
201 | | - {gallery.name} |
202 | | - </h3> |
| 203 | + {/* Site Name */} |
| 204 | + <h3 className="group-hover:text-accent mb-2 font-serif text-xl text-white transition"> |
| 205 | + {gallery.name} |
| 206 | + </h3> |
203 | 207 |
|
204 | | - {/* Description */} |
205 | | - {gallery.description && ( |
206 | | - <p className="mb-4 line-clamp-2 text-sm leading-relaxed text-white/70"> |
207 | | - {gallery.description} |
208 | | - </p> |
209 | | - )} |
| 208 | + {/* Description */} |
| 209 | + {gallery.description && ( |
| 210 | + <p className="mb-4 line-clamp-2 text-sm leading-relaxed text-white/70"> |
| 211 | + {gallery.description} |
| 212 | + </p> |
| 213 | + )} |
210 | 214 |
|
211 | | - {/* Photo Count & Tags */} |
212 | | - <div className="mb-4 space-y-2"> |
213 | | - {gallery.photoCount > 0 && ( |
214 | | - <div className="flex items-center gap-2 text-xs text-white/60"> |
215 | | - <i className="i-lucide-image size-3.5" /> |
216 | | - <span> |
217 | | - {gallery.photoCount}{' '} |
| 215 | + {/* Photo Count & Tags */} |
| 216 | + <div className="mb-4 space-y-2"> |
| 217 | + {gallery.photoCount > 0 && ( |
| 218 | + <div className="flex items-center gap-2 text-xs text-white/60"> |
| 219 | + <i className="i-lucide-image size-3.5" /> |
218 | 220 | <span> |
219 | | - {gallery.photoCount === 1 ? 'photo' : 'photos'} |
220 | | - </span> |
221 | | - </span> |
222 | | - </div> |
223 | | - )} |
224 | | - {gallery.tags.length > 0 && ( |
225 | | - <div className="flex flex-wrap gap-1.5"> |
226 | | - {gallery.tags.slice(0, 4).map((tag) => ( |
227 | | - <span |
228 | | - key={tag} |
229 | | - className="rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-xs text-white/70" |
230 | | - > |
231 | | - {tag} |
232 | | - </span> |
233 | | - ))} |
234 | | - {gallery.tags.length > 4 && ( |
235 | | - <span className="rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-xs text-white/50"> |
236 | | - +{gallery.tags.length - 4} |
| 221 | + {gallery.photoCount}{' '} |
| 222 | + <span> |
| 223 | + {gallery.photoCount === 1 ? 'photo' : 'photos'} |
| 224 | + </span> |
237 | 225 | </span> |
238 | | - )} |
239 | | - </div> |
240 | | - )} |
241 | | - </div> |
| 226 | + </div> |
| 227 | + )} |
| 228 | + {gallery.tags.length > 0 && ( |
| 229 | + <div className="flex flex-wrap gap-1.5"> |
| 230 | + {gallery.tags.slice(0, 4).map((tag) => ( |
| 231 | + <span |
| 232 | + key={tag} |
| 233 | + className="rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-xs text-white/70" |
| 234 | + > |
| 235 | + {tag} |
| 236 | + </span> |
| 237 | + ))} |
| 238 | + {gallery.tags.length > 4 && ( |
| 239 | + <span className="rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-xs text-white/50"> |
| 240 | + +{gallery.tags.length - 4} |
| 241 | + </span> |
| 242 | + )} |
| 243 | + </div> |
| 244 | + )} |
| 245 | + </div> |
242 | 246 |
|
243 | | - {/* Divider */} |
244 | | - <div className="mb-4 h-px w-full bg-linear-to-r from-transparent via-white/30 to-transparent opacity-50" /> |
| 247 | + {/* Divider */} |
| 248 | + <div className="mb-4 h-px w-full bg-linear-to-r from-transparent via-white/30 to-transparent opacity-50" /> |
245 | 249 |
|
246 | | - {/* Footer */} |
247 | | - <div className="flex items-center justify-between"> |
248 | | - <div className="text-xs text-white/40"> |
249 | | - {formatDate(gallery.createdAt)} |
250 | | - </div> |
251 | | - <div className="text-white/30 transition group-hover:text-white/60"> |
252 | | - <i className="i-lucide-external-link size-4" /> |
| 250 | + {/* Footer */} |
| 251 | + <div className="flex items-center justify-between"> |
| 252 | + <div className="text-xs text-white/40"> |
| 253 | + {formatDate(gallery.createdAt)} |
| 254 | + </div> |
| 255 | + <div className="text-white/30 transition group-hover:text-white/60"> |
| 256 | + <i className="i-lucide-external-link size-4" /> |
| 257 | + </div> |
253 | 258 | </div> |
254 | | - </div> |
255 | | - </a> |
256 | | - ))} |
257 | | - </div> |
| 259 | + </a> |
| 260 | + ))} |
| 261 | + </Masonry> |
| 262 | + </ResponsiveMasonry> |
258 | 263 | )} |
259 | 264 | </section> |
260 | 265 | ) |
|
0 commit comments