Modal.purs 2.41 KB
Newer Older
James Laver's avatar
James Laver committed
1 2 3 4 5
-- | The modal component sits atop everything else.  It darkens the
-- | rest of the page and centers a box in which we can put
-- | content. Clicking outside of the box will close the modal
module Gargantext.Components.Modal where

6 7 8
import Prelude (Unit, bind, const, discard, pure, unit, ($))
import Data.Maybe ( maybe )
import Data.Nullable ( Nullable, null )
James Laver's avatar
James Laver committed
9
import DOM.Simple as DOM
10
import DOM.Simple.EventListener ( callback )
James Laver's avatar
James Laver committed
11 12 13 14 15 16
import DOM.Simple.Element as Element
import DOM.Simple.Event (MouseEvent, target)
import DOM.Simple.Document ( document )
import Effect (Effect)
import Reactix as R
import Reactix.DOM.HTML as H
17

James Laver's avatar
James Laver committed
18 19
import Gargantext.Utils.Reactix as R2

20
here = R2.here "Gargantext.Components.Modal"
21

22
type Props = ( setVisible :: R.Setter Boolean )
James Laver's avatar
James Laver committed
23

24
modal :: R2.Component Props
James Laver's avatar
James Laver committed
25 26 27
modal = R.createElement modalCpt

modalCpt :: R.Component Props
28
modalCpt = here.component "modal" cpt
James Laver's avatar
James Laver committed
29 30 31 32 33 34 35 36
  where
    cpt {setVisible} children = do
      host <- R2.getPortalHost
      root <- R.useRef null -- used to close when user clicks outside
      R2.useLayoutEffectOnce $ modalEffect root setVisible
      pure $ R.createPortal
        [ H.div { ref: root, className: "modal", data: {toggle: "popover", placement: "right"}}
          [ H.div { className: "popover-content" }
37
            [ H.div { className: "card" }
James Laver's avatar
James Laver committed
38 39 40 41 42
              [ H.ul { className: "list-group" } children ]]]]
        host

modalEffect
  :: R.Ref (Nullable DOM.Element)
43
  -> R.Setter Boolean
James Laver's avatar
James Laver committed
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
  -> Effect (Effect Unit)
modalEffect rootRef setVisible = maybe (pure R.nothing) withRoot (R.readNullableRef rootRef)
  where
    onScroll = R2.named "hideModalOnScroll" $ callback handler
      where -- removing this type declaration will unleash the hounds, so don't
        handler :: MouseEvent -> Effect Unit
        handler _ = setVisible (const false)
    withRoot root = do
      let onClick = clickHandler root
      DOM.addEventListener document "click" onClick
      DOM.addEventListener document "scroll" onScroll
      pure $ do
        DOM.removeEventListener document "click" onClick
        DOM.removeEventListener document "scroll" onScroll
    clickHandler root =
      R2.named "hideModalOnClickOutside" $ callback handler
        where -- removing this type declaration will unleash the hounds, so don't
          handler :: MouseEvent -> Effect Unit
          handler e =
            if Element.contains root (target e)
            then pure unit
            else setVisible (const false)