From 6c0c3b89a94b8f2279a74bf4a02dc18e3bf8e980 Mon Sep 17 00:00:00 2001 From: TerryM Date: Tue, 2 Jun 2026 10:51:17 +0800 Subject: [PATCH] fix(lightbox): anchor save hint below rendered image Measure the image's rendered bottom edge with refs + ResizeObserver and position the long-press save hint relative to it instead of pinning to screen center or stage bottom. Enlarges the toast for mobile legibility and clamps the offset so tall portrait images don't push it offscreen. --- .../messageStream/overlays/ImageLightbox.tsx | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/src/components/messageStream/overlays/ImageLightbox.tsx b/src/components/messageStream/overlays/ImageLightbox.tsx index 7b2dc2c..4c4fbfa 100644 --- a/src/components/messageStream/overlays/ImageLightbox.tsx +++ b/src/components/messageStream/overlays/ImageLightbox.tsx @@ -152,7 +152,38 @@ function LightboxView({ const [index, setIndex] = useState(startIndex); const [isCaptionVisible, setIsCaptionVisible] = useState(true); const [showSaveHint, setShowSaveHint] = useState(true); + const [hintTop, setHintTop] = useState(null); const touchStartX = useRef(null); + const stageRef = useRef(null); + const imgRef = useRef(null); + + const measureHintPosition = useCallback(() => { + const stage = stageRef.current; + const img = imgRef.current; + if (!stage || !img) return; + const stageRect = stage.getBoundingClientRect(); + const imgRect = img.getBoundingClientRect(); + const gap = 12; + const safeBottomReserve = 80; + const desired = imgRect.bottom - stageRect.top + gap; + const maxTop = stageRect.height - safeBottomReserve; + setHintTop(Math.max(0, Math.min(desired, maxTop))); + }, []); + + useEffect(() => { + if (!showSaveHint) return; + measureHintPosition(); + const img = imgRef.current; + const stage = stageRef.current; + const ro = new ResizeObserver(() => measureHintPosition()); + if (img) ro.observe(img); + if (stage) ro.observe(stage); + window.addEventListener("resize", measureHintPosition); + return () => { + ro.disconnect(); + window.removeEventListener("resize", measureHintPosition); + }; + }, [measureHintPosition, showSaveHint, index]); // Clamp at the ends instead of wrapping; the nav arrows / swipe / arrow // keys should all behave like a linear gallery, not a carousel. @@ -241,7 +272,10 @@ function LightboxView({ {/* Image stage */} -
+
{hasMany && index > 0 ? ( -
- ☝️ -
-
- {t("longPressImageSave")} -
) : null} @@ -315,8 +350,10 @@ function LightboxView({ {/* No select-none / touch-callout:none here so iOS Safari's native long-press menu ("Save in Photos") works on the full-size image. */} {current.filename}