From 33d304c0afb9507c541f8fa86963c051cdf73273 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 27 Oct 2026 11:41:19 +0100 Subject: [PATCH] :recycle: Use DS icons in the dashboard sidebar (#12534) * :recycle: Use DS icons in the dashboard sidebar The search, clear search, pinned projects placeholder and logout icons of the dashboard sidebar use the DS icons, and they get their color from the color property. The two search buttons had untranslated test names as their labels ("dashboard-search"); they are now named "Search" and "Clear search". The pin keeps its place: the DS small icon draws 12px inside a 16px box, so its margins shrink by 2px. New key: dashboard.clear-search (en, es). The dashboard page object looks the search button up by its new name. A visual test covers the search box with and without a term, the pinned projects placeholder and the profile menu. AI-assisted-by: claude-opus-5-5 * :recycle: Move dashboard sidebar styles off the deprecated styles Legacy color aliases resolve to their DS tokens, deprecated mixins, sizes and font variables are inlined with DS values, physical properties become logical ones and the DS typography module is used without a namespace. An invalid max-width with two values, which the browser ignored, is gone. Two rules that match nothing are commented out with a TODO: the profile menu arrow svg rule (the arrow is the DS icon svg itself) and the upgrade plan section, which sidebar.cljs does not use. refactor/ stays only for the profile menu placeholders (%menu-dropdown, %menu-item-base) and the shared dashboard header styles (common-dashboard). No visual change. AI-assisted-by: claude-opus-5-5 * :fire: Remove unused styles from the dashboard sidebar The SCSS refactor of the dashboard sidebar left rules commented out with a TODO because the module does not use them, or because they target an svg inside a DS icon that is the svg itself. CSS modules scope the classes to this module and no other source uses them, so they go: .profile-dropdown-item .open-arrow svg, .upgrade-plan-section, .penpot-free, .no-limits, .power-up. No visual change. AI-assisted-by: claude-opus-5-5 --- frontend/playwright/ui/pages/DashboardPage.js | 5 +- .../ui/visual-specs/visual-dashboard.spec.js | 28 +++ .../src/app/main/ui/dashboard/sidebar.cljs | 37 ++-- .../src/app/main/ui/dashboard/sidebar.scss | 207 ++++++++---------- frontend/translations/en.po | 4 + frontend/translations/es.po | 4 + 6 files changed, 145 insertions(+), 140 deletions(-) diff --git a/frontend/playwright/ui/pages/DashboardPage.js b/frontend/playwright/ui/pages/DashboardPage.js index 8532455c88..8d0f7df082 100644 --- a/frontend/playwright/ui/pages/DashboardPage.js +++ b/frontend/playwright/ui/pages/DashboardPage.js @@ -82,7 +82,10 @@ export class DashboardPage extends BaseWebSocketPage { this.fontsLink = this.sidebar.getByText("Fonts"); this.librariesLink = this.sidebar.getByText("Libraries"); - this.searchButton = page.getByRole("button", { name: "dashboard-search" }); + this.searchButton = page.getByRole("button", { + name: "Search", + exact: true, + }); this.searchInput = page.getByPlaceholder("Search…"); this.teamDropdown = this.sidebar.getByRole("button", { diff --git a/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js b/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js index 5b271306cb..320d855e64 100644 --- a/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js +++ b/frontend/playwright/ui/visual-specs/visual-dashboard.spec.js @@ -486,3 +486,31 @@ test.describe("Team modals controls", () => { await expect(modal).toHaveScreenshot(shot); }); }); + +test.describe("Dashboard sidebar controls", () => { + const shot = { maxDiffPixelRatio: 0, threshold: 0 }; + + test("Search box, pinned placeholder and profile menu", async ({ page }) => { + const dashboardPage = new DashboardPage(page); + await dashboardPage.goToDashboard(); + + const search = page.locator("[class*='sidebar-search']"); + await expect(search).toBeVisible(); + await expect(search).toHaveScreenshot(shot); + + await expect(page.getByTestId("pinned-projects")).toHaveScreenshot(shot); + + // A search term swaps the search icon for the clear button. + await page.locator("#search-input").fill("test"); + await expect(page).toHaveURL(/search/); + const clear = search.locator("button"); + await clear.hover(); + await expect(search).toHaveScreenshot(shot); + + await page.getByTestId("profile-btn").click(); + await expect(page.getByTestId("logout-profile-opt")).toBeVisible(); + await expect( + page.getByTestId("logout-profile-opt").locator("xpath=.."), + ).toHaveScreenshot(shot); + }); +}); diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index b528a5c788..d9514fb927 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -37,7 +37,6 @@ [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.layout.menu :refer [context-menu*]] [app.main.ui.hooks :refer [use-focus-timer-ref]] - [app.main.ui.icons :as deprecated-icon] [app.main.ui.nitrate.nitrate-form] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] @@ -48,18 +47,6 @@ [goog.functions :as f] [rumext.v2 :as mf])) -(def ^:private clear-search-icon - (deprecated-icon/icon-xref :delete-text (stl/css :clear-search-icon))) - -(def ^:private search-icon - (deprecated-icon/icon-xref :search (stl/css :search-icon))) - -(def ^:private pin-icon - (deprecated-icon/icon-xref :pin (stl/css :pin-icon))) - -(def ^:private exit-icon - (deprecated-icon/icon-xref :exit (stl/css :exit-icon))) - (defn schedule-focus-by-id! [ref element-id] (when-let [h (mf/ref-val ref)] @@ -101,7 +88,6 @@ (schedule-focus-by-id! focus-timer-ref (str project-id)) (st/emit! (dcm/go-to-dashboard-files :project-id project-id))))) - on-edit-open (mf/use-fn #(swap! local* assoc :edition? true)) @@ -239,15 +225,19 @@ (if (or @focused? (seq search-term)) [:button {:class (stl/css :search-btn :clear-search-btn) :tab-index "0" - :aria-label "dashboard-clear-search" + :aria-label (tr "dashboard.clear-search") :on-click on-clear-click :on-key-down handle-clear-search} - clear-search-icon] + [:> icon* {:icon-id i/delete-text + :class (stl/css :search-icon) + :aria-hidden true}]] [:button {:class (stl/css :search-btn) - :aria-label "dashboard-search" + :aria-label (tr "labels.search") :on-click on-clear-click} - search-icon])])) + [:> icon* {:icon-id i/search + :class (stl/css :search-icon) + :aria-hidden true}]])])) (mf/defc sidebar-content* {::mf/private true} @@ -364,7 +354,6 @@ :keyboard-action go-drafts-with-key} [:span {:class (stl/css :element-title)} (tr "labels.drafts")]]]]] - [:div {:class (stl/css :sidebar-content-section)} [:div {:class (stl/css :sidebar-section-title)} (tr "labels.sources")] @@ -384,7 +373,6 @@ :keyboard-action go-libs-with-key} [:span {:class (stl/css :element-title)} (tr "labels.shared-libraries")]]]]] - [:div {:class (stl/css :sidebar-content-section) :data-testid "pinned-projects"} [:div {:class (stl/css :sidebar-section-title)} @@ -399,7 +387,9 @@ :team-id (:id team) :is-selected (= (:id item) (:id project))}])] [:div {:class (stl/css :sidebar-empty-placeholder)} - pin-icon + [:> icon* {:icon-id i/pin + :size "s" + :class (stl/css :pin-icon)}] [:span {:class (stl/css :empty-text)} (tr "dashboard.no-projects-placeholder")]])]] [:div {:class (stl/css-case :separator true :overflow-separator overflow?)}]]])) @@ -673,7 +663,6 @@ [:> dashboard-cta* {:profile profile}] [:> subscription-sidebar* {:profile profile}]))) - (when (and team profile) [:& comments-section {:profile profile @@ -704,7 +693,6 @@ [:li {:class (stl/css :profile-separator)}] - [:> dropdown-menu-item* {:class (stl/css-case :profile-dropdown-item true) :on-click on-menu-click :on-key-down (fn [event] @@ -749,7 +737,8 @@ [:> dropdown-menu-item* {:class (stl/css :profile-dropdown-item :item-with-icon) :on-click handle-logout-click :data-testid "logout-profile-opt"} - exit-icon + [:> icon* {:icon-id i/exit + :class (stl/css :exit-icon)}] (tr "labels.logout")]] (when (and team profile) diff --git a/frontend/src/app/main/ui/dashboard/sidebar.scss b/frontend/src/app/main/ui/dashboard/sidebar.scss index 4bbd6d243b..424f56427b 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.scss +++ b/frontend/src/app/main/ui/dashboard/sidebar.scss @@ -4,14 +4,21 @@ // // Copyright (c) KALEIDOS SUBSIDIARY SL -@use "ds/typography.scss" as t; +@use "ds/typography.scss" as *; @use "ds/colors.scss" as *; @use "ds/spacing.scss" as *; @use "ds/_borders.scss" as *; @use "ds/_sizes.scss" as *; @use "ds/_utils.scss" as *; @use "ds/z-index.scss" as *; +@use "ds/mixins.scss" as *; + +// The shared dashboard header styles move in their own change: they are +// used by ten dashboard modules. @use "common/refactor/common-dashboard"; + +// refactor/ stays for the menu placeholders (%menu-dropdown, +// %menu-item-base) of the profile menu and its submenus. @use "common/refactor/common-refactor.scss" as deprecated; // SIDEBAR COMPONENT @@ -20,19 +27,19 @@ grid-row: 1 / span 2; grid-column: 1 / span 2; grid-template-rows: auto 1fr auto; - height: 100%; - width: 100%; + block-size: 100%; + inline-size: 100%; margin: 0 var(--sp-l) 0 0; - border-right: $b-1 solid var(--panel-border-color); - background-color: var(--panel-background-color); + border-inline-end: $b-1 solid var(--color-background-quaternary); + background-color: var(--color-background-primary); } // SIDEBAR CONTENT COMPONENT .sidebar-content-wrapper { display: flex; flex-direction: column; - min-height: 0; - height: 100%; + min-block-size: 0; + block-size: 100%; overflow: hidden; } @@ -41,7 +48,7 @@ grid-template-rows: auto auto auto auto 1fr; gap: var(--sp-xxl); flex: 1; - min-height: 0; + min-block-size: 0; padding: 0; overflow: hidden auto; } @@ -52,17 +59,17 @@ } .separator { - height: var(--sp-xxs); - width: 94%; - margin-left: 3%; + block-size: var(--sp-xxs); + inline-size: 94%; + margin-inline-start: 3%; } .overflow-separator { - border-bottom: $b-1 solid var(--color-background-quaternary); + border-block-end: $b-1 solid var(--color-background-quaternary); } .sidebar-section-title { - @include t.use-typography("headline-small"); + @include use-typography("headline-small"); padding: 0 var(--sp-s) var(--sp-s) var(--sp-xxl); color: var(--color-foreground-secondary); @@ -82,31 +89,33 @@ cursor: pointer; &:hover { - background-color: var(--sidebar-element-background-color-hover); + background-color: var(--color-background-secondary); + // The item labels have no class. span { - color: var(--sidebar-element-foreground-color-hover); + color: var(--color-accent-primary); } } &.current { - background-color: var(--sidebar-element-background-color-selected); + background-color: var(--color-background-quaternary); .element-title { - color: var(--sidebar-element-foreground-color-selected); + color: var(--color-accent-primary); } } } +// The icon has no class. .recent-projects svg { - stroke: var(--main-icon-foreground); + stroke: var(--color-foreground-primary); } .sidebar-link { display: block; padding: var(--sp-s) var(--sp-s) var(--sp-s) var(--sp-xxl); - font-weight: deprecated.$fw400; - width: 100%; + font-weight: 400; + inline-size: 100%; &:hover { text-decoration: none; @@ -118,31 +127,33 @@ } .element-title { - @include deprecated.text-ellipsis; + @include text-ellipsis; - width: deprecated.$s-256; + inline-size: px2rem(256); color: var(--color-foreground-primary); - font-size: deprecated.$fs-14; + font-size: px2rem(14); } // Pinned projects .sidebar-empty-placeholder { padding: var(--sp-m); - color: var(--empty-message-foreground-color); + color: var(--color-foreground-secondary); display: flex; align-items: center; } +// The DS small icon draws 12px inside a 16px box: 2px less margin on each +// side keeps the drawing and the text where they were. .pin-icon { - @extend %button-icon-small; - - stroke: var(--icon-foreground); - margin: 0 var(--sp-m); + flex-shrink: 0; + stroke-width: 1.33px; + color: var(--color-foreground-secondary); + margin: 0 px2rem(10); } .empty-text { - font-size: deprecated.$fs-12; + font-size: px2rem(12); } // Search @@ -155,55 +166,58 @@ border: $b-1 solid transparent; margin: 0 var(--sp-l); border-radius: $br-8; - background-color: var(--search-bar-input-background-color); + background-color: var(--color-background-tertiary); } .input-text { - @include t.use-typography("title-small"); + @include use-typography("title-small"); - height: $sz-40; - width: 100%; + block-size: $sz-40; + inline-size: 100%; padding: $sz-6 var(--sp-m); margin: 0; border: transparent; border-radius: $br-8; background: transparent; - color: var(--search-bar-foreground-color); + color: var(--color-foreground-primary); &:focus, &:focus-within, &:focus-visible { outline: none; - border: $b-1 solid var(--search-bar-input-border-color-focus); + border: $b-1 solid var(--color-accent-primary); } ::placeholder { - color: var(--search-bar-placeholder-foreground-color); + color: var(--color-foreground-secondary); } } .search-btn { - @include deprecated.button-style; - @include deprecated.flex-center; + border: none; + background: none; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + --sidebar-search-foreground-color: var(--color-foreground-secondary); + + color: var(--sidebar-search-foreground-color); position: absolute; - right: 0; - height: var(--sp-xxl); - width: var(--sp-xxxl); + inset-inline-end: 0; + block-size: var(--sp-xxl); + inline-size: var(--sp-xxxl); padding: 0 var(--sp-s); } -.search-icon, -.clear-search-btn { - @extend %button-icon; - - --sidebar-search-foreground-color: var(--search-bar-icon-foreground-color); - - stroke: var(--sidebar-search-foreground-color); +.search-icon { + flex-shrink: 0; + stroke-width: 1px; } .clear-search-btn:hover { - --sidebar-search-foreground-color: var(--search-bar-icon-foreground-color-hover); + --sidebar-search-foreground-color: var(--color-accent-primary); } // Profile @@ -212,33 +226,32 @@ display: grid; grid-template-columns: 1fr auto; padding: var(--sp-m) var(--sp-l); - border-top: $b-1 solid var(--panel-border-color); - background-color: var(--profile-section-background-color); + border-block-start: $b-1 solid var(--color-background-quaternary); + background-color: var(--color-background-tertiary); cursor: pointer; } .profile { - @include deprecated.button-style; - + border: none; + background: none; display: grid; grid-template-columns: auto 1fr; gap: var(--sp-s); cursor: pointer; - text-align: left; + text-align: start; } .profile-fullname { - @include t.use-typography("title-small"); - @include deprecated.text-ellipsis; + @include use-typography("title-small"); + @include text-ellipsis; align-self: center; - max-width: var(--sp-l) 0; - color: var(--profile-foreground-color); + color: var(--color-foreground-primary); } .profile-img { - height: $sz-40; - width: $sz-40; + block-size: $sz-40; + inline-size: $sz-40; border-radius: $br-circle; } @@ -247,14 +260,14 @@ inset-inline-start: var(--sp-s); inset-block-end: px2rem(72); // 72 is the height of the profile button - min-width: calc(100% - var(--sp-s)); + min-inline-size: calc(100% - var(--sp-s)); // TODO ADD animation fadeInUp } .profile-dropdown-item { @extend %menu-item-base; - @include t.use-typography("body-medium"); + @include use-typography("body-medium"); block-size: $sz-40; margin-block-end: var(--sp-xs); @@ -262,29 +275,25 @@ padding-inline-start: var(--sp-l); &:hover { - color: var(--menu-foreground-color-hover); + color: var(--color-foreground-primary); .open-arrow { - --icon-stroke-color: var(--menu-foreground-color-hover); + --icon-stroke-color: var(--color-foreground-primary); } } } .profile-dropdown-item .open-arrow { - @include deprecated.flex-center; -} - -.profile-dropdown-item .open-arrow svg { - @extend %button-icon; - - stroke: var(--icon-foreground); + display: flex; + justify-content: center; + align-items: center; } .sub-menu { @extend %menu-dropdown; - inset-inline-start: calc(deprecated.$s-292 + var(--sp-s)); - min-width: deprecated.$s-192; + inset-inline-start: calc(#{px2rem(292)} + var(--sp-s)); + min-inline-size: $sz-192; } // Each submenu is positioned via its bottom edge; the visual top lands @@ -293,11 +302,11 @@ // its top edge sits one row above Community — aligning with the // "Help & Learning" trigger row in the parent menu. .sub-menu.help-learning { - inset-block-end: deprecated.$s-120; + inset-block-end: $sz-120; } .sub-menu.community { - inset-block-end: deprecated.$s-120; + inset-block-end: $sz-120; } .sub-menu.about { @@ -306,7 +315,7 @@ .submenu-item { @extend %menu-item-base; - @include t.use-typography("body-medium"); + @include use-typography("body-medium"); block-size: $sz-40; margin-block-end: var(--sp-xs); @@ -314,7 +323,7 @@ padding-inline: var(--sp-l); &:hover { - color: var(--menu-foreground-color-hover); + color: var(--color-foreground-primary); } &.checking { @@ -334,7 +343,7 @@ } .submenu-separator { - border-top: $b-1 solid var(--color-background-quaternary); + border-block-start: $b-1 solid var(--color-background-quaternary); margin: 0; } @@ -345,8 +354,8 @@ } .menu-version { - @include t.use-typography("code-font"); - @include deprecated.text-ellipsis; + @include use-typography("code-font"); + @include text-ellipsis; color: var(--color-foreground-secondary); margin-inline-start: var(--sp-s); @@ -354,8 +363,8 @@ } .profile-separator { - height: $sz-6; - border-top: $b-1 solid var(--color-background-quaternary); + block-size: $sz-6; + border-block-start: $b-1 solid var(--color-background-quaternary); } .item-with-icon { @@ -365,38 +374,6 @@ } .exit-icon { - @extend %button-icon; - - stroke: var(--icon-foreground); -} - -.upgrade-plan-section { - @include deprecated.button-style; - - display: flex; - justify-content: space-between; - border: $b-1 solid var(--color-background-quaternary); - border-radius: var(--sp-s); - padding: var(--sp-m); - margin: var(--sp-m); + stroke-width: 1px; color: var(--color-foreground-secondary); - cursor: pointer; -} - -.penpot-free { - @include t.use-typography("body-medium"); - - display: flex; - flex-direction: column; - text-align: left; -} - -.no-limits { - @include t.use-typography("body-small"); -} - -.power-up { - @include t.use-typography("body-small"); - - color: var(--color-accent-tertiary); } diff --git a/frontend/translations/en.po b/frontend/translations/en.po index c747726caa..57b5569932 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -356,6 +356,10 @@ msgstr "View full changelog" msgid "dashboard.check-updates.view-release-notes" msgstr "View release notes" +#: src/app/main/ui/dashboard/sidebar.cljs:228 +msgid "dashboard.clear-search" +msgstr "Clear search" + #: src/app/main/ui/dashboard/deleted.cljs:322 msgid "dashboard.clear-trash-button" msgstr "Clear trash" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index c95dafcebd..6d20e94fe9 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -362,6 +362,10 @@ msgstr "Ver changelog completo" msgid "dashboard.check-updates.view-release-notes" msgstr "Ver notas de la versión" +#: src/app/main/ui/dashboard/sidebar.cljs:228 +msgid "dashboard.clear-search" +msgstr "Borrar búsqueda" + #: src/app/main/ui/dashboard/deleted.cljs:322 msgid "dashboard.clear-trash-button" msgstr "Vaciar papelera"