From 19fc9ebce870508b3d9c1c109544984073302cc6 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 27 Oct 2026 10:47:07 +0100 Subject: [PATCH] :recycle: Use DS icons in the viewer flows and interactions menus (#12520) * :recycle: 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 * :recycle: 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 --- .../ui/visual-specs/visual-viewer.spec.js | 33 ++++++++ .../src/app/main/ui/viewer/interactions.cljs | 40 ++++++---- .../src/app/main/ui/viewer/interactions.scss | 79 +++++++------------ 3 files changed, 89 insertions(+), 63 deletions(-) diff --git a/frontend/playwright/ui/visual-specs/visual-viewer.spec.js b/frontend/playwright/ui/visual-specs/visual-viewer.spec.js index 2085eca3e3..80ed3f7d52 100644 --- a/frontend/playwright/ui/visual-specs/visual-viewer.spec.js +++ b/frontend/playwright/ui/visual-specs/visual-viewer.spec.js @@ -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, + }); + }); +}); diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index bd96266d37..0aff863b5d 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -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] diff --git a/frontend/src/app/main/ui/viewer/interactions.scss b/frontend/src/app/main/ui/viewer/interactions.scss index b17ac08b84..669e02940f 100644 --- a/frontend/src/app/main/ui/viewer/interactions.scss +++ b/frontend/src/app/main/ui/viewer/interactions.scss @@ -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