Input work

This commit is contained in:
Mark Eibes 2020-12-20 23:44:24 +01:00
parent 774db186ef
commit c32928933f
47 changed files with 1331 additions and 275 deletions

1
.psci_modules/index.js Normal file
View File

@ -0,0 +1 @@
require('$PSCI')['$main']();

8
convert-more.fish Normal file
View File

@ -0,0 +1,8 @@
for f in (ls src/Yoga/Block/Icon/SVG/*.svg)
npx svgo -i $f
end
node convert-svgs.js
for f in (ls src/Yoga/Block/Icon/SVG/*.purs)
sed -i "" 's/stroke: "#333"/stroke: "var(--stroke-colour)"/g' $f
end

View File

@ -1,6 +1,6 @@
const path = require('path'); const path = require('path');
const svg2psreact = require("svg2psreact"); const svg2psreact = require("svg2psreact");
const assetsDir = path.join(__dirname, 'src', 'Assets'); const assetsDir = path.join(__dirname, 'src', 'Yoga', 'Block', 'Icon', 'SVG');
svg2psreact.convertAllSVGsInDirectory("Assets", assetsDir) svg2psreact.convertAllSVGsInDirectory("Yoga.Block.Icon.SVG", assetsDir)

View File

@ -17,7 +17,7 @@
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",
"compression-webpack-plugin": "^6.0.3", "compression-webpack-plugin": "^6.0.3",
"framer-motion": "^2.7.0", "framer-motion": "^3.0.0",
"html-webpack-plugin": "^4.5.0", "html-webpack-plugin": "^4.5.0",
"jsdom": "^16.3.0", "jsdom": "^16.3.0",
"jsdom-global": "^3.0.2", "jsdom-global": "^3.0.2",

View File

@ -119,7 +119,7 @@ let additions =
let upstream = let upstream =
https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20201021/packages.dhall sha256:55ebdbda1bd6ede4d5307fbc1ef19988c80271b4225d833c8d6fb9b6fb1aa6d8 https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20201217/packages.dhall sha256:f46d45e29977f3b57717b56d20a5ceac12532224516eea3012a4688f22ac1539
let overrides = let overrides =
{ spec-discovery = upstream.spec-discovery // { version = "master" } { spec-discovery = upstream.spec-discovery // { version = "master" }

View File

@ -18,6 +18,7 @@ You can edit this file as you like.
, "routing-duplex" , "routing-duplex"
, "spec-discovery" , "spec-discovery"
, "untagged-union" , "untagged-union"
, "web-uievents"
] ]
, packages = ./packages.dhall , packages = ./packages.dhall
, sources = [ "src/**/*.purs", "test/**/*.purs" ] , sources = [ "src/**/*.purs", "test/**/*.purs" ]

View File

@ -6,6 +6,14 @@ module Framer.Motion
, callback , callback
, class EffectFnMaker , class EffectFnMaker
, toEffectFn , toEffectFn
, OnHoverStart
, onHoverStart
, customProp
, OnHoverEnd
, onHoverEnd
, whileHover
, WhileHover
, EventInfo
, OnTap , OnTap
, TapInfo , TapInfo
, OnTapStart , OnTapStart
@ -80,6 +88,7 @@ import Data.Nullable (Nullable)
import Data.Symbol (class IsSymbol, SProxy, reflectSymbol) import Data.Symbol (class IsSymbol, SProxy, reflectSymbol)
import Effect (Effect) import Effect (Effect)
import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, mkEffectFn2) import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, mkEffectFn2)
import Foreign (Foreign, unsafeToForeign)
import Foreign.Object (Object) import Foreign.Object (Object)
import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmapWithIndex) import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmapWithIndex)
import Literals.Undefined (Undefined) import Literals.Undefined (Undefined)
@ -97,6 +106,7 @@ import Untagged.Castable (class Castable, cast)
import Untagged.Union (type (|+|)) import Untagged.Union (type (|+|))
import Web.DOM (Node) import Web.DOM (Node)
import Web.Event.Internal.Types (Event) import Web.Event.Internal.Types (Event)
import Web.UIEvent.MouseEvent (MouseEvent)
import Yoga.Block.Internal (Id) import Yoga.Block.Internal (Id)
foreign import divImpl ∷ ∀ a. ReactComponent { | a } foreign import divImpl ∷ ∀ a. ReactComponent { | a }
@ -216,6 +226,28 @@ onTap fn2 = cast (mkEffectFn2 fn2)
onTapCancel ∷ (Event -> TapInfo -> Effect Unit) -> OnTap onTapCancel ∷ (Event -> TapInfo -> Effect Unit) -> OnTap
onTapCancel fn2 = cast (mkEffectFn2 fn2) onTapCancel fn2 = cast (mkEffectFn2 fn2)
type EventInfo =
{ point ∷ { x ∷ Number, y ∷ Number }
}
type WhileHover =
(EffectFn2 MouseEvent EventInfo Unit |+| Undefined)
type OnHoverEnd =
(EffectFn2 MouseEvent EventInfo Unit |+| Undefined)
type OnHoverStart =
(EffectFn2 MouseEvent EventInfo Unit |+| Undefined)
onHoverStart ∷ (MouseEvent -> EventInfo -> Effect Unit) -> OnHoverStart
onHoverStart = cast <<< toEffectFn
onHoverEnd ∷ (MouseEvent -> EventInfo -> Effect Unit) -> OnHoverEnd
onHoverEnd = cast <<< toEffectFn
whileHover ∷ ∀ c. Castable c WhileHover => c -> WhileHover
whileHover = cast
type TapInfo = type TapInfo =
{ x ∷ Number, y ∷ Number } { x ∷ Number, y ∷ Number }
@ -250,9 +282,13 @@ onDragEnd = cast <<< toEffectFn
onDrag ∷ (Event -> PanInfo -> Effect Unit) -> OnDrag onDrag ∷ (Event -> PanInfo -> Effect Unit) -> OnDrag
onDrag fn2 = cast (mkEffectFn2 fn2) onDrag fn2 = cast (mkEffectFn2 fn2)
customProp ∷ ∀ a. a -> Foreign
customProp = unsafeToForeign
type MotionPropsF f r = type MotionPropsF f r =
( initial ∷ f Initial ( initial ∷ f Initial
, animate ∷ f Animate , animate ∷ f Animate
, custom ∷ f Foreign
, drag ∷ f Drag , drag ∷ f Drag
, dragMomentum ∷ f DragMomentum , dragMomentum ∷ f DragMomentum
, dragElastic ∷ f DragElastic , dragElastic ∷ f DragElastic
@ -270,6 +306,9 @@ type MotionPropsF f r =
, onTapStart ∷ f OnTapStart , onTapStart ∷ f OnTapStart
, onTapEnd ∷ f OnTapEnd , onTapEnd ∷ f OnTapEnd
, onTapCancel ∷ f OnTapCancel , onTapCancel ∷ f OnTapCancel
, whileHover ∷ f WhileHover
, onHoverStart ∷ f OnHoverStart
, onHoverEnd ∷ f OnHoverEnd
, exit ∷ f Exit , exit ∷ f Exit
| r | r
) )

View File

@ -7,43 +7,11 @@ import Effect.Uncurried (EffectFn3, runEffectFn3)
import Foreign.Object (Object) import Foreign.Object (Object)
import Foreign.Object as Object import Foreign.Object as Object
import Prim.Row (class Union) import Prim.Row (class Union)
import React.Basic.DOM (CSS) import Yoga.Prelude.View (Hook, unsafeHook)
import Unsafe.Coerce (unsafeCoerce) import React.Basic.Popper.Types (Options, PopperData, PopperElement, ReferenceElement)
import Yoga.Prelude.View (Hook, NodeRef, unsafeHook)
foreign import data UsePopper ∷ Type -> Type -> Type foreign import data UsePopper ∷ Type -> Type -> Type
type ReferenceElement =
NodeRef
type PopperElement =
NodeRef
type ArrowElement =
NodeRef
type DataAttributes =
{ popper ∷ Object String, arrow ∷ Object String }
type Styles =
{ popper ∷ CSS, arrow ∷ CSS }
type PopperData =
{ styles ∷ Styles, attributes ∷ DataAttributes }
foreign import data Modifier ∷ Type
modifierArrow ∷ ArrowElement -> Modifier
modifierArrow element = unsafeCoerce { name: "arrow", options: { element } }
modifierOffset ∷ { x ∷ Number, y ∷ Number } -> Modifier
modifierOffset { x, y } = unsafeCoerce { name: "offset", options: { offset: [ x, y ] } }
type Options =
( modifiers ∷ Array Modifier
, strategy ∷ String
)
foreign import usePopperImpl ∷ ∀ opts. EffectFn3 ReferenceElement PopperElement { | opts } PopperData foreign import usePopperImpl ∷ ∀ opts. EffectFn3 ReferenceElement PopperElement { | opts } PopperData
toDataAttributes ∷ Object String -> Object String toDataAttributes ∷ Object String -> Object String

View File

