diff --git a/.psci_modules/index.js b/.psci_modules/index.js
new file mode 100644
index 0000000..54960f9
--- /dev/null
+++ b/.psci_modules/index.js
@@ -0,0 +1 @@
+require('$PSCI')['$main']();
\ No newline at end of file
diff --git a/convert-more.fish b/convert-more.fish
new file mode 100644
index 0000000..3db31fe
--- /dev/null
+++ b/convert-more.fish
@@ -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
+
diff --git a/convert-svgs.js b/convert-svgs.js
index c9ecc82..774d145 100644
--- a/convert-svgs.js
+++ b/convert-svgs.js
@@ -1,6 +1,6 @@
const path = require('path');
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)
diff --git a/package.json b/package.json
index 0070d23..218e117 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"compression-webpack-plugin": "^6.0.3",
- "framer-motion": "^2.7.0",
+ "framer-motion": "^3.0.0",
"html-webpack-plugin": "^4.5.0",
"jsdom": "^16.3.0",
"jsdom-global": "^3.0.2",
diff --git a/packages.dhall b/packages.dhall
index 0df5a13..4bc844e 100644
--- a/packages.dhall
+++ b/packages.dhall
@@ -119,7 +119,7 @@ let additions =
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 =
{ spec-discovery = upstream.spec-discovery // { version = "master" }
diff --git a/spago.dhall b/spago.dhall
index 53c3cf6..2934604 100644
--- a/spago.dhall
+++ b/spago.dhall
@@ -18,6 +18,7 @@ You can edit this file as you like.
, "routing-duplex"
, "spec-discovery"
, "untagged-union"
+ , "web-uievents"
]
, packages = ./packages.dhall
, sources = [ "src/**/*.purs", "test/**/*.purs" ]
diff --git a/src/Framer/Motion.purs b/src/Framer/Motion.purs
index 30273f0..8dfb7d3 100644
--- a/src/Framer/Motion.purs
+++ b/src/Framer/Motion.purs
@@ -6,6 +6,14 @@ module Framer.Motion
, callback
, class EffectFnMaker
, toEffectFn
+ , OnHoverStart
+ , onHoverStart
+ , customProp
+ , OnHoverEnd
+ , onHoverEnd
+ , whileHover
+ , WhileHover
+ , EventInfo
, OnTap
, TapInfo
, OnTapStart
@@ -80,6 +88,7 @@ import Data.Nullable (Nullable)
import Data.Symbol (class IsSymbol, SProxy, reflectSymbol)
import Effect (Effect)
import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, mkEffectFn2)
+import Foreign (Foreign, unsafeToForeign)
import Foreign.Object (Object)
import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmapWithIndex)
import Literals.Undefined (Undefined)
@@ -97,6 +106,7 @@ import Untagged.Castable (class Castable, cast)
import Untagged.Union (type (|+|))
import Web.DOM (Node)
import Web.Event.Internal.Types (Event)
+import Web.UIEvent.MouseEvent (MouseEvent)
import Yoga.Block.Internal (Id)
foreign import divImpl ∷ ∀ a. ReactComponent { | a }
@@ -216,6 +226,28 @@ onTap fn2 = cast (mkEffectFn2 fn2)
onTapCancel ∷ (Event -> TapInfo -> Effect Unit) -> OnTap
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 =
{ x ∷ Number, y ∷ Number }
@@ -250,9 +282,13 @@ onDragEnd = cast <<< toEffectFn
onDrag ∷ (Event -> PanInfo -> Effect Unit) -> OnDrag
onDrag fn2 = cast (mkEffectFn2 fn2)
+customProp ∷ ∀ a. a -> Foreign
+customProp = unsafeToForeign
+
type MotionPropsF f r =
( initial ∷ f Initial
, animate ∷ f Animate
+ , custom ∷ f Foreign
, drag ∷ f Drag
, dragMomentum ∷ f DragMomentum
, dragElastic ∷ f DragElastic
@@ -270,6 +306,9 @@ type MotionPropsF f r =
, onTapStart ∷ f OnTapStart
, onTapEnd ∷ f OnTapEnd
, onTapCancel ∷ f OnTapCancel
+ , whileHover ∷ f WhileHover
+ , onHoverStart ∷ f OnHoverStart
+ , onHoverEnd ∷ f OnHoverEnd
, exit ∷ f Exit
| r
)
diff --git a/src/React/Basic/Popper/Hook.purs b/src/React/Basic/Popper/Hook.purs
index 965d98b..308c9ee 100644
--- a/src/React/Basic/Popper/Hook.purs
+++ b/src/React/Basic/Popper/Hook.purs
@@ -7,43 +7,11 @@ import Effect.Uncurried (EffectFn3, runEffectFn3)
import Foreign.Object (Object)
import Foreign.Object as Object
import Prim.Row (class Union)
-import React.Basic.DOM (CSS)
-import Unsafe.Coerce (unsafeCoerce)
-import Yoga.Prelude.View (Hook, NodeRef, unsafeHook)
+import Yoga.Prelude.View (Hook, unsafeHook)
+import React.Basic.Popper.Types (Options, PopperData, PopperElement, ReferenceElement)
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
toDataAttributes ∷ Object String -> Object String
diff --git a/src/React/Basic/Popper/Story.purs b/src/React/Basic/Popper/Story.purs
index 7e17012..eb1edb4 100644
--- a/src/React/Basic/Popper/Story.purs
+++ b/src/React/Basic/Popper/Story.purs
@@ -1,27 +1,24 @@
module React.Basic.Popper.Story where
import Prelude
-import Color as Color
-import Data.Maybe (Maybe(..))
+import Data.Interpolate (i)
import Data.Nullable (null)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Uncurried (mkEffectFn1)
import Effect.Unsafe (unsafePerformEffect)
-import Framer.Motion (onTap)
import Framer.Motion as Motion
import React.Basic (JSX, element, fragment)
import React.Basic.DOM as R
import React.Basic.Emotion as E
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 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.Internal.CSS (nest)
-import Yoga.Prelude.View (NodeRef, el, styled, styledLeaf)
+import Yoga.Prelude.View (NodeRef, el, guard, handler, preventDefault, styled, styledLeaf)
default ∷
{ decorators ∷ Array (Effect JSX -> JSX)
@@ -136,6 +133,7 @@ animatedPopper = do
mkBasicExample =
React.reactComponent "Popper example" \p -> React.do
+ -- Hooks
referenceElement /\ setReferenceElement <- React.useState' nullRef
popperElement /\ setPopperElement <- React.useState' nullRef
arrowElement /\ setArrowElement <- React.useState' nullRef
@@ -143,63 +141,105 @@ animatedPopper = do
usePopper referenceElement popperElement
{ modifiers:
[ modifierArrow arrowElement
- , modifierOffset { x: 0.0, y: 8.0 }
+ , modifierOffset { x: 0.0, y: 12.0 }
]
}
- pure
- $ fragment
- $ [ element R.div'
- { ref: unsafeCoerce (mkEffectFn1 setReferenceElement)
- , children: [ R.text "Reference element" ]
- }
- , el Motion.animatePresence {}
- [ styled Motion.div
- { className: "popper-element"
- , key: "heinz"
- , css:
- E.css
- { background: E.str "darkslateblue"
- , borderRadius: E.str "8px"
- , 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
- }
+ on /\ setOn <- React.useState' true
+ -- Handlers
+ let buttonClicked = setOn (not on)
+ -- Elements
+ let
+ result =
+ fragment
+ $ [ refElem
+ , popperEl
+ [ animatePresence
+ $ guard on
+ [ content
+ [ el Block.box {} [ R.text "Ich bin Stinky Bill" ]
+ , 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" }
+ }
diff --git a/src/React/Basic/Popper/Types.purs b/src/React/Basic/Popper/Types.purs
new file mode 100644
index 0000000..8029260
--- /dev/null
+++ b/src/React/Basic/Popper/Types.purs
@@ -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
+ )
diff --git a/src/Yoga/Block/Atom/CodeInput/Style.purs b/src/Yoga/Block/Atom/CodeInput/Style.purs
index b2027b8..4b9d77a 100644
--- a/src/Yoga/Block/Atom/CodeInput/Style.purs
+++ b/src/Yoga/Block/Atom/CodeInput/Style.purs
@@ -21,7 +21,7 @@ codeInput props = styles <>? cast props.css
, background: str colour.inputBackground
, border: str $ i "solid 1px " colour.inputBorder
, borderRadius: str "var(--s-2)"
- , fontFamily: str "var(--monoFont)"
+ , fontFamily: str "var(--mono-font)"
, fontSize: str "var(--s0)"
, lineHeight: str "var(--s0)"
, width: str $ i "calc(" (cast props.maxLength ?|| 10) "ch + 4.3 * var(--s-5))"
diff --git a/src/Yoga/Block/Atom/Icon.purs b/src/Yoga/Block/Atom/Icon.purs
new file mode 100644
index 0000000..cad0bd3
--- /dev/null
+++ b/src/Yoga/Block/Atom/Icon.purs
@@ -0,0 +1,5 @@
+module Yoga.Block.Atom.Icon
+ ( module Yoga.Block.Atom.Icon.View
+ ) where
+
+import Yoga.Block.Atom.Icon.View (component, Props)
diff --git a/src/Yoga/Block/Atom/Icon/Story.purs b/src/Yoga/Block/Atom/Icon/Story.purs
new file mode 100644
index 0000000..7a7a3b4
--- /dev/null
+++ b/src/Yoga/Block/Atom/Icon/Story.purs
@@ -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 }
+ ]
+ ]
diff --git a/src/Yoga/Block/Atom/Icon/Style.purs b/src/Yoga/Block/Atom/Icon/Style.purs
new file mode 100644
index 0000000..f4fc117
--- /dev/null
+++ b/src/Yoga/Block/Atom/Icon/Style.purs
@@ -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")
+ }
+ }
diff --git a/src/Yoga/Block/Atom/Icon/View.purs b/src/Yoga/Block/Atom/Icon/View.purs
new file mode 100644
index 0000000..0253d70
--- /dev/null
+++ b/src/Yoga/Block/Atom/Icon/View.purs
@@ -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 ]
diff --git a/src/Yoga/Block/Atom/Input.purs b/src/Yoga/Block/Atom/Input.purs
new file mode 100644
index 0000000..4d19cfe
--- /dev/null
+++ b/src/Yoga/Block/Atom/Input.purs
@@ -0,0 +1,5 @@
+module Yoga.Block.Atom.Input
+ ( module Yoga.Block.Atom.Input.View
+ ) where
+
+import Yoga.Block.Atom.Input.View (component, Props)
diff --git a/src/Yoga/Block/Atom/Input/Story.purs b/src/Yoga/Block/Atom/Input/Story.purs
new file mode 100644
index 0000000..d52a268
--- /dev/null
+++ b/src/Yoga/Block/Atom/Input/Story.purs
@@ -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" }
+ ]
+ ]
diff --git a/src/Yoga/Block/Atom/Input/Style.purs b/src/Yoga/Block/Atom/Input/Style.purs
new file mode 100644
index 0000000..3bafc2a
--- /dev/null
+++ b/src/Yoga/Block/Atom/Input/Style.purs
@@ -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"
+ }
+ }
diff --git a/src/Yoga/Block/Atom/Input/View.purs b/src/Yoga/Block/Atom/Input/View.purs
new file mode 100644
index 0000000..86cc9bc
--- /dev/null
+++ b/src/Yoga/Block/Atom/Input/View.purs
@@ -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
+ ]
diff --git a/src/Yoga/Block/Atom/Range/Style.purs b/src/Yoga/Block/Atom/Range/Style.purs
index c669adf..89630a0 100644
--- a/src/Yoga/Block/Atom/Range/Style.purs
+++ b/src/Yoga/Block/Atom/Range/Style.purs
@@ -6,8 +6,6 @@ import Yoga.Block.Container.Style (colour)
type Props f r =
( css ∷ f Style
- , space ∷ f StyleProperty
- , splitAfter ∷ f Int
| r
)
diff --git a/src/Yoga/Block/Atom/Range/View.purs b/src/Yoga/Block/Atom/Range/View.purs
index 1fc7ccc..875b604 100644
--- a/src/Yoga/Block/Atom/Range/View.purs
+++ b/src/Yoga/Block/Atom/Range/View.purs
@@ -1,13 +1,15 @@
module Yoga.Block.Atom.Range.View (component, Props, PropsF) where
import Yoga.Prelude.View
-import Yoga.Block.Atom.Range.Style as Style
import Data.Int as Int
import Foreign.Object as Object
+import Framer.Motion as M
import React.Basic.DOM (css)
import React.Basic.DOM as R
import React.Basic.DOM.Events as Event
import React.Basic.Hooks as React
+import Yoga.Block.Atom.Range.Style as Style
+import Yoga.Block.Atom.Tooltip as Tooltip
type PropsF f =
( className ∷ f String
@@ -58,11 +60,11 @@ rawComponent =
{ className: "ry-range-focus-circle"
, 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
, css: Style.range props <> guard props.disabled Style.inputDisabled <>? props.css
- , style: props.style
- , value: show value
, type: "range"
, onChange: handler Event.targetValue ((_ >>= Int.fromString) >>> (foldMap setValue))
}
diff --git a/src/Yoga/Block/Atom/Toggle/Spec.purs b/src/Yoga/Block/Atom/Toggle/Spec.purs
index 1187545..cec98a0 100644
--- a/src/Yoga/Block/Atom/Toggle/Spec.purs
+++ b/src/Yoga/Block/Atom/Toggle/Spec.purs
@@ -2,6 +2,7 @@ module Yoga.Block.Atom.Toggle.Spec where
import Yoga.Prelude.Spec
import Yoga.Block.Atom.Toggle as Toggle
+import Yoga.Block.Atom.Toggle.Types (TogglePosition(..))
spec ∷ Spec Unit
spec =
@@ -10,6 +11,6 @@ spec =
it "renders without errors" do
void
$ renderComponent Toggle.component
- { value: true
- , onToggle: mempty
+ { togglePosition: ToggleIsLeft
+ , setTogglePosition: mempty
}
diff --git a/src/Yoga/Block/Atom/Toggle/Story.purs b/src/Yoga/Block/Atom/Toggle/Story.purs
index 9c8f062..291a40d 100644
--- a/src/Yoga/Block/Atom/Toggle/Story.purs
+++ b/src/Yoga/Block/Atom/Toggle/Story.purs
@@ -10,6 +10,7 @@ import React.Basic.DOM as R
import React.Basic.Hooks as React
import Yoga.Block as Block
import Yoga.Block.Atom.Toggle as Toggle
+import Yoga.Block.Atom.Toggle.Types (TogglePosition(..))
import Yoga.Block.Container.Style (DarkOrLightMode(..))
default ∷
@@ -35,30 +36,32 @@ toggle = do
where
mkBasicExample =
React.reactComponent "Toggle example" \p -> React.do
- isOn /\ turnOnOrOff <- React.useState' false
+ togglePosition /\ setTogglePosition <- React.useState' ToggleIsRight
pure
$ element Toggle.component
- { value: isOn
- , onToggle: turnOnOrOff
+ { togglePosition
+ , setTogglePosition
}
mkDarkLightToggle =
React.reactComponent "Toggle dark night example" \p -> React.do
- isOn /\ turnOnOrOff <- React.useState' false
+ togglePosition /\ setTogglePosition <- React.useState' ToggleIsLeft
theme /\ setTheme <- React.useState' Nothing
let
content =
element Toggle.component
- { value: isOn
- , onToggle:
- \b -> do
- turnOnOrOff b
- setTheme (Just if b then DarkMode else LightMode)
- , on: R.text "🌒"
- , off: R.text "🌞"
- , backgroundOn:
- Color.hsl 205.0 1.0 0.93
- , backgroundOff:
+ { togglePosition: togglePosition
+ , setTogglePosition:
+ \newTogglePosition -> do
+ setTogglePosition newTogglePosition
+ setTheme case newTogglePosition of
+ ToggleIsRight -> Just DarkMode
+ ToggleIsLeft -> Just LightMode
+ , left: R.text "🌒"
+ , right: R.text "🌞"
+ , backgroundLeft:
+ Color.hsl 205.0 1.0 0.83
+ , backgroundRight:
Color.hsl 260.0 0.7 0.45
}
pure
diff --git a/src/Yoga/Block/Atom/Toggle/Style.purs b/src/Yoga/Block/Atom/Toggle/Style.purs
index 9b1e861..93219e4 100644
--- a/src/Yoga/Block/Atom/Toggle/Style.purs
+++ b/src/Yoga/Block/Atom/Toggle/Style.purs
@@ -5,8 +5,8 @@ import Yoga.Block.Container.Style (colour)
type Props f r =
( css ∷ f Style
- , backgroundOn ∷ f Color
- , backgroundOff ∷ f Color
+ , backgroundLeft ∷ f Color
+ , backgroundRight ∷ f Color
| r
)
@@ -23,17 +23,17 @@ button =
, padding: _0
}
-theToggle ∷ ∀ p. { | Props OptionalProp p } -> Style
-theToggle props =
+theToggle ∷ Style
+theToggle =
css
- { width: var "--s2"
- , height: var "--s2"
+ { width: str "calc(var(--s2) * 0.8)"
+ , height: str "calc(var(--s2) * 0.8)"
, background: str $ colour.interfaceBackground
, border: none
, borderRadius: str $ "calc(var(--s2) / 2)"
, position: absolute
- , top: str "-1px"
- , left: _0
+ , top: str "3px"
+ , left: str "3px"
, margin: _0
, boxShadow: str "0 0.5px 3px rgba(0,0,0,0.50)"
}
@@ -46,25 +46,33 @@ toggleTextContainer =
, fontWeight: str "bold"
, position: absolute
, top: _0
+ , fontSize: str "calc(0.6 * var(--s1))"
, lineHeight: var "--s2"
, height: var "--s2"
, display: flex
}
+successTextColour ∷ StyleProperty
+successTextColour = str colour.successText
+
+disabledTextColour ∷ StyleProperty
+disabledTextColour = str colour.interfaceTextDisabled
+
toggleText ∷ Style
toggleText =
css
{ textAlign: str "left"
, margin: _0
+ , padding: _0
, width: str "50%"
, height: str "100%"
, display: flex
, justifyContent: center
, alignItems: center
- , color: str colour.interfaceTextDisabled
- , "&:first-child":
+ , color: disabledTextColour
+ , "& > *":
nest
- { color: str colour.successText
+ { color: successTextColour
}
}
diff --git a/src/Yoga/Block/Atom/Toggle/Types.purs b/src/Yoga/Block/Atom/Toggle/Types.purs
new file mode 100644
index 0000000..6fede17
--- /dev/null
+++ b/src/Yoga/Block/Atom/Toggle/Types.purs
@@ -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
diff --git a/src/Yoga/Block/Atom/Toggle/View.purs b/src/Yoga/Block/Atom/Toggle/View.purs
index 56b291d..b834405 100644
--- a/src/Yoga/Block/Atom/Toggle/View.purs
+++ b/src/Yoga/Block/Atom/Toggle/View.purs
@@ -3,26 +3,33 @@ module Yoga.Block.Atom.Toggle.View (component, MandatoryProps, Props, PropsF) wh
import Yoga.Prelude.View
import Color as Color
import Data.Interpolate (i)
+import Data.Maybe (isNothing)
import Effect.Class.Console as Console
+import Effect.Unsafe (unsafePerformEffect)
import Foreign.Object as Object
import Framer.Motion as Motion
+import Partial.Unsafe (unsafeCrashWith)
import React.Basic.DOM (css)
import React.Basic.DOM as R
import React.Basic.Emotion as Emotion
+import React.Basic.Hooks (reactComponent)
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.Types (TogglePosition(..), flipToggle)
import Yoga.Block.Container.Style (colour)
+import Yoga.Block.Icon.SVG as SVGIcon
type PropsF f =
( className ∷ f String
- , on ∷ f JSX
- , off ∷ f JSX
+ , left ∷ f JSX
+ , right ∷ f JSX
| Style.Props f (MandatoryProps InputProps)
)
type MandatoryProps r =
- ( value ∷ Boolean
- , onToggle ∷ Boolean -> Effect Unit
+ ( togglePosition ∷ TogglePosition
+ , setTogglePosition ∷ TogglePosition -> Effect Unit
| r
)
@@ -32,7 +39,7 @@ data TappingState
data DragState
= NotDragging
- | Dragging { startX ∷ Number }
+ | Dragging { startX ∷ Number, currentX ∷ Number }
| DragDone { startX ∷ Number, endX ∷ Number }
derive instance eqDragState ∷ Eq DragState
@@ -40,8 +47,8 @@ derive instance eqDragState ∷ Eq DragState
instance showDragState ∷ Show DragState where
show = case _ of
NotDragging -> "NotDragging"
- Dragging x -> "Dragging " <> (show x)
- DragDone x -> "DragDone " <> (show x)
+ Dragging x -> "Dragging " <> show x
+ DragDone x -> "DragDone " <> show x
type Props =
PropsF Id
@@ -57,147 +64,213 @@ rawComponent =
mkForwardRefComponent "Toggle" do
\(props ∷ { | PropsOptional }) ref -> React.do
let disabled = props.disabled
- tapState <- useRef TapNotAllowed
- dragState /\ setDragState <- React.useState' NotDragging
- maxLeft /\ setMaxLeft <- useState' 0.0
buttonRef <- useRef null
toggleRef <- useRef null
- leftState /\ setLeftState <- React.useState' 0.0
- let
- 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
+ tapState <- useRef TapNotAllowed
+ dragState /\ setDragState <- React.useState' NotDragging
useEffect dragState do
case dragState of
NotDragging -> mempty
- Dragging { startX } -> writeRef tapState TapNotAllowed
+ Dragging { startX, currentX } -> do
+ writeRef tapState TapNotAllowed
DragDone { startX, endX } -> do
maybeBbox <- getBoundingBoxFromRef buttonRef
for_ maybeBbox \bbox -> do
if endX - startX <= (bbox.left - startX) + (bbox.width / 2.0) then do
- props.onToggle false
+ props.setTogglePosition ToggleIsLeft
else do
- props.onToggle true
+ props.setTogglePosition ToggleIsRight
mempty
- pure
- $ styled Motion.button
+ let
+ 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"
- , css: Style.button <> guard props.disabled Style.inputDisabled
- , onFocus: handler_ $ setHasFocus true
- , onBlur: handler_ $ setHasFocus false
+ , css: Style.button
, transition: Motion.transition { type: "tween", duration: 0.33, ease: "easeOut" }
, variants: Motion.variants buttonVariants
- , animate: Motion.animate if props.value then buttonVariant.on else buttonVariant.off
- , value: show props.value
- , onClick: handler preventDefault \_ -> props.onToggle (not props.value)
+ , animate:
+ Motion.animate case props.togglePosition of
+ ToggleIsRight -> buttonVariant.right
+ ToggleIsLeft -> buttonVariant.left
+ , value: show props.togglePosition
+ , onClick: handler preventDefault \_ -> props.setTogglePosition (flipToggle props.togglePosition)
, style: props.style
, _data: Object.singleton "testid" "toggle-testid"
, role: "switch"
, _aria: Object.singleton "checked" "switch"
, ref: buttonRef
}
- [ styled R.div'
- { className: "ry-toggle-text"
- , css: Style.toggleTextContainer
- }
- [ styled R.div'
- { className: "ry-toggle-text-on"
- , css: Style.toggleText
- }
- [ el Motion.animatePresence {}
- [ guard (props.value)
- $ styled Motion.div
- { className: "ry-toggle-text-on-container"
- , css: Style.toggleOnText
- , key: "ry-toggle-text-on-container"
- , initial: Motion.initial $ css { scale: 0, opacity: 0 }
- , animate: Motion.animate $ css { scale: 1, opacity: 1 }
- , exit: Motion.exit $ css { scale: 0, opacity: 0 }
- }
- [ props.on ?|| R.text "I" ]
- ]
- ]
- , styled R.div'
- { className: "ry-toggle-text-off"
- , css: Style.toggleText
- }
- [ el Motion.animatePresence {}
- [ guard (not props.value)
- $ styled Motion.div
- { className: "ry-toggle-text-on-container"
- , css: Style.toggleOnText
- , key: "ry-toggle-text-on-container"
- , initial: Motion.initial $ css { scale: 0, opacity: 0 }
- , animate: Motion.animate $ css { scale: 1, opacity: 1 }
- , exit: Motion.exit $ css { scale: 0, opacity: 0 }
- }
- [ props.off ?|| R.text "O" ]
- ]
- ]
- ]
- , styled Motion.div
+ textContainer =
+ styled R.div'
+ { className: "ry-toggle-text"
+ , css: Style.toggleTextContainer
+ }
+ textOnContainer =
+ styled R.div'
+ { className: "ry-toggle-text-on"
+ , css: Style.toggleText
+ }
+ textOffContainer =
+ styled R.div'
+ { className: "ry-toggle-text-off"
+ , css: Style.toggleText
+ }
+ textOn =
+ el Motion.div
+ { className: "ry-toggle-text-container"
+ , key: "ry-toggle-text-on-container"
+ , initial: Motion.initial $ css { scale: 0, opacity: 0 }
+ , animate: Motion.animate $ css { scale: 1, opacity: 1 }
+ , exit: Motion.exit $ css { scale: 0, opacity: 0 }
+ }
+ textOff =
+ el Motion.div
+ { className: "ry-toggle-text-container"
+ , key: "ry-toggle-text-off-container"
+ , initial: Motion.initial $ css { scale: 0, opacity: 0 }
+ , animate: Motion.animate $ css { scale: 1, opacity: 1 }
+ , exit: Motion.exit $ css { scale: 0, opacity: 0 }
+ }
+ animateTextPresence = el Motion.animatePresence {}
+ toggle =
+ el_ toggleCircle
+ { buttonRef
+ , toggleRef
+ , togglePosition: props.togglePosition
+ , setTogglePosition: props.setTogglePosition
+ , dragState
+ , 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"
- , css: Style.theToggle props
, layout: Motion.layout true
, onClick: handler stopPropagation mempty
+ , onTouchStart: handler stopPropagation mempty
+ , onTouchEnd: handler stopPropagation mempty
+ , css: Style.theToggle
, drag: Motion.drag "x"
, dragMomentum: Motion.dragMomentum false
+ , key: if isNothing maxLeft then "initialising" else "ready"
, dragElastic: Motion.dragElastic false
, dragConstraints:
Motion.dragConstraints
- { left: if props.value then negate maxLeft else zero
- , right: if props.value then zero else maxLeft
+ { left:
+ 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
, 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
, onTap:
Motion.onTap \_ pi -> do
ts <- readRef tapState
case ts of
- TapAllowed -> props.onToggle $ not props.value
+ TapAllowed -> setTogglePosition (flipToggle togglePosition)
_ -> mempty
mempty
, onTapCancel:
Motion.onTapCancel \_ pi -> do
writeRef tapState TapNotAllowed
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:
Motion.onDragStart \_ pi -> do
maybeBBox <- getBoundingBoxFromRef toggleRef
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:
Motion.onDragEnd \_ pi -> do
case dragState of
Dragging { startX } -> do
maybeBBox <- getBoundingBoxFromRef toggleRef
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"
, ref: toggleRef
}
- []
- ]
diff --git a/src/Yoga/Block/Atom/Tooltip.purs b/src/Yoga/Block/Atom/Tooltip.purs
new file mode 100644
index 0000000..d4b1f97
--- /dev/null
+++ b/src/Yoga/Block/Atom/Tooltip.purs
@@ -0,0 +1,5 @@
+module Yoga.Block.Atom.Tooltip
+ ( module Yoga.Block.Atom.Tooltip.View
+ ) where
+
+import Yoga.Block.Atom.Tooltip.View (component, Props)
diff --git a/src/Yoga/Block/Atom/Tooltip/Spec.purs b/src/Yoga/Block/Atom/Tooltip/Spec.purs
new file mode 100644
index 0000000..5cd3801
--- /dev/null
+++ b/src/Yoga/Block/Atom/Tooltip/Spec.purs
@@ -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"
+ }
diff --git a/src/Yoga/Block/Atom/Tooltip/Story.purs b/src/Yoga/Block/Atom/Tooltip/Story.purs
new file mode 100644
index 0000000..171bf51
--- /dev/null
+++ b/src/Yoga/Block/Atom/Tooltip/Story.purs
@@ -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" ]
+ }
+ ]
+ ]
diff --git a/src/Yoga/Block/Atom/Tooltip/Style.purs b/src/Yoga/Block/Atom/Tooltip/Style.purs
new file mode 100644
index 0000000..b6ced34
--- /dev/null
+++ b/src/Yoga/Block/Atom/Tooltip/Style.purs
@@ -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" }
+ }
diff --git a/src/Yoga/Block/Atom/Tooltip/View.purs b/src/Yoga/Block/Atom/Tooltip/View.purs
new file mode 100644
index 0000000..27b7e8c
--- /dev/null
+++ b/src/Yoga/Block/Atom/Tooltip/View.purs
@@ -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
diff --git a/src/Yoga/Block/Container/Style.purs b/src/Yoga/Block/Container/Style.purs
index b18289b..20445a5 100644
--- a/src/Yoga/Block/Container/Style.purs
+++ b/src/Yoga/Block/Container/Style.purs
@@ -50,7 +50,7 @@ mkGlobal maybeMode =
, body:
nested
$ css
- { fontFamily: str "var(--mainFont)"
+ { fontFamily: str "var(--main-font)"
, background: str colour.background0
, color: str colour.text
, margin: str "0"
@@ -61,7 +61,7 @@ mkGlobal maybeMode =
Just LightMode -> lightModeStyle
, "pre,code":
nest
- { fontFamily: str "var(--monoFont)"
+ { fontFamily: str "var(--mono-font)"
}
, "h1,h2,h3,h4,h5":
nest
@@ -76,7 +76,6 @@ mkGlobal maybeMode =
$ css
{ boxSizing: str "inherit"
}
- , input
, form:
nested
$ css
@@ -155,7 +154,7 @@ defaultColours =
-- highlight = Color.rgb 0x10 0x45 0x4A
darkBg = Color.rgb 0 0 0
- lightBg = Color.rgb 255 255 255
+ lightBg = Color.rgb 250 250 250
type FlatTheme a =
{ background0 ∷ a
@@ -262,22 +261,8 @@ variables =
fontVariables ∷ { main ∷ String, mono ∷ String } -> Style
fontVariables { main, mono } =
css
- { "--mainFont": 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"
- }
-
-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
+ { "--main-font": str $ main <> """, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol""""
+ , "--mono-font": str $ mono <> ", monospace, monospace"
}
-- Standalone style for storybook
diff --git a/src/Yoga/Block/Icon/SVG/EyeClosed.purs b/src/Yoga/Block/Icon/SVG/EyeClosed.purs
new file mode 100644
index 0000000..5775e53
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/EyeClosed.purs
@@ -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"
+ }
+ ]
+ }
diff --git a/src/Yoga/Block/Icon/SVG/EyeOpen.purs b/src/Yoga/Block/Icon/SVG/EyeOpen.purs
new file mode 100644
index 0000000..ab7bedc
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/EyeOpen.purs
@@ -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"
+ }
+ ]
+ }
diff --git a/src/Yoga/Block/Icon/SVG/Icon.purs b/src/Yoga/Block/Icon/SVG/Icon.purs
new file mode 100644
index 0000000..48110ee
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/Icon.purs
@@ -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)
diff --git a/src/Yoga/Block/Icon/SVG/MagnifyingGlass.purs b/src/Yoga/Block/Icon/SVG/MagnifyingGlass.purs
new file mode 100644
index 0000000..4f78c84
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/MagnifyingGlass.purs
@@ -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"
+ }
+ ]
+ }
diff --git a/src/Yoga/Block/Icon/SVG/Off.purs b/src/Yoga/Block/Icon/SVG/Off.purs
new file mode 100644
index 0000000..939ca9c
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/Off.purs
@@ -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"
+ }
+ ]
+ }
diff --git a/src/Yoga/Block/Icon/SVG/On.purs b/src/Yoga/Block/Icon/SVG/On.purs
new file mode 100644
index 0000000..091fcfc
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/On.purs
@@ -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"
+ }
+ ]
+ }
+ ]
+ }
diff --git a/src/Yoga/Block/Icon/SVG/QuestionMark.purs b/src/Yoga/Block/Icon/SVG/QuestionMark.purs
new file mode 100644
index 0000000..eda17cf
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/QuestionMark.purs
@@ -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"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/src/Yoga/Block/Icon/SVG/eyeClosed.svg b/src/Yoga/Block/Icon/SVG/eyeClosed.svg
new file mode 100644
index 0000000..6b23a08
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/eyeClosed.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Yoga/Block/Icon/SVG/eyeOpen.svg b/src/Yoga/Block/Icon/SVG/eyeOpen.svg
new file mode 100644
index 0000000..d955416
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/eyeOpen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Yoga/Block/Icon/SVG/magnifyingGlass.svg b/src/Yoga/Block/Icon/SVG/magnifyingGlass.svg
new file mode 100644
index 0000000..b24ce0b
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/magnifyingGlass.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Yoga/Block/Icon/SVG/off.svg b/src/Yoga/Block/Icon/SVG/off.svg
new file mode 100644
index 0000000..061a6b9
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/off.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Yoga/Block/Icon/SVG/on.svg b/src/Yoga/Block/Icon/SVG/on.svg
new file mode 100644
index 0000000..9d59632
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/on.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Yoga/Block/Icon/SVG/questionMark.svg b/src/Yoga/Block/Icon/SVG/questionMark.svg
new file mode 100644
index 0000000..13f939a
--- /dev/null
+++ b/src/Yoga/Block/Icon/SVG/questionMark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Yoga/Prelude/Default.purs b/src/Yoga/Prelude/Default.purs
index d76de60..cb01c73 100644
--- a/src/Yoga/Prelude/Default.purs
+++ b/src/Yoga/Prelude/Default.purs
@@ -11,6 +11,7 @@ module Yoga.Prelude.Default
, module Data.Foldable
, module Data.FoldableWithIndex
, module Data.FunctorWithIndex
+ , runMaybeT_
) where
import Prelude
@@ -25,3 +26,6 @@ import Data.Maybe (Maybe(..), fromMaybe, fromMaybe', isJust, maybe)
import Data.Monoid (guard)
import Effect (Effect)
import Effect.Class (liftEffect)
+
+runMaybeT_ ∷ ∀ f a. Functor f => MaybeT f a -> f Unit
+runMaybeT_ = void <<< runMaybeT
diff --git a/yarn.lock b/yarn.lock
index b819422..8322ad4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4854,25 +4854,23 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"
-framer-motion@^2.7.0:
- version "2.9.5"
- resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-2.9.5.tgz#bbb185325d531c57f494cf3f6cf7719fc2c225c7"
- integrity sha512-epSX4Co1YbDv0mjfHouuY0q361TpHE7WQzCp/xMTilxy4kXd+Z23uJzPVorfzbm1a/9q1Yu8T5bndaw65NI4Tg==
+framer-motion@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-3.1.1.tgz#a8a779501213b7ce02cc35beb27621d73cc2f1e7"
+ integrity sha512-Gm1QSb0xUxuhcPar5FIs5Ws+STrhLZ6XZf2Io8dVwFofe1OzwkL9asGFVu7z3y6WqC4Hvnxm7wsW5SBHlxZDYw==
dependencies:
- framesync "^4.1.0"
+ framesync "^5.0.0"
hey-listen "^1.0.8"
- popmotion "9.0.0-rc.20"
- style-value-types "^3.1.9"
+ popmotion "^9.0.2"
+ style-value-types "^3.2.0"
tslib "^1.10.0"
optionalDependencies:
"@emotion/is-prop-valid" "^0.8.2"
-framesync@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/framesync/-/framesync-4.1.0.tgz#69a8db3ca432dc70d6a76ba882684a1497ef068a"
- integrity sha512-MmgZ4wCoeVxNbx2xp5hN/zPDCbLSKiDt4BbbslK7j/pM2lg5S0vhTNv1v8BCVb99JPIo6hXBFdwzU7Q4qcAaoQ==
- dependencies:
- hey-listen "^1.0.5"
+framesync@5.0.0, framesync@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.0.0.tgz#7de8caedf53ac441118e79680f1beb7391c328b6"
+ integrity sha512-wd8t+JsQGisluSv1twiEeDv0aNGpavGb9q7xgIk9fGbcIWkNXF/KVtrjnOrCwBWJuiXxlJfNkcvGudsI32FxYA==
fresh@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"
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"
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
@@ -7489,14 +7487,14 @@ polished@^3.4.4:
dependencies:
"@babel/runtime" "^7.9.2"
-popmotion@9.0.0-rc.20:
- version "9.0.0-rc.20"
- resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.0-rc.20.tgz#f3550042ae31957b5416793ae8723200951ad39d"
- integrity sha512-f98sny03WuA+c8ckBjNNXotJD4G2utG/I3Q23NU69OEafrXtxxSukAaJBxzbtxwDvz3vtZK69pu9ojdkMoBNTg==
+popmotion@^9.0.2:
+ version "9.0.2"
+ resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.2.tgz#477650c3b4af97161011809223d9ca6860f3a2b5"
+ integrity sha512-WfSg8IfoUwYIP9uqeqbgncIsMHLAKWqebT2IP1aGAI6gdSJqTPy/H8NvP4ZyDtDCUCx5Yh3Pth/7iUJjIwR7LA==
dependencies:
- framesync "^4.1.0"
+ framesync "5.0.0"
hey-listen "^1.0.8"
- style-value-types "^3.1.9"
+ style-value-types "3.2.0"
tslib "^1.10.0"
portfinder@^1.0.26:
@@ -9087,10 +9085,10 @@ style-loader@^1.2.1:
loader-utils "^2.0.0"
schema-utils "^2.7.0"
-style-value-types@^3.1.9:
- version "3.1.9"
- resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.1.9.tgz#faf7da660d3f284ed695cff61ea197d85b9122cc"
- integrity sha512-050uqgB7WdvtgacoQKm+4EgKzJExVq0sieKBQQtJiU3Muh6MYcCp4T3M8+dfl6VOF2LR0NNwXBP1QYEed8DfIw==
+style-value-types@3.2.0, style-value-types@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.2.0.tgz#eb89cab1340823fa7876f3e289d29d99c92111bb"
+ integrity sha512-ih0mGsrYYmVvdDi++/66O6BaQPRPRMQHoZevNNdMMcPlP/cH28Rnfsqf1UEba/Bwfuw9T8BmIMwbGdzsPwQKrQ==
dependencies:
hey-listen "^1.0.8"
tslib "^1.10.0"