mirror of
https://github.com/penpot/penpot.git
synced 2025-06-15 12:51:40 +02:00
✨ Make modal work through react portal mechanism
The rationale behind this change is to allow use of already declared react context on modals; because with portals, react propagates top context to the children, independently if they are direct descendant on dom or not.
This commit is contained in:
parent
7758d5f747
commit
d8a3c10191
7 changed files with 60 additions and 58 deletions
|
@ -20,8 +20,8 @@
|
||||||
:git/url "https://github.com/funcool/beicon.git"}
|
:git/url "https://github.com/funcool/beicon.git"}
|
||||||
|
|
||||||
funcool/rumext
|
funcool/rumext
|
||||||
{:git/tag "v2.14"
|
{:git/tag "v2.15"
|
||||||
:git/sha "0016623"
|
:git/sha "28783a7"
|
||||||
:git/url "https://github.com/funcool/rumext.git"}
|
:git/url "https://github.com/funcool/rumext.git"}
|
||||||
|
|
||||||
instaparse/instaparse {:mvn/version "1.5.0"}
|
instaparse/instaparse {:mvn/version "1.5.0"}
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
[app.main.ui.confirm]
|
[app.main.ui.confirm]
|
||||||
[app.main.ui.css-cursors :as cur]
|
[app.main.ui.css-cursors :as cur]
|
||||||
[app.main.ui.delete-shared]
|
[app.main.ui.delete-shared]
|
||||||
[app.main.ui.modal :refer [modal]]
|
|
||||||
[app.main.ui.routes :as rt]
|
[app.main.ui.routes :as rt]
|
||||||
[app.main.worker :as worker]
|
[app.main.worker :as worker]
|
||||||
[app.plugins :as plugins]
|
[app.plugins :as plugins]
|
||||||
|
@ -52,14 +51,9 @@
|
||||||
(let [el (dom/get-element "app")]
|
(let [el (dom/get-element "app")]
|
||||||
(mf/create-root el)))
|
(mf/create-root el)))
|
||||||
|
|
||||||
(defonce modal-root
|
|
||||||
(let [el (dom/get-element "modal")]
|
|
||||||
(mf/create-root el)))
|
|
||||||
|
|
||||||
(defn init-ui
|
(defn init-ui
|
||||||
[]
|
[]
|
||||||
(mf/render! app-root (mf/element ui/app))
|
(mf/render! app-root (mf/element ui/app)))
|
||||||
(mf/render! modal-root (mf/element modal)))
|
|
||||||
|
|
||||||
(defn- initialize-profile
|
(defn- initialize-profile
|
||||||
"Event used mainly on application bootstrap; it fetches the profile
|
"Event used mainly on application bootstrap; it fetches the profile
|
||||||
|
@ -132,9 +126,7 @@
|
||||||
;; The hard flag will force to unmount the whole UI and will redraw every component
|
;; The hard flag will force to unmount the whole UI and will redraw every component
|
||||||
(when hard?
|
(when hard?
|
||||||
(mf/unmount! app-root)
|
(mf/unmount! app-root)
|
||||||
(mf/unmount! modal-root)
|
(set! app-root (mf/create-root (dom/get-element "app"))))
|
||||||
(set! app-root (mf/create-root (dom/get-element "app")))
|
|
||||||
(set! modal-root (mf/create-root (dom/get-element "modal"))))
|
|
||||||
(st/emit! (ev/initialize))
|
(st/emit! (ev/initialize))
|
||||||
(init-ui)))
|
(init-ui)))
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
[app.main.ui.dashboard.team :refer [team-settings-page* team-members-page* team-invitations-page* webhooks-page*]]
|
[app.main.ui.dashboard.team :refer [team-settings-page* team-members-page* team-invitations-page* webhooks-page*]]
|
||||||
[app.main.ui.dashboard.templates :refer [templates-section*]]
|
[app.main.ui.dashboard.templates :refer [templates-section*]]
|
||||||
[app.main.ui.hooks :as hooks]
|
[app.main.ui.hooks :as hooks]
|
||||||
|
[app.main.ui.modal :refer [modal-container*]]
|
||||||
[app.main.ui.workspace.plugins]
|
[app.main.ui.workspace.plugins]
|
||||||
[app.plugins.register :as preg]
|
[app.plugins.register :as preg]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
|
@ -240,6 +241,7 @@
|
||||||
(use-plugin-register plugin-url team-id (:id default-project))
|
(use-plugin-register plugin-url team-id (:id default-project))
|
||||||
|
|
||||||
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
||||||
|
[:> modal-container*]
|
||||||
;; NOTE: dashboard events and other related functions assumes
|
;; NOTE: dashboard events and other related functions assumes
|
||||||
;; that the team is a implicit context variable that is
|
;; that the team is a implicit context variable that is
|
||||||
;; available using react context or accessing
|
;; available using react context or accessing
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
(ns app.main.ui.modal
|
(ns app.main.ui.modal
|
||||||
(:require-macros [app.main.style :as stl])
|
(:require-macros [app.main.style :as stl])
|
||||||
(:require
|
(:require
|
||||||
[app.main.data.modal :as dm]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.main.data.modal :as modal]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.keyboard :as k]
|
[app.util.keyboard :as k]
|
||||||
|
@ -20,13 +21,13 @@
|
||||||
[event allow-click-outside]
|
[event allow-click-outside]
|
||||||
(when (and (k/esc? event) (not allow-click-outside))
|
(when (and (k/esc? event) (not allow-click-outside))
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(st/emit! (dm/hide))))
|
(st/emit! (modal/hide))))
|
||||||
|
|
||||||
(defn- on-pop-state
|
(defn- on-pop-state
|
||||||
[event]
|
[event]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(st/emit! (dm/hide))
|
(st/emit! (modal/hide))
|
||||||
(.forward js/history))
|
(.forward js/history))
|
||||||
|
|
||||||
(defn- on-click-outside
|
(defn- on-click-outside
|
||||||
|
@ -41,15 +42,14 @@
|
||||||
(= (.-button event) 0))
|
(= (.-button event) 0))
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(st/emit! (dm/hide)))))
|
(st/emit! (modal/hide)))))
|
||||||
|
|
||||||
(mf/defc modal-wrapper
|
(mf/defc modal-wrapper*
|
||||||
{::mf/wrap-props false
|
{::mf/props :obj
|
||||||
::mf/wrap [mf/memo]}
|
::mf/wrap [mf/memo]}
|
||||||
[props]
|
[{:keys [data]}]
|
||||||
(let [data (unchecked-get props "data")
|
(let [wrapper-ref (mf/use-ref nil)
|
||||||
wrapper-ref (mf/use-ref nil)
|
components (mf/deref modal/components)
|
||||||
components (mf/deref dm/components)
|
|
||||||
|
|
||||||
allow-click-outside (:allow-click-outside data)
|
allow-click-outside (:allow-click-outside data)
|
||||||
|
|
||||||
|
@ -61,9 +61,7 @@
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(on-esc-clicked event allow-click-outside))]
|
(on-esc-clicked event allow-click-outside))]
|
||||||
|
|
||||||
(mf/use-layout-effect
|
(mf/with-effect [allow-click-outside]
|
||||||
(mf/deps allow-click-outside)
|
|
||||||
(fn []
|
|
||||||
(let [keys [(events/listen js/window EventType.POPSTATE on-pop-state)
|
(let [keys [(events/listen js/window EventType.POPSTATE on-pop-state)
|
||||||
(events/listen js/document EventType.KEYDOWN handle-keydown)
|
(events/listen js/document EventType.KEYDOWN handle-keydown)
|
||||||
|
|
||||||
|
@ -71,21 +69,21 @@
|
||||||
(events/listen (dom/get-root) EventType.POINTERDOWN handle-click-outside)
|
(events/listen (dom/get-root) EventType.POINTERDOWN handle-click-outside)
|
||||||
|
|
||||||
(events/listen js/document EventType.CONTEXTMENU handle-click-outside)]]
|
(events/listen js/document EventType.CONTEXTMENU handle-click-outside)]]
|
||||||
#(doseq [key keys]
|
(fn []
|
||||||
(events/unlistenByKey key)))))
|
(run! events/unlistenByKey keys))))
|
||||||
|
|
||||||
(when-let [component (get components (:type data))]
|
(when-let [component (get components (:type data))]
|
||||||
[:div {:ref wrapper-ref
|
[:div {:ref wrapper-ref
|
||||||
:class (stl/css :modal-wrapper)}
|
:class (stl/css :modal-wrapper)}
|
||||||
(mf/element component (:props data))])))
|
(mf/element component (:props data))])))
|
||||||
|
|
||||||
(def modal-ref
|
(def ^:private ref:modal
|
||||||
(l/derived ::dm/modal st/state))
|
(l/derived ::modal/modal st/state))
|
||||||
|
|
||||||
(mf/defc modal
|
(mf/defc modal-container*
|
||||||
{::mf/wrap-props false}
|
{::mf/props :obj}
|
||||||
[]
|
[]
|
||||||
(let [modal (mf/deref modal-ref)]
|
(when-let [modal (mf/deref ref:modal)]
|
||||||
(when modal
|
(mf/portal
|
||||||
[:& modal-wrapper {:data modal
|
(mf/html [:> modal-wrapper* {:data modal :key (dm/str (:id modal))}])
|
||||||
:key (:id modal)}])))
|
(.-body js/document))))
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
[app.main.router :as rt]
|
[app.main.router :as rt]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.hooks :as hooks]
|
[app.main.ui.hooks :as hooks]
|
||||||
|
[app.main.ui.modal :refer [modal-container*]]
|
||||||
[app.main.ui.settings.access-tokens :refer [access-tokens-page]]
|
[app.main.ui.settings.access-tokens :refer [access-tokens-page]]
|
||||||
[app.main.ui.settings.change-email]
|
[app.main.ui.settings.change-email]
|
||||||
[app.main.ui.settings.delete-account]
|
[app.main.ui.settings.delete-account]
|
||||||
|
@ -41,7 +42,11 @@
|
||||||
(when (nil? profile)
|
(when (nil? profile)
|
||||||
(st/emit! (rt/nav :auth-login))))
|
(st/emit! (rt/nav :auth-login))))
|
||||||
|
|
||||||
|
[:*
|
||||||
|
[:> modal-container*]
|
||||||
[:section {:class (stl/css :dashboard-layout-refactor :dashboard)}
|
[:section {:class (stl/css :dashboard-layout-refactor :dashboard)}
|
||||||
|
|
||||||
|
|
||||||
[:& sidebar {:profile profile
|
[:& sidebar {:profile profile
|
||||||
:section section}]
|
:section section}]
|
||||||
|
|
||||||
|
@ -62,4 +67,4 @@
|
||||||
[:& options-page]
|
[:& options-page]
|
||||||
|
|
||||||
:settings-access-tokens
|
:settings-access-tokens
|
||||||
[:& access-tokens-page])]]]))
|
[:& access-tokens-page])]]]]))
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||||
[app.main.ui.hooks :as hooks]
|
[app.main.ui.hooks :as hooks]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
|
[app.main.ui.modal :refer [modal-container*]]
|
||||||
[app.main.ui.viewer.comments :refer [comments-layer comments-sidebar*]]
|
[app.main.ui.viewer.comments :refer [comments-layer comments-sidebar*]]
|
||||||
[app.main.ui.viewer.header :as header]
|
[app.main.ui.viewer.header :as header]
|
||||||
[app.main.ui.viewer.inspect :as inspect]
|
[app.main.ui.viewer.inspect :as inspect]
|
||||||
|
@ -633,7 +634,9 @@
|
||||||
|
|
||||||
(if-let [data (mf/deref refs/viewer-data)]
|
(if-let [data (mf/deref refs/viewer-data)]
|
||||||
(let [props (obj/merge props #js {:data data :key (dm/str file-id)})]
|
(let [props (obj/merge props #js {:data data :key (dm/str file-id)})]
|
||||||
[:> viewer-content* props])
|
[:*
|
||||||
|
[:> modal-container*]
|
||||||
|
[:> viewer-content* props]])
|
||||||
|
|
||||||
[:> loader* {:title (tr "labels.loading")
|
[:> loader* {:title (tr "labels.loading")
|
||||||
:overlay true}]))
|
:overlay true}]))
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||||
[app.main.ui.hooks :as hooks]
|
[app.main.ui.hooks :as hooks]
|
||||||
[app.main.ui.hooks.resize :refer [use-resize-observer]]
|
[app.main.ui.hooks.resize :refer [use-resize-observer]]
|
||||||
|
[app.main.ui.modal :refer [modal-container*]]
|
||||||
[app.main.ui.workspace.colorpicker]
|
[app.main.ui.workspace.colorpicker]
|
||||||
[app.main.ui.workspace.context-menu :refer [context-menu]]
|
[app.main.ui.workspace.context-menu :refer [context-menu]]
|
||||||
[app.main.ui.workspace.coordinates :as coordinates]
|
[app.main.ui.workspace.coordinates :as coordinates]
|
||||||
|
@ -211,6 +212,7 @@
|
||||||
[:& (mf/provider ctx/components-v2) {:value components-v2?}
|
[:& (mf/provider ctx/components-v2) {:value components-v2?}
|
||||||
[:& (mf/provider ctx/design-tokens) {:value design-tokens?}
|
[:& (mf/provider ctx/design-tokens) {:value design-tokens?}
|
||||||
[:& (mf/provider ctx/workspace-read-only?) {:value read-only?}
|
[:& (mf/provider ctx/workspace-read-only?) {:value read-only?}
|
||||||
|
[:> modal-container*]
|
||||||
[:section {:class (stl/css :workspace)
|
[:section {:class (stl/css :workspace)
|
||||||
:style {:background-color background-color
|
:style {:background-color background-color
|
||||||
:touch-action "none"}}
|
:touch-action "none"}}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue