From 112e6dedd7b10e5818d31f7ce6302f85d9168e26 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 27 Oct 2026 11:41:36 +0100 Subject: [PATCH] :recycle: Use DS icons in the copy button (#12558) * :recycle: Use DS icons in the copy button The shared copy button (Inspect attribute rows and code blocks, share link, integrations) draws its clipboard and tick icons with the DS icons at the small size, colored through the color property. The button itself stays: it wraps whole rows. The icons are the same drawings, so nothing changes on screen. A visual test covers an Inspect computed attribute row and the code block copy button, at rest, on hover and after copying. AI-assisted-by: claude-opus-5-5 * :recycle: Move copy button styles off the deprecated styles The copy button stylesheet no longer imports refactor/: the button-style, flex-center and copy-wrapper-base mixins are inlined with DS values, the tertiary button and icon color aliases resolve to their DS tokens, and physical properties become logical ones. No visual change (whole viewer visual spec compared; the code tab syntax highlighting loads late on a cold first run, with either stylesheet). AI-assisted-by: claude-opus-5-5 --- .../ui/visual-specs/visual-viewer.spec.js | 47 ++++++++ .../app/main/ui/components/copy_button.cljs | 8 +- .../app/main/ui/components/copy_button.scss | 102 +++++++++--------- 3 files changed, 105 insertions(+), 52 deletions(-) diff --git a/frontend/playwright/ui/visual-specs/visual-viewer.spec.js b/frontend/playwright/ui/visual-specs/visual-viewer.spec.js index 80ed3f7d52..5dac8b7377 100644 --- a/frontend/playwright/ui/visual-specs/visual-viewer.spec.js +++ b/frontend/playwright/ui/visual-specs/visual-viewer.spec.js @@ -395,3 +395,50 @@ test.describe("Interactions menu", () => { }); }); }); + +test.describe("Copy buttons", () => { + const shot = { maxDiffPixelRatio: 0, threshold: 0 }; + + const openInspectTab = async (page, name) => { + const viewerPage = new ViewerPage(page); + await viewerPage.setupLoggedInUser(); + await viewerPage.setupFileWithComments(); + await viewerPage.goToViewer({ + fileId: singleBoardFileId, + pageId: singleBoardPageId, + }); + await viewerPage.showCode(); + await page.getByRole("combobox").filter({ hasText: "Styles" }).click(); + await page.getByRole("option", { name }).click(); + }; + + test("Computed attribute rows", async ({ page }) => { + await openInspectTab(page, "Computed"); + const row = page.locator("[class*='copy-wrapper']").first(); + await expect(row).toBeVisible(); + await page.mouse.move(10, 700); + await expect(row).toHaveScreenshot(shot); + + await row.hover(); + await expect(row).toHaveScreenshot(shot); + + await row.click(); + await expect(row).toHaveAttribute("data-active", "true"); + await expect(row).toHaveScreenshot(shot); + }); + + test("Code block copy button", async ({ page }) => { + await openInspectTab(page, "Code"); + const copy = page.locator("[class*='css-copy-btn']"); + await expect(copy).toBeVisible(); + await page.mouse.move(10, 700); + await expect(copy).toHaveScreenshot(shot); + + await copy.hover(); + await expect(copy).toHaveScreenshot(shot); + + await copy.click(); + await expect(copy).toHaveAttribute("data-active", "true"); + await expect(copy).toHaveScreenshot(shot); + }); +}); diff --git a/frontend/src/app/main/ui/components/copy_button.cljs b/frontend/src/app/main/ui/components/copy_button.cljs index b3d5c0f1f6..a4db5ddc65 100644 --- a/frontend/src/app/main/ui/components/copy_button.cljs +++ b/frontend/src/app/main/ui/components/copy_button.cljs @@ -9,7 +9,7 @@ (:require [app.common.data.macros :as dm] [app.main.data.event :as-alias ev] - [app.main.ui.icons :as deprecated-icon] + [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.util.clipboard :as clipboard] [app.util.dom :as dom] [app.util.i18n :refer [tr]] @@ -43,6 +43,6 @@ :on-click on-click} children [:span {:class (stl/css :icon-btn)} - (if active? - deprecated-icon/tick - deprecated-icon/clipboard)]])) + [:> icon* {:icon-id (if active? i/tick i/clipboard) + :size "s" + :class (stl/css :icon)}]]])) diff --git a/frontend/src/app/main/ui/components/copy_button.scss b/frontend/src/app/main/ui/components/copy_button.scss index 3173698c92..8ae7cbf664 100644 --- a/frontend/src/app/main/ui/components/copy_button.scss +++ b/frontend/src/app/main/ui/components/copy_button.scss @@ -4,89 +4,95 @@ // // 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 *; .copy-button { - @include deprecated.button-style; - - width: 100%; - height: deprecated.$s-32; - border: deprecated.$s-1 solid transparent; - border-radius: deprecated.$br-8; + border: $b-1 solid transparent; + background: none; + cursor: pointer; + inline-size: 100%; + block-size: $sz-32; + border-radius: $br-8; background-color: transparent; box-sizing: border-box; .icon-btn { - @include deprecated.flex-center; + display: flex; + justify-content: center; + align-items: center; + block-size: $sz-32; + min-inline-size: $sz-28; + inline-size: $sz-28; - height: deprecated.$s-32; - min-width: deprecated.$s-28; - width: deprecated.$s-28; - - svg { - @extend %button-icon-small; - - stroke: var(--icon-foreground); + .icon { + flex-shrink: 0; + stroke-width: 1.33px; + color: var(--color-foreground-secondary); } } &:hover { background-color: var(--color-background-tertiary); color: var(--color-foreground-primary); - border: deprecated.$s-1 solid var(--color-background-tertiary); + border: $b-1 solid var(--color-background-tertiary); - .icon-btn { - svg { - stroke: var(--button-tertiary-foreground-color-active); - } + .icon-btn .icon { + color: var(--color-accent-primary); } } &:focus, &:focus-visible { outline: none; - border: deprecated.$s-1 solid var(--button-tertiary-border-color-focus); + border: $b-1 solid var(--color-accent-primary); background-color: transparent; - color: var(--button-tertiary-foreground-color-focus); + color: var(--color-foreground-primary); - .icon-btn svg { - stroke: var(--button-tertiary-foreground-color-active); + .icon-btn .icon { + color: var(--color-accent-primary); } } } .copy-wrapper { - @include deprecated.button-style; - @include deprecated.copy-wrapper-base; - - width: 100%; - height: fit-content; - text-align: left; - border: deprecated.$s-1 solid transparent; + position: relative; + min-block-size: $sz-32; + inline-size: 100%; + block-size: fit-content; + padding: px2rem(7) 0 px2rem(7) px2rem(7); + border: $b-1 solid transparent; + border-radius: $br-8; + background: none; + box-sizing: border-box; + cursor: pointer; + text-align: start; .icon-btn { - @include deprecated.flex-center; - + display: flex; + justify-content: center; + align-items: center; position: absolute; - top: 0; - right: 0; - height: deprecated.$s-32; - width: deprecated.$s-28; + inset-block-start: 0; + inset-inline-end: 0; + block-size: $sz-32; + inline-size: $sz-28; - svg { - @extend %button-icon-small; - - stroke: var(--button-tertiary-foreground-color-focus); + .icon { + flex-shrink: 0; + stroke-width: 1.33px; + color: var(--color-foreground-primary); display: none; } } &:hover { - background-color: var(--button-tertiary-background-color-focus); - color: var(--button-tertiary-foreground-color-focus); - border: deprecated.$s-1 solid var(--button-tertiary-background-color-focus); + background-color: var(--color-background-tertiary); + color: var(--color-foreground-primary); + border: $b-1 solid var(--color-background-tertiary); - .icon-btn svg { + .icon-btn .icon { display: flex; } } @@ -94,8 +100,8 @@ &:focus, &:focus-visible { outline: none; - border: deprecated.$s-1 solid var(--button-tertiary-border-color-focus); + border: $b-1 solid var(--color-accent-primary); background-color: transparent; - color: var(--button-tertiary-foreground-color-focus); + color: var(--color-foreground-primary); } }