module Gargantext.Components.Bootstrap.BaseModal ( baseModal , showModal, hideModal ) where import Gargantext.Prelude import DOM.Simple (Window, window) import Data.Foldable (intercalate) import Data.Maybe (Maybe(..)) import Data.UUID as UUID import Effect (Effect) import Effect.Uncurried (EffectFn2, runEffectFn2) import Gargantext.Components.Bootstrap.IconButton (iconButton) import Gargantext.Components.Bootstrap.Types (Elevation(..), ModalSizing(..)) import Gargantext.Hooks.UpdateEffect (useUpdateEffect1') import Gargantext.Utils ((?)) import Gargantext.Utils.Reactix as R2 import Reactix as R import Reactix.DOM.HTML as H import Toestand as T foreign import _show :: EffectFn2 Window String Unit showModal :: Window -> String -> Effect Unit showModal = runEffectFn2 _show foreign import _hide :: EffectFn2 Window String Unit hideModal :: Window -> String -> Effect Unit hideModal = runEffectFn2 _hide type Props = ( isVisibleBox :: T.Box Boolean | Options ) type Options = ( modalClassName :: String , title :: Maybe String , hasCollapsibleBackground :: Boolean , hasInnerScroll :: Boolean , noHeader :: Boolean , noBody :: Boolean -- ie. Bootstrap Body , size :: ModalSizing ) options :: Record Options options = { modalClassName : "" , title : Nothing , hasCollapsibleBackground : true , hasInnerScroll : false , noHeader : false , noBody : false , size : MediumModalSize } componentName :: String componentName = "b-modal" -- | Structural Component for the Bootstrap modal -- | -- | @XXX Bootstrap not removing some modal elements on "hide" method -- | This implies that: -- | - a FFI fix has been added to remove left elements -- | - an overlay has been added to synchronise the close button -- | - the keyboard shortcut has been removed -- | @link https://stackoverflow.com/questions/50168312/bootstrap-4-close-modal-backdrop-doesnt-disappear -- | -- | @link https://getbootstrap.com/docs/4.6/components/modal/ baseModal :: forall r. R2.OptComponent Options Props r baseModal = R2.optComponent component options component :: R.Memo Props component = R.memo' $ R.hooksComponent componentName cpt where cpt props@{ isVisibleBox , title , hasCollapsibleBackground , hasInnerScroll , noHeader , noBody , size } children = do -- | States -- | isVisible <- R2.useLive' isVisibleBox uuid <- R.unsafeHooksEffect (UUID.genUUID >>= pure <<< UUID.toString) -- | Computed -- | let className = intercalate " " -- Component [ componentName -- Bootstrap , "modal" ] id = componentName <> "-" <> uuid selector = "#" <> id -- | Hooks -- | useUpdateEffect1' isVisible if isVisible then showModal window selector else hideModal window selector -- | Behaviors -- | let onCloseButtonClick _ = T.modify_ (not) isVisibleBox -- [ Render -- | R.createPortal [ H.div { id: id , className , tabIndex: "-1" , key: id , data: { keyboard: "false" , backdrop: hasCollapsibleBackground ? "true" $ "static" } } [ -- Overlay fixing collapsable click event R2.when (hasCollapsibleBackground) $ H.div { className: componentName <> "__overlay" , on: { click: onCloseButtonClick } } [] , H.div { className: intercalate " " -- Bootstrap classNames [ "modal-dialog" , show size , "modal-dialog-centered" , hasInnerScroll ? "modal-dialog-scrollable" $ "" -- provided custom className , props.modalClassName ] } [ H.div { className: intercalate " " [ componentName <> "__content" , "modal-content" ] } [ -- Header R2.when (not noHeader) $ H.div { className: intercalate " " [ componentName <> "__header" , "modal-header" ] } [ R2.fromMaybe (title) \title' -> H.div { className: componentName <> "__header__title" } [ H.text title' ] , iconButton { name: "times" , callback: onCloseButtonClick , elevation: Level2 , className: componentName <> "__header__close" } ] , -- Body H.div { className: intercalate " " [ componentName <> "__body" , noBody ? "" $ "modal-body" ] } children ] ] ] ] <$> R2.getPortalHost