♻️ Use DS icon buttons for the bottom palette toggles (#12443)

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

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

* ♻️ 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
This commit is contained in:
Eva Marco 2026-10-21 14:35:12 +02:00 committed by GitHub
parent 95bc48a08a
commit d6e850e917
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 192 additions and 117 deletions

View File

@ -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", () => {

View File

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

View File

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

View File

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

View File

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