@ -1,27 +1,24 @@
module React.Basic.Popper.Story where module React.Basic.Popper.Story where
import Prelude import Prelude
import Color as Color import Data.Interpolate (i)
import Data.Maybe (Maybe(..))
import Data.Nullable (null) import Data.Nullable (null)
import Data.Tuple.Nested ((/\)) import Data.Tuple.Nested ((/\))
import Effect (Effect) import Effect (Effect)
import Effect.Uncurried (mkEffectFn1) import Effect.Uncurried (mkEffectFn1)
import Effect.Unsafe (unsafePerformEffect) import Effect.Unsafe (unsafePerformEffect)
import Framer.Motion (onTap)
import Framer.Motion as Motion import Framer.Motion as Motion
import React.Basic (JSX, element, fragment) import React.Basic (JSX, element, fragment)
import React.Basic.DOM as R import React.Basic.DOM as R
import React.Basic.Emotion as E import React.Basic.Emotion as E
import React.Basic.Hooks as React import React.Basic.Hooks as React
import React.Basic.Popper.Hook (modifierArrow, modifierOffset, usePopper) import React.Basic.Popper.Hook (usePopper)
import React.Basic.Popper.Types (modifierArrow, modifierOffset)
import Unsafe.Coerce (unsafeCoerce) import Unsafe.Coerce (unsafeCoerce)
import Yoga.Block as Block import Yoga.Block as Block
import Yoga.Block.Atom.Toggle as Toggle
import Yoga.Block.Container.Style (DarkOrLightMode(..))
import Yoga.Block.Container.Style as Styles import Yoga.Block.Container.Style as Styles
import Yoga.Block.Internal.CSS (nest) import Yoga.Block.Internal.CSS (nest)
import Yoga.Prelude.View (NodeRef, el, styled, styledLeaf) import Yoga.Prelude.View (NodeRef, el, guard, handler, preventDefault, styled, styledLeaf)
default ∷ default ∷
{ decorators ∷ Array (Effect JSX -> JSX) { decorators ∷ Array (Effect JSX -> JSX)
@ -136,6 +133,7 @@ animatedPopper = do
mkBasicExample = mkBasicExample =
React.reactComponent "Popper example" \p -> React.do React.reactComponent "Popper example" \p -> React.do
-- Hooks
referenceElement /\ setReferenceElement <- React.useState' nullRef referenceElement /\ setReferenceElement <- React.useState' nullRef
popperElement /\ setPopperElement <- React.useState' nullRef popperElement /\ setPopperElement <- React.useState' nullRef
arrowElement /\ setArrowElement <- React.useState' nullRef arrowElement /\ setArrowElement <- React.useState' nullRef
@ -143,63 +141,105 @@ animatedPopper = do
usePopper referenceElement popperElement usePopper referenceElement popperElement
{ modifiers: { modifiers:
[ modifierArrow arrowElement [ modifierArrow arrowElement
, modifierOffset { x: 0.0, y: 8.0 } , modifierOffset { x: 0.0, y: 12.0 }
] ]
} }
pure on /\ setOn <- React.useState' true
$ fragment -- Handlers
$ [ element R.div' let buttonClicked = setOn (not on)
{ ref: unsafeCoerce (mkEffectFn1 setReferenceElement) -- Elements
, children: [ R.text "Reference element" ] let
} result =
, el Motion.animatePresence {} fragment
[ styled Motion.div $ [ refElem
{ className: "popper-element" , popperEl
, key: "heinz" [ animatePresence
, css: $ guard on
E.css [ content
{ background: E.str "darkslateblue" [ el Block.box {} [ R.text "Ich bin Stinky Bill" ]
, borderRadius: E.str "8px" , arrow
, display: E.str "block" ]
, padding: E.str "4px 8px" ]
, "&[data-popper-placement^='top'] > .popper-arrow":
nest { bottom: E.str "-4px" }
, "&[data-popper-placement^='bottom'] > .popper-arrow":
nest { top: E.str "-4px" }
, "&[data-popper-placement^='left'] > .popper-arrow":
nest { right: E.str "-4px" }
, "&[data-popper-placement^='right'] > .popper-arrow":
nest { left: E.str "-4px" }
}
, ref: unsafeCoerce (mkEffectFn1 setPopperElement)
, style: styles.popper
, _data: attributes.popper
}
[ R.text "Popper Element"
, styledLeaf R.div'
{ className: "popper-arrow"
, id: "arrow"
, css:
E.css
{ position: E.str "absolute"
, width: E.str "8px"
, height: E.str "8px"
, zIndex: E.str "-1"
, "&::before":
nest
{ position: E.str "absolute"
, width: E.str "8px"
, height: E.str "8px"
, zIndex: E.str "-1"
, content: E.str "''"
, transform: E.str "rotate(45deg)"
, background: E.str "darkslateblue"
}
}
, ref: unsafeCoerce (mkEffectFn1 setArrowElement)
, style: styles.arrow
, _data: attributes.arrow
}
] ]
] ]
] animatePresence =
el Motion.animatePresence
{ initial: false
}
content =
styled Motion.div
{ className: "popper-element-content"
, css: contentCss
, initial: Motion.initial $ R.css { opacity: [ 1.0, 0.8, 0.0 ], scale: [ 1.0, 0.85 ] }
, 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 ] }
, exit: Motion.exit $ R.css { opacity: [ 1.0, 0.8, 0.0 ], scale: [ 1.0, 0.85 ] }
, transition: Motion.transition { duration: 0.2 }
, key: "container"
}
popperEl =
styled R.div'
{ className: "popper-element"
, css: popperCss
, ref: unsafeCoerce (mkEffectFn1 setPopperElement)
, style: styles.popper
, _data: attributes.popper
}
arrow =
styledLeaf R.div'
{ className: "popper-arrow"
, id: "arrow"
, css: arrowCss
, ref: unsafeCoerce (mkEffectFn1 setArrowElement)
, style: styles.arrow
, _data: attributes.arrow
}
refElem =
element R.button'
{ ref: unsafeCoerce (mkEffectFn1 setReferenceElement)
, children: [ R.text "Reference element" ]
, onClick: handler preventDefault (const buttonClicked)
}
pure result
backgroundColour = "var(--highlight)"
backgroundColour2 = "rgb(100, 160, 240)"
arrowCss =
E.css
{ position: E.str "absolute"
, width: E.str "8px"
, height: E.str "8px"
, zIndex: E.str "0"
, "&::before":
nest
{ position: E.str "absolute"
, width: E.str "8px"
, height: E.str "8px"
, content: E.str "''"
, transform: E.str "rotate(45deg)"
, background: E.str backgroundColour
}
}
contentCss =
E.css
{ background: E.str $ i "linear-gradient(0deg," backgroundColour "," backgroundColour2 "," backgroundColour ")"
, fontSize: E.str "14px"
, zIndex: E.str "-1"
, color: E.str $ "white"
, boxShadow: E.str "0 1px 8px rgba(0,0,0,0.5)"
, borderRadius: E.str "12px"
}
popperCss =
E.css
{ "&[data-popper-placement^='top'] > * > .popper-arrow":
nest { bottom: E.str "-4px" }
, "&[data-popper-placement^='bottom'] > * > .popper-arrow":
nest { top: E.str "-4px" }
, "&[data-popper-placement^='left'] > * > .popper-arrow":
nest { right: E.str "-4px" }
, "&[data-popper-placement^='right'] > * > .popper-arrow":
nest { left: E.str "-4px" }
}

View File

@ -0,0 +1,40 @@
module React.Basic.Popper.Types where
import Foreign.Object (Object)
import React.Basic.DOM (CSS)
import Unsafe.Coerce (unsafeCoerce)
import Yoga.Prelude.View (NodeRef, null)
nullRef ∷ NodeRef
nullRef = unsafeCoerce null
type ReferenceElement =
NodeRef
type PopperElement =
NodeRef
type ArrowElement =
NodeRef
type DataAttributes =
{ popper ∷ Object String, arrow ∷ Object String }
type Styles =
{ popper ∷ CSS, arrow ∷ CSS }
type PopperData =
{ styles ∷ Styles, attributes ∷ DataAttributes }
foreign import data Modifier ∷ Type
modifierArrow ∷ ArrowElement -> Modifier
modifierArrow element = unsafeCoerce { name: "arrow", options: { element } }
modifierOffset ∷ { x ∷ Number, y ∷ Number } -> Modifier
modifierOffset { x, y } = unsafeCoerce { name: "offset", options: { offset: [ x, y ] } }
type Options =
( modifiers ∷ Array Modifier
, strategy ∷ String
)

View File

@ -21,7 +21,7 @@ codeInput props = styles <>? cast props.css
, background: str colour.inputBackground , background: str colour.inputBackground
, border: str $ i "solid 1px " colour.inputBorder , border: str $ i "solid 1px " colour.inputBorder
, borderRadius: str "var(--s-2)" , borderRadius: str "var(--s-2)"
, fontFamily: str "var(--monoFont)" , fontFamily: str "var(--mono-font)"
, fontSize: str "var(--s0)" , fontSize: str "var(--s0)"
, lineHeight: str "var(--s0)" , lineHeight: str "var(--s0)"
, width: str $ i "calc(" (cast props.maxLength ?|| 10) "ch + 4.3 * var(--s-5))" , width: str $ i "calc(" (cast props.maxLength ?|| 10) "ch + 4.3 * var(--s-5))"

View File

@ -0,0 +1,5 @@
module Yoga.Block.Atom.Icon
( module Yoga.Block.Atom.Icon.View
) where
import Yoga.Block.Atom.Icon.View (component, Props)

View File

