git.iko.soy/rowtype-yoga/ry-blocks
on main help sign in
Input work
commitc32928933fe49d41ed2b7bea4c0712021867c1cc authorMark Eibes <mark.eibes@gmail.com> dateSun 20 Dec 2020, 23:44 (6 years ago) parent774db18
47 files changed+1331−275
.psci_modules/index.js+1 convert-more.fish+8 convert-svgs.js+2−2 package.json+1−1 packages.dhall+1−1 spago.dhall+1 src/Framer/Motion.purs+39 src/React/Basic/Popper/Hook.purs+2−34 src/React/Basic/Popper/Story.purs+103−63 src/React/Basic/Popper/Types.purs+40 src/Yoga/Block/Atom/CodeInput/Style.purs+1−1 src/Yoga/Block/Atom/Icon.purs+5 src/Yoga/Block/Atom/Icon/Story.purs+37 src/Yoga/Block/Atom/Icon/Style.purs+27 src/Yoga/Block/Atom/Icon/View.purs+32 src/Yoga/Block/Atom/Input.purs+5 src/Yoga/Block/Atom/Input/Story.purs+56 src/Yoga/Block/Atom/Input/Style.purs+91 src/Yoga/Block/Atom/Input/View.purs+142 src/Yoga/Block/Atom/Range/Style.purs−2 src/Yoga/Block/Atom/Range/View.purs+6−4 src/Yoga/Block/Atom/Toggle/Spec.purs+3−2 src/Yoga/Block/Atom/Toggle/Story.purs+17−14 src/Yoga/Block/Atom/Toggle/Style.purs+19−11 src/Yoga/Block/Atom/Toggle/Types.purs+19 src/Yoga/Block/Atom/Toggle/View.purs+169−96 src/Yoga/Block/Atom/Tooltip.purs+5 src/Yoga/Block/Atom/Tooltip/Spec.purs+16 src/Yoga/Block/Atom/Tooltip/Story.purs+39 src/Yoga/Block/Atom/Tooltip/Style.purs+57 src/Yoga/Block/Atom/Tooltip/View.purs+129 src/Yoga/Block/Container/Style.purs+5−20 src/Yoga/Block/Icon/SVG/EyeClosed.purs+32 src/Yoga/Block/Icon/SVG/EyeOpen.purs+44 src/Yoga/Block/Icon/SVG/Icon.purs+13 src/Yoga/Block/Icon/SVG/MagnifyingGlass.purs+36 src/Yoga/Block/Icon/SVG/Off.purs+30 src/Yoga/Block/Icon/SVG/On.purs+41 src/Yoga/Block/Icon/SVG/QuestionMark.purs+25 src/Yoga/Block/Icon/SVG/eyeClosed.svg+1 src/Yoga/Block/Icon/SVG/eyeOpen.svg+1 src/Yoga/Block/Icon/SVG/magnifyingGlass.svg+1 src/Yoga/Block/Icon/SVG/off.svg+1 src/Yoga/Block/Icon/SVG/on.svg+1 src/Yoga/Block/Icon/SVG/questionMark.svg+1 src/Yoga/Prelude/Default.purs+4 yarn.lock+22−24
.psci_modules/index.jsnew file+1
@@ -0,0 +1 @@
1+require('$PSCI')['$main']();
convert-more.fishnew file+8
@@ -0,0 +1,8 @@
1+for f in (ls src/Yoga/Block/Icon/SVG/*.svg)
2+ npx svgo -i $f
3+end
4+node convert-svgs.js
5+for f in (ls src/Yoga/Block/Icon/SVG/*.purs)
6+ sed -i "" 's/stroke: "#333"/stroke: "var(--stroke-colour)"/g' $f
7+end
8+
convert-svgs.js+2−2
@@ -1,6 +1,6 @@
11const path = require('path');
22const svg2psreact = require("svg2psreact");
33
4−const assetsDir = path.join(__dirname, 'src', 'Assets');
4+const assetsDir = path.join(__dirname, 'src', 'Yoga', 'Block', 'Icon', 'SVG');
55
6−svg2psreact.convertAllSVGsInDirectory("Assets", assetsDir)
6+svg2psreact.convertAllSVGsInDirectory("Yoga.Block.Icon.SVG", assetsDir)
package.json+1−1
@@ -17,7 +17,7 @@
1717 "babel-loader": "^8.1.0",
1818 "clean-webpack-plugin": "^3.0.0",
1919 "compression-webpack-plugin": "^6.0.3",
20− "framer-motion": "^2.7.0",
20+ "framer-motion": "^3.0.0",
2121 "html-webpack-plugin": "^4.5.0",
2222 "jsdom": "^16.3.0",
2323 "jsdom-global": "^3.0.2",
packages.dhall+1−1
@@ -119,7 +119,7 @@ let additions =
119119
120120
121121let upstream =
122− https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20201021/packages.dhall sha256:55ebdbda1bd6ede4d5307fbc1ef19988c80271b4225d833c8d6fb9b6fb1aa6d8
122+ https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20201217/packages.dhall sha256:f46d45e29977f3b57717b56d20a5ceac12532224516eea3012a4688f22ac1539
123123
124124let overrides =
125125 { spec-discovery = upstream.spec-discovery // { version = "master" }
spago.dhall+1
@@ -18,6 +18,7 @@ You can edit this file as you like.
1818 , "routing-duplex"
1919 , "spec-discovery"
2020 , "untagged-union"
21+ , "web-uievents"
2122 ]
2223, packages = ./packages.dhall
2324, sources = [ "src/**/*.purs", "test/**/*.purs" ]
src/Framer/Motion.purs+39
@@ -6,6 +6,14 @@ module Framer.Motion
66 , callback
77 , class EffectFnMaker
88 , toEffectFn
9+ , OnHoverStart
10+ , onHoverStart
11+ , customProp
12+ , OnHoverEnd
13+ , onHoverEnd
14+ , whileHover
15+ , WhileHover
16+ , EventInfo
917 , OnTap
1018 , TapInfo
1119 , OnTapStart
@@ -80,6 +88,7 @@ import Data.Nullable (Nullable)
8088import Data.Symbol (class IsSymbol, SProxy, reflectSymbol)
8189import Effect (Effect)
8290import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, mkEffectFn2)
91+import Foreign (Foreign, unsafeToForeign)
8392import Foreign.Object (Object)
8493import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmapWithIndex)
8594import Literals.Undefined (Undefined)
@@ -97,6 +106,7 @@ import Untagged.Castable (class Castable, cast)
97106import Untagged.Union (type (|+|))
98107import Web.DOM (Node)
99108import Web.Event.Internal.Types (Event)
109+import Web.UIEvent.MouseEvent (MouseEvent)
100110import Yoga.Block.Internal (Id)
101111
102112foreign import divImpl ∷ ∀ a. ReactComponent { | a }
@@ -216,6 +226,28 @@ onTap fn2 = cast (mkEffectFn2 fn2)
216226onTapCancel ∷ (Event -> TapInfo -> Effect Unit) -> OnTap
217227onTapCancel fn2 = cast (mkEffectFn2 fn2)
218228
229+type EventInfo =
230+ { point ∷ { x ∷ Number, y ∷ Number }
231+ }
232+
233+type WhileHover =
234+ (EffectFn2 MouseEvent EventInfo Unit |+| Undefined)
235+
236+type OnHoverEnd =
237+ (EffectFn2 MouseEvent EventInfo Unit |+| Undefined)
238+
239+type OnHoverStart =
240+ (EffectFn2 MouseEvent EventInfo Unit |+| Undefined)
241+
242+onHoverStart ∷ (MouseEvent -> EventInfo -> Effect Unit) -> OnHoverStart
243+onHoverStart = cast <<< toEffectFn
244+
245+onHoverEnd ∷ (MouseEvent -> EventInfo -> Effect Unit) -> OnHoverEnd
246+onHoverEnd = cast <<< toEffectFn
247+
248+whileHover ∷ ∀ c. Castable c WhileHover => c -> WhileHover
249+whileHover = cast
250+
219251type TapInfo =
220252 { x ∷ Number, y ∷ Number }
221253
@@ -250,9 +282,13 @@ onDragEnd = cast <<< toEffectFn
250282onDrag ∷ (Event -> PanInfo -> Effect Unit) -> OnDrag
251283onDrag fn2 = cast (mkEffectFn2 fn2)
252284
285+customProp ∷ ∀ a. a -> Foreign
286+customProp = unsafeToForeign
287+
253288type MotionPropsF f r =
254289 ( initial ∷ f Initial
255290 , animate ∷ f Animate
291+ , custom ∷ f Foreign
256292 , drag ∷ f Drag
257293 , dragMomentum ∷ f DragMomentum
258294 , dragElastic ∷ f DragElastic
@@ -270,6 +306,9 @@ type MotionPropsF f r =
270306 , onTapStart ∷ f OnTapStart
271307 , onTapEnd ∷ f OnTapEnd
272308 , onTapCancel ∷ f OnTapCancel
309+ , whileHover ∷ f WhileHover
310+ , onHoverStart ∷ f OnHoverStart
311+ , onHoverEnd ∷ f OnHoverEnd
273312 , exit ∷ f Exit
274313 | r
275314 )
src/React/Basic/Popper/Hook.purs+2−34
@@ -7,43 +7,11 @@ import Effect.Uncurried (EffectFn3, runEffectFn3)
77import Foreign.Object (Object)
88import Foreign.Object as Object
99import Prim.Row (class Union)
10−import React.Basic.DOM (CSS)
11−import Unsafe.Coerce (unsafeCoerce)
12−import Yoga.Prelude.View (Hook, NodeRef, unsafeHook)
10+import Yoga.Prelude.View (Hook, unsafeHook)
11+import React.Basic.Popper.Types (Options, PopperData, PopperElement, ReferenceElement)
1312
1413foreign import data UsePopper ∷ Type -> Type -> Type
1514
16−type ReferenceElement =
17− NodeRef
18−
19−type PopperElement =
20− NodeRef
21−
22−type ArrowElement =
23− NodeRef
24−
25−type DataAttributes =
26− { popper ∷ Object String, arrow ∷ Object String }
27−
28−type Styles =
29− { popper ∷ CSS, arrow ∷ CSS }
30−
31−type PopperData =
32− { styles ∷ Styles, attributes ∷ DataAttributes }
33−
34−foreign import data Modifier ∷ Type
35−
36−modifierArrow ∷ ArrowElement -> Modifier
37−modifierArrow element = unsafeCoerce { name: "arrow", options: { element } }
38−
39−modifierOffset ∷ { x ∷ Number, y ∷ Number } -> Modifier
40−modifierOffset { x, y } = unsafeCoerce { name: "offset", options: { offset: [ x, y ] } }
41−
42−type Options =
43− ( modifiers ∷ Array Modifier
44− , strategy ∷ String
45− )
46−
4715foreign import usePopperImpl ∷ ∀ opts. EffectFn3 ReferenceElement PopperElement { | opts } PopperData
4816
4917toDataAttributes ∷ Object String -> Object String
src/React/Basic/Popper/Story.purs+103−63
@@ -1,27 +1,24 @@
11module React.Basic.Popper.Story where
22
33import Prelude
4−import Color as Color
5−import Data.Maybe (Maybe(..))
4+import Data.Interpolate (i)
65import Data.Nullable (null)
76import Data.Tuple.Nested ((/\))
87import Effect (Effect)
98import Effect.Uncurried (mkEffectFn1)
109import Effect.Unsafe (unsafePerformEffect)
11−import Framer.Motion (onTap)
1210import Framer.Motion as Motion
1311import React.Basic (JSX, element, fragment)
1412import React.Basic.DOM as R
1513import React.Basic.Emotion as E
1614import React.Basic.Hooks as React
17−import React.Basic.Popper.Hook (modifierArrow, modifierOffset, usePopper)
15+import React.Basic.Popper.Hook (usePopper)
16+import React.Basic.Popper.Types (modifierArrow, modifierOffset)
1817import Unsafe.Coerce (unsafeCoerce)
1918import Yoga.Block as Block
20−import Yoga.Block.Atom.Toggle as Toggle
21−import Yoga.Block.Container.Style (DarkOrLightMode(..))
2219import Yoga.Block.Container.Style as Styles
2320import Yoga.Block.Internal.CSS (nest)
24−import Yoga.Prelude.View (NodeRef, el, styled, styledLeaf)
21+import Yoga.Prelude.View (NodeRef, el, guard, handler, preventDefault, styled, styledLeaf)
2522
2623default ∷
2724 { decorators ∷ Array (Effect JSX -> JSX)
@@ -136,6 +133,7 @@ animatedPopper = do
136133
137134 mkBasicExample =
138135 React.reactComponent "Popper example" \p -> React.do
136+ -- Hooks
139137 referenceElement /\ setReferenceElement <- React.useState' nullRef
140138 popperElement /\ setPopperElement <- React.useState' nullRef
141139 arrowElement /\ setArrowElement <- React.useState' nullRef
@@ -143,63 +141,105 @@ animatedPopper = do
143141 usePopper referenceElement popperElement
144142 { modifiers:
145143 [ modifierArrow arrowElement
146− , modifierOffset { x: 0.0, y: 8.0 }
144+ , modifierOffset { x: 0.0, y: 12.0 }
147145 ]
148146 }
149− pure
150− $ fragment
151− $ [ element R.div'
152− { ref: unsafeCoerce (mkEffectFn1 setReferenceElement)
153− , children: [ R.text "Reference element" ]
154− }
155− , el Motion.animatePresence {}
156− [ styled Motion.div
157− { className: "popper-element"
158− , key: "heinz"
159− , css:
160− E.css
161− { background: E.str "darkslateblue"
162− , borderRadius: E.str "8px"
163− , display: E.str "block"
164− , padding: E.str "4px 8px"
165− , "&[data-popper-placement^='top'] > .popper-arrow":
166− nest { bottom: E.str "-4px" }
167− , "&[data-popper-placement^='bottom'] > .popper-arrow":
168− nest { top: E.str "-4px" }
169− , "&[data-popper-placement^='left'] > .popper-arrow":
170− nest { right: E.str "-4px" }
171− , "&[data-popper-placement^='right'] > .popper-arrow":
172− nest { left: E.str "-4px" }
173− }
174− , ref: unsafeCoerce (mkEffectFn1 setPopperElement)
175− , style: styles.popper
176− , _data: attributes.popper
177− }
178− [ R.text "Popper Element"
179− , styledLeaf R.div'
180− { className: "popper-arrow"
181− , id: "arrow"
182− , css:
183− E.css
184− { position: E.str "absolute"
185− , width: E.str "8px"
186− , height: E.str "8px"
187− , zIndex: E.str "-1"
188− , "&::before":
189− nest
190− { position: E.str "absolute"
191− , width: E.str "8px"
192− , height: E.str "8px"
193− , zIndex: E.str "-1"
194− , content: E.str "''"
195− , transform: E.str "rotate(45deg)"
196− , background: E.str "darkslateblue"
197− }
198− }
199− , ref: unsafeCoerce (mkEffectFn1 setArrowElement)
200− , style: styles.arrow
201− , _data: attributes.arrow
202− }
147+ on /\ setOn <- React.useState' true
148+ -- Handlers
149+ let buttonClicked = setOn (not on)
150+ -- Elements
151+ let
152+ result =
153+ fragment
154+ $ [ refElem
155+ , popperEl
156+ [ animatePresence
157+ $ guard on
158+ [ content
159+ [ el Block.box {} [ R.text "Ich bin Stinky Bill" ]
160+ , arrow
161+ ]
162+ ]
203163 ]
204164 ]
205− ]
165+ animatePresence =
166+ el Motion.animatePresence
167+ { initial: false
168+ }
169+ content =
170+ styled Motion.div
171+ { className: "popper-element-content"
172+ , css: contentCss
173+ , initial: Motion.initial $ R.css { opacity: [ 1.0, 0.8, 0.0 ], scale: [ 1.0, 0.85 ] }
174+ , animate: Motion.animate $ R.css { opacity: [ 0.0, 0.3, 1.0 ], scale: [ 0.0, 1.05, 1.0, 0.98, 1.01, 1.0 ] }
175+ , exit: Motion.exit $ R.css { opacity: [ 1.0, 0.8, 0.0 ], scale: [ 1.0, 0.85 ] }
176+ , transition: Motion.transition { duration: 0.2 }
177+ , key: "container"
178+ }
179+ popperEl =
180+ styled R.div'
181+ { className: "popper-element"
182+ , css: popperCss
183+ , ref: unsafeCoerce (mkEffectFn1 setPopperElement)
184+ , style: styles.popper
185+ , _data: attributes.popper
186+ }
187+ arrow =
188+ styledLeaf R.div'
189+ { className: "popper-arrow"
190+ , id: "arrow"
191+ , css: arrowCss
192+ , ref: unsafeCoerce (mkEffectFn1 setArrowElement)
193+ , style: styles.arrow
194+ , _data: attributes.arrow
195+ }
196+ refElem =
197+ element R.button'
198+ { ref: unsafeCoerce (mkEffectFn1 setReferenceElement)
199+ , children: [ R.text "Reference element" ]
200+ , onClick: handler preventDefault (const buttonClicked)
201+ }
202+ pure result
203+
204+ backgroundColour = "var(--highlight)"
205+
206+ backgroundColour2 = "rgb(100, 160, 240)"
207+
208+ arrowCss =
209+ E.css
210+ { position: E.str "absolute"
211+ , width: E.str "8px"
212+ , height: E.str "8px"
213+ , zIndex: E.str "0"
214+ , "&::before":
215+ nest
216+ { position: E.str "absolute"
217+ , width: E.str "8px"
218+ , height: E.str "8px"
219+ , content: E.str "''"
220+ , transform: E.str "rotate(45deg)"
221+ , background: E.str backgroundColour
222+ }
223+ }
224+
225+ contentCss =
226+ E.css
227+ { background: E.str $ i "linear-gradient(0deg," backgroundColour "," backgroundColour2 "," backgroundColour ")"
228+ , fontSize: E.str "14px"
229+ , zIndex: E.str "-1"
230+ , color: E.str $ "white"
231+ , boxShadow: E.str "0 1px 8px rgba(0,0,0,0.5)"
232+ , borderRadius: E.str "12px"
233+ }
234+
235+ popperCss =
236+ E.css
237+ { "&[data-popper-placement^='top'] > * > .popper-arrow":
238+ nest { bottom: E.str "-4px" }
239+ , "&[data-popper-placement^='bottom'] > * > .popper-arrow":
240+ nest { top: E.str "-4px" }
241+ , "&[data-popper-placement^='left'] > * > .popper-arrow":
242+ nest { right: E.str "-4px" }
243+ , "&[data-popper-placement^='right'] > * > .popper-arrow":
244+ nest { left: E.str "-4px" }
245+ }
src/React/Basic/Popper/Types.pursnew file+40
@@ -0,0 +1,40 @@
1+module React.Basic.Popper.Types where
2+
3+import Foreign.Object (Object)
4+import React.Basic.DOM (CSS)
5+import Unsafe.Coerce (unsafeCoerce)
6+import Yoga.Prelude.View (NodeRef, null)
7+
8+nullRef ∷ NodeRef
9+nullRef = unsafeCoerce null
10+
11+type ReferenceElement =
12+ NodeRef
13+
14+type PopperElement =
15+ NodeRef
16+
17+type ArrowElement =
18+ NodeRef
19+
20+type DataAttributes =
21+ { popper ∷ Object String, arrow ∷ Object String }
22+
23+type Styles =
24+ { popper ∷ CSS, arrow ∷ CSS }
25+
26+type PopperData =
27+ { styles ∷ Styles, attributes ∷ DataAttributes }
28+
29+foreign import data Modifier ∷ Type
30+
31+modifierArrow ∷ ArrowElement -> Modifier
32+modifierArrow element = unsafeCoerce { name: "arrow", options: { element } }
33+
34+modifierOffset ∷ { x ∷ Number, y ∷ Number } -> Modifier
35+modifierOffset { x, y } = unsafeCoerce { name: "offset", options: { offset: [ x, y ] } }
36+
37+type Options =
38+ ( modifiers ∷ Array Modifier
39+ , strategy ∷ String
40+ )
src/Yoga/Block/Atom/CodeInput/Style.purs+1−1
@@ -21,7 +21,7 @@ codeInput props = styles <>? cast props.css
2121 , background: str colour.inputBackground
2222 , border: str $ i "solid 1px " colour.inputBorder
2323 , borderRadius: str "var(--s-2)"
24− , fontFamily: str "var(--monoFont)"
24+ , fontFamily: str "var(--mono-font)"
2525 , fontSize: str "var(--s0)"
2626 , lineHeight: str "var(--s0)"
2727 , width: str $ i "calc(" (cast props.maxLength ?|| 10) "ch + 4.3 * var(--s-5))"
src/Yoga/Block/Atom/Icon.pursnew file+5
@@ -0,0 +1,5 @@
1+module Yoga.Block.Atom.Icon
2+ ( module Yoga.Block.Atom.Icon.View
3+ ) where
4+
5+import Yoga.Block.Atom.Icon.View (component, Props)
src/Yoga/Block/Atom/Icon/Story.pursnew file+37
@@ -0,0 +1,37 @@
1+module Yoga.Block.Atom.Icon.Story where
2+
3+import Prelude
4+import Effect (Effect)
5+import Effect.Unsafe (unsafePerformEffect)
6+import React.Basic (JSX, element, fragment)
7+import React.Basic.DOM as R
8+import React.Basic.Emotion as E
9+import Yoga.Block.Atom.Icon as Icon
10+import Yoga.Block.Container.Style as Styles
11+import Yoga.Block.Icon.SVG as SVGIcon
12+
13+default ∷
14+ { decorators ∷ Array (Effect JSX -> JSX)
15+ , title ∷ String
16+ }
17+default =
18+ { title: "Atom/Icon"
19+ , decorators:
20+ [ \storyFn ->
21+ R.div_
22+ [ element E.global { styles: Styles.global }
23+ , unsafePerformEffect storyFn
24+ ]
25+ ]
26+ }
27+
28+icon ∷ Effect JSX
29+icon = do
30+ pure
31+ $ fragment
32+ [ R.div_
33+ [ R.h2_ [ R.text "Icon" ]
34+ , element Icon.component { icon: SVGIcon.on }
35+ , element Icon.component { icon: SVGIcon.off }
36+ ]
37+ ]
src/Yoga/Block/Atom/Icon/Style.pursnew file+27
@@ -0,0 +1,27 @@
1+module Yoga.Block.Atom.Icon.Style where
2+
3+import Yoga.Prelude.Style
4+import Yoga.Block.Container.Style (colour)
5+
6+type Props f r =
7+ ( css ∷ f Style
8+ , colour ∷ f StyleProperty
9+ , stroke ∷ f StyleProperty
10+ , fill ∷ f StyleProperty
11+ , size ∷ f StyleProperty
12+ , width ∷ f StyleProperty
13+ , height ∷ f StyleProperty
14+ | r
15+ )
16+
17+span ∷ ∀ r. { | Props OptionalProp r } -> Style
18+span props =
19+ css
20+ { "--stroke-colour": (props.stroke <|> props.colour) ?|| (str colour.text)
21+ , "--fill-colour": (props.fill <|> props.colour) ?|| (str "transparent")
22+ , "& > svg":
23+ nest
24+ { width: (props.width <|> props.size) ?|| (str "auto")
25+ , height: (props.height <|> props.size) ?|| (str "1.2ch")
26+ }
27+ }
src/Yoga/Block/Atom/Icon/View.pursnew file+32
@@ -0,0 +1,32 @@
1+module Yoga.Block.Atom.Icon.View where
2+
3+import Yoga.Prelude.View
4+import React.Basic.DOM as R
5+import Yoga.Block.Atom.Icon.Style as Style
6+
7+type Props =
8+ PropsF Id
9+
10+type MandatoryProps r =
11+ ( icon ∷ JSX
12+ | r
13+ )
14+
15+type PropsF f =
16+ ( className ∷ f String
17+ | Style.Props f (MandatoryProps ())
18+ )
19+
20+component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | MandatoryProps p }
21+component = rawComponent
22+
23+rawComponent ∷ ∀ p. ReactComponent { | p }
24+rawComponent =
25+ mkForwardRefComponent "Yoga Icon" \(props ∷ { | PropsF OptionalProp }) ref -> React.do
26+ pure
27+ $ styled R.span'
28+ { className: "ry-icon" <>? props.className
29+ , css: Style.span props
30+ , ref
31+ }
32+ [ props.icon ]
src/Yoga/Block/Atom/Input.pursnew file+5
@@ -0,0 +1,5 @@
1+module Yoga.Block.Atom.Input
2+ ( module Yoga.Block.Atom.Input.View
3+ ) where
4+
5+import Yoga.Block.Atom.Input.View (component, Props)
src/Yoga/Block/Atom/Input/Story.pursnew file+56
@@ -0,0 +1,56 @@
1+module Yoga.Block.Atom.Input.Story where
2+
3+import Prelude
4+import Effect (Effect)
5+import Effect.Unsafe (unsafePerformEffect)
6+import React.Basic (JSX, element, fragment)
7+import React.Basic.DOM as R
8+import React.Basic.Emotion as E
9+import React.Basic.Events (handler_)
10+import Yoga.Block.Atom.Input as Input
11+import Yoga.Block.Container.Style as Styles
12+
13+default ∷
14+ { decorators ∷ Array (Effect JSX -> JSX)
15+ , title ∷ String
16+ }
17+default =
18+ { title: "Atom/Input"
19+ , decorators:
20+ [ \storyFn ->
21+ R.div_
22+ [ element E.global { styles: Styles.global }
23+ , unsafePerformEffect storyFn
24+ ]
25+ ]
26+ }
27+
28+input ∷ Effect JSX
29+input = do
30+ pure
31+ $ fragment
32+ [ R.div_
33+ [ R.h2_ [ R.text "Generic Input" ]
34+ , element Input.component { value: "A Generic Input", onChange: handler_ mempty }
35+ , R.h2_ [ R.text "Password" ]
36+ , element Input.component { type: "password" }
37+ , R.h2_ [ R.text "Text Input" ]
38+ , element Input.component { type: "text", value: "Some text", onChange: handler_ mempty }
39+ , R.h2_ [ R.text "Search Input" ]
40+ , element Input.component { type: "search", value: "Search...", onChange: handler_ mempty }
41+ , R.h2_ [ R.text "Button" ]
42+ , element Input.component { type: "button", value: "A button", onChange: handler_ mempty }
43+ , R.h2_ [ R.text "Submit" ]
44+ , element Input.component { type: "submit" }
45+ , R.h2_ [ R.text "Radio" ]
46+ , element Input.component { type: "radio" }
47+ , R.h2_ [ R.text "Checkbox" ]
48+ , element Input.component { type: "checkbox" }
49+ , R.h2_ [ R.text "File" ]
50+ , element Input.component { type: "file" }
51+ , R.h2_ [ R.text "Image" ]
52+ , element Input.component { type: "image" }
53+ , R.h2_ [ R.text "Number" ]
54+ , element Input.component { type: "number" }
55+ ]
56+ ]
src/Yoga/Block/Atom/Input/Style.pursnew file+91
@@ -0,0 +1,91 @@
1+module Yoga.Block.Atom.Input.Style where
2+
3+import Yoga.Prelude.Style
4+import Yoga.Block.Container.Style (colour)
5+import Yoga.Prelude.Style as Color
6+
7+type Props f r =
8+ ( css ∷ f Style
9+ | r
10+ )
11+
12+leftIconSize ∷ StyleProperty
13+leftIconSize = var "--left-icon-size"
14+
15+rightIconSize ∷ StyleProperty
16+rightIconSize = var "--right-icon-size"
17+
18+leftIconStyle ∷ Style
19+leftIconStyle =
20+ css
21+ { "--stroke-colour": str colour.interfaceTextDisabled
22+ , marginTop: str "4px"
23+ , marginLeft: str "-4px"
24+ }
25+
26+rightIconStyle ∷ Style
27+rightIconStyle =
28+ css
29+ { "--stroke-colour": str colour.interfaceTextDisabled
30+ , paddingTop: str "6px"
31+ , marginRight: str "6px"
32+ }
33+
34+textWrapper ∷ ∀ r. { | Props OptionalProp r } -> Style
35+textWrapper props =
36+ css
37+ { fontFamily: var "--main-font"
38+ , boxSizing: borderBox
39+ , backgroundColor: str $ colour.background07
40+ , display: inlineFlex
41+ , "--left-icon-size": var "--s0"
42+ , "--right-icon-size": str "calc(var(--s0) * 1.2)"
43+ , alignItems: center
44+ , justifyContent: flexStart
45+ , paddingLeft: var "--s-1"
46+ , gap: var "--s-3"
47+ , "--border-width": var "--s-5"
48+ , border: str $ "var(--border-width) solid " <> colour.inputBorder
49+ , borderRadius: var "--s-1"
50+ , "&:focus-within":
51+ nest
52+ {}
53+ }
54+
55+input ∷ ∀ r. { | Props OptionalProp r } -> Style
56+input props =
57+ css
58+ { "&[type=text],&[type=search],&[type=password],&[type=number],&:not([type])":
59+ nest
60+ { color: str colour.text
61+ , background: str "transparent"
62+ , alignSelf: stretch
63+ , "--padding-top": str "var(--s-1)"
64+ , "--padding-bottom": str "calc(var(--padding-top) * 0.85)"
65+ , paddingTop: var "--padding-top"
66+ , paddingBottom: var "--padding-bottom"
67+ , paddingLeft: _0
68+ , paddingRight: _0
69+ -- , background: str colour.inputBackground
70+ , border: none
71+ }
72+ , "&[type=search]":
73+ nest
74+ { "&::-webkit-search-decoration, &::-webkit-search-cancel-button, &::-webkit-serch-results-button, &::-webkit-search-results-decoration":
75+ nest
76+ { "WebkitAppearance": none
77+ }
78+ }
79+ , "&:focus":
80+ nest
81+ { outline: none
82+ }
83+ , "&[type=button], &[type=submit]":
84+ nest
85+ { background: str colour.highlight
86+ , color: str "white"
87+ , boxShadow: str "0 1px 4px rgba(0,0,0,0.5)"
88+ , borderColour: str colour.highlight
89+ , height: str "auto"
90+ }
91+ }
src/Yoga/Block/Atom/Input/View.pursnew file+142
@@ -0,0 +1,142 @@
1+module Yoga.Block.Atom.Input.View where
2+
3+import Yoga.Prelude.View
4+import Framer.Motion as M
5+import React.Basic.DOM (css)
6+import React.Basic.DOM as R
7+import React.Basic.DOM.SVG as SVG
8+import React.Basic.Hooks as React
9+import Record.Extra (pick)
10+import Unsafe.Coerce (unsafeCoerce)
11+import Yoga.Block.Atom.Icon as Icon
12+import Yoga.Block.Atom.Input.Style as Style
13+import Yoga.Block.Atom.Tooltip as Tooltip
14+import Yoga.Block.Icon.SVG as SVGIcon
15+
16+type PropsF f =
17+ ( iconLeft ∷ f JSX
18+ , iconRight ∷ f JSX
19+ | Style.Props f InputProps
20+ )
21+
22+type Props =
23+ PropsF Id
24+
25+type PropsOptional =
26+ PropsF OptionalProp
27+
28+component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | p }
29+component = rawComponent
30+
31+mkLeftIcon ∷ JSX -> JSX
32+mkLeftIcon icon =
33+ styledLeaf
34+ Icon.component
35+ { size: Style.leftIconSize
36+ , className: "ry-input-right-icon"
37+ , css: Style.leftIconStyle
38+ , icon
39+ }
40+
41+mkRightIcon ∷ JSX -> JSX
42+mkRightIcon icon =
43+ styledLeaf Icon.component
44+ { size: Style.rightIconSize
45+ , className: "ry-input-left-icon"
46+ , css: Style.rightIconStyle
47+ , icon
48+ }
49+
50+rawComponent ∷ ∀ p. ReactComponent { | p }
51+rawComponent =
52+ mkForwardRefComponent "Input" do
53+ \(props ∷ { | PropsOptional }) ref -> React.do
54+ let
55+ iconLeft =
56+ props.iconLeft
57+ ?|| if (((cast props.type) ?|| "") == "search") then mkLeftIcon SVGIcon.magnifyingGlass else mempty
58+ iconRight =
59+ props.iconRight
60+ ?|| if (((cast props.type) ?|| "") == "password") then mkRightIcon SVGIcon.eyeOpen else mempty
61+ pure
62+ $ case props.type # cast # opToMaybe of
63+ Just "password" -> el_ password props
64+ _ ->
65+ styled R.div'
66+ { className: "ry-input-wrapper"
67+ , css: Style.textWrapper props
68+ }
69+ [ iconLeft
70+ , emotionInput
71+ ref
72+ props
73+ { className: "ry-input"
74+ , css: Style.input props
75+ }
76+ , iconRight
77+ ]
78+
79+password ∷ ∀ p. ReactComponent { | p }
80+password =
81+ mkForwardRefComponent "Password" do
82+ \(props ∷ { | PropsOptional }) ref -> React.do
83+ hidePassword /\ modifyHidePassword <- useState true
84+ let
85+ eyeCon =
86+ el_ Tooltip.component
87+ { target:
88+ el R.div'
89+ { onClick: handler preventDefault \_ -> modifyHidePassword not
90+ }
91+ [ el M.animatePresence
92+ { exitBeforeEnter: true
93+ }
94+ [ if hidePassword then
95+ el M.div
96+ { key: "eyeOpen"
97+ , initial: M.initial $ css { scaleY: 0 }
98+ , animate: M.animate $ css { scaleY: 1 }
99+ , exit: M.exit $ css { scaleY: 0.9 }
100+ , transition: M.transition { duration: 0.15 }
101+ }
102+ [ styledLeaf Icon.component
103+ { size: Style.rightIconSize
104+ , className: "ry-input-left-icon"
105+ , css: Style.rightIconStyle
106+ , icon: SVGIcon.eyeOpen
107+ }
108+ ]
109+ else
110+ el M.div
111+ { key: "eyeClosed"
112+ , initial: M.initial $ css { scaleY: 0.0 }
113+ , animate: M.animate $ css { scaleY: 1 }
114+ , exit: M.exit $ css { scaleY: 0.9 }
115+ , transition: M.transition { duration: 0.15 }
116+ }
117+ [ styledLeaf Icon.component
118+ { size: Style.rightIconSize
119+ , className: "ry-input-left-icon"
120+ , css: Style.rightIconStyle
121+ , icon: SVGIcon.eyeClosed
122+ }
123+ ]
124+ ]
125+ ]
126+ , theTip: R.text if hidePassword then "Show password" else "Hide password"
127+ }
128+ let iconRight = props.iconRight ?|| eyeCon
129+ pure
130+ $ styled R.div'
131+ { className: "ry-input-wrapper"
132+ , css: Style.textWrapper props
133+ }
134+ [ emotionInput
135+ ref
136+ props
137+ { className: "ry-input"
138+ , css: Style.input props
139+ , type: if hidePassword then "password" else "text"
140+ }
141+ , iconRight
142+ ]
src/Yoga/Block/Atom/Range/Style.purs−2
@@ -6,8 +6,6 @@ import Yoga.Block.Container.Style (colour)
66
77type Props f r =
88 ( css ∷ f Style
9− , space ∷ f StyleProperty
10− , splitAfter ∷ f Int
119 | r
1210 )
1311
src/Yoga/Block/Atom/Range/View.purs+6−4
@@ -1,13 +1,15 @@
11module Yoga.Block.Atom.Range.View (component, Props, PropsF) where
22
33import Yoga.Prelude.View
4−import Yoga.Block.Atom.Range.Style as Style
54import Data.Int as Int
65import Foreign.Object as Object
6+import Framer.Motion as M
77import React.Basic.DOM (css)
88import React.Basic.DOM as R
99import React.Basic.DOM.Events as Event
1010import React.Basic.Hooks as React
11+import Yoga.Block.Atom.Range.Style as Style
12+import Yoga.Block.Atom.Tooltip as Tooltip
1113
1214type PropsF f =
1315 ( className ∷ f String
@@ -58,11 +60,11 @@ rawComponent =
5860 { className: "ry-range-focus-circle"
5961 , css: Style.focusCircle
6062 }
61− , emotionInput ref (props { max = show max, min = show min, value = show value })
63+ , emotionInput
64+ ref
65+ (props { max = show max, min = show min, value = show value })
6266 { className: "ry-range-thumb " <>? props.className
6367 , css: Style.range props <> guard props.disabled Style.inputDisabled <>? props.css
64− , style: props.style
65− , value: show value
6668 , type: "range"
6769 , onChange: handler Event.targetValue ((_ >>= Int.fromString) >>> (foldMap setValue))
6870 }
src/Yoga/Block/Atom/Toggle/Spec.purs+3−2
@@ -2,6 +2,7 @@ module Yoga.Block.Atom.Toggle.Spec where
22
33import Yoga.Prelude.Spec
44import Yoga.Block.Atom.Toggle as Toggle
5+import Yoga.Block.Atom.Toggle.Types (TogglePosition(..))
56
67spec ∷ Spec Unit
78spec =
@@ -10,6 +11,6 @@ spec =
1011 it "renders without errors" do
1112 void
1213 $ renderComponent Toggle.component
13− { value: true
14− , onToggle: mempty
14+ { togglePosition: ToggleIsLeft
15+ , setTogglePosition: mempty
1516 }
src/Yoga/Block/Atom/Toggle/Story.purs+17−14
@@ -10,6 +10,7 @@ import React.Basic.DOM as R
1010import React.Basic.Hooks as React
1111import Yoga.Block as Block
1212import Yoga.Block.Atom.Toggle as Toggle
13+import Yoga.Block.Atom.Toggle.Types (TogglePosition(..))
1314import Yoga.Block.Container.Style (DarkOrLightMode(..))
1415
1516default ∷
@@ -35,30 +36,32 @@ toggle = do
3536 where
3637 mkBasicExample =
3738 React.reactComponent "Toggle example" \p -> React.do
38− isOn /\ turnOnOrOff <- React.useState' false
39+ togglePosition /\ setTogglePosition <- React.useState' ToggleIsRight
3940 pure
4041 $ element Toggle.component
41− { value: isOn
42− , onToggle: turnOnOrOff
42+ { togglePosition
43+ , setTogglePosition
4344 }
4445
4546 mkDarkLightToggle =
4647 React.reactComponent "Toggle dark night example" \p -> React.do
47− isOn /\ turnOnOrOff <- React.useState' false
48+ togglePosition /\ setTogglePosition <- React.useState' ToggleIsLeft
4849 theme /\ setTheme <- React.useState' Nothing
4950 let
5051 content =
5152 element Toggle.component
52− { value: isOn
53− , onToggle:
54− \b -> do
55− turnOnOrOff b
56− setTheme (Just if b then DarkMode else LightMode)
57− , on: R.text "🌒"
58− , off: R.text "🌞"
59− , backgroundOn:
60− Color.hsl 205.0 1.0 0.93
61− , backgroundOff:
53+ { togglePosition: togglePosition
54+ , setTogglePosition:
55+ \newTogglePosition -> do
56+ setTogglePosition newTogglePosition
57+ setTheme case newTogglePosition of
58+ ToggleIsRight -> Just DarkMode
59+ ToggleIsLeft -> Just LightMode
60+ , left: R.text "🌒"
61+ , right: R.text "🌞"
62+ , backgroundLeft:
63+ Color.hsl 205.0 1.0 0.83
64+ , backgroundRight:
6265 Color.hsl 260.0 0.7 0.45
6366 }
6467 pure
src/Yoga/Block/Atom/Toggle/Style.purs+19−11
@@ -5,8 +5,8 @@ import Yoga.Block.Container.Style (colour)
55
66type Props f r =
77 ( css ∷ f Style
8− , backgroundOn ∷ f Color
9− , backgroundOff ∷ f Color
8+ , backgroundLeft ∷ f Color
9+ , backgroundRight ∷ f Color
1010 | r
1111 )
1212
@@ -23,17 +23,17 @@ button =
2323 , padding: _0
2424 }
2525
26−theToggle ∷ ∀ p. { | Props OptionalProp p } -> Style
27−theToggle props =
26+theToggle ∷ Style
27+theToggle =
2828 css
29− { width: var "--s2"
30− , height: var "--s2"
29+ { width: str "calc(var(--s2) * 0.8)"
30+ , height: str "calc(var(--s2) * 0.8)"
3131 , background: str $ colour.interfaceBackground
3232 , border: none
3333 , borderRadius: str $ "calc(var(--s2) / 2)"
3434 , position: absolute
35− , top: str "-1px"
36− , left: _0
35+ , top: str "3px"
36+ , left: str "3px"
3737 , margin: _0
3838 , boxShadow: str "0 0.5px 3px rgba(0,0,0,0.50)"
3939 }
@@ -46,25 +46,33 @@ toggleTextContainer =
4646 , fontWeight: str "bold"
4747 , position: absolute
4848 , top: _0
49+ , fontSize: str "calc(0.6 * var(--s1))"
4950 , lineHeight: var "--s2"
5051 , height: var "--s2"
5152 , display: flex
5253 }
5354
55+successTextColour ∷ StyleProperty
56+successTextColour = str colour.successText
57+
58+disabledTextColour ∷ StyleProperty
59+disabledTextColour = str colour.interfaceTextDisabled
60+
5461toggleText ∷ Style
5562toggleText =
5663 css
5764 { textAlign: str "left"
5865 , margin: _0
66+ , padding: _0
5967 , width: str "50%"
6068 , height: str "100%"
6169 , display: flex
6270 , justifyContent: center
6371 , alignItems: center
64− , color: str colour.interfaceTextDisabled
65− , "&:first-child":
72+ , color: disabledTextColour
73+ , "& > *":
6674 nest
67− { color: str colour.successText
75+ { color: successTextColour
6876 }
6977 }
7078
src/Yoga/Block/Atom/Toggle/Types.pursnew file+19
@@ -0,0 +1,19 @@
1+module Yoga.Block.Atom.Toggle.Types where
2+
3+import Prelude
4+
5+data TogglePosition
6+ = ToggleIsLeft
7+ | ToggleIsRight
8+
9+derive instance eqTogglePosition ∷ Eq TogglePosition
10+
11+instance showTogglePosition ∷ Show TogglePosition where
12+ show = case _ of
13+ ToggleIsLeft -> "ToggleIsLeft"
14+ ToggleIsRight -> "ToggleIsRight"
15+
16+flipToggle ∷ TogglePosition -> TogglePosition
17+flipToggle = case _ of
18+ ToggleIsLeft -> ToggleIsRight
19+ ToggleIsRight -> ToggleIsLeft
src/Yoga/Block/Atom/Toggle/View.purs+169−96
@@ -3,26 +3,33 @@ module Yoga.Block.Atom.Toggle.View (component, MandatoryProps, Props, PropsF) wh
33import Yoga.Prelude.View
44import Color as Color
55import Data.Interpolate (i)
6+import Data.Maybe (isNothing)
67import Effect.Class.Console as Console
8+import Effect.Unsafe (unsafePerformEffect)
79import Foreign.Object as Object
810import Framer.Motion as Motion
11+import Partial.Unsafe (unsafeCrashWith)
912import React.Basic.DOM (css)
1013import React.Basic.DOM as R
1114import React.Basic.Emotion as Emotion
15+import React.Basic.Hooks (reactComponent)
1216import React.Basic.Hooks as React
17+import Yoga.Block.Atom.Icon as Icon
1318import Yoga.Block.Atom.Toggle.Style as Style
19+import Yoga.Block.Atom.Toggle.Types (TogglePosition(..), flipToggle)
1420import Yoga.Block.Container.Style (colour)
21+import Yoga.Block.Icon.SVG as SVGIcon
1522
1623type PropsF f =
1724 ( className ∷ f String
18− , on ∷ f JSX
19− , off ∷ f JSX
25+ , left ∷ f JSX
26+ , right ∷ f JSX
2027 | Style.Props f (MandatoryProps InputProps)
2128 )
2229
2330type MandatoryProps r =
24− ( value ∷ Boolean
25− , onToggle ∷ Boolean -> Effect Unit
31+ ( togglePosition ∷ TogglePosition
32+ , setTogglePosition ∷ TogglePosition -> Effect Unit
2633 | r
2734 )
2835
@@ -32,7 +39,7 @@ data TappingState
3239
3340data DragState
3441 = NotDragging
35− | Dragging { startX ∷ Number }
42+ | Dragging { startX ∷ Number, currentX ∷ Number }
3643 | DragDone { startX ∷ Number, endX ∷ Number }
3744
3845derive instance eqDragState ∷ Eq DragState
@@ -40,8 +47,8 @@ derive instance eqDragState ∷ Eq DragState
4047instance showDragState ∷ Show DragState where
4148 show = case _ of
4249 NotDragging -> "NotDragging"
43− Dragging x -> "Dragging " <> (show x)
44− DragDone x -> "DragDone " <> (show x)
50+ Dragging x -> "Dragging " <> show x
51+ DragDone x -> "DragDone " <> show x
4552
4653type Props =
4754 PropsF Id
@@ -57,147 +64,213 @@ rawComponent =
5764 mkForwardRefComponent "Toggle" do
5865 \(props ∷ { | PropsOptional }) ref -> React.do
5966 let disabled = props.disabled
60− tapState <- useRef TapNotAllowed
61− dragState /\ setDragState <- React.useState' NotDragging
62− maxLeft /\ setMaxLeft <- useState' 0.0
6367 buttonRef <- useRef null
6468 toggleRef <- useRef null
65− leftState /\ setLeftState <- React.useState' 0.0
66− let
67− getWidth aRef = do
68− bbox <- getBoundingBoxFromRef aRef
69− pure $ bbox <#> _.width # fromMaybe 0.0
70− buttonWidth = getWidth buttonRef
71− toggleWidth = getWidth toggleRef
72− useLayoutEffectAlways do
73− when (maxLeft == 0.0) do
74− bw <- buttonWidth
75− tw <- toggleWidth
76− setMaxLeft (bw - tw)
77− mempty
78− let
79− toggleVariants =
80− { off: { x: 0.0 }
81− , on: { x: maxLeft }
82− }
83− toggleVariant = Motion.makeVariantLabels toggleVariants
84− buttonVariants =
85− { off: { backgroundColor: (Emotion.str <<< Color.cssStringRGBA <$> props.backgroundOn) ?|| Emotion.str colour.inputBackground }
86− , on: { backgroundColor: (Emotion.str <<< Color.cssStringRGBA <$> props.backgroundOff) ?|| Emotion.str colour.success }
87− }
88− buttonVariant = Motion.makeVariantLabels buttonVariants
89− hasFocus /\ setHasFocus <- useState' false
69+ tapState <- useRef TapNotAllowed
70+ dragState /\ setDragState <- React.useState' NotDragging
9071 useEffect dragState do
9172 case dragState of
9273 NotDragging -> mempty
93− Dragging { startX } -> writeRef tapState TapNotAllowed
74+ Dragging { startX, currentX } -> do
75+ writeRef tapState TapNotAllowed
9476 DragDone { startX, endX } -> do
9577 maybeBbox <- getBoundingBoxFromRef buttonRef
9678 for_ maybeBbox \bbox -> do
9779 if endX - startX <= (bbox.left - startX) + (bbox.width / 2.0) then do
98− props.onToggle false
80+ props.setTogglePosition ToggleIsLeft
9981 else do
100− props.onToggle true
82+ props.setTogglePosition ToggleIsRight
10183 mempty
102− pure
103− $ styled Motion.button
84+ let
85+ buttonVariants =
86+ { left: { backgroundColor: (Emotion.str <<< Color.cssStringRGBA <$> props.backgroundLeft) ?|| Emotion.str colour.inputBackground }
87+ , right: { backgroundColor: (Emotion.str <<< Color.cssStringRGBA <$> props.backgroundRight) ?|| Emotion.str colour.success }
88+ }
89+ buttonVariant = Motion.makeVariantLabels buttonVariants
90+ -- components
91+ let
92+ result =
93+ container
94+ [ animateTextPresence
95+ [ textContainer
96+ [ textOnContainer
97+ [ guard (props.togglePosition == ToggleIsRight)
98+ $ textOn
99+ [ props.left
100+ ?|| el_ Icon.component { icon: SVGIcon.on, stroke: Style.successTextColour }
101+ ]
102+ ]
103+ , textOffContainer
104+ [ guard (props.togglePosition == ToggleIsLeft)
105+ $ textOff
106+ [ props.right
107+ ?|| el_ Icon.component { icon: SVGIcon.off, stroke: Style.disabledTextColour }
108+ ]
109+ ]
110+ ]
111+ ]
112+ , toggle
113+ ]
114+ container =
115+ styled Motion.button
104116 { className: "ry-toggle"
105− , css: Style.button <> guard props.disabled Style.inputDisabled
106− , onFocus: handler_ $ setHasFocus true
107− , onBlur: handler_ $ setHasFocus false
117+ , css: Style.button
108118 , transition: Motion.transition { type: "tween", duration: 0.33, ease: "easeOut" }
109119 , variants: Motion.variants buttonVariants
110− , animate: Motion.animate if props.value then buttonVariant.on else buttonVariant.off
111− , value: show props.value
112− , onClick: handler preventDefault \_ -> props.onToggle (not props.value)
120+ , animate:
121+ Motion.animate case props.togglePosition of
122+ ToggleIsRight -> buttonVariant.right
123+ ToggleIsLeft -> buttonVariant.left
124+ , value: show props.togglePosition
125+ , onClick: handler preventDefault \_ -> props.setTogglePosition (flipToggle props.togglePosition)
113126 , style: props.style
114127 , _data: Object.singleton "testid" "toggle-testid"
115128 , role: "switch"
116129 , _aria: Object.singleton "checked" "switch"
117130 , ref: buttonRef
118131 }
119− [ styled R.div'
120− { className: "ry-toggle-text"
121− , css: Style.toggleTextContainer
122− }
123− [ styled R.div'
124− { className: "ry-toggle-text-on"
125− , css: Style.toggleText
126− }
127− [ el Motion.animatePresence {}
128− [ guard (props.value)
129− $ styled Motion.div
130− { className: "ry-toggle-text-on-container"
131− , css: Style.toggleOnText
132− , key: "ry-toggle-text-on-container"
133− , initial: Motion.initial $ css { scale: 0, opacity: 0 }
134− , animate: Motion.animate $ css { scale: 1, opacity: 1 }
135− , exit: Motion.exit $ css { scale: 0, opacity: 0 }
136− }
137− [ props.on ?|| R.text "I" ]
138− ]
139− ]
140− , styled R.div'
141− { className: "ry-toggle-text-off"
142− , css: Style.toggleText
143− }
144− [ el Motion.animatePresence {}
145− [ guard (not props.value)
146− $ styled Motion.div
147− { className: "ry-toggle-text-on-container"
148− , css: Style.toggleOnText
149− , key: "ry-toggle-text-on-container"
150− , initial: Motion.initial $ css { scale: 0, opacity: 0 }
151− , animate: Motion.animate $ css { scale: 1, opacity: 1 }
152− , exit: Motion.exit $ css { scale: 0, opacity: 0 }
153− }
154− [ props.off ?|| R.text "O" ]
155− ]
156− ]
157− ]
158− , styled Motion.div
132+ textContainer =
133+ styled R.div'
134+ { className: "ry-toggle-text"
135+ , css: Style.toggleTextContainer
136+ }
137+ textOnContainer =
138+ styled R.div'
139+ { className: "ry-toggle-text-on"
140+ , css: Style.toggleText
141+ }
142+ textOffContainer =
143+ styled R.div'
144+ { className: "ry-toggle-text-off"
145+ , css: Style.toggleText
146+ }
147+ textOn =
148+ el Motion.div
149+ { className: "ry-toggle-text-container"
150+ , key: "ry-toggle-text-on-container"
151+ , initial: Motion.initial $ css { scale: 0, opacity: 0 }
152+ , animate: Motion.animate $ css { scale: 1, opacity: 1 }
153+ , exit: Motion.exit $ css { scale: 0, opacity: 0 }
154+ }
155+ textOff =
156+ el Motion.div
157+ { className: "ry-toggle-text-container"
158+ , key: "ry-toggle-text-off-container"
159+ , initial: Motion.initial $ css { scale: 0, opacity: 0 }
160+ , animate: Motion.animate $ css { scale: 1, opacity: 1 }
161+ , exit: Motion.exit $ css { scale: 0, opacity: 0 }
162+ }
163+ animateTextPresence = el Motion.animatePresence {}
164+ toggle =
165+ el_ toggleCircle
166+ { buttonRef
167+ , toggleRef
168+ , togglePosition: props.togglePosition
169+ , setTogglePosition: props.setTogglePosition
170+ , dragState
171+ , setDragState
172+ , tapState
173+ }
174+ pure result
175+
176+toggleCircle ∷
177+ ReactComponent
178+ { setTogglePosition ∷ TogglePosition -> Effect Unit
179+ , toggleRef ∷ NodeRef
180+ , buttonRef ∷ NodeRef
181+ , togglePosition ∷ TogglePosition
182+ , dragState ∷ DragState
183+ , setDragState ∷ DragState -> Effect Unit
184+ , tapState ∷ Ref TappingState
185+ }
186+toggleCircle =
187+ unsafePerformEffect
188+ $ reactComponent "ToggleCircle" do
189+ \( { togglePosition
190+ , setTogglePosition
191+ , toggleRef
192+ , buttonRef
193+ , dragState
194+ , setDragState
195+ , tapState
196+ }
197+ ) -> React.do
198+ maxLeft /\ setMaxLeft <- useState' Nothing
199+ useEffectAlways do
200+ when (maxLeft == Nothing) do
201+ runMaybeT_ do
202+ b <- getBoundingBoxFromRef buttonRef # MaybeT
203+ t <- getBoundingBoxFromRef toggleRef # MaybeT
204+ let ml = b.width - t.width - (2.0 * (t.left - b.left))
205+ setMaxLeft (Just ml) # lift
206+ mempty
207+ let
208+ toggleVariants =
209+ { left: { x: 0.0 }
210+ , right: { x: maxLeft # fromMaybe 0.0 }
211+ }
212+ toggleVariant = Motion.makeVariantLabels toggleVariants
213+ pure
214+ $ styledLeaf Motion.div
159215 { className: "ry-toggle-toggle"
160− , css: Style.theToggle props
161216 , layout: Motion.layout true
162217 , onClick: handler stopPropagation mempty
218+ , onTouchStart: handler stopPropagation mempty
219+ , onTouchEnd: handler stopPropagation mempty
220+ , css: Style.theToggle
163221 , drag: Motion.drag "x"
164222 , dragMomentum: Motion.dragMomentum false
223+ , key: if isNothing maxLeft then "initialising" else "ready"
165224 , dragElastic: Motion.dragElastic false
166225 , dragConstraints:
167226 Motion.dragConstraints
168− { left: if props.value then negate maxLeft else zero
169− , right: if props.value then zero else maxLeft
227+ { left:
228+ case togglePosition of
229+ ToggleIsLeft -> zero
230+ ToggleIsRight -> negate (maxLeft # fromMaybe 0.0)
231+ , right:
232+ case togglePosition of
233+ ToggleIsRight -> zero
234+ ToggleIsLeft -> maxLeft # fromMaybe 0.0
170235 }
171236 , variants: Motion.variants toggleVariants
172237 , transition: Motion.transition { type: "tween", duration: 0.33, ease: "easeOut" }
173− , whileTap: Motion.prop $ css { scale: 0.85, transition: { type: "tween", duration: 0.10, ease: "easeInOut" } }
238+ , whileTap: Motion.prop $ css { scale: 1.1, transition: { type: "tween", duration: 0.10, ease: "easeInOut" } }
174239 , onTapStart: Motion.onTapStart \_ _ -> writeRef tapState TapAllowed
175240 , onTap:
176241 Motion.onTap \_ pi -> do
177242 ts <- readRef tapState
178243 case ts of
179− TapAllowed -> props.onToggle $ not props.value
244+ TapAllowed -> setTogglePosition (flipToggle togglePosition)
180245 _ -> mempty
181246 mempty
182247 , onTapCancel:
183248 Motion.onTapCancel \_ pi -> do
184249 writeRef tapState TapNotAllowed
185250 mempty
186− , animate: Motion.animate if props.value then toggleVariant.on else toggleVariant.off
251+ , initial: Motion.initial false
252+ , animate:
253+ Motion.animate case togglePosition of
254+ ToggleIsLeft -> toggleVariant.left
255+ ToggleIsRight -> toggleVariant.right
187256 , onDragStart:
188257 Motion.onDragStart \_ pi -> do
189258 maybeBBox <- getBoundingBoxFromRef toggleRef
190259 let x = maybeBBox <#> \bbox -> bbox.left + (bbox.width / 2.0)
191− setDragState $ Dragging { startX: x # fromMaybe pi.point.x }
260+ setDragState $ Dragging { startX: x # fromMaybe pi.point.x, currentX: pi.point.x }
261+ , onDrag:
262+ Motion.onDrag \_ pi -> do
263+ case dragState of
264+ Dragging { startX } -> do
265+ setDragState $ Dragging { startX, currentX: pi.point.x }
266+ other -> Console.warn $ i "Unexpected drag state " (show other) " in onDragEvent"
192267 , onDragEnd:
193268 Motion.onDragEnd \_ pi -> do
194269 case dragState of
195270 Dragging { startX } -> do
196271 maybeBBox <- getBoundingBoxFromRef toggleRef
197272 let x = maybeBBox <#> \bbox -> bbox.left + (bbox.width / 2.0)
198− setDragState (DragDone { startX, endX: x # fromMaybe pi.point.x })
273+ setDragState (DragDone { startX, endX: x # fromMaybe' \_ -> unsafeCrashWith "shit" })
199274 other -> Console.warn $ i "Unexpected drag state " (show other) " in onDragEvent"
200275 , ref: toggleRef
201276 }
202− []
203− ]
src/Yoga/Block/Atom/Tooltip.pursnew file+5
@@ -0,0 +1,5 @@
1+module Yoga.Block.Atom.Tooltip
2+ ( module Yoga.Block.Atom.Tooltip.View
3+ ) where
4+
5+import Yoga.Block.Atom.Tooltip.View (component, Props)
src/Yoga/Block/Atom/Tooltip/Spec.pursnew file+16
@@ -0,0 +1,16 @@
1+module Yoga.Block.Atom.Tooltip.Spec where
2+
3+import Yoga.Prelude.Spec
4+import React.Basic.DOM as R
5+import Yoga.Block.Atom.Tooltip as Tooltip
6+
7+spec ∷ Spec Unit
8+spec =
9+ after_ cleanup do
10+ describe "The tooltip" do
11+ it "renders without errors" do
12+ void
13+ $ renderComponent Tooltip.component
14+ { theTip: R.text "Tip"
15+ , target: R.text "Target"
16+ }
src/Yoga/Block/Atom/Tooltip/Story.pursnew file+39
@@ -0,0 +1,39 @@
1+module Yoga.Block.Atom.Tooltip.Story where
2+
3+import Prelude
4+import Effect (Effect)
5+import Effect.Unsafe (unsafePerformEffect)
6+import React.Basic (JSX, element, fragment)
7+import React.Basic.DOM as R
8+import React.Basic.Emotion as E
9+import Yoga (el)
10+import Yoga.Block.Atom.Tooltip as Tooltip
11+import Yoga.Block.Container.Style as Styles
12+
13+default ∷
14+ { decorators ∷ Array (Effect JSX -> JSX)
15+ , title ∷ String
16+ }
17+default =
18+ { title: "Atom/Tooltip"
19+ , decorators:
20+ [ \storyFn ->
21+ R.div_
22+ [ element E.global { styles: Styles.global }
23+ , unsafePerformEffect storyFn
24+ ]
25+ ]
26+ }
27+
28+tooltip ∷ Effect JSX
29+tooltip = do
30+ pure
31+ $ fragment
32+ [ R.div_
33+ [ R.h2_ [ R.text "Basics" ]
34+ , element Tooltip.component
35+ { theTip: R.text "Hi"
36+ , target: el R.button' {} [ R.text "Holzkop" ]
37+ }
38+ ]
39+ ]
src/Yoga/Block/Atom/Tooltip/Style.pursnew file+57
@@ -0,0 +1,57 @@
1+module Yoga.Block.Atom.Tooltip.Style where
2+
3+import Yoga.Prelude.Style
4+import Yoga.Block.Container.Style (colour)
5+
6+type Props f r =
7+ ( css ∷ f Style
8+ , backgroundColour ∷ f Color
9+ | r
10+ )
11+
12+backgroundColour ∷ String
13+backgroundColour = colour.text
14+
15+arrow ∷ ∀ r. { | Props OptionalProp r } -> Style
16+arrow props =
17+ css
18+ { position: str "absolute"
19+ , width: str "12px"
20+ , height: str "12px"
21+ , zIndex: str "0"
22+ , "&::before":
23+ nest
24+ { position: str "absolute"
25+ , width: str "12px"
26+ , height: str "12px"
27+ , borderRadius: str "2px"
28+ , content: str "''"
29+ , transform: str "rotate(45deg)"
30+ , background: str $ (cssStringRGBA <$> props.backgroundColour) ?|| backgroundColour
31+ }
32+ }
33+
34+content ∷ ∀ r. { | Props OptionalProp r } -> Style
35+content props =
36+ css
37+ { background: str $ (cssStringRGBA <$> props.backgroundColour) ?|| backgroundColour
38+ , minWidth: str "50px"
39+ , padding: str "6px"
40+ , zIndex: str "-1"
41+ , color: str colour.background0
42+ , boxShadow: str "0 1px 8px rgba(0,0,0,0.33)"
43+ , borderRadius: var "--s-2"
44+ }
45+
46+popper ∷ Style
47+popper =
48+ css
49+ { "&[data-popper-placement^='top'] > * > .popper-arrow":
50+ nest { bottom: str "-4px" }
51+ , "&[data-popper-placement^='bottom'] > * > .popper-arrow":
52+ nest { top: str "-4px" }
53+ , "&[data-popper-placement^='left'] > * > .popper-arrow":
54+ nest { right: str "-4px" }
55+ , "&[data-popper-placement^='right'] > * > .popper-arrow":
56+ nest { left: str "-4px" }
57+ }
src/Yoga/Block/Atom/Tooltip/View.pursnew file+129
@@ -0,0 +1,129 @@
1+module Yoga.Block.Atom.Tooltip.View (component, MandatoryProps, Props, PropsF) where
2+
3+import Yoga.Prelude.View
4+import Debug.Trace (spy)
5+import Effect.Aff (Milliseconds(..), delay)
6+import Effect.Uncurried (mkEffectFn1)
7+import Framer.Motion as Motion
8+import React.Basic.DOM (css)
9+import React.Basic.DOM as R
10+import React.Basic.Hooks as React
11+import React.Basic.Hooks.Aff (useAff)
12+import React.Basic.Popper.Hook (usePopper)
13+import React.Basic.Popper.Types (modifierArrow, modifierOffset, nullRef)
14+import Unsafe.Coerce (unsafeCoerce)
15+import Yoga.Block.Atom.Tooltip.Style as Style
16+
17+type PropsF f =
18+ ( className ∷ f String
19+ , hideDelay ∷ f Milliseconds
20+ | Style.Props f (MandatoryProps ())
21+ )
22+
23+type MandatoryProps r =
24+ ( theTip ∷ JSX
25+ , target ∷ JSX
26+ | r
27+ )
28+
29+type Props =
30+ PropsF Id
31+
32+type PropsOptional =
33+ PropsF OptionalProp
34+
35+component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | MandatoryProps p }
36+component = rawComponent
37+
38+rawComponent ∷ ∀ p. ReactComponent (Record p)
39+rawComponent =
40+ mkForwardRefComponent "Tooltip" do
41+ \(props ∷ { | PropsOptional }) ref -> React.do
42+ -- Hooks
43+ referenceElement /\ setReferenceElement <- React.useState' nullRef
44+ popperElement /\ setPopperElement <- React.useState' nullRef
45+ arrowElement /\ setArrowElement <- React.useState' nullRef
46+ { styles, attributes } <-
47+ usePopper referenceElement popperElement
48+ { modifiers:
49+ [ modifierArrow arrowElement
50+ , modifierOffset { x: 0.0, y: 12.0 }
51+ ]
52+ }
53+ visible /\ setVisible <- React.useState' false
54+ touching /\ setTouching <- React.useState' false
55+ hovering /\ setHovering <- React.useState' false
56+ useAff hovering do
57+ if hovering then
58+ setVisible true # liftEffect
59+ else do
60+ delay (props.hideDelay ?|| (0.0 # Milliseconds))
61+ setVisible false # liftEffect
62+ useAff touching do
63+ if touching then do
64+ let _ = spy "sausage" touching
65+ delay (1000.0 # Milliseconds)
66+ setVisible true # liftEffect
67+ else do
68+ delay (props.hideDelay ?|| (450.0 # Milliseconds))
69+ setVisible false # liftEffect
70+ -- Handlers
71+ let
72+ hoveredIn = setHovering true
73+ hoveredOut = setHovering false
74+ touchStarted = setTouching true
75+ touchEnded = setTouching false
76+ -- Elements
77+ let
78+ result =
79+ fragment
80+ $ [ refElem
81+ , popperEl
82+ [ animatePresence
83+ $ guard visible
84+ [ content
85+ [ props.theTip
86+ , arrow
87+ ]
88+ ]
89+ ]
90+ ]
91+ animatePresence = el Motion.animatePresence { initial: false }
92+ content =
93+ styled Motion.div
94+ { className: "popper-element-content"
95+ , css: Style.content props
96+ , initial: Motion.initial $ R.css { opacity: [ 1.0, 0.8, 0.0 ], scale: [ 1.0, 0.85 ] }
97+ , animate: Motion.animate $ R.css { opacity: [ 0.0, 0.3, 1.0 ], scale: [ 0.0, 1.05, 1.0, 0.98, 1.01, 1.0 ] }
98+ , exit: Motion.exit $ R.css { opacity: [ 1.0, 0.8, 0.0 ], scale: [ 1.0, 0.85 ] }
99+ , transition: Motion.transition { duration: 0.2 }
100+ , key: "container"
101+ }
102+ popperEl =
103+ styled R.div'
104+ { className: "popper-element"
105+ , css: Style.popper
106+ , ref: unsafeCoerce (mkEffectFn1 setPopperElement)
107+ , style: styles.popper
108+ , _data: attributes.popper
109+ }
110+ arrow =
111+ styledLeaf R.div'
112+ { className: "popper-arrow"
113+ , id: "arrow"
114+ , css: Style.arrow props
115+ , ref: unsafeCoerce (mkEffectFn1 setArrowElement)
116+ , style: styles.arrow
117+ , _data: attributes.arrow
118+ }
119+ refElem =
120+ element Motion.div
121+ { ref: unsafeCoerce (mkEffectFn1 setReferenceElement)
122+ , style: css { display: "inline-block" }
123+ , children: [ props.target ]
124+ , onHoverStart: Motion.onHoverStart \_ _ -> hoveredIn
125+ , onHoverEnd: Motion.onHoverEnd \_ _ -> hoveredOut
126+ , onTouchStart: handler_ touchStarted
127+ , onTouchEnd: handler_ touchEnded
128+ }
129+ pure result
src/Yoga/Block/Container/Style.purs+5−20
@@ -50,7 +50,7 @@ mkGlobal maybeMode =
5050 , body:
5151 nested
5252 $ css
53− { fontFamily: str "var(--mainFont)"
53+ { fontFamily: str "var(--main-font)"
5454 , background: str colour.background0
5555 , color: str colour.text
5656 , margin: str "0"
@@ -61,7 +61,7 @@ mkGlobal maybeMode =
6161 Just LightMode -> lightModeStyle
6262 , "pre,code":
6363 nest
64− { fontFamily: str "var(--monoFont)"
64+ { fontFamily: str "var(--mono-font)"
6565 }
6666 , "h1,h2,h3,h4,h5":
6767 nest
@@ -76,7 +76,6 @@ mkGlobal maybeMode =
7676 $ css
7777 { boxSizing: str "inherit"
7878 }
79− , input
8079 , form:
8180 nested
8281 $ css
@@ -155,7 +154,7 @@ defaultColours =
155154 -- highlight = Color.rgb 0x10 0x45 0x4A
156155 darkBg = Color.rgb 0 0 0
157156
158− lightBg = Color.rgb 255 255 255
157+ lightBg = Color.rgb 250 250 250
159158
160159type FlatTheme a =
161160 { background0 ∷ a
@@ -262,22 +261,8 @@ variables =
262261fontVariables ∷ { main ∷ String, mono ∷ String } -> Style
263262fontVariables { main, mono } =
264263 css
265− { "--mainFont": str $ main <> """, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol""""
266− , "--monoFont": str $ mono <> ", monospace, monospace"
267− }
268−
269−input ∷ StyleProperty
270−input =
271− nest
272− { backgroundColor: "var(--input-bg-col)" # str
273− , padding: "5px" # str
274− , border: str "1px solid var(--bg-col)"
275− , borderRadius: "5px" # str
276− , fontSize: "12px" # str
277− , paddingBottom: "4px" # str
278− , paddingLeft: "8px" # str
279− , "&:disabled": nest { color: str "var(--input-text-col-disabled)" }
280− , "&:focus": nested inputFocus
264+ { "--main-font": str $ main <> """, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol""""
265+ , "--mono-font": str $ mono <> ", monospace, monospace"
281266 }
282267
283268-- Standalone style for storybook
src/Yoga/Block/Icon/SVG/EyeClosed.pursnew file+32
@@ -0,0 +1,32 @@
1+module Yoga.Block.Icon.SVG.EyeClosed where
2+
3+import React.Basic (JSX)
4+import React.Basic.DOM as R
5+import React.Basic.DOM.SVG as SVG
6+
7+eyeClosed ∷ JSX
8+eyeClosed =
9+ SVG.svg
10+ { viewBox: "0 0 100 100"
11+ , xmlns: "http://www.w3.org/2000/svg"
12+ , fillRule: "evenodd"
13+ , clipRule: "evenodd"
14+ , strokeLinejoin: "round"
15+ , strokeMiterlimit: "1.5"
16+ , children:
17+ [ SVG.path
18+ { fill: "none"
19+ , d: "M0 0h100v100H0z"
20+ }
21+ , SVG.path
22+ { d: "M5.492 63.902c-1.539-2.426-2.587-11.465-1.593-13.076.352.369 4.353-.86 4.732-.54 24.519 20.777 51.057 22.859 83.889.676.892-.603 2.312-1.948 3.567-.637.892 2.137 2.479 6.187 2.439 10.011-.858 3.046-4.013 8.107-6.554 10.666.091-3.033-1.654-9.054-2.787-11.919-1.169 1.053-4.237 3.361-5.557 4.507 2.021 6.439 3.704 10.343 4.342 13.77-1.418 2.716-6.433 6.382-8.172 6.837-.695-3.519-3.589-12.626-4.791-16.545-1.721 1.014-7.746 2.85-7.739 2.967.294 5.01 1.269 11.753 3.159 17.431-2.331 1.521-7.403 2.045-10.644 2.233-1.477-3.05-1.902-13.039-2.221-17.74-1.768.532-6.838.5-8.865.679-.682 5.829-.99 11.62-.47 17.094-2.634-.024-8.846-1.614-10.179-2.127-.34-.886.675-13.622.808-16.23-1.442-.074-7.906-2.268-8.801-2.513-.501 3.058-1.153 12.152-.578 14.598-1.516-.601-8.651-6.109-9.148-6.3-.617-3.543-.097-9.17.23-12.361-.919-.313-5.78-3.571-6.551-4.211-.87 8.919-.1 11.254.106 11.869-1.502-1.338-7.45-7.873-8.622-9.139z"
23+ , fill: "var(--stroke-colour)"
24+ }
25+ , SVG.path
26+ { d: "M50.026 26.996c23.346 0 42.3 24.595 42.3 24.595s-18.954 17.592-42.3 17.592c-23.347 0-42.301-17.592-42.301-17.592s18.954-24.595 42.301-24.595z"
27+ , fill: "none"
28+ , stroke: "var(--stroke-colour)"
29+ , strokeWidth: "8"
30+ }
31+ ]
32+ }
src/Yoga/Block/Icon/SVG/EyeOpen.pursnew file+44
@@ -0,0 +1,44 @@
1+module Yoga.Block.Icon.SVG.EyeOpen where
2+
3+import React.Basic (JSX)
4+import React.Basic.DOM as R
5+import React.Basic.DOM.SVG as SVG
6+
7+eyeOpen ∷ JSX
8+eyeOpen =
9+ SVG.svg
10+ { viewBox: "0 0 100 100"
11+ , xmlns: "http://www.w3.org/2000/svg"
12+ , fillRule: "evenodd"
13+ , clipRule: "evenodd"
14+ , strokeLinejoin: "round"
15+ , strokeMiterlimit: "1.5"
16+ , children:
17+ [ SVG.path
18+ { fill: "none"
19+ , d: "M0 0h100v100H0z"
20+ }
21+ , SVG.path
22+ { d: "M50.173 21.996c23.346 0 42.3 27.978 42.3 27.978s-18.954 27.979-42.3 27.979c-23.346 0-42.301-27.979-42.301-27.979s18.955-27.978 42.301-27.978z"
23+ , fill: "none"
24+ , stroke: "var(--stroke-colour)"
25+ , strokeWidth: "8"
26+ }
27+ , SVG.circle
28+ { cx: "49.963"
29+ , cy: "50.087"
30+ , r: "24.205"
31+ , fill: "none"
32+ , stroke: "var(--stroke-colour)"
33+ , strokeWidth: "6"
34+ }
35+ , SVG.circle
36+ { cx: "49.963"
37+ , cy: "50.087"
38+ , r: "9.748"
39+ , fill: "var(--stroke-colour)"
40+ , stroke: "var(--stroke-colour)"
41+ , strokeWidth: "8"
42+ }
43+ ]
44+ }
src/Yoga/Block/Icon/SVG/Icon.pursnew file+13
@@ -0,0 +1,13 @@
1+module Yoga.Block.Icon.SVG
2+ ( module Yoga.Block.Icon.SVG.On
3+ , module Yoga.Block.Icon.SVG.Off
4+ , module Yoga.Block.Icon.SVG.MagnifyingGlass
5+ , module Yoga.Block.Icon.SVG.EyeClosed
6+ , module Yoga.Block.Icon.SVG.EyeOpen
7+ ) where
8+
9+import Yoga.Block.Icon.SVG.On (on)
10+import Yoga.Block.Icon.SVG.Off (off)
11+import Yoga.Block.Icon.SVG.MagnifyingGlass (magnifyingGlass)
12+import Yoga.Block.Icon.SVG.EyeClosed (eyeClosed)
13+import Yoga.Block.Icon.SVG.EyeOpen (eyeOpen)
src/Yoga/Block/Icon/SVG/MagnifyingGlass.pursnew file+36
@@ -0,0 +1,36 @@
1+module Yoga.Block.Icon.SVG.MagnifyingGlass where
2+
3+import React.Basic (JSX)
4+import React.Basic.DOM as R
5+import React.Basic.DOM.SVG as SVG
6+
7+magnifyingGlass ∷ JSX
8+magnifyingGlass =
9+ SVG.svg
10+ { viewBox: "0 0 100 100"
11+ , xmlns: "http://www.w3.org/2000/svg"
12+ , fillRule: "evenodd"
13+ , clipRule: "evenodd"
14+ , strokeLinejoin: "round"
15+ , strokeMiterlimit: "1.5"
16+ , children:
17+ [ SVG.path
18+ { fill: "none"
19+ , d: "M0 0h100v100H0z"
20+ }
21+ , SVG.circle
22+ { cx: "42.994"
23+ , cy: "42.837"
24+ , r: "30.313"
25+ , fill: "none"
26+ , stroke: "var(--stroke-colour)"
27+ , strokeWidth: "10"
28+ }
29+ , SVG.path
30+ { d: "M66.723 66.665l24.062 24.062"
31+ , fill: "none"
32+ , stroke: "var(--stroke-colour)"
33+ , strokeWidth: "8"
34+ }
35+ ]
36+ }
src/Yoga/Block/Icon/SVG/Off.pursnew file+30
@@ -0,0 +1,30 @@
1+module Yoga.Block.Icon.SVG.Off where
2+
3+import React.Basic (JSX)
4+import React.Basic.DOM as R
5+import React.Basic.DOM.SVG as SVG
6+
7+off ∷ JSX
8+off =
9+ SVG.svg
10+ { viewBox: "0 0 100 100"
11+ , xmlns: "http://www.w3.org/2000/svg"
12+ , fillRule: "evenodd"
13+ , clipRule: "evenodd"
14+ , strokeLinejoin: "round"
15+ , strokeMiterlimit: "1.5"
16+ , children:
17+ [ SVG.path
18+ { fill: "none"
19+ , d: "M0 0h100v100H0z"
20+ }
21+ , SVG.circle
22+ { cx: "50"
23+ , cy: "50"
24+ , r: "37.808"
25+ , fill: "none"
26+ , stroke: "var(--stroke-colour)"
27+ , strokeWidth: "20"
28+ }
29+ ]
30+ }
src/Yoga/Block/Icon/SVG/On.pursnew file+41
@@ -0,0 +1,41 @@
1+module Yoga.Block.Icon.SVG.On where
2+
3+import React.Basic (JSX)
4+-- import React.Basic.DOM as R
5+import React.Basic.DOM.SVG as SVG
6+
7+on ∷ JSX
8+on =
9+ SVG.svg
10+ { viewBox: "0 0 100 100"
11+ , xmlns: "http://www.w3.org/2000/svg"
12+ , fillRule: "evenodd"
13+ , clipRule: "evenodd"
14+ , strokeLinejoin: "round"
15+ , strokeMiterlimit: "1.5"
16+ , children:
17+ [ SVG.path
18+ { fill: "none"
19+ , d: "M0 0h100v100H0z"
20+ }
21+ , SVG.clipPath
22+ { id: "a"
23+ , children:
24+ [ SVG.path
25+ { d: "M0 0h100v100H0z"
26+ }
27+ ]
28+ }
29+ , SVG.g
30+ { clipPath: "url(#a)"
31+ , children:
32+ [ SVG.path
33+ { d: "M50 1.921v96.261"
34+ , fill: "none"
35+ , stroke: "var(--stroke-colour)"
36+ , strokeWidth: "20"
37+ }
38+ ]
39+ }
40+ ]
41+ }
src/Yoga/Block/Icon/SVG/QuestionMark.pursnew file+25
@@ -0,0 +1,25 @@
1+module Yoga.Block.Icon.SVG.QuestionMark where
2+
3+import React.Basic (JSX)
4+import React.Basic.DOM as R
5+import React.Basic.DOM.SVG as SVG
6+
7+questionMark :: JSX
8+questionMark = SVG.svg
9+ { viewBox: "0 0 100 100"
10+ , xmlns: "http://www.w3.org/2000/svg"
11+ , fillRule: "evenodd"
12+ , clipRule: "evenodd"
13+ , strokeLinejoin: "round"
14+ , strokeMiterlimit: "2"
15+ , children:
16+ [ SVG.path
17+ { fill: "none"
18+ , d: "M0 0h100v100H0z"
19+ }
20+ , SVG.path
21+ { d: "M50 10.037c22.056 0 39.963 17.907 39.963 39.963S72.056 89.963 50 89.963 10.037 72.056 10.037 50 27.944 10.037 50 10.037zM48.871 77.03c2.87 0 5.326-2.384 5.351-5.351-.025-2.918-2.481-5.301-5.351-5.301-2.967 0-5.374 2.383-5.35 5.301-.024 2.967 2.383 5.351 5.35 5.351zm-4.255-15.176h8.098v-.632c.049-5.156 1.727-7.515 5.691-9.947 4.474-2.675 7.271-6.323 7.271-11.892 0-8.22-6.396-13.375-15.686-13.375-8.512 0-15.418 4.718-15.661 13.959h8.657c.219-4.572 3.526-6.761 6.955-6.761 3.721 0 6.713 2.481 6.713 6.299 0 3.405-2.262 5.739-5.205 7.587-4.304 2.676-6.785 5.375-6.833 14.13v.632z"
22+ , fill: "#333"
23+ }
24+ ]
25+ }
src/Yoga/Block/Icon/SVG/eyeClosed.svgnew file+1
@@ -0,0 +1 @@
1+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.5"><path fill="none" d="M0 0h100v100H0z"/><path d="M5.492 63.902c-1.539-2.426-2.587-11.465-1.593-13.076.352.369 4.353-.86 4.732-.54 24.519 20.777 51.057 22.859 83.889.676.892-.603 2.312-1.948 3.567-.637.892 2.137 2.479 6.187 2.439 10.011-.858 3.046-4.013 8.107-6.554 10.666.091-3.033-1.654-9.054-2.787-11.919-1.169 1.053-4.237 3.361-5.557 4.507 2.021 6.439 3.704 10.343 4.342 13.77-1.418 2.716-6.433 6.382-8.172 6.837-.695-3.519-3.589-12.626-4.791-16.545-1.721 1.014-7.746 2.85-7.739 2.967.294 5.01 1.269 11.753 3.159 17.431-2.331 1.521-7.403 2.045-10.644 2.233-1.477-3.05-1.902-13.039-2.221-17.74-1.768.532-6.838.5-8.865.679-.682 5.829-.99 11.62-.47 17.094-2.634-.024-8.846-1.614-10.179-2.127-.34-.886.675-13.622.808-16.23-1.442-.074-7.906-2.268-8.801-2.513-.501 3.058-1.153 12.152-.578 14.598-1.516-.601-8.651-6.109-9.148-6.3-.617-3.543-.097-9.17.23-12.361-.919-.313-5.78-3.571-6.551-4.211-.87 8.919-.1 11.254.106 11.869-1.502-1.338-7.45-7.873-8.622-9.139z" fill="#333"/><path d="M50.026 26.996c23.346 0 42.3 24.595 42.3 24.595s-18.954 17.592-42.3 17.592c-23.347 0-42.301-17.592-42.301-17.592s18.954-24.595 42.301-24.595z" fill="none" stroke="#333" stroke-width="8"/></svg>
src/Yoga/Block/Icon/SVG/eyeOpen.svgnew file+1
@@ -0,0 +1 @@
1+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.5"><path fill="none" d="M0 0h100v100H0z"/><path d="M50.173 21.996c23.346 0 42.3 27.978 42.3 27.978s-18.954 27.979-42.3 27.979c-23.346 0-42.301-27.979-42.301-27.979s18.955-27.978 42.301-27.978z" fill="none" stroke="#333" stroke-width="8"/><circle cx="49.963" cy="50.087" r="24.205" fill="none" stroke="#333" stroke-width="6"/><circle cx="49.963" cy="50.087" r="9.748" fill="#333" stroke="#333" stroke-width="8"/></svg>
src/Yoga/Block/Icon/SVG/magnifyingGlass.svgnew file+1
@@ -0,0 +1 @@
1+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.5"><path fill="none" d="M0 0h100v100H0z"/><circle cx="42.994" cy="42.837" r="30.313" fill="none" stroke="#333" stroke-width="10"/><path d="M66.723 66.665l24.062 24.062" fill="none" stroke="#333" stroke-width="8"/></svg>
src/Yoga/Block/Icon/SVG/off.svgnew file+1
@@ -0,0 +1 @@
1+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.5"><path fill="none" d="M0 0h100v100H0z"/><circle cx="50" cy="50" r="37.808" fill="none" stroke="#333" stroke-width="20"/></svg>
src/Yoga/Block/Icon/SVG/on.svgnew file+1
@@ -0,0 +1 @@
1+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.5"><path fill="none" d="M0 0h100v100H0z"/><clipPath id="a"><path d="M0 0h100v100H0z"/></clipPath><g clip-path="url(#a)"><path d="M50 1.921v96.261" fill="none" stroke="#333" stroke-width="20"/></g></svg>
src/Yoga/Block/Icon/SVG/questionMark.svgnew file+1
@@ -0,0 +1 @@
1+<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><path fill="none" d="M0 0h100v100H0z"/><path d="M50 10.037c22.056 0 39.963 17.907 39.963 39.963S72.056 89.963 50 89.963 10.037 72.056 10.037 50 27.944 10.037 50 10.037zM48.871 77.03c2.87 0 5.326-2.384 5.351-5.351-.025-2.918-2.481-5.301-5.351-5.301-2.967 0-5.374 2.383-5.35 5.301-.024 2.967 2.383 5.351 5.35 5.351zm-4.255-15.176h8.098v-.632c.049-5.156 1.727-7.515 5.691-9.947 4.474-2.675 7.271-6.323 7.271-11.892 0-8.22-6.396-13.375-15.686-13.375-8.512 0-15.418 4.718-15.661 13.959h8.657c.219-4.572 3.526-6.761 6.955-6.761 3.721 0 6.713 2.481 6.713 6.299 0 3.405-2.262 5.739-5.205 7.587-4.304 2.676-6.785 5.375-6.833 14.13v.632z" fill="#333"/></svg>
src/Yoga/Prelude/Default.purs+4
@@ -11,6 +11,7 @@ module Yoga.Prelude.Default
1111 , module Data.Foldable
1212 , module Data.FoldableWithIndex
1313 , module Data.FunctorWithIndex
14+ , runMaybeT_
1415 ) where
1516
1617import Prelude
@@ -25,3 +26,6 @@ import Data.Maybe (Maybe(..), fromMaybe, fromMaybe', isJust, maybe)
2526import Data.Monoid (guard)
2627import Effect (Effect)
2728import Effect.Class (liftEffect)
29+
30+runMaybeT_ ∷ ∀ f a. Functor f => MaybeT f a -> f Unit
31+runMaybeT_ = void <<< runMaybeT
yarn.lock+22−24
@@ -4854,25 +4854,23 @@ fragment-cache@^0.2.1:
48544854 dependencies:
48554855 map-cache "^0.2.2"
48564856
4857−framer-motion@^2.7.0:
4858− version "2.9.5"
4859− resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-2.9.5.tgz#bbb185325d531c57f494cf3f6cf7719fc2c225c7"
4860− integrity sha512-epSX4Co1YbDv0mjfHouuY0q361TpHE7WQzCp/xMTilxy4kXd+Z23uJzPVorfzbm1a/9q1Yu8T5bndaw65NI4Tg==
4857+framer-motion@^3.0.0:
4858+ version "3.1.1"
4859+ resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-3.1.1.tgz#a8a779501213b7ce02cc35beb27621d73cc2f1e7"
4860+ integrity sha512-Gm1QSb0xUxuhcPar5FIs5Ws+STrhLZ6XZf2Io8dVwFofe1OzwkL9asGFVu7z3y6WqC4Hvnxm7wsW5SBHlxZDYw==
48614861 dependencies:
4862− framesync "^4.1.0"
4862+ framesync "^5.0.0"
48634863 hey-listen "^1.0.8"
4864− popmotion "9.0.0-rc.20"
4865− style-value-types "^3.1.9"
4864+ popmotion "^9.0.2"
4865+ style-value-types "^3.2.0"
48664866 tslib "^1.10.0"
48674867 optionalDependencies:
48684868 "@emotion/is-prop-valid" "^0.8.2"
48694869
4870−framesync@^4.1.0:
4871− version "4.1.0"
4872− resolved "https://registry.yarnpkg.com/framesync/-/framesync-4.1.0.tgz#69a8db3ca432dc70d6a76ba882684a1497ef068a"
4873− integrity sha512-MmgZ4wCoeVxNbx2xp5hN/zPDCbLSKiDt4BbbslK7j/pM2lg5S0vhTNv1v8BCVb99JPIo6hXBFdwzU7Q4qcAaoQ==
4874− dependencies:
4875− hey-listen "^1.0.5"
4870+framesync@5.0.0, framesync@^5.0.0:
4871+ version "5.0.0"
4872+ resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.0.0.tgz#7de8caedf53ac441118e79680f1beb7391c328b6"
4873+ integrity sha512-wd8t+JsQGisluSv1twiEeDv0aNGpavGb9q7xgIk9fGbcIWkNXF/KVtrjnOrCwBWJuiXxlJfNkcvGudsI32FxYA==
48764874
48774875fresh@0.5.2:
48784876 version "0.5.2"
@@ -5315,7 +5313,7 @@ he@^1.2.0:
53155313 resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
53165314 integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
53175315
5318−hey-listen@^1.0.5, hey-listen@^1.0.8:
5316+hey-listen@^1.0.8:
53195317 version "1.0.8"
53205318 resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
53215319 integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
@@ -7489,14 +7487,14 @@ polished@^3.4.4:
74897487 dependencies:
74907488 "@babel/runtime" "^7.9.2"
74917489
7492−popmotion@9.0.0-rc.20:
7493− version "9.0.0-rc.20"
7494− resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.0-rc.20.tgz#f3550042ae31957b5416793ae8723200951ad39d"
7495− integrity sha512-f98sny03WuA+c8ckBjNNXotJD4G2utG/I3Q23NU69OEafrXtxxSukAaJBxzbtxwDvz3vtZK69pu9ojdkMoBNTg==
7490+popmotion@^9.0.2:
7491+ version "9.0.2"
7492+ resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.2.tgz#477650c3b4af97161011809223d9ca6860f3a2b5"
7493+ integrity sha512-WfSg8IfoUwYIP9uqeqbgncIsMHLAKWqebT2IP1aGAI6gdSJqTPy/H8NvP4ZyDtDCUCx5Yh3Pth/7iUJjIwR7LA==
74967494 dependencies:
7497− framesync "^4.1.0"
7495+ framesync "5.0.0"
74987496 hey-listen "^1.0.8"
7499− style-value-types "^3.1.9"
7497+ style-value-types "3.2.0"
75007498 tslib "^1.10.0"
75017499
75027500portfinder@^1.0.26:
@@ -9087,10 +9085,10 @@ style-loader@^1.2.1:
90879085 loader-utils "^2.0.0"
90889086 schema-utils "^2.7.0"
90899087
9090−style-value-types@^3.1.9:
9091− version "3.1.9"
9092− resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.1.9.tgz#faf7da660d3f284ed695cff61ea197d85b9122cc"
9093− integrity sha512-050uqgB7WdvtgacoQKm+4EgKzJExVq0sieKBQQtJiU3Muh6MYcCp4T3M8+dfl6VOF2LR0NNwXBP1QYEed8DfIw==
9088+style-value-types@3.2.0, style-value-types@^3.2.0:
9089+ version "3.2.0"
9090+ resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.2.0.tgz#eb89cab1340823fa7876f3e289d29d99c92111bb"
9091+ integrity sha512-ih0mGsrYYmVvdDi++/66O6BaQPRPRMQHoZevNNdMMcPlP/cH28Rnfsqf1UEba/Bwfuw9T8BmIMwbGdzsPwQKrQ==
90949092 dependencies:
90959093 hey-listen "^1.0.8"
90969094 tslib "^1.10.0"