♻️ Use DS buttons and icons in the assets tab (#12559)

* ♻️ 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

* ♻️ 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
This commit is contained in:
Eva Marco 2026-10-27 11:41:23 +01:00 committed by GitHub
parent 33d304c0af
commit 8800ff4bc9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 159 additions and 146 deletions

View File

@ -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);
});
});

View File

@ -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

View File

@ -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);
}

View File

@ -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"))]]]]]))

View File

@ -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;
}
}