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>
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
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>
|
|
);
|
|
}
|