Change gap calculation on gallery for better breakpoint on mobile and tablet

This commit is contained in:
2026-02-02 10:51:37 +01:00
parent 79b186889b
commit 90c27ff60a
2 changed files with 37 additions and 6 deletions

View File

@ -44,6 +44,9 @@ type Props = {
maxRowItems?: number; // desktop
maxRowItemsMobile?: number; // <640px
gap?: number; // px
gapNarrow?: number; // px for narrower containers
gapNarrowMaxWidth?: number; // px breakpoint for gapNarrow
gapBreakpoints?: Array<{ maxWidth: number; gap: number }>;
debug?: boolean;
className?: string;
};
@ -85,12 +88,33 @@ export default function JustifiedGallery({
maxRowItems = 5,
maxRowItemsMobile = 3,
gap = 12,
gapNarrow,
gapNarrowMaxWidth = 720,
gapBreakpoints,
debug = false,
className,
}: Props) {
const containerRef = useRef<HTMLDivElement | null>(null);
const sentinelRef = useRef<HTMLDivElement | null>(null);
const [containerWidth, setContainerWidth] = useState(0);
const effectiveGap = (() => {
if (gapBreakpoints && containerWidth > 0) {
const sorted = [...gapBreakpoints].sort((a, b) => a.maxWidth - b.maxWidth);
for (const bp of sorted) {
if (containerWidth <= bp.maxWidth) return bp.gap;
}
}
if (
gapNarrow != null &&
containerWidth > 0 &&
containerWidth <= gapNarrowMaxWidth
) {
return gapNarrow;
}
return gap;
})();
// Measure container width (responsive)
useEffect(() => {
@ -143,7 +167,7 @@ export default function JustifiedGallery({
const flush = () => {
if (current.length === 0) return;
const gaps = gap * (current.length - 1);
const gaps = effectiveGap * (current.length - 1);
const widthWithoutGaps = Math.max(0, available - gaps);
// Compute row height so it exactly fills the row width.
@ -172,14 +196,15 @@ export default function JustifiedGallery({
aspectSum += a;
// Estimate the row width if we were to keep targetH
const estimatedWidth = aspectSum * targetH + gap * (current.length - 1);
const estimatedWidth =
aspectSum * targetH + effectiveGap * (current.length - 1);
// If we've filled the row (or reached max items) and have at least 2 tiles, flush.
if (
(estimatedWidth >= available || current.length >= maxItems) &&
current.length > 1
) {
const gaps = gap * (current.length - 1);
const gaps = effectiveGap * (current.length - 1);
const widthWithoutGaps = Math.max(0, available - gaps);
const computedH = widthWithoutGaps / aspectSum;
@ -227,7 +252,7 @@ export default function JustifiedGallery({
}, [
items,
containerWidth,
gap,
effectiveGap,
targetRowHeight,
targetRowHeightMobile,
maxRowHeight,
@ -260,7 +285,7 @@ export default function JustifiedGallery({
? "justify-start"
: "justify-between",
)}
style={{ columnGap: gap }}
style={{ columnGap: effectiveGap }}
>
{row.map((t) => (
<GalleryTile
@ -275,7 +300,8 @@ export default function JustifiedGallery({
{debug ? (
<div className="text-xs text-muted-foreground font-mono">
{`row ${idx + 1} | h=${Math.round(row[0]?.h ?? 0)} | w=${Math.round(
row.reduce((sum, t) => sum + t.w, 0) + gap * (row.length - 1),
row.reduce((sum, t) => sum + t.w, 0) +
effectiveGap * (row.length - 1),
)} | items=${row.length} | `}
{row
.map(

View File

@ -151,6 +151,11 @@ export default function PortfolioGallery({
maxRowItems={5}
maxRowItemsMobile={1}
gap={12}
gapBreakpoints={[
{ maxWidth: 685, gap: 6 },
{ maxWidth: 910, gap: 8 },
{ maxWidth: 1130, gap: 10 },
]}
onLoadMore={done ? undefined : () => void loadMore()}
hasMore={!done}
isLoadingMore={loading}