mirror of
https://github.com/penpot/penpot.git
synced 2026-10-28 21:40:34 +00:00
♻️ Use DS icons in the layer row buttons (#12477)
* ♻️ Use DS icons in the layer row buttons The expand, visibility and lock buttons of the layer rows drew their icons with the deprecated icon set (arrow, shown, hide, unlock, lock). Use the DS icon instead. The buttons stay raw: they are segments of the layer row, with backgrounds that follow the row state. The icons get a class each, so their colors move from svg rules to those classes, and they keep the old stroke weight. The DS icon draws 12px inside a 16px box: - The expand button start padding goes from 8px to 6px, so the arrow stays in place. - The visibility and lock buttons leave 12px for the icon, so their icons do not shrink (flex-shrink: 0) and stay centered, uncut. The rows look the same as before, pixel for pixel. Add a visual test with a selected, hidden and blocked layer and a hovered row. AI-assisted-by: claude-opus-5-5 * ♻️ Move the layer name styles off the deprecated styles The layer name and its rename input used the deprecated body-small typography, mixins, spacing and token aliases. They now use the DS body-small typography, sizes, borders and px2rem(), and the layer row color aliases resolve to their DS tokens, in layer_item.scss too. The DS body-small has a 1.3 line-height instead of 1.4, so the layer names sit 1px higher. The name color is one local variable for every state; the rename input sets it too, since it also has .element-name. A visual test now covers the rename input. AI-assisted-by: claude-opus-5-5
This commit is contained in:
parent
9d5dad3de7
commit
f6f9df9a1a
@ -599,6 +599,68 @@ test.describe("Layers panel", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Layers", () => {
|
||||
test("Shows the buttons of the layer rows", async ({ page }) => {
|
||||
const workspace = new WorkspacePage(page);
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockRPC(/get\-file\?/, "workspace/get-file-9066.json");
|
||||
await workspace.mockRPC(
|
||||
"update-file?id=*",
|
||||
"workspace/update-file-empty.json",
|
||||
);
|
||||
await workspace.goToWorkspace({
|
||||
fileId: "e179d9df-de35-80bf-8005-283bbd5516b0",
|
||||
pageId: "e179d9df-de35-80bf-8005-283bbd5516b1",
|
||||
});
|
||||
|
||||
await workspace.clickToggableLayer("Board");
|
||||
await workspace.clickToggableLayer("Group");
|
||||
const layer = workspace.layers.getByText("A", { exact: true });
|
||||
await layer.click();
|
||||
await layer.hover();
|
||||
await workspace.layers.getByRole("button", { name: "Hide" }).click();
|
||||
await workspace.layers.getByRole("button", { name: "Lock" }).click();
|
||||
await expect(
|
||||
workspace.layers.getByRole("button", { name: "Show" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
workspace.layers.getByRole("button", { name: "Unlock" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Selected, hidden and blocked layer, with the pointer on its row.
|
||||
await expect(workspace.layers).toHaveScreenshot({
|
||||
maxDiffPixelRatio: 0,
|
||||
threshold: 0,
|
||||
});
|
||||
|
||||
// The pointer on a collapsed group, away from the selected layer.
|
||||
await workspace.layers.getByText("Board", { exact: true }).hover();
|
||||
await expect(workspace.layers).toHaveScreenshot({
|
||||
maxDiffPixelRatio: 0,
|
||||
threshold: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("Shows the rename input of a layer", async ({ page }) => {
|
||||
const workspace = new WorkspacePage(page);
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockRPC(/get\-file\?/, "workspace/get-file-9066.json");
|
||||
await workspace.goToWorkspace({
|
||||
fileId: "e179d9df-de35-80bf-8005-283bbd5516b0",
|
||||
pageId: "e179d9df-de35-80bf-8005-283bbd5516b1",
|
||||
});
|
||||
|
||||
await workspace.layers.getByText("Board", { exact: true }).dblclick();
|
||||
const input = workspace.layers.getByRole("textbox");
|
||||
await expect(input).toBeVisible();
|
||||
await page.mouse.move(0, 0);
|
||||
await expect(workspace.layers).toHaveScreenshot({
|
||||
maxDiffPixelRatio: 0,
|
||||
threshold: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Guides", () => {
|
||||
test("Shows the advanced options of a square guide", async ({ page }) => {
|
||||
// A taller window keeps the whole Design tab visible: a scrolled
|
||||
|
||||
@ -21,9 +21,8 @@
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.sidebar.layer-name :refer [layer-name*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@ -138,7 +137,9 @@
|
||||
:data-testid "toggle-content"
|
||||
:aria-expanded is-expanded
|
||||
:on-click on-toggle-collapse}
|
||||
deprecated-icon/arrow])
|
||||
[:> icon* {:icon-id i/arrow
|
||||
:size "s"
|
||||
:class (stl/css :toggle-content-icon)}]])
|
||||
|
||||
[:div {:class (stl/css :icon-shape)
|
||||
:on-double-click on-zoom-to-selected}
|
||||
@ -189,7 +190,9 @@
|
||||
(tr "workspace.shape.menu.show")
|
||||
(tr "workspace.shape.menu.hide"))
|
||||
:on-click on-toggle-visibility}
|
||||
(if ^boolean hidden? deprecated-icon/hide deprecated-icon/shown)]
|
||||
[:> icon* {:icon-id (if ^boolean hidden? i/hide i/shown)
|
||||
:size "s"
|
||||
:class (stl/css :element-action-icon)}]]
|
||||
[:button {:class (stl/css-case
|
||||
:block-element true
|
||||
:selected blocked?)
|
||||
@ -197,7 +200,9 @@
|
||||
(tr "workspace.shape.menu.unlock")
|
||||
(tr "workspace.shape.menu.lock"))
|
||||
:on-click on-toggle-blocking}
|
||||
(if ^boolean blocked? deprecated-icon/lock deprecated-icon/unlock)]])]
|
||||
[:> icon* {:icon-id (if ^boolean blocked? i/lock i/unlock)
|
||||
:size "s"
|
||||
:class (stl/css :element-action-icon)}]]])]
|
||||
|
||||
children]))
|
||||
|
||||
|
||||
@ -27,9 +27,9 @@
|
||||
&:hover {
|
||||
--layer-background-color: var(--color-background-secondary);
|
||||
--shadow-color: var(--color-background-secondary);
|
||||
--context-hover-color: var(--layer-row-foreground-color-hover);
|
||||
--context-hover-color: var(--color-accent-primary);
|
||||
--context-hover-opacity: 1;
|
||||
--layer-foreground-color: var(--layer-row-foreground-color-hover);
|
||||
--layer-foreground-color: var(--color-accent-primary);
|
||||
|
||||
&.hidden {
|
||||
opacity: 1;
|
||||
@ -200,43 +200,40 @@
|
||||
align-items: center;
|
||||
block-size: 100%;
|
||||
inline-size: $sz-24;
|
||||
padding-inline-start: var(--sp-s);
|
||||
|
||||
svg {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
block-size: $sz-12;
|
||||
inline-size: $sz-12;
|
||||
stroke-width: 1.33px;
|
||||
stroke: var(--color-foreground-secondary);
|
||||
|
||||
.layer-row.hidden & {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.layer-row.selected & {
|
||||
stroke: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
.layer-row.type-comp & {
|
||||
stroke: var(--color-accent-secondary);
|
||||
}
|
||||
|
||||
.layer-row.highlight &,
|
||||
.layer-row:hover & {
|
||||
opacity: 1;
|
||||
stroke: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
// The DS icon draws 12px inside a 16px box: 2px less start padding
|
||||
// keeps the arrow where the old 12px icon was.
|
||||
padding-inline-start: px2rem(6);
|
||||
|
||||
.layer-row.selected & {
|
||||
background-color: var(--color-background-quaternary);
|
||||
}
|
||||
}
|
||||
|
||||
&.inverse svg {
|
||||
.toggle-content-icon {
|
||||
// Same stroke weight as the old 12px icons.
|
||||
stroke-width: 1.33px;
|
||||
color: var(--color-foreground-secondary);
|
||||
|
||||
.layer-row.hidden & {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.layer-row.selected & {
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
.layer-row.type-comp & {
|
||||
color: var(--color-accent-secondary);
|
||||
}
|
||||
|
||||
.layer-row.highlight &,
|
||||
.layer-row:hover & {
|
||||
opacity: 1;
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
.toggle-content.inverse & {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
@ -256,26 +253,6 @@
|
||||
background: var(--layer-row-action-btn-background);
|
||||
padding-inline-end: px2rem(6);
|
||||
|
||||
svg {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
block-size: $sz-12;
|
||||
inline-size: $sz-12;
|
||||
stroke-width: 1.33px;
|
||||
stroke: var(--color-foreground-secondary);
|
||||
|
||||
.layer-row.hidden & {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.type-comp & {
|
||||
stroke: var(--color-accent-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.element-actions.selected & {
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
@ -294,21 +271,40 @@
|
||||
display: flex;
|
||||
|
||||
--layer-row-action-btn-background: var(--color-background-secondary);
|
||||
|
||||
svg {
|
||||
opacity: 1;
|
||||
stroke: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-row.selected & {
|
||||
display: flex;
|
||||
|
||||
--layer-row-action-btn-background: var(--color-background-quaternary);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
stroke: var(--color-accent-primary);
|
||||
}
|
||||
.element-action-icon {
|
||||
// The DS icon box is 16px and the button leaves 12px: keep the box
|
||||
// whole, centered, instead of letting it shrink and cut the icon.
|
||||
flex-shrink: 0;
|
||||
|
||||
// Same stroke weight as the old 12px icons.
|
||||
stroke-width: 1.33px;
|
||||
color: var(--color-foreground-secondary);
|
||||
|
||||
.layer-row.hidden & {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.type-comp & {
|
||||
color: var(--color-accent-secondary);
|
||||
}
|
||||
|
||||
.layer-row.highlight &,
|
||||
.layer-row:hover & {
|
||||
opacity: 1;
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
.layer-row.selected & {
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,16 +4,22 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "ds/_utils.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/typography.scss" as *;
|
||||
|
||||
.element-name {
|
||||
--element-name-color: var(--context-hover-color, var(--layer-row-foreground-color));
|
||||
--element-name-comp-color: var(--context-hover-color, var(--layer-row-component-foreground-color));
|
||||
--element-name-opacity: var(--context-hover-opacity, deprecated.$op-7);
|
||||
--element-name-color: var(--context-hover-color, var(--color-foreground-secondary));
|
||||
--element-name-opacity: var(--context-hover-opacity, 0.7);
|
||||
|
||||
@include deprecated.text-ellipsis;
|
||||
@include deprecated.body-small-typography;
|
||||
@include use-typography("body-small");
|
||||
|
||||
display: block;
|
||||
max-inline-size: 99%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--element-name-color);
|
||||
flex-grow: 1;
|
||||
block-size: 100%;
|
||||
@ -21,15 +27,11 @@
|
||||
min-inline-size: var(--sp-xl);
|
||||
|
||||
&.selected {
|
||||
color: var(--layer-row-foreground-color-selected);
|
||||
--element-name-color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
&.type-comp {
|
||||
color: var(--element-name-comp-color);
|
||||
|
||||
&.hidden {
|
||||
opacity: var(--element-name-opacity);
|
||||
}
|
||||
--element-name-color: var(--context-hover-color, var(--color-accent-secondary));
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
@ -37,30 +39,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
// The rename input also carries .element-name, so it takes its typography,
|
||||
// ellipsis and color from there.
|
||||
.element-name-input {
|
||||
--element-name-input-border-color: var(--input-border-color-focus);
|
||||
--element-name-input-color: var(--layer-row-foreground-color);
|
||||
|
||||
@include deprecated.text-ellipsis;
|
||||
@include deprecated.body-small-typography;
|
||||
@include deprecated.remove-input-style;
|
||||
--element-name-color: var(--color-foreground-secondary);
|
||||
|
||||
background: none;
|
||||
outline: none;
|
||||
flex-grow: 1;
|
||||
height: deprecated.$s-28;
|
||||
block-size: $sz-28;
|
||||
max-inline-size: calc(var(--parent-size) - (var(--depth) * var(--layer-indentation-size)));
|
||||
margin: 0;
|
||||
padding-inline-start: deprecated.$s-6;
|
||||
border-radius: deprecated.$br-8;
|
||||
border: deprecated.$s-1 solid var(--element-name-input-border-color);
|
||||
color: var(--element-name-input-color);
|
||||
padding-inline-start: px2rem(6);
|
||||
border-radius: $br-8;
|
||||
border: $b-1 solid var(--color-accent-primary);
|
||||
|
||||
&.selected {
|
||||
min-inline-size: deprecated.$s-100;
|
||||
min-inline-size: px2rem(100);
|
||||
}
|
||||
}
|
||||
|
||||
.element-name-touched {
|
||||
--element-name-touched-color: var(--layer-row-component-foreground-color);
|
||||
|
||||
color: var(--element-name-touched-color);
|
||||
color: var(--color-accent-secondary);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user