@ -0,0 +1,37 @@
module Yoga.Block.Atom.Icon.Story where
import Prelude
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import React.Basic (JSX, element, fragment)
import React.Basic.DOM as R
import React.Basic.Emotion as E
import Yoga.Block.Atom.Icon as Icon
import Yoga.Block.Container.Style as Styles
import Yoga.Block.Icon.SVG as SVGIcon
default ∷
{ decorators ∷ Array (Effect JSX -> JSX)
, title ∷ String
}
default =
{ title: "Atom/Icon"
, decorators:
[ \storyFn ->
R.div_
[ element E.global { styles: Styles.global }
, unsafePerformEffect storyFn
]
]
}
icon ∷ Effect JSX
icon = do
pure
$ fragment
[ R.div_
[ R.h2_ [ R.text "Icon" ]
, element Icon.component { icon: SVGIcon.on }
, element Icon.component { icon: SVGIcon.off }
]
]

View File

@ -0,0 +1,27 @@
module Yoga.Block.Atom.Icon.Style where
import Yoga.Prelude.Style
import Yoga.Block.Container.Style (colour)
type Props f r =
( css ∷ f Style
, colour ∷ f StyleProperty
, stroke ∷ f StyleProperty
, fill ∷ f StyleProperty
, size ∷ f StyleProperty
, width ∷ f StyleProperty
, height ∷ f StyleProperty
| r
)
span ∷ ∀ r. { | Props OptionalProp r } -> Style
span props =
css
{ "--stroke-colour": (props.stroke <|> props.colour) ?|| (str colour.text)
, "--fill-colour": (props.fill <|> props.colour) ?|| (str "transparent")
, "& > svg":
nest
{ width: (props.width <|> props.size) ?|| (str "auto")
, height: (props.height <|> props.size) ?|| (str "1.2ch")
}
}

View File

@ -0,0 +1,32 @@
module Yoga.Block.Atom.Icon.View where
import Yoga.Prelude.View
import React.Basic.DOM as R
import Yoga.Block.Atom.Icon.Style as Style
type Props =
PropsF Id
type MandatoryProps r =
( icon ∷ JSX
| r
)
type PropsF f =
( className ∷ f String
| Style.Props f (MandatoryProps ())
)
component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | MandatoryProps p }
component = rawComponent
rawComponent ∷ ∀ p. ReactComponent { | p }
rawComponent =
mkForwardRefComponent "Yoga Icon" \(props ∷ { | PropsF OptionalProp }) ref -> React.do
pure
$ styled R.span'
{ className: "ry-icon" <>? props.className
, css: Style.span props
, ref
}
[ props.icon ]

View File

@ -0,0 +1,5 @@
module Yoga.Block.Atom.Input
( module Yoga.Block.Atom.Input.View
) where
import Yoga.Block.Atom.Input.View (component, Props)

View File

@ -0,0 +1,56 @@
module Yoga.Block.Atom.Input.Story where
import Prelude
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import React.Basic (JSX, element, fragment)
import React.Basic.DOM as R
import React.Basic.Emotion as E
import React.Basic.Events (handler_)
import Yoga.Block.Atom.Input as Input
import Yoga.Block.Container.Style as Styles
default ∷
{ decorators ∷ Array (Effect JSX -> JSX)
, title ∷ String
}
default =
{ title: "Atom/Input"
, decorators:
[ \storyFn ->
R.div_
[ element E.global { styles: Styles.global }
, unsafePerformEffect storyFn
]
]
}
input ∷ Effect JSX
input = do
pure
$ fragment
[ R.div_
[ R.h2_ [ R.text "Generic Input" ]
, element Input.component { value: "A Generic Input", onChange: handler_ mempty }
, R.h2_ [ R.text "Password" ]
, element Input.component { type: "password" }
, R.h2_ [ R.text "Text Input" ]
, element Input.component { type: "text", value: "Some text", onChange: handler_ mempty }
, R.h2_ [ R.text "Search Input" ]
, element Input.component { type: "search", value: "Search...", onChange: handler_ mempty }
, R.h2_ [ R.text "Button" ]
, element Input.component { type: "button", value: "A button", onChange: handler_ mempty }
, R.h2_ [ R.text "Submit" ]
, element Input.component { type: "submit" }
, R.h2_ [ R.text "Radio" ]
, element Input.component { type: "radio" }
, R.h2_ [ R.text "Checkbox" ]
, element Input.component { type: "checkbox" }
, R.h2_ [ R.text "File" ]
, element Input.component { type: "file" }
, R.h2_ [ R.text "Image" ]
, element Input.component { type: "image" }
, R.h2_ [ R.text "Number" ]
, element Input.component { type: "number" }
]
]

View File

@ -0,0 +1,91 @@
module Yoga.Block.Atom.Input.Style where
import Yoga.Prelude.Style
import Yoga.Block.Container.Style (colour)
import Yoga.Prelude.Style as Color
type Props f r =
( css ∷ f Style
| r
)
leftIconSize ∷ StyleProperty
leftIconSize = var "--left-icon-size"
rightIconSize ∷ StyleProperty
rightIconSize = var "--right-icon-size"
leftIconStyle ∷ Style
leftIconStyle =
css
{ "--stroke-colour": str colour.interfaceTextDisabled
, marginTop: str "4px"
, marginLeft: str "-4px"
}
rightIconStyle ∷ Style
rightIconStyle =
css
{ "--stroke-colour": str colour.interfaceTextDisabled
, paddingTop: str "6px"
, marginRight: str "6px"
}
textWrapper ∷ ∀ r. { | Props OptionalProp r } -> Style
textWrapper props =
css
{ fontFamily: var "--main-font"
, boxSizing: borderBox
, backgroundColor: str $ colour.background07
, display: inlineFlex
, "--left-icon-size": var "--s0"
, "--right-icon-size": str "calc(var(--s0) * 1.2)"
, alignItems: center
, justifyContent: flexStart
, paddingLeft: var "--s-1"
, gap: var "--s-3"
, "--border-width": var "--s-5"
, border: str $ "var(--border-width) solid " <> colour.inputBorder
, borderRadius: var "--s-1"
, "&:focus-within":
nest
{}
}
input ∷ ∀ r. { | Props OptionalProp r } -> Style
input props =
css
{ "&[type=text],&[type=search],&[type=password],&[type=number],&:not([type])":
nest
{ color: str colour.text
, background: str "transparent"
, alignSelf: stretch
, "--padding-top": str "var(--s-1)"
, "--padding-bottom": str "calc(var(--padding-top) * 0.85)"
, paddingTop: var "--padding-top"
, paddingBottom: var "--padding-bottom"
, paddingLeft: _0
, paddingRight: _0
-- , background: str colour.inputBackground
, border: none
}
, "&[type=search]":
nest
{ "&::-webkit-search-decoration, &::-webkit-search-cancel-button, &::-webkit-serch-results-button, &::-webkit-search-results-decoration":
nest
{ "WebkitAppearance": none
}
}
, "&:focus":
nest
{ outline: none
}
, "&[type=button], &[type=submit]":
nest
{ background: str colour.highlight
, color: str "white"
, boxShadow: str "0 1px 4px rgba(0,0,0,0.5)"
, borderColour: str colour.highlight
, height: str "auto"
}
}

View File

@ -0,0 +1,142 @@
module Yoga.Block.Atom.Input.View where
import Yoga.Prelude.View
import Framer.Motion as M
import React.Basic.DOM (css)
import React.Basic.DOM as R
import React.Basic.DOM.SVG as SVG
import React.Basic.Hooks as React
import Record.Extra (pick)
import Unsafe.Coerce (unsafeCoerce)
import Yoga.Block.Atom.Icon as Icon
import Yoga.Block.Atom.Input.Style as Style
import Yoga.Block.Atom.Tooltip as Tooltip
import Yoga.Block.Icon.SVG as SVGIcon
type PropsF f =
( iconLeft ∷ f JSX
, iconRight ∷ f JSX
| Style.Props f InputProps
)
type Props =
PropsF Id
type PropsOptional =
PropsF OptionalProp
component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | p }
component = rawComponent
mkLeftIcon ∷ JSX -> JSX
mkLeftIcon icon =
styledLeaf
Icon.component
{ size: Style.leftIconSize
, className: "ry-input-right-icon"
, css: Style.leftIconStyle
, icon
}
mkRightIcon ∷ JSX -> JSX
mkRightIcon icon =
styledLeaf Icon.component
{ size: Style.rightIconSize
, className: "ry-input-left-icon"
, css: Style.rightIconStyle
, icon
}
rawComponent ∷ ∀ p. ReactComponent { | p }
rawComponent =
mkForwardRefComponent "Input" do
\(props ∷ { | PropsOptional }) ref -> React.do
let
iconLeft =
props.iconLeft
?|| if (((cast props.type) ?|| "") == "search") then mkLeftIcon SVGIcon.magnifyingGlass else mempty
iconRight =
props.iconRight
?|| if (((cast props.type) ?|| "") == "password") then mkRightIcon SVGIcon.eyeOpen else mempty
pure
$ case props.type # cast # opToMaybe of
Just "password" -> el_ password props
_ ->
styled R.div'
{ className: "ry-input-wrapper"
, css: Style.textWrapper props
}
[ iconLeft
, emotionInput
ref
props
{ className: "ry-input"
, css: Style.input props
}
, iconRight
]
password ∷ ∀ p. ReactComponent { | p }
password =
mkForwardRefComponent "Password" do
\(props ∷ { | PropsOptional }) ref -> React.do
hidePassword /\ modifyHidePassword <- useState true
let
eyeCon =
el_ Tooltip.component
{ target:
el R.div'
{ onClick: handler preventDefault \_ -> modifyHidePassword not
}
[ el M.animatePresence
{ exitBeforeEnter: true
}
[ if hidePassword then
el M.div
{ key: "eyeOpen"
, initial: M.initial $ css { scaleY: 0 }
, animate: M.animate $ css { scaleY: 1 }
, exit: M.exit $ css { scaleY: 0.9 }
, transition: M.transition { duration: 0.15 }
}
[ styledLeaf Icon.component
{ size: Style.rightIconSize
, className: "ry-input-left-icon"
, css: Style.rightIconStyle
, icon: SVGIcon.eyeOpen
}
]
else
el M.div
{ key: "eyeClosed"
, initial: M.initial $ css { scaleY: 0.0 }
, animate: M.animate $ css { scaleY: 1 }
, exit: M.exit $ css { scaleY: 0.9 }
, transition: M.transition { duration: 0.15 }
}
[ styledLeaf Icon.component
{ size: Style.rightIconSize
, className: "ry-input-left-icon"
, css: Style.rightIconStyle
, icon: SVGIcon.eyeClosed
}
]
]
]
, theTip: R.text if hidePassword then "Show password" else "Hide password"
}
let iconRight = props.iconRight ?|| eyeCon
pure
$ styled R.div'
{ className: "ry-input-wrapper"
, css: Style.textWrapper props
}
[ emotionInput
ref
props
{ className: "ry-input"
, css: Style.input props
, type: if hidePassword then "password" else "text"
}
, iconRight
]

