module Container.Header where import Prelude import CSS.Safer (cssSafer) import Data.Int (toNumber) import Data.Maybe (Maybe(..)) import Data.Monoid (guard) import Data.Newtype (class Newtype) import Data.Nullable (Nullable, null) import Effect (Effect) import Effect.Class.Console (log) import React.Basic.DOM as R import React.Basic.Hooks (type (/\), Hook, ReactComponent, Ref, UseLayoutEffect, UseRef, UseState, coerceHook, component, element, readRefMaybe, useLayoutEffect, useRef, useState, (/\)) import React.Basic.Hooks as React import SVG.Icon (trianglelogoIcon) import Theme.Styles (makeStyles) import Theme.Types (CSSTheme) import Typography.Header (HeadingLevel(..), mkH) import Web.DOM (Node) import Web.Event.Event (EventType(..)) import Web.Event.EventTarget (EventListener, addEventListener, eventListener, removeEventListener) import Web.HTML (HTMLElement, window) import Web.HTML.HTMLElement (offsetTop) import Web.HTML.HTMLElement as HTMLElement import Web.HTML.Window (scrollY, toEventTarget) mkHeader ∷ Effect (ReactComponent {}) mkHeader = do h <- mkH useStyles <- makeStyles \(theme ∷ CSSTheme) -> { header: cssSafer { backgroundColor: theme.backgroundColour , borderBottom: "0" , fontFamily: theme.headingFontFamily , gridArea: "header" , display: "flex" , alignItems: "center" , justifyContent: "flex-end" , width: "100%" , height: "80px" } , sticky: cssSafer { position: "fixed", top: "0" } , title: cssSafer { paddingRight: "30px"} , logo: cssSafer { width: "70px" , height: "70px" , padding: "5px" , marginTop: "9px" , marginRight: "7px" , fill: theme.textColour } } component "Header" \{} -> React.do classes <- useStyles shouldBeSticky /\ nodeRef <- useShouldBeSticky pure $ R.header { ref: nodeRef , className: classes.header <> " " <> guard shouldBeSticky classes.sticky , children: [ element h { level: H2, text: "Kleislove", className: Just classes.title } , R.div { children: [ element trianglelogoIcon {} ], className: classes.logo } ] } newtype UseShouldBeSticky hooks = UseShouldBeSticky (UseLayoutEffect Unit (UseState Boolean (UseRef (Nullable Node) hooks))) derive instance ntUseScrollYPosition ∷ Newtype (UseShouldBeSticky hooks) _ useShouldBeSticky ∷ Hook UseShouldBeSticky (Boolean /\ Ref (Nullable Node)) useShouldBeSticky = coerceHook React.do nodeRef <- useRef null mustBeSticky /\ modifyMustBeSticky <- useState false let setSticky = modifyMustBeSticky <<< const useLayoutEffect unit do maybeNode <- readRefMaybe nodeRef case maybeNode >>= HTMLElement.fromNode of Nothing -> do log "Could not register listener because there was no node" pure (pure unit) Just element -> do listener <- makeListener setSticky element registerListener listener pure (mustBeSticky /\ nodeRef) scrollEventType ∷ EventType scrollEventType = EventType "scroll" registerListener ∷ EventListener -> Effect (Effect Unit) registerListener listener = do target <- window <#> toEventTarget addEventListener scrollEventType listener false target pure $ removeEventListener scrollEventType listener false target makeListener ∷ (Boolean -> Effect Unit) -> HTMLElement -> Effect EventListener makeListener setSticky element = do nodeStartPos <- offsetTop element eventListener $ const do yPos <- window >>= scrollY <#> toNumber setSticky $ nodeStartPos < yPos