mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 14:00:37 +00:00
♻️ 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:
parent
95bc48a08a
commit
d6e850e917
@ -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", () => {
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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)"
|
||||
|
||||
@ -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)"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user