From c32928933fe49d41ed2b7bea4c0712021867c1cc Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Sun, 20 Dec 2020 23:44:24 +0100 Subject: [PATCH] Input work --- .psci_modules/index.js | 1 + convert-more.fish | 8 + convert-svgs.js | 4 +- package.json | 2 +- packages.dhall | 2 +- spago.dhall | 1 + src/Framer/Motion.purs | 39 +++ src/React/Basic/Popper/Hook.purs | 36 +-- src/React/Basic/Popper/Story.purs | 166 +++++++----- src/React/Basic/Popper/Types.purs | 40 +++ src/Yoga/Block/Atom/CodeInput/Style.purs | 2 +- src/Yoga/Block/Atom/Icon.purs | 5 + src/Yoga/Block/Atom/Icon/Story.purs | 37 +++ src/Yoga/Block/Atom/Icon/Style.purs | 27 ++ src/Yoga/Block/Atom/Icon/View.purs | 32 +++ src/Yoga/Block/Atom/Input.purs | 5 + src/Yoga/Block/Atom/Input/Story.purs | 56 ++++ src/Yoga/Block/Atom/Input/Style.purs | 91 +++++++ src/Yoga/Block/Atom/Input/View.purs | 142 ++++++++++ src/Yoga/Block/Atom/Range/Style.purs | 2 - src/Yoga/Block/Atom/Range/View.purs | 10 +- src/Yoga/Block/Atom/Toggle/Spec.purs | 5 +- src/Yoga/Block/Atom/Toggle/Story.purs | 31 ++- src/Yoga/Block/Atom/Toggle/Style.purs | 30 ++- src/Yoga/Block/Atom/Toggle/Types.purs | 19 ++ src/Yoga/Block/Atom/Toggle/View.purs | 265 ++++++++++++------- src/Yoga/Block/Atom/Tooltip.purs | 5 + src/Yoga/Block/Atom/Tooltip/Spec.purs | 16 ++ src/Yoga/Block/Atom/Tooltip/Story.purs | 39 +++ src/Yoga/Block/Atom/Tooltip/Style.purs | 57 ++++ src/Yoga/Block/Atom/Tooltip/View.purs | 129 +++++++++ src/Yoga/Block/Container/Style.purs | 25 +- src/Yoga/Block/Icon/SVG/EyeClosed.purs | 32 +++ src/Yoga/Block/Icon/SVG/EyeOpen.purs | 44 +++ src/Yoga/Block/Icon/SVG/Icon.purs | 13 + src/Yoga/Block/Icon/SVG/MagnifyingGlass.purs | 36 +++ src/Yoga/Block/Icon/SVG/Off.purs | 30 +++ src/Yoga/Block/Icon/SVG/On.purs | 41 +++ src/Yoga/Block/Icon/SVG/QuestionMark.purs | 25 ++ src/Yoga/Block/Icon/SVG/eyeClosed.svg | 1 + src/Yoga/Block/Icon/SVG/eyeOpen.svg | 1 + src/Yoga/Block/Icon/SVG/magnifyingGlass.svg | 1 + src/Yoga/Block/Icon/SVG/off.svg | 1 + src/Yoga/Block/Icon/SVG/on.svg | 1 + src/Yoga/Block/Icon/SVG/questionMark.svg | 1 + src/Yoga/Prelude/Default.purs | 4 + yarn.lock | 46 ++-- 47 files changed, 1331 insertions(+), 275 deletions(-) create mode 100644 .psci_modules/index.js create mode 100644 convert-more.fish create mode 100644 src/React/Basic/Popper/Types.purs create mode 100644 src/Yoga/Block/Atom/Icon.purs create mode 100644 src/Yoga/Block/Atom/Icon/Story.purs create mode 100644 src/Yoga/Block/Atom/Icon/Style.purs create mode 100644 src/Yoga/Block/Atom/Icon/View.purs create mode 100644 src/Yoga/Block/Atom/Input.purs create mode 100644 src/Yoga/Block/Atom/Input/Story.purs create mode 100644 src/Yoga/Block/Atom/Input/Style.purs create mode 100644 src/Yoga/Block/Atom/Input/View.purs create mode 100644 src/Yoga/Block/Atom/Toggle/Types.purs create mode 100644 src/Yoga/Block/Atom/Tooltip.purs create mode 100644 src/Yoga/Block/Atom/Tooltip/Spec.purs create mode 100644 src/Yoga/Block/Atom/Tooltip/Story.purs create mode 100644 src/Yoga/Block/Atom/Tooltip/Style.purs create mode 100644 src/Yoga/Block/Atom/Tooltip/View.purs create mode 100644 src/Yoga/Block/Icon/SVG/EyeClosed.purs create mode 100644 src/Yoga/Block/Icon/SVG/EyeOpen.purs create mode 100644 src/Yoga/Block/Icon/SVG/Icon.purs create mode 100644 src/Yoga/Block/Icon/SVG/MagnifyingGlass.purs create mode 100644 src/Yoga/Block/Icon/SVG/Off.purs create mode 100644 src/Yoga/Block/Icon/SVG/On.purs create mode 100644 src/Yoga/Block/Icon/SVG/QuestionMark.purs create mode 100644 src/Yoga/Block/Icon/SVG/eyeClosed.svg create mode 100644 src/Yoga/Block/Icon/SVG/eyeOpen.svg create mode 100644 src/Yoga/Block/Icon/SVG/magnifyingGlass.svg create mode 100644 src/Yoga/Block/Icon/SVG/off.svg create mode 100644 src/Yoga/Block/Icon/SVG/on.svg create mode 100644 src/Yoga/Block/Icon/SVG/questionMark.svg 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"