Skip to content

Commit

Permalink
Merge pull request #3018 from input-output-hk/hrajchert/scp-2091-toas…
Browse files Browse the repository at this point in the history
…t-notification-animations

Add animation when the toast closes
  • Loading branch information
hrajchert authored Apr 19, 2021
2 parents 122739c + 5ef1757 commit 7458eab
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 8 deletions.
13 changes: 6 additions & 7 deletions marlowe-dashboard-client/src/Toast/State.purs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ module Toast.State

import Prelude
import Data.Foldable (for_)
import Data.Lens (assign, preview)
import Data.Lens (assign)
import Data.Lens.Extra (peruse)
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Maybe (Maybe(..))
import Data.Time.Duration (Milliseconds(..))
import Effect.Aff (error)
import Effect.Aff as Aff
import Effect.Aff.Class (class MonadAff)
import Halogen (HalogenM, get, subscribe, unsubscribe)
import Halogen (HalogenM, RefLabel(..), getHTMLElementRef, subscribe, unsubscribe)
import Halogen.Animation (animateAndWaitUntilFinish)
import Halogen.Query.EventSource (EventSource)
import Halogen.Query.EventSource as EventSource
import Toast.Lenses (_expanded, _mToast, _timeoutSubscription)
Expand Down Expand Up @@ -54,7 +55,5 @@ handleAction ExpandToast = do
handleAction CloseToast = assign _mToast Nothing

handleAction ToastTimeout = do
state <- get
let
expanded = fromMaybe false $ preview _expanded state
when (not expanded) $ handleAction CloseToast
mElement <- getHTMLElementRef (RefLabel "collapsed-toast")
for_ mElement $ subscribe <<< animateAndWaitUntilFinish "to-bottom" CloseToast
7 changes: 6 additions & 1 deletion marlowe-dashboard-client/src/Toast/View.purs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import Css (classNames)
import Css as Css
import Data.Lens (preview)
import Data.Maybe (Maybe(..), fromMaybe)
import Halogen (RefLabel(..))
import Halogen.HTML (HTML, a, div, div_, span, span_, text)
import Halogen.HTML.Events.Extra (onClick_)
import Halogen.HTML.Properties (ref)
import Material.Icons (Icon(..), icon_, icon)
import Toast.Lenses (_expanded, _toastMessage)
import Toast.Types (Action(..), State, ToastMessage)
Expand Down Expand Up @@ -66,7 +68,10 @@ renderCollapsed toast =
div
[ classNames [ "fixed", "bottom-6", "md:bottom-10", "left-0", "right-0", "flex", "justify-center", "z-50" ] ]
[ div
[ classNames [ "px-4", "py-2", "rounded", "shadow-lg", "min-w-90p", "max-w-90p", "sm:min-w-sm", "flex", "justify-between", "animate-from-below", toast.bgColor, toast.textColor ] ]
[ classNames [ "px-4", "py-2", "rounded", "shadow-lg", "min-w-90p", "max-w-90p", "sm:min-w-sm", "flex", "justify-between", "animate-from-below", toast.bgColor, toast.textColor ]
, ref
(RefLabel "collapsed-toast")
]
[ div [ classNames [ "flex", "overflow-hidden" ] ]
[ icon toast.icon [ "mr-2", toast.iconColor ]
, span [ classNames [ "font-semibold", "overflow-ellipsis", "whitespace-nowrap", "overflow-hidden" ] ] [ text toast.shortDescription ]
Expand Down
5 changes: 5 additions & 0 deletions marlowe-dashboard-client/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ module.exports = {
extend: {
animation: {
"from-below": "from-below 250ms ease-out 1",
"to-bottom": "to-bottom 250ms ease-out 1",
},
keyframes: {
"from-below": {
"0%": { transform: "translateY(20px)", opacity: 0 },
"100%": { transform: "translateY(0px)", opacity: 1 },
},
"to-bottom": {
"0%": { transform: "translateY(0px)", opacity: 1 },
"100%": { transform: "translateY(20px)", opacity: 0 },
},
},
gridTemplateRows: {
main: "auto minmax(0, 1fr) auto",
Expand Down
17 changes: 17 additions & 0 deletions web-common/src/Halogen/Animation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
exports.getAnimations_ = function (element) {
// If the browser does not implement the Web Animation API
// we return an empty array instead of failing.
if ("getAnimations" in element) {
return element.getAnimations();
} else {
return [];
}
};

exports.getAnimationName = function (animation) {
return animation.animationName;
};

exports.setOnFinishHandler_ = function (animation, cb) {
animation.onfinish = cb;
};
60 changes: 60 additions & 0 deletions web-common/src/Halogen/Animation.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
-- This module offers some helpers for using Tailwind animations with halogen using the Web Animation API
-- https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API
module Halogen.Animation where

import Prelude
import Data.Array (filter)
import Effect (Effect)
import Effect.Aff.Class (class MonadAff)
import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn1, runEffectFn2)
import Halogen.Query.EventSource (EventSource)
import Halogen.Query.EventSource as EventSource
import Web.DOM.DOMTokenList as DOMTokenList
import Web.HTML (HTMLElement)
import Web.HTML.HTMLElement (classList)

foreign import data CSSAnimation :: Type

foreign import getAnimations_ :: EffectFn1 HTMLElement (Array CSSAnimation)

foreign import getAnimationName :: CSSAnimation -> String

foreign import setOnFinishHandler_ :: EffectFn2 CSSAnimation (Effect Unit) Unit

getAnimations :: HTMLElement -> Effect (Array CSSAnimation)
getAnimations = runEffectFn1 getAnimations_

setOnFinishHandler :: CSSAnimation -> Effect Unit -> Effect Unit
setOnFinishHandler = runEffectFn2 setOnFinishHandler_

-- This function adds a tailwind animation `animationName` (which can be customized using CSS Animations)
-- to an element, and calls an `action` once it finished. The only way to call HalogenM from the Effect world
-- is via the subscriptions mechanism, so you need to subscribe to this EventSource.
-- You don't need to unsubscribe as the EventSource closes itself after firing the action.
-- https://tailwindcss.com/docs/animation
animateAndWaitUntilFinish ::
forall m action.
MonadAff m =>
String ->
action ->
HTMLElement ->
EventSource m action
animateAndWaitUntilFinish animationName action element =
EventSource.effectEventSource \emitter -> do
let
className = "animate-" <> animationName
-- Adding the class to the element starts the animation
classes <- classList element
DOMTokenList.add classes className
animations <- getAnimations element <#> filter (\animation -> animationName == getAnimationName animation)
let
cb :: Effect Unit
cb = do
EventSource.emit emitter action
EventSource.close emitter
-- We remove the css class so we can redo the animation if necessary
DOMTokenList.remove classes className
case animations of
[ animation ] -> setOnFinishHandler animation cb
_ -> cb
pure $ EventSource.Finalizer mempty

0 comments on commit 7458eab

Please sign in to comment.