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>
47 lines
1.7 KiB
TypeScript
47 lines
1.7 KiB
TypeScript
// Layout shell shared by the public gallery page (GalleryClient) and its
|
|
// loading placeholder (GallerySkeleton). Column count, gaps, radii and
|
|
// container padding are defined once here, so the skeleton's geometry can
|
|
// never drift from the grid it stands in for.
|
|
|
|
export function GalleryHeroFrame({
|
|
children,
|
|
backdrop,
|
|
}: {
|
|
children: React.ReactNode;
|
|
/** Cover image + scrim, absolutely positioned behind the text. */
|
|
backdrop?: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="relative bg-brand-navy overflow-hidden">
|
|
{backdrop}
|
|
<div className="relative container-page px-4 pt-20 pb-8 md:pt-32 md:pb-12">{children}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function GalleryContainer({ children }: { children: React.ReactNode }) {
|
|
return <div className="container-page px-2 sm:px-4 py-4 md:py-8">{children}</div>;
|
|
}
|
|
|
|
export function MasonryGrid({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div className="columns-2 sm:columns-3 lg:columns-4 gap-2 md:gap-3 [column-fill:_balance]">
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Per-tile geometry. Deliberately carries no background so callers can pick
|
|
// one (photo tiles: bg-gray-100, skeleton tiles: the skeleton surface) without
|
|
// two `bg-*` utilities fighting over CSS order.
|
|
export const masonryTileClass = 'mb-2 md:mb-3 break-inside-avoid overflow-hidden rounded-xl';
|
|
|
|
/**
|
|
* Reserves the tile's aspect ratio up front so the image can load into a box
|
|
* that is already the right size — late arrivals never reflow the columns.
|
|
* Returns undefined when the photo has no stored dimensions.
|
|
*/
|
|
export function aspectStyle(width?: number, height?: number): React.CSSProperties | undefined {
|
|
return width && height ? { aspectRatio: `${width} / ${height}` } : undefined;
|
|
}
|