git.iko.soy/rowtype-yoga/psfp
on master help sign in
More animations
commitf10a067e18fa68b985e4e12687528361a6a2fb5e authorMark Eibes <mark.eibes@gmail.com> dateSun 5 Apr 2020, 22:27 (7 years ago) parent982389d
20 files changed+518−210
components/src/React/Basic/Hooks/Spring.purs+3−1 components/src/React/Basic/Hooks/Spring.ts+2−2 components/src/React/Basic/Hooks/Spring/Stories.purs+51−35 components/src/Yoga/Card/Component.purs+1−2 components/src/Yoga/Card/Stories.purs+2−5 components/src/Yoga/ClickAway/Component.purs+45 components/src/Yoga/ClickAway/Spec.purs+21 components/src/Yoga/ClickAway/Stories.purs+56 components/src/Yoga/ClickAway/Styles.purs+24 components/src/Yoga/CloseIcon/Component.purs+65 components/src/Yoga/CloseIcon/Spec.purs+35 components/src/Yoga/CloseIcon/Stories.purs+92 components/src/Yoga/CloseIcon/Styles.purs+33 components/src/Yoga/InlineCode/Stories.purs+6−2 components/src/Yoga/Modal/Component.purs+17−122 components/src/Yoga/Modal/Spec.purs+2−20 components/src/Yoga/Modal/Stories.purs+54−8 components/src/Yoga/Modal/Styles.purs+2−9 components/src/Yoga/Spec/Helpers.purs+3−1 components/src/Yoga/Theme/CSSBaseline.purs+4−3
components/src/React/Basic/Hooks/Spring.purs+3−1
@@ -6,12 +6,14 @@ import Data.Nullable (Nullable, toNullable)
66import Data.Nullable as Nullable
77import Effect (Effect)
88import Effect.Uncurried (EffectFn1, EffectFn3, runEffectFn1, runEffectFn3)
9+import Foreign (Foreign)
910import Prim.Row (class Union)
10−import React.Basic (JSX, ReactComponent, element)
11+import React.Basic (JSX, ReactComponent, Ref, element)
1112import React.Basic.DOM (CSS, Props_div, Props_span)
1213import React.Basic.DOM.SVG (Props_svg, Props_path)
1314import React.Basic.Hooks (Hook, unsafeHook)
1415import React.Basic.Hooks as React
16+import Simple.JSON (class WriteForeign)
1517
1618foreign import data UseSpring ∷ Type -> Type -> Type
1719
components/src/React/Basic/Hooks/Spring.ts+2−2
@@ -1,6 +1,6 @@
1−import { useSpring, useTransition, animated } from "react-spring";
1+import { useChain, useSpring, useTransition, animated } from "react-spring";
22
3−exports.useSpringImpl = mkStyles => () => {
3+exports.useSpringImpl = (mkStyles) => () => {
44 const result = useSpring(mkStyles);
55 return { style: result[0], set: result[1], stop: result[2] };
66};
components/src/React/Basic/Hooks/Spring/Stories.purs+51−35
@@ -10,16 +10,18 @@ import React.Basic.DOM (css)
1010import React.Basic.DOM as R
1111import React.Basic.Events (handler_)
1212import React.Basic.Helpers (jsx)
13−import React.Basic.Hooks (ReactComponent, component, fragment, useLayoutEffect, useState)
13+import React.Basic.Hooks (ReactComponent, component, fragment, useEffect, useState)
1414import React.Basic.Hooks as React
1515import React.Basic.Hooks.Spring (animatedDiv, useSpring, useTransition)
1616import React.Basic.Hooks.UseGesture (useDrag, withDragProps)
1717import Storybook.React (Storybook, add, storiesOf)
1818import Yoga.Box.Component as Box
19−import Yoga.Box.Component as Center
2019import Yoga.Button.Component (mkButton)
2120import Yoga.Card.Component (mkCard)
21+import Yoga.Centre.Component as Centre
22+import Yoga.Cluster.Component as Cluster
2223import Yoga.Spec.Helpers (withDarkTheme)
24+import Yoga.Stack.Component as Stack
2325import Yoga.Theme.Styles (makeStylesJSS)
2426import Yoga.Theme.Types (CSSTheme)
2527
@@ -35,33 +37,51 @@ stories = do
3537
3638mkAnimated ∷ Effect (ReactComponent {})
3739mkAnimated = do
40+ button <- mkButton
41+ stack <- Stack.makeComponent
42+ centre <- Centre.makeComponent
3843 box <- Box.makeComponent
44+ cluster <- Cluster.makeComponent
3945 component "Animated Example" \{} -> React.do
4046 toggled /\ modifyToggled <- useState false
41− { style, set, stop } <- useSpring $ const { opacity: 0.7, transform: "scale3d(0.9,0.9,0.9)" }
42− let
43− _ =
44− unsafePerformEffect
45− $ set { opacity: if toggled then 0.7 else 0.9, transform: "scale3d(" <> if toggled then "0.9, 0.9, 1.0" else "0.95, 0.95, 1.0" <> ")" }
47+ { style, set, stop } <- useSpring $ const { opacity: 1.0, transform: "scale3d(1.0,1.0,1.0)" }
48+ useEffect toggled do
49+ set
50+ { opacity: if toggled then 0.7 else 1.0
51+ , transform: "scale3d(" <> if toggled then "0.2, 0.2, 1.0" else "1.0, 1.0, 1.0" <> ")"
52+ , config: { mass: 1, tension: 200, friction: 20 }
53+ }
54+ pure mempty
4655 pure
47− $ fragment
48− [ R.button { children: [ R.text "Toggle" ], onClick: handler_ $ modifyToggled not }
49− , animatedDiv
50− { style: css style
51− , children: [ jsx box {} [ R.text "Click the button" ] ]
52− }
56+ $ jsx stack {}
57+ [ jsx cluster {}
58+ [ R.div_
59+ [ jsx centre {}
60+ [ jsx button { onClick: handler_ $ modifyToggled not } [ R.text $ if toggled then "Zoom in" else "Zoom out" ]
61+ ]
62+ ]
63+ ]
64+ , jsx cluster {}
65+ [ R.div_
66+ [ jsx centre {}
67+ [ animatedDiv
68+ { style: css style
69+ , children: [ jsx box { invert: true } [ R.text "Click the button" ] ]
70+ }
71+ ]
72+ ]
73+ ]
5374 ]
5475
5576mkTransition ∷ Effect (ReactComponent {})
5677mkTransition = do
5778 button <- mkButton
79+ card <- mkCard
5880 useStyles <-
5981 makeStylesJSS
6082 $ jssClasses \(t ∷ CSSTheme) ->
6183 { div:
6284 { marginTop: "300px", marginLeft: "300px", width: "200px", height: "200px" }
63− , blue: { background: t.textColour }
64− , red: { background: t.white }
6585 }
6686 component "Transition Example" \{} -> React.do
6787 toggled /\ modifyToggled <- useState false
@@ -69,9 +89,10 @@ mkTransition = do
6989 transitions <-
7090 useTransition [ toggled ] Nothing
7191 $ css
72− { from: { opacity: 0.0, transform: "translate3d(-1000px, 0px, 0.0)", position: "absolute" }
73− , enter: { opacity: 1.0, transform: "translate3d(0.0, 0px, 0.0)" }
74− , leave: { opacity: 0.0, transform: "scale3d(0, 0, 1)" }
92+ { from: { opacity: 0.0, transform: "translate3d(-50vw, 0px, 0.0) scale3d(0.2, 0.2, 0.2)", position: "absolute" }
93+ , enter: { opacity: 1.0, transform: "translate3d(0.0, 0px, 0.0) scale3d(1.0,1.0,1.0)" }
94+ , leave: { opacity: 0.0, transform: "translate3d(50vw, 0px, 0.0) scale3d(0.0, 0.0, 1.0)" }
95+ , config: { mass: 5, tension: 500, friction: 100 }
7596 }
7697 pure
7798 $ fragment
@@ -79,15 +100,15 @@ mkTransition = do
79100 <#> \{ item, key, props } ->
80101 if item == Just true then
81102 animatedDiv
82− { className: classes.div <> " " <> classes.red
103+ { className: classes.div
83104 , style: props
84− , children: [ R.text "hello, you" ]
105+ , children: [ jsx card {} [ R.text "hello, you" ] ]
85106 }
86107 else
87108 animatedDiv
88− { className: classes.div <> " " <> classes.blue
109+ { className: classes.div
89110 , style: props
90− , children: [ R.text "I am different" ]
111+ , children: [ jsx card {} [ R.text "hello, again" ] ]
91112 }
92113 )
93114 <> [ jsx button { onClick: handler_ (modifyToggled not) } [ R.text "Toggle" ] ]
@@ -98,8 +119,7 @@ mkDragAnimated = do
98119 useStyles <-
99120 makeStylesJSS
100121 $ jssClasses \t ->
101− { card: { width: "200px", height: "200px" }
102− , div: { overflow: "visible" }
122+ { card: { width: "200px", height: "200px", marginTop: "30px" }
103123 }
104124 component "Draggable Example" \{} -> React.do
105125 { style, set } <- useSpring $ const { x: 0.0, y: 0.0, config: { mass: 1, tension: 210, friction: 20 } }
@@ -108,15 +128,11 @@ mkDragAnimated = do
108128 useDrag \{ down, movement: mx /\ my } ->
109129 set { x: if down then mx else 0.0, y: if down then my else 0.0 }
110130 pure
111− $ fragment
112− [ animatedDiv
113− ( { style: css style
114− , className: classes.div
115− , children:
116− [ jsx card { className: classes.card } [ R.text "Drag me somewhere" ]
117− ]
118− }
119− `withDragProps`
120− mkDragProps
121− )
131+ $ animatedDiv
132+ $ { style: css style
133+ , children:
134+ [ jsx card { className: classes.card } [ R.text "Drag me somewhere" ]
122135 ]
136+ }
137+ `withDragProps`
138+ mkDragProps
components/src/Yoga/Card/Component.purs+1−2
@@ -1,7 +1,7 @@
11module Yoga.Card.Component where
22
33import Prelude
4−import CSS (CSS, angular, deg, desaturate, linearGradient, pct)
4+import CSS (angular, deg, linearGradient, pct)
55import Color (cssStringRGBA)
66import Color as Color
77import Data.Interpolate (i)
@@ -13,7 +13,6 @@ import React.Basic (JSX)
1313import React.Basic.Helpers (jsx)
1414import React.Basic.Hooks (ReactComponent, component)
1515import React.Basic.Hooks as React
16−import Unsafe.Coerce (unsafeCoerce)
1716import Yoga.Box.Component as Box
1817import Yoga.Helpers ((?||))
1918import Yoga.Theme.Styles (makeStylesJSS)
components/src/Yoga/Card/Stories.purs+2−5
@@ -15,16 +15,13 @@ stories = do
1515 storiesOf "Card" do
1616 addDecorator fullScreenDecorator
1717 add "Example card" mkExample
18− [ { title: "An example card"
19− , subtitle: "It says some more"
20− , content: R.text loremIpsum
21− }
18+ [ {}
2219 ]
2320 where
2421 mkExample = do
2522 box <- Box.makeComponent
2623 card <- mkCard
27− component "ExampleCard" \{ title, subtitle, content } -> React.do
24+ component "ExampleCard" \{} -> React.do
2825 pure
2926 $ jsx box {}
3027 [ jsx card {}
components/src/Yoga/ClickAway/Component.pursnew file+45
@@ -0,0 +1,45 @@
1+module Yoga.ClickAway.Component where
2+
3+import Prelude
4+import Data.Maybe (Maybe(..))
5+import Data.Tuple.Nested ((/\))
6+import Effect (Effect)
7+import React.Basic.DOM (CSS)
8+import React.Basic.Events (handler_)
9+import React.Basic.Extra.Hooks.UseKeyUp (useKeyUp)
10+import React.Basic.Helpers (jsx, orUndefined)
11+import React.Basic.Hooks (ReactComponent, component, useState)
12+import React.Basic.Hooks as React
13+import Record.Extra (pick)
14+import Yoga.ClickAway.Styles as Style
15+import Yoga.Helpers (ifJustTrue)
16+import Yoga.Imposter.Component as Imposter
17+import Yoga.Theme.Styles (makeStylesJSS)
18+
19+type Props
20+ = Record PropsR
21+
22+type PropsR
23+ = ( onClick ∷ Effect Unit
24+ , allowEscape ∷ Maybe Boolean
25+ , style ∷ Maybe CSS
26+ | Style.PropsR
27+ )
28+
29+makeComponent ∷ Effect (ReactComponent Props)
30+makeComponent = do
31+ imposter <- Imposter.makeComponent
32+ useStyles <- makeStylesJSS Style.styles
33+ component "ClickAway" \props -> React.do
34+ cs <- useStyles (pick props)
35+ useKeyUp 27 $ ifJustTrue props.allowEscape props.onClick
36+ animationDone /\ modifyAnimationDone <- useState false
37+ pure
38+ $ jsx imposter
39+ { className: cs.darkOverlay
40+ , fixed: true
41+ , breakout: false
42+ , onClick: Just (handler_ props.onClick)
43+ , style: props.style # orUndefined
44+ }
45+ []
components/src/Yoga/ClickAway/Spec.pursnew file+21
@@ -0,0 +1,21 @@
1+module Yoga.ClickAway.Spec where
2+
3+import Prelude
4+import Effect (Effect)
5+import Justifill (justifill)
6+import React.TestingLibrary (describeComponent, renderComponent)
7+import Test.Spec (Spec, it)
8+import Yoga.ClickAway.Component as ClickAway
9+import Yoga.Spec.Helpers (withDarkTheme)
10+
11+spec ∷ Spec Unit
12+spec =
13+ describeComponent (withDarkTheme ClickAway.makeComponent)
14+ "The ClickAway Component" do
15+ it "renders without problems" \clickaway -> do
16+ _ <-
17+ renderComponent clickaway
18+ $ justifill
19+ { onClick: (pure unit) ∷ Effect Unit
20+ }
21+ pure unit
components/src/Yoga/ClickAway/Stories.pursnew file+56
@@ -0,0 +1,56 @@
1+module Yoga.ClickAway.Stories where
2+
3+import Prelude hiding (add)
4+import Data.Maybe (Maybe(..))
5+import Data.Monoid (guard)
6+import Data.Tuple.Nested ((/\))
7+import Effect (Effect)
8+import Justifill (justifill)
9+import React.Basic.DOM as R
10+import React.Basic.Events (handler_)
11+import React.Basic.Helpers (jsx)
12+import React.Basic.Hooks (ReactComponent, component, element, fragment, useState)
13+import React.Basic.Hooks as React
14+import Storybook.Decorator.FullScreen (fullScreenDecorator)
15+import Storybook.React (Storybook, add, addDecorator, storiesOf)
16+import Yoga.Button.Component (mkButton)
17+import Yoga.Centre.Component as Centre
18+import Yoga.ClickAway.Component as ClickAway
19+
20+stories ∷ Effect Storybook
21+stories = do
22+ storiesOf "ClickAway" do
23+ addDecorator fullScreenDecorator
24+ add "The ClickAway" ClickAway.makeComponent
25+ [ ( justifill
26+ { onClick: mempty ∷ Effect Unit
27+ }
28+ )
29+ ]
30+ add "Interactive ClickAway Example" mkExample
31+ [ {}
32+ ]
33+
34+mkExample ∷ Effect (ReactComponent {})
35+mkExample = do
36+ clickaway <- ClickAway.makeComponent
37+ centre <- Centre.makeComponent
38+ button <- mkButton
39+ component "ClickAwayStory" \{} -> React.do
40+ state /\ modState <- useState { open: true }
41+ pure
42+ $ fragment
43+ [ jsx centre {}
44+ [ jsx button
45+ { onClick: handler_ (modState (_ { open = true }))
46+ }
47+ [ R.text "Open ClickAway" ]
48+ ]
49+ , R.p_ [ R.text "Here's some text to see what happens when there's actual content" ]
50+ , guard (state.open)
51+ $ element clickaway
52+ { onClick: modState (_ { open = false })
53+ , allowEscape: Just true
54+ , style: Nothing
55+ }
56+ ]
components/src/Yoga/ClickAway/Styles.pursnew file+24
@@ -0,0 +1,24 @@
1+module Yoga.ClickAway.Styles where
2+
3+import Prelude hiding (top)
4+import JSS (JSSClasses, JSSElem, jssClasses)
5+import Yoga.Theme.Types (YogaTheme)
6+
7+type PropsR
8+ = ()
9+
10+type Props
11+ = Record PropsR
12+
13+type Classes a
14+ = ( darkOverlay ∷ a )
15+
16+styles ∷ JSSClasses YogaTheme Props (Classes (JSSElem Props))
17+styles =
18+ jssClasses \theme ->
19+ { darkOverlay:
20+ { width: "100vw"
21+ , height: "100vh"
22+ , backdropFilter: "blur(6px) brightness(67%)"
23+ }
24+ }
components/src/Yoga/CloseIcon/Component.pursnew file+65
@@ -0,0 +1,65 @@
1+module Yoga.CloseIcon.Component where
2+
3+import Prelude
4+import Data.Foldable (for_, traverse_)
5+import Data.Int (round, toNumber)
6+import Data.Interpolate (i)
7+import Data.Maybe (Maybe(..))
8+import Data.Tuple.Nested ((/\))
9+import Effect (Effect)
10+import Foreign.Object as Obj
11+import Math (pi, pow, sqrt)
12+import React.Basic.DOM (CSS, css)
13+import React.Basic.DOM.SVG as R
14+import React.Basic.DOM.SVG as SVG
15+import React.Basic.Events (handler_)
16+import React.Basic.Helpers (orUndefined)
17+import React.Basic.Hooks (ReactComponent, component, useEffect, useState)
18+import React.Basic.Hooks as React
19+import React.Basic.Hooks.Spring (animatedPath, useSpring)
20+import Record.Extra (pick)
21+import Yoga.Box.Component as Box
22+import Yoga.CloseIcon.Styles as Style
23+import Yoga.Theme.Styles (makeStylesJSS)
24+
25+type Props
26+ = Record PropsR
27+
28+type PropsR
29+ = ( onClick ∷ Effect Unit
30+ , style ∷ Maybe CSS
31+ | Style.PropsR
32+ )
33+
34+makeComponent ∷ Effect (ReactComponent Props)
35+makeComponent = do
36+ box <- Box.makeComponent
37+ useStyles <- makeStylesJSS Style.styles
38+ component "CloseIcon" \(props ∷ Props) -> React.do
39+ cs <- useStyles (pick props)
40+ animationDone /\ modifyAnimationDone <- useState false
41+ pure
42+ $ SVG.svg
43+ { viewBox: i "0 0 " width " " width
44+ , _data: Obj.singleton "testid" "close-icon-svg"
45+ , onClick: handler_ props.onClick
46+ , style: props.style # orUndefined
47+ , fill: "none"
48+ , className: cs.closeIcon
49+ , children:
50+ [ R.path
51+ { d: i "M" padding "," padding "L" (width - padding) "," (width - padding)
52+ }
53+ , R.path
54+ { d: i "M" padding "," (width - padding) ",L" (width - padding) "," padding
55+ }
56+ ]
57+ }
58+ where
59+ padding = 45
60+ width = 200
61+ cx = width / 2
62+ r = round ((toNumber width * 0.8) / 2.0)
63+ circleCircumf = round $ 2.0 * pi * toNumber r
64+ aSquare = toNumber (width - (2 * padding)) `pow` 2.0
65+ lineLen = sqrt (aSquare * 2.0)
components/src/Yoga/CloseIcon/Spec.pursnew file+35
@@ -0,0 +1,35 @@
1+module Yoga.CloseIcon.Spec where
2+
3+import Prelude
4+import Effect (Effect)
5+import Effect.Class (liftEffect)
6+import Effect.Ref as Ref
7+import Justifill (justifill)
8+import React.TestingLibrary (describeComponent, fireEventClick, renderComponent)
9+import Test.Spec (Spec, it)
10+import Test.Spec.Assertions (shouldEqual)
11+import Yoga.CloseIcon.Component as CloseIcon
12+import Yoga.Spec.Helpers (withDarkTheme)
13+
14+spec ∷ Spec Unit
15+spec =
16+ describeComponent (withDarkTheme CloseIcon.makeComponent)
17+ "The CloseIcon Component" do
18+ it "renders without problems" \closeicon -> do
19+ _ <-
20+ renderComponent closeicon
21+ $ justifill
22+ { onClick: (pure unit) ∷ Effect Unit
23+ }
24+ pure unit
25+ it "calls the onClose handler when clicking on the svg close" \closeicon -> do
26+ ref <- Ref.new false # liftEffect
27+ { findByTestId } <-
28+ renderComponent closeicon
29+ $ justifill
30+ { onClick: Ref.write true ref
31+ }
32+ closeBtn <- findByTestId "close-icon-svg"
33+ fireEventClick closeBtn
34+ clicked <- Ref.read ref # liftEffect
35+ clicked `shouldEqual` true
components/src/Yoga/CloseIcon/Stories.pursnew file+92
@@ -0,0 +1,92 @@
1+module Yoga.CloseIcon.Stories where
2+
3+import Prelude hiding (add)
4+import Data.Maybe (Maybe(..))
5+import Data.Monoid (guard)
6+import Data.Tuple.Nested ((/\))
7+import Effect (Effect)
8+import Justifill (justifill)
9+import React.Basic.DOM (css)
10+import React.Basic.DOM as R
11+import React.Basic.Events (handler_)
12+import React.Basic.Helpers (jsx)
13+import React.Basic.Hooks (ReactComponent, component, element, fragment, useState)
14+import React.Basic.Hooks as React
15+import React.Basic.Hooks.Spring (useTransition)
16+import Storybook.Decorator.FullScreen (fullScreenDecorator)
17+import Storybook.React (Storybook, add, addDecorator, storiesOf)
18+import Yoga.Button.Component (mkButton)
19+import Yoga.Centre.Component as Centre
20+import Yoga.CloseIcon.Component as CloseIcon
21+
22+stories ∷ Effect Storybook
23+stories = do
24+ storiesOf "CloseIcon" do
25+ addDecorator fullScreenDecorator
26+ add "The CloseIcon" CloseIcon.makeComponent
27+ [ justifill
28+ { onClick: pure unit ∷ Effect Unit
29+ }
30+ ]
31+ add "Interactive CloseIcon Example" mkExample
32+ [ {}
33+ ]
34+ add "Interactive Animated CloseIcon Example" mkAnimatedExample
35+ [ {}
36+ ]
37+
38+mkExample ∷ Effect (ReactComponent {})
39+mkExample = do
40+ closeicon <- CloseIcon.makeComponent
41+ centre <- Centre.makeComponent
42+ button <- mkButton
43+ component "CloseIconStory" \{} -> React.do
44+ state /\ modState <- useState { open: true }
45+ pure
46+ $ fragment
47+ [ jsx centre {}
48+ [ jsx button
49+ { onClick: handler_ (modState (_ { open = true }))
50+ }
51+ [ R.text "Open CloseIcon" ]
52+ ]
53+ , R.p_ [ R.text "Here's some text to see what happens when there's actual content" ]
54+ , guard (state.open)
55+ $ element closeicon
56+ { onClick: modState (_ { open = false })
57+ , style: Nothing
58+ }
59+ ]
60+
61+mkAnimatedExample ∷ Effect (ReactComponent {})
62+mkAnimatedExample = do
63+ closeicon <- CloseIcon.makeComponent
64+ centre <- Centre.makeComponent
65+ button <- mkButton
66+ component "CloseIconStoryAnimated" \{} -> React.do
67+ state /\ modState <- useState { open: true }
68+ transitions <-
69+ useTransition [ state.open ] Nothing
70+ $ css
71+ { from: { opacity: 0.0, transform: "translate3d(-50%, -50%, 0) scale3d(0.0, 0.0, 1.0)" }
72+ , enter: { opacity: 1.0, transform: "translate3d(-50%, -50%, 0) scale3d(1.0, 1.0, 1.0)" }
73+ , leave: { opacity: 0.0, transform: "translate3d(-50%, -50%, 0) scale3d(0.1, 0.1, 1.0)" }
74+ }
75+ pure
76+ $ fragment
77+ $ [ jsx centre {}
78+ [ jsx button
79+ { onClick: handler_ $ modState _ { open = true }
80+ }
81+ [ R.text "Open CloseIcon" ]
82+ ]
83+ , R.p_ [ R.text "Here's some text to see what happens when there's actual content" ]
84+ ]
85+ <> ( transitions
86+ <#> \{ item, key, props } ->
87+ guard (item == Just true)
88+ $ element closeicon
89+ { onClick: modState _ { open = false }
90+ , style: Just props
91+ }
92+ )
components/src/Yoga/CloseIcon/Styles.pursnew file+33
@@ -0,0 +1,33 @@
1+module Yoga.CloseIcon.Styles where
2+
3+import Prelude hiding (top)
4+import CSS (rem)
5+import JSS (JSSClasses, JSSElem, jssClasses)
6+import Yoga.Theme.Types (YogaTheme)
7+
8+type PropsR
9+ = (
10+ )
11+
12+type Props
13+ = Record PropsR
14+
15+type Classes a
16+ = ( closeIcon ∷ a
17+ )
18+
19+styles ∷ JSSClasses YogaTheme Props (Classes (JSSElem Props))
20+styles =
21+ jssClasses \theme ->
22+ let
23+ boxBorderRadius = 0.33 # rem
24+ in
25+ { closeIcon:
26+ { stroke: theme.textColour
27+ , strokeWidth: "var(--s0)"
28+ , width: "var(--s1)"
29+ , height: "var(--s1)"
30+ , margin: 0
31+ , padding: 0
32+ }
33+ }
components/src/Yoga/InlineCode/Stories.purs+6−2
@@ -16,6 +16,7 @@ import Milkis.Impl.Window (windowFetch)
1616import React.Basic (JSX, fragment)
1717import React.Basic.DOM as R
1818import React.Basic.Extra.Hooks.UseAffReducer (useAffReducer)
19+import React.Basic.Helpers (jsx)
1920import React.Basic.Hooks (ReactComponent, component, element)
2021import React.Basic.Hooks as React
2122import Storybook.Decorator.FullScreen (fullScreenDecorator)
@@ -56,14 +57,17 @@ stories = do
5657 add "The InlineCode with some real code" mkRealWrapper
5758 [ {} ]
5859
60+codePrefix ∷ String
5961codePrefix =
6062 """module Main where
6163import Batteries
6264
6365main = log """"
6466
67+codeSuffix ∷ String
6568codeSuffix = "\""
6669
70+renderCode ∷ String -> Array JSX
6771renderCode c = intercalate [ R.br {} ] (toJSX <$> lines)
6872 where
6973 toJSX line = [ R.code_ [ R.text line ] ]
@@ -98,8 +102,8 @@ mkRealWrapper = do
98102 , fragment $ renderCode codeSuffix
99103 , R.br {}
100104 , case state of
101− Just true -> element modal $ justifill { title: "Success!", content: R.text "You did it!", onClose: dispatch CloseModal }
102− Just false -> element modal $ justifill { title: "Oh no!", content: R.text "Try again!", onClose: dispatch CloseModal }
105+ Just true -> jsx modal { title: "Success!" } [ R.text "You did it!" ]
106+ Just false -> jsx modal { title: "Oh no!" } [ R.text "Try again!" ]
103107 Nothing -> mempty
104108 ]
105109
components/src/Yoga/Modal/Component.purs+17−122
@@ -2,29 +2,20 @@ module Yoga.Modal.Component where
22
33import Prelude
44import CSS (JustifyContentValue(..), spaceBetween)
5−import Data.Foldable (foldMap)
6−import Data.Int (round, toNumber)
7−import Data.Interpolate (i)
5+import Data.Array (fromFoldable)
86import Data.Maybe (Maybe)
9−import Data.Tuple.Nested ((/\))
107import Effect (Effect)
11−import Foreign.Object as Obj
12−import Math (pi, pow, sqrt)
13−import React.Basic (JSX, fragment)
14−import React.Basic.DOM (css)
8+import React.Basic (JSX)
9+import React.Basic.DOM (CSS)
1510import React.Basic.DOM as R
1611import React.Basic.DOM.Events (stopPropagation)
17−import React.Basic.DOM.SVG as SVG
18−import React.Basic.Events (handler, handler_)
19−import React.Basic.Extra.Hooks.UseKeyUp (useKeyUp)
20−import React.Basic.Helpers (jsx)
21−import React.Basic.Hooks (ReactComponent, component, element, useEffect, useState)
12+import React.Basic.Events (handler)
13+import React.Basic.Helpers (jsx, orUndefined)
14+import React.Basic.Hooks (ReactComponent, component)
2215import React.Basic.Hooks as React
23−import React.Basic.Hooks.Spring (animatedPath, useSpring)
2416import Record.Extra (pick)
2517import Yoga.Box.Component as Box
2618import Yoga.Cluster.Component as Cluster
27−import Yoga.Helpers ((?||))
2819import Yoga.Imposter.Component as Imposter
2920import Yoga.Modal.Styles as Style
3021import Yoga.Stack.Component as Stack
@@ -35,8 +26,9 @@ type Props
3526
3627type PropsR
3728 = ( title ∷ String
38− , content ∷ JSX
39− , onClose ∷ Maybe (Effect Unit)
29+ , kids ∷ Array JSX
30+ , icon ∷ Maybe JSX
31+ , style ∷ Maybe CSS
4032 | Style.PropsR
4133 )
4234
@@ -46,43 +38,16 @@ makeComponent = do
4638 box <- Box.makeComponent
4739 stack <- Stack.makeComponent
4840 cluster <- Cluster.makeComponent
49− closeIcon <- mkCloseIcon
5041 useStyles <- makeStylesJSS Style.styles
5142 component "Modal" \props -> React.do
5243 cs <- useStyles (pick props)
53− 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
6944 let
70− darkOverlay =
71− jsx imposter
72− { className: cs.darkOverlay
73− , fixed: true
74− , breakout: false
75− , onClick: props.onClose ?|| mempty # handler_
76− , style: css overlayStyle.style
77− }
78− []
79−
8045 dialogImposter =
8146 jsx imposter
8247 { className: cs.dialog
8348 , onClick: handler stopPropagation mempty
8449 , fixed: true
85− , style: css dialogStyle.style
50+ , style: props.style # orUndefined
8651 }
8752
8853 dialogBox =
@@ -107,85 +72,15 @@ makeComponent = do
10772 [ R.h4 { className: cs.title, children: [ R.text props.title ] }
10873 , R.div
10974 { className: cs.closeIcon
110− , children:
111− [ props.onClose
112− # foldMap
113− ( \onClick ->
114− ( element closeIcon
115− { className: cs.closeIcon, onClick, startAnimation: animationDone }
116− )
117− )
118− ]
75+ , children: fromFoldable props.icon
11976 }
12077 ]
12178 pure
122− $ fragment
123− [ darkOverlay
124− , dialogImposter
125− [ dialogBox
126− [ dialogBoxStack
127− [ titleCluster [ title ]
128− , props.content
129− ]
130− ]
79+ $ dialogImposter
80+ [ dialogBox
81+ [ dialogBoxStack
82+ $ [ titleCluster [ title ]
83+ ]
84+ <> props.kids
13185 ]
13286 ]
133−
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)
components/src/Yoga/Modal/Spec.purs+2−20
@@ -1,14 +1,10 @@
11module Yoga.Modal.Spec where
22
33import Prelude
4−import Effect (Effect)
5−import Effect.Class (liftEffect)
6−import Effect.Ref as Ref
74import Justifill (justifill)
85import React.Basic.DOM as R
9−import React.TestingLibrary (describeComponent, fireEventClick, renderComponent)
6+import React.TestingLibrary (describeComponent, renderComponent)
107import Test.Spec (Spec, it)
11−import Test.Spec.Assertions (shouldEqual)
128import Yoga.Modal.Component as Modal
139import Yoga.Spec.Helpers (withDarkTheme)
1410
@@ -21,20 +17,6 @@ spec =
2117 renderComponent modal
2218 $ justifill
2319 { title: "Hey"
24− , content: R.text "content"
25− , onClose: (pure unit) ∷ Effect Unit
20+ , kids: [ R.text "content" ]
2621 }
2722 pure unit
28− it "calls the onClose handler when clicking on the svg close" \modal -> do
29− ref <- Ref.new false # liftEffect
30− { findByTestId } <-
31− renderComponent modal
32− $ justifill
33− { title: "Hey"
34− , onClose: Ref.write true ref
35− , content: R.text "content"
36− }
37− closeBtn <- findByTestId "close-icon-svg"
38− fireEventClick closeBtn
39− clicked <- Ref.read ref # liftEffect
40− clicked `shouldEqual` true
components/src/Yoga/Modal/Stories.purs+54−8
@@ -4,19 +4,21 @@ import Prelude hiding (add)
44import Data.Maybe (Maybe(..))
55import Data.Monoid (guard)
66import Data.Tuple.Nested ((/\))
7+import Debug.Trace (spy)
78import Effect (Effect)
8−import Effect.Console (log)
99import Justifill (justifill)
10+import React.Basic.DOM (css)
1011import React.Basic.DOM as R
1112import React.Basic.Events (handler_)
1213import React.Basic.Helpers (jsx)
13−import React.Basic.Hooks (ReactComponent, component, element, fragment, useState)
14+import React.Basic.Hooks (ReactComponent, component, element, fragment, memo, useState)
1415import React.Basic.Hooks as React
15−import React.Basic.Hooks.Spring (animatedDiv, useSpring)
16+import React.Basic.Hooks.Spring (useTransition)
1617import Storybook.Decorator.FullScreen (fullScreenDecorator)
1718import Storybook.React (Storybook, add, addDecorator, storiesOf)
1819import Yoga.Button.Component (mkButton)
1920import Yoga.Centre.Component as Centre
21+import Yoga.CloseIcon.Component as CloseIcon
2022import Yoga.Modal.Component as Modal
2123
2224stories ∷ Effect Storybook
@@ -25,13 +27,16 @@ stories = do
2527 addDecorator fullScreenDecorator
2628 add "The Modal" Modal.makeComponent
2729 [ justifill
28− { content: R.text "I'm a Modal"
30+ { kids: [ R.text "I'm a Modal" ]
2931 , title: "Something important has happened this is excessively, if not prohibitively long title text to see what will happen"
3032 }
3133 ]
3234 add "Interactive Modal Example" mkExample
3335 [ {}
3436 ]
37+ add "Interactive Animated Modal Example" mkAnimatedExample
38+ [ {}
39+ ]
3540
3641mkExample ∷ Effect (ReactComponent {})
3742mkExample = do
@@ -50,10 +55,51 @@ mkExample = do
5055 ]
5156 , R.p_ [ R.text "Here's some text to see what happens when there's actual content" ]
5257 , guard (state.open)
53− $ element modal
58+ $ jsx modal
5459 { title: "Warning"
55− , content:
56− R.text "This is more interactive"
57− , onClose: Just $ modState (_ { open = false })
5860 }
61+ [ R.text "This is more interactive"
62+ ]
5963 ]
64+
65+mkAnimatedExample ∷ Effect (ReactComponent {})
66+mkAnimatedExample = do
67+ modal <- Modal.makeComponent
68+ centre <- Centre.makeComponent
69+ closeIcon <- memo CloseIcon.makeComponent
70+ button <- mkButton
71+ component "ModalStoryAnimated" \{} -> React.do
72+ state /\ modState <- useState { open: true }
73+ transitions <-
74+ useTransition [ state.open ] (Just $ show)
75+ $ css
76+ { from: { opacity: 0.0, transform: "translate3d(-50%, -50%, 0) scale3d(0.3, 0.3, 1.0)" }
77+ , enter: { opacity: 1.0, transform: "translate3d(-50%, -50%, 0) scale3d(1.0, 1.0, 1.0)" }
78+ , leave: { opacity: 0.0, transform: "translate3d(-50%, -50%, 0) scale3d(0.3, 0.3, 1.0)" }
79+ , config: { mass: 1.0, tension: 170, friction: 20 }
80+ }
81+ pure
82+ $ fragment
83+ $ [ jsx centre {}
84+ [ jsx button
85+ { onClick: handler_ $ modState _ { open = true }
86+ }
87+ [ R.text "Open Modal" ]
88+ ]
89+ , R.p_ [ R.text "Here's some text to see what happens when there's actual content" ]
90+ ]
91+ <> ( transitions
92+ <#> \{ item, key, props } ->
93+ guard (item == Just true)
94+ $ jsx modal
95+ { title: "Warning"
96+ , icon:
97+ element closeIcon
98+ $ justifill
99+ { onClick: modState _ { open = false }
100+ }
101+ , style: Just props
102+ }
103+ [ R.text "This is more interactive"
104+ ]
105+ )
components/src/Yoga/Modal/Styles.purs+2−9
@@ -1,9 +1,8 @@
11module Yoga.Modal.Styles where
22
33import Prelude hiding (top)
4−import CSS (backgroundColor, borderRadius, boxShadow, nil, opacity, rem, toHexString, transform, transforms, transition)
4+import CSS (backgroundColor, borderRadius, boxShadow, nil, rem)
55import CSS as Color
6−import CSS.Transform (scaleZ)
76import JSS (JSSClasses, JSSElem, jssClasses)
87import Yoga.Theme.Types (YogaTheme)
98
@@ -17,7 +16,6 @@ type Props
1716type Classes a
1817 = ( box ∷ a
1918 , closeIcon ∷ a
20− , darkOverlay ∷ a
2119 , dialog ∷ a
2220 , dialogBoxStack ∷ a
2321 , title ∷ a
@@ -30,12 +28,7 @@ styles =
3028 let
3129 boxBorderRadius = 0.33 # rem
3230 in
33− { darkOverlay:
34− { width: "100vw"
35− , height: "100vh"
36− , backdropFilter: "blur(6px) brightness(67%)"
37− }
38− , box:
31+ { box:
3932 do
4033 backgroundColor theme.interfaceColour
4134 boxShadow nil nil (3.5 # rem) (Color.rgba 0 0 0 0.5)
components/src/Yoga/Spec/Helpers.purs+3−1
@@ -5,6 +5,7 @@ import Effect (Effect)
55import Prim.Row (class Lacks)
66import React.Basic.Hooks (ReactComponent, component, element)
77import Yoga.Theme (fromTheme)
8+import Yoga.Theme.CSSBaseline (mkCssBaseline)
89import Yoga.Theme.Default (darkTheme, lightTheme)
910import Yoga.Theme.Provider (mkThemeProvider)
1011import Yoga.Theme.Types (CSSTheme)
@@ -19,12 +20,13 @@ withTheme ∷
1920 Effect (ReactComponent { | props })
2021withTheme theme mkComp = do
2122 themeProvider <- mkThemeProvider
23+ baseline <- mkCssBaseline
2224 comp <- mkComp
2325 component "ThemeWrapper" \(props ∷ { | props }) -> React.do
2426 pure
2527 $ element themeProvider
2628 { theme
27− , children: [ element comp props ]
29+ , children: [ element baseline { kids: [ element comp props ] } ]
2830 }
2931
3032withDarkTheme ∷
components/src/Yoga/Theme/CSSBaseline.purs+4−3
@@ -1,7 +1,7 @@
11module Yoga.Theme.CSSBaseline where
22
33import Prelude hiding (add)
4−import CSS (declare)
4+import CSS (backgroundColor, cssStringRGBA, declare)
55import Color (toHexString)
66import Data.Array.NonEmpty as NEA
77import Effect (Effect)
@@ -34,6 +34,7 @@ html =
3434root ∷ CSSTheme -> JSSElem {}
3535root theme =
3636 jss do
37+ backgroundColor theme.backgroundColour
3738 declare theme.ratioVar
3839 declare theme.s_5Var
3940 declare theme.s_4Var
@@ -72,11 +73,11 @@ styles =
7273 }
7374 , body:
7475 { margin: 0
75− , backgroundColor: theme.backgroundColour # toHexString
76+ , backgroundColor: theme.backgroundColour # cssStringRGBA
7677 , color: theme.textColour # toHexString
7778 , fontFamily: NEA.head theme.textFontFamily
7879 , "&::backdrop":
79− { backgroundColor: theme.backgroundColour # toHexString
80+ { backgroundColor: theme.backgroundColour # cssStringRGBA
8081 }
8182 }
8283 , "h1, h2, h3, h4":