git.iko.soy/rowtype-yoga/psfp
on master help sign in
More animations
f10a067Mark Eibes
components/src/React/Basic/Hooks/Spring.purs+3−1
@@ -6,12 +6,14 @@ import Data.Nullable (Nullable, toNullable)
6import Data.Nullable as Nullable
6import Data.Nullable as Nullable
7import Effect (Effect)
7import Effect (Effect)
8import Effect.Uncurried (EffectFn1, EffectFn3, runEffectFn1, runEffectFn3)
8import Effect.Uncurried (EffectFn1, EffectFn3, runEffectFn1, runEffectFn3)
9+import Foreign (Foreign)
9import Prim.Row (class Union)
10import Prim.Row (class Union)
10−import React.Basic (JSX, ReactComponent, element)
11+import React.Basic (JSX, ReactComponent, Ref, element)
11import React.Basic.DOM (CSS, Props_div, Props_span)
12import React.Basic.DOM (CSS, Props_div, Props_span)
12import React.Basic.DOM.SVG (Props_svg, Props_path)
13import React.Basic.DOM.SVG (Props_svg, Props_path)
13import React.Basic.Hooks (Hook, unsafeHook)
14import React.Basic.Hooks (Hook, unsafeHook)
14import React.Basic.Hooks as React
15import React.Basic.Hooks as React
16+import Simple.JSON (class WriteForeign)
15
17
16foreign import data UseSpring ∷ Type -> Type -> Type
18foreign import data UseSpring ∷ Type -> Type -> Type
17
19
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";
2
2
3−exports.useSpringImpl = mkStyles => () => {
3+exports.useSpringImpl = (mkStyles) => () => {
4 const result = useSpring(mkStyles);
4 const result = useSpring(mkStyles);
5 return { style: result[0], set: result[1], stop: result[2] };
5 return { style: result[0], set: result[1], stop: result[2] };
6};
6};
components/src/React/Basic/Hooks/Spring/Stories.purs+51−35
@@ -10,16 +10,18 @@ import React.Basic.DOM (css)
10import React.Basic.DOM as R
10import React.Basic.DOM as R
11import React.Basic.Events (handler_)
11import React.Basic.Events (handler_)
12import React.Basic.Helpers (jsx)
12import React.Basic.Helpers (jsx)
13−import React.Basic.Hooks (ReactComponent, component, fragment, useLayoutEffect, useState)
13+import React.Basic.Hooks (ReactComponent, component, fragment, useEffect, useState)
14import React.Basic.Hooks as React
14import React.Basic.Hooks as React
15import React.Basic.Hooks.Spring (animatedDiv, useSpring, useTransition)
15import React.Basic.Hooks.Spring (animatedDiv, useSpring, useTransition)
16import React.Basic.Hooks.UseGesture (useDrag, withDragProps)
16import React.Basic.Hooks.UseGesture (useDrag, withDragProps)
17import Storybook.React (Storybook, add, storiesOf)
17import Storybook.React (Storybook, add, storiesOf)
18import Yoga.Box.Component as Box
18import Yoga.Box.Component as Box
19−import Yoga.Box.Component as Center
20import Yoga.Button.Component (mkButton)
19import Yoga.Button.Component (mkButton)
21import Yoga.Card.Component (mkCard)
20import Yoga.Card.Component (mkCard)
21+import Yoga.Centre.Component as Centre
22+import Yoga.Cluster.Component as Cluster
22import Yoga.Spec.Helpers (withDarkTheme)
23import Yoga.Spec.Helpers (withDarkTheme)
24+import Yoga.Stack.Component as Stack
23import Yoga.Theme.Styles (makeStylesJSS)
25import Yoga.Theme.Styles (makeStylesJSS)
24import Yoga.Theme.Types (CSSTheme)
26import Yoga.Theme.Types (CSSTheme)
25
27
@@ -35,33 +37,51 @@ stories = do
35
37
36mkAnimated ∷ Effect (ReactComponent {})
38mkAnimated ∷ Effect (ReactComponent {})
37mkAnimated = do
39mkAnimated = do
40+ button <- mkButton
41+ stack <- Stack.makeComponent
42+ centre <- Centre.makeComponent
38 box <- Box.makeComponent
43 box <- Box.makeComponent
44+ cluster <- Cluster.makeComponent
39 component "Animated Example" \{} -> React.do
45 component "Animated Example" \{} -> React.do
40 toggled /\ modifyToggled <- useState false
46 toggled /\ modifyToggled <- useState false
41− { style, set, stop } <- useSpring $ const { opacity: 0.7, transform: "scale3d(0.9,0.9,0.9)" }
47+ { style, set, stop } <- useSpring $ const { opacity: 1.0, transform: "scale3d(1.0,1.0,1.0)" }
42− let
48+ useEffect toggled do
43− _ =
49+ set
44− unsafePerformEffect
50+ { opacity: if toggled then 0.7 else 1.0
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" <> ")" }
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
46 pure
55 pure
47− $ fragment
56+ $ jsx stack {}
57+ [ jsx cluster {}
58+ [ R.div_
59+ [ jsx centre {}
48− [ R.button { children: [ R.text "Toggle" ], onClick: handler_ $ modifyToggled not }
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 {}
49− , animatedDiv
67+ [ animatedDiv
50− { style: css style
68+ { style: css style
51− , children: [ jsx box {} [ R.text "Click the button" ] ]
69+ , children: [ jsx box { invert: true } [ R.text "Click the button" ] ]
52− }
70+ }
71+ ]
72+ ]
73+ ]
53 ]
74 ]
54
75
55mkTransition ∷ Effect (ReactComponent {})
76mkTransition ∷ Effect (ReactComponent {})
56mkTransition = do
77mkTransition = do
57 button <- mkButton
78 button <- mkButton
79+ card <- mkCard
58 useStyles <-
80 useStyles <-
59 makeStylesJSS
81 makeStylesJSS
60 $ jssClasses \(t ∷ CSSTheme) ->
82 $ jssClasses \(t ∷ CSSTheme) ->
61 { div:
83 { div:
62 { marginTop: "300px", marginLeft: "300px", width: "200px", height: "200px" }
84 { marginTop: "300px", marginLeft: "300px", width: "200px", height: "200px" }
63− , blue: { background: t.textColour }
64− , red: { background: t.white }
65 }
85 }
66 component "Transition Example" \{} -> React.do
86 component "Transition Example" \{} -> React.do
67 toggled /\ modifyToggled <- useState false
87 toggled /\ modifyToggled <- useState false
@@ -69,9 +89,10 @@ mkTransition = do
69 transitions <-
89 transitions <-
70 useTransition [ toggled ] Nothing
90 useTransition [ toggled ] Nothing
71 $ css
91 $ css
72− { from: { opacity: 0.0, transform: "translate3d(-1000px, 0px, 0.0)", position: "absolute" }
92+ { from: { opacity: 0.0, transform: "translate3d(-50vw, 0px, 0.0) scale3d(0.2, 0.2, 0.2)", position: "absolute" }
73− , enter: { opacity: 1.0, transform: "translate3d(0.0, 0px, 0.0)" }
93+ , enter: { opacity: 1.0, transform: "translate3d(0.0, 0px, 0.0) scale3d(1.0,1.0,1.0)" }
74− , leave: { opacity: 0.0, transform: "scale3d(0, 0, 1)" }
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 }
75 }
96 }
76 pure
97 pure
77 $ fragment
98 $ fragment
@@ -79,15 +100,15 @@ mkTransition = do
79 <#> \{ item, key, props } ->
100 <#> \{ item, key, props } ->
80 if item == Just true then
101 if item == Just true then
81 animatedDiv
102 animatedDiv
82− { className: classes.div <> " " <> classes.red
103+ { className: classes.div
83 , style: props
104 , style: props
84− , children: [ R.text "hello, you" ]
105+ , children: [ jsx card {} [ R.text "hello, you" ] ]
85 }
106 }
86 else
107 else
87 animatedDiv
108 animatedDiv
88− { className: classes.div <> " " <> classes.blue
109+ { className: classes.div
89 , style: props
110 , style: props
90− , children: [ R.text "I am different" ]
111+ , children: [ jsx card {} [ R.text "hello, again" ] ]
91 }
112 }
92 )
113 )
93 <> [ jsx button { onClick: handler_ (modifyToggled not) } [ R.text "Toggle" ] ]
114 <> [ jsx button { onClick: handler_ (modifyToggled not) } [ R.text "Toggle" ] ]
@@ -98,8 +119,7 @@ mkDragAnimated = do
98 useStyles <-
119 useStyles <-
99 makeStylesJSS
120 makeStylesJSS
100 $ jssClasses \t ->
121 $ jssClasses \t ->
101− { card: { width: "200px", height: "200px" }
122+ { card: { width: "200px", height: "200px", marginTop: "30px" }
102− , div: { overflow: "visible" }
103 }
123 }
104 component "Draggable Example" \{} -> React.do
124 component "Draggable Example" \{} -> React.do
105 { style, set } <- useSpring $ const { x: 0.0, y: 0.0, config: { mass: 1, tension: 210, friction: 20 } }
125 { style, set } <- useSpring $ const { x: 0.0, y: 0.0, config: { mass: 1, tension: 210, friction: 20 } }
@@ -108,15 +128,11 @@ mkDragAnimated = do
108 useDrag \{ down, movement: mx /\ my } ->
128 useDrag \{ down, movement: mx /\ my } ->
109 set { x: if down then mx else 0.0, y: if down then my else 0.0 }
129 set { x: if down then mx else 0.0, y: if down then my else 0.0 }
110 pure
130 pure
111− $ fragment
112− [ animatedDiv
131+ $ animatedDiv
113− ( { style: css style
132+ $ { style: css style
114− , className: classes.div
115− , children:
133+ , children:
116− [ jsx card { className: classes.card } [ R.text "Drag me somewhere" ]
134+ [ jsx card { className: classes.card } [ R.text "Drag me somewhere" ]
117− ]
118− }
119− `withDragProps`
120− mkDragProps
121− )
122 ]
135 ]
136+ }
137+ `withDragProps`
138+ mkDragProps
components/src/Yoga/Card/Component.purs+1−2
@@ -1,7 +1,7 @@
1module Yoga.Card.Component where
1module Yoga.Card.Component where
2
2
3import Prelude
3import Prelude
4−import CSS (CSS, angular, deg, desaturate, linearGradient, pct)
4+import CSS (angular, deg, linearGradient, pct)
5import Color (cssStringRGBA)
5import Color (cssStringRGBA)
6import Color as Color
6import Color as Color
7import Data.Interpolate (i)
7import Data.Interpolate (i)
@@ -13,7 +13,6 @@ import React.Basic (JSX)
13import React.Basic.Helpers (jsx)
13import React.Basic.Helpers (jsx)
14import React.Basic.Hooks (ReactComponent, component)
14import React.Basic.Hooks (ReactComponent, component)
15import React.Basic.Hooks as React
15import React.Basic.Hooks as React
16−import Unsafe.Coerce (unsafeCoerce)
17import Yoga.Box.Component as Box
16import Yoga.Box.Component as Box
18import Yoga.Helpers ((?||))
17import Yoga.Helpers ((?||))
19import Yoga.Theme.Styles (makeStylesJSS)
18import Yoga.Theme.Styles (makeStylesJSS)
components/src/Yoga/Card/Stories.purs+2−5
@@ -15,16 +15,13 @@ stories = do
15 storiesOf "Card" do
15 storiesOf "Card" do
16 addDecorator fullScreenDecorator
16 addDecorator fullScreenDecorator
17 add "Example card" mkExample
17 add "Example card" mkExample
18− [ { title: "An example card"
19− , subtitle: "It says some more"
20− , content: R.text loremIpsum
21− }
18+ [ {}
22 ]
19 ]
23 where
20 where
24 mkExample = do
21 mkExample = do
25 box <- Box.makeComponent
22 box <- Box.makeComponent
26 card <- mkCard
23 card <- mkCard
27− component "ExampleCard" \{ title, subtitle, content } -> React.do
24+ component "ExampleCard" \{} -> React.do
28 pure
25 pure
29 $ jsx box {}
26 $ jsx box {}
30 [ jsx card {}
27 [ 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)
16import React.Basic (JSX, fragment)
16import React.Basic (JSX, fragment)
17import React.Basic.DOM as R
17import React.Basic.DOM as R
18import React.Basic.Extra.Hooks.UseAffReducer (useAffReducer)
18import React.Basic.Extra.Hooks.UseAffReducer (useAffReducer)
19+import React.Basic.Helpers (jsx)
19import React.Basic.Hooks (ReactComponent, component, element)
20import React.Basic.Hooks (ReactComponent, component, element)
20import React.Basic.Hooks as React
21import React.Basic.Hooks as React
21import Storybook.Decorator.FullScreen (fullScreenDecorator)
22import Storybook.Decorator.FullScreen (fullScreenDecorator)
@@ -56,14 +57,17 @@ stories = do
56 add "The InlineCode with some real code" mkRealWrapper
57 add "The InlineCode with some real code" mkRealWrapper
57 [ {} ]
58 [ {} ]
58
59
60+codePrefix ∷ String
59codePrefix =
61codePrefix =
60 """module Main where
62 """module Main where
61import Batteries
63import Batteries
62
64
63main = log """"
65main = log """"
64
66
67+codeSuffix ∷ String
65codeSuffix = "\""
68codeSuffix = "\""
66
69
70+renderCode ∷ String -> Array JSX
67renderCode c = intercalate [ R.br {} ] (toJSX <$> lines)
71renderCode c = intercalate [ R.br {} ] (toJSX <$> lines)
68 where
72 where
69 toJSX line = [ R.code_ [ R.text line ] ]
73 toJSX line = [ R.code_ [ R.text line ] ]
@@ -98,8 +102,8 @@ mkRealWrapper = do
98 , fragment $ renderCode codeSuffix
102 , fragment $ renderCode codeSuffix
99 , R.br {}
103 , R.br {}
100 , case state of
104 , case state of
101− Just true -> element modal $ justifill { title: "Success!", content: R.text "You did it!", onClose: dispatch CloseModal }
105+ Just true -> jsx modal { title: "Success!" } [ R.text "You did it!" ]
102− Just false -> element modal $ justifill { title: "Oh no!", content: R.text "Try again!", onClose: dispatch CloseModal }
106+ Just false -> jsx modal { title: "Oh no!" } [ R.text "Try again!" ]
103 Nothing -> mempty
107 Nothing -> mempty
104 ]
108 ]
105
109
components/src/Yoga/Modal/Component.purs+17−122
@@ -2,29 +2,20 @@ module Yoga.Modal.Component where
2
2
3import Prelude
3import Prelude
4import CSS (JustifyContentValue(..), spaceBetween)
4import CSS (JustifyContentValue(..), spaceBetween)
5−import Data.Foldable (foldMap)
5+import Data.Array (fromFoldable)
6−import Data.Int (round, toNumber)
7−import Data.Interpolate (i)
8import Data.Maybe (Maybe)
6import Data.Maybe (Maybe)
9−import Data.Tuple.Nested ((/\))
10import Effect (Effect)
7import Effect (Effect)
11−import Foreign.Object as Obj
12−import Math (pi, pow, sqrt)
13−import React.Basic (JSX, fragment)
8+import React.Basic (JSX)
14−import React.Basic.DOM (css)
9+import React.Basic.DOM (CSS)
15import React.Basic.DOM as R
10import React.Basic.DOM as R
16import React.Basic.DOM.Events (stopPropagation)
11import React.Basic.DOM.Events (stopPropagation)
17−import React.Basic.DOM.SVG as SVG
12+import React.Basic.Events (handler)
18−import React.Basic.Events (handler, handler_)
19−import React.Basic.Extra.Hooks.UseKeyUp (useKeyUp)
20−import React.Basic.Helpers (jsx)
13+import React.Basic.Helpers (jsx, orUndefined)
21−import React.Basic.Hooks (ReactComponent, component, element, useEffect, useState)
14+import React.Basic.Hooks (ReactComponent, component)
22import React.Basic.Hooks as React
15import React.Basic.Hooks as React
23−import React.Basic.Hooks.Spring (animatedPath, useSpring)
24import Record.Extra (pick)
16import Record.Extra (pick)
25import Yoga.Box.Component as Box
17import Yoga.Box.Component as Box
26import Yoga.Cluster.Component as Cluster
18import Yoga.Cluster.Component as Cluster
27−import Yoga.Helpers ((?||))
28import Yoga.Imposter.Component as Imposter
19import Yoga.Imposter.Component as Imposter
29import Yoga.Modal.Styles as Style
20import Yoga.Modal.Styles as Style
30import Yoga.Stack.Component as Stack
21import Yoga.Stack.Component as Stack
@@ -35,8 +26,9 @@ type Props
35
26
36type PropsR
27type PropsR
37 = ( title ∷ String
28 = ( title ∷ String
38− , content ∷ JSX
29+ , kids ∷ Array JSX
39− , onClose ∷ Maybe (Effect Unit)
30+ , icon ∷ Maybe JSX
31+ , style ∷ Maybe CSS
40 | Style.PropsR
32 | Style.PropsR
41 )
33 )
42
34
@@ -46,43 +38,16 @@ makeComponent = do
46 box <- Box.makeComponent
38 box <- Box.makeComponent
47 stack <- Stack.makeComponent
39 stack <- Stack.makeComponent
48 cluster <- Cluster.makeComponent
40 cluster <- Cluster.makeComponent
49− closeIcon <- mkCloseIcon
50 useStyles <- makeStylesJSS Style.styles
41 useStyles <- makeStylesJSS Style.styles
51 component "Modal" \props -> React.do
42 component "Modal" \props -> React.do
52 cs <- useStyles (pick props)
43 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
69 let
44 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−
80 dialogImposter =
45 dialogImposter =
81 jsx imposter
46 jsx imposter
82 { className: cs.dialog
47 { className: cs.dialog
83 , onClick: handler stopPropagation mempty
48 , onClick: handler stopPropagation mempty
84 , fixed: true
49 , fixed: true
85− , style: css dialogStyle.style
50+ , style: props.style # orUndefined
86 }
51 }
87
52
88 dialogBox =
53 dialogBox =
@@ -107,85 +72,15 @@ makeComponent = do
107 [ R.h4 { className: cs.title, children: [ R.text props.title ] }
72 [ R.h4 { className: cs.title, children: [ R.text props.title ] }
108 , R.div
73 , R.div
109 { className: cs.closeIcon
74 { className: cs.closeIcon
110− , children:
75+ , children: fromFoldable props.icon
111− [ props.onClose
112− # foldMap
113− ( \onClick ->
114− ( element closeIcon
115− { className: cs.closeIcon, onClick, startAnimation: animationDone }
116− )
117− )
118− ]
119 }
76 }
120 ]
77 ]
121 pure
78 pure
122− $ fragment
123− [ darkOverlay
124− , dialogImposter
79+ $ dialogImposter
125− [ dialogBox
80+ [ dialogBox
126− [ dialogBoxStack
81+ [ dialogBoxStack
127− [ titleCluster [ title ]
82+ $ [ titleCluster [ title ]
83+ ]
128− , props.content
84+ <> props.kids
129− ]
130− ]
131 ]
85 ]
132 ]
86 ]
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 @@
1module Yoga.Modal.Spec where
1module Yoga.Modal.Spec where
2
2
3import Prelude
3import Prelude
4−import Effect (Effect)
5−import Effect.Class (liftEffect)
6−import Effect.Ref as Ref
7import Justifill (justifill)
4import Justifill (justifill)
8import React.Basic.DOM as R
5import React.Basic.DOM as R
9−import React.TestingLibrary (describeComponent, fireEventClick, renderComponent)
6+import React.TestingLibrary (describeComponent, renderComponent)
10import Test.Spec (Spec, it)
7import Test.Spec (Spec, it)
11−import Test.Spec.Assertions (shouldEqual)
12import Yoga.Modal.Component as Modal
8import Yoga.Modal.Component as Modal
13import Yoga.Spec.Helpers (withDarkTheme)
9import Yoga.Spec.Helpers (withDarkTheme)
14
10
@@ -21,20 +17,6 @@ spec =
21 renderComponent modal
17 renderComponent modal
22 $ justifill
18 $ justifill
23 { title: "Hey"
19 { title: "Hey"
24− , content: R.text "content"
20+ , kids: [ R.text "content" ]
25− , onClose: (pure unit) ∷ Effect Unit
26 }
21 }
27 pure unit
22 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)
4import Data.Maybe (Maybe(..))
4import Data.Maybe (Maybe(..))
5import Data.Monoid (guard)
5import Data.Monoid (guard)
6import Data.Tuple.Nested ((/\))
6import Data.Tuple.Nested ((/\))
7+import Debug.Trace (spy)
7import Effect (Effect)
8import Effect (Effect)
8−import Effect.Console (log)
9import Justifill (justifill)
9import Justifill (justifill)
10+import React.Basic.DOM (css)
10import React.Basic.DOM as R
11import React.Basic.DOM as R
11import React.Basic.Events (handler_)
12import React.Basic.Events (handler_)
12import React.Basic.Helpers (jsx)
13import 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)
14import React.Basic.Hooks as React
15import React.Basic.Hooks as React
15−import React.Basic.Hooks.Spring (animatedDiv, useSpring)
16+import React.Basic.Hooks.Spring (useTransition)
16import Storybook.Decorator.FullScreen (fullScreenDecorator)
17import Storybook.Decorator.FullScreen (fullScreenDecorator)
17import Storybook.React (Storybook, add, addDecorator, storiesOf)
18import Storybook.React (Storybook, add, addDecorator, storiesOf)
18import Yoga.Button.Component (mkButton)
19import Yoga.Button.Component (mkButton)
19import Yoga.Centre.Component as Centre
20import Yoga.Centre.Component as Centre
21+import Yoga.CloseIcon.Component as CloseIcon
20import Yoga.Modal.Component as Modal
22import Yoga.Modal.Component as Modal
21
23
22stories ∷ Effect Storybook
24stories ∷ Effect Storybook
@@ -25,13 +27,16 @@ stories = do
25 addDecorator fullScreenDecorator
27 addDecorator fullScreenDecorator
26 add "The Modal" Modal.makeComponent
28 add "The Modal" Modal.makeComponent
27 [ justifill
29 [ justifill
28− { content: R.text "I'm a Modal"
30+ { kids: [ R.text "I'm a Modal" ]
29 , title: "Something important has happened this is excessively, if not prohibitively long title text to see what will happen"
31 , title: "Something important has happened this is excessively, if not prohibitively long title text to see what will happen"
30 }
32 }
31 ]
33 ]
32 add "Interactive Modal Example" mkExample
34 add "Interactive Modal Example" mkExample
33 [ {}
35 [ {}
34 ]
36 ]
37+ add "Interactive Animated Modal Example" mkAnimatedExample
38+ [ {}
39+ ]
35
40
36mkExample ∷ Effect (ReactComponent {})
41mkExample ∷ Effect (ReactComponent {})
37mkExample = do
42mkExample = do
@@ -50,10 +55,51 @@ mkExample = do
50 ]
55 ]
51 , R.p_ [ R.text "Here's some text to see what happens when there's actual content" ]
56 , R.p_ [ R.text "Here's some text to see what happens when there's actual content" ]
52 , guard (state.open)
57 , guard (state.open)
53− $ element modal
58+ $ jsx modal
54 { title: "Warning"
59 { title: "Warning"
55− , content:
56− R.text "This is more interactive"
57− , onClose: Just $ modState (_ { open = false })
58 }
60 }
61+ [ R.text "This is more interactive"
62+ ]
59 ]
63 ]
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 @@
1module Yoga.Modal.Styles where
1module Yoga.Modal.Styles where
2
2
3import Prelude hiding (top)
3import Prelude hiding (top)
4−import CSS (backgroundColor, borderRadius, boxShadow, nil, opacity, rem, toHexString, transform, transforms, transition)
4+import CSS (backgroundColor, borderRadius, boxShadow, nil, rem)
5import CSS as Color
5import CSS as Color
6−import CSS.Transform (scaleZ)
7import JSS (JSSClasses, JSSElem, jssClasses)
6import JSS (JSSClasses, JSSElem, jssClasses)
8import Yoga.Theme.Types (YogaTheme)
7import Yoga.Theme.Types (YogaTheme)
9
8
@@ -17,7 +16,6 @@ type Props
17type Classes a
16type Classes a
18 = ( box ∷ a
17 = ( box ∷ a
19 , closeIcon ∷ a
18 , closeIcon ∷ a
20− , darkOverlay ∷ a
21 , dialog ∷ a
19 , dialog ∷ a
22 , dialogBoxStack ∷ a
20 , dialogBoxStack ∷ a
23 , title ∷ a
21 , title ∷ a
@@ -30,12 +28,7 @@ styles =
30 let
28 let
31 boxBorderRadius = 0.33 # rem
29 boxBorderRadius = 0.33 # rem
32 in
30 in
33− { darkOverlay:
34− { width: "100vw"
35− , height: "100vh"
36− , backdropFilter: "blur(6px) brightness(67%)"
37− }
38− , box:
31+ { box:
39 do
32 do
40 backgroundColor theme.interfaceColour
33 backgroundColor theme.interfaceColour
41 boxShadow nil nil (3.5 # rem) (Color.rgba 0 0 0 0.5)
34 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)
5import Prim.Row (class Lacks)
5import Prim.Row (class Lacks)
6import React.Basic.Hooks (ReactComponent, component, element)
6import React.Basic.Hooks (ReactComponent, component, element)
7import Yoga.Theme (fromTheme)
7import Yoga.Theme (fromTheme)
8+import Yoga.Theme.CSSBaseline (mkCssBaseline)
8import Yoga.Theme.Default (darkTheme, lightTheme)
9import Yoga.Theme.Default (darkTheme, lightTheme)
9import Yoga.Theme.Provider (mkThemeProvider)
10import Yoga.Theme.Provider (mkThemeProvider)
10import Yoga.Theme.Types (CSSTheme)
11import Yoga.Theme.Types (CSSTheme)
@@ -19,12 +20,13 @@ withTheme ∷
19 Effect (ReactComponent { | props })
20 Effect (ReactComponent { | props })
20withTheme theme mkComp = do
21withTheme theme mkComp = do
21 themeProvider <- mkThemeProvider
22 themeProvider <- mkThemeProvider
23+ baseline <- mkCssBaseline
22 comp <- mkComp
24 comp <- mkComp
23 component "ThemeWrapper" \(props ∷ { | props }) -> React.do
25 component "ThemeWrapper" \(props ∷ { | props }) -> React.do
24 pure
26 pure
25 $ element themeProvider
27 $ element themeProvider
26 { theme
28 { theme
27− , children: [ element comp props ]
29+ , children: [ element baseline { kids: [ element comp props ] } ]
28 }
30 }
29
31
30withDarkTheme ∷
32withDarkTheme ∷
components/src/Yoga/Theme/CSSBaseline.purs+4−3
@@ -1,7 +1,7 @@
1module Yoga.Theme.CSSBaseline where
1module Yoga.Theme.CSSBaseline where
2
2
3import Prelude hiding (add)
3import Prelude hiding (add)
4−import CSS (declare)
4+import CSS (backgroundColor, cssStringRGBA, declare)
5import Color (toHexString)
5import Color (toHexString)
6import Data.Array.NonEmpty as NEA
6import Data.Array.NonEmpty as NEA
7import Effect (Effect)
7import Effect (Effect)
@@ -34,6 +34,7 @@ html =
34root ∷ CSSTheme -> JSSElem {}
34root ∷ CSSTheme -> JSSElem {}
35root theme =
35root theme =
36 jss do
36 jss do
37+ backgroundColor theme.backgroundColour
37 declare theme.ratioVar
38 declare theme.ratioVar
38 declare theme.s_5Var
39 declare theme.s_5Var
39 declare theme.s_4Var
40 declare theme.s_4Var
@@ -72,11 +73,11 @@ styles =
72 }
73 }
73 , body:
74 , body:
74 { margin: 0
75 { margin: 0
75− , backgroundColor: theme.backgroundColour # toHexString
76+ , backgroundColor: theme.backgroundColour # cssStringRGBA
76 , color: theme.textColour # toHexString
77 , color: theme.textColour # toHexString
77 , fontFamily: NEA.head theme.textFontFamily
78 , fontFamily: NEA.head theme.textFontFamily
78 , "&::backdrop":
79 , "&::backdrop":
79− { backgroundColor: theme.backgroundColour # toHexString
80+ { backgroundColor: theme.backgroundColour # cssStringRGBA
80 }
81 }
81 }
82 }
82 , "h1, h2, h3, h4":
83 , "h1, h2, h3, h4":