From ec66cd2684b5ee608f23304ec0da029f38a3eed4 Mon Sep 17 00:00:00 2001 From: Paul Nothaft <53005142+the-luap@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:27:29 +0200 Subject: [PATCH] fix(gallery): keep the lightbox toolbar from masking the photo (#888) (#892) The bottom info/action bar was a translucent gradient overlaying the image, hiding the lower edge of the photo. The bar is now opaque and the image area stops above it (measured via ResizeObserver, since the bar height varies with flex-wrap, the optional filename line and safe-area padding), so the photo is always fully visible. Co-authored-by: Paul Nothaft --- .../src/components/gallery/PhotoLightbox.tsx | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/gallery/PhotoLightbox.tsx b/frontend/src/components/gallery/PhotoLightbox.tsx index 4276af9c..d079c564 100644 --- a/frontend/src/components/gallery/PhotoLightbox.tsx +++ b/frontend/src/components/gallery/PhotoLightbox.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useLayoutEffect, useRef } from 'react'; import { useDevToolsProtection } from '../../hooks/useDevToolsProtection'; import { X, ChevronLeft, ChevronRight, Download, ZoomIn, ZoomOut, Minimize2, MessageSquare, Heart, Star } from 'lucide-react'; import type { Photo } from '../../types'; @@ -103,6 +103,21 @@ export const PhotoLightbox: React.FC = ({ return () => window.removeEventListener('resize', onResize); }, []); + // The bottom toolbar is opaque and the image area stops above it (#888) + // so the toolbar never masks part of the photo. Its height varies + // (flex-wrap on small screens, optional filename line, safe-area + // padding), so measure it and keep the measurement fresh. + const toolbarRef = useRef(null); + const [toolbarHeight, setToolbarHeight] = useState(0); + useLayoutEffect(() => { + const el = toolbarRef.current; + if (!el) return; + setToolbarHeight(el.offsetHeight); + const observer = new ResizeObserver(() => setToolbarHeight(el.offsetHeight)); + observer.observe(el); + return () => observer.disconnect(); + }, []); + // Save-aware download. On mobile (where Web Share + files is supported) // this opens the OS share sheet so "Save to Photos" actually lands in @@ -656,9 +671,12 @@ export const PhotoLightbox: React.FC = ({ {/* Bottom toolbar. flex-wrap + reduced gap/padding on mobile prevent the action row from clipping when feedback (likes / 5-star ratings / comments) is enabled. pb-[env(safe-area-inset-bottom)] keeps the - buttons above the iOS home indicator. */} + buttons above the iOS home indicator. Opaque, and the image area + above is shortened by toolbarHeight so it never masks the photo + (#888). */}
= ({ return (
= ({ style={{ cursor: isVideoCurrent ? 'default' : (zoom > 1 ? (isDragging ? 'grabbing' : 'grab') : 'default'), right: isDesktopFeedback ? `${desktopFeedbackWidth}px` : 0, + // Stop above the opaque toolbar so it never masks the + // photo (#888). + bottom: `${toolbarHeight}px`, // Tell the browser we handle horizontal gestures ourselves so // it doesn't fight us with edge-swipe back navigation, native // pinch-zoom, etc. Videos keep default touch behaviour.