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.
This commit is contained in:
Tommaso Casaburi
2026-06-09 15:46:14 +07:00
parent 4308d26cf0
commit 2e58961cf1
2 changed files with 28 additions and 5 deletions
@@ -18,6 +18,7 @@ const testState = vi.hoisted(() => ({
} as Record<string, any>,
challenges: [] as Array<{ challenge: any; id: number }>,
commentsByCid: {} as Record<string, { author?: { shortAddress?: string } }>,
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<HTMLElement>('[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(),
@@ -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',
}}
>