From 5cf93e7a3de520a386288ad101108a592ec95dae Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Thu, 9 Nov 2023 19:19:21 +0100 Subject: [PATCH] :lipstick: New UI for Swap Component --- .../styles/common/refactor/design-tokens.scss | 4 + .../partials/sidebar-element-options.scss | 125 ------- .../sidebar/options/menus/component.cljs | 261 +++++++------- .../sidebar/options/menus/component.scss | 322 +++++++++++++++++- 4 files changed, 449 insertions(+), 263 deletions(-) diff --git a/frontend/resources/styles/common/refactor/design-tokens.scss b/frontend/resources/styles/common/refactor/design-tokens.scss index 6cfc4e003..8af861164 100644 --- a/frontend/resources/styles/common/refactor/design-tokens.scss +++ b/frontend/resources/styles/common/refactor/design-tokens.scss @@ -187,10 +187,14 @@ --assets-item-background-color-hover: var(--color-background-quaternary); --assets-item-name-foreground-color: var(--color-foreground-secondary); --assets-item-name-foreground-color-hover: var(--color-foreground-primary); + --assets-item-name-foreground-color-disabled: var(--color-foreground-disabled); --assets-item-border-color: var(--color-accent-primary); --assets-item-background-color-drag: var(--color-accent-primary-muted); --assets-item-border-color-drag: var(--color-select); --assets-component-background-color: var(--white); // We don't want this color to change with palette + --assets-component-background-color-disabled: var( + --off-white + ); // We don't want this color to change with palette --radio-btns-background-color: var(--color-background-tertiary); --radio-btn-background-color-selected: var(--color-background-quaternary); diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 2616a51f7..5dab0ad90 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -2591,128 +2591,3 @@ cursor: pointer; } } - -.component-swap { - .search-block { - margin: 0.7rem 0.5rem 0.2rem 0.2rem; - height: 2.1rem; - width: 100%; - } - - svg { - fill: $color-gray-20; - height: 0.7rem; - width: 0.7rem; - cursor: pointer; - } - - .search-block { - border: 1px solid $color-gray-30; - margin: 0.6rem 0.5rem 0.2rem 0.2rem; - padding: $size-1 $size-2; - display: flex; - align-items: center; - - &:hover { - border-color: $color-gray-20; - } - - &:focus-within { - border-color: $color-primary; - } - - & .search-input { - background-color: $color-gray-50; - border: none; - color: $color-gray-10; - font-size: $fs12; - margin: 0; - padding: 0; - flex-grow: 1; - - &:focus { - color: lighten($color-gray-10, 8%); - outline: none; - } - } - - & .search-icon { - display: flex; - align-items: center; - - svg { - fill: $color-gray-30; - height: 16px; - width: 16px; - } - - &.close { - transform: rotate(45deg); - cursor: pointer; - } - } - } - - .component-path { - display: flex; - margin: 0.4rem 0 0 0.4rem; - cursor: pointer; - svg { - height: 8px; - width: 8px; - margin-right: 0.5rem; - transform: rotate(180deg); - } - } - - .component-list { - margin: 0.7rem 0.5rem 0.5rem 0.5rem; - } - - .component-item { - display: flex; - align-items: center; - margin-bottom: 0.5rem; - font-size: 12px; - cursor: pointer; - svg, - img { - background-color: $color-canvas; - border-radius: $br4; - border: 2px solid transparent; - height: 24px; - width: 24px; - margin-right: $size-2; - } - - .selected { - color: $color-primary; - } - - &:hover { - color: $color-primary; - } - - &.disabled { - cursor: auto; - color: $color-gray-30; - } - } - - .component-group { - display: flex; - align-items: center; - margin-bottom: 0.5rem; - justify-content: space-between; - font-size: 12px; - cursor: pointer; - height: 24px; - svg { - height: 8px; - width: 8px; - } - &:hover { - color: $color-primary; - } - } -} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index e05500ead..230e25c33 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -19,6 +19,9 @@ [app.main.store :as st] [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] + [app.main.ui.components.search-bar :refer [search-bar]] + [app.main.ui.components.select :refer [select]] [app.main.ui.components.title-bar :refer [title-bar]] [app.main.ui.context :as ctx] [app.main.ui.hooks :as h] @@ -26,7 +29,6 @@ [app.main.ui.workspace.sidebar.assets.common :as cmm] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [app.util.keyboard :as kbd] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -149,7 +151,7 @@ (mf/defc component-swap-item {::mf/wrap-props false} - [{:keys [item loop shapes file-id root-shape container component-id is-search] :as props}] + [{:keys [item loop shapes file-id root-shape container component-id is-search listing-thumbs] :as props}] (let [on-select-component (mf/use-fn (mf/deps shapes file-id item) @@ -157,9 +159,12 @@ (st/emit! (dwl/component-multi-swap shapes file-id (:id item))))) item-ref (mf/use-ref) visible? (h/use-visible item-ref :once? true)] - [:div.component-item + [:div {:ref item-ref - :class (stl/css-case :disabled loop) + :class (stl/css-case :component-item (not listing-thumbs) + :grid-cell listing-thumbs + :selected (= (:id item) component-id) + :disabled loop) :key (str "swap-item-" (:id item)) :on-click on-select-component} (when visible? @@ -167,16 +172,28 @@ :root-shape root-shape :component item :container container}]) - [:span.component-name - {:class (stl/css-case :selected (= (:id item) component-id))} + [:span + {:class (stl/css-case :component-name true :selected (= (:id item) component-id))} (if is-search (:full-name item) (:name item))]])) +(mf/defc component-group-item + [{:keys [item on-enter-group] :as props}] + (let [group-name (:name item) + path (cph/butlast-path group-name) + on-group-click #(on-enter-group group-name)] + [:div {:class (stl/css :component-group) + :key (uuid/next) :on-click on-group-click} + [:div + (when-not (str/blank? path) + [:span {:class (stl/css :component-group-path)} (str "\u00A0/\u00A0" path)]) + [:span {:class (stl/css :component-group-name)} (cph/last-path group-name)]] + [:span i/arrow-slide]])) + (mf/defc component-swap [{:keys [shapes] :as props}] (let [single? (= 1 (count shapes)) shape (first shapes) - new-css-system false ;;(mf/use-ctx ctx/new-css-system) ;;TODO do not show new-css-system until we have the new design current-file-id (mf/use-ctx ctx/current-file-id) workspace-file (deref refs/workspace-file) workspace-data (deref refs/workspace-data) @@ -211,7 +228,8 @@ filters* (mf/use-state {:term "" :file-id file-id - :path path}) + :path path + :listing-thumbs? false}) filters (deref filters*) is-search? (not (str/blank? (:term filters))) @@ -236,13 +254,14 @@ (filter #(= (cph/butlast-path %) (:path filters)))) groups (when-not is-search? - (sort (sequence xform components))) + (->> (sort (sequence xform components)) + (map #(assoc {} :name %)))) components (if is-search? (filter #(str/includes? (str/lower (:full-name %)) (str/lower (:term filters))) components) (filter #(= (:path %) (:path filters)) components)) - items (if is-search? + items (if (or is-search? (:listing-thumbs? filters)) (sort-by :full-name components) (->> (concat groups components) (sort-by :name))) @@ -262,27 +281,22 @@ (mapcat #(get-comps-ids % [])) set) + libraries-options (map (fn [library] {:value (:id library) :label (:name library)}) (vals libraries)) + + current-library-id (:file-id filters) + current-library-name (if (= current-library-id current-file-id) + (str/upper (tr "workspace.assets.local-library")) + (get-in libraries [current-library-id :name])) on-library-change (mf/use-fn - (fn [event] - (let [value (or (-> (dom/get-target event) - (dom/get-value)) - (as-> (dom/get-current-target event) $ - (dom/get-attribute $ "data-test"))) - value (uuid/uuid value)] - (swap! filters* assoc :file-id value :term "" :path "")))) + (fn [id] + (swap! filters* assoc :file-id id :term "" :path ""))) on-search-term-change (mf/use-fn - (mf/deps new-css-system) - (fn [event] - ;; NOTE: When old-css-system is removed this function will recibe value and event - ;; Let won't be necessary any more - (let [value (if ^boolean new-css-system - event - (dom/get-target-val event))] - (swap! filters* assoc :term value)))) + (fn [term] + (swap! filters* assoc :term term))) on-search-clear-click @@ -296,47 +310,58 @@ on-enter-group (mf/use-fn #(swap! filters* assoc :path %)) - handle-key-down + toggle-list-style (mf/use-fn - (fn [event] - (let [enter? (kbd/enter? event) - esc? (kbd/esc? event) - node (dom/event->target event)] + (fn [style] + (swap! filters* assoc :listing-thumbs? (= style "grid"))))] - (when ^boolean enter? (dom/blur! node)) - (when ^boolean esc? (dom/blur! node)))))] - - [:div.component-swap - [:div.element-set-title + [:div {:class (stl/css :component-swap)} + [:div {:class (stl/css :element-set-title)} [:span (tr "workspace.options.component.swap")]] - [:div.component-swap-content - [:div.search-block - [:input.search-input - {:placeholder (str (tr "labels.search") " " (get-in libraries [(:file-id filters) :name])) - :type "text" - :value (:term filters) - :on-change on-search-term-change - :on-key-down handle-key-down}] + [:div {:class (stl/css :component-swap-content)} + [:div {:class (stl/css :search-field)} + [:& search-bar {:on-change on-search-term-change + :clear-action on-search-clear-click + :value (:term filters) + :placeholder (str (tr "labels.search") " " (get-in libraries [(:file-id filters) :name])) + :icon (mf/html [:span {:class (stl/css :search-icon)} i/search-refactor])}]] - (if is-search? - [:div.search-icon.close - {:on-click on-search-clear-click} - i/close] - [:div.search-icon - i/search])] + [:div {:class (stl/css :select-field)} + [:& select + {:class (stl/css :select-library) + :default-value (:file-id filters) + :options libraries-options + :on-change on-library-change}]] - [:select.input-select {:value (:file-id filters) - :data-mousetrap-dont-stop true - :on-change on-library-change} - (for [library (vals libraries)] - [:option {:key (:id library) :value (:id library)} (:name library)])] + [:div {:class (stl/css :library-name)} current-library-name] - (when-not (or is-search? (str/empty? (:path filters))) - [:div.component-path {:on-click on-go-back} + [:div {:class (stl/css :listing-options-wrapper)} + [:& radio-buttons {:class (stl/css :listing-options) + :selected (if (:listing-thumbs? filters) "grid" "list") + :on-change toggle-list-style + :name "swap-listing-style"} + [:& radio-button {:icon i/view-as-list-refactor + :value "list" + :id "swap-opt-list"}] + [:& radio-button {:icon i/flex-grid-refactor + :value "grid" + :id "swap-opt-grid"}]]] + + + (if (or is-search? (str/empty? (:path filters))) + [:div {:class (stl/css :component-path-empty)}] + [:button {:class (stl/css :component-path) + :on-click on-go-back} [:span i/arrow-slide] - [:span (-> (cph/split-path (:path filters)) - last)]]) - [:div.component-list + [:span (:path filters)]]) + + (when (:listing-thumbs? filters) + [:div {:class (stl/css :component-list)} + (for [item groups] + [:& component-group-item {:item item :on-enter-group on-enter-group}])]) + + [:div {:class (stl/css-case :component-grid (:listing-thumbs? filters) + :component-list (not (:listing-thumbs? filters)))} (for [item items] (if (:id item) (let [data (get-in libraries [(:file-id filters) :data]) @@ -351,11 +376,9 @@ :root-shape root-shape :container container :component-id current-comp-id - :is-search is-search?}]) - (let [on-group-click #(on-enter-group item)] - [:div.component-group {:key (uuid/next) :on-click on-group-click} - [:span (cph/last-path item)] - [:span i/arrow-slide]])))]]])) + :is-search is-search? + :listing-thumbs (:listing-thumbs? filters)}]) + [:& component-group-item {:item item :on-enter-group on-enter-group}]))]]])) (mf/defc component-ctx-menu [{:keys [menu-entries on-close show type] :as props}] @@ -379,8 +402,7 @@ (mf/defc component-menu [{:keys [shapes swap-opened?] :as props}] - (let [new-css-system false ;;(mf/use-ctx ctx/new-css-system) ;;TODO do not show new-css-system until we have the new design - current-file-id (mf/use-ctx ctx/current-file-id) + (let [current-file-id (mf/use-ctx ctx/current-file-id) components-v2 (mf/use-ctx ctx/components-v2) workspace-data (deref refs/workspace-data) workspace-libraries (deref refs/workspace-libraries) @@ -421,89 +443,56 @@ (mf/use-fn #(st/emit! :interrupt)) + open-component-panel + (mf/use-fn + (mf/deps can-swap? shapes) + #(when can-swap? (st/emit! (dwsp/open-specialized-panel :component-swap shapes)))) + menu-entries (cmm/generate-components-menu-entries shapes components-v2) show-menu? (seq menu-entries)] (when (seq shapes) - (if new-css-system - [:div {:class (stl/css :element-set)} - [:div {:class (stl/css :element-title)} + [:div {:class (stl/css :element-set)} + [:div {:class (stl/css :element-title)} + (if swap-opened? + [:button {:class (stl/css :title-back) + :on-click on-component-back} + [:span i/arrow-slide] + [:span (tr "workspace.options.component")]] [:& title-bar {:collapsable? true :collapsed? (not open?) :on-collapsed toggle-content :title (tr "workspace.options.component") - :class (stl/css :title-spacing-component)}]] + :class (stl/css :title-spacing-component)}])] - (when open? - [:div {:class (stl/css :element-content)} - [:div {:class (stl/css :component-wrapper)} - [:div {:class (stl/css :component-name-wrapper)} - [:span {:class (stl/css :component-icon)} - (if main-instance? - i/component-refactor - i/copy-refactor)] - - [:div {:class (stl/css :component-name)} shape-name]] - - [:div {:class (stl/css :component-actions)} - [:button {:class (stl/css :menu-btn) - :on-click on-menu-click} - i/menu-refactor] - - [:& component-ctx-menu {:show menu-open? - :on-close on-menu-close - :menu-entries menu-entries - :type :dropdown}]]] - (when (and (not multi) components-v2) - [:& component-annotation {:id id :shape shape :component component}])])] - - [:div.element-set - (if swap-opened? - [:button.element-set-title.component-block-title {:class (stl/css-case :back swap-opened?) - :on-click on-component-back} - [:div - [:span i/arrow-slide] - [:span (tr "workspace.options.component")]] - - (when-not multi - [:span (if main-instance? - (tr "workspace.options.component.main") - (tr "workspace.options.component.copy"))])] - [:div.element-set-title - [:span (tr "workspace.options.component")] - (when-not multi - [:span (if main-instance? - (tr "workspace.options.component.main") - (tr "workspace.options.component.copy"))])]) - - - [:div.element-set-content - [:div.row-flex.component-row - {:class (stl/css-case :copy can-swap?) - :on-click #(when can-swap? (st/emit! (dwsp/open-specialized-panel :component-swap shapes)))} - (if multi - i/component-copy + (when open? + [:div {:class (stl/css :element-content)} + [:div {:class (stl/css :component-wrapper)} + [:div {:class (stl/css-case :component-name-wrapper true :with-main (and can-swap? (not multi)) :swappeable (and can-swap? (not swap-opened?))) + :on-click open-component-panel} + [:span {:class (stl/css :component-icon)} (if main-instance? - i/component - i/component-copy)) - [:div.component-name (if multi - (tr "settings.multiple") - shape-name)] - (when show-menu? - [:div.row-actions - {:on-click on-menu-click} - i/actions - [:& component-ctx-menu {:on-close on-menu-close - :show menu-open? - :menu-entries menu-entries - :type :context-menu}]]) + i/component-refactor + i/copy-refactor)] - (when (and can-swap? (not multi)) - [:div.component-parent-name - (cph/merge-path-item (:path component) (:name component))])] + [:div {:class (stl/css :component-name)} (if multi + (tr "settings.multiple") + shape-name)] + (when show-menu? + [:div {:class (stl/css :component-actions)} + [:button {:class (stl/css :menu-btn) + :on-click on-menu-click} + i/menu-refactor] + [:& component-ctx-menu {:show menu-open? + :on-close on-menu-close + :menu-entries menu-entries + :type :dropdown}]]) + (when (and can-swap? (not multi)) + [:div {:class (stl/css :component-parent-name)} + (cph/merge-path-item (:path component) (:name component))])]] (when swap-opened? [:& component-swap {:shapes shapes}]) (when (and (not swap-opened?) (not multi) components-v2) - [:& component-annotation {:id id :shape shape :component component}])]])))) + [:& component-annotation {:id id :shape shape :component component}])])]))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss index b6fadf3d3..0d8ad621b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss @@ -10,11 +10,21 @@ @include flexColumn; .component-wrapper { display: flex; - gap: $s-4; + margin: 0 $s-4 0 $s-8; .component-name-wrapper { @extend .asset-element; @include flexRow; flex-grow: 1; + height: 100%; + width: 100%; + flex-wrap: wrap; + padding: 0 0 0 $s-12; + margin-top: $s-8; + + &.with-main { + padding-bottom: $s-12; + } + .component-icon { @include flexCenter; height: $s-24; @@ -26,8 +36,20 @@ } .component-name { @include titleTipography; - flex-grow: 1; + @include textEllipsis; + width: 70%; + flex-grow: 2; + margin-left: $s-8; } + .component-parent-name { + @include titleTipography; + @include textEllipsis; + padding-left: $s-36; + color: var(--title-foreground-color); + } + } + .swappeable { + cursor: pointer; } .component-actions { position: relative; @@ -52,4 +74,300 @@ } } } + + .title-back { + @include tabTitleTipography; + cursor: pointer; + width: 100%; + background-color: var(--title-background-color); + color: var(--title-foreground-color); + text-align: left; + border: 0; + margin-bottom: $s-16; + svg { + height: $s-8; + width: $s-8; + fill: var(--icon-foreground); + margin-right: $s-16; + transform: rotate(180deg); + } + } + + .component-swap { + .element-set-title { + @include titleTipography; + text-transform: uppercase; + margin: $s-16 $s-4 0 $s-12; + } + + svg { + stroke: var(--icon-foreground); + fill: var(--icon-foreground); + height: $s-12; + width: $s-12; + cursor: pointer; + } + + .search-field { + display: flex; + align-items: center; + height: $s-32; + margin: $s-16 $s-4 $s-4 $s-12; + border-radius: $br-8; + font-family: "worksans", sans-serif; + background-color: var(--input-background-color); + .search-box { + align-items: center; + display: flex; + width: 100%; + + .icon-wrapper { + display: flex; + svg { + @extend .button-icon-small; + stroke: var(--icon-foreground); + } + } + + .input-text { + @include removeInputStyle; + height: $s-32; + width: 100%; + margin: 0; + padding: $s-4; + border: 0; + font-size: $fs-12; + color: var(--input-foreground-color-active); + &::placeholder { + color: var(--input-foreground-color-disabled); + } + &:focus-visible { + border-color: var(--input-border-outline-color-active); + } + } + .clear-btn { + @include buttonStyle; + @include flexCenter; + height: $s-16; + width: $s-16; + .clear-icon { + @include flexCenter; + svg { + @extend .button-icon-small; + stroke: var(--icon-foreground); + } + } + } + } + .search-icon { + @include flexCenter; + width: $s-28; + svg { + @extend .button-icon-small; + stroke: var(--icon-foreground); + } + } + } + + .select-field { + margin: $s-8 $s-4 0 $s-12; + } + + .select-library { + padding-left: $s-20; + } + + .library-name { + @include titleTipography; + @include textEllipsis; + margin: $s-20 $s-4 0 $s-12; + } + + .listing-options-wrapper { + width: 100%; + } + + .listing-options { + margin-left: auto; + margin-right: $s-4; + } + + .component-path { + @include titleTipography; + @include textEllipsis; + text-align: left; + cursor: pointer; + width: 100%; + background-color: var(--title-background-color); + color: var(--title-foreground-color); + border: 0; + margin: $s-16 0 $s-12 0; + padding: 0 $s-16 0 $s-24; + svg { + height: $s-8; + width: $s-8; + fill: var(--icon-foreground); + margin-right: $s-16; + transform: rotate(180deg); + } + } + + .component-path-empty { + height: $s-16; + } + + .component-list { + margin: 0 $s-4 0 $s-8; + .component-item { + display: flex; + align-items: center; + margin-bottom: $s-4; + font-size: $s-12; + cursor: pointer; + width: 100%; + height: $s-36; + border-radius: $br-8; + background-color: var(--assets-item-background-color); + color: var(--assets-item-name-foreground-color); + + svg, + img { + background-color: var(--assets-component-background-color); + border-radius: $br-8; + height: $s-32; + width: $s-32; + margin: $s-2 $s-8 $s-2 $s-2; + } + + .selected { + color: var(--assets-item-name-foreground-color-hover); + } + + &:hover { + color: var(--assets-item-name-foreground-color-hover); + background-color: var(--assets-item-background-color-hover); + } + + &.disabled { + cursor: auto; + color: var(--assets-item-name-foreground-color-disabled); + background-color: var(--assets-item-background-color); + + svg { + cursor: auto; + } + } + } + + .component-group { + @include titleTipography; + text-align: left; + display: flex; + align-items: center; + margin: 0 $s-16 $s-8 $s-8; + justify-content: space-between; + cursor: pointer; + height: $s-24; + svg { + height: $s-8; + width: $s-8; + } + + div { + display: flex; + width: 90%; + } + span { + @include textEllipsis; + } + .component-group-path { + direction: rtl; + } + .component-group-name { + color: var(--assets-item-name-foreground-color); + } + &:hover { + color: var(--assets-item-name-foreground-color-hover); + .component-group-name { + color: var(--assets-item-name-foreground-color-hover); + } + } + } + } + } + + .component-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-auto-rows: calc(10vh + $s-16); + gap: $s-4; + margin: 0 $s-4 0 $s-8; + .grid-cell { + @include flexCenter; + flex-wrap: wrap; + position: relative; + padding: $s-8; + border-radius: $br-8; + background-color: var(--assets-component-background-color); + overflow: hidden; + cursor: pointer; + img { + height: auto; + width: auto; + max-height: 100%; + max-width: 100%; + pointer-events: none; + border: 0; + } + svg { + height: 10vh; + width: 10vh; + stroke: none; + } + .component-name { + @include titleTipography; + @include textEllipsis; + display: none; + position: absolute; + left: 0; + bottom: 0; + width: 100%; + padding: $s-2; + text-align: center; + direction: rtl; + } + + &:hover { + background-color: var(--assets-item-background-color-hover); + .component-name { + display: block; + color: var(--assets-item-name-foreground-color-hover); + background: linear-gradient(to top, var(--assets-item-background-color-hover) 0%, transparent 100%); + } + } + + &.selected { + border: $s-1 solid var(--assets-item-border-color); + .component-name { + color: var(--assets-item-name-foreground-color-hover); + } + } + + &.disabled { + background: var(--assets-component-background-color-disabled); + cursor: auto; + svg { + cursor: auto; + } + .component-name { + background: linear-gradient( + to top, + var(--assets-component-background-color-disabled) 0%, + transparent 100% + ); + color: var(--assets-item-name-foreground-color-hover); + } + } + } + } }