From ddffe7896ee03166281fff23d0296a0a24e9e742 Mon Sep 17 00:00:00 2001 From: Yashveer Singh Date: Tue, 4 Aug 2026 19:23:46 +0530 Subject: [PATCH] Fix: mobile menu backdrop fails to dismiss modal (#37430) * Fix: mobile menu backdrop fails to dismiss modal * test: add mobile RHS focus functional spec * fix: stabilize mobile RHS focus tests and handle keyboard trap * fix: refine mobile menu tests and scope focus trap to backdrop interaction * e2e: test mobile menu backdrop dismissal with click action * style: fix trailing whitespace and missing eol newline --------- Co-authored-by: Mattermost Build --- .../lib/src/ui/components/channels/header.ts | 10 +++++-- .../channels/search/mobile_rhs_focus.spec.ts | 27 +++++++++++++++++++ webapp/channels/src/components/menu/menu.scss | 8 ++++++ .../components/src/hooks/useFocusTrap.ts | 1 - 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/header.ts b/e2e-tests/playwright/lib/src/ui/components/channels/header.ts index 7cbfcf98017..085fdf320a5 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/header.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/header.ts @@ -30,8 +30,14 @@ export default class ChannelsHeader { } async openChannelMenu() { - await this.channelMenuDropdown.isVisible(); - await this.channelMenuDropdown.click(); + const page = this.container.page(); + const mobileMenuButton = page.locator('#navbar #channelHeaderDropdownButton'); + const mobileVisible = await mobileMenuButton.isVisible({timeout: 1000}).catch(() => false); + const menuButton = mobileVisible ? mobileMenuButton : this.channelMenuDropdown; + + await expect(menuButton).toBeVisible(); + await menuButton.scrollIntoViewIfNeeded(); + await menuButton.click(); } async openCalls() { diff --git a/e2e-tests/playwright/specs/functional/channels/search/mobile_rhs_focus.spec.ts b/e2e-tests/playwright/specs/functional/channels/search/mobile_rhs_focus.spec.ts index 9ef9da005a6..1178f3c2a0d 100644 --- a/e2e-tests/playwright/specs/functional/channels/search/mobile_rhs_focus.spec.ts +++ b/e2e-tests/playwright/specs/functional/channels/search/mobile_rhs_focus.spec.ts @@ -73,6 +73,33 @@ test.describe('Mobile view RHS auto-focus', () => { await expect(channelsPage.sidebarRight.mobileSearchInput).toBeFocused(); }); + /** + * @objective Tapping the mobile menu backdrop should dismiss/close the modal + */ + test('closes mobile menu when clicking the backdrop layer', {tag: '@mobile'}, async ({pw}) => { + const {user} = await pw.initSetup(); + + // # Log in as the test user and navigate to channels page + const {channelsPage, page} = await pw.testBrowser.login(user); + await channelsPage.goto(); + await channelsPage.toBeVisible(); + + // # Open the mobile channel header menu + await channelsPage.centerView.header.openChannelMenu(); + + // # Define the mobile menu modal locator + const menuModal = page.locator('.modal-dialog.menuModal'); + + // * Verify the menu modal is visible after the click + await expect(menuModal).toBeVisible(); + + // # Click the modal's backdrop layer outside the menu content + await page.mouse.click(5, 5); + + // * Verify the mobile menu modal is now successfully dismissed/hidden + await expect(menuModal).toBeHidden(); + }); + /** * @objective Verify that in narrow/mobile view, the channel header shows an icon-only Search * button; clicking it opens the RHS search panel where a search can be performed, and closing diff --git a/webapp/channels/src/components/menu/menu.scss b/webapp/channels/src/components/menu/menu.scss index 905955e42d9..affcc51809a 100644 --- a/webapp/channels/src/components/menu/menu.scss +++ b/webapp/channels/src/components/menu/menu.scss @@ -20,6 +20,14 @@ } } +.modal-dialog.menuModal { + pointer-events: none; +} + +.modal-dialog.menuModal .modal-content { + pointer-events: auto; +} + .menuModal { & .modal-header { display: none; diff --git a/webapp/platform/components/src/hooks/useFocusTrap.ts b/webapp/platform/components/src/hooks/useFocusTrap.ts index e1f1df7c336..ba930167e3d 100644 --- a/webapp/platform/components/src/hooks/useFocusTrap.ts +++ b/webapp/platform/components/src/hooks/useFocusTrap.ts @@ -198,7 +198,6 @@ function isElementVisible(element: HTMLElement): boolean { if ( style.display === 'none' || style.visibility === 'hidden' || - style.pointerEvents === 'none' || currentElement.hasAttribute('hidden') ) { return false;