From 234bb27bc4606950fc544c20fbc2861803f34bb0 Mon Sep 17 00:00:00 2001 From: yuminnnnni Date: Sun, 9 Aug 2026 16:26:51 +0900 Subject: [PATCH] [ZEPPELIN-6586] Make the login page responsive on narrow viewports The login panel was a fixed 800px with absolutely positioned columns, so at a 375px viewport it overflowed both sides of the screen. Lay the panel out with flexbox instead: the desktop two-column presentation is unchanged (500px form + 300px sidebar), while the panel now shrinks with the viewport and stacks the sidebar below the form at @screen-sm-max. On short viewports the panel is capped to the viewport height and scrolls internally. Add narrow-viewport Playwright coverage to the login suite for both light and dark themes: no document-level horizontal overflow and the login controls stay fully visible and usable. Co-Authored-By: Claude Fable 5 --- .../e2e/tests/login/login.spec.ts | 29 +++++++++++++++++++ .../src/app/pages/login/login.component.less | 28 +++++++++++++----- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/zeppelin-web-angular/e2e/tests/login/login.spec.ts b/zeppelin-web-angular/e2e/tests/login/login.spec.ts index e7d07c649e5..6fe65800712 100644 --- a/zeppelin-web-angular/e2e/tests/login/login.spec.ts +++ b/zeppelin-web-angular/e2e/tests/login/login.spec.ts @@ -11,6 +11,7 @@ */ import { expect, test } from '@playwright/test'; +import { DarkModePage } from '../../models/dark-mode-page'; import { LoginPage } from '../../models/login-page'; import { LoginTestUtil, TestCredentials } from '../../models/login-page.util'; import { addPageAnnotationBeforeEach, PAGES } from '../../utils'; @@ -127,4 +128,32 @@ test.describe('Login Page', () => { await loginPage.waitForErrorMessage(); }); + + for (const theme of ['light', 'dark'] as const) { + test(`should fit a narrow viewport without horizontal overflow in ${theme} theme`, async ({ page }) => { + await test.step(`Given the ${theme} theme is active on a 375x812 viewport`, async () => { + const darkModePage = new DarkModePage(page); + await darkModePage.setThemeInLocalStorage(theme); + await page.setViewportSize({ width: 375, height: 812 }); + await page.reload(); + await expect(loginPage.formContainer).toBeVisible(); + }); + + await test.step('Then the document should not overflow horizontally', async () => { + const overflow = await page.evaluate( + () => document.documentElement.scrollWidth - document.documentElement.clientWidth + ); + expect(overflow).toBe(0); + }); + + await test.step('Then the login controls should be fully visible and usable', async () => { + await expect(loginPage.userNameInput).toBeInViewport({ ratio: 1 }); + await expect(loginPage.passwordInput).toBeInViewport({ ratio: 1 }); + await expect(loginPage.loginButton).toBeInViewport({ ratio: 1 }); + + await loginPage.userNameInput.fill('narrow-viewport-user'); + await expect(loginPage.userNameInput).toHaveValue('narrow-viewport-user'); + }); + }); + } }); diff --git a/zeppelin-web-angular/src/app/pages/login/login.component.less b/zeppelin-web-angular/src/app/pages/login/login.component.less index c4e89d02115..e150dd8cea8 100644 --- a/zeppelin-web-angular/src/app/pages/login/login.component.less +++ b/zeppelin-web-angular/src/app/pages/login/login.component.less @@ -32,7 +32,9 @@ } .inner { + display: flex; width: 800px; + max-width: calc(100% - 32px); height: 300px; position: absolute; z-index: 1; @@ -42,19 +44,14 @@ box-shadow: @box-shadow-base; .form { - width: 500px; - position: absolute; - left: 0; - height: 100%; + flex: 1 1 auto; + min-width: 0; background: @component-background; padding: 36px; } .sidebar { - width: 300px; - position: absolute; - right: 0; - height: 100%; + flex: 0 0 300px; background: @primary-color; padding: 24px 36px; color: @text-color-dark; @@ -63,6 +60,21 @@ color: @heading-color-dark; } } + + @media (max-width: @screen-sm-max) { + flex-direction: column; + height: auto; + max-height: calc(100% - 32px); + overflow-y: auto; + + .form { + flex: none; + } + + .sidebar { + flex: none; + } + } } } });