From 88d85706ad732dc78f752b049205ecf41233b762 Mon Sep 17 00:00:00 2001 From: Andrey Antukh <niwi@niwi.nz> Date: Tue, 15 Oct 2024 17:51:29 +0200 Subject: [PATCH] :recycle: Refactor context-menu component --- .../main/ui/components/context_menu_a11y.cljs | 396 +++++++++--------- .../src/app/main/ui/dashboard/file_menu.cljs | 221 +++++----- frontend/src/app/main/ui/dashboard/fonts.cljs | 23 +- .../app/main/ui/dashboard/project_menu.cljs | 77 ++-- .../app/main/ui/settings/access_tokens.cljs | 14 +- .../app/main/ui/workspace/sidebar/assets.cljs | 51 +-- .../ui/workspace/sidebar/assets/colors.cljs | 24 +- .../ui/workspace/sidebar/assets/common.cljs | 9 +- .../workspace/sidebar/assets/components.cljs | 34 +- .../ui/workspace/sidebar/assets/graphics.cljs | 18 +- .../ui/workspace/sidebar/assets/groups.cljs | 12 +- .../sidebar/assets/typographies.cljs | 30 +- 12 files changed, 460 insertions(+), 449 deletions(-) diff --git a/frontend/src/app/main/ui/components/context_menu_a11y.cljs b/frontend/src/app/main/ui/components/context_menu_a11y.cljs index ea81d8d50..04475e7c2 100644 --- a/frontend/src/app/main/ui/components/context_menu_a11y.cljs +++ b/frontend/src/app/main/ui/components/context_menu_a11y.cljs @@ -9,90 +9,107 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.schema :as sm] [app.main.refs :as refs] [app.main.ui.components.dropdown :refer [dropdown']] [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] - [app.util.object :as obj] [app.util.timers :as tm] - [goog.object :as gobj] [rumext.v2 :as mf])) -(defn generate-ids-group - [options parent-name] - (let [ids (->> options - (map :id) - (filter some?))] - (if parent-name - (cons "go-back-sub-option" ids) - ids))) +(def ^:private xf:options + (comp + (map :id) + (filter some?))) -(mf/defc context-menu-a11y-item - {::mf/wrap-props false} - [props] +(defn- generate-ids-group + [options has-parents?] + (let [ids (sequence xf:options options) + ids (if has-parents? + (cons "go-back-sub-option" ids) + ids)] + (vec ids))) - (let [children (gobj/get props "children") - on-click (gobj/get props "on-click") - on-key-down (gobj/get props "on-key-down") - id (gobj/get props "id") - klass (gobj/get props "class") - key-index (gobj/get props "key-index") - data-testid (gobj/get props "data-testid")] - [:li {:id id - :class klass - :tab-index "0" - :on-key-down on-key-down - :on-click on-click - :key key-index - :role "menuitem" - :data-testid data-testid} - children])) +(def ^:private schema:option + [:schema {:registry + {::option + [:or + :nil + [:map [:name [:= :separator]]] + [:and + [:map + [:name :string] + [:id :string] + [:handler {:optional true} fn?] + [:options {:optional true} + [:sequential [:ref ::option]]]] + [::sm/contains-any #{:handler :options}]]]}} + [:ref ::option]]) + +(def ^:private valid-option? + (sm/lazy-validator schema:option)) + +(mf/defc context-menu* + {::mf/props :obj} + + [{:keys [show on-close options selectable selected + top left fixed min-width origin width] + :as props}] + + (assert (every? valid-option? options) "expected valid options") + (assert (fn? on-close) "missing `on-close` prop") + (assert (boolean? show) "missing `show` prop") + (assert (vector? options) "missing `options` prop") + + (let [width (d/nilv width "initial") + min-width (d/nilv min-width false) + left (d/nilv left 0) + top (d/nilv top 0) -(mf/defc context-menu-a11y' - {::mf/wrap-props false} - [props] - (assert (fn? (gobj/get props "on-close")) "missing `on-close` prop") - (assert (boolean? (gobj/get props "show")) "missing `show` prop") - (assert (vector? (gobj/get props "options")) "missing `options` prop") - (let [open? (gobj/get props "show") - on-close (gobj/get props "on-close") - options (gobj/get props "options") - is-selectable (gobj/get props "selectable") - selected (gobj/get props "selected") - top (gobj/get props "top" 0) - left (gobj/get props "left" 0) - fixed? (gobj/get props "fixed?" false) - min-width? (gobj/get props "min-width?" false) - origin (gobj/get props "origin") route (mf/deref refs/route) in-dashboard? (= :dashboard-projects (:name (:data route))) - local (mf/use-state {:offset-y 0 - :offset-x 0 - :levels nil}) - width (gobj/get props "width" "initial") + state* (mf/use-state + #(-> {:offset-y 0 + :offset-x 0 + :levels nil})) + + state (deref state*) + offset-x (get state :offset-x) + offset-y (get state :offset-y) + levels (get state :levels) on-local-close - (mf/use-callback + (mf/use-fn + (mf/deps on-close) (fn [] - (swap! local assoc :levels [{:parent-option nil - :options options}]) + (swap! state* assoc :levels [{:parent nil + :options options}]) (on-close))) - props (obj/merge props #js {:on-close on-local-close}) + props + (mf/spread props :on-close on-local-close) + + ids + (mf/with-memo [levels] + (let [last-level (last levels)] + (generate-ids-group (:options last-level) + (:parent last-level)))) - ids (generate-ids-group (:options (last (:levels @local))) (:parent-option (last (:levels @local)))) check-menu-offscreen - (mf/use-callback - (mf/deps top (:offset-y @local) left (:offset-x @local)) + (mf/use-fn + (mf/deps top left offset-x offset-y) (fn [node] (when (some? node) - (let [bounding_rect (dom/get-bounding-rect node) - window_size (dom/get-window-size) - {node-height :height node-width :width} bounding_rect - {window-height :height window-width :width} window_size + (let [bounding-rect (dom/get-bounding-rect node) + window-size (dom/get-window-size) + node-height (dm/get-prop bounding-rect :height) + node-width (dm/get-prop bounding-rect :width) + window-height (get window-size :height) + window-width (get window-size :width) + target-offset-y (if (> (+ top node-height) window-height) (- node-height) 0) @@ -100,74 +117,86 @@ (- node-width) 0)] - (when (or (not= target-offset-y (:offset-y @local)) (not= target-offset-x (:offset-x @local))) - (swap! local assoc :offset-y target-offset-y :offset-x target-offset-x)))))) + (when (or (not= target-offset-y offset-y) + (not= target-offset-x offset-x)) + (swap! state* assoc + :offset-y target-offset-y + :offset-x target-offset-x)))))) + ;; NOTE: this function is used for build navigation callbacks + ;; so we don't really need to use the use-fn here. It is not + ;; an efficient approach but this manages a reasonable small + ;; list of objects, so doing it this way has no real + ;; implications on performance but facilitates a lot the + ;; implementation enter-submenu - (mf/use-callback - (mf/deps options) - (fn [option-name sub-options] - (fn [event] - (dom/stop-propagation event) - (swap! local update :levels - conj {:parent-option option-name - :options sub-options})))) - - exit-submenu - (mf/use-callback + (fn [name options] + (fn [event] + (dom/stop-propagation event) + (swap! state* update :levels conj {:parent name + :options options}))) + on-submenu-exit + (mf/use-fn (fn [event] (dom/stop-propagation event) - (swap! local update :levels pop))) + (swap! state* update :levels pop))) + ;; NOTE: this function is used for build navigation callbacks + ;; so we don't really need to use the use-fn here. It is not + ;; an efficient approach but this manages a reasonable small + ;; list of objects, so doing it this way has no real + ;; implications on performance but facilitates a lot the + ;; implementation on-key-down (fn [options-original parent-original] (fn [event] - (let [ids (generate-ids-group options-original parent-original) - first-id (dom/get-element (first ids)) - first-element (dom/get-element first-id) - len (count ids) - parent (dom/get-target event) - parent-id (dom/get-attribute parent "id") - option (first (filter #(= parent-id (:id %)) options-original)) - sub-options (:sub-options option) - has-suboptions? (some? (:sub-options option)) - option-handler (:option-handler option) - is-back-option (= "go-back-sub-option" parent-id)] + (let [ids (generate-ids-group options-original + parent-original) + first-id (dom/get-element (first ids)) + first-element (dom/get-element first-id) + len (count ids) + + parent (dom/get-target event) + parent-id (dom/get-attribute parent "id") + + option (d/seek #(= parent-id (:id %)) options-original) + sub-options (not-empty (:options option)) + handler (:handler option) + is-back-option? (= "go-back-sub-option" parent-id)] + (when (kbd/home? event) (when first-element (dom/focus! first-element))) (when (kbd/enter? event) - (if is-back-option - (exit-submenu event) + (if is-back-option? + (on-submenu-exit event) - (if has-suboptions? + (if sub-options (do (dom/stop-propagation event) - (swap! local update :levels - conj {:parent-option (:option-name option) - :options sub-options})) + (swap! state* update :levels conj {:parent (:name option) + :options sub-options})) (do (dom/stop-propagation event) - (option-handler event))))) + (handler event))))) - (when (and is-back-option - (kbd/left-arrow? event)) - (exit-submenu event)) + (when (and is-back-option? (kbd/left-arrow? event)) + (on-submenu-exit event)) - (when (and has-suboptions? (kbd/right-arrow? event)) + (when (and sub-options (kbd/right-arrow? event)) (dom/stop-propagation event) - (swap! local update :levels - conj {:parent-option (:option-name option) - :options sub-options})) + (swap! state* update :levels conj {:parent (:name option) + :options sub-options})) + (when (kbd/up-arrow? event) (let [actual-selected (dom/get-active) - actual-id (dom/get-attribute actual-selected "id") - actual-index (d/index-of ids actual-id) - previous-id (if (= 0 actual-index) - (last ids) - (nth ids (- actual-index 1)))] + actual-id (dom/get-attribute actual-selected "id") + actual-index (d/index-of ids actual-id) + previous-id (if (= 0 actual-index) + (last ids) + (nth ids (- actual-index 1)))] (dom/focus! (dom/get-element previous-id)))) (when (kbd/down-arrow? event) @@ -180,98 +209,87 @@ (dom/focus! (dom/get-element next-id)))) (when (or (kbd/esc? event) (kbd/tab? event)) - (on-close) + (on-close event) (dom/focus! (dom/get-element origin))))))] (mf/with-effect [options] - (swap! local assoc :levels [{:parent-option nil - :options options}])) + (swap! state* assoc :levels [{:parent nil + :options options}])) (mf/with-effect [ids] (tm/schedule-on-idle #(dom/focus! (dom/get-element (first ids))))) - (when (and open? (some? (:levels @local))) + (when (and show (some? levels)) [:> dropdown' props - (let [level (-> @local :levels peek) - original-options (:options level) - parent-original (:parent-option level)] - [:div {:class (stl/css-case :is-selectable is-selectable - :context-menu true - :is-open open? - :fixed fixed?) - :style {:top (+ top (:offset-y @local)) - :left (+ left (:offset-x @local))} - :on-key-down (on-key-down original-options parent-original)} - (let [level (-> @local :levels peek)] - [:ul {:class (stl/css-case :min-width min-width? - :context-menu-items true) - :style {:width width} - :role "menu" - :ref check-menu-offscreen} - (when-let [parent-option (:parent-option level)] - [:* - [:& context-menu-a11y-item - {:id "go-back-sub-option" - :class (stl/css :context-menu-item) - :tab-index "0" - :on-key-down (fn [event] - (dom/prevent-default event))} - [:button {:class (stl/css :context-menu-action :submenu-back) + (let [level (peek levels) + options (:options level) + parent (:parent level)] + + [:div {:class (stl/css-case + :is-selectable selectable + :context-menu true + :is-open show + :fixed fixed) + :style {:top (+ top offset-y) + :left (+ left offset-x)} + :on-key-down (on-key-down options parent)} + + [:ul {:class (stl/css-case :min-width min-width + :context-menu-items true) + :style {:width width} + :role "menu" + :ref check-menu-offscreen} + + (when-let [parent (:parent level)] + [:* + [:li {:id "go-back-sub-option" + :class (stl/css :context-menu-item) + :role "menuitem" + :tab-index "0" + :on-key-down dom/prevent-default} + [:button {:class (stl/css :context-menu-action :submenu-back) + :data-no-close true + :on-click on-submenu-exit} + [:span {:class (stl/css :submenu-icon-back)} i/arrow] + parent]] + + [:li {:class (stl/css :separator)}]]) + + (for [[index option] (d/enumerate (:options level))] + (let [name (:name option) + id (:id option) + sub-options (:options option) + handler (:handler option)] + (when name + (if (= name :separator) + [:li {:key (dm/str "context-item-" index) + :class (stl/css :separator)}] + [:li {:id id + :key id + :class (stl/css-case + :is-selected (and selected (= name selected)) + :selected (and selected (= id selected)) + :context-menu-item true) + :tab-index "0" + :role "menuitem" + :on-key-down dom/prevent-default} + (if-not sub-options + [:a {:class (stl/css :context-menu-action) + :on-click #(do (dom/stop-propagation %) + (on-close %) + (handler %)) + :data-testid id} + (if (and in-dashboard? (= name "Default")) + (tr "dashboard.default-team-name") + name) + + (when (and selected (= id selected)) + [:span {:class (stl/css :selected-icon)} i/tick])] + + [:a {:class (stl/css :context-menu-action :submenu) :data-no-close true - :on-click exit-submenu} - [:span {:class (stl/css :submenu-icon-back)} i/arrow] - parent-option]] - - [:li {:class (stl/css :separator)}]]) - - (for [[index option] (d/enumerate (:options level))] - (let [option-name (:option-name option) - id (:id option) - sub-options (:sub-options option) - option-handler (:option-handler option) - data-testid (:data-testid option)] - (when option-name - (if (= option-name :separator) - [:li {:key (dm/str "context-item-" index) - :class (stl/css :separator)}] - [:& context-menu-a11y-item - {:id id - :key id - :class (stl/css-case - :is-selected (and selected (= option-name selected)) - :selected (and selected (= data-testid selected)) - :context-menu-item true) - :key-index (dm/str "context-item-" index) - :tab-index "0" - :on-key-down (fn [event] - (dom/prevent-default event))} - (if-not sub-options - [:a {:class (stl/css :context-menu-action) - :on-click #(do (dom/stop-propagation %) - (on-close) - (option-handler %)) - :data-testid data-testid} - (if (and in-dashboard? (= option-name "Default")) - (tr "dashboard.default-team-name") - option-name) - - (when (and selected (= data-testid selected)) - [:span {:class (stl/css :selected-icon)} i/tick])] - - [:a {:class (stl/css :context-menu-action :submenu) - :data-no-close true - :on-click (enter-submenu option-name sub-options) - :data-testid data-testid} - option-name - [:span {:class (stl/css :submenu-icon)} i/arrow]])]))))])])]))) - -(mf/defc context-menu-a11y - {::mf/wrap-props false} - [props] - (assert (fn? (gobj/get props "on-close")) "missing `on-close` prop") - (assert (boolean? (gobj/get props "show")) "missing `show` prop") - (assert (vector? (gobj/get props "options")) "missing `options` prop") - - (when (gobj/get props "show") - (mf/element context-menu-a11y' props))) + :on-click (enter-submenu name sub-options) + :data-testid id} + name + [:span {:class (stl/css :submenu-icon)} i/arrow]])]))))]])]))) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index aa87cb907..f7e46bf60 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -14,7 +14,7 @@ [app.main.refs :as refs] [app.main.repo :as rp] [app.main.store :as st] - [app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]] + [app.main.ui.components.context-menu-a11y :refer [context-menu*]] [app.main.ui.context :as ctx] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] @@ -221,113 +221,118 @@ (reset! teams %))))))) (when current-team - (let [sub-options (concat (vec (for [project current-projects] - {:option-name (get-project-name project) - :id (get-project-id project) - :option-handler (on-move (:id current-team) - (:id project))})) - (when (seq other-teams) - [{:option-name (tr "dashboard.move-to-other-team") - :id "move-to-other-team" - :sub-options - (for [team other-teams] - {:option-name (get-team-name team) - :id (get-project-id team) - :sub-options - (for [sub-project (:projects team)] - {:option-name (get-project-name sub-project) - :id (get-project-id sub-project) - :option-handler (on-move (:id team) - (:id sub-project))})})}])) + (let [sub-options + (concat + (for [project current-projects] + {:name (get-project-name project) + :id (get-project-id project) + :handler (on-move (:id current-team) + (:id project))}) + (when (seq other-teams) + [{:name (tr "dashboard.move-to-other-team") + :id "move-to-other-team" + :options + (for [team other-teams] + {:name (get-team-name team) + :id (get-project-id team) + :options + (for [sub-project (:projects team)] + {:name (get-project-name sub-project) + :id (get-project-id sub-project) + :handler (on-move (:id team) + (:id sub-project))})})}])) - options (if multi? - [(when-not you-viewer? - {:option-name (tr "dashboard.duplicate-multi" file-count) - :id "file-duplicate-multi" - :option-handler on-duplicate - :data-testid "duplicate-multi"}) - (when (and (or (seq current-projects) (seq other-teams)) - (not you-viewer?)) - {:option-name (tr "dashboard.move-to-multi" file-count) - :id "file-move-multi" - :sub-options sub-options - :data-testid "move-to-multi"}) - {:option-name (tr "dashboard.export-binary-multi" file-count) - :id "file-binari-export-multi" - :option-handler on-export-binary-files} - {:option-name (tr "dashboard.export-standard-multi" file-count) - :id "file-standard-export-multi" - :option-handler on-export-standard-files} - (when (and (:is-shared file) - (not you-viewer?)) - {:option-name (tr "labels.unpublish-multi-files" file-count) - :id "file-unpublish-multi" - :option-handler on-del-shared - :data-testid "file-del-shared"}) - (when (and (not is-lib-page?) - (not you-viewer?)) - {:option-name :separator} - {:option-name (tr "labels.delete-multi-files" file-count) - :id "file-delete-multi" - :option-handler on-delete - :data-testid "delete-multi-files"})] + options + (if multi? + [(when-not you-viewer? + {:name (tr "dashboard.duplicate-multi" file-count) + :id "duplicate-multi" + :handler on-duplicate}) - [{:option-name (tr "dashboard.open-in-new-tab") - :id "file-open-new-tab" - :option-handler on-new-tab} - (when (and (not is-search-page?) - (not you-viewer?)) - {:option-name (tr "labels.rename") - :id "file-rename" - :option-handler on-edit - :data-testid "file-rename"}) - (when (and (not is-search-page?) - (not you-viewer?)) - {:option-name (tr "dashboard.duplicate") - :id "file-duplicate" - :option-handler on-duplicate - :data-testid "file-duplicate"}) - (when (and (not is-lib-page?) - (not is-search-page?) - (or (seq current-projects) (seq other-teams)) - (not you-viewer?)) - {:option-name (tr "dashboard.move-to") - :id "file-move-to" - :sub-options sub-options - :data-testid "file-move-to"}) - (when (and (not is-search-page?) - (not you-viewer?)) - (if (:is-shared file) - {:option-name (tr "dashboard.unpublish-shared") - :id "file-del-shared" - :option-handler on-del-shared - :data-testid "file-del-shared"} - {:option-name (tr "dashboard.add-shared") - :id "file-add-shared" - :option-handler on-add-shared - :data-testid "file-add-shared"})) - {:option-name :separator} - {:option-name (tr "dashboard.download-binary-file") - :id "file-download-binary" - :option-handler on-export-binary-files - :data-testid "download-binary-file"} - {:option-name (tr "dashboard.download-standard-file") - :id "file-download-standard" - :option-handler on-export-standard-files - :data-testid "download-standard-file"} - (when (and (not is-lib-page?) (not is-search-page?) (not you-viewer?)) - {:option-name :separator} - {:option-name (tr "labels.delete") - :id "file-delete" - :option-handler on-delete - :data-testid "file-delete"})])] + (when (and (or (seq current-projects) (seq other-teams)) + (not you-viewer?)) + {:name (tr "dashboard.move-to-multi" file-count) + :id "file-move-multi" + :options sub-options}) - [:& context-menu-a11y {:on-close on-menu-close - :show show? - :fixed? (or (not= top 0) (not= left 0)) - :min-width? true - :top top - :left left - :options options - :origin parent-id - :workspace? false}])))) + {:name (tr "dashboard.export-binary-multi" file-count) + :id "file-binari-export-multi" + :handler on-export-binary-files} + + {:name (tr "dashboard.export-standard-multi" file-count) + :id "file-standard-export-multi" + :handler on-export-standard-files} + + (when (and (:is-shared file) + (not you-viewer?)) + {:name (tr "labels.unpublish-multi-files" file-count) + :id "file-unpublish-multi" + :handler on-del-shared}) + + (when (and (not is-lib-page?) + (not you-viewer?)) + {:name :separator} + {:name (tr "labels.delete-multi-files" file-count) + :id "file-delete-multi" + :handler on-delete})] + + [{:name (tr "dashboard.open-in-new-tab") + :id "file-open-new-tab" + :handler on-new-tab} + (when (and (not is-search-page?) + (not you-viewer?)) + {:name (tr "labels.rename") + :id "file-rename" + :handler on-edit}) + + (when (and (not is-search-page?) + (not you-viewer?)) + {:name (tr "dashboard.duplicate") + :id "file-duplicate" + :handler on-duplicate}) + + (when (and (not is-lib-page?) + (not is-search-page?) + (or (seq current-projects) (seq other-teams)) + (not you-viewer?)) + {:name (tr "dashboard.move-to") + :id "file-move-to" + :options sub-options}) + + (when (and (not is-search-page?) + (not you-viewer?)) + (if (:is-shared file) + {:name (tr "dashboard.unpublish-shared") + :id "file-del-shared" + :handler on-del-shared} + {:name (tr "dashboard.add-shared") + :id "file-add-shared" + :handler on-add-shared})) + + {:name :separator} + + {:name (tr "dashboard.download-binary-file") + :id "download-binary-file" + :handler on-export-binary-files} + + {:name (tr "dashboard.download-standard-file") + :id "download-standard-file" + :handler on-export-standard-files} + + (when (and (not is-lib-page?) (not is-search-page?) (not you-viewer?)) + {:name :separator}) + + (when (and (not is-lib-page?) (not is-search-page?) (not you-viewer?)) + {:name (tr "labels.delete") + :id "file-delete" + :handler on-delete})])] + + [:> context-menu* + {:on-close on-menu-close + :show show? + :fixed (or (not= top 0) (not= left 0)) + :min-width true + :top top + :left left + :options options + :origin parent-id}])))) diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index 9c2f09cf2..1b6b87ad0 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -14,7 +14,7 @@ [app.main.refs :as refs] [app.main.repo :as rp] [app.main.store :as st] - [app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]] + [app.main.ui.components.context-menu-a11y :refer [context-menu*]] [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]] [app.main.ui.icons :as i] @@ -250,21 +250,20 @@ ::mf/private true} [{:keys [is-open on-close on-edit on-delete]}] (let [options (mf/with-memo [on-edit on-delete] - [{:option-name (tr "labels.edit") - :id "font-edit" - :option-handler on-edit} - {:option-name (tr "labels.delete") - :id "font-delete" - :option-handler on-delete}])] - [:& context-menu-a11y + [{:name (tr "labels.edit") + :id "font-edit" + :handler on-edit} + {:name (tr "labels.delete") + :id "font-delete" + :handler on-delete}])] + [:> context-menu* {:on-close on-close :show is-open - :fixed? false - :min-width? true + :fixed false + :min-width true :top -15 :left -115 - :options options - :workspace? false}])) + :options options}])) (mf/defc installed-font {::mf/props :obj diff --git a/frontend/src/app/main/ui/dashboard/project_menu.cljs b/frontend/src/app/main/ui/dashboard/project_menu.cljs index 36293395d..f3168a87c 100644 --- a/frontend/src/app/main/ui/dashboard/project_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/project_menu.cljs @@ -11,7 +11,7 @@ [app.main.data.notifications :as ntf] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]] + [app.main.ui.components.context-menu-a11y :refer [context-menu*]] [app.main.ui.context :as ctx] [app.main.ui.dashboard.import :as udi] [app.util.dom :as dom] @@ -81,53 +81,50 @@ (fn [] (when (fn? on-import) (on-import)))) - options [(when-not (:is-default project) - {:option-name (tr "labels.rename") - :id "project-menu-rename" - :option-handler on-edit - :data-testid "project-rename"}) - (when-not (:is-default project) - {:option-name (tr "dashboard.duplicate") - :id "project-menu-duplicated" - :option-handler on-duplicate - :data-testid "project-duplicate"}) - (when-not (:is-default project) - {:option-name (tr "dashboard.pin-unpin") - :id "project-menu-pin" - :option-handler toggle-pin}) + options + [(when-not (:is-default project) + {:name (tr "labels.rename") + :id "project-rename" + :handler on-edit}) + (when-not (:is-default project) + {:name (tr "dashboard.duplicate") + :id "project-duplicate" + :handler on-duplicate}) + (when-not (:is-default project) + {:name (tr "dashboard.pin-unpin") + :id "project-pin" + :handler toggle-pin}) - (when (and (seq teams) (not (:is-default project))) - {:option-name (tr "dashboard.move-to") - :id "project-menu-move-to" - :sub-options (for [team teams] - {:option-name (:name team) - :id (:name team) - :option-handler (on-move (:id team))}) - :data-testid "project-move-to"}) - (when (some? on-import) - {:option-name (tr "dashboard.import") - :id "project-menu-import" - :option-handler on-import-files - :data-testid "file-import"}) - (when-not (:is-default project) - {:option-name :separator}) - (when-not (:is-default project) - {:option-name (tr "labels.delete") - :id "project-menu-delete" - :option-handler on-delete - :data-testid "project-delete"})]] + (when (and (seq teams) (not (:is-default project))) + {:name (tr "dashboard.move-to") + :id "project-move-to" + :options (for [team teams] + {:name (:name team) + :id (str "move-to-" (:id team)) + :handler (on-move (:id team))})}) + + (when (some? on-import) + {:name (tr "dashboard.import") + :id "file-import" + :handler on-import-files}) + (when-not (:is-default project) + {:name :separator}) + (when-not (:is-default project) + {:name (tr "labels.delete") + :id "project-delete" + :handler on-delete})]] [:* - [:& context-menu-a11y + [:> context-menu* {:on-close on-menu-close :show show? - :fixed? (or (not= top 0) (not= left 0)) - :min-width? true + :fixed (or (not= top 0) (not= left 0)) + :min-width true :top top :left left - :options options - :workspace false}] + :options options}] [:& udi/import-form {:ref file-input :project-id (:id project) :on-finish-import on-finish-import}]])) + diff --git a/frontend/src/app/main/ui/settings/access_tokens.cljs b/frontend/src/app/main/ui/settings/access_tokens.cljs index f084b0366..deae25f54 100644 --- a/frontend/src/app/main/ui/settings/access_tokens.cljs +++ b/frontend/src/app/main/ui/settings/access_tokens.cljs @@ -12,7 +12,7 @@ [app.main.data.notifications :as ntf] [app.main.data.users :as du] [app.main.store :as st] - [app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]] + [app.main.ui.components.context-menu-a11y :refer [context-menu*]] [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] [app.util.dom :as dom] @@ -195,9 +195,9 @@ (let [local (mf/use-state {:menu-open false}) show? (:menu-open @local) options (mf/with-memo [on-delete] - [{:option-name (tr "labels.delete") - :id "access-token-delete" - :option-handler on-delete}]) + [{:name (tr "labels.delete") + :id "access-token-delete" + :handler on-delete}]) menu-ref (mf/use-ref) @@ -224,11 +224,11 @@ :on-click on-menu-click :on-key-down on-keydown} menu-icon - [:& context-menu-a11y + [:> context-menu* {:on-close on-menu-close :show show? - :fixed? true - :min-width? true + :fixed true + :min-width true :top "auto" :left "auto" :options options}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index a1ec84728..7c4a6632c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -13,7 +13,7 @@ [app.main.data.workspace.assets :as dwa] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]] + [app.main.ui.components.context-menu-a11y :refer [context-menu*]] [app.main.ui.components.search-bar :refer [search-bar]] [app.main.ui.context :as ctx] [app.main.ui.icons :as i] @@ -130,32 +130,26 @@ on-menu-close (mf/use-fn #(swap! filters* assoc :open-menu false)) - options (into [] (remove nil? - [{:option-name (tr "workspace.assets.box-filter-all") - :id "section-all" - :option-handler on-section-filter-change - :data-testid "all"} + options + [{:name (tr "workspace.assets.box-filter-all") + :id "section-all" + :handler on-section-filter-change} + {:name (tr "workspace.assets.components") + :id "section-components" + :handler on-section-filter-change} - {:option-name (tr "workspace.assets.components") - :id "section-components" - :option-handler on-section-filter-change - :data-testid "components"} + (when (not components-v2) + {:name (tr "workspace.assets.graphics") + :id "section-graphics" + :handler on-section-filter-change}) - (when (not components-v2) - {:option-name (tr "workspace.assets.graphics") - :id "section-graphics" - :option-handler on-section-filter-change - :data-testid "graphics"}) + {:name (tr "workspace.assets.colors") + :id "section-colors" + :handler on-section-filter-change} - {:option-name (tr "workspace.assets.colors") - :id "section-color" - :option-handler on-section-filter-change - :data-testid "colors"} - - {:option-name (tr "workspace.assets.typography") - :id "section-typography" - :option-handler on-section-filter-change - :data-testid "typographies"}]))] + {:name (tr "workspace.assets.typography") + :id "section-typographies" + :handler on-section-filter-change}]] [:article {:class (stl/css :assets-bar)} [:div {:class (stl/css :assets-header)} @@ -177,18 +171,17 @@ :class (stl/css-case :section-button true :opened menu-open?)} i/filter-icon]] - [:& context-menu-a11y + [:> context-menu* {:on-close on-menu-close :selectable true :selected section :show menu-open? - :fixed? true - :min-width? true + :fixed true + :min-width true :width size :top 158 :left 18 - :options options - :workspace? true}] + :options options}] [:button {:class (stl/css :sort-button) :title (tr "workspace.assets.sort") :on-click toggle-ordering} diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs index 06e12e3f0..dd8e0156f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs @@ -240,21 +240,21 @@ {:on-close on-close-menu :state @menu-state :options [(when-not (or multi-colors? multi-assets?) - {:option-name (tr "workspace.assets.rename") - :id "assets-rename-color" - :option-handler rename-color-clicked}) + {:name (tr "workspace.assets.rename") + :id "assets-rename-color" + :handler rename-color-clicked}) (when-not (or multi-colors? multi-assets?) - {:option-name (tr "workspace.assets.edit") - :id "assets-edit-color" - :option-handler edit-color-clicked}) + {:name (tr "workspace.assets.edit") + :id "assets-edit-color" + :handler edit-color-clicked}) - {:option-name (tr "workspace.assets.delete") - :id "assets-delete-color" - :option-handler delete-color} + {:name (tr "workspace.assets.delete") + :id "assets-delete-color" + :handler delete-color} (when-not multi-assets? - {:option-name (tr "workspace.assets.group") - :id "assets-group-color" - :option-handler (on-group (:id color))})]}]) + {:name (tr "workspace.assets.group") + :id "assets-group-color" + :handler (on-group (:id color))})]}]) (when ^boolean dragging? [:div {:class (stl/css :dragging)}])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs index 25a6252aa..080e64c40 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs @@ -22,7 +22,7 @@ [app.main.refs :as refs] [app.main.render :refer [component-svg component-svg-thumbnail]] [app.main.store :as st] - [app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]] + [app.main.ui.components.context-menu-a11y :refer [context-menu*]] [app.main.ui.components.title-bar :refer [title-bar]] [app.main.ui.context :as ctx] [app.main.ui.icons :as i] @@ -111,14 +111,13 @@ (mf/defc assets-context-menu {::mf/wrap-props false} [{:keys [options state on-close]}] - [:& context-menu-a11y + [:> context-menu* {:show (:open? state) - :fixed? (or (not= (:top state) 0) (not= (:left state) 0)) + :fixed (or (not= (:top state) 0) (not= (:left state) 0)) :on-close on-close :top (:top state) :left (:left state) - :options options - :workspace? true}]) + :options options}]) (mf/defc section-icon {::mf/wrap-props false} diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs index 7461f4202..398bd9a10 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs @@ -559,26 +559,26 @@ {:on-close on-close-menu :state @menu-state :options [(when (and local? (not (or multi-components? multi-assets? read-only?))) - {:option-name (tr "workspace.assets.rename") - :id "assets-rename-component" - :option-handler on-rename}) + {:name (tr "workspace.assets.rename") + :id "assets-rename-component" + :handler on-rename}) (when (and local? (not (or multi-assets? read-only?))) - {:option-name (if components-v2 - (tr "workspace.assets.duplicate-main") - (tr "workspace.assets.duplicate")) - :id "assets-duplicate-component" - :option-handler on-duplicate}) + {:name (if components-v2 + (tr "workspace.assets.duplicate-main") + (tr "workspace.assets.duplicate")) + :id "assets-duplicate-component" + :handler on-duplicate}) (when (and local? (not read-only?)) - {:option-name (tr "workspace.assets.delete") - :id "assets-delete-component" - :option-handler on-delete}) + {:name (tr "workspace.assets.delete") + :id "assets-delete-component" + :handler on-delete}) (when (and local? (not (or multi-assets? read-only?))) - {:option-name (tr "workspace.assets.group") - :id "assets-group-component" - :option-handler on-group}) + {:name (tr "workspace.assets.group") + :id "assets-group-component" + :handler on-group}) (when (and components-v2 (not multi-assets?)) - {:option-name (tr "workspace.shape.menu.show-main") - :id "assets-show-main-component" - :option-handler on-show-main})]}]]])) + {:name (tr "workspace.shape.menu.show-main") + :id "assets-show-main-component" + :handler on-show-main})]}]]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/graphics.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/graphics.cljs index f8c7e2b8a..a1bb804cb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/graphics.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/graphics.cljs @@ -418,13 +418,13 @@ {:on-close on-close-menu :state @menu-state :options [(when-not (or multi-objects? multi-assets?) - {:option-name (tr "workspace.assets.rename") - :id "assets-rename-graphics" - :option-handler on-rename}) - {:option-name (tr "workspace.assets.delete") - :id "assets-delete-graphics" - :option-handler on-delete} + {:name (tr "workspace.assets.rename") + :id "assets-rename-graphics" + :handler on-rename}) + {:name (tr "workspace.assets.delete") + :id "assets-delete-graphics" + :handler on-delete} (when-not multi-assets? - {:option-name (tr "workspace.assets.group") - :id "assets-group-graphics" - :option-handler on-group})]}])]])) + {:name (tr "workspace.assets.group") + :id "assets-group-graphics" + :handler on-group})]}])]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs index 82d97180d..3bc12106a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs @@ -59,12 +59,12 @@ [:& cmm/assets-context-menu {:on-close on-close-menu :state @menu-state - :options [{:option-name (tr "workspace.assets.rename") - :id "assets-rename-group" - :option-handler #(on-rename % path last-path)} - {:option-name (tr "workspace.assets.ungroup") - :id "assets-ungroup-group" - :option-handler #(on-ungroup path)}]}]]))) + :options [{:name (tr "workspace.assets.rename") + :id "assets-rename-group" + :handler #(on-rename % path last-path)} + {:name (tr "workspace.assets.ungroup") + :id "assets-ungroup-group" + :handler #(on-ungroup path)}]}]]))) (defn group-assets "Convert a list of assets in a nested structure like this: diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs index 5b8f2a3a8..33b12a9c9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs @@ -434,27 +434,27 @@ {:on-close on-close-menu :state @menu-state :options [(when-not (or multi-typographies? multi-assets?) - {:option-name (tr "workspace.assets.rename") - :id "assets-rename-typography" - :option-handler handle-rename-typography-clicked}) + {:name (tr "workspace.assets.rename") + :id "assets-rename-typography" + :handler handle-rename-typography-clicked}) (when-not (or multi-typographies? multi-assets?) - {:option-name (tr "workspace.assets.edit") - :id "assets-edit-typography" - :option-handler handle-edit-typography-clicked}) + {:name (tr "workspace.assets.edit") + :id "assets-edit-typography" + :handler handle-edit-typography-clicked}) - {:option-name (tr "workspace.assets.delete") - :id "assets-delete-typography" - :option-handler handle-delete-typography} + {:name (tr "workspace.assets.delete") + :id "assets-delete-typography" + :handler handle-delete-typography} (when-not multi-assets? - {:option-name (tr "workspace.assets.group") - :id "assets-group-typography" - :option-handler on-group})]}] + {:name (tr "workspace.assets.group") + :id "assets-group-typography" + :handler on-group})]}] [:& cmm/assets-context-menu {:on-close on-close-menu :state @menu-state - :options [{:option-name "show info" - :id "assets-rename-typography" - :option-handler handle-edit-typography-clicked}]}])]]])) + :options [{:name "show info" + :id "assets-rename-typography" + :handler handle-edit-typography-clicked}]}])]]]))