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 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
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
import Gargantext.Utils.Reactix as R2

type Props = ( setVisible :: R2.Setter Boolean )

modal :: Record Props -> Array R.Element -> R.Element
modal = R.createElement modalCpt

modalCpt :: R.Component Props
modalCpt = R.hooksComponent "Modal" cpt
  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" }
            [ H.div { className: "panel panel-default" }
              [ H.ul { className: "list-group" } children ]]]]
        host

modalEffect
  :: R.Ref (Nullable DOM.Element)
  -> R2.Setter Boolean
  -> 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)