mirror of
https://github.com/penpot/penpot.git
synced 2025-05-30 11:06:11 +02:00
131 lines
5.6 KiB
Clojure
131 lines
5.6 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns app.main.ui.components.context-menu
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.data.macros :as dm]
|
|
[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.object :as obj]
|
|
[goog.object :as gobj]
|
|
[rumext.alpha :as mf]))
|
|
|
|
(mf/defc context-menu
|
|
{::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)
|
|
route (mf/deref refs/route)
|
|
in-dashboard? (= :dashboard-projects (:name (:data route)))
|
|
|
|
local (mf/use-state {:offset-y 0
|
|
:offset-x 0
|
|
:levels nil})
|
|
|
|
on-local-close
|
|
(mf/use-callback
|
|
(fn []
|
|
(swap! local assoc :levels [{:parent-option nil
|
|
:options options}])
|
|
(on-close)))
|
|
|
|
check-menu-offscreen
|
|
(mf/use-callback
|
|
(mf/deps top (:offset-y @local) left (:offset-x @local))
|
|
(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
|
|
target-offset-y (if (> (+ top node-height) window-height)
|
|
(- node-height)
|
|
0)
|
|
target-offset-x (if (> (+ left node-width) window-width)
|
|
(- 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))))))
|
|
|
|
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 [event]
|
|
(dom/stop-propagation event)
|
|
(swap! local update :levels pop)))
|
|
|
|
props (obj/merge props #js {:on-close on-local-close})]
|
|
|
|
(mf/use-effect
|
|
(mf/deps options)
|
|
#(swap! local assoc :levels [{:parent-option nil
|
|
:options options}]))
|
|
|
|
(when (and open? (some? (:levels @local)))
|
|
[:> dropdown' props
|
|
[:div.context-menu {:class (dom/classnames :is-open open?
|
|
:fixed fixed?
|
|
:is-selectable is-selectable)
|
|
:style {:top (+ top (:offset-y @local))
|
|
:left (+ left (:offset-x @local))}}
|
|
(let [level (-> @local :levels peek)]
|
|
[:ul.context-menu-items {:class (dom/classnames :min-width min-width?)
|
|
:ref check-menu-offscreen}
|
|
(when-let [parent-option (:parent-option level)]
|
|
[:*
|
|
[:li.context-menu-item
|
|
[:a.context-menu-action.submenu-back
|
|
{:data-no-close true
|
|
:on-click exit-submenu}
|
|
[:span i/arrow-slide]
|
|
parent-option]]
|
|
[:li.separator]])
|
|
(for [[index [option-name option-handler sub-options data-test]] (d/enumerate (:options level))]
|
|
(when option-name
|
|
(if (= option-name :separator)
|
|
[:li.separator {:key (dm/str "context-item-" index)}]
|
|
[:li.context-menu-item
|
|
{:class (dom/classnames :is-selected (and selected (= option-name selected)))
|
|
:key (dm/str "context-item-" index)}
|
|
(if-not sub-options
|
|
[:a.context-menu-action {:on-click #(do (dom/stop-propagation %)
|
|
(on-close)
|
|
(option-handler %))
|
|
:data-test data-test}
|
|
(if (and in-dashboard? (= option-name "Default"))
|
|
(tr "dashboard.default-team-name")
|
|
option-name)]
|
|
[:a.context-menu-action.submenu
|
|
{:data-no-close true
|
|
:on-click (enter-submenu option-name sub-options)
|
|
:data-test data-test}
|
|
option-name
|
|
[:span i/arrow-slide]])])))])]])))
|