View File

@ -6,8 +6,6 @@ import Yoga.Block.Container.Style (colour)
type Props f r = type Props f r =
( css ∷ f Style ( css ∷ f Style
, space ∷ f StyleProperty
, splitAfter ∷ f Int
| r | r
) )

View File

@ -1,13 +1,15 @@
module Yoga.Block.Atom.Range.View (component, Props, PropsF) where module Yoga.Block.Atom.Range.View (component, Props, PropsF) where
import Yoga.Prelude.View import Yoga.Prelude.View
import Yoga.Block.Atom.Range.Style as Style
import Data.Int as Int import Data.Int as Int
import Foreign.Object as Object import Foreign.Object as Object
import Framer.Motion as M
import React.Basic.DOM (css) import React.Basic.DOM (css)
import React.Basic.DOM as R import React.Basic.DOM as R
import React.Basic.DOM.Events as Event import React.Basic.DOM.Events as Event
import React.Basic.Hooks as React import React.Basic.Hooks as React
import Yoga.Block.Atom.Range.Style as Style
import Yoga.Block.Atom.Tooltip as Tooltip
type PropsF f = type PropsF f =
( className ∷ f String ( className ∷ f String
@ -58,11 +60,11 @@ rawComponent =
{ className: "ry-range-focus-circle" { className: "ry-range-focus-circle"
, css: Style.focusCircle , css: Style.focusCircle
} }
, emotionInput ref (props { max = show max, min = show min, value = show value }) , emotionInput
ref
(props { max = show max, min = show min, value = show value })
{ className: "ry-range-thumb " <>? props.className { className: "ry-range-thumb " <>? props.className
, css: Style.range props <> guard props.disabled Style.inputDisabled <>? props.css , css: Style.range props <> guard props.disabled Style.inputDisabled <>? props.css
, style: props.style
, value: show value
, type: "range" , type: "range"
, onChange: handler Event.targetValue ((_ >>= Int.fromString) >>> (foldMap setValue)) , onChange: handler Event.targetValue ((_ >>= Int.fromString) >>> (foldMap setValue))
} }

View File

@ -2,6 +2,7 @@ module Yoga.Block.Atom.Toggle.Spec where
import Yoga.Prelude.Spec import Yoga.Prelude.Spec
import Yoga.Block.Atom.Toggle as Toggle import Yoga.Block.Atom.Toggle as Toggle
import Yoga.Block.Atom.Toggle.Types (TogglePosition(..))
spec ∷ Spec Unit spec ∷ Spec Unit
spec = spec =
@ -10,6 +11,6 @@ spec =
it "renders without errors" do it "renders without errors" do
void void
$ renderComponent Toggle.component $ renderComponent Toggle.component
{ value: true { togglePosition: ToggleIsLeft
, onToggle: mempty , setTogglePosition: mempty
} }

View File

@ -10,6 +10,7 @@ import React.Basic.DOM as R
import React.Basic.Hooks as React import React.Basic.Hooks as React
import Yoga.Block as Block import Yoga.Block as Block
import Yoga.Block.Atom.Toggle as Toggle import Yoga.Block.Atom.Toggle as Toggle
import Yoga.Block.Atom.Toggle.Types (TogglePosition(..))
import Yoga.Block.Container.Style (DarkOrLightMode(..)) import Yoga.Block.Container.Style (DarkOrLightMode(..))
default ∷ default ∷
@ -35,30 +36,32 @@ toggle = do
where where
mkBasicExample = mkBasicExample =
React.reactComponent "Toggle example" \p -> React.do React.reactComponent "Toggle example" \p -> React.do
isOn /\ turnOnOrOff <- React.useState' false togglePosition /\ setTogglePosition <- React.useState' ToggleIsRight
pure pure
$ element Toggle.component $ element Toggle.component
{ value: isOn { togglePosition
, onToggle: turnOnOrOff , setTogglePosition
} }
mkDarkLightToggle = mkDarkLightToggle =
React.reactComponent "Toggle dark night example" \p -> React.do React.reactComponent "Toggle dark night example" \p -> React.do
isOn /\ turnOnOrOff <- React.useState' false togglePosition /\ setTogglePosition <- React.useState' ToggleIsLeft
theme /\ setTheme <- React.useState' Nothing theme /\ setTheme <- React.useState' Nothing
let let
content = content =
element Toggle.component element Toggle.component
{ value: isOn { togglePosition: togglePosition
, onToggle: , setTogglePosition:
\b -> do \newTogglePosition -> do
turnOnOrOff b setTogglePosition newTogglePosition
setTheme (Just if b then DarkMode else LightMode) setTheme case newTogglePosition of
, on: R.text "🌒" ToggleIsRight -> Just DarkMode
, off: R.text "🌞" ToggleIsLeft -> Just LightMode
, backgroundOn: , left: R.text "🌒"
Color.hsl 205.0 1.0 0.93 , right: R.text "🌞"
, backgroundOff: , backgroundLeft:
Color.hsl 205.0 1.0 0.83
, backgroundRight:
Color.hsl 260.0 0.7 0.45 Color.hsl 260.0 0.7 0.45
} }
pure pure

View File

@ -5,8 +5,8 @@ import Yoga.Block.Container.Style (colour)
type Props f r = type Props f r =
( css ∷ f Style ( css ∷ f Style
, backgroundOn ∷ f Color , backgroundLeft ∷ f Color
, backgroundOff ∷ f Color , backgroundRight ∷ f Color
| r | r
) )
@ -23,17 +23,17 @@ button =
, padding: _0 , padding: _0
} }
theToggle ∷ ∀ p. { | Props OptionalProp p } -> Style theToggle ∷ Style
theToggle props = theToggle =
css css
{ width: var "--s2" { width: str "calc(var(--s2) * 0.8)"
, height: var "--s2" , height: str "calc(var(--s2) * 0.8)"
, background: str $ colour.interfaceBackground , background: str $ colour.interfaceBackground
, border: none , border: none
, borderRadius: str $ "calc(var(--s2) / 2)" , borderRadius: str $ "calc(var(--s2) / 2)"
, position: absolute , position: absolute
, top: str "-1px" , top: str "3px"
, left: _0 , left: str "3px"
, margin: _0 , margin: _0
, boxShadow: str "0 0.5px 3px rgba(0,0,0,0.50)" , boxShadow: str "0 0.5px 3px rgba(0,0,0,0.50)"
} }
@ -46,25 +46,33 @@ toggleTextContainer =
, fontWeight: str "bold" , fontWeight: str "bold"
, position: absolute , position: absolute
, top: _0 , top: _0
, fontSize: str "calc(0.6 * var(--s1))"
, lineHeight: var "--s2" , lineHeight: var "--s2"
, height: var "--s2" , height: var "--s2"
, display: flex , display: flex
} }
successTextColour ∷ StyleProperty
successTextColour = str colour.successText
disabledTextColour ∷ StyleProperty
disabledTextColour = str colour.interfaceTextDisabled
toggleText ∷ Style toggleText ∷ Style
toggleText = toggleText =
css css
{ textAlign: str "left" { textAlign: str "left"
, margin: _0 , margin: _0
, padding: _0
, width: str "50%" , width: str "50%"
, height: str "100%" , height: str "100%"
, display: flex , display: flex
, justifyContent: center , justifyContent: center
, alignItems: center , alignItems: center
, color: str colour.interfaceTextDisabled , color: disabledTextColour
, "&:first-child": , "& > *":
nest nest
{ color: str colour.successText { color: successTextColour
} }
} }

View File

@ -0,0 +1,19 @@
module Yoga.Block.Atom.Toggle.Types where
import Prelude
data TogglePosition
= ToggleIsLeft
| ToggleIsRight
derive instance eqTogglePosition ∷ Eq TogglePosition
instance showTogglePosition ∷ Show TogglePosition where
show = case _ of
ToggleIsLeft -> "ToggleIsLeft"
ToggleIsRight -> "ToggleIsRight"
flipToggle ∷ TogglePosition -> TogglePosition
flipToggle = case _ of
ToggleIsLeft -> ToggleIsRight
ToggleIsRight -> ToggleIsLeft

View File

@ -3,26 +3,33 @@ module Yoga.Block.Atom.Toggle.View (component, MandatoryProps, Props, PropsF) wh
import Yoga.Prelude.View import Yoga.Prelude.View
import Color as Color import Color as Color
import Data.Interpolate (i) import Data.Interpolate (i)
import Data.Maybe (isNothing)
import Effect.Class.Console as Console import Effect.Class.Console as Console
import Effect.Unsafe (unsafePerformEffect)
import Foreign.Object as Object import Foreign.Object as Object
import Framer.Motion as Motion import Framer.Motion as Motion
import Partial.Unsafe (unsafeCrashWith)
import React.Basic.DOM (css) import React.Basic.DOM (css)
import React.Basic.DOM as R import React.Basic.DOM as R
import React.Basic.Emotion as Emotion import React.Basic.Emotion as Emotion
import React.Basic.Hooks (reactComponent)
import React.Basic.Hooks as React import React.Basic.Hooks as React
import Yoga.Block.Atom.Icon as Icon
import Yoga.Block.Atom.Toggle.Style as Style import Yoga.Block.Atom.Toggle.Style as Style
import Yoga.Block.Atom.Toggle.Types (TogglePosition(..), flipToggle)
import Yoga.Block.Container.Style (colour) import Yoga.Block.Container.Style (colour)
import Yoga.Block.Icon.SVG as SVGIcon
type PropsF f = type PropsF f =
( className ∷ f String ( className ∷ f String
, on ∷ f JSX , left ∷ f JSX
, off ∷ f JSX , right ∷ f JSX
| Style.Props f (MandatoryProps InputProps) | Style.Props f (MandatoryProps InputProps)
) )
type MandatoryProps r = type MandatoryProps r =
( value ∷ Boolean ( togglePosition ∷ TogglePosition
, onToggle ∷ Boolean -> Effect Unit , setTogglePosition ∷ TogglePosition -> Effect Unit
| r | r
) )
@ -32,7 +39,7 @@ data TappingState
data DragState data DragState
= NotDragging = NotDragging
| Dragging { startX ∷ Number } | Dragging { startX ∷ Number, currentX ∷ Number }
| DragDone { startX ∷ Number, endX ∷ Number } | DragDone { startX ∷ Number, endX ∷ Number }
derive instance eqDragState ∷ Eq DragState derive instance eqDragState ∷ Eq DragState
@ -40,8 +47,8 @@ derive instance eqDragState ∷ Eq DragState
instance showDragState ∷ Show DragState where instance showDragState ∷ Show DragState where
show = case _ of show = case _ of
NotDragging -> "NotDragging" NotDragging -> "NotDragging"
Dragging x -> "Dragging " <> (show x) Dragging x -> "Dragging " <> show x
DragDone x -> "DragDone " <> (show x) DragDone x -> "DragDone " <> show x
type Props = type Props =
PropsF Id PropsF Id
@ -57,147 +64,213 @@ rawComponent =
mkForwardRefComponent "Toggle" do mkForwardRefComponent "Toggle" do
\(props ∷ { | PropsOptional }) ref -> React.do \(props ∷ { | PropsOptional }) ref -> React.do
let disabled = props.disabled let disabled = props.disabled
tapState <- useRef TapNotAllowed
dragState /\ setDragState <- React.useState' NotDragging
maxLeft /\ setMaxLeft <- useState' 0.0
buttonRef <- useRef null buttonRef <- useRef null
toggleRef <- useRef null toggleRef <- useRef null
leftState /\ setLeftState <- React.useState' 0.0 tapState <- useRef TapNotAllowed
let dragState /\ setDragState <- React.useState' NotDragging
getWidth aRef = do
bbox <- getBoundingBoxFromRef aRef
pure $ bbox <#> _.width # fromMaybe 0.0
buttonWidth = getWidth buttonRef
toggleWidth = getWidth toggleRef
useLayoutEffectAlways do
when (maxLeft == 0.0) do
bw <- buttonWidth
tw <- toggleWidth
setMaxLeft (bw - tw)
mempty
let
toggleVariants =
{ off: { x: 0.0 }
, on: { x: maxLeft }
}
toggleVariant = Motion.makeVariantLabels toggleVariants
buttonVariants =
{ off: { backgroundColor: (Emotion.str <<< Color.cssStringRGBA <$> props.backgroundOn) ?|| Emotion.str colour.inputBackground }
, on: { backgroundColor: (Emotion.str <<< Color.cssStringRGBA <$> props.backgroundOff) ?|| Emotion.str colour.success }
}
buttonVariant = Motion.makeVariantLabels buttonVariants
hasFocus /\ setHasFocus <- useState' false
useEffect dragState do useEffect dragState do
case dragState of case dragState of
NotDragging -> mempty NotDragging -> mempty
Dragging { startX } -> writeRef tapState TapNotAllowed Dragging { startX, currentX } -> do
writeRef tapState TapNotAllowed
DragDone { startX, endX } -> do DragDone { startX, endX } -> do
maybeBbox <- getBoundingBoxFromRef buttonRef maybeBbox <- getBoundingBoxFromRef buttonRef
for_ maybeBbox \bbox -> do for_ maybeBbox \bbox -> do
if endX - startX <= (bbox.left - startX) + (bbox.width / 2.0) then do if endX - startX <= (bbox.left - startX) + (bbox.width / 2.0) then do
props.onToggle false props.setTogglePosition ToggleIsLeft
else do else do
props.onToggle true props.setTogglePosition ToggleIsRight
mempty mempty
pure let
$ styled Motion.button buttonVariants =
{ left: { backgroundColor: (Emotion.str <<< Color.cssStringRGBA <$> props.backgroundLeft) ?|| Emotion.str colour.inputBackground }
, right: { backgroundColor: (Emotion.str <<< Color.cssStringRGBA <$> props.backgroundRight) ?|| Emotion.str colour.success }
}
buttonVariant = Motion.makeVariantLabels buttonVariants
-- components
let
result =
container
[ animateTextPresence
[ textContainer
[ textOnContainer
[ guard (props.togglePosition == ToggleIsRight)
$ textOn
[ props.left
?|| el_ Icon.component { icon: SVGIcon.on, stroke: Style.successTextColour }
]
]
, textOffContainer
[ guard (props.togglePosition == ToggleIsLeft)
$ textOff
[ props.right
?|| el_ Icon.component { icon: SVGIcon.off, stroke: Style.disabledTextColour }
]
]
]
]
, toggle
]
container =
styled Motion.button
{ className: "ry-toggle" { className: "ry-toggle"
, css: Style.button <> guard props.disabled Style.inputDisabled , css: Style.button
, onFocus: handler_ $ setHasFocus true
, onBlur: handler_ $ setHasFocus false
, transition: Motion.transition { type: "tween", duration: 0.33, ease: "easeOut" } , transition: Motion.transition { type: "tween", duration: 0.33, ease: "easeOut" }
, variants: Motion.variants buttonVariants , variants: Motion.variants buttonVariants
, animate: Motion.animate if props.value then buttonVariant.on else buttonVariant.off , animate:
, value: show props.value Motion.animate case props.togglePosition of
, onClick: handler preventDefault \_ -> props.onToggle (not props.value) ToggleIsRight -> buttonVariant.right
ToggleIsLeft -> buttonVariant.left
, value: show props.togglePosition
, onClick: handler preventDefault \_ -> props.setTogglePosition (flipToggle props.togglePosition)
, style: props.style , style: props.style
, _data: Object.singleton "testid" "toggle-testid" , _data: Object.singleton "testid" "toggle-testid"
, role: "switch" , role: "switch"
, _aria: Object.singleton "checked" "switch" , _aria: Object.singleton "checked" "switch"
, ref: buttonRef , ref: buttonRef
} }
[ styled R.div' textContainer =
{ className: "ry-toggle-text" styled R.div'
, css: Style.toggleTextContainer { className: "ry-toggle-text"
} , css: Style.toggleTextContainer
[ styled R.div' }
{ className: "ry-toggle-text-on" textOnContainer =
, css: Style.toggleText styled R.div'
} { className: "ry-toggle-text-on"
[ el Motion.animatePresence {} , css: Style.toggleText
[ guard (props.value) }
$ styled Motion.div textOffContainer =
{ className: "ry-toggle-text-on-container" styled R.div'
, css: Style.toggleOnText { className: "ry-toggle-text-off"
, key: "ry-toggle-text-on-container" , css: Style.toggleText
, initial: Motion.initial $ css { scale: 0, opacity: 0 } }
, animate: Motion.animate $ css { scale: 1, opacity: 1 } textOn =
, exit: Motion.exit $ css { scale: 0, opacity: 0 } el Motion.div
} { className: "ry-toggle-text-container"
[ props.on ?|| R.text "I" ] , key: "ry-toggle-text-on-container"
] , initial: Motion.initial $ css { scale: 0, opacity: 0 }
] , animate: Motion.animate $ css { scale: 1, opacity: 1 }
, styled R.div' , exit: Motion.exit $ css { scale: 0, opacity: 0 }
{ className: "ry-toggle-text-off" }
, css: Style.toggleText textOff =
} el Motion.div
[ el Motion.animatePresence {} { className: "ry-toggle-text-container"
[ guard (not props.value) , key: "ry-toggle-text-off-container"
$ styled Motion.div , initial: Motion.initial $ css { scale: 0, opacity: 0 }
{ className: "ry-toggle-text-on-container" , animate: Motion.animate $ css { scale: 1, opacity: 1 }
, css: Style.toggleOnText , exit: Motion.exit $ css { scale: 0, opacity: 0 }
, key: "ry-toggle-text-on-container" }
, initial: Motion.initial $ css { scale: 0, opacity: 0 } animateTextPresence = el Motion.animatePresence {}
, animate: Motion.animate $ css { scale: 1, opacity: 1 } toggle =
, exit: Motion.exit $ css { scale: 0, opacity: 0 } el_ toggleCircle
} { buttonRef
[ props.off ?|| R.text "O" ] , toggleRef
] , togglePosition: props.togglePosition
] , setTogglePosition: props.setTogglePosition
] , dragState
, styled Motion.div , setDragState
, tapState
}
pure result
toggleCircle ∷
ReactComponent
{ setTogglePosition ∷ TogglePosition -> Effect Unit
, toggleRef ∷ NodeRef
, buttonRef ∷ NodeRef
, togglePosition ∷ TogglePosition
, dragState ∷ DragState
, setDragState ∷ DragState -> Effect Unit
, tapState ∷ Ref TappingState
}
toggleCircle =
unsafePerformEffect
$ reactComponent "ToggleCircle" do
\( { togglePosition
, setTogglePosition
, toggleRef
, buttonRef
, dragState
, setDragState
, tapState
}
) -> React.do
maxLeft /\ setMaxLeft <- useState' Nothing
useEffectAlways do
when (maxLeft == Nothing) do
runMaybeT_ do
b <- getBoundingBoxFromRef buttonRef # MaybeT
t <- getBoundingBoxFromRef toggleRef # MaybeT
let ml = b.width - t.width - (2.0 * (t.left - b.left))
setMaxLeft (Just ml) # lift
mempty
let
toggleVariants =
{ left: { x: 0.0 }
, right: { x: maxLeft # fromMaybe 0.0 }
}
toggleVariant = Motion.makeVariantLabels toggleVariants
pure
$ styledLeaf Motion.div
{ className: "ry-toggle-toggle" { className: "ry-toggle-toggle"
, css: Style.theToggle props
, layout: Motion.layout true , layout: Motion.layout true
, onClick: handler stopPropagation mempty , onClick: handler stopPropagation mempty
, onTouchStart: handler stopPropagation mempty
, onTouchEnd: handler stopPropagation mempty
, css: Style.theToggle
, drag: Motion.drag "x" , drag: Motion.drag "x"
, dragMomentum: Motion.dragMomentum false , dragMomentum: Motion.dragMomentum false
, key: if isNothing maxLeft then "initialising" else "ready"
, dragElastic: Motion.dragElastic false , dragElastic: Motion.dragElastic false
, dragConstraints: , dragConstraints:
Motion.dragConstraints Motion.dragConstraints
{ left: if props.value then negate maxLeft else zero { left:
, right: if props.value then zero else maxLeft case togglePosition of
ToggleIsLeft -> zero
ToggleIsRight -> negate (maxLeft # fromMaybe 0.0)
, right:
case togglePosition of
ToggleIsRight -> zero
ToggleIsLeft -> maxLeft # fromMaybe 0.0
} }
, variants: Motion.variants toggleVariants , variants: Motion.variants toggleVariants
, transition: Motion.transition { type: "tween", duration: 0.33, ease: "easeOut" } , transition: Motion.transition { type: "tween", duration: 0.33, ease: "easeOut" }
, whileTap: Motion.prop $ css { scale: 0.85, transition: { type: "tween", duration: 0.10, ease: "easeInOut" } } , whileTap: Motion.prop $ css { scale: 1.1, transition: { type: "tween", duration: 0.10, ease: "easeInOut" } }
, onTapStart: Motion.onTapStart \_ _ -> writeRef tapState TapAllowed , onTapStart: Motion.onTapStart \_ _ -> writeRef tapState TapAllowed
, onTap: , onTap:
Motion.onTap \_ pi -> do Motion.onTap \_ pi -> do
ts <- readRef tapState ts <- readRef tapState
case ts of case ts of
TapAllowed -> props.onToggle $ not props.value TapAllowed -> setTogglePosition (flipToggle togglePosition)
_ -> mempty _ -> mempty
mempty mempty
, onTapCancel: , onTapCancel:
Motion.onTapCancel \_ pi -> do Motion.onTapCancel \_ pi -> do
writeRef tapState TapNotAllowed writeRef tapState TapNotAllowed
mempty mempty
, animate: Motion.animate if props.value then toggleVariant.on else toggleVariant.off , initial: Motion.initial false
, animate:
Motion.animate case togglePosition of
ToggleIsLeft -> toggleVariant.left
ToggleIsRight -> toggleVariant.right
, onDragStart: , onDragStart:
Motion.onDragStart \_ pi -> do Motion.onDragStart \_ pi -> do
maybeBBox <- getBoundingBoxFromRef toggleRef maybeBBox <- getBoundingBoxFromRef toggleRef
let x = maybeBBox <#> \bbox -> bbox.left + (bbox.width / 2.0) let x = maybeBBox <#> \bbox -> bbox.left + (bbox.width / 2.0)
setDragState $ Dragging { startX: x # fromMaybe pi.point.x } setDragState $ Dragging { startX: x # fromMaybe pi.point.x, currentX: pi.point.x }
, onDrag:
Motion.onDrag \_ pi -> do
case dragState of
Dragging { startX } -> do
setDragState $ Dragging { startX, currentX: pi.point.x }
other -> Console.warn $ i "Unexpected drag state " (show other) " in onDragEvent"
, onDragEnd: , onDragEnd:
Motion.onDragEnd \_ pi -> do Motion.onDragEnd \_ pi -> do
case dragState of case dragState of
Dragging { startX } -> do Dragging { startX } -> do
maybeBBox <- getBoundingBoxFromRef toggleRef maybeBBox <- getBoundingBoxFromRef toggleRef
let x = maybeBBox <#> \bbox -> bbox.left + (bbox.width / 2.0) let x = maybeBBox <#> \bbox -> bbox.left + (bbox.width / 2.0)
setDragState (DragDone { startX, endX: x # fromMaybe pi.point.x }) setDragState (DragDone { startX, endX: x # fromMaybe' \_ -> unsafeCrashWith "shit" })
other -> Console.warn $ i "Unexpected drag state " (show other) " in onDragEvent" other -> Console.warn $ i "Unexpected drag state " (show other) " in onDragEvent"
, ref: toggleRef , ref: toggleRef
} }
[]
]

View File

@ -0,0 +1,5 @@
module Yoga.Block.Atom.Tooltip
( module Yoga.Block.Atom.Tooltip.View
) where
import Yoga.Block.Atom.Tooltip.View (component, Props)

View File

@ -0,0 +1,16 @@
module Yoga.Block.Atom.Tooltip.Spec where
import Yoga.Prelude.Spec
import React.Basic.DOM as R
import Yoga.Block.Atom.Tooltip as Tooltip
spec ∷ Spec Unit
spec =
after_ cleanup do
describe "The tooltip" do
it "renders without errors" do
void
$ renderComponent Tooltip.component
{ theTip: R.text "Tip"
, target: R.text "Target"
}

View File

@ -0,0 +1,39 @@
module Yoga.Block.Atom.Tooltip.Story where
import Prelude
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import React.Basic (JSX, element, fragment)
import React.Basic.DOM as R
import React.Basic.Emotion as E
import Yoga (el)
import Yoga.Block.Atom.Tooltip as Tooltip
import Yoga.Block.Container.Style as Styles
default ∷
{ decorators ∷ Array (Effect JSX -> JSX)
, title ∷ String
}
default =
{ title: "Atom/Tooltip"
, decorators:
[ \storyFn ->
R.div_
[ element E.global { styles: Styles.global }
, unsafePerformEffect storyFn
]
]
}
tooltip ∷ Effect JSX
tooltip = do
pure
$ fragment
[ R.div_
[ R.h2_ [ R.text "Basics" ]
, element Tooltip.component
{ theTip: R.text "Hi"
, target: el R.button' {} [ R.text "Holzkop" ]
}
]
]

View File

@ -0,0 +1,57 @@
module Yoga.Block.Atom.Tooltip.Style where
import Yoga.Prelude.Style
import Yoga.Block.Container.Style (colour)
type Props f r =
( css ∷ f Style
, backgroundColour ∷ f Color
| r
)
backgroundColour ∷ String
backgroundColour = colour.text
arrow ∷ ∀ r. { | Props OptionalProp r } -> Style
arrow props =
css
{ position: str "absolute"
, width: str "12px"
, height: str "12px"
, zIndex: str "0"
, "&::before":
nest
{ position: str "absolute"
, width: str "12px"
, height: str "12px"
, borderRadius: str "2px"
, content: str "''"
, transform: str "rotate(45deg)"
, background: str $ (cssStringRGBA <$> props.backgroundColour) ?|| backgroundColour
}
}
content ∷ ∀ r. { | Props OptionalProp r } -> Style
content props =
css
{ background: str $ (cssStringRGBA <$> props.backgroundColour) ?|| backgroundColour
, minWidth: str "50px"
, padding: str "6px"
, zIndex: str "-1"
, color: str colour.background0
, boxShadow: str "0 1px 8px rgba(0,0,0,0.33)"
, borderRadius: var "--s-2"
}
popper ∷ Style
popper =
css
{ "&[data-popper-placement^='top'] > * > .popper-arrow":
nest { bottom: str "-4px" }
, "&[data-popper-placement^='bottom'] > * > .popper-arrow":
nest { top: str "-4px" }
, "&[data-popper-placement^='left'] > * > .popper-arrow":
nest { right: str "-4px" }
, "&[data-popper-placement^='right'] > * > .popper-arrow":
nest { left: str "-4px" }
}

View File

@ -0,0 +1,129 @@
module Yoga.Block.Atom.Tooltip.View (component, MandatoryProps, Props, PropsF) where
import Yoga.Prelude.View
import Debug.Trace (spy)
import Effect.Aff (Milliseconds(..), delay)
import Effect.Uncurried (mkEffectFn1)
import Framer.Motion as Motion
import React.Basic.DOM (css)
import React.Basic.DOM as R
import React.Basic.Hooks as React
import React.Basic.Hooks.Aff (useAff)
import React.Basic.Popper.Hook (usePopper)
import React.Basic.Popper.Types (modifierArrow, modifierOffset, nullRef)
import Unsafe.Coerce (unsafeCoerce)
import Yoga.Block.Atom.Tooltip.Style as Style
type PropsF f =
( className ∷ f String
, hideDelay ∷ f Milliseconds
| Style.Props f (MandatoryProps ())
)
type MandatoryProps r =
( theTip ∷ JSX
, target ∷ JSX
| r
)
type Props =
PropsF Id
type PropsOptional =
PropsF OptionalProp
component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | MandatoryProps p }
component = rawComponent
rawComponent ∷ ∀ p. ReactComponent (Record p)
rawComponent =
mkForwardRefComponent "Tooltip" do
\(props ∷ { | PropsOptional }) ref -> React.do
-- Hooks
referenceElement /\ setReferenceElement <- React.useState' nullRef
popperElement /\ setPopperElement <- React.useState' nullRef
arrowElement /\ setArrowElement <- React.useState' nullRef
{ styles, attributes } <-
usePopper referenceElement popperElement
{ modifiers:
[ modifierArrow arrowElement
, modifierOffset { x: 0.0, y: 12.0 }
]
}
visible /\ setVisible <- React.useState' false
touching /\ setTouching <- React.useState' false
hovering /\ setHovering <- React.useState' false
useAff hovering do
if hovering then
setVisible true # liftEffect
else do
delay (props.hideDelay ?|| (0.0 # Milliseconds))
setVisible false # liftEffect
useAff touching do
if touching then do
let _ = spy "sausage" touching
delay (1000.0 # Milliseconds)
setVisible true # liftEffect
else do
delay (props.hideDelay ?|| (450.0 # Milliseconds))
setVisible false # liftEffect
-- Handlers
let
hoveredIn = setHovering true
hoveredOut = setHovering false
touchStarted = setTouching true
touchEnded = setTouching false
-- Elements
let
result =
fragment
$ [ refElem
, popperEl
[ animatePresence
$ guard visible
[ content
[ props.theTip
, arrow
]
]
]
]
animatePresence = el Motion.animatePresence { initial: false }
content =
styled Motion.div
{ className: "popper-element-content"
, css: Style.content props
, initial: Motion.initial $ R.css { opacity: [ 1.0, 0.8, 0.0 ], scale: [ 1.0, 0.85 ] }
, 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 ] }
, exit: Motion.exit $ R.css { opacity: [ 1.0, 0.8, 0.0 ], scale: [ 1.0, 0.85 ] }
, transition: Motion.transition { duration: 0.2 }
, key: "container"
}
popperEl =
styled R.div'
{ className: "popper-element"
, css: Style.popper
, ref: unsafeCoerce (mkEffectFn1 setPopperElement)
, style: styles.popper
, _data: attributes.popper
}
arrow =
styledLeaf R.div'
{ className: "popper-arrow"
, id: "arrow"
, css: Style.arrow props
, ref: unsafeCoerce (mkEffectFn1 setArrowElement)
, style: styles.arrow
, _data: attributes.arrow
}
refElem =
element Motion.div
{ ref: unsafeCoerce (mkEffectFn1 setReferenceElement)
, style: css { display: "inline-block" }
, children: [ props.target ]
, onHoverStart: Motion.onHoverStart \_ _ -> hoveredIn
, onHoverEnd: Motion.onHoverEnd \_ _ -> hoveredOut
, onTouchStart: handler_ touchStarted
, onTouchEnd: handler_ touchEnded
}
pure result

View File

@ -50,7 +50,7 @@ mkGlobal maybeMode =
, body: , body:
nested nested
$ css $ css
{ fontFamily: str "var(--mainFont)" { fontFamily: str "var(--main-font)"
, background: str colour.background0 , background: str colour.background0
, color: str colour.text , color: str colour.text
, margin: str "0" , margin: str "0"
@ -61,7 +61,7 @@ mkGlobal maybeMode =
Just LightMode -> lightModeStyle Just LightMode -> lightModeStyle
, "pre,code": , "pre,code":
nest nest
{ fontFamily: str "var(--monoFont)" { fontFamily: str "var(--mono-font)"
} }
, "h1,h2,h3,h4,h5": , "h1,h2,h3,h4,h5":
nest nest
@ -76,7 +76,6 @@ mkGlobal maybeMode =
$ css $ css
{ boxSizing: str "inherit" { boxSizing: str "inherit"
} }
, input
, form: , form:
nested nested
$ css $ css
@ -155,7 +154,7 @@ defaultColours =
-- highlight = Color.rgb 0x10 0x45 0x4A -- highlight = Color.rgb 0x10 0x45 0x4A
darkBg = Color.rgb 0 0 0 darkBg = Color.rgb 0 0 0
lightBg = Color.rgb 255 255 255 lightBg = Color.rgb 250 250 250
type FlatTheme a = type FlatTheme a =
{ background0 ∷ a { background0 ∷ a
@ -262,22 +261,8 @@ variables =
fontVariables ∷ { main ∷ String, mono ∷ String } -> Style fontVariables ∷ { main ∷ String, mono ∷ String } -> Style
fontVariables { main, mono } = fontVariables { main, mono } =
css css
{ "--mainFont": str $ main <> """, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"""" { "--main-font": str $ main <> """, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol""""
, "--monoFont": str $ mono <> ", monospace, monospace" , "--mono-font": str $ mono <> ", monospace, monospace"
}
input ∷ StyleProperty
input =
nest
{ backgroundColor: "var(--input-bg-col)" # str
, padding: "5px" # str
, border: str "1px solid var(--bg-col)"
, borderRadius: "5px" # str
, fontSize: "12px" # str
, paddingBottom: "4px" # str
, paddingLeft: "8px" # str
, "&:disabled": nest { color: str "var(--input-text-col-disabled)" }
, "&:focus": nested inputFocus
} }
-- Standalone style for storybook -- Standalone style for storybook

View File

@ -0,0 +1,32 @@
module Yoga.Block.Icon.SVG.EyeClosed where
import React.Basic (JSX)
import React.Basic.DOM as R
import React.Basic.DOM.SVG as SVG
eyeClosed ∷ JSX
eyeClosed =
SVG.svg
{ viewBox: "0 0 100 100"
, xmlns: "http://www.w3.org/2000/svg"
, fillRule: "evenodd"
, clipRule: "evenodd"
, strokeLinejoin: "round"
, strokeMiterlimit: "1.5"
, children:
[ SVG.path
{ fill: "none"
, d: "M0 0h100v100H0z"
}
, SVG.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: "var(--stroke-colour)"
}
, SVG.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: "var(--stroke-colour)"
, strokeWidth: "8"
}
]
}

View File

@ -0,0 +1,44 @@
module Yoga.Block.Icon.SVG.EyeOpen where
import React.Basic (JSX)
import React.Basic.DOM as R
import React.Basic.DOM.SVG as SVG
eyeOpen ∷ JSX
eyeOpen =
SVG.svg
{ viewBox: "0 0 100 100"
, xmlns: "http://www.w3.org/2000/svg"
, fillRule: "evenodd"
, clipRule: "evenodd"
, strokeLinejoin: "round"
, strokeMiterlimit: "1.5"
, children:
[ SVG.path
{ fill: "none"
, d: "M0 0h100v100H0z"
}
, SVG.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: "var(--stroke-colour)"
, strokeWidth: "8"
}
, SVG.circle
{ cx: "49.963"
, cy: "50.087"
, r: "24.205"
, fill: "none"
, stroke: "var(--stroke-colour)"
, strokeWidth: "6"
}
, SVG.circle
{ cx: "49.963"
, cy: "50.087"
, r: "9.748"
, fill: "var(--stroke-colour)"
, stroke: "var(--stroke-colour)"
, strokeWidth: "8"
}
]
}

View File

@ -0,0 +1,13 @@
module Yoga.Block.Icon.SVG
( module Yoga.Block.Icon.SVG.On
, module Yoga.Block.Icon.SVG.Off
, module Yoga.Block.Icon.SVG.MagnifyingGlass
, module Yoga.Block.Icon.SVG.EyeClosed
, module Yoga.Block.Icon.SVG.EyeOpen
) where
import Yoga.Block.Icon.SVG.On (on)
import Yoga.Block.Icon.SVG.Off (off)
import Yoga.Block.Icon.SVG.MagnifyingGlass (magnifyingGlass)
import Yoga.Block.Icon.SVG.EyeClosed (eyeClosed)
import Yoga.Block.Icon.SVG.EyeOpen (eyeOpen)

View File

@ -0,0 +1,36 @@
module Yoga.Block.Icon.SVG.MagnifyingGlass where
import React.Basic (JSX)
import React.Basic.DOM as R
import React.Basic.DOM.SVG as SVG
magnifyingGlass ∷ JSX
magnifyingGlass =
SVG.svg
{ viewBox: "0 0 100 100"
, xmlns: "http://www.w3.org/2000/svg"
, fillRule: "evenodd"
, clipRule: "evenodd"
, strokeLinejoin: "round"
, strokeMiterlimit: "1.5"
, children:
[ SVG.path
{ fill: "none"
, d: "M0 0h100v100H0z"
}
, SVG.circle
{ cx: "42.994"
, cy: "42.837"
, r: "30.313"
, fill: "none"
, stroke: "var(--stroke-colour)"
, strokeWidth: "10"
}
, SVG.path
{ d: "M66.723 66.665l24.062 24.062"
, fill: "none"
, stroke: "var(--stroke-colour)"
, strokeWidth: "8"
}
]
}

View File

@ -0,0 +1,30 @@
module Yoga.Block.Icon.SVG.Off where
import React.Basic (JSX)
import React.Basic.DOM as R
import React.Basic.DOM.SVG as SVG
off ∷ JSX
off =
SVG.svg
{ viewBox: "0 0 100 100"
, xmlns: "http://www.w3.org/2000/svg"
, fillRule: "evenodd"
, clipRule: "evenodd"
, strokeLinejoin: "round"
, strokeMiterlimit: "1.5"
, children:
[ SVG.path
{ fill: "none"
, d: "M0 0h100v100H0z"
}
, SVG.circle
{ cx: "50"
, cy: "50"
, r: "37.808"
, fill: "none"
, stroke: "var(--stroke-colour)"
, strokeWidth: "20"
}
]
}

View File

@ -0,0 +1,41 @@
module Yoga.Block.Icon.SVG.On where
import React.Basic (JSX)
-- import React.Basic.DOM as R
import React.Basic.DOM.SVG as SVG
on ∷ JSX
on =
SVG.svg
{ viewBox: "0 0 100 100"
, xmlns: "http://www.w3.org/2000/svg"
, fillRule: "evenodd"
, clipRule: "evenodd"
, strokeLinejoin: "round"
, strokeMiterlimit: "1.5"
, children:
[ SVG.path
{ fill: "none"
, d: "M0 0h100v100H0z"
}
, SVG.clipPath
{ id: "a"
, children:
[ SVG.path
{ d: "M0 0h100v100H0z"
}
]
}
, SVG.g
{ clipPath: "url(#a)"
, children:
[ SVG.path
{ d: "M50 1.921v96.261"
, fill: "none"
, stroke: "var(--stroke-colour)"
, strokeWidth: "20"
}
]
}
]
}

View File

@ -0,0 +1,25 @@
module Yoga.Block.Icon.SVG.QuestionMark where
import React.Basic (JSX)
import React.Basic.DOM as R
import React.Basic.DOM.SVG as SVG
questionMark :: JSX
questionMark = SVG.svg
{ viewBox: "0 0 100 100"
, xmlns: "http://www.w3.org/2000/svg"
, fillRule: "evenodd"
, clipRule: "evenodd"
, strokeLinejoin: "round"
, strokeMiterlimit: "2"
, children:
[ SVG.path
{ fill: "none"
, d: "M0 0h100v100H0z"
}
, SVG.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"
}
]
}

View File

@ -0,0 +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>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +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>

After

Width:  |  Height:  |  Size: 564 B

View File

@ -0,0 +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>

After

Width:  |  Height:  |  Size: 366 B

View File

@ -0,0 +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>

After

Width:  |  Height:  |  Size: 275 B

View File

@ -0,0 +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>

After

Width:  |  Height:  |  Size: 349 B

View File

@ -0,0 +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>

After

Width:  |  Height:  |  Size: 796 B

View File

@ -11,6 +11,7 @@ module Yoga.Prelude.Default
, module Data.Foldable , module Data.Foldable
, module Data.FoldableWithIndex , module Data.FoldableWithIndex
, module Data.FunctorWithIndex , module Data.FunctorWithIndex
, runMaybeT_
) where ) where
import Prelude import Prelude
@ -25,3 +26,6 @@ import Data.Maybe (Maybe(..), fromMaybe, fromMaybe', isJust, maybe)
import Data.Monoid (guard) import Data.Monoid (guard)
import Effect (Effect) import Effect (Effect)
import Effect.Class (liftEffect) import Effect.Class (liftEffect)
runMaybeT_ ∷ ∀ f a. Functor f => MaybeT f a -> f Unit
runMaybeT_ = void <<< runMaybeT

View File

@ -4854,25 +4854,23 @@ fragment-cache@^0.2.1:
dependencies: dependencies:
map-cache "^0.2.2" map-cache "^0.2.2"
framer-motion@^2.7.0: framer-motion@^3.0.0:
version "2.9.5" version "3.1.1"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-2.9.5.tgz#bbb185325d531c57f494cf3f6cf7719fc2c225c7" resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-3.1.1.tgz#a8a779501213b7ce02cc35beb27621d73cc2f1e7"
integrity sha512-epSX4Co1YbDv0mjfHouuY0q361TpHE7WQzCp/xMTilxy4kXd+Z23uJzPVorfzbm1a/9q1Yu8T5bndaw65NI4Tg== integrity sha512-Gm1QSb0xUxuhcPar5FIs5Ws+STrhLZ6XZf2Io8dVwFofe1OzwkL9asGFVu7z3y6WqC4Hvnxm7wsW5SBHlxZDYw==
dependencies: dependencies:
framesync "^4.1.0" framesync "^5.0.0"
hey-listen "^1.0.8" hey-listen "^1.0.8"
popmotion "9.0.0-rc.20" popmotion "^9.0.2"
style-value-types "^3.1.9" style-value-types "^3.2.0"
tslib "^1.10.0" tslib "^1.10.0"
optionalDependencies: optionalDependencies:
"@emotion/is-prop-valid" "^0.8.2" "@emotion/is-prop-valid" "^0.8.2"
framesync@^4.1.0: framesync@5.0.0, framesync@^5.0.0:
version "4.1.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/framesync/-/framesync-4.1.0.tgz#69a8db3ca432dc70d6a76ba882684a1497ef068a" resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.0.0.tgz#7de8caedf53ac441118e79680f1beb7391c328b6"
integrity sha512-MmgZ4wCoeVxNbx2xp5hN/zPDCbLSKiDt4BbbslK7j/pM2lg5S0vhTNv1v8BCVb99JPIo6hXBFdwzU7Q4qcAaoQ== integrity sha512-wd8t+JsQGisluSv1twiEeDv0aNGpavGb9q7xgIk9fGbcIWkNXF/KVtrjnOrCwBWJuiXxlJfNkcvGudsI32FxYA==
dependencies:
hey-listen "^1.0.5"
fresh@0.5.2: fresh@0.5.2:
version "0.5.2" version "0.5.2"
@ -5315,7 +5313,7 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hey-listen@^1.0.5, hey-listen@^1.0.8: hey-listen@^1.0.8:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68" resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q== integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
@ -7489,14 +7487,14 @@ polished@^3.4.4:
dependencies: dependencies:
"@babel/runtime" "^7.9.2" "@babel/runtime" "^7.9.2"
popmotion@9.0.0-rc.20: popmotion@^9.0.2:
version "9.0.0-rc.20" version "9.0.2"
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.0-rc.20.tgz#f3550042ae31957b5416793ae8723200951ad39d" resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.2.tgz#477650c3b4af97161011809223d9ca6860f3a2b5"
integrity sha512-f98sny03WuA+c8ckBjNNXotJD4G2utG/I3Q23NU69OEafrXtxxSukAaJBxzbtxwDvz3vtZK69pu9ojdkMoBNTg== integrity sha512-WfSg8IfoUwYIP9uqeqbgncIsMHLAKWqebT2IP1aGAI6gdSJqTPy/H8NvP4ZyDtDCUCx5Yh3Pth/7iUJjIwR7LA==
dependencies: dependencies:
framesync "^4.1.0" framesync "5.0.0"
hey-listen "^1.0.8" hey-listen "^1.0.8"
style-value-types "^3.1.9" style-value-types "3.2.0"
tslib "^1.10.0" tslib "^1.10.0"
portfinder@^1.0.26: portfinder@^1.0.26:
@ -9087,10 +9085,10 @@ style-loader@^1.2.1:
loader-utils "^2.0.0" loader-utils "^2.0.0"
schema-utils "^2.7.0" schema-utils "^2.7.0"
style-value-types@^3.1.9: style-value-types@3.2.0, style-value-types@^3.2.0:
version "3.1.9" version "3.2.0"
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.1.9.tgz#faf7da660d3f284ed695cff61ea197d85b9122cc" resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.2.0.tgz#eb89cab1340823fa7876f3e289d29d99c92111bb"
integrity sha512-050uqgB7WdvtgacoQKm+4EgKzJExVq0sieKBQQtJiU3Muh6MYcCp4T3M8+dfl6VOF2LR0NNwXBP1QYEed8DfIw== integrity sha512-ih0mGsrYYmVvdDi++/66O6BaQPRPRMQHoZevNNdMMcPlP/cH28Rnfsqf1UEba/Bwfuw9T8BmIMwbGdzsPwQKrQ==
dependencies: dependencies:
hey-listen "^1.0.8" hey-listen "^1.0.8"
tslib "^1.10.0" tslib "^1.10.0"