mirror of
https://github.com/penpot/penpot.git
synced 2025-01-21 06:02:32 -05: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"}
|
||||
|
||||
funcool/rumext
|
||||
{:git/tag "v2.14"
|
||||
:git/sha "0016623"
|
||||
{:git/tag "v2.15"
|
||||
:git/sha "28783a7"
|
||||
:git/url "https://github.com/funcool/rumext.git"}
|
||||
|
||||
instaparse/instaparse {:mvn/version "1.5.0"}
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
[app.main.ui.confirm]
|
||||
[app.main.ui.css-cursors :as cur]
|
||||
[app.main.ui.delete-shared]
|
||||
[app.main.ui.modal :refer [modal]]
|
||||
[app.main.ui.routes :as rt]
|
||||
[app.main.worker :as worker]
|
||||
[app.plugins :as plugins]
|
||||
|
@ -52,14 +51,9 @@
|
|||
(let [el (dom/get-element "app")]
|
||||
(mf/create-root el)))
|
||||
|
||||
(defonce modal-root
|
||||
(let [el (dom/get-element "modal")]
|
||||
(mf/create-root el)))
|
||||
|
||||
(defn init-ui
|
||||
[]
|
||||
(mf/render! app-root (mf/element ui/app))
|
||||
(mf/render! modal-root (mf/element modal)))
|
||||
(mf/render! app-root (mf/element ui/app)))
|
||||
|
||||
(defn- initialize-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
|
||||
(when hard?
|
||||
(mf/unmount! app-root)
|
||||
(mf/unmount! modal-root)
|
||||
(set! app-root (mf/create-root (dom/get-element "app")))
|
||||
(set! modal-root (mf/create-root (dom/get-element "modal"))))
|
||||
(set! app-root (mf/create-root (dom/get-element "app"))))
|
||||
(st/emit! (ev/initialize))
|
||||
(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.templates :refer [templates-section*]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.modal :refer [modal-container*]]
|
||||
[app.main.ui.workspace.plugins]
|
||||
[app.plugins.register :as preg]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -240,6 +241,7 @@
|
|||
(use-plugin-register plugin-url team-id (:id default-project))
|
||||
|
||||
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
||||
[:> modal-container*]
|
||||
;; NOTE: dashboard events and other related functions assumes
|
||||
;; that the team is a implicit context variable that is
|
||||
;; available using react context or accessing
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
(ns app.main.ui.modal
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(: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.util.dom :as dom]
|
||||
[app.util.keyboard :as k]
|
||||
|
@ -20,13 +21,13 @@
|
|||
[event allow-click-outside]
|
||||
(when (and (k/esc? event) (not allow-click-outside))
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dm/hide))))
|
||||
(st/emit! (modal/hide))))
|
||||
|
||||
(defn- on-pop-state
|
||||
[event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dm/hide))
|
||||
(st/emit! (modal/hide))
|
||||
(.forward js/history))
|
||||
|
||||
(defn- on-click-outside
|
||||
|
@ -41,15 +42,14 @@
|
|||
(= (.-button event) 0))
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (dm/hide)))))
|
||||
(st/emit! (modal/hide)))))
|
||||
|
||||
(mf/defc modal-wrapper
|
||||
{::mf/wrap-props false
|
||||
(mf/defc modal-wrapper*
|
||||
{::mf/props :obj
|
||||
::mf/wrap [mf/memo]}
|
||||
[props]
|
||||
(let [data (unchecked-get props "data")
|
||||
wrapper-ref (mf/use-ref nil)
|
||||
components (mf/deref dm/components)
|
||||
[{:keys [data]}]
|
||||
(let [wrapper-ref (mf/use-ref nil)
|
||||
components (mf/deref modal/components)
|
||||
|
||||
allow-click-outside (:allow-click-outside data)
|
||||
|
||||
|
@ -61,31 +61,29 @@
|
|||
(fn [event]
|
||||
(on-esc-clicked event allow-click-outside))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps allow-click-outside)
|
||||
(fn []
|
||||
(let [keys [(events/listen js/window EventType.POPSTATE on-pop-state)
|
||||
(events/listen js/document EventType.KEYDOWN handle-keydown)
|
||||
(mf/with-effect [allow-click-outside]
|
||||
(let [keys [(events/listen js/window EventType.POPSTATE on-pop-state)
|
||||
(events/listen js/document EventType.KEYDOWN handle-keydown)
|
||||
|
||||
;; Changing to js/document breaks the color picker
|
||||
(events/listen (dom/get-root) EventType.POINTERDOWN handle-click-outside)
|
||||
;; Changing to js/document breaks the color picker
|
||||
(events/listen (dom/get-root) EventType.POINTERDOWN handle-click-outside)
|
||||
|
||||
(events/listen js/document EventType.CONTEXTMENU handle-click-outside)]]
|
||||
#(doseq [key keys]
|
||||
(events/unlistenByKey key)))))
|
||||
(events/listen js/document EventType.CONTEXTMENU handle-click-outside)]]
|
||||
(fn []
|
||||
(run! events/unlistenByKey keys))))
|
||||
|
||||
(when-let [component (get components (:type data))]
|
||||
[:div {:ref wrapper-ref
|
||||
:class (stl/css :modal-wrapper)}
|
||||
(mf/element component (:props data))])))
|
||||
|
||||
(def modal-ref
|
||||
(l/derived ::dm/modal st/state))
|
||||
(def ^:private ref:modal
|
||||
(l/derived ::modal/modal st/state))
|
||||
|
||||
(mf/defc modal
|
||||
{::mf/wrap-props false}
|
||||
(mf/defc modal-container*
|
||||
{::mf/props :obj}
|
||||
[]
|
||||
(let [modal (mf/deref modal-ref)]
|
||||
(when modal
|
||||
[:& modal-wrapper {:data modal
|
||||
:key (:id modal)}])))
|
||||
(when-let [modal (mf/deref ref:modal)]
|
||||
(mf/portal
|
||||
(mf/html [:> modal-wrapper* {:data modal :key (dm/str (:id modal))}])
|
||||
(.-body js/document))))
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[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.change-email]
|
||||
[app.main.ui.settings.delete-account]
|
||||
|
@ -41,25 +42,29 @@
|
|||
(when (nil? profile)
|
||||
(st/emit! (rt/nav :auth-login))))
|
||||
|
||||
[:section {:class (stl/css :dashboard-layout-refactor :dashboard)}
|
||||
[:& sidebar {:profile profile
|
||||
:section section}]
|
||||
[:*
|
||||
[:> modal-container*]
|
||||
[:section {:class (stl/css :dashboard-layout-refactor :dashboard)}
|
||||
|
||||
[:div {:class (stl/css :dashboard-content)}
|
||||
[:& header]
|
||||
[:section {:class (stl/css :dashboard-container)}
|
||||
(case section
|
||||
:settings-profile
|
||||
[:& profile-page]
|
||||
|
||||
:settings-feedback
|
||||
[:& feedback-page]
|
||||
[:& sidebar {:profile profile
|
||||
:section section}]
|
||||
|
||||
:settings-password
|
||||
[:& password-page]
|
||||
[:div {:class (stl/css :dashboard-content)}
|
||||
[:& header]
|
||||
[:section {:class (stl/css :dashboard-container)}
|
||||
(case section
|
||||
:settings-profile
|
||||
[:& profile-page]
|
||||
|
||||
:settings-options
|
||||
[:& options-page]
|
||||
:settings-feedback
|
||||
[:& feedback-page]
|
||||
|
||||
:settings-access-tokens
|
||||
[:& access-tokens-page])]]]))
|
||||
:settings-password
|
||||
[:& password-page]
|
||||
|
||||
:settings-options
|
||||
[:& options-page]
|
||||
|
||||
:settings-access-tokens
|
||||
[:& access-tokens-page])]]]]))
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[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.header :as header]
|
||||
[app.main.ui.viewer.inspect :as inspect]
|
||||
|
@ -633,7 +634,9 @@
|
|||
|
||||
(if-let [data (mf/deref refs/viewer-data)]
|
||||
(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")
|
||||
:overlay true}]))
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[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.context-menu :refer [context-menu]]
|
||||
[app.main.ui.workspace.coordinates :as coordinates]
|
||||
|
@ -211,6 +212,7 @@
|
|||
[:& (mf/provider ctx/components-v2) {:value components-v2?}
|
||||
[:& (mf/provider ctx/design-tokens) {:value design-tokens?}
|
||||
[:& (mf/provider ctx/workspace-read-only?) {:value read-only?}
|
||||
[:> modal-container*]
|
||||
[:section {:class (stl/css :workspace)
|
||||
:style {:background-color background-color
|
||||
:touch-action "none"}}
|
||||
|
|
Loading…
Add table
Reference in a new issue