Skip to content

Instantly share code, notes, and snippets.

@smitch88
Created April 20, 2017 00:57
Show Gist options
  • Save smitch88/fdbd3a4ded63ba337126052e45f28a00 to your computer and use it in GitHub Desktop.
Save smitch88/fdbd3a4ded63ba337126052e45f28a00 to your computer and use it in GitHub Desktop.
(ns example.dialog
(:require [reagent.core :as r]
[reagent.dom :as rdom]
[clojure.string :refer [blank? join]]
[goog.dom :as gdom]))
(defn add-container-el
[mount-to id {:keys [mount-to]}]
(when-not (gdom/getElement id)
(let [container-el (.createElement js/document "div")]
(aset container-el "id" id)
(.appendChild mount-to container-el))))
(defn PortalContainer
[uuid {:keys [id class content]}]
[:div.portal {:id uuid
:key uuid
:class class}
[:div.portal-content {:id id}
content]])
(defn Portal
"Wrapping component that places children into a `mount-to` dom node.
This generates an empty render container which is populated on update in the
container element uuid. This pattern allows for arbitrary
DOM positioning of the child node"
[{:keys [uuid mount-to] :as props}]
(let [uuid (or uuid (rand-int 100000000))
portal-uuid (str "portal-" uuid)
container (str "portal-container-" uuid)]
(r/create-class
{:component-will-unmount
(fn []
(when-let [el (gdom/getElement container)]
(when-let [parent-node (aget el "parentNode")]
(.removeChild parent-node el))))
:component-did-update
(fn [this]
(let [props (r/props this)]
(if (:opened? props)
(do
(add-container-el mount-to container props)
(rdom/render [PortalContainer portal-uuid props]
(gdom/getElement container)))
(when-let [portal-el (gdom/getElement portal-uuid)]
(.remove (gdom/getElement container))))))
:component-did-mount
(fn [this]
(let [props (r/props this)]
(when (:opened? props)
(add-container-el mount-to container props)
(rdom/render [PortalContainer portal-uuid props]
(gdom/getElement container)))))
:reagent-render
(fn [] nil)})))
(def base-styles
{:dialog {:position "fixed"
:left 0
:top 0
:height "100%"
:width "100%"
:z-index 1081
:margin "0 auto"}
:overlay {:position "absolute"
:left 0
:top 0
:height "100%"
:width "100%"
:z-index 1
:background-color "rgba(0,0,0,0.8)"}
:container {:position "absolute"
:height "100%"
:width "100%"
:z-index 2}
:window {:height "auto"
:width "50%"
:margin "100px auto"
:background-color "#ffffff"
:border "1px solid #eee"}})
(defn Dialog
[{:keys [opened? mount-pt style]} & children]
(let [styles (merge-with merge base-styles style)]
[Portal {:opened? opened?
:mount-to (gdom/getElement mount-pt)
:content [:div.dialog {:style (:dialog styles)}
[:div.overlay {:style (:overlay styles)}]
[:div.container {:style (:container styles)}
(into [:div.window {:style (:window styles)}]
children)]]}]))
;; Example of using generic dialog for specific case
(defn specific-dialog []
(let [dialog-opened? (atom false)
open-dialog #(reset! dialog-opened? true)
close-dialog #(reset! dialog-opened? false)
dialog-style {:window {:width 400
:background-color "red"}}]
(fn []
[:div [:h1 "Click button to open dialog"]
[Dialog {:mount-pt "app"
:opened? @dialog-opened?
:on-dismiss close-dialog
:style dialog-style}
[:div {:style {:padding 20}}
[:h2 "Header"]
[:p "Content"]
[:button {:on-click close-dialog} "Close"]]]
[:button {:on-click open-dialog} "Open dialog"]])))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment