From 8800ff4bc9b1179650399cfa3b6c90157e4265cd Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 27 Oct 2026 11:41:23 +0100 Subject: [PATCH] :recycle: Use DS buttons and icons in the assets tab (#12559) * :recycle: Use DS buttons and icons in the assets tab - "Add library" and "Manage library" become DS primary and secondary buttons, full width with the label centered; they keep their test id. - The filter button stays a raw segment of the search bar; its legacy icon becomes the DS filter icon. Its opened state comes before the focus and hover rules, as the legacy placeholder did, so the focus border still shows. - The create/rename group modal gets a ghost DS icon button named "Close", placed on the tooltip wrapper, and DS cancel (secondary) and create/rename (primary) buttons instead of raw inputs. A visual test covers the add and manage library buttons, the filter at rest, on hover and opened, and the create group modal. AI-assisted-by: claude-opus-5-5 * :recycle: Move assets tab styles off the deprecated styles assets.scss no longer imports refactor/: its mixins, sizes, spacing and input / button icon color aliases resolve to DS values and tokens, with logical properties. The unused .sections-container, .section-item and .section-btn rules are deleted (the filter menu is the shared context menu). assets/groups.scss does the same and keeps refactor/ only for the modal placeholders (%modal-*) and %input-with-label. Its legacy uppercase title becomes the DS headline-small typography, which differs only in its size (12px instead of 11px): the group modal grows 2px. AI-assisted-by: claude-opus-5-5 --- .../ui/visual-specs/workspace.spec.js | 55 +++++++ .../app/main/ui/workspace/sidebar/assets.cljs | 22 ++- .../app/main/ui/workspace/sidebar/assets.scss | 149 +++++++----------- .../ui/workspace/sidebar/assets/groups.cljs | 30 ++-- .../ui/workspace/sidebar/assets/groups.scss | 49 +++--- 5 files changed, 159 insertions(+), 146 deletions(-) diff --git a/frontend/playwright/ui/visual-specs/workspace.spec.js b/frontend/playwright/ui/visual-specs/workspace.spec.js index 91dc893850..a9a4ef2bf3 100644 --- a/frontend/playwright/ui/visual-specs/workspace.spec.js +++ b/frontend/playwright/ui/visual-specs/workspace.spec.js @@ -1106,3 +1106,58 @@ test.describe("Unpublish library modal", () => { await expect(modal).toHaveScreenshot(shot); }); }); + +test.describe("Assets tab controls", () => { + const shot = { maxDiffPixelRatio: 0, threshold: 0 }; + + test("Add library button", async ({ page }) => { + const workspace = new WorkspacePage(page); + await workspace.setupEmptyFile(); + await workspace.goToWorkspace(); + await workspace.clickAssets(); + + const add = workspace.sidebar.getByTestId("libraries"); + await expect(add).toBeVisible(); + await page.mouse.move(640, 600); + await expect(add).toHaveScreenshot(shot); + await add.hover(); + await expect(add).toHaveScreenshot(shot); + }); + + test("Libraries, filter and group modal", async ({ page }) => { + const workspace = new WorkspacePage(page); + const { fileId, pageId } = await setupFileWithAssets(workspace); + await workspace.goToWorkspace({ fileId, pageId }); + await workspace.clickAssets(); + + const header = workspace.sidebar + .getByTestId("libraries") + .locator("xpath=.."); + await expect(workspace.sidebar.getByTestId("libraries")).toBeVisible(); + await page.mouse.move(640, 600); + await expect(header).toHaveScreenshot(shot); + + await workspace.sidebar.getByTestId("libraries").hover(); + await expect(header).toHaveScreenshot(shot); + + const filter = workspace.sidebar.getByTitle("Filter"); + await filter.hover(); + await expect(header).toHaveScreenshot(shot); + + await filter.click(); + await page.mouse.move(640, 600); + await expect(header).toHaveScreenshot(shot); + await page.keyboard.press("Escape"); + + await workspace.sidebar.getByRole("button", { name: "Colors" }).click(); + await workspace.sidebar + .locator("[class*='colors'] [class*='asset-list-item']") + .first() + .click({ button: "right" }); + await page.getByText("Group", { exact: true }).click(); + const modal = page.locator("[class*='groups__modal-container']"); + await expect(modal).toBeVisible(); + await page.mouse.move(10, 700); + await expect(modal).toHaveScreenshot(shot); + }); +}); diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 9650b235f3..ff5b91e7ef 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -18,8 +18,9 @@ [app.main.ui.components.context-menu-a11y :refer [context-menu*]] [app.main.ui.components.search-bar :refer [search-bar*]] [app.main.ui.context :as ctx] + [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] - [app.main.ui.icons :as deprecated-icon] + [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.workspace.sidebar.assets.common :as cmm] [app.main.ui.workspace.sidebar.assets.file-library :refer [file-library*]] [app.main.ui.workspace.sidebar.scroll :as sc] @@ -201,14 +202,18 @@ [:div {:class (stl/css :assets-header)} (when-not ^boolean read-only? (if (and (= num-libs 1) (empty? components) (not shared?)) - [:button {:class (stl/css :add-library-button) - :on-click show-libraries-dialog - :data-testid "libraries"} + [:> button* {:variant "primary" + :type "button" + :class (stl/css :libraries-button) + :on-click show-libraries-dialog + :data-testid "libraries"} (tr "workspace.assets.add-library")] - [:button {:class (stl/css :libraries-button) - :on-click show-libraries-dialog - :data-testid "libraries"} + [:> button* {:variant "secondary" + :type "button" + :class (stl/css :libraries-button) + :on-click show-libraries-dialog + :data-testid "libraries"} (tr "workspace.assets.manage-library")])) @@ -221,7 +226,8 @@ :title (tr "workspace.assets.filter") :class (stl/css-case :section-button true :opened menu-open?)} - deprecated-icon/filter-icon]] + [:> icon* {:icon-id i/filter + :class (stl/css :section-icon)}]]] [:> context-menu* {:on-close on-menu-close diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.scss b/frontend/src/app/main/ui/workspace/sidebar/assets.scss index 716d0682b8..afccd3404d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.scss @@ -4,141 +4,98 @@ // // Copyright (c) KALEIDOS SUBSIDIARY SL -@use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; +@use "ds/_utils.scss" as *; .assets-bar { display: grid; grid-auto-rows: max-content; // TODO: ugly hack :( Fix this! we shouldn't be hardcoding this height - height: calc(100vh - deprecated.$s-92); + block-size: calc(100vh - #{px2rem(92)}); scrollbar-gutter: stable; overflow-y: auto; - padding-top: deprecated.$s-8; + padding-block-start: var(--sp-s); } +// A DS button keeps its label at the start when it grows. .libraries-button { - @extend %button-secondary; - @include deprecated.uppercase-title-typography; - - gap: deprecated.$s-2; - height: deprecated.$s-32; - width: 100%; - margin-bottom: deprecated.$s-4; - border-radius: deprecated.$s-8; - - &:hover { - background-color: var(--button-secondary-background-color-hover); - color: var(--button-secondary-foreground-color-hover); - border: deprecated.$s-1 solid var(--button-secondary-border-color-hover); - } - - &:focus { - background-color: var(--button-secondary-background-color-focus); - color: var(--button-secondary-foreground-color-focus); - border: deprecated.$s-1 solid var(--button-secondary-border-color-focus); - } -} - -.add-library-button { - @extend %button-primary; - @include deprecated.uppercase-title-typography; - - gap: deprecated.$s-2; - height: deprecated.$s-32; - width: 100%; - margin-bottom: deprecated.$s-4; - border-radius: deprecated.$s-8; + inline-size: 100%; + margin-block-end: var(--sp-xs); + justify-content: center; } .section-button { - @include deprecated.flex-center; - @include deprecated.button-style; - - height: deprecated.$s-32; - width: deprecated.$s-32; + display: flex; + justify-content: center; + align-items: center; + background: none; + cursor: pointer; + block-size: $sz-32; + inline-size: $sz-32; margin: 0; - border: deprecated.$s-1 solid var(--input-border-color-rest); - border-radius: deprecated.$br-8 deprecated.$br-2 deprecated.$br-2 deprecated.$br-8; - background-color: var(--input-background-color-rest); + border: $b-1 solid var(--color-background-tertiary); + border-radius: $br-8 px2rem(2) px2rem(2) $br-8; + background-color: var(--color-background-tertiary); - svg { - height: deprecated.$s-16; - width: deprecated.$s-16; - stroke: var(--icon-foreground); + .section-icon { + flex-shrink: 0; + stroke-width: 1px; + color: var(--color-foreground-secondary); + } + + // Before :focus and :hover, as the legacy placeholder was: the focus + // border wins over the opened state. + &.opened { + outline: none; + border-color: var(--color-background-quaternary); + background-color: var(--color-background-quaternary); + color: var(--color-accent-primary); + + .section-icon { + color: var(--color-accent-primary); + } } &:focus { - border: deprecated.$s-1 solid var(--input-border-color-focus); + border: $b-1 solid var(--color-accent-primary); outline: 0; - background-color: var(--input-background-color-focus); - color: var(--input-foreground-color-focus); + background-color: var(--color-background-tertiary); + color: var(--color-foreground-primary); - svg { - background-color: var(--input-background-color-focus); + .section-icon { + background-color: var(--color-background-tertiary); } } &:hover { - border: deprecated.$s-1 solid var(--input-border-color-hover); - background-color: var(--input-background-color-hover); + border: $b-1 solid var(--color-background-quaternary); + background-color: var(--color-background-quaternary); - svg { - background-color: var(--input-background-color-hover); - stroke: var(--button-foreground-hover); + .section-icon { + background-color: var(--color-background-quaternary); + color: var(--color-accent-primary); } &:focus { - border: deprecated.$s-1 solid var(--input-border-color-focus); + border: $b-1 solid var(--color-accent-primary); outline: 0; - background-color: var(--input-background-color-focus); - color: var(--input-foreground-color-focus); + background-color: var(--color-background-tertiary); + color: var(--color-foreground-primary); - svg { - background-color: var(--input-background-color-focus); + .section-icon { + background-color: var(--color-background-tertiary); } } } - - &.opened { - @extend %button-icon-selected; - } -} - -.sections-container { - @include deprecated.menu-shadow; - @include deprecated.flex-column; - - position: absolute; - top: deprecated.$s-84; - left: deprecated.$s-12; - width: deprecated.$s-192; - padding: deprecated.$s-4; - border-radius: deprecated.$br-8; - background-color: var(--menu-background-color); - z-index: deprecated.$z-index-2; -} - -.section-item { - @include deprecated.body-small-typography; - - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: deprecated.$s-6; - border-radius: deprecated.$br-8; -} - -.section-btn { - @include deprecated.button-style; } .assets-header { - padding: 0 0 deprecated.$s-24 deprecated.$s-12; + padding: 0 0 var(--sp-xxl) var(--sp-m); } .search-wrapper { display: flex; - gap: deprecated.$s-4; + gap: var(--sp-xs); } diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs index b072139f1e..745491c625 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs @@ -14,9 +14,9 @@ [app.main.store :as st] [app.main.ui.components.forms :as fm] [app.main.ui.components.title-bar :refer [title-bar*]] + [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.foundations.assets.icon :as i] - [app.main.ui.icons :as deprecated-icon] [app.main.ui.workspace.sidebar.assets.common :as cmm] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] @@ -159,8 +159,11 @@ (if create? (tr "workspace.assets.create-group") (tr "workspace.assets.rename-group"))] - [:button {:class (stl/css :modal-close-btn) - :on-click modal/hide!} deprecated-icon/close]] + [:> icon-button* {:variant "ghost" + :tooltip-class (stl/css :modal-close-btn) + :aria-label (tr "labels.close") + :icon i/close + :on-click modal/hide!}]] [:div {:class (stl/css :modal-content)} [:& fm/form {:form form :on-submit on-accept} @@ -172,16 +175,13 @@ [:div {:class (stl/css :modal-footer)} [:div {:class (stl/css :action-buttons)} - [:input - {:class (stl/css :cancel-button) - :type "button" - :value (tr "labels.cancel") - :on-click modal/hide!}] + [:> button* {:variant "secondary" + :type "button" + :on-click modal/hide!} + (tr "labels.cancel")] - [:input - {:type "button" - :class (stl/css-case :accept-btn true - :global/disabled (not (:valid @form))) - :disabled (not (:valid @form)) - :value (if create? (tr "labels.create") (tr "labels.rename")) - :on-click on-accept}]]]]])) + [:> button* {:variant "primary" + :type "button" + :disabled (not (:valid @form)) + :on-click on-accept} + (if create? (tr "labels.create") (tr "labels.rename"))]]]]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.scss b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.scss index 003db6e756..2e752f499d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.scss @@ -4,10 +4,14 @@ // // Copyright (c) KALEIDOS SUBSIDIARY SL +// refactor/ stays for the modal placeholders (%modal-*), which move with +// the modals refactor, and the input placeholder (%input-with-label). @use "refactor/common-refactor.scss" as deprecated; +@use "ds/mixins.scss" as *; +@use "ds/typography.scss" as *; .group-title-wrapper { - width: 100%; + inline-size: 100%; display: flex; justify-content: space-between; @@ -30,21 +34,21 @@ } .group-title { - padding-left: deprecated.$s-4; + padding-inline-start: var(--sp-xs); } .pre-path { - margin-left: deprecated.$s-2; + margin-inline-start: var(--sp-xxs); text-transform: initial; - color: var(--title-foreground-color); + color: var(--color-foreground-secondary); } .path { - @include deprecated.text-ellipsis; + @include text-ellipsis; - margin-left: deprecated.$s-2; + margin-inline-start: var(--sp-xxs); text-transform: initial; - color: var(--title-foreground-color-hover); + color: var(--color-foreground-primary); } .modal-overlay { @@ -56,44 +60,35 @@ } .modal-header { - margin-bottom: deprecated.$s-24; + margin-block-end: var(--sp-xxl); } .modal-title { - @include deprecated.uppercase-title-typography; + @include use-typography("headline-small"); - color: var(--modal-title-foreground-color); + color: var(--color-foreground-primary); } +// Tooltip wrapper of the close button: it places it in the corner. .modal-close-btn { - @extend %modal-close-btn-base; + position: absolute; + inset-block-start: var(--sp-s); + inset-inline-end: var(--sp-xs); } .modal-content { - @include deprecated.body-small-typography; + @include use-typography("body-small"); - margin-bottom: deprecated.$s-24; + margin-block-end: var(--sp-xxl); } .input-wrapper { @extend %input-with-label; - @include deprecated.body-small-typography; + @include use-typography("body-small"); - margin-bottom: deprecated.$s-8; + margin-block-end: var(--sp-s); } .action-buttons { @extend %modal-action-btns; } - -.cancel-button { - @extend %modal-cancel-btn; -} - -.accept-btn { - @extend %modal-accept-btn; - - &.danger { - @extend %modal-danger-btn; - } -}