From 129b7afda93682091c90fc49c9ed8a759365d512 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Wed, 14 Aug 2024 11:56:04 +0200 Subject: [PATCH 1/8] :recycle: Remove components preview --- frontend/src/app/main/ui.cljs | 7 - .../app/main/ui/debug/components_preview.cljs | 270 ------------------ .../app/main/ui/debug/components_preview.scss | 99 ------- frontend/src/app/main/ui/routes.cljs | 2 - 4 files changed, 378 deletions(-) delete mode 100644 frontend/src/app/main/ui/debug/components_preview.cljs delete mode 100644 frontend/src/app/main/ui/debug/components_preview.scss diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 30668501f..57e1b7372 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -10,7 +10,6 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.context :as ctx] - [app.main.ui.debug.components-preview :as cm] [app.main.ui.debug.icons-preview :refer [icons-preview]] [app.main.ui.frame-preview :as frame-preview] [app.main.ui.icons :as i] @@ -169,12 +168,6 @@ :layout-name layout :key file-id}]]) - - :debug-components-preview - [:div.debug-preview - [:h1 "Components preview"] - [:& cm/components-preview]] - :frame-preview [:& frame-preview/frame-preview] diff --git a/frontend/src/app/main/ui/debug/components_preview.cljs b/frontend/src/app/main/ui/debug/components_preview.cljs deleted file mode 100644 index 9fd0788b7..000000000 --- a/frontend/src/app/main/ui/debug/components_preview.cljs +++ /dev/null @@ -1,270 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.main.ui.debug.components-preview - (:require-macros [app.main.style :as stl]) - (:require - [app.common.data :as d] - [app.main.data.users :as du] - [app.main.refs :as refs] - [app.main.store :as st] - [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] - [app.main.ui.components.search-bar :refer [search-bar]] - [app.main.ui.components.tab-container :refer [tab-container tab-element]] - [app.main.ui.components.title-bar :refer [title-bar]] - [app.main.ui.icons :as i] - [app.util.dom :as dom] - [rumext.v2 :as mf])) - -(mf/defc component-wrapper - {::mf/wrap-props false} - [props] - (let [children (unchecked-get props "children") - title (unchecked-get props "title")] - [:div {:class (stl/css :component)} - [:h4 {:class (stl/css :component-name)} title] - children])) - -(mf/defc components-preview - {::mf/wrap-props false} - [] - (let [profile (mf/deref refs/profile) - initial (mf/with-memo [profile] - (update profile :lang #(or % ""))) - initial-theme (:theme initial) - on-change (fn [event] - (let [theme (dom/event->value event) - data (assoc initial :theme theme)] - (st/emit! (du/update-profile data)))) - colors ["var(--color-background-primary)" - "var(--color-background-secondary)" - "var(--color-background-tertiary)" - "var(--color-background-quaternary)" - "var(--color-foreground-primary)" - "var(--color-foreground-secondary)" - "var(--color-accent-primary)" - "var(--color-accent-primary-muted)" - "var(--color-accent-secondary)" - "var(--color-accent-tertiary)"] - - ;; COMPONENTS FNs - state* (mf/use-state {:collapsed? true - :tab-selected :first - :input-value "" - :radio-selected "first"}) - state (deref state*) - - collapsed? (:collapsed? state) - toggle-collapsed - (mf/use-fn #(swap! state* update :collapsed? not)) - - tab-selected (:tab-selected state) - set-tab (mf/use-fn #(swap! state* assoc :tab-selected %)) - - input-value (:input-value state) - radio-selected (:radio-selected state) - - set-radio-selected (mf/use-fn #(swap! state* assoc :radio-selected %)) - - update-search - (mf/use-fn - (fn [value _event] - (swap! state* assoc :input-value value))) - - - on-btn-click (mf/use-fn #(prn "eyy"))] - - [:section.debug-components-preview - [:div {:class (stl/css :themes-row)} - [:h2 "Themes"] - [:select {:label "Select theme color" - :name :theme - :default "default" - :value initial-theme - :on-change on-change} - [:option {:label "Penpot Dark (default)" :value "default"}] - [:option {:label "Penpot Light" :value "light"}]] - [:div {:class (stl/css :wrapper)} - (for [color colors] - [:div {:class (stl/css :color-wrapper)} - [:span (d/name color)] - [:div {:key color - :style {:background color} - :class (stl/css :rect)}]])]] - - [:div {:class (stl/css :components-row)} - [:h2 {:class (stl/css :title)} "Components"] - [:div {:class (stl/css :components-wrapper)} - [:div {:class (stl/css :components-group)} - [:h3 "Titles"] - [:& component-wrapper - {:title "Title"} - [:& title-bar {:collapsable false - :title "Title"}]] - [:& component-wrapper - {:title "Title and action button"} - [:& title-bar {:collapsable false - :title "Title" - :on-btn-click on-btn-click - :btn-children i/add}]] - [:& component-wrapper - {:title "Collapsed title and action button"} - [:& title-bar {:collapsable true - :collapsed collapsed? - :on-collapsed toggle-collapsed - :title "Title" - :on-btn-click on-btn-click - :btn-children i/add}]] - [:& component-wrapper - {:title "Collapsed title and children"} - [:& title-bar {:collapsable true - :collapsed collapsed? - :on-collapsed toggle-collapsed - :title "Title"} - [:& tab-container {:on-change-tab set-tab - :selected tab-selected} - [:& tab-element {:id :first - :title "A tab"}] - [:& tab-element {:id :second - :title "B tab"}]]]]] - - [:div {:class (stl/css :components-group)} - [:h3 "Tabs component"] - [:& component-wrapper - {:title "2 tab component"} - [:& tab-container {:on-change-tab set-tab - :selected tab-selected} - [:& tab-element {:id :first :title "First tab"} - [:div "This is first tab content"]] - - [:& tab-element {:id :second :title "Second tab"} - [:div "This is second tab content"]]]] - [:& component-wrapper - {:title "3 tab component"} - [:& tab-container {:on-change-tab set-tab - :selected tab-selected} - [:& tab-element {:id :first :title "First tab"} - [:div "This is first tab content"]] - - [:& tab-element {:id :second - :title "Second tab"} - [:div "This is second tab content"]] - [:& tab-element {:id :third - :title "Third tab"} - [:div "This is third tab content"]]]]] - - [:div {:class (stl/css :components-group)} - [:h3 "Search bar"] - [:& component-wrapper - {:title "Search bar only"} - [:& search-bar {:on-change update-search - :value input-value - :placeholder "Test value"}]] - [:& component-wrapper - {:title "Search and button"} - [:& search-bar {:on-change update-search - :value input-value - :placeholder "Test value"} - [:button {:class (stl/css :button-secondary) - :on-click on-btn-click} - "X"]]]] - - [:div {:class (stl/css :components-group)} - [:h3 "Radio buttons"] - [:& component-wrapper - {:title "Two radio buttons (toggle)"} - [:& radio-buttons {:selected radio-selected - :on-change set-radio-selected - :name "listing-style"} - [:& radio-button {:icon i/view-as-list - :value "first" - :id :list}] - [:& radio-button {:icon i/flex-grid - :value "second" - :id :grid}]]] - [:& component-wrapper - {:title "Three radio buttons"} - [:& radio-buttons {:selected radio-selected - :on-change set-radio-selected - :name "listing-style"} - [:& radio-button {:icon i/view-as-list - :value "first" - :id :first}] - [:& radio-button {:icon i/flex-grid - :value "second" - :id :second}] - - [:& radio-button {:icon i/add - :value "third" - :id :third}]]] - - [:& component-wrapper - {:title "Four radio buttons"} - [:& radio-buttons {:selected radio-selected - :on-change set-radio-selected - :name "listing-style"} - [:& radio-button {:icon i/view-as-list - :value "first" - :id :first}] - [:& radio-button {:icon i/flex-grid - :value "second" - :id :second}] - - [:& radio-button {:icon i/add - :value "third" - :id :third}] - - [:& radio-button {:icon i/board - :value "forth" - :id :forth}]]]] - [:div {:class (stl/css :components-group)} - [:h3 "Buttons"] - [:& component-wrapper - {:title "Button primary"} - [:button {:class (stl/css :button-primary)} - "Primary"]] - [:& component-wrapper - {:title "Button primary with icon"} - [:button {:class (stl/css :button-primary)} - i/add]] - - [:& component-wrapper - {:title "Button secondary"} - [:button {:class (stl/css :button-secondary)} - "secondary"]] - [:& component-wrapper - {:title "Button secondary with icon"} - [:button {:class (stl/css :button-secondary)} - i/add]] - - [:& component-wrapper - {:title "Button tertiary"} - [:button {:class (stl/css :button-tertiary)} - "tertiary"]] - [:& component-wrapper - {:title "Button tertiary with icon"} - [:button {:class (stl/css :button-tertiary)} - i/add]]] - [:div {:class (stl/css :components-group)} - [:h3 "Inputs"] - [:& component-wrapper - {:title "Only input"} - [:div {:class (stl/css :input-wrapper)} - [:input {:class (stl/css :basic-input) - :placeholder "----"}]]] - [:& component-wrapper - {:title "Input with label"} - [:div {:class (stl/css :input-wrapper)} - [:span {:class (stl/css :input-label)} "label"] - [:input {:class (stl/css :basic-input) - :placeholder "----"}]]] - [:& component-wrapper - {:title "Input with icon"} - [:div {:class (stl/css :input-wrapper)} - [:span {:class (stl/css :input-label)} - i/add] - [:input {:class (stl/css :basic-input) - :placeholder "----"}]]]]]]])) diff --git a/frontend/src/app/main/ui/debug/components_preview.scss b/frontend/src/app/main/ui/debug/components_preview.scss deleted file mode 100644 index 8a087c9ee..000000000 --- a/frontend/src/app/main/ui/debug/components_preview.scss +++ /dev/null @@ -1,99 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) KALEIDOS INC - -@import "refactor/common-refactor.scss"; - -.themes-row { - width: 100%; - padding: $s-20; - color: var(--color-foreground-primary); - background: var(--color-background-secondary); - .wrapper { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: $s-40; - background-color: var(--color-background-primary); - width: 100%; - padding: $s-20; - .rect { - display: flex; - justify-content: center; - align-items: center; - border: $s-1 solid var(--color-foreground-primary); - padding: $s-20; - height: $s-96; - min-width: $s-152; - } - } -} -.color-wrapper { - display: grid; - grid-template-rows: auto $s-96; -} - -.components-row { - color: var(--color-foreground-primary); - background: var(--color-background-secondary); - height: 100%; - padding: 0 $s-20; - .title { - padding: $s-20; - } - .components-wrapper { - padding: $s-20; - display: flex; - flex-wrap: wrap; - gap: $s-20; - .components-group { - @include flexCenter; - justify-content: flex-start; - flex-direction: column; - border-radius: $s-8; - h3 { - @include bodySmallTypography; - font-size: $fs-24; - width: 100%; - } - .component { - display: flex; - flex-direction: column; - gap: $s-8; - width: $s-240; - max-height: $s-80; - margin-bottom: $s-16; - .component-name { - @include uppercaseTitleTipography; - font-weight: bold; - } - } - } - .button-primary { - @extend .button-primary; - height: $s-32; - svg { - @extend .button-icon; - } - } - .button-secondary { - @extend .button-secondary; - height: $s-32; - svg { - @extend .button-icon; - } - } - .button-tertiary { - @extend .button-tertiary; - height: $s-32; - svg { - @extend .button-icon; - } - } - .input-wrapper { - @extend .input-element; - @include bodySmallTypography; - } - } -} diff --git a/frontend/src/app/main/ui/routes.cljs b/frontend/src/app/main/ui/routes.cljs index eeb5f156c..ff943a0f7 100644 --- a/frontend/src/app/main/ui/routes.cljs +++ b/frontend/src/app/main/ui/routes.cljs @@ -60,8 +60,6 @@ (when *assert* ["/debug/icons-preview" :debug-icons-preview]) - ["/debug/components-preview" :debug-components-preview] - ;; Used for export ["/render-sprite/:file-id" :render-sprite] From 63ffa704f54f87680c0710b5436e5dffe913c506 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Wed, 14 Aug 2024 14:39:48 +0200 Subject: [PATCH 2/8] :recycle: Replace tab switcher on viewer --- frontend/src/app/main/ui/ds/tab_switcher.scss | 6 ++ frontend/src/app/main/ui/viewer/inspect.cljs | 2 +- .../main/ui/viewer/inspect/attributes.scss | 5 +- .../src/app/main/ui/viewer/inspect/code.scss | 5 +- .../main/ui/viewer/inspect/right_sidebar.cljs | 56 +++++++++++-------- .../main/ui/viewer/inspect/right_sidebar.scss | 8 +-- 6 files changed, 51 insertions(+), 31 deletions(-) diff --git a/frontend/src/app/main/ui/ds/tab_switcher.scss b/frontend/src/app/main/ui/ds/tab_switcher.scss index a0572f54f..e8df69e6c 100644 --- a/frontend/src/app/main/ui/ds/tab_switcher.scss +++ b/frontend/src/app/main/ui/ds/tab_switcher.scss @@ -99,3 +99,9 @@ .tab-text-and-icon { padding-inline: var(--sp-xxs); } + +.tab-panel { + display: grid; + width: 100%; + height: 100%; +} diff --git a/frontend/src/app/main/ui/viewer/inspect.cljs b/frontend/src/app/main/ui/viewer/inspect.cljs index 110317c18..836fa8cae 100644 --- a/frontend/src/app/main/ui/viewer/inspect.cljs +++ b/frontend/src/app/main/ui/viewer/inspect.cljs @@ -43,7 +43,7 @@ [{:keys [local file page frame index viewer-pagination size share-id]}] (let [inspect-svg-container-ref (mf/use-ref nil) current-section* (mf/use-state :info) - current-section (deref current-section*) + current-section (deref current-section*) can-be-expanded? (= current-section :code) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes.scss b/frontend/src/app/main/ui/viewer/inspect/attributes.scss index 54980db83..6975e304e 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes.scss +++ b/frontend/src/app/main/ui/viewer/inspect/attributes.scss @@ -11,6 +11,9 @@ flex-direction: column; gap: $s-16; width: 100%; - height: 100%; + height: calc(100vh - #{$s-128}); // TODO: Fix this hardcoded value padding-top: $s-8; + overflow-y: auto; + overflow-x: hidden; + scrollbar-gutter: stable; } diff --git a/frontend/src/app/main/ui/viewer/inspect/code.scss b/frontend/src/app/main/ui/viewer/inspect/code.scss index b0caabde2..5786bd74f 100644 --- a/frontend/src/app/main/ui/viewer/inspect/code.scss +++ b/frontend/src/app/main/ui/viewer/inspect/code.scss @@ -9,9 +9,12 @@ .element-options { display: flex; flex-direction: column; - height: 100%; + height: calc(100vh - #{$s-128}); // TODO: Fix this hardcoded value overflow: hidden; padding-bottom: $s-16; + overflow-y: auto; + overflow-x: hidden; + scrollbar-gutter: stable; } .download-button { diff --git a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs index fe76cb6f3..1f7d22edf 100644 --- a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.cljs @@ -11,7 +11,7 @@ [app.common.types.component :as ctk] [app.main.refs :as refs] [app.main.ui.components.shape-icon :as sir] - [app.main.ui.components.tab-container :refer [tab-container tab-element]] + [app.main.ui.ds.tab-switcher :refer [tab-switcher*]] [app.main.ui.icons :as i] [app.main.ui.viewer.inspect.attributes :refer [attributes]] [app.main.ui.viewer.inspect.code :refer [code]] @@ -61,9 +61,9 @@ (mf/use-fn (mf/deps from on-change-section) (fn [new-section] - (reset! section new-section) + (reset! section (keyword new-section)) (when on-change-section - (on-change-section new-section)))) + (on-change-section (keyword new-section))))) handle-expand (mf/use-fn @@ -74,7 +74,33 @@ navigate-to-help (mf/use-fn (fn [] - (dom/open-new-window "https://help.penpot.app/user-guide/inspect/")))] + (dom/open-new-window "https://help.penpot.app/user-guide/inspect/"))) + + info-content + (mf/html [:& attributes {:page-id page-id + :objects objects + :file-id file-id + :frame frame + :shapes shapes + :from from + :libraries libraries + :share-id share-id}]) + + code-content + (mf/html [:& code {:frame frame + :shapes shapes + :on-expand handle-expand + :from from}]) + + tabs + #js [#js {:label (tr "inspect.tabs.info") + :id "info" + :content info-content} + + #js {:label (tr "inspect.tabs.code") + :data-testid "code" + :id "code" + :content code-content}]] (mf/use-effect (mf/deps shapes handle-change-tab) @@ -108,25 +134,11 @@ ;; (tr "inspect.tabs.code.selected.text") [:span {:class (stl/css :layer-title)} (:name first-shape)]])] [:div {:class (stl/css :inspect-content)} - [:& tab-container {:on-change-tab handle-change-tab - :selected @section - :content-class (stl/css :tab-content) - :header-class (stl/css :tab-header)} - [:& tab-element {:id :info :title (tr "inspect.tabs.info")} - [:& attributes {:page-id page-id - :objects objects - :file-id file-id - :frame frame - :shapes shapes - :from from - :libraries libraries - :share-id share-id}]] - [:& tab-element {:id :code :title (tr "inspect.tabs.code")} - [:& code {:frame frame - :shapes shapes - :on-expand handle-expand - :from from}]]]]] + [:> tab-switcher* {:tabs tabs + :default-selected "info" + :on-change-tab handle-change-tab + :class (stl/css :viewer-tab-switcher)}]]] [:div {:class (stl/css :empty)} [:div {:class (stl/css :code-info)} [:span {:class (stl/css :placeholder-icon)} diff --git a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss index 48bb94620..1f1f8cf89 100644 --- a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss +++ b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss @@ -97,10 +97,6 @@ overflow: hidden; } -.tab-content { - scrollbar-gutter: stable; -} - -.tab-header { - margin-right: $s-12; +.viewer-tab-switcher { + --tabs-nav-padding-inline-end: var(--sp-m); } From 2f99d1788591a259f3ee88d912b5cf3caffed9b3 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Mon, 19 Aug 2024 14:14:10 +0200 Subject: [PATCH 3/8] :recycle: Replace tab switcher on design tab --- .../main/ui/viewer/inspect/attributes.cljs | 3 +- .../main/ui/viewer/inspect/attributes.scss | 4 + .../src/app/main/ui/workspace/sidebar.cljs | 2 +- .../src/app/main/ui/workspace/sidebar.scss | 6 +- .../main/ui/workspace/sidebar/options.cljs | 183 ++++++++++-------- .../main/ui/workspace/sidebar/options.scss | 20 +- .../sidebar/options/rows/stroke_row.scss | 76 ++++---- 7 files changed, 162 insertions(+), 132 deletions(-) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes.cljs index 9798af677..af75e956b 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes.cljs @@ -41,7 +41,8 @@ content (when (= (count shapes) 1) (ctkl/get-component-annotation (first shapes) libraries))] - [:div {:class (stl/css :element-options)} + [:div {:class (stl/css-case :element-options true + :workspace-element-options (= from :workspace))} (for [[idx option] (map-indexed vector options)] [:> (case option :geometry geometry-panel diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes.scss b/frontend/src/app/main/ui/viewer/inspect/attributes.scss index 6975e304e..3ae51a85d 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes.scss +++ b/frontend/src/app/main/ui/viewer/inspect/attributes.scss @@ -17,3 +17,7 @@ overflow-x: hidden; scrollbar-gutter: stable; } + +.workspace-element-options { + height: calc(100vh - #{$s-164}); // TODO: Fix this hardcoded value +} diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index f83b56686..11a189d4b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -171,7 +171,7 @@ :id "right-sidebar-aside" :data-testid "right-sidebar" :data-size (str size) - :style #js {"--width" (if can-be-expanded? (dm/str size "px") 276)}} + :style #js {"--width" (if can-be-expanded? (dm/str size "px") "276px")}} (when can-be-expanded? [:div {:class (stl/css :resize-area) :on-pointer-down on-pointer-down diff --git a/frontend/src/app/main/ui/workspace/sidebar.scss b/frontend/src/app/main/ui/workspace/sidebar.scss index f70e37e5e..e73dc621a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.scss +++ b/frontend/src/app/main/ui/workspace/sidebar.scss @@ -59,11 +59,11 @@ $width-settings-bar-max: $s-500; .right-settings-bar { grid-area: right-sidebar; + display: grid; + grid-template-rows: auto 1fr; + height: 100vh; width: $width-settings-bar; background-color: var(--panel-background-color); - height: 100%; - display: flex; - flex-direction: column; z-index: 0; &.not-expand { max-width: $width-settings-bar; diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index ea5414360..e77c504db 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -15,8 +15,8 @@ [app.main.data.workspace :as udw] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.components.tab-container :refer [tab-container tab-element]] [app.main.ui.context :as ctx] + [app.main.ui.ds.tab-switcher :refer [tab-switcher*]] [app.main.ui.viewer.inspect.right-sidebar :as hrs] [app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]] [app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options]] @@ -72,102 +72,117 @@ (when (= (:type panel) :component-swap) [:& component-menu {:shapes (:shapes panel) :swap-opened? true}])) +(mf/defc design-menu + {::mf/wrap [mf/memo]} + [{:keys [selected objects page-id file-id selected-shapes shapes-with-children]}] + (let [sp-panel (mf/deref refs/specialized-panel) + drawing (mf/deref refs/workspace-drawing) + shared-libs (mf/deref refs/workspace-libraries) + edition (mf/deref refs/selected-edition) + edit-grid? (ctl/grid-layout? objects edition) + grid-edition (mf/deref refs/workspace-grid-edition) + selected-cells (->> (dm/get-in grid-edition [edition :selected]) + (map #(dm/get-in objects [edition :layout-grid-cells %])))] + + [:div {:class (stl/css :element-options :design-options)} + [:& align-options] + [:& bool-options] + + (cond + (and edit-grid? (d/not-empty? selected-cells)) + [:& grid-cell/options + {:shape (get objects edition) + :cells selected-cells}] + + edit-grid? + [:& layout-container/grid-layout-edition + {:ids [edition] + :values (get objects edition)}] + + (not (nil? sp-panel)) + [:& specialized-panel {:panel sp-panel}] + + (d/not-empty? drawing) + [:& shape-options + {:shape (:object drawing) + :page-id page-id + :file-id file-id + :shared-libs shared-libs}] + + (= 0 (count selected)) + [:& page/options] + + (= 1 (count selected)) + [:& shape-options + {:shape (first selected-shapes) + :page-id page-id + :file-id file-id + :shared-libs shared-libs + :shapes-with-children shapes-with-children}] + + :else + [:& multiple/options + {:shapes-with-children shapes-with-children + :shapes selected-shapes + :page-id page-id + :file-id file-id + :shared-libs shared-libs}])])) (mf/defc options-content {::mf/memo true ::mf/props :obj} - [{:keys [selected section shapes shapes-with-children page-id file-id on-change-section on-expand]}] - (let [drawing (mf/deref refs/workspace-drawing) - objects (mf/deref refs/workspace-page-objects) - shared-libs (mf/deref refs/workspace-libraries) - edition (mf/deref refs/selected-edition) - grid-edition (mf/deref refs/workspace-grid-edition) - sp-panel (mf/deref refs/specialized-panel) + [{:keys [selected shapes shapes-with-children page-id file-id on-change-section on-expand]}] + (let [objects (mf/deref refs/workspace-page-objects) selected-shapes (into [] (keep (d/getf objects)) selected) first-selected-shape (first selected-shapes) shape-parent-frame (cfh/get-frame objects (:frame-id first-selected-shape)) - edit-grid? (ctl/grid-layout? objects edition) - selected-cells (->> (dm/get-in grid-edition [edition :selected]) - (map #(dm/get-in objects [edition :layout-grid-cells %]))) - on-change-tab (fn [options-mode] - (st/emit! (udw/set-options-mode options-mode)) - (if (= options-mode :inspect) - (st/emit! :interrupt (udw/set-workspace-read-only true)) - (st/emit! :interrupt (udw/set-workspace-read-only false))))] + (let [options-mode (keyword options-mode)] + (st/emit! (udw/set-options-mode options-mode)) + (if (= options-mode :inspect) + (st/emit! :interrupt (udw/set-workspace-read-only true)) + (st/emit! :interrupt (udw/set-workspace-read-only false))))) + + design-content (mf/html [:& design-menu {:selected selected + :objects objects + :page-id page-id + :file-id file-id + :selected-shapes selected-shapes + :shapes-with-children shapes-with-children}]) + + inspect-content (mf/html [:div {:class (stl/css :element-options :inspect-options)} + [:& hrs/right-sidebar {:page-id page-id + :objects objects + :file-id file-id + :frame shape-parent-frame + :shapes selected-shapes + :on-change-section on-change-section + :on-expand on-expand + :from :workspace}]]) + + interactions-content (mf/html [:div {:class (stl/css :element-options :interaction-options)} + [:& interactions-menu {:shape (first shapes)}]]) + tabs + #js [#js {:label (tr "workspace.options.design") + :id "design" + :content design-content} + + #js {:label (tr "workspace.options.prototype") + :id "prototype" + :content interactions-content} + + #js {:label (tr "workspace.options.inspect") + :id "inspect" + :content inspect-content}]] [:div {:class (stl/css :tool-window)} - [:& tab-container - {:on-change-tab on-change-tab - :selected section - :collapsable false - :content-class (stl/css-case - :content-class true - :inspect (= section :inspect)) - :header-class (stl/css :tab-spacing)} - [:& tab-element {:id :design - :title (tr "workspace.options.design")} - [:div {:class (stl/css :element-options)} - [:& align-options] - [:& bool-options] - - (cond - (and edit-grid? (d/not-empty? selected-cells)) - [:& grid-cell/options - {:shape (get objects edition) - :cells selected-cells}] - - edit-grid? - [:& layout-container/grid-layout-edition - {:ids [edition] - :values (get objects edition)}] - - (not (nil? sp-panel)) - [:& specialized-panel {:panel sp-panel}] - - (d/not-empty? drawing) - [:& shape-options - {:shape (:object drawing) - :page-id page-id - :file-id file-id - :shared-libs shared-libs}] - - (= 0 (count selected)) - [:& page/options] - - (= 1 (count selected)) - [:& shape-options - {:shape (first selected-shapes) - :page-id page-id - :file-id file-id - :shared-libs shared-libs - :shapes-with-children shapes-with-children}] - - :else - [:& multiple/options - {:shapes-with-children shapes-with-children - :shapes selected-shapes - :page-id page-id - :file-id file-id - :shared-libs shared-libs}])]] - [:& tab-element {:id :prototype - :title (tr "workspace.options.prototype")} - [:div {:class (stl/css :element-options)} - [:& interactions-menu {:shape (first shapes)}]]] - [:& tab-element {:id :inspect - :title (tr "workspace.options.inspect")} - [:div {:class (stl/css :element-options)} - [:& hrs/right-sidebar {:page-id page-id - :objects objects - :file-id file-id - :frame shape-parent-frame - :shapes selected-shapes - :on-change-section on-change-section - :on-expand on-expand - :from :workspace}]]]]])) + [:> tab-switcher* {:tabs tabs + :default-selected "info" + :on-change-tab on-change-tab + :class (stl/css :options-tab-switcher)}]])) ;; TODO: this need optimizations, selected-objects and ;; selected-objects-with-children are derefed always but they only diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.scss b/frontend/src/app/main/ui/workspace/sidebar/options.scss index 80a122994..0f987be55 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options.scss @@ -8,8 +8,7 @@ .tool-window { position: relative; - display: flex; - flex-direction: column; + display: grid; width: 100%; height: 100%; padding-left: $s-12; @@ -27,14 +26,23 @@ } .element-options { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; + display: grid; gap: $s-8; + width: 100%; + height: calc(100vh - $s-80); padding-top: $s-8; } +.design-options, +.interaction-options { + overflow: auto; + scrollbar-gutter: stable; +} + .inspect { scrollbar-gutter: unset; } + +.options-tab-switcher { + --tabs-nav-padding-inline-end: 12px; +} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss index 634075ea2..3a8a0f23c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.scss @@ -8,44 +8,7 @@ .stroke-data { @include flexColumn; - .stroke-options { - @include flexRow; - .stroke-width-input-element { - @extend .input-element; - @include bodySmallTypography; - width: $s-60; - } - .select-wrapper { - width: $s-124; - } - } - .stroke-caps-options { - @include flexRow; - .cap-select { - width: $s-124; - } - .stroke-cap-dropdown, - .stroke-cap-dropdown-start { - min-width: $s-124; - width: fit-content; - max-width: $s-252; - right: 0; - left: unset; - } - .stroke-cap-dropdown-start { - left: 0; - right: unset; - } - .swap-caps-btn { - @extend .button-secondary; - height: $s-32; - width: $s-28; - svg { - @extend .button-icon; - } - } - } &.dnd-over-top { border-top: $s-1 solid var(--layer-row-foreground-color-drag); } @@ -53,3 +16,42 @@ border-bottom: $s-1 solid var(--layer-row-foreground-color-drag); } } + +.stroke-options { + @include flexRow; + display: grid; + grid-template-columns: 1fr 2fr 2fr; + + .stroke-width-input-element { + @extend .input-element; + @include bodySmallTypography; + } +} +.stroke-caps-options { + @include flexRow; +} + +.cap-select { + width: $s-124; +} +.stroke-cap-dropdown, +.stroke-cap-dropdown-start { + min-width: $s-124; + width: fit-content; + max-width: $s-252; + right: 0; + left: unset; +} + +.stroke-cap-dropdown-start { + left: 0; + right: unset; +} +.swap-caps-btn { + @extend .button-secondary; + height: $s-32; + width: $s-28; + svg { + @extend .button-icon; + } +} From c0cd980f5f81c336fc7f1cdee6c70020328f5a50 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 20 Aug 2024 09:04:57 +0200 Subject: [PATCH 4/8] :recycle: Replace libraries modal tab component --- .../src/app/main/ui/workspace/libraries.cljs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 5e354e423..ed527fac6 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -24,8 +24,8 @@ [app.main.ui.components.color-bullet :as cb] [app.main.ui.components.link-button :as lb] [app.main.ui.components.search-bar :refer [search-bar]] - [app.main.ui.components.tab-container :refer [tab-container tab-element]] [app.main.ui.components.title-bar :refer [title-bar]] + [app.main.ui.ds.tab-switcher :refer [tab-switcher*]] [app.main.ui.hooks :as h] [app.main.ui.icons :as i] [app.util.color :as uc] @@ -487,9 +487,6 @@ file-id (:id file) shared? (:is-shared file) - selected-tab* (mf/use-state starting-tab) - selected-tab (deref selected-tab*) - libraries (mf/deref refs/workspace-libraries) libraries (mf/with-memo [libraries] (d/removem (fn [[_ val]] (:is-indirect val)) libraries)) @@ -498,9 +495,6 @@ shared-libraries (mf/deref refs/workspace-shared-files) - on-tab-change - (mf/use-fn #(reset! selected-tab* %)) - close-dialog-outside (mf/use-fn (fn [event] (when (= (dom/get-target event) (dom/get-current-target event)) @@ -509,7 +503,21 @@ close-dialog (mf/use-fn (fn [_] (modal/hide!) - (modal/disallow-click-outside!)))] + (modal/disallow-click-outside!))) + + tabs + #js [#js {:label (tr "workspace.libraries.libraries") + :id "libraries" + :content (mf/html [:& libraries-tab {:file-id file-id + :shared? shared? + :linked-libraries libraries + :shared-libraries shared-libraries}])} + + #js {:label (tr "workspace.libraries.updates") + :id "updates" + :content (mf/html [:& updates-tab {:file-id file-id + :file-data file-data + :libraries libraries}])}]] (mf/with-effect [team-id] (when team-id @@ -524,19 +532,9 @@ close-icon] [:div {:class (stl/css :modal-title)} (tr "workspace.libraries.libraries")] - [:& tab-container - {:on-change-tab on-tab-change - :selected selected-tab - :collapsable false} - [:& tab-element {:id :libraries :title (tr "workspace.libraries.libraries")} - [:& libraries-tab {:file-id file-id - :shared? shared? - :linked-libraries libraries - :shared-libraries shared-libraries}]] - [:& tab-element {:id :updates :title (tr "workspace.libraries.updates")} - [:& updates-tab {:file-id file-id - :file-data file-data - :libraries libraries}]]]]])) + + [:> tab-switcher* {:tabs tabs + :default-selected (dm/str starting-tab)}]]])) (mf/defc v2-info-dialog {::mf/register modal/components From 1782837a38bc7d92a3b4e8168ee2c758aae27b50 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 20 Aug 2024 13:59:54 +0200 Subject: [PATCH 5/8] :recycle: Replace colorpicker modal tab component --- .../app/main/ui/workspace/colorpicker.cljs | 91 ++++++++++--------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index 2170ebfeb..08c7bae9c 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -21,7 +21,8 @@ [app.main.store :as st] [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.select :refer [select]] - [app.main.ui.components.tab-container :refer [tab-container tab-element]] + [app.main.ui.ds.foundations.assets.icon :as ic] + [app.main.ui.ds.tab-switcher :refer [tab-switcher*]] [app.main.ui.icons :as i] [app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]] [app.main.ui.workspace.colorpicker.gradients :refer [gradients]] @@ -208,7 +209,50 @@ [{:value :linear-gradient :label (tr "media.linear")} {:value :radial-gradient :label (tr "media.radial")}]) (when (not disable-image) - [{:value :image :label (tr "media.image")}])))] + [{:value :image :label (tr "media.image")}]))) + + tabs + #js [#js {:aria-label (tr "workspace.libraries.colors.rgba") + :icon ic/rgba + :id "ramp" + :content (mf/html (if picking-color? + [:div {:class (stl/css :picker-detail-wrapper)} + [:div {:class (stl/css :center-circle)}] + [:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]] + [:& ramp-selector + {:color current-color + :disable-opacity disable-opacity + :on-change handle-change-color + :on-start-drag on-start-drag + :on-finish-drag on-finish-drag}]))} + + #js {:aria-label "Harmony" + :icon ic/rgba-complementary + :id "harmony" + :content (mf/html (if picking-color? + [:div {:class (stl/css :picker-detail-wrapper)} + [:div {:class (stl/css :center-circle)}] + [:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]] + [:& harmony-selector + {:color current-color + :disable-opacity disable-opacity + :on-change handle-change-color + :on-start-drag on-start-drag + :on-finish-drag on-finish-drag}]))} + + #js {:aria-label "HSVA" + :icon ic/hsva + :id "hsva" + :content (mf/html (if picking-color? + [:div {:class (stl/css :picker-detail-wrapper)} + [:div {:class (stl/css :center-circle)}] + [:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]] + [:& hsva-selector + {:color current-color + :disable-opacity disable-opacity + :on-change handle-change-color + :on-start-drag on-start-drag + :on-finish-drag on-finish-drag}]))}]] ;; Initialize colorpicker state (mf/with-effect [] @@ -306,46 +350,9 @@ :on-selected on-fill-image-selected}]]]) [:* [:div {:class (stl/css :colorpicker-tabs)} - [:& tab-container - {:on-change-tab on-change-tab - :selected @active-color-tab - :collapsable false} - - [:& tab-element {:id :ramp :title i/rgba} - (if picking-color? - [:div {:class (stl/css :picker-detail-wrapper)} - [:div {:class (stl/css :center-circle)}] - [:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]] - [:& ramp-selector - {:color current-color - :disable-opacity disable-opacity - :on-change handle-change-color - :on-start-drag on-start-drag - :on-finish-drag on-finish-drag}])] - - [:& tab-element {:id :harmony :title i/rgba-complementary} - (if picking-color? - [:div {:class (stl/css :picker-detail-wrapper)} - [:div {:class (stl/css :center-circle)}] - [:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]] - [:& harmony-selector - {:color current-color - :disable-opacity disable-opacity - :on-change handle-change-color - :on-start-drag on-start-drag - :on-finish-drag on-finish-drag}])] - - [:& tab-element {:id :hsva :title i/hsva} - (if picking-color? - [:div {:class (stl/css :picker-detail-wrapper)} - [:div {:class (stl/css :center-circle)}] - [:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]] - [:& hsva-selector - {:color current-color - :disable-opacity disable-opacity - :on-change handle-change-color - :on-start-drag on-start-drag - :on-finish-drag on-finish-drag}])]]] + [:> tab-switcher* {:tabs tabs + :default-selected "ramp" + :on-change-tab on-change-tab}]] [:& color-inputs {:type (if (= @active-color-tab :hsva) :hsv :rgb) From 3df9c88bb7bc6487d47a289199582f1f928128f8 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 20 Aug 2024 16:53:56 +0200 Subject: [PATCH 6/8] :recycle: Replace layer tabs component with the new tab switcher component --- frontend/src/app/main/ui/ds/tab_switcher.cljs | 168 +++++++++++------- .../src/app/main/ui/workspace/sidebar.cljs | 115 +++++++++--- .../src/app/main/ui/workspace/sidebar.scss | 19 ++ .../workspace/sidebar/collapsable_button.cljs | 10 +- .../workspace/sidebar/collapsable_button.scss | 3 +- frontend/src/app/util/array.cljs | 20 ++- 6 files changed, 236 insertions(+), 99 deletions(-) diff --git a/frontend/src/app/main/ui/ds/tab_switcher.cljs b/frontend/src/app/main/ui/ds/tab_switcher.cljs index 5a5d5218a..1235514f9 100644 --- a/frontend/src/app/main/ui/ds/tab_switcher.cljs +++ b/frontend/src/app/main/ui/ds/tab_switcher.cljs @@ -9,8 +9,8 @@ [app.common.data.macros :as dm] [app.main.style :as stl]) (:require - [app.common.data :as d] - [app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i] + [app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]] + [app.util.array :as array] [app.util.dom :as dom] [app.util.keyboard :as kbd] [app.util.object :as obj] @@ -19,45 +19,48 @@ (mf/defc tab* {::mf/props :obj ::mf/private true} - [{:keys [selected icon label aria-label id tab-ref] :rest props}] - + [{:keys [selected icon label aria-label id on-ref] :rest props}] (let [class (stl/css-case :tab true :selected selected) - props (mf/spread-props props {:class class - :role "tab" - :aria-selected selected - :title (or label aria-label) - :tab-index (if selected nil -1) - :ref tab-ref - :data-id id})] + props (mf/spread-props props + {:class class + :role "tab" + :aria-selected selected + :title (or label aria-label) + :tab-index (if selected nil -1) + :ref (fn [node] + (on-ref node id)) + :data-id id})] - [:> "li" {} - [:> "button" props - (when icon + [:li + [:> :button props + (when (some? icon) [:> icon* {:id icon :aria-hidden (when label true) :aria-label (when (not label) aria-label)}]) - (when label + (when (string? label) [:span {:class (stl/css-case :tab-text true - :tab-text-and-icon icon)} label])]])) + :tab-text-and-icon icon)} + label])]])) (mf/defc tab-nav* {::mf/props :obj ::mf/private true} - [{:keys [tabs-refs tabs selected on-click button-position action-button] :rest props}] + [{:keys [on-ref tabs selected on-click button-position action-button] :rest props}] (let [class (stl/css-case :tab-nav true :tab-nav-start (= "start" button-position) :tab-nav-end (= "end" button-position)) - props (mf/spread-props props {:class (stl/css :tab-list) - :role "tablist" - :aria-orientation "horizontal"})] - [:> "nav" {:class class} + props (mf/spread-props props + {:class (stl/css :tab-list) + :role "tablist" + :aria-orientation "horizontal"})] + [:nav {:class class} (when (= button-position "start") action-button) [:> "ul" props - (for [[index element] (map-indexed vector tabs)] + (for [element ^js tabs] (let [icon (obj/get element "icon") label (obj/get element "label") aria-label (obj/get element "aria-label") @@ -67,24 +70,14 @@ :key (dm/str "tab-" id) :label label :aria-label aria-label - :selected (= index selected) + :selected (= id selected) :on-click on-click - :id id - :tab-ref (nth tabs-refs index)}]))] + :on-ref on-ref + :id id}]))] (when (= button-position "end") action-button)])) -(mf/defc tab-panel* - {::mf/props :obj - ::mf/private true} - [{:keys [children name] :rest props}] - (let [props (mf/spread-props props {:class (stl/css :tab-panel) - :aria-labelledby name - :role "tabpanel"})] - [:> "section" props - children])) - (defn- valid-tabs? [tabs] (every? (fn [tab] @@ -96,10 +89,24 @@ (not (and aria-label (or (nil? icon) label)))))) (seq tabs))) -(def ^:private positions (set '("start" "end"))) +(def ^:private positions + #{"start" "end"}) -(defn- valid-button-position? [position button] - (or (nil? position) (and (contains? positions position) (some? button)))) +(defn- valid-button-position? + [position button] + (or (nil? position) + (and (contains? positions position) + (some? button)))) + +(defn- get-tab + [tabs id] + (or (array/find #(= id (obj/get % "id")) tabs) + (aget tabs 0))) + +(defn- get-selected-tab-id + [tabs default] + (let [tab (get-tab tabs default)] + (obj/get tab "id"))) (mf/defc tab-switcher* {::mf/props :obj} @@ -107,54 +114,83 @@ ;; TODO: Use a schema to assert the tabs prop -> https://tree.taiga.io/project/penpot/task/8521 (assert (valid-tabs? tabs) "unexpected props for tab-switcher") (assert (valid-button-position? action-button-position action-button) "invalid action-button-position") - (let [tab-ids (mapv #(obj/get % "id") tabs) - active-tab-index* (mf/use-state (or (d/index-of tab-ids default-selected) 0)) - active-tab-index (deref active-tab-index*) + (let [selected* (mf/use-state #(get-selected-tab-id tabs default-selected)) + selected (deref selected*) - tabs-refs (mapv (fn [_] (mf/use-ref)) tabs) + tabs-nodes-refs (mf/use-ref nil) + tabs-ref (mf/use-ref nil) - active-tab (nth tabs active-tab-index) - panel-content (obj/get active-tab "content") - - handle-click + on-click (mf/use-fn - (mf/deps on-change-tab tab-ids) + (mf/deps on-change-tab) (fn [event] - (let [id (dom/get-data (dom/get-current-target event) "id") - index (d/index-of tab-ids id)] - (reset! active-tab-index* index) + (let [node (dom/get-current-target event) + id (dom/get-data node "id")] + (reset! selected* id) (when (fn? on-change-tab) (on-change-tab id))))) + on-ref + (mf/use-fn + (fn [node id] + (let [refs (or (mf/ref-val tabs-nodes-refs) #js {}) + refs (if node + (obj/set! refs id node) + (obj/unset! refs id))] + (mf/set-ref-val! tabs-nodes-refs refs)))) + on-key-down (mf/use-fn - (mf/deps tabs-refs active-tab-index) + (mf/deps selected) (fn [event] - (let [len (count tabs-refs) - index (cond - (kbd/home? event) 0 - (kbd/left-arrow? event) (mod (- active-tab-index 1) len) - (kbd/right-arrow? event) (mod (+ active-tab-index 1) len))] - (when index - (reset! active-tab-index* index) - (dom/focus! (mf/ref-val (nth tabs-refs index))))))) + (let [tabs (mf/ref-val tabs-ref) + len (alength tabs) + sel? #(= selected (obj/get % "id")) + id (cond + (kbd/home? event) + (let [tab (aget tabs 0)] + (obj/get tab "id")) + + (kbd/left-arrow? event) + (let [index (array/find-index sel? tabs) + index (mod (- index 1) len) + tab (aget tabs index)] + (obj/get tab "id")) + + (kbd/right-arrow? event) + (let [index (array/find-index sel? tabs) + index (mod (+ index 1) len) + tab (aget tabs index)] + (obj/get tab "id")))] + + (when (some? id) + (reset! selected* id) + (let [nodes (mf/ref-val tabs-nodes-refs) + node (obj/get nodes id)] + (dom/focus! node)))))) class (dm/str class " " (stl/css :tabs)) props (mf/spread-props props {:class class :on-key-down on-key-down})] - [:> "article" props - [:> "div" {:class (stl/css :padding-wrapper)} + (mf/with-effect [tabs] + (mf/set-ref-val! tabs-ref tabs)) + + [:> :article props + [:div {:class (stl/css :padding-wrapper)} [:> tab-nav* {:button-position action-button-position :action-button action-button :tabs tabs - :selected active-tab-index - :on-click handle-click - :tabs-refs tabs-refs}]] + :on-ref on-ref + :selected selected + :on-click on-click}]] - [:> tab-panel* {:tab-index 0} - panel-content]])) + [:section {:class (stl/css :tab-panel) + :tab-index 0 + :role "tabpanel"} + (let [active-tab (get-tab tabs selected)] + (obj/get active-tab "content"))]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index 11a189d4b..7b5b876e0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -11,8 +11,9 @@ [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.components.tab-container :refer [tab-container tab-element]] [app.main.ui.context :as muc] + [app.main.ui.ds.foundations.assets.icon :refer [icon*]] + [app.main.ui.ds.tab-switcher :refer [tab-switcher*]] [app.main.ui.hooks.resize :refer [use-resize-hook]] [app.main.ui.workspace.comments :refer [comments-sidebar]] [app.main.ui.workspace.left-header :refer [left-header]] @@ -31,6 +32,17 @@ ;; --- Left Sidebar (Component) +(mf/defc collapse-button + {::mf/wrap [mf/memo] + ::mf/wrap-props false} + [{:keys [on-click] :as props}] + ;; NOTE: This custom button may be replace by an action button when this variant is designed + [:button {:class (stl/css :collapse-sidebar-button) + :on-click on-click} + [:& icon* {:id "arrow" + :size "s" + :aria-label (tr "workspace.sidebar.collapse")}]]) + (mf/defc left-sidebar {::mf/wrap [mf/memo] ::mf/wrap-props false} @@ -60,7 +72,49 @@ (mf/use-fn #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar))) on-tab-change - (mf/use-fn #(st/emit! (dw/go-to-layout %)))] + (mf/use-fn #(st/emit! (dw/go-to-layout (keyword %)))) + + tabs (if ^boolean mode-inspect? + #js [#js {:label (tr "workspace.sidebar.layers") + :id "layers" + :content (mf/html [:article {:class (stl/css :layers-tab) + :style #js {"--height" (str size-pages "px")}} + + [:& sitemap {:layout layout + :toggle-pages toggle-pages + :show-pages? @show-pages? + :size size-pages}] + + (when @show-pages? + [:div {:class (stl/css :resize-area-horiz) + :on-pointer-down on-pointer-down-pages + :on-lost-pointer-capture on-lost-pointer-capture-pages + :on-pointer-move on-pointer-move-pages}]) + + [:& layers-toolbox {:size-parent size + :size size-pages}]])}] + #js [#js {:label (tr "workspace.sidebar.layers") + :id "layers" + :content (mf/html [:article {:class (stl/css :layers-tab) + :style #js {"--height" (str size-pages "px")}} + + [:& sitemap {:layout layout + :toggle-pages toggle-pages + :show-pages? @show-pages? + :size size-pages}] + + (when @show-pages? + [:div {:class (stl/css :resize-area-horiz) + :on-pointer-down on-pointer-down-pages + :on-lost-pointer-capture on-lost-pointer-capture-pages + :on-pointer-move on-pointer-move-pages}]) + + [:& layers-toolbox {:size-parent size + :size size-pages}]])} + #js {:label (tr "workspace.toolbar.assets") + :id "assets" + :content (mf/html [:& assets-toolbox {:size (- size 58)}])}])] + [:& (mf/provider muc/sidebar) {:value :left} [:aside {:ref parent-ref @@ -89,36 +143,43 @@ :else [:div {:class (stl/css :settings-bar-content)} - [:& tab-container - {:on-change-tab on-tab-change - :selected section - :collapsable true - :handle-collapse handle-collapse - :header-class (stl/css :tab-spacing)} + [:> tab-switcher* {:tabs tabs + :default-selected (dm/str section) + :on-change-tab on-tab-change + :class (stl/css :left-sidebar-tabs) + :action-button-position "start" + :action-button (mf/html [:& collapse-button {:on-click handle-collapse}])}] - [:& tab-element {:id :layers - :title (tr "workspace.sidebar.layers")} - [:article {:class (stl/css :layers-tab) - :style #js {"--height" (str size-pages "px")}} + #_[:& tab-container + {:on-change-tab on-tab-change + :selected section + :collapsable true + :handle-collapse handle-collapse + :header-class (stl/css :tab-spacing)} - [:& sitemap {:layout layout - :toggle-pages toggle-pages - :show-pages? @show-pages? - :size size-pages}] + [:& tab-element {:id :layers + :title (tr "workspace.sidebar.layers")} + [:article {:class (stl/css :layers-tab) + :style #js {"--height" (str size-pages "px")}} - (when @show-pages? - [:div {:class (stl/css :resize-area-horiz) - :on-pointer-down on-pointer-down-pages - :on-lost-pointer-capture on-lost-pointer-capture-pages - :on-pointer-move on-pointer-move-pages}]) + [:& sitemap {:layout layout + :toggle-pages toggle-pages + :show-pages? @show-pages? + :size size-pages}] - [:& layers-toolbox {:size-parent size - :size size-pages}]]] + (when @show-pages? + [:div {:class (stl/css :resize-area-horiz) + :on-pointer-down on-pointer-down-pages + :on-lost-pointer-capture on-lost-pointer-capture-pages + :on-pointer-move on-pointer-move-pages}]) - (when-not ^boolean mode-inspect? - [:& tab-element {:id :assets - :title (tr "workspace.toolbar.assets")} - [:& assets-toolbox {:size (- size 58)}]])]])]])) + [:& layers-toolbox {:size-parent size + :size size-pages}]]] + + (when-not ^boolean mode-inspect? + [:& tab-element {:id :assets + :title (tr "workspace.toolbar.assets")} + [:& assets-toolbox {:size (- size 58)}]])]])]])) ;; --- Right Sidebar (Component) diff --git a/frontend/src/app/main/ui/workspace/sidebar.scss b/frontend/src/app/main/ui/workspace/sidebar.scss index e73dc621a..af53021b1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.scss +++ b/frontend/src/app/main/ui/workspace/sidebar.scss @@ -89,3 +89,22 @@ $width-settings-bar-max: $s-500; border-bottom: $s-2 solid var(--resize-area-border-color); cursor: ns-resize; } + +.left-sidebar-tabs { + --tabs-nav-padding-inline-start: var(--sp-m); + --tabs-nav-padding-inline-end: var(--sp-m); +} + +.collapse-sidebar-button { + --collapse-icon-color: var(--color-foreground-secondary); + @include flexCenter; + @include buttonStyle; + height: 100%; + width: $s-24; + border-radius: $br-5; + color: var(--collapse-icon-color); + transform: rotate(180deg); + &:hover { + --collapse-icon-color: var(--color-foreground-primary); + } +} diff --git a/frontend/src/app/main/ui/workspace/sidebar/collapsable_button.cljs b/frontend/src/app/main/ui/workspace/sidebar/collapsable_button.cljs index 31c9b0af5..11adff3db 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/collapsable_button.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/collapsable_button.cljs @@ -9,7 +9,7 @@ (:require [app.main.data.workspace :as dw] [app.main.store :as st] - [app.main.ui.icons :as i] + [app.main.ui.ds.foundations.assets.icon :refer [icon*]] [app.util.i18n :refer [tr]] [rumext.v2 :as mf])) @@ -22,6 +22,8 @@ :class (stl/css :collapsed-sidebar)} [:div {:class (stl/css :collapsed-title)} [:button {:class (stl/css :collapsed-button) - :on-click on-click - :aria-label (tr "workspace.sidebar.expand")} - i/arrow]]])) + :title (tr "workspace.sidebar.expand") + :on-click on-click} + [:& icon* {:id "arrow" + :size "s" + :aria-label (tr "workspace.sidebar.expand")}]]]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/collapsable_button.scss b/frontend/src/app/main/ui/workspace/sidebar/collapsable_button.scss index 072a072a5..3f62cd24b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/collapsable_button.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/collapsable_button.scss @@ -14,10 +14,11 @@ padding: $s-4; border-radius: $br-8; background: var(--color-background-primary); + margin-inline-start: var(--sp-m); } .collapsed-title { @include flexCenter; - height: $s-32; + height: $s-36; width: $s-24; border-radius: $br-8; background: var(--color-background-secondary); diff --git a/frontend/src/app/util/array.cljs b/frontend/src/app/util/array.cljs index 012b62c0a..1e56d99b3 100644 --- a/frontend/src/app/util/array.cljs +++ b/frontend/src/app/util/array.cljs @@ -6,7 +6,9 @@ (ns app.util.array "A collection of helpers for work with javascript arrays." - (:refer-clojure :exclude [conj! conj filter])) + (:refer-clojure :exclude [conj! conj filter map reduce find]) + (:require + [cljs.core :as c])) (defn conj "A conj like function for js arrays." @@ -49,3 +51,19 @@ "A specific filter for js arrays." [pred ^js/Array o] (.filter o pred)) + +(defn map + [f a] + (.map ^js/Array a f)) + +(defn reduce + [f init val] + (.reduce ^js/Array val f init)) + +(defn find-index + [f v] + (.findIndex ^js/Array v f)) + +(defn find + [f v] + (.find ^js/Array v f)) From 89562d0231213270b2b1c747525b62f2c0b1f126 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 20 Aug 2024 21:15:26 +0200 Subject: [PATCH 7/8] :sparkles: Add schema validation for tabs --- frontend/deps.edn | 4 +- frontend/src/app/main/ui/ds/tab_switcher.cljs | 52 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/frontend/deps.edn b/frontend/deps.edn index ec67bf562..538d8b156 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -20,8 +20,8 @@ :git/url "https://github.com/funcool/beicon.git"} funcool/rumext - {:git/tag "v2.12" - :git/sha "ab819f5" + {:git/tag "v2.13" + :git/sha "dc8e1e5" :git/url "https://github.com/funcool/rumext.git"} instaparse/instaparse {:mvn/version "1.5.0"} diff --git a/frontend/src/app/main/ui/ds/tab_switcher.cljs b/frontend/src/app/main/ui/ds/tab_switcher.cljs index 1235514f9..980f7e3a5 100644 --- a/frontend/src/app/main/ui/ds/tab_switcher.cljs +++ b/frontend/src/app/main/ui/ds/tab_switcher.cljs @@ -78,26 +78,6 @@ (when (= button-position "end") action-button)])) -(defn- valid-tabs? - [tabs] - (every? (fn [tab] - (let [icon (obj/get tab "icon") - label (obj/get tab "label") - aria-label (obj/get tab "aria-label")] - (and (or (not icon) (contains? icon-list icon)) - (not (and icon (nil? label) (nil? aria-label))) - (not (and aria-label (or (nil? icon) label)))))) - (seq tabs))) - -(def ^:private positions - #{"start" "end"}) - -(defn- valid-button-position? - [position button] - (or (nil? position) - (and (contains? positions position) - (some? button)))) - (defn- get-tab [tabs id] (or (array/find #(= id (obj/get % "id")) tabs) @@ -108,13 +88,33 @@ (let [tab (get-tab tabs default)] (obj/get tab "id"))) -(mf/defc tab-switcher* - {::mf/props :obj} - [{:keys [class tabs on-change-tab default-selected action-button-position action-button] :rest props}] - ;; TODO: Use a schema to assert the tabs prop -> https://tree.taiga.io/project/penpot/task/8521 - (assert (valid-tabs? tabs) "unexpected props for tab-switcher") - (assert (valid-button-position? action-button-position action-button) "invalid action-button-position") +(def ^:private schema:tab + [:and + [:map {:title "tab"} + [:icon {:optional true} + [:and :string [:fn #(contains? icon-list %)]]] + [:label {:optional true} :string] + [:aria-label {:optional true} :string] + [:content some?]] + [:fn {:error/message "invalid data: missing required props"} + (fn [tab] + (or (and (contains? tab :icon) + (or (contains? tab :label) + (contains? tab :aria-label))) + (contains? tab :label)))]]) +(def ^:private schema:tab-switcher + [:map + [:class {:optional true} :string] + [:action-button-position {:optional true} + [:enum "start" "end"]] + [:default-selected {:optional true} :string] + [:tabs [:vector {:min 1} schema:tab]]]) + +(mf/defc tab-switcher* + {::mf/props :obj + ::mf/schema schema:tab-switcher} + [{:keys [class tabs on-change-tab default-selected action-button-position action-button] :rest props}] (let [selected* (mf/use-state #(get-selected-tab-id tabs default-selected)) selected (deref selected*) From 3f3c3a3df4b8277ca8cf86378e52bed1b40a0b55 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Wed, 21 Aug 2024 12:59:00 +0200 Subject: [PATCH 8/8] :bug: Fix labelled by on tab-panel component --- frontend/src/app/main/ui/ds/tab_switcher.cljs | 18 +++++---- .../src/app/main/ui/workspace/sidebar.cljs | 37 +------------------ 2 files changed, 12 insertions(+), 43 deletions(-) diff --git a/frontend/src/app/main/ui/ds/tab_switcher.cljs b/frontend/src/app/main/ui/ds/tab_switcher.cljs index 980f7e3a5..521ec239f 100644 --- a/frontend/src/app/main/ui/ds/tab_switcher.cljs +++ b/frontend/src/app/main/ui/ds/tab_switcher.cljs @@ -30,7 +30,9 @@ :tab-index (if selected nil -1) :ref (fn [node] (on-ref node id)) - :data-id id})] + :data-id id + ;; This prop is to be used for accessibility purposes only. + :id id})] [:li [:> :button props @@ -188,9 +190,11 @@ :selected selected :on-click on-click}]] - - [:section {:class (stl/css :tab-panel) - :tab-index 0 - :role "tabpanel"} - (let [active-tab (get-tab tabs selected)] - (obj/get active-tab "content"))]])) + (let [active-tab (get-tab tabs selected) + content (obj/get active-tab "content") + id (obj/get active-tab "id")] + [:section {:class (stl/css :tab-panel) + :tab-index 0 + :role "tabpanel" + :aria-labelledby id} + content])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index 7b5b876e0..4cfff5949 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -51,7 +51,6 @@ mode-inspect? (= options-mode :inspect) project (mf/deref refs/workspace-project) - section (cond (or mode-inspect? (contains? layout :layers)) :layers (contains? layout :assets) :assets) @@ -148,38 +147,7 @@ :on-change-tab on-tab-change :class (stl/css :left-sidebar-tabs) :action-button-position "start" - :action-button (mf/html [:& collapse-button {:on-click handle-collapse}])}] - - #_[:& tab-container - {:on-change-tab on-tab-change - :selected section - :collapsable true - :handle-collapse handle-collapse - :header-class (stl/css :tab-spacing)} - - [:& tab-element {:id :layers - :title (tr "workspace.sidebar.layers")} - [:article {:class (stl/css :layers-tab) - :style #js {"--height" (str size-pages "px")}} - - [:& sitemap {:layout layout - :toggle-pages toggle-pages - :show-pages? @show-pages? - :size size-pages}] - - (when @show-pages? - [:div {:class (stl/css :resize-area-horiz) - :on-pointer-down on-pointer-down-pages - :on-lost-pointer-capture on-lost-pointer-capture-pages - :on-pointer-move on-pointer-move-pages}]) - - [:& layers-toolbox {:size-parent size - :size size-pages}]]] - - (when-not ^boolean mode-inspect? - [:& tab-element {:id :assets - :title (tr "workspace.toolbar.assets")} - [:& assets-toolbox {:size (- size 58)}]])]])]])) + :action-button (mf/html [:& collapse-button {:on-click handle-collapse}])}]])]])) ;; --- Right Sidebar (Component) @@ -193,9 +161,6 @@ is-history? (contains? layout :document-history) is-inspect? (= section :inspect) - ;;expanded? (mf/deref refs/inspect-expanded) - ;;prev-expanded? (hooks/use-previous expanded?) - current-section* (mf/use-state :info) current-section (deref current-section*)