From 1c70b0ce2d5d0c3d5f2b1b566dbbdcf8e0c72bc4 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Mon, 19 Oct 2026 15:52:48 +0200 Subject: [PATCH] :recycle: Use the DS icon in the workspace left header (#12432) * :recycle: Use the DS icon in the workspace left header The shared library badge of the left header used a deprecated icon; use the DS library icon instead. Drop the deprecated icons inside the save status dot. They rendered as 500px SVGs with no stroke, so they never showed: the dot is only its background color, and its title keeps the status text. The Penpot logo is brand art and stays. Add a visual test that opens a shared file and shows the header with the badge, then the saved status after drawing a shape. AI-assisted-by: claude-opus-5-5 * :recycle: Move workspace left header styles off the deprecated styles Inline the flex and ellipsis mixins, move spacing, sizes and borders to DS tokens and logical properties, and resolve the color aliases to their DS tokens. The status dot takes its color from a local variable. The badge icon gets its color through a class instead of an svg selector. The project name uses the DS headline-small typography: 12px instead of 11px. The file name matches title-small exactly. Left with a TODO: the rename input font size and z-index have no DS match, and the pending status orange (#f5a91b) has no DS color. left_header.scss no longer imports refactor/. AI-assisted-by: claude-opus-5-5 * :recycle: Resolve the TODOs left in the workspace left header styles The status dot used the legacy warning orange #f5a91b, which drifted from the DS one when both replaced #fe4811 a few days apart in early 2025. Use the DS --color-accent-warning (#fe9c07), the orange of every other DS warning, so the dot looks a little more orange while saving. The rename input had z-index 20, which did nothing: the input is neither positioned nor a flex or grid item. Remove it. The rename input only set its font size and got the rest from the global reset; it now uses the DS title-small typography, like the file name. Its line-height goes from normal to 1.2, so it grows from 16 to 16.8px. AI-assisted-by: claude-opus-5-5 --- .../ui/visual-specs/workspace.spec.js | 33 +++++ .../app/main/ui/workspace/left_header.cljs | 13 +- .../app/main/ui/workspace/left_header.scss | 134 ++++++++++-------- 3 files changed, 110 insertions(+), 70 deletions(-) diff --git a/frontend/playwright/ui/visual-specs/workspace.spec.js b/frontend/playwright/ui/visual-specs/workspace.spec.js index 7ae69e0520..73c5e2a524 100644 --- a/frontend/playwright/ui/visual-specs/workspace.spec.js +++ b/frontend/playwright/ui/visual-specs/workspace.spec.js @@ -320,6 +320,39 @@ test.describe("Assets tab", () => { }); }); +test.describe("Left header", () => { + test("Shows the shared badge and the save status", async ({ page }) => { + const workspace = new WorkspacePage(page); + await workspace.setupEmptyFile(); + await workspace.mockRPC(/get\-file\?/, "workspace/get-file-library.json"); + await workspace.mockRPC( + "update-file?id=*", + "workspace/update-file-create-rect.json", + ); + await workspace.goToWorkspace({ + fileId: "c1249a66-fce0-8175-8004-7433fe4be8bc", + pageId: "c1249a66-fce0-8175-8004-7433fe4be8bd", + }); + + const header = page.locator("header", { hasText: "Testing library 1" }); + await expect(header).toBeVisible(); + // The badge and the status dot are tiny: compare every pixel. + await expect(header).toHaveScreenshot("left-header-shared.png", { + maxDiffPixelRatio: 0, + threshold: 0, + }); + + await workspace.rectShapeButton.click(); + await workspace.clickWithDragViewportAt(128, 128, 200, 100); + await expect(header.getByTitle("Saved")).toBeVisible(); + await page.mouse.move(640, 600); + await expect(header).toHaveScreenshot("left-header-saved.png", { + maxDiffPixelRatio: 0, + threshold: 0, + }); + }); +}); + test.describe("Palette", () => { test("Shows the bottom palette expanded and collapsed", async ({ page }) => { const workspace = new WorkspacePage(page); diff --git a/frontend/src/app/main/ui/workspace/left_header.cljs b/frontend/src/app/main/ui/workspace/left_header.cljs index 37e6247b87..3eec46c77d 100644 --- a/frontend/src/app/main/ui/workspace/left_header.cljs +++ b/frontend/src/app/main/ui/workspace/left_header.cljs @@ -15,6 +15,7 @@ [app.main.refs :as refs] [app.main.router :as rt] [app.main.store :as st] + [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]] [app.main.ui.icons :as deprecated-icon] [app.main.ui.workspace.main-menu :as main-menu] [app.util.dom :as dom] @@ -124,17 +125,11 @@ :saved (tr "workspace.header.saved") :retrying (tr "workspace.header.retrying") :error (tr "workspace.header.save-error") - nil)} - (case persistence-status - :pending deprecated-icon/status-alert - :saving deprecated-icon/status-alert - :saved deprecated-icon/status-tick - :retrying deprecated-icon/status-alert - :error deprecated-icon/status-wrong - nil)] + nil)}] [:div {:class (stl/css :file-name-label)} file-name]])] (when ^boolean shared? - [:span {:class (stl/css :shared-badge)} deprecated-icon/library]) + [:span {:class (stl/css :shared-badge)} + [:> icon* {:icon-id i/library :class (stl/css :shared-badge-icon)}]]) [:div {:class (stl/css :menu-section)} [:> main-menu/menu* {:layout layout :file file}]]])) diff --git a/frontend/src/app/main/ui/workspace/left_header.scss b/frontend/src/app/main/ui/workspace/left_header.scss index 663c7465b2..a1542d48a9 100644 --- a/frontend/src/app/main/ui/workspace/left_header.scss +++ b/frontend/src/app/main/ui/workspace/left_header.scss @@ -4,27 +4,32 @@ // // 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 *; +@use "ds/typography.scss" as *; .workspace-header-left { display: flex; align-items: center; - padding: deprecated.$s-12 deprecated.$s-12 deprecated.$s-8 deprecated.$s-12; - min-height: deprecated.$s-52; + padding: var(--sp-m) var(--sp-m) var(--sp-s) var(--sp-m); + min-block-size: px2rem(52); } .main-icon { - @include deprecated.flex-center; - - width: deprecated.$s-32; - height: deprecated.$s-32; - min-height: deprecated.$s-32; - margin-right: deprecated.$s-4; + display: flex; + justify-content: center; + align-items: center; + inline-size: $sz-32; + block-size: $sz-32; + min-block-size: $sz-32; + margin-inline-end: var(--sp-xs); + // The Penpot logo is brand art from the legacy icon set and has no class. svg { - min-height: deprecated.$s-32; - width: deprecated.$s-32; - fill: var(--icon-foreground-hover); + min-block-size: $sz-32; + inline-size: $sz-32; + fill: var(--color-foreground-primary); } } @@ -32,49 +37,60 @@ position: relative; flex-grow: 1; flex-shrink: 1; - min-width: 0; - height: deprecated.$s-32; - min-height: deprecated.$s-32; + min-inline-size: 0; + block-size: $sz-32; + min-block-size: $sz-32; } .project-name, .file-name { - @include deprecated.uppercase-title-typography; - @include deprecated.text-ellipsis; - - height: deprecated.$s-16; - width: 100%; - padding-bottom: deprecated.$s-2; - color: var(--title-foreground-color); + display: block; + max-inline-size: 99%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + block-size: $sz-16; + inline-size: 100%; + padding-block-end: var(--sp-xxs); cursor: pointer; } -.file-name { - @include deprecated.small-title-typography; +.project-name { + @include use-typography("headline-small"); - text-transform: none; - color: var(--title-foreground-color-hover); + color: var(--color-foreground-secondary); +} + +.file-name { + @include use-typography("title-small"); + + color: var(--color-foreground-primary); align-items: center; display: flex; flex-direction: row; } .file-name-label { - @include deprecated.text-ellipsis; + display: block; + max-inline-size: 99%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .file-name-input { - @include deprecated.flex-center; + @include use-typography("title-small"); - width: 100%; + display: flex; + justify-content: center; + align-items: center; + inline-size: 100%; margin: 0; border: 0; padding: 0; - border-radius: deprecated.$br-4; - background-color: var(--input-background-color); - font-size: deprecated.$fs-14; - color: var(--input-foreground-color); - z-index: deprecated.$z-index-20; + border-radius: $br-4; + background-color: var(--color-background-tertiary); + color: var(--color-foreground-secondary); white-space: break-spaces; &:focus { @@ -83,46 +99,42 @@ } .shared-badge { - @include deprecated.flex-center; + display: flex; + justify-content: center; + align-items: center; + inline-size: $sz-16; + block-size: $sz-32; + margin-inline-end: var(--sp-xs); +} - width: deprecated.$s-16; - height: deprecated.$s-32; - margin-right: deprecated.$s-4; - - svg { - stroke: var(--button-secondary-foreground-color-rest); - fill: none; - height: deprecated.$s-16; - width: deprecated.$s-16; - } +.shared-badge-icon { + color: var(--color-foreground-secondary); } .status-notification { - width: deprecated.$s-6; - height: deprecated.$s-6; - border-radius: 50%; - margin-right: deprecated.$s-4; + --status-notification-background-color: transparent; + + inline-size: $sz-6; + block-size: $sz-6; + border-radius: $br-circle; + margin-inline-end: var(--sp-xs); flex-shrink: 0; + background-color: var(--status-notification-background-color); - &.pending-status { - background-color: var(--status-widget-background-color-warning); - } - - &.saving-status { - background-color: var(--status-widget-background-color-pending); + &.pending-status, + &.saving-status, + &.retrying-status { + --status-notification-background-color: var(--color-accent-warning); } &.saved-status { - background-color: var(--status-widget-background-color-success); + --status-notification-background-color: var(--color-accent-success); + animation: jump 0.3s ease-out; } - &.retrying-status { - background-color: var(--status-widget-background-color-warning); - } - &.error-status { - background-color: var(--status-widget-background-color-error); + --status-notification-background-color: var(--color-foreground-error); } }