0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-27 23:21:47 -05:00

♻️ Refactor context-menu component

This commit is contained in:
Andrey Antukh 2024-10-15 17:51:29 +02:00
parent 782d733bc9
commit 88d85706ad
12 changed files with 460 additions and 449 deletions

View file

@ -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]])]))))]])])))

View file

@ -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}]))))

View file

@ -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

View file

@ -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}]]))

View file

@ -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}]]))

View file

@ -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}

View file

@ -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)}])]))

View file

@ -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}

View file

@ -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})]}]]]))

View file

@ -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})]}])]]))

View file

@ -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:

View file

@ -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}]}])]]]))