From 1d2e6acecba4f40b6a9027eee31c53c484db70b9 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 27 Oct 2026 09:41:37 +0100 Subject: [PATCH] :recycle: Make the profile photo Update overlay a button The "Update" overlay on the profile photo was a span with an on-click, so the keyboard could not reach it. It is now a button: it shows on keyboard focus as well as on hover. The button centers its line in the circle, so a 4px bottom padding keeps the label where the span drew it (0px difference). New ds visual test "Profile page controls": the page at rest, the overlay on hover and on focus, Save enabled, and the Change email link on hover. AI-assisted-by: claude-opus-5-5 --- .../ui/visual-specs/visual-dashboard.spec.js | 36 +++++++++++++++++++ .../src/app/main/ui/settings/profile.cljs | 6 ++-- .../src/app/main/ui/settings/profile.scss | 9 +++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js b/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js index d4563b69d2..9729d035bc 100644 --- a/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js +++ b/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js @@ -630,6 +630,42 @@ test.describe("Onboarding questions controls", () => { }); }); +test.describe("Profile page controls", () => { + const shot = { maxDiffPixelRatio: 0, threshold: 0 }; + + test("Profile page avatar, form and links", async ({ page }) => { + const dashboardPage = new DashboardPage(page); + await dashboardPage.goToDashboard(); + await dashboardPage.goToAccount(); + + const container = page.locator( + "[class*='settings_profile__form-container']", + ); + await expect(page.getByText("Change email")).toBeVisible(); + await page.mouse.move(10, 700); + await expect(container).toHaveScreenshot(shot); + + // The "Update" overlay shows when the pointer is on the avatar. + await page.locator("[class*='image-change-field']").hover(); + await expect(container).toHaveScreenshot(shot); + + // Save turns enabled once the name changes. + await page.getByLabel("Your name").fill("Renamed user"); + await page.mouse.move(10, 700); + await expect(container).toHaveScreenshot(shot); + + await page.getByText("Change email").hover(); + await expect(container).toHaveScreenshot(shot); + + // The "Update" overlay is a button: it shows on keyboard focus too. + await page.mouse.move(10, 700); + await page.getByRole("button", { name: "Update" }).focus(); + await page.keyboard.press("Shift+Tab"); + await page.keyboard.press("Tab"); + await expect(container).toHaveScreenshot(shot); + }); +}); + test.describe("Templates section controls", () => { const shot = { maxDiffPixelRatio: 0, threshold: 0 }; diff --git a/frontend/src/app/main/ui/settings/profile.cljs b/frontend/src/app/main/ui/settings/profile.cljs index 1c1add422b..de5e3a89de 100644 --- a/frontend/src/app/main/ui/settings/profile.cljs +++ b/frontend/src/app/main/ui/settings/profile.cljs @@ -119,8 +119,10 @@ [:form {:class (stl/css :avatar-form)} [:div {:class (stl/css :image-change-field)} - [:span {:class (stl/css :update-overlay) - :on-click on-image-click} (tr "labels.update")] + [:button {:type "button" + :class (stl/css :update-overlay) + :on-click on-image-click} + (tr "labels.update")] [:img {:src photo}] (when has-photo? [:button {:type "button" diff --git a/frontend/src/app/main/ui/settings/profile.scss b/frontend/src/app/main/ui/settings/profile.scss index 1a6fca03c6..d9e628c971 100644 --- a/frontend/src/app/main/ui/settings/profile.scss +++ b/frontend/src/app/main/ui/settings/profile.scss @@ -277,7 +277,16 @@ form.avatar-form { line-height: 6; text-align: center; background: var(--color-accent-tertiary); + border: none; + + // The button centers its 144px line in the 148px circle; the bottom + // padding keeps the label where the old span drew it. + padding: 0 0 $s-4; z-index: $z-index-modal; + + &:focus-visible { + opacity: 0.8; + } } .delete-overlay {