From 5109b00bc0a0822ff19d588cbffd5c3f9c31467a Mon Sep 17 00:00:00 2001 From: diondiondion Date: Wed, 24 Jun 2026 10:54:58 +0200 Subject: [PATCH] [Glitch] Fix media modal navigation in RTL languages Port f1b0832bfb08dc2d8ccb410be917095f0fcac62e to glitch-soc Signed-off-by: Claire --- .../features/ui/components/media_modal.tsx | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.tsx b/app/javascript/flavours/glitch/features/ui/components/media_modal.tsx index e035e45d68..4f518e1109 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.tsx +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.tsx @@ -47,11 +47,9 @@ interface MediaModalProps { } const MIN_SWIPE_DISTANCE = 400; +const isLtrDir = getComputedStyle(document.body).direction !== 'rtl'; -export const MediaModal: FC = forwardRef< - HTMLDivElement, - MediaModalProps ->( +export const MediaModal = forwardRef( ( { media, @@ -64,15 +62,16 @@ export const MediaModal: FC = forwardRef< statusId, onChangeBackgroundColor, }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars -- _ref is required to keep the ref forwarding working _ref, ) => { const [index, setIndex] = useState(startIndex); const [zoomedIn, setZoomedIn] = useState(false); const currentMedia = media.get(index); + const sign = isLtrDir ? '-' : ''; + const [wrapperStyles, api] = useSpring(() => ({ - x: `-${index * 100}%`, + x: `${sign}${index * 100}%`, })); const handleChangeIndex = useCallback( @@ -85,10 +84,12 @@ export const MediaModal: FC = forwardRef< setIndex(newIndex); setZoomedIn(false); if (animate) { - void api.start({ x: `calc(-${newIndex * 100}% + 0px)` }); + void api.start({ + x: `calc(${sign}${newIndex * 100}% + 0px)`, + }); } }, - [api, media.size], + [api, media.size, sign], ); const handlePrevClick = useCallback(() => { handleChangeIndex(index - 1, true); @@ -99,11 +100,14 @@ export const MediaModal: FC = forwardRef< const handleKeyDown = useCallback( (event: KeyboardEvent) => { - if (event.key === 'ArrowLeft') { + const prevKey = isLtrDir ? 'ArrowLeft' : 'ArrowRight'; + const nextKey = isLtrDir ? 'ArrowRight' : 'ArrowLeft'; + + if (event.key === prevKey) { handlePrevClick(); event.preventDefault(); event.stopPropagation(); - } else if (event.key === 'ArrowRight') { + } else if (event.key === nextKey) { handleNextClick(); event.preventDefault(); event.stopPropagation(); @@ -124,13 +128,14 @@ export const MediaModal: FC = forwardRef< active && Math.abs(mx) > Math.min(window.innerWidth / 4, MIN_SWIPE_DISTANCE) ) { - handleChangeIndex(index - xDir); + handleChangeIndex(isLtrDir ? index - xDir : index + xDir); cancel(); } // Set the x position via calc to ensure proper centering regardless of screen size. const x = active ? mx : 0; + const operator = isLtrDir ? '+' : '-'; void api.start({ - x: `calc(-${index * 100}% + ${x}px)`, + x: `calc(${sign}${index * 100}% ${operator} ${x}px)`, }); }, { pointer: { capture: false } }, @@ -161,14 +166,23 @@ export const MediaModal: FC = forwardRef< width: number; height: number; }>({ width: 0, height: 0 }); - const handleRef: RefCallback = useCallback((ele) => { - if (ele?.clientWidth && ele.clientHeight) { - setViewportDimensions({ - width: ele.clientWidth, - height: ele.clientHeight, - }); - } - }, []); + const handleRef: RefCallback = useCallback( + (ele) => { + if (typeof _ref === 'function') { + _ref(ele); + } else if (_ref) { + _ref.current = ele; + } + + if (ele?.clientWidth && ele.clientHeight) { + setViewportDimensions({ + width: ele.clientWidth, + height: ele.clientHeight, + }); + } + }, + [_ref], + ); const zoomable = currentMedia?.get('type') === 'image' && @@ -268,9 +282,8 @@ export const MediaModal: FC = forwardRef< const intl = useIntl(); - const leftNav = media.size > 1 && ( + const prevNav = media.size > 1 && ( ); - const rightNav = media.size > 1 && ( + const nextNav = media.size > 1 && (