diff --git a/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js b/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js index 878a02d347..932ebb7061 100644 --- a/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js +++ b/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js @@ -833,6 +833,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..74f073ae36 100644 --- a/frontend/src/app/main/ui/settings/profile.cljs +++ b/frontend/src/app/main/ui/settings/profile.cljs @@ -17,6 +17,8 @@ [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.forms :as fm] [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]] + [app.main.ui.ds.foundations.typography :as t] + [app.main.ui.ds.foundations.typography.heading :refer [heading*]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [rumext.v2 :as mf])) @@ -71,7 +73,8 @@ [:div {:class (stl/css :options)} [:div.change-email - [:a {:on-click on-show-change-email} + [:a {:class (stl/css :link) + :on-click on-show-change-email} (tr "dashboard.change-email")]]]] [:> fm/submit-button* @@ -80,8 +83,9 @@ :class (stl/css :btn-primary)}] [:div {:class (stl/css :links)} - [:div {:class (stl/css :link-item)} - [:a {:on-click on-show-delete-account + [:div + [:a {:class (stl/css :link) + :on-click on-show-delete-account :data-testid "remove-acount-btn"} (tr "dashboard.remove-account")]]]])) @@ -119,9 +123,12 @@ [: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")] - [:img {:src photo}] + [:button {:type "button" + :class (stl/css :update-overlay) + :on-click on-image-click} + (tr "labels.update")] + [:img {:class (stl/css :avatar-image) + :src photo}] (when has-photo? [:button {:type "button" :class (stl/css :delete-overlay) @@ -146,6 +153,10 @@ [:section {:class (stl/css :dashboard-settings) :aria-labelledby "profile-section-title"} [:div {:class (stl/css :form-container)} - [:h2 {:id "profile-section-title"} (tr "labels.profile")] + [:> heading* {:level 2 + :typography t/title-large + :id "profile-section-title" + :class (stl/css :page-title)} + (tr "labels.profile")] [:> profile-photo-form*] [:> profile-form*]]]) diff --git a/frontend/src/app/main/ui/settings/profile.scss b/frontend/src/app/main/ui/settings/profile.scss index 1a6fca03c6..0c2b954a18 100644 --- a/frontend/src/app/main/ui/settings/profile.scss +++ b/frontend/src/app/main/ui/settings/profile.scss @@ -4,316 +4,83 @@ // // Copyright (c) KALEIDOS SUBSIDIARY SL -@use "common/refactor/common-refactor" as *; +// Only for %button-primary: the save button is the shared forms submit +// button, which moves to the DS with the forms component. +@use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; +@use "ds/_utils.scss" as *; +@use "ds/typography.scss" as *; .dashboard-settings { display: flex; - width: 100%; + inline-size: 100%; justify-content: center; align-items: center; +} - a:not(.button-primary, .link) { - color: var(--color-foreground-secondary); - } +.link { + @include use-typography("body-medium"); + + color: var(--color-accent-primary); } .form-container { display: flex; flex-direction: column; - max-width: $s-500; - width: $s-580; - margin: $s-80 auto $s-120 auto; + max-inline-size: $sz-500; + inline-size: px2rem(580); + margin-block: px2rem(80) px2rem(120); + margin-inline: auto; justify-content: center; - - form { - display: flex; - flex-direction: column; - - .btn-secondary { - width: 100%; - font-size: $fs-11; - text-transform: uppercase; - background-color: var(--color-background-tertiary); - color: var(--color-foreground-primary); - - &:hover { - color: var(--color-accent-primary); - background-color: var(--color-background-quaternary); - } - } - - hr { - display: none; - } - } - - .fields-row { - --input-height: #{$s-40}; - - margin-bottom: $s-20; - flex-direction: column; - - .options { - display: flex; - justify-content: flex-end; - font-size: $fs-14; - margin-top: $s-12; - } - } - - .field { - margin-bottom: $s-20; - } - - .field-title { - color: var(--color-foreground-primary); - - &:not(:first-child) { - margin-top: $s-64; - } - } - - .field-text { - color: var(--color-foreground-secondary); - } - - .custom-input, - .custom-select { - flex-direction: column-reverse; - - label { - position: relative; - text-transform: uppercase; - color: var(--color-foreground-primary); - font-size: $fs-11; - margin-bottom: $s-12; - margin-left: calc(-1 * $s-4); - } - - input, - select { - background-color: var(--color-background-tertiary); - border-radius: $br-8; - border-color: transparent; - color: var(--color-foreground-primary); - padding: 0 $s-16; - - &:focus { - outline: $s-1 solid var(--color-accent-primary); - } - - ::placeholder { - color: var(--color-foreground-secondary); - } - } - - .help-icon { - bottom: $s-12; - top: auto; - - svg { - fill: var(--color-foreground-secondary); - } - } - - &.disabled { - input { - background-color: var(--input-background-color-disabled); - border-color: var(--color-background-quaternary); - color: var(--color-foreground-secondary); - } - } - - .input-container { - background-color: var(--color-background-tertiary); - border-radius: $br-8; - border-color: transparent; - margin-top: $s-24; - - .main-content { - label { - position: absolute; - top: calc(-1 * $s-24); - } - - span { - color: var(--color-foreground-primary); - } - } - - &:focus { - border: $s-1 solid var(--color-accent-primary); - } - } - - textarea { - border-radius: $br-8; - padding: $s-12 $s-16; - background-color: var(--color-background-tertiary); - color: var(--color-foreground-primary); - border: none; - - &:focus { - outline: $s-1 solid var(--color-accent-primary); - } - } - } - - &.two-columns { - max-width: $s-520; - justify-content: space-between; - flex-direction: row; - } - - h1 { - font-size: $fs-36; - color: var(--color-background-tertiary); - margin-bottom: $s-20; - } - - .subtitle { - font-size: $fs-24; - color: var(--color-background-tertiary); - margin-bottom: $s-20; - } - - .notification-icon { - justify-content: center; - display: flex; - margin-bottom: $s-48; - - svg { - fill: var(--color-background-primary); - height: 40%; - width: 40%; - } - } - - .notification-text { - font-size: $fs-16; - color: var(--color-background-primary); - margin-bottom: $s-20; - } - - .notification-text-email { - background: var(--color-foreground-primary); - border-radius: $br-4; - color: var(--color-background-primary); - font-size: $fs-16; - font-weight: $fw500; - margin: $s-24 0 $s-40 0; - padding: $s-16; - text-align: center; - } - - h2 { - font-size: $fs-24; - font-weight: $fw400; - color: var(--color-foreground-primary); - display: flex; - align-items: center; - margin: $s-16 0; - } - - h3 { - font-size: $fs-12; - font-weight: $fw400; - color: var(--color-foreground-primary); - display: flex; - align-items: center; - margin: $s-8 0; - text-transform: uppercase; - } - - a:not(.button-primary) { - &:hover { - text-decoration: underline; - } - } - - p { - color: var(--color-background-primary); - } - - hr { - border-color: var(--color-foreground-secondary); - } - - .links { - margin-top: $s-12; - } } -form.avatar-form { +.page-title { + color: var(--color-foreground-primary); + display: flex; + align-items: center; + margin-block: var(--sp-l); + margin-inline: 0; +} + +.fields-row { + --input-height: #{$sz-40}; + + margin-block-end: var(--sp-xl); + flex-direction: column; +} + +.options { + display: flex; + justify-content: flex-end; + margin-block-start: var(--sp-m); +} + +.links { + margin-block-start: var(--sp-m); +} + +.avatar-form { display: flex; flex-direction: column; - width: $s-148; - height: $s-148; - margin: $s-16 0; + inline-size: px2rem(148); + block-size: px2rem(148); + margin-block: var(--sp-l); + margin-inline: 0; +} - img { - border-radius: 50%; - flex-shrink: 0; - height: 100%; - margin-right: $s-16; - width: 100%; - } +.avatar-image { + border-radius: $br-circle; + flex-shrink: 0; + block-size: 100%; + margin-inline-end: var(--sp-l); + inline-size: 100%; } .image-change-field { position: relative; - width: 100%; - height: 100%; - - .update-overlay { - opacity: 0; - cursor: pointer; - position: absolute; - width: 100%; - height: 100%; - border-radius: 50%; - font-size: $fs-24; - color: var(--color-foreground-primary); - line-height: 6; - text-align: center; - background: var(--color-accent-tertiary); - z-index: $z-index-modal; - } - - .delete-overlay { - position: absolute; - top: $s-4; - inset-inline-end: $s-4; - display: flex; - align-items: center; - justify-content: center; - width: $s-32; - height: $s-32; - padding: 0; - border: none; - border-radius: 50%; - background: var(--color-background-primary); - color: var(--color-foreground-primary); - cursor: pointer; - opacity: 0; - transition: opacity 0.15s ease-in-out; - z-index: calc(#{$z-index-modal} + 1); - - &:hover { - background: var(--color-background-quaternary); - color: var(--color-accent-primary); - } - } - - input[type="file"] { - width: 100%; - height: 100%; - position: absolute; - opacity: 0; - cursor: pointer; - top: 0; - z-index: $z-index-modal; - } + inline-size: 100%; + block-size: 100%; &:hover { .update-overlay { @@ -326,52 +93,70 @@ form.avatar-form { } } +.update-overlay { + @include use-typography("title-large"); + + opacity: 0; + cursor: pointer; + position: absolute; + inline-size: 100%; + block-size: 100%; + border: none; + border-radius: $br-circle; + color: var(--color-foreground-primary); + + // Draws the label in the middle of the circle. + line-height: 6; + text-align: center; + background: var(--color-accent-tertiary); + + // 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 var(--sp-xs); + z-index: var(--z-index-set); + + &:focus-visible { + opacity: 0.8; + } +} + +.delete-overlay { + --delete-overlay-background-color: var(--color-background-primary); + --delete-overlay-foreground-color: var(--color-foreground-primary); + + position: absolute; + inset-block-start: var(--sp-xs); + inset-inline-end: var(--sp-xs); + display: flex; + align-items: center; + justify-content: center; + inline-size: $sz-32; + block-size: $sz-32; + padding: 0; + border: none; + border-radius: $br-circle; + background: var(--delete-overlay-background-color); + color: var(--delete-overlay-foreground-color); + cursor: pointer; + opacity: 0; + transition: opacity 0.15s ease-in-out; + z-index: calc(var(--z-index-set) + 1); + + &:hover { + --delete-overlay-background-color: var(--color-background-quaternary); + --delete-overlay-foreground-color: var(--color-accent-primary); + } +} + .profile-form { display: flex; flex-direction: column; - max-width: $s-368; - width: 100%; -} - -.newsletter-subs { - border-bottom: $s-1 solid var(--color-foreground-secondary); - border-top: $s-1 solid var(--color-foreground-secondary); - padding: $s-32 0; - margin-bottom: $s-32; - - .newsletter-title { - font-family: "worksans", "vazirmatn", sans-serif; - color: var(--color-foreground-secondary); - font-size: $fs-14; - } - - label { - font-family: "worksans", "vazirmatn", sans-serif; - color: var(--color-background-primary); - font-size: $fs-12; - margin-right: calc(-1 * $s-16); - margin-bottom: $s-12; - } - - .info { - color: var(--color-foreground-secondary); - font-size: $fs-12; - margin-bottom: $s-8; - } - - .input-checkbox label { - align-items: flex-start; - } -} - -.btn-secondary { - @extend %button-secondary; - - height: $s-32; + max-inline-size: px2rem(368); + inline-size: 100%; } .btn-primary { @extend %button-primary; - height: $s-32; + block-size: $sz-32; }