From 2e58961cf1d32fa303c042809309172add45eb51 Mon Sep 17 00:00:00 2001 From: Tommaso Casaburi Date: Tue, 9 Jun 2026 15:46:14 +0700 Subject: [PATCH] fix(challenge-modal): center mobile modal in viewport Use left: 50% with translateX(-50%) so the dialog stays centered on mobile instead of offset from a stale static position. --- .../__tests__/challenge-modal.test.tsx | 25 ++++++++++++++++++- .../challenge-modal/challenge-modal.tsx | 8 +++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/components/challenge-modal/__tests__/challenge-modal.test.tsx b/src/components/challenge-modal/__tests__/challenge-modal.test.tsx index bdde3b4e..59f09b0a 100644 --- a/src/components/challenge-modal/__tests__/challenge-modal.test.tsx +++ b/src/components/challenge-modal/__tests__/challenge-modal.test.tsx @@ -18,6 +18,7 @@ const testState = vi.hoisted(() => ({ } as Record, challenges: [] as Array<{ challenge: any; id: number }>, commentsByCid: {} as Record, + isMobile: false, publicationPreview: 'preview body', publicationType: 'post', removeChallengeMock: vi.fn(), @@ -55,7 +56,7 @@ vi.mock('../../../lib/utils/challenge-utils', () => ({ })); vi.mock('../../../hooks/use-is-mobile', () => ({ - default: () => false, + default: () => testState.isMobile, })); vi.mock('../../../hooks/use-theme', () => ({ @@ -162,6 +163,7 @@ describe('ChallengeModal', () => { }, }, }; + testState.isMobile = false; testState.publicationPreview = 'preview body'; testState.publicationType = 'post'; testState.removeChallengeMock.mockReset(); @@ -226,6 +228,27 @@ describe('ChallengeModal', () => { expect(testState.removeChallengeMock).toHaveBeenCalledOnce(); }); + it('centers the mobile modal from the viewport instead of its static position', async () => { + testState.isMobile = true; + Object.defineProperty(window, 'innerWidth', { configurable: true, value: 375 }); + Object.defineProperty(window, 'innerHeight', { configurable: true, value: 667 }); + testState.publicationType = 'reply'; + testState.challenges = [ + createStoredChallenge({ + challenge: 'https://spamblocker.bitsocial.net/api/v1/iframe/session-123', + type: 'url/iframe', + }), + ]; + + await renderModal(); + + const dialog = container.querySelector('[role="dialog"]'); + expect(container.textContent).toContain('wants to open spamblocker.bitsocial.net.'); + expect(dialog?.style.left).toBe('50%'); + expect(dialog?.style.top).toBe('0px'); + expect(dialog?.style.transform).toBe('translate3d(-50%, 134px, 0)'); + }); + it('redacts generated fortune BBCode from challenge publication details', async () => { const publication = { ...createPublication(), diff --git a/src/components/challenge-modal/challenge-modal.tsx b/src/components/challenge-modal/challenge-modal.tsx index a94b9e24..05be1845 100644 --- a/src/components/challenge-modal/challenge-modal.tsx +++ b/src/components/challenge-modal/challenge-modal.tsx @@ -481,8 +481,8 @@ const Challenge = ({ challenge, closeModal, abandonModal }: ChallengeProps) => { if (publication?.parentCid) extraTitleParts.push(parentAddress ? `reply ${parentAddress}` : 'reply'); if (publicationContent && publicationType !== 'vote') extraTitleParts.push(publicationContent); - const mobileX = isIframeVisible ? 5 : window.innerWidth / 2 - 150; const mobileY = isIframeVisible ? Math.max(10, (window.innerHeight - 600) / 2) : window.innerHeight / 2 - 200; + const mobileTransform = isIframeVisible ? `translate3d(5px, ${Math.round(mobileY)}px, 0)` : `translate3d(-50%, ${Math.round(mobileY)}px, 0)`; const displayCommunityAddress = getDisplayCommunityAddress(shortCommunityAddress, communityAddress) || t('board'); const iframeConfirmMessage = stripLegacyIframeConfirmDetails( t('iframe_challenge_confirm', { @@ -524,9 +524,9 @@ const Challenge = ({ challenge, closeModal, abandonModal }: ChallengeProps) => { aria-modal='true' aria-labelledby='challenge-modal-title' style={{ - transform: isMobile - ? `translate3d(${Math.round(mobileX)}px, ${Math.round(mobileY)}px, 0)` - : `translate3d(${Math.round(initialPosition.x)}px, ${Math.round(initialPosition.y)}px, 0)`, + top: 0, + left: isMobile && !isIframeVisible ? '50%' : 0, + transform: isMobile ? mobileTransform : `translate3d(${Math.round(initialPosition.x)}px, ${Math.round(initialPosition.y)}px, 0)`, touchAction: 'none', }} >