From d6e850e917f0c8a88880301ef1b0f905740ddab1 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Wed, 21 Oct 2026 14:35:12 +0200 Subject: [PATCH] :recycle: Use DS icon buttons for the bottom palette toggles (#12443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :recycle: Use DS icon buttons for the bottom palette toggles The color palette and typographies toggles of the bottom palette were raw buttons with deprecated icons. Replace them with DS ghost icon buttons with small icons, as before, that show the open palette through aria-pressed. The layout flags are keywords, so they are coerced to a boolean for aria-pressed. Their title moves to the label, shown in the DS tooltip with the shortcut. The palette actions button spans the palette height, not a DS button size, so it stays raw; only its deprecated icon moves to the DS icon. Add a visual test that compares the palette with each toggle selected with no tolerance. AI-assisted-by: claude-opus-5-5 * :recycle: Move bottom palette styles off the deprecated styles The palette toggles are DS icon buttons, so their placeholders go away. The actions button keeps the tertiary button look through local state variables, and its icon takes the color from the button. Spacing, sizes, borders, opacity and the color aliases move to DS tokens and logical properties, except right and width of the palette, which are the transitioned properties. The grid template is split into areas, rows and columns, since Sass reads its slash as a division with a size variable. The z-index values came from the legacy global scale. The resize area, the toggles and the actions button had their own (4, 2 and 2), plus 0 overrides while collapsed, but they sit in separate grid areas and the palette content does not reach them: removing them changes no pixel with the text or color palette, the actions menu open, the 80px size, or the palette collapsed and hidden. Only the palette's own z-index stays, with a comment: it keeps the palette above the viewport overlays (z-index 1), and drops to their level while collapsed. The help button styles are not used in palette.cljs, but the external chat widget injected into the palette wrapper may use them, so they stay, inlined on DS tokens. palette.scss no longer imports refactor/. AI-assisted-by: claude-opus-5-5 * :recycle: Use a DS icon button for the bottom palette actions The palette actions button (⋮) stayed a raw button because it spans the palette height, not a DS button size. The DS icon button takes its height from --button-height, so a class now stretches it to the palette and the hand-made colors and states go away. It uses the ghost variant, like the palette toggles next to it. The button had no accessible name and did not say that it opens a menu: it now has the "Palette options" label (new en and es keys) with its tooltip, aria-expanded and aria-haspopup. The class given to its icon did not exist in palette.scss and is gone. Only the button corners change in the palette screenshots: the raw button painted its own rounded background, the ghost button is transparent at rest. AI-assisted-by: claude-opus-5-5 --- .../ui/visual-specs/workspace.spec.js | 34 +++ .../src/app/main/ui/workspace/palette.cljs | 42 ++-- .../src/app/main/ui/workspace/palette.scss | 225 ++++++++++-------- frontend/translations/en.po | 4 + frontend/translations/es.po | 4 + 5 files changed, 192 insertions(+), 117 deletions(-) diff --git a/frontend/playwright/ui/visual-specs/workspace.spec.js b/frontend/playwright/ui/visual-specs/workspace.spec.js index f324163b4a..6a7b7325e9 100644 --- a/frontend/playwright/ui/visual-specs/workspace.spec.js +++ b/frontend/playwright/ui/visual-specs/workspace.spec.js @@ -586,6 +586,40 @@ test.describe("Palette", () => { ), ).toBeVisible(); }); + + test("Shows the palette toggles selected", async ({ page }) => { + const workspace = new WorkspacePage(page); + const { fileId, pageId } = await setupFileWithAssets(workspace); + + await workspace.goToWorkspace({ fileId, pageId }); + + await workspace.palette + .getByRole("button", { name: "Typographies" }) + .click(); + await expect( + workspace.palette.getByText("Source Sans Pro Regular"), + ).toBeVisible(); + await page.mouse.move(640, 300); + // The toggles are a small part of the palette: compare every pixel. + await expect(workspace.palette).toHaveScreenshot("palette-text.png", { + maxDiffPixelRatio: 0, + threshold: 0, + }); + + await workspace.palette + .getByRole("button", { name: "Color Palette" }) + .click(); + await expect( + workspace.palette.getByText( + "There are no color styles in your library yet", + ), + ).toBeVisible(); + await page.mouse.move(640, 300); + await expect(workspace.palette).toHaveScreenshot("palette-color.png", { + maxDiffPixelRatio: 0, + threshold: 0, + }); + }); }); test.describe("Plugins", () => { diff --git a/frontend/src/app/main/ui/workspace/palette.cljs b/frontend/src/app/main/ui/workspace/palette.cljs index 7887add48d..2f27304d97 100644 --- a/frontend/src/app/main/ui/workspace/palette.cljs +++ b/frontend/src/app/main/ui/workspace/palette.cljs @@ -18,9 +18,10 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.context :as ctx] + [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.hooks :as h] [app.main.ui.hooks.resize :as r] - [app.main.ui.icons :as deprecated-icon] [app.main.ui.workspace.color-palette :refer [color-palette*]] [app.main.ui.workspace.color-palette-ctx-menu :refer [color-palette-ctx-menu*]] [app.main.ui.workspace.text-palette :refer [text-palette*]] @@ -95,6 +96,11 @@ (fn [_] (swap! state* assoc :show-menu false))) + on-toggle-menu + (mf/use-fn + (fn [_] + (swap! state* update :show-menu not))) + on-select-palette (mf/use-fn (mf/deps on-select) @@ -182,27 +188,31 @@ [:ul {:class (dm/str size-classname " " (stl/css-case :palette-btn-list true :hidden-bts hide-palettes?))} [:li {:class (stl/css :palette-item)} - [:button {:title (tr "workspace.toolbar.color-palette" (get-tt :toggle-colorpalette)) - :aria-label (tr "workspace.toolbar.color-palette" (get-tt :toggle-colorpalette)) - :class (stl/css-case :palette-btn true - :selected color-palette?) - :on-click on-select-color-palette} - deprecated-icon/drop-icon]] + [:> icon-button* {:variant "ghost" + :aria-pressed (boolean color-palette?) + :icon-size "s" + :aria-label (tr "workspace.toolbar.color-palette" (get-tt :toggle-colorpalette)) + :icon i/drop + :on-click on-select-color-palette}]] [:li {:class (stl/css :palette-item)} - [:button {:title (tr "workspace.toolbar.text-palette" (get-tt :toggle-textpalette)) - :aria-label (tr "workspace.toolbar.text-palette" (get-tt :toggle-textpalette)) - :class (stl/css-case :palette-btn true - :selected text-palette?) - :on-click on-select-text-palette} - deprecated-icon/text-palette]]] + [:> icon-button* {:variant "ghost" + :aria-pressed (boolean text-palette?) + :icon-size "s" + :aria-label (tr "workspace.toolbar.text-palette" (get-tt :toggle-textpalette)) + :icon i/text-palette + :on-click on-select-text-palette}]]] (if any-palette? [:* - [:button {:class (stl/css :palette-actions) - :on-click #(swap! state* update :show-menu not)} - deprecated-icon/menu] + [:> icon-button* {:variant "ghost" + :class (stl/css :palette-actions) + :aria-label (tr "workspace.toolbar.palette-options") + :aria-expanded (boolean show-menu?) + :aria-haspopup "menu" + :icon i/menu + :on-click on-toggle-menu}] [:div {:class (stl/css :palette) :ref container} (when text-palette? diff --git a/frontend/src/app/main/ui/workspace/palette.scss b/frontend/src/app/main/ui/workspace/palette.scss index b61814d2ba..d743596d59 100644 --- a/frontend/src/app/main/ui/workspace/palette.scss +++ b/frontend/src/app/main/ui/workspace/palette.scss @@ -4,17 +4,16 @@ // // Copyright (c) KALEIDOS SUBSIDIARY SL -@use "ds/spacing.scss" as *; -@use "ds/z-index.scss" as *; +@use "ds/_borders.scss" as *; @use "ds/_sizes.scss" as *; -@use "refactor/common-refactor.scss" as deprecated; +@use "ds/_utils.scss" as *; .palette-wrapper { position: absolute; - width: 100vw; - left: 0; - bottom: 0; - padding-bottom: deprecated.$s-4; + inline-size: 100vw; + inset-inline-start: 0; + inset-block-end: 0; + padding-block-end: var(--sp-xs); /** Aligns AI Chat button **/ display: flex; @@ -23,20 +22,26 @@ gap: var(--sp-s); } +// `right` and `width` stay physical: they are the transitioned properties. .palettes { - z-index: deprecated.$z-index-2; + // Above .viewport-overlays (z-index 1), which covers the whole viewport. + z-index: 2; position: relative; right: 0; grid-area: color-palette; display: grid; - grid-template: "resize resize resize" deprecated.$s-8 "buttons actions palette" 1fr / deprecated.$s-32 auto 1fr; - max-height: deprecated.$s-80; - height: var(--height); + grid-template-areas: + "resize resize resize" + "buttons actions palette"; + grid-template-rows: px2rem(8) 1fr; + grid-template-columns: $sz-32 auto 1fr; + max-block-size: px2rem(80); + block-size: var(--height); width: fit-content; - padding: deprecated.$s-0 deprecated.$s-0 deprecated.$s-8 deprecated.$s-8; - border-radius: deprecated.$br-8; - background-color: var(--palette-background-color); - border: deprecated.$s-2 solid var(--panel-border-color); + padding: 0 0 var(--sp-s) var(--sp-s); + border-radius: $br-8; + background-color: var(--color-background-primary); + border: $b-2 solid var(--color-background-quaternary); transition: right 0.3s, opacity 0.2s, @@ -48,23 +53,21 @@ .resize-area { grid-area: resize; - height: deprecated.$s-8; - z-index: deprecated.$z-index-4; - width: calc(100% - deprecated.$s-8); - border-radius: deprecated.$br-circle; + block-size: px2rem(8); + inline-size: calc(100% - px2rem(8)); + border-radius: $br-circle; cursor: ns-resize; - background-color: var(--palette-background-color); + background-color: var(--color-background-primary); } .palette-btn-list { grid-area: buttons; - background-color: var(--palette-background-color); - height: calc(var(--height) - deprecated.$s-16); - width: deprecated.$s-32; - margin: deprecated.$s-0; + background-color: var(--color-background-primary); + block-size: calc(var(--height) - px2rem(16)); + inline-size: $sz-32; + margin: 0; list-style: none; - z-index: deprecated.$z-index-2; - gap: deprecated.$s-2; + gap: var(--sp-xxs); &.mid-palette, &.small-palette { @@ -72,151 +75,171 @@ } .palette-item { - @include deprecated.flex-center; - - border-radius: deprecated.$br-8; - opacity: deprecated.$op-10; + display: flex; + justify-content: center; + align-items: center; + border-radius: $br-8; + opacity: 1; transition: opacity 1s ease; - - .palette-btn { - @extend %button-tertiary; - - height: deprecated.$s-32; - width: deprecated.$s-32; - border-radius: deprecated.$br-8; - background-clip: padding-box; - padding: 0; - - svg { - @extend %button-icon-small; - - stroke: var(--icon-foreground); - } - - &.selected { - @extend %button-icon-selected; - } - } } } + // DS icon button stretched to the palette height. .palette-actions { - @extend %button-tertiary; + --button-height: calc(var(--height) - #{px2rem(16)}); grid-area: actions; - height: calc(var(--height) - deprecated.$s-16); - width: deprecated.$s-32; - padding: 0; - margin-left: deprecated.$s-4; - border-radius: deprecated.$br-8; - background-color: var(--palette-background-color); - z-index: deprecated.$z-index-2; - - svg { - @extend %button-icon; - - stroke: var(--icon-foreground); - } + margin-inline-start: var(--sp-xs); } .palette { grid-area: palette; - width: 100%; - min-width: 0; + inline-size: 100%; + min-inline-size: 0; } } .handler { - @include deprecated.button-style; - @include deprecated.flex-center; - - width: deprecated.$s-12; - height: 100%; + display: flex; + justify-content: center; + align-items: center; + border: none; + background: none; + cursor: pointer; + inline-size: $sz-12; + block-size: 100%; .handler-btn { - width: deprecated.$s-4; - height: 100%; - max-height: deprecated.$s-40; - margin: deprecated.$s-8 deprecated.$s-4; + inline-size: px2rem(4); + block-size: 100%; + max-block-size: $sz-40; + margin: var(--sp-s) var(--sp-xs); padding: 0; - border-radius: deprecated.$s-4; - background-color: var(--palette-handler-background-color); + border-radius: $br-4; + background-color: var(--color-background-quaternary); } } .mid-palette, .small-palette { - grid-template-columns: deprecated.$s-64 auto 1fr; + grid-template-columns: px2rem(64) auto 1fr; } .hidden-bts { - right: deprecated.$s-2; - z-index: deprecated.$z-index-1; - width: 22px; - grid-template-columns: deprecated.$s-8 auto 1fr; + right: px2rem(2); + + // Back to the .viewport-overlays level (z-index 1) while collapsed. + z-index: 1; + width: px2rem(22); + grid-template-columns: px2rem(8) auto 1fr; padding: 0; border-inline-start: 0; border-start-start-radius: 0; border-end-start-radius: 0; .palette-btn-list { - opacity: deprecated.$op-0; + opacity: 0; visibility: hidden; - width: 0; + inline-size: 0; .palette-item { - opacity: deprecated.$op-0; + opacity: 0; visibility: hidden; - z-index: 0; } } .resize-area { visibility: hidden; - z-index: 0; - width: 0; + inline-size: 0; } .palette-actions { visibility: hidden; - z-index: 0; } .palette { visibility: hidden; - z-index: 0; } .handler { - padding-bottom: deprecated.$s-8; + padding-block-end: var(--sp-s); } } /** AI Chat button styles **/ +// Not used in palette.cljs: the external chat widget (Inkeep, see #6509) +// injected into #palette-wrapper may use these class names, so they stay. .help-btn { + --help-btn-foreground-color: var(--color-foreground-secondary); + --help-btn-background-color: var(--color-background-tertiary); + z-index: var(--z-index-panels); flex-shrink: 0; - - @extend %button-secondary; - + display: flex; + justify-content: center; + align-items: center; inline-size: $sz-40; block-size: $sz-40; - border-radius: deprecated.$br-circle; border: none; + border-radius: $br-circle; + background-color: var(--help-btn-background-color); + color: var(--help-btn-foreground-color); + cursor: pointer; - &.selected { - @extend %button-icon-selected; + // The widget markup is external, so its icon has no known class. + svg { + stroke: var(--help-btn-foreground-color); } &:hover { - border: none; + --help-btn-foreground-color: var(--color-accent-primary); + --help-btn-background-color: var(--color-background-quaternary); + + text-decoration: none; + } + + &:active { + --help-btn-foreground-color: var(--color-accent-primary); + --help-btn-background-color: var(--color-background-secondary); + + outline: none; + border: $b-1 solid var(--color-background-quaternary); + } + + &:focus-visible { + --help-btn-foreground-color: var(--color-foreground-secondary); + --help-btn-background-color: var(--color-background-tertiary); + + outline: none; + border: $b-1 solid var(--color-accent-primary); + } + + &:global(.disabled), + &[disabled], + &:disabled { + --help-btn-foreground-color: var(--color-foreground-disabled); + --help-btn-background-color: var(--color-background-quaternary); + + border: $b-1 solid var(--color-background-quaternary); + cursor: unset; + } + + &.selected { + --help-btn-foreground-color: var(--color-accent-primary); + --help-btn-background-color: var(--color-background-quaternary); + + outline: none; } } .icon-help { - @extend %button-icon; - - stroke: var(--icon-foreground); + display: flex; + justify-content: center; + align-items: center; inline-size: var(--sp-xxl); block-size: var(--sp-xxl); + color: transparent; + fill: none; + stroke-width: 1px; + stroke: var(--color-foreground-secondary); } diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 9e4020a717..078f9c694b 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -9354,6 +9354,10 @@ msgstr "Move (%s)" msgid "workspace.toolbar.palette-bar" msgstr "Palette bar" +#: src/app/main/ui/workspace/palette.cljs:211 +msgid "workspace.toolbar.palette-options" +msgstr "Palette options" + #: src/app/main/ui/workspace/top_toolbar.cljs:72 msgid "workspace.toolbar.path" msgstr "Path (%s)" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 1cf8ab2e51..23f9f2f47c 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -9460,6 +9460,10 @@ msgstr "Mover (%s)" msgid "workspace.toolbar.palette-bar" msgstr "Barra de paletas" +#: src/app/main/ui/workspace/palette.cljs:211 +msgid "workspace.toolbar.palette-options" +msgstr "Opciones de la paleta" + #: src/app/main/ui/workspace/top_toolbar.cljs:72 msgid "workspace.toolbar.path" msgstr "Ruta (%s)"