Share gallery layout with loading skeletons and add download progress.
Keep the public gallery hero/masonry geometry stable across route and client loading, and show busy state while photos download. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import clsx from 'clsx';
|
||||
import { Skeleton, SkeletonGroup } from '@/components/ui/Skeleton';
|
||||
import { GalleryContainer, GalleryHeroFrame, MasonryGrid, masonryTileClass } from './GalleryLayout';
|
||||
|
||||
// Loading placeholder for the public gallery page. It renders through the same
|
||||
// hero frame, container and masonry grid as the real page (see GalleryLayout),
|
||||
// so replacing it with photos changes only the pixels inside the tiles.
|
||||
|
||||
// Portrait/landscape/square mix standing in for a real event set. Fixed order
|
||||
// on purpose — a random shuffle would differ between the server and client
|
||||
// render and blow up hydration.
|
||||
const FALLBACK_RATIOS = [3 / 4, 4 / 3, 1, 2 / 3, 3 / 2, 4 / 5, 1, 3 / 4, 16 / 9, 4 / 5, 3 / 4, 4 / 3];
|
||||
|
||||
interface GallerySkeletonProps {
|
||||
/** Number of tiles to draw; ignored when `ratios` is given. */
|
||||
count?: number;
|
||||
/**
|
||||
* Real width/height ratios when the caller already knows them (e.g. a
|
||||
* cached gallery payload). Produces a grid with exactly the right geometry
|
||||
* instead of the guessed mix above.
|
||||
*/
|
||||
ratios?: number[];
|
||||
}
|
||||
|
||||
export default function GallerySkeleton({ count = 12, ratios }: GallerySkeletonProps) {
|
||||
const tiles =
|
||||
ratios && ratios.length > 0
|
||||
? ratios
|
||||
: Array.from({ length: count }, (_, i) => FALLBACK_RATIOS[i % FALLBACK_RATIOS.length]);
|
||||
|
||||
return (
|
||||
<SkeletonGroup>
|
||||
<GalleryHeroFrame>
|
||||
{/* Matches the h1 (text-3xl / md:text-5xl) and the pill row below it. */}
|
||||
<Skeleton tone="on-dark" className="h-9 md:h-12 w-2/3 max-w-md" />
|
||||
<div className="mt-4 flex flex-wrap items-center gap-2">
|
||||
<Skeleton tone="on-dark" className="h-7 w-28 rounded-full" />
|
||||
<Skeleton tone="on-dark" className="h-7 w-56 max-w-[60%] rounded-full" />
|
||||
</div>
|
||||
</GalleryHeroFrame>
|
||||
|
||||
<GalleryContainer>
|
||||
<MasonryGrid>
|
||||
{tiles.map((ratio, i) => (
|
||||
<div
|
||||
key={i}
|
||||
aria-hidden="true"
|
||||
className={clsx(
|
||||
masonryTileClass,
|
||||
'bg-secondary-light-gray/70 animate-pulse motion-reduce:animate-none'
|
||||
)}
|
||||
style={{ aspectRatio: `${ratio}` }}
|
||||
/>
|
||||
))}
|
||||
</MasonryGrid>
|
||||
</GalleryContainer>
|
||||
</SkeletonGroup>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user