♻️ Use DS icons in the viewer flows and interactions menus (#12520)

* ♻️ Use DS icons in the viewer flows and interactions menus

The flows and interactions triggers in the viewer header were divs with
a click handler, and their menus lived inside them. They are now
buttons with aria-expanded and aria-haspopup, and each menu sits next
to its button in a wrapper, since a button cannot hold the menu
entries. The play, arrow and tick icons use DS icons. The menu entries
stay as they are.

A visual test covers the interactions menu closed and open; the test
files have no flows, and the flows trigger shares its styles.

AI-assisted-by: claude-opus-5-5

* ♻️ Move viewer interactions styles off the deprecated styles

Spacing, sizes, borders, typographies and color aliases move to DS
tokens and logical properties. The DS icons take their color from the
text and no longer shrink, so the old icon placeholders go away. The
DS body-small line-height is more compact, so the trigger labels sit
1px higher. The refactor/ import stays only for the shared menu
placeholders.

AI-assisted-by: claude-opus-5-5
This commit is contained in:
Eva Marco 2026-10-27 10:47:07 +01:00 committed by GitHub
parent 9854f8e54f
commit 19fc9ebce8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 89 additions and 63 deletions

View File

@ -362,3 +362,36 @@ test.describe("Inspect code tab", () => {
});
});
});
test.describe("Interactions menu", () => {
test("Shows the interactions menu closed and open", async ({ page }) => {
const viewerPage = new ViewerPage(page);
await viewerPage.setupLoggedInUser();
await viewerPage.setupFileWithMultipleBoards();
await viewerPage.goToViewer({
fileId: "dd5cc0bb-91ff-81b9-8004-77df9cd3edb0",
pageId: "dd5cc0bb-91ff-81b9-8004-77df9cd3edb3",
});
const trigger = page
.getByText("Interactions", { exact: true })
.locator("..");
await expect(trigger).toBeVisible();
await page.mouse.move(640, 600);
// Closed: the "Interactions" trigger with its arrow.
await expect(trigger).toHaveScreenshot({
maxDiffPixelRatio: 0,
threshold: 0,
});
await trigger.click();
const menu = page.getByText("Show interactions", { exact: true });
await expect(menu).toBeVisible();
await page.mouse.move(640, 600);
// Open: the menu with the selected mode ticked.
await expect(menu.locator("../..")).toHaveScreenshot({
maxDiffPixelRatio: 0,
threshold: 0,
});
});
});

View File

@ -18,8 +18,8 @@
[app.main.features :as features]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.main.ui.hooks :as h]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.viewer.shapes :as shapes]
[app.main.ui.viewer.viewport-common :as vpc]
[app.main.ui.viewer.viewport-wasm :as viewport.wasm]
@ -218,11 +218,17 @@
(st/emit! (dv/go-to-frame (:starting-frame flow))))))]
(when flows
[:div {:on-click toggle-dropdown
:class (stl/css :view-options)}
[:span {:class (stl/css :icon)} deprecated-icon/play]
[:span {:class (stl/css :dropdown-title)} (:name current-flow)]
[:span {:class (stl/css :icon-dropdown)} deprecated-icon/arrow]
[:div {:class (stl/css :view-options-wrapper)}
[:button {:type "button"
:class (stl/css :view-options)
:aria-expanded show-dropdown?
:aria-haspopup "menu"
:on-click toggle-dropdown}
[:> icon* {:icon-id i/play :size "s" :class (stl/css :icon)}]
[:span {:class (stl/css :dropdown-title)} (:name current-flow)]
[:> icon* {:icon-id i/arrow-down
:size "s"
:class (stl/css :icon-dropdown)}]]
[:& dropdown {:show show-dropdown?
:on-close hide-dropdown}
[:ul {:class (stl/css :dropdown)}
@ -236,7 +242,7 @@
:on-click select-flow}
[:span {:class (stl/css :label)} (:name flow)]
(when (= flow-id (:id current-flow))
[:span {:class (stl/css :icon)} deprecated-icon/tick])])]]])))
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :icon)}])])]]])))
(mf/defc interactions-menu*
[{:keys [interactions-mode]}]
@ -253,10 +259,16 @@
(dom/stop-propagation event)
(st/emit! (dv/set-interactions-mode mode)))))]
[:div {:on-click toggle-dropdown
:class (stl/css :view-options)}
[:span {:class (stl/css :dropdown-title)} (tr "viewer.header.interactions")]
[:span {:class (stl/css :icon-dropdown)} deprecated-icon/arrow]
[:div {:class (stl/css :view-options-wrapper)}
[:button {:type "button"
:class (stl/css :view-options)
:aria-expanded (deref show-dropdown?)
:aria-haspopup "menu"
:on-click toggle-dropdown}
[:span {:class (stl/css :dropdown-title)} (tr "viewer.header.interactions")]
[:> icon* {:icon-id i/arrow-down
:size "s"
:class (stl/css :icon-dropdown)}]]
[:& dropdown {:show @show-dropdown?
:on-close hide-dropdown}
[:ul {:class (stl/css :dropdown)}
@ -267,7 +279,7 @@
[:span {:class (stl/css :label)} (tr "viewer.header.dont-show-interactions")]
(when (= interactions-mode :hide)
[:span {:class (stl/css :icon)} deprecated-icon/tick])]
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :icon)}])]
[:li {:class (stl/css-case :dropdown-element true
:selected (= interactions-mode :show))
@ -275,7 +287,7 @@
:data-mode "show"}
[:span {:class (stl/css :label)} (tr "viewer.header.show-interactions")]
(when (= interactions-mode :show)
[:span {:class (stl/css :icon)} deprecated-icon/tick])]
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :icon)}])]
@ -286,7 +298,7 @@
[:span {:class (stl/css :label)} (tr "viewer.header.show-interactions-on-click")]
(when (= interactions-mode :show-on-click)
[:span {:class (stl/css :icon)} deprecated-icon/tick])]]]]))
[:> icon* {:icon-id i/tick :size "s" :class (stl/css :icon)}])]]]]))
(defn animate-go-to-frame
[animation current-viewport orig-viewport current-size orig-size wrapper-size]

View File

@ -4,94 +4,75 @@
//
// Copyright (c) KALEIDOS SUBSIDIARY SL
// Only for the shared menu placeholders, which move to the DS with the
// menus and dropdowns.
@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 *;
.view-options-wrapper {
position: relative;
}
.view-options {
@include deprecated.body-small-typography;
@include use-typography("body-small");
display: flex;
align-items: center;
position: relative;
gap: deprecated.$s-4;
height: deprecated.$s-32;
border-radius: deprecated.$br-8;
background-color: var(--input-background-color);
padding: deprecated.$s-8;
border: none;
gap: var(--sp-xs);
block-size: $sz-32;
border-radius: $br-8;
background-color: var(--color-background-tertiary);
padding: var(--sp-s);
cursor: pointer;
}
.dropdown-title {
@include deprecated.body-small-typography;
@include use-typography("body-small");
flex-grow: 1;
color: var(--input-foreground-color-active);
color: var(--color-foreground-primary);
}
.label {
flex-grow: 1;
color: var(--input-foreground-color);
color: var(--color-foreground-secondary);
}
.dropdown {
@extend %menu-dropdown;
right: deprecated.$s-2;
top: calc(deprecated.$s-2 + deprecated.$s-48);
width: deprecated.$s-272;
padding: deprecated.$s-6;
max-height: calc(100vh - 3 * (deprecated.$s-2 + deprecated.$s-48));
inset-inline-end: px2rem(2);
inset-block-start: px2rem(50);
inline-size: px2rem(272);
padding: px2rem(6);
max-block-size: calc(100vh - #{px2rem(150)});
overflow: auto;
}
.dropdown-element {
@extend %dropdown-element-base;
min-height: deprecated.$s-32;
.icon {
@include deprecated.flex-center;
height: 100%;
width: deprecated.$s-16;
svg {
@extend %button-icon-small;
stroke: var(--icon-foreground);
}
}
min-block-size: $sz-32;
&:hover .label {
color: var(--input-foreground-color-active);
color: var(--color-foreground-primary);
}
}
.dropdown-element.selected {
.label {
color: var(--input-foreground-color-active);
}
.icon svg {
stroke: var(--input-foreground-color);
color: var(--color-foreground-primary);
}
}
.icon,
.icon-dropdown {
@include deprecated.flex-center;
height: 100%;
width: deprecated.$s-16;
svg {
@extend %button-icon-small;
stroke: var(--icon-foreground);
}
}
.icon-dropdown svg {
transform: rotate(90deg);
flex-shrink: 0;
color: var(--color-foreground-secondary);
}
// breakpoint 1013px