mirror of
https://github.com/penpot/penpot.git
synced 2026-10-31 06:50:33 +00:00
♻️ Move the profile page to the DS and make the Update overlay a button (#12651)
* ♻️ 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 * ♻️ Move profile page styles off the deprecated styles profile.scss imported the whole deprecated module and kept a long copy of shared settings rules. It now uses the DS tokens, sizes, borders, z-index and typography. The deprecated import stays only for %button-primary: the save button is the shared forms submit button. Deleted selectors that match nothing in profile.cljs: .btn-secondary (both), .field, .field-title, .field-text, .custom-input, .custom-select, .two-columns, .subtitle, .notification-icon, .notification-text, .notification-text-email, .newsletter-subs, the h1, h3, p and hr rules, the form rule (both forms set their own flex) and the link hover underline (the global link rule already does it). Typographies: the page title uses title-large (line-height 1.4 instead of 1.15), the Change email link title-small (line-height 1.2 instead of the default), and the Update overlay title-large with its own line-height. Visible change: the taller title line moves the title 3px down and the rest of the page 6px down. Nothing else changes. AI-assisted-by: claude-opus-5-5 * ♻️ Give the profile page links, title and photo classes The links and the profile photo get their own classes instead of bare a and img selectors, and the page title uses the DS heading with title-large (it already used that typography). The link wrapper drops a class with no styles, and the rule for the file input goes: the shared uploader hides that input inline, so the rule did nothing. No visual change. AI-assisted-by: claude-opus-5-5 * 💄 Use one DS typography and the accent color for the profile links "Change email" took title-small from its container, overridden by the global link styles (14px, weight 500), and "Want to remove your account?" had no typography and inherited 16px. Both links now use body-medium and --color-accent-primary, as design asked in the review. The "Change email" line is taller with the 1.4 line height, so Save settings and the link below it sit 3px lower. AI-assisted-by: claude-opus-5-5
This commit is contained in:
parent
c0f0b14540
commit
2607ec89fa
@ -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 };
|
||||
|
||||
|
||||
@ -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*]]])
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user