From d8b540d90c0ccf416413ab14cc2717c1c7e187a7 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Wed, 3 Jun 2026 19:28:55 -0400 Subject: [PATCH] Show image count badge on in-feed carousel Add a persistent "{n}/{total}" badge to the top-right of the in-feed image carousel so the number of images in a set is always obvious, including on mobile (previously the count was only implied by the peeking next image). - Mirror the existing currentIndexRef into reactive state, updated only when the index actually changes, so the badge stays live without re-rendering on every scroll frame. - Styling follows the Lightbox PagerDots pattern (white bold text, rounded), on a translucent dark pill so it stays legible over arbitrary in-feed images. - Hidden in quote posts (hideBadges) and for single images. A web aria-live region announces position changes; native screen-reader users continue to get the existing per-image stacked layout. APP-2285 --- src/components/images/Gallery/index.tsx | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/components/images/Gallery/index.tsx b/src/components/images/Gallery/index.tsx index 9ae677c2d2..53c5b331fe 100644 --- a/src/components/images/Gallery/index.tsx +++ b/src/components/images/Gallery/index.tsx @@ -165,6 +165,13 @@ export function Gallery({ const containerRefsRef = useRef>>(new Map()) const thumbDimsRef = useRef>(new Map()) const currentIndexRef = useRef(0) + /* + * Reactive mirror of currentIndexRef for the numbering badge. The ref drives + * scroll/keyboard logic without re-rendering; this state only updates when + * the index actually changes (once per image, not per scroll frame), so the + * badge stays live and cheap. + */ + const [currentIndex, setCurrentIndexState] = useState(0) const emitSwipeMetric = useMemo( () => @@ -182,6 +189,7 @@ export function Gallery({ const prev = currentIndexRef.current if (prev !== index) { currentIndexRef.current = index + setCurrentIndexState(index) emitSwipeMetric(prev, index) } } @@ -348,10 +356,69 @@ export function Gallery({ }} /> + + {!hideBadges && images.length > 1 ? ( + + ) : null} ) } +/* + * Top-right "{n}/{total}" badge for the in-feed carousel. The visible pill is + * decorative (accessible={false}) since the digits are redundant for screen + * reader users. + * + * On native, screen reader users never reach this carousel branch - the Gallery + * renders a separate per-image stack early when screenReaderEnabled is true, + * and each slide there is labelled "Image N of M". So the live region only + * needs to exist on web, where screenReaderEnabled is always false and the + * carousel is the only branch. There we announce position changes via a + * visually-hidden aria-live region. + */ +function GalleryCounter({ + currentIndex, + imageCount, +}: { + currentIndex: number + imageCount: number +}) { + const {t: l} = useLingui() + + return ( + <> + + + {currentIndex + 1}/{imageCount} + + + + {IS_WEB ? ( + + {l`Image ${currentIndex + 1} of ${imageCount}`} + + ) : null} + + ) +} + function computeDims({ height, aspectRatio,