@@ -3,18 +3,24 @@ module Yoga.Modal.Component where
33import Prelude
44import CSS (JustifyContentValue(..), spaceBetween)
55import Data.Foldable (foldMap)
6+import Data.Int (round, toNumber)
7+import Data.Interpolate (i)
68import Data.Maybe (Maybe)
9+import Data.Tuple.Nested ((/\))
710import Effect (Effect)
811import Foreign.Object as Obj
9−import React.Basic (JSX)
12+import Math (pi, pow, sqrt)
13+import React.Basic (JSX, fragment)
14+import React.Basic.DOM (css)
1015import React.Basic.DOM as R
1116import React.Basic.DOM.Events (stopPropagation)
1217import React.Basic.DOM.SVG as SVG
13−import React.Basic.Events (EventHandler, handler, handler_)
18+import React.Basic.Events (handler, handler_)
1419import React.Basic.Extra.Hooks.UseKeyUp (useKeyUp)
1520import React.Basic.Helpers (jsx)
16−import React.Basic.Hooks (ReactComponent, component, useEffect, useMemo, useState)
21+import React.Basic.Hooks (ReactComponent, component, element, useEffect, useState)
1722import React.Basic.Hooks as React
23+import React.Basic.Hooks.Spring (animatedPath, useSpring)
1824import Record.Extra (pick)
1925import Yoga.Box.Component as Box
2026import Yoga.Cluster.Component as Cluster
@@ -40,29 +46,48 @@ makeComponent = do
4046 box <- Box.makeComponent
4147 stack <- Stack.makeComponent
4248 cluster <- Cluster.makeComponent
49+ closeIcon <- mkCloseIcon
4350 useStyles <- makeStylesJSS Style.styles
4451 component "Modal" \props -> React.do
4552 cs <- useStyles (pick props)
4653 useKeyUp 27 $ props.onClose ?|| pure unit
54+ animationDone /\ modifyAnimationDone <- useState false
55+ overlayStyle <- useSpring $ const { opacity: 0.0 }
56+ dialogStyle <-
57+ useSpring
58+ $ const
59+ { transform: "translate3d(-50%, -50%, 0) scale3d(0.2, 0.2, 0.0)"
60+ , config: { mass: 0.8, tension: 120, friction: 14 }
61+ }
62+ useEffect unit do
63+ overlayStyle.set { opacity: 1.0 }
64+ dialogStyle.set
65+ { transform: "translate3d(-50%, -50%, 0) scale3d(1.0, 1.0, 1.0)"
66+ , onRest: modifyAnimationDone (const true)
67+ }
68+ pure mempty
4769 let
4870 darkOverlay =
4971 jsx imposter
50− { className: cs.darkOverlay <> " " <> cs.fadeIn
72+ { className: cs.darkOverlay
5173 , fixed: true
5274 , breakout: false
5375 , onClick: props.onClose ?|| mempty # handler_
76+ , style: css overlayStyle.style
5477 }
78+ []
5579
5680 dialogImposter =
5781 jsx imposter
5882 { className: cs.dialog
5983 , onClick: handler stopPropagation mempty
6084 , fixed: true
85+ , style: css dialogStyle.style
6186 }
6287
6388 dialogBox =
6489 jsx box
65− { className: cs.box <> " " <> cs.zoomIn
90+ { className: cs.box
6691 }
6792
6893 dialogBoxStack =
@@ -83,13 +108,20 @@ makeComponent = do
83108 , R.div
84109 { className: cs.closeIcon
85110 , children:
86− [ props.onClose # foldMap (closeIcon cs <<< handler_)
111+ [ props.onClose
112+ # foldMap
113+ ( \onClick ->
114+ ( element closeIcon
115+ { className: cs.closeIcon, onClick, startAnimation: animationDone }
116+ )
117+ )
87118 ]
88119 }
89120 ]
90121 pure
91− $ darkOverlay
92− [ dialogImposter
122+ $ fragment
123+ [ darkOverlay
124+ , dialogImposter
93125 [ dialogBox
94126 [ dialogBoxStack
95127 [ titleCluster [ title ]
@@ -99,27 +131,61 @@ makeComponent = do
99131 ]
100132 ]
101133
102−closeIcon ∷ ∀ a. { closeIcon ∷ String | a } -> EventHandler -> JSX
103−closeIcon classes onClick =
104− SVG.svg
105− { xmlns: "http://www.w3.org/2000/svg"
106− , viewBox: "5 0 100 105"
107− , fillRule: "evenodd"
108− , clipRule: "evenodd"
109− , strokeLinejoin: "round"
110− , strokeMiterlimit: "2"
111− , _data: Obj.singleton "testid" "close-icon-svg"
112− , onClick
113− , className: classes.closeIcon
114− , children:
115− [ SVG.g_
116− [ SVG.path
117− { d: "M101.703,36.06c2.054,-2.054 2.054,-5.388 0,-7.442l-7.441,-7.441c-2.054,-2.054 -5.388,-2.054 -7.442,0l-65.643,65.643c-2.054,2.054 -2.054,5.388 0,7.442l7.441,7.441c2.054,2.054 5.388,2.054 7.442,0l65.643,-65.643Z"
118− }
119− , SVG.path
120− { d:
121− "M86.82,101.703c2.054,2.054 5.388,2.054 7.442,0l7.441,-7.441c2.054,-2.054 2.054,-5.388 0,-7.442l-65.643,-65.643c-2.054,-2.054 -5.388,-2.054 -7.442,0l-7.441,7.441c-2.054,2.054 -2.054,5.388 0,7.442l65.643,65.643Z"
122− }
123− ]
124− ]
125− }
134+mkCloseIcon ∷
135+ Effect
136+ ( ReactComponent
137+ { className ∷ String
138+ , onClick ∷ Effect Unit
139+ , startAnimation ∷ Boolean
140+ }
141+ )
142+mkCloseIcon = do
143+ box <- Box.makeComponent
144+ component "CloseIcon" \{ className, onClick, startAnimation } -> React.do
145+ { style, set, stop } <-
146+ useSpring
147+ $ const
148+ { strokeDasharray: "0, " <> show lineLen
149+ }
150+ circleStyle <-
151+ useSpring
152+ $ const
153+ { strokeDasharray: "0," <> show circleCircumf
154+ , onRest: set { "strokeDasharray": i lineLen "," lineLen ∷ String }
155+ }
156+ useEffect startAnimation do
157+ when startAnimation $ circleStyle.set { "strokeDasharray": (i circleCircumf "," circleCircumf) ∷ String }
158+ pure mempty
159+ pure
160+ $ SVG.svg
161+ { viewBox: i "0 0 " width " " width
162+ , _data: Obj.singleton "testid" "close-icon-svg"
163+ , onClick: handler_ onClick
164+ , fill: "none"
165+ , className
166+ , children:
167+ [ animatedPath
168+ { d: i "M" padding "," padding "L" (width - padding) "," (width - padding)
169+ , style: css style
170+ }
171+ , animatedPath
172+ { d: i "M" padding "," (width - padding) ",L" (width - padding) "," padding
173+ , style: css style
174+ }
175+ , animatedPath
176+ { d: move <> line1 <> line2
177+ , style: css circleStyle.style
178+ }
179+ ]
180+ }
181+ where
182+ move = i "M " (cx - r) "," cx
183+ line1 = i " a " r "," r " 0 1,0 " (r * 2) ",0 "
184+ line2 = i " a " r "," r " 0 1,0 " (-(r * 2)) ",0 "
185+ padding = 70
186+ width = 200
187+ cx = width / 2
188+ r = round ((toNumber width * 0.8) / 2.0)
189+ circleCircumf = round $ 2.0 * pi * toNumber r
190+ aSquare = toNumber (width - (2 * padding)) `pow` 2.0
191+ lineLen = sqrt (aSquare * 2.0)