diff --git a/frontend/playwright/ui/visual-specs/visual-viewer.spec.js b/frontend/playwright/ui/visual-specs/visual-viewer.spec.js index 65ffbe4af8..432b3df9c6 100644 --- a/frontend/playwright/ui/visual-specs/visual-viewer.spec.js +++ b/frontend/playwright/ui/visual-specs/visual-viewer.spec.js @@ -266,3 +266,59 @@ test.describe("Board navigation", () => { }); }); }); + +test.describe("Viewer header", () => { + test("Shows the header buttons and its menus", async ({ page }) => { + const viewerPage = new ViewerPage(page); + await viewerPage.setupLoggedInUser(); + await viewerPage.setupFileWithComments(); + await viewerPage.goToViewer({ + fileId: singleBoardFileId, + pageId: singleBoardPageId, + }); + + const header = page.locator("header").first(); + await expect(header).toBeVisible(); + await page.mouse.move(640, 600); + // Interactions section. + await expect(header).toHaveScreenshot({ + maxDiffPixelRatio: 0, + threshold: 0, + }); + + // Zoom menu. + await page.getByTitle("Zoom").click(); + await expect(page.getByText("Fit - Scale down to fit")).toBeVisible(); + await page.mouse.move(640, 600); + await expect(page).toHaveScreenshot({ + clip: { x: 760, y: 0, width: 520, height: 260 }, + maxDiffPixelRatio: 0, + threshold: 0, + }); + await page.keyboard.press("Escape"); + await page.mouse.click(640, 600); + + // Sitemap menu. + await page + .locator("#current-frame") + .locator("..") + .locator("div") + .first() + .click(); + await page.mouse.move(640, 600); + await expect(page).toHaveScreenshot({ + clip: { x: 0, y: 0, width: 520, height: 200 }, + maxDiffPixelRatio: 0, + threshold: 0, + }); + await page.mouse.click(640, 600); + + // Comments section, with its button selected. + await viewerPage.showComments(); + await page.mouse.move(640, 600); + await expect(header).toHaveScreenshot({ + maxDiffPixelRatio: 0, + threshold: 0, + }); + }); +}); diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 3d1404373a..e0907d26aa 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -14,6 +14,9 @@ [app.main.data.viewer.shortcuts :as sc] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.ds.buttons.button :refer [button*]] + [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]] [app.main.ui.exports.assets :refer [progress-widget]] [app.main.ui.formats :as fmt] [app.main.ui.icons :as deprecated-icon] @@ -47,11 +50,11 @@ (let [open* (mf/use-state false) open? (deref open*) - open-dropdown + toggle-dropdown (mf/use-fn (fn [event] (dom/stop-propagation event) - (reset! open* true))) + (swap! open* not))) close-dropdown (mf/use-fn @@ -73,28 +76,31 @@ (dom/stop-propagation event) (on-decrease)))] - [:div {:class (stl/css-case :zoom-widget true - :selected open?) - :on-click open-dropdown - :title (tr "workspace.header.zoom")} - [:span {:class (stl/css :label)} (fmt/format-percent zoom)] + [:div {:class (stl/css :zoom-widget-wrapper)} + [:button {:type "button" + :class (stl/css-case :zoom-widget true + :selected open?) + :on-click toggle-dropdown + :title (tr "workspace.header.zoom") + :aria-expanded open?} + [:span {:class (stl/css :label)} (fmt/format-percent zoom)]] [:& dropdown {:show open? :on-close close-dropdown} [:ul {:class (stl/css :dropdown)} [:li {:class (stl/css :basic-zoom-bar)} [:span {:class (stl/css :zoom-btns)} - [:button {:class (stl/css :zoom-btn) - :on-click on-decrease} - [:span {:class (stl/css :zoom-icon)} - deprecated-icon/remove-icon]] + [:> icon-button* {:variant "ghost" + :aria-label (tr "shortcuts.decrease-zoom") + :on-click on-decrease + :icon i/remove}] [:p {:class (stl/css :zoom-text)} (fmt/format-percent zoom)] - [:button {:class (stl/css :zoom-btn) - :on-click on-increase} - [:span {:class (stl/css :zoom-icon)} - deprecated-icon/add]]] - [:button {:class (stl/css :reset-btn) - :on-click on-zoom-reset} + [:> icon-button* {:variant "ghost" + :aria-label (tr "shortcuts.increase-zoom") + :on-click on-increase + :icon i/add}]] + [:> button* {:variant "ghost" + :on-click on-zoom-reset} (tr "workspace.header.reset-zoom")]] [:li {:class (stl/css :zoom-option) @@ -188,27 +194,30 @@ :on-fullscreen toggle-fullscreen}] (when (:in-team permissions) - [:span {:on-click go-to-workspace - :class (stl/css :edit-btn)} - deprecated-icon/curve]) + [:> icon-button* {:variant "ghost" + :aria-label (tr "labels.edit") + :icon i/curve + :on-click go-to-workspace}]) - [:span {:title (tr "viewer.header.fullscreen") - :class (stl/css-case :fullscreen-btn true - :selected fullscreen?) - :on-click toggle-fullscreen} - deprecated-icon/expand] + [:> icon-button* {:variant "ghost" + :aria-label (tr "viewer.header.fullscreen") + :aria-pressed (boolean fullscreen?) + :icon i/expand + :on-click toggle-fullscreen}] (when (:in-team permissions) - [:button {:on-click open-share-dialog - :class (stl/css :share-btn)} + [:> button* {:variant "primary" + :class (stl/css :share-btn) + :on-click open-share-dialog} (tr "labels.share")]) (when-not (:is-logged permissions) - [:span {:on-click open-login-dialog - :class (stl/css :go-log-btn)} (tr "labels.log-or-sign")])])) + [:> button* {:variant "ghost" + :on-click open-login-dialog} + (tr "labels.log-or-sign")])])) (mf/defc header-sitemap - [{:keys [project file page frame toggle-thumbnails] :as props}] + [{:keys [project file page frame toggle-thumbnails shown-thumbnails] :as props}] (let [project-name (:name project) file-name (:name file) page-name (:name page) @@ -236,11 +245,15 @@ :title (tr "viewer.header.sitemap")} [:span {:class (stl/css :project-name)} project-name] [:div {:class (stl/css :sitemap-text)} - [:div {:class (stl/css :breadcrumb) - :on-click open-dropdown} - [:span {:class (stl/css :breadcrumb-text)} - (dm/str file-name " / " page-name)] - [:span {:class (stl/css :icon)} deprecated-icon/arrow] + [:div {:class (stl/css :breadcrumb)} + [:button {:type "button" + :class (stl/css :breadcrumb-btn) + :aria-expanded (deref show-dropdown?) + :aria-haspopup "menu" + :on-click open-dropdown} + [:span {:class (stl/css :breadcrumb-text)} + (dm/str file-name " / " page-name)] + [:> icon* {:icon-id i/arrow-down :size "s" :class (stl/css :icon)}]] [:span "/"] [:& dropdown {:show @show-dropdown? :on-close close-dropdown} @@ -254,12 +267,14 @@ [:span {:class (stl/css :label)} (get-in file [:data :pages-index id :name])] (when (= page-id id) - [:span {:class (stl/css :icon-check)} deprecated-icon/tick])])]]] - [:div {:class (stl/css :current-frame) - :id "current-frame" - :on-click toggle-thumbnails} + [:> icon* {:icon-id i/tick :size "s" :class (stl/css :icon-check)}])])]]] + [:button {:type "button" + :class (stl/css :current-frame) + :id "current-frame" + :aria-expanded (boolean shown-thumbnails) + :on-click toggle-thumbnails} [:span {:class (stl/css :frame-name)} frame-name] - [:span {:class (stl/css :icon)} deprecated-icon/arrow]]]])) + [:> icon* {:icon-id i/arrow-down :size "s" :class (stl/css :icon)}]]]])) (def ^:private penpot-logo-icon (deprecated-icon/icon-xref :penpot-logo-icon (stl/css :logo-icon))) @@ -322,33 +337,34 @@ :page page :frame frame :toggle-thumbnails toggle-thumbnails + :shown-thumbnails shown-thumbnails :index index}]] [:div {:class (stl/css :mode-zone)} - [:button {:on-click navigate - :data-value "interactions" - :class (stl/css-case :mode-zone-btn true - :selected (= section :interactions)) - :title (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))} - deprecated-icon/play] + [:> icon-button* {:variant "ghost" + :on-click navigate + :data-value "interactions" + :aria-pressed (= section :interactions) + :aria-label (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions)) + :icon i/play}] (when (or (:in-team permissions) (= (:who-comment permissions) "all")) - [:button {:on-click navigate - :data-value "comments" - :class (stl/css-case :mode-zone-btn true - :selected (= section :comments)) - :title (tr "viewer.header.comments-section" (sc/get-tooltip :open-comments))} - deprecated-icon/comments]) + [:> icon-button* {:variant "ghost" + :on-click navigate + :data-value "comments" + :aria-pressed (= section :comments) + :aria-label (tr "viewer.header.comments-section" (sc/get-tooltip :open-comments)) + :icon i/comments}]) (when (or (:in-team permissions) (and (= (:type permissions) :share-link) (= (:who-inspect permissions) "all"))) - [:button {:on-click go-to-inspect - :class (stl/css-case :mode-zone-btn true - :selected (= section :inspect)) - :title (tr "viewer.header.inspect-section" (sc/get-tooltip :open-inspect))} - deprecated-icon/code])] + [:> icon-button* {:variant "ghost" + :on-click go-to-inspect + :aria-pressed (= section :inspect) + :aria-label (tr "viewer.header.inspect-section" (sc/get-tooltip :open-inspect)) + :icon i/code}])] [:& header-options {:section section :permissions permissions diff --git a/frontend/src/app/main/ui/viewer/header.scss b/frontend/src/app/main/ui/viewer/header.scss index ffe476cbda..aee1220748 100644 --- a/frontend/src/app/main/ui/viewer/header.scss +++ b/frontend/src/app/main/ui/viewer/header.scss @@ -4,22 +4,29 @@ // // 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/mixins.scss" as *; +@use "ds/typography.scss" as *; .viewer-header { position: absolute; - top: 0; + inset-block-start: 0; grid-column: 1 / span 1; grid-row: 1 / span 1; display: grid; - grid-template-columns: 1fr deprecated.$s-92 1fr; + grid-template-columns: 1fr auto 1fr; justify-content: space-between; align-items: center; - height: deprecated.$s-48; - width: 100vw; - padding: deprecated.$s-8 deprecated.$s-12; + block-size: $sz-48; + inline-size: 100vw; + padding: var(--sp-s) var(--sp-m); transition: transform 400ms ease 300ms; - background-color: var(--panel-background-color); + background-color: var(--color-background-primary); } // FILE NAVIGATION @@ -28,8 +35,8 @@ display: flex; justify-content: flex-start; flex-basis: min-content; - width: 100%; - gap: deprecated.$s-12; + inline-size: 100%; + gap: var(--sp-m); } .home-link { @@ -39,143 +46,137 @@ } .logo-icon { - width: deprecated.$s-28; - height: deprecated.$s-28; - fill: var(--icon-foreground-hover); + inline-size: $sz-28; + block-size: $sz-28; + fill: var(--color-foreground-primary); } .sitemap-zone { - @include deprecated.flex-column; - + display: flex; + flex-direction: column; + gap: var(--sp-xs); position: relative; - width: 100%; + inline-size: 100%; } .project-name { - @include deprecated.uppercase-title-typography; + @include use-typography("headline-small"); - color: var(--title-foreground-color); + color: var(--color-foreground-secondary); } .sitemap-text { - @include deprecated.flex-row; + display: flex; + align-items: center; + gap: var(--sp-xs); } .breadcrumb { - @include deprecated.body-small-typography; - @include deprecated.flex-row; + @include use-typography("body-small"); - color: var(--title-foreground-color); + display: flex; + align-items: center; + gap: var(--sp-xs); + color: var(--color-foreground-secondary); +} + +// Text and arrow that open the page menu: not a DS button. +.breadcrumb-btn { + @include use-typography("body-small"); + + display: flex; + align-items: center; + gap: var(--sp-xs); + padding: 0; + border: none; + background: none; + color: inherit; cursor: pointer; } .breadcrumb-text { - @include deprecated.text-ellipsis; + @include text-ellipsis; - max-width: 12vw; // This is a fallback - max-width: 12cqw; // This is a unit refered to container + max-inline-size: 12vw; // This is a fallback + max-inline-size: 12cqw; // This is a unit refered to container } -.icon { - @include deprecated.flex-center; - - height: deprecated.$s-16; - width: deprecated.$s-16; - - svg { - @extend %button-icon-small; - - transform: rotate(90deg); - stroke: var(--icon-foreground); - } -} - -.dropdown { - position: absolute; +.icon, +.icon-check { + flex-shrink: 0; + color: var(--color-foreground-secondary); } .dropdown-sitemap { @extend %menu-dropdown; - left: 0; - top: calc(deprecated.$s-2 + deprecated.$s-48); - width: deprecated.$s-272; - padding: deprecated.$s-6; + inset-inline-start: 0; + inset-block-start: px2rem(50); + inline-size: px2rem(272); + padding: px2rem(6); } .dropdown-element { @extend %dropdown-element-base; - .icon-check { - @include deprecated.flex-center; - - height: 100%; - width: deprecated.$s-16; - - svg { - @extend %button-icon-small; - - stroke: var(--icon-foreground); - } + &:hover .label { + color: var(--color-foreground-primary); } - &:hover .label { - color: var(--input-foreground-color-active); + // The current page. + &.selected { + outline: none; + border-color: var(--color-background-quaternary); + background-color: var(--color-background-quaternary); + color: var(--color-accent-primary); } } +// Board name that toggles the thumbnails: not a DS button. .current-frame { - @include deprecated.body-small-typography; - @include deprecated.flex-row; + @include use-typography("body-small"); + display: flex; + align-items: center; + gap: var(--sp-xs); flex-grow: 1; - color: var(--title-foreground-color-hover); + padding: 0; + border: none; + background: none; + color: var(--color-foreground-primary); cursor: pointer; - .icon svg { - stroke: var(--title-foreground-color-hover); + .icon { + color: var(--color-foreground-primary); } } .frame-name { - @include deprecated.text-ellipsis; + @include text-ellipsis; - max-width: 17vw; // This is a fallback - max-width: 17cqw; // This is a unit refered to container + max-inline-size: 17vw; // This is a fallback + max-inline-size: 17cqw; // This is a unit refered to container } // SECTION BUTTONS .mode-zone { - @include deprecated.flex-row; - - height: 100%; -} - -.mode-zone-btn { - @extend %button-tertiary; - @include deprecated.flex-center; - - height: deprecated.$s-32; - width: deprecated.$s-28; - padding: 0; - - svg { - @extend %button-icon; - } -} - -.selected { - @extend %button-icon-selected; + display: flex; + align-items: center; + gap: var(--sp-xs); + block-size: 100%; } // OPTION AREA .options-zone { - @include deprecated.flex-row; - + display: flex; + align-items: center; position: relative; justify-content: flex-end; - gap: deprecated.$s-8; - z-index: deprecated.$z-index-10; + gap: var(--sp-s); + + // TODO: no DS z-index token for 10; it keeps the menus above the + // viewer content. + z-index: 10; } .view-options { @@ -185,92 +186,54 @@ cursor: pointer; } -.fullscreen-btn { - @extend %button-tertiary; - @include deprecated.flex-center; - - height: deprecated.$s-32; - width: deprecated.$s-28; - - svg { - @extend %button-icon; - - stroke: var(--icon-foreground); - } -} - .share-btn { - @extend %button-primary; - - height: deprecated.$s-32; - min-width: deprecated.$s-72; - margin-left: deprecated.$s-4; -} - -.edit-btn { - @extend %button-tertiary; - @include deprecated.flex-center; - - height: deprecated.$s-32; - width: deprecated.$s-28; - - svg { - @extend %button-icon; - - stroke: var(--icon-foreground); - } -} - -.go-log-btn { - @extend %button-tertiary; - @include deprecated.body-small-typography; - - height: deprecated.$s-32; - padding: 0 deprecated.$s-8; - border-radius: deprecated.$br-8; - color: var(--button-tertiary-foreground-color-rest); + min-inline-size: px2rem(72); + margin-inline-start: var(--sp-xs); } // ZOOM WIDGET -.zoom-widget { - @include deprecated.button-style; - @include deprecated.flex-center; +.zoom-widget-wrapper { + display: flex; +} - height: deprecated.$s-28; - min-width: deprecated.$s-64; - border-radius: deprecated.$br-8; +.zoom-widget { + --zoom-widget-foreground-color: var(--color-foreground-secondary); + + display: flex; + align-items: center; + justify-content: center; + block-size: $sz-28; + min-inline-size: $sz-64; + padding: 0; + border: none; + border-radius: $br-8; + background: none; + cursor: pointer; .label { - @include deprecated.body-small-typography; + @include use-typography("body-small"); - color: var(--button-tertiary-foreground-color-rest); - } - - &:hover { - .label { - color: var(--button-tertiary-foreground-color-focus); - } + color: var(--zoom-widget-foreground-color); } + &:hover, &.selected { - .label { - color: var(--button-tertiary-foreground-color-focus); - } + --zoom-widget-foreground-color: var(--color-foreground-primary); } } .dropdown { @extend %menu-dropdown; - right: deprecated.$s-2; - top: calc(deprecated.$s-2 + deprecated.$s-48); - width: deprecated.$s-272; + inset-inline-end: px2rem(2); + inset-block-start: px2rem(50); + inline-size: px2rem(272); } .basic-zoom-bar { display: flex; justify-content: space-between; - padding: deprecated.$s-6; + padding: px2rem(6); cursor: auto; } @@ -278,49 +241,15 @@ display: flex; } -.zoom-btn { - @extend %button-tertiary; - - height: deprecated.$s-28; - width: deprecated.$s-28; - border-radius: deprecated.$br-8; - - .zoom-icon { - @include deprecated.flex-center; - - width: deprecated.$s-24; - height: deprecated.$s-32; - - svg { - @extend %button-icon; - - stroke: var(--icon-foreground); - } - } - - &:hover { - .zoom-icon svg { - stroke: var(--button-tertiary-foreground-color-hover); - } - } -} - .zoom-text { - @include deprecated.flex-center; - - height: 100%; - min-width: deprecated.$s-64; + display: flex; + justify-content: center; + align-items: center; + block-size: 100%; + min-inline-size: $sz-64; padding: 0; - margin: 0 deprecated.$s-2; - color: var(--modal-title-foreground-color); -} - -.reset-btn { - @extend %button-tertiary; - - color: var(--button-tertiary-foreground-color-hover); - height: deprecated.$s-28; - border-radius: deprecated.$br-8; + margin: 0 px2rem(2); + color: var(--color-foreground-primary); } .zoom-option { @@ -335,11 +264,11 @@ } &:hover { - color: var(--menu-foreground-color-hover); + color: var(--color-foreground-primary); .shortcuts { .shortcut-key { - color: var(--menu-foreground-color-hover); + color: var(--color-foreground-primary); } } } @@ -349,14 +278,14 @@ [data-fullscreen="true"] .viewer-header::after { content: " "; position: absolute; - width: 100%; - height: deprecated.$s-48; - left: 0; - top: deprecated.$s-48; + inline-size: 100%; + block-size: $sz-48; + inset-inline-start: 0; + inset-block-start: $sz-48; } [data-fullscreen="true"] .viewer-header { - transform: translateY(-#{deprecated.$s-48}); + transform: translateY(-#{$sz-48}); } [data-force-visible="true"] .viewer-header,