git.iko.soy/rowtype-yoga/ry-blocks
on main help sign in
Add flat buttons and ripple option
commit85c9ecffa183f1e977248785ed9362c12b582d1d authorMark Eibes <mark.eibes@gmail.com> dateSun 26 Jun 2022, 21:51 (4 years ago) parentc6daa29
12 files changed+270−18
.vscode/settings.json+1−1 spago.dhall+1 src/Yoga/Block/Atom/Button/Story.purs+7−4 src/Yoga/Block/Atom/Button/Style.purs+22−7 src/Yoga/Block/Atom/Button/Types.purs+2 src/Yoga/Block/Atom/Button/View.purs+55−4 src/Yoga/Block/Container/Style.purs+4−1 src/Yoga/Block/Hook/UseDrip.purs+54 src/Yoga/Block/Molecule/Sheet/Story.purs+1−1 src/Yoga/Block/Quark/Drip.purs+1 src/Yoga/Block/Quark/Drip/Style.purs+33 src/Yoga/Block/Quark/Drip/View.purs+89
.vscode/settings.json+1−1
@@ -1,6 +1,6 @@
11{
22 "editor.formatOnSave": true,
3− "workbench.colorTheme": "Irul Theme",
3+ "workbench.colorTheme": "Night Owl Light",
44 "search.useGlobalIgnoreFiles": true,
55 "files.watcherExclude": {
66 "**/.spago/**": true,
spago.dhall+1
@@ -10,6 +10,7 @@ You can edit this file as you like.
1010 , "console"
1111 , "control"
1212 , "datetime"
13+ , "debug"
1314 , "effect"
1415 , "either"
1516 , "enums"
src/Yoga/Block/Atom/Button/Story.purs+7−4
@@ -2,6 +2,7 @@ module Yoga.Block.Atom.Button.Story (default, button) where
22
33import Prelude hiding (div)
44
5+import Color (cssStringRGBA)
56import Effect (Effect)
67import Effect.Unsafe (unsafePerformEffect)
78import Fahrtwind as F
@@ -43,13 +44,15 @@ button = do
4344 [ Block.button { buttonType: ButtonType.Generic } [ R.text "Generic" ]
4445 , Block.button { buttonType: ButtonType.Primary } [ R.text "Primary" ]
4546 , Block.button { buttonType: ButtonType.Dangerous } [ R.text "Dangerous" ]
46− , Block.button { css: customStyle } [ R.text "Custom" ]
47+ , Block.button { css: customStyle, ripple: F.emerald._500 # cssStringRGBA } [ R.text "Custom" ]
4748 , Block.button { css: customStyle2 } [ R.text "Custom 2" ]
4849 ]
4950 , R.h2_ [ R.text "Button shapes" ]
5051 , Block.cluster { space: "var(--s-1)" }
51− [ Block.button { buttonType: ButtonType.Generic, buttonShape: ButtonType.Pill } [ R.text "Generic" ]
52− , Block.button { buttonType: ButtonType.Primary, buttonShape: ButtonType.Pill } [ R.text "Primary" ]
52+ [ Block.button { buttonType: ButtonType.Generic, buttonShape: ButtonType.Pill } [ R.text "Generic Pill" ]
53+ , Block.button { buttonType: ButtonType.Primary, buttonShape: ButtonType.Pill } [ R.text "Generic Primary" ]
54+ , Block.button { buttonType: ButtonType.Generic, buttonShape: ButtonType.Flat } [ R.text "Flat Pill" ]
55+ , Block.button { buttonType: ButtonType.Primary, buttonShape: ButtonType.Flat } [ R.text "Flat Primary" ]
5356 , Block.button { css: customStyle, buttonShape: ButtonType.Pill } [ R.text "+ Custom" ]
5457 ]
5558 , R.h2_ [ R.text "Icon button" ]
@@ -101,7 +104,7 @@ customStyle2 =
101104 E.css
102105 { background: E.color F.green._500
103106 , color: E.str "white"
104− , border: E.str "1px solid transparent"
107+ , border: E.none
105108 , fontWeight: E.str "400"
106109 , minWidth: E.str size."3xl"
107110 , "&:focus":
src/Yoga/Block/Atom/Button/Style.purs+22−7
@@ -3,7 +3,8 @@ module Yoga.Block.Atom.Button.Style where
33import Yoga.Prelude.Style
44
55import Data.Interpolate (i)
6−import Yoga.Block.Container.Style (col, colour, darkMode, size)
6+import Fahrtwind (zIndex)
7+import Yoga.Block.Container.Style (col, colour, size)
78
89type Props :: forall k. (Type -> k) -> Row k -> Row k
910type Props f r
@@ -37,10 +38,10 @@ button ∷ Style
3738button =
3839 css
3940 { background: str colour.backgroundLayer5
41+ , borderWidth: int 0
42+ , position: relative
43+ , overflow: hidden
4044 , boxShadow: str "0 1px 4px rgba(100,100,120,0.3), 0 1px 2px rgba(100,100,100,0.2)"
41− , border: str $ i "1px solid transparent"
42− , borderTop: str $ i "1px solid " colour.backgroundLayer5
43− , borderBottom: str $ i "1px solid " colour.backgroundLayer3
4445 , display: inlineFlex
4546 , padding: str "calc(var(--s-1) * 0.8) var(--s0)"
4647 , paddingBottom: str "calc(var(--s-1) * 0.8 + 1px)"
@@ -56,6 +57,23 @@ button =
5657 , letterSpacing: str "calc(var(--s-5)* (-0.1))"
5758 , userSelect: none
5859 , transition: str "all 0.2s ease-out" <> str "transform 50ms ease-in"
60+ , "& > .ry-drip": nested (zIndex 0)
61+ , "& > :not(.ry-drip)": nested (zIndex 1)
62+ , """&[data-button-shape="flat"]""":
63+ nest
64+ { background: str "transparent"
65+ , boxShadow: none
66+ , color: str colour.highlight
67+ , "&:active":
68+ nest
69+ { boxShadow: none
70+ }
71+ , """&[data-button-type="primary"]""":
72+ nest {
73+ background: str colour.highlight,
74+ boxShadow: none
75+ }
76+ }
5977 , """&[data-button-shape="pill"]""":
6078 nest
6179 { borderRadius: str "calc(var(--s1) * 0.85)"
@@ -79,7 +97,6 @@ button =
7997 , "&:active":
8098 nest
8199 { boxShadow: str "inset 0 1px 6px rgba(0,0,0,0.40)"
82− , border: str $ i "1px solid transparent"
83100 }
84101 }
85102 , """&[data-button-type="dangerous"]""":
@@ -97,7 +114,6 @@ button =
97114 , "&:active":
98115 nest
99116 { boxShadow: str $ "inset 0 1px calc(var(--s0) * var(--dark-mode) + var(--s-2) * var(--light-mode)) rgba(0,0,0, calc(0.18 * var(--dark-mode) + 0.09 * var(--light-mode)))"
100− , border: str $ "1px solid " <> colour.backgroundBright5
101117 , transform: str "scale3d(0.96,0.96,0.36)"
102118 , transition: str "transform 100ms ease"
103119 }
@@ -106,7 +122,6 @@ button =
106122 { color: str colour.interfaceTextDisabled
107123 , boxShadow: none
108124 , background: str colour.interfaceBackgroundDisabled
109− , border: str $ i "1px solid " colour.interfaceBackgroundShadow
110125 , transform: str "none"
111126 }
112127 }
src/Yoga/Block/Atom/Button/Types.purs+2
@@ -18,6 +18,7 @@ renderButtonType = case _ of
1818data ButtonShape
1919 = Rounded
2020 | Pill
21+ | Flat
2122
2223derive instance eqButtonShape ∷ Eq ButtonShape
2324
@@ -25,3 +26,4 @@ renderButtonShape ∷ ButtonShape -> String
2526renderButtonShape = case _ of
2627 Rounded -> "rounded"
2728 Pill -> "pill"
29+ Flat -> "flat"
src/Yoga/Block/Atom/Button/View.purs+55−4
@@ -2,16 +2,25 @@ module Yoga.Block.Atom.Button.View where
22
33import Yoga.Prelude.View
44
5+import Data.Array as Array
6+import Effect.Uncurried (runEffectFn1)
57import Foreign.Object as Object
8+import React.Basic.Hooks as React
9+import Record.Unsafe (unsafeGet, unsafeSet)
10+import Untagged.Union (uorToMaybe)
611import Yoga.Block.Atom.Button.Style as Style
712import Yoga.Block.Atom.Button.Types (ButtonShape, ButtonType, renderButtonShape, renderButtonType)
813import Yoga.Block.Atom.Button.Types as Button
14+import Yoga.Block.Container.Style (colour)
15+import Yoga.Block.Hook.UseDrip (useDrip)
916import Yoga.Block.Internal (ButtonWritablePropsNoChildrenF)
17+import Yoga.Block.Quark.Drip.View as Drip
1018
1119type PropsF :: forall k. (Type -> k) -> Row k -> Row k
1220type PropsF f r =
1321 ( buttonType ∷ f ButtonType
1422 , buttonShape ∷ f ButtonShape
23+ , ripple ∷ f String
1524 | Style.Props f r
1625 )
1726
@@ -31,15 +40,57 @@ rawComponent ∷ ∀ p. ReactComponent { | p }
3140rawComponent =
3241 mkForwardRefComponent "Button" do
3342 \(props ∷ { | PropsOptional }) propsRef -> React.do
43+ backupRef <- React.useRef null
44+ let ref = forwardedRefAsMaybe propsRef # fromMaybe backupRef
45+ let buttonType = props.buttonType ?|| Button.Generic
46+ let buttonShape = props.buttonShape ?|| Button.Rounded
47+ let
48+ rippleʔ = props.ripple # opToMaybe # case buttonShape of
49+ Button.Flat -> case buttonType of
50+ Button.Primary ->
51+ fromMaybe colour.highlightLighter >>> Just
52+ _ -> fromMaybe colour.highlightAlpha50 >>> Just
53+ _ -> identity
54+ dripValues <- useDrip ref
55+ let propsChildren = unsafeGet "children" props # reactChildrenToArray
56+ let
57+ children = reactChildrenFromArray case rippleʔ of
58+ Nothing -> propsChildren
59+ Just rippleColour -> do
60+ Array.cons
61+ ( Drip.component
62+ </>
63+ ( { className: "ry-button-drip"
64+ , visible: dripValues.visible
65+ , x: dripValues.x
66+ , y: dripValues.y
67+ , onComplete: dripValues.onComplete
68+ , colour: rippleColour
69+ }
70+ )
71+ )
72+ propsChildren
73+
74+ let
75+ onClick = case unsafeGet "onClick" props # uorToMaybe of
76+ Just givenHandler -> handler syntheticEvent \e -> do
77+ void $ runEffectFn1 givenHandler e
78+ void $ runEffectFn1 dripValues.onClick e
79+
80+ Nothing -> dripValues.onClick
81+ let
82+ props' = props
83+ # unsafeSet "onClick" onClick
84+ # unsafeSet "children" (children)
3485 let
3586 _data =
3687 Object.fromHomogeneous
37− { "button-type": renderButtonType (props.buttonType ?|| Button.Generic)
38− , "button-shape": renderButtonShape (props.buttonShape ?|| Button.Rounded)
88+ { "button-type": renderButtonType buttonType
89+ , "button-shape": renderButtonShape buttonShape
3990 }
4091 pure
41− $ emotionButton propsRef
42− props
92+ $ emotionButton ref
93+ props'
4394 { className: "ry-button"
4495 , css: Style.button <>? props.css
4596 , _data
src/Yoga/Block/Container/Style.purs+4−1
@@ -278,6 +278,7 @@ defaultColours =
278278 , link
279279 , placeholderText: lighten 0.35 darkBg # desaturate 0.1
280280 , required
281+ , ripple: lightBg # darken 0.1
281282 , success
282283 , successText
283284 , text: text
@@ -340,6 +341,7 @@ defaultColours =
340341 , link: linkDark
341342 , placeholderText: darken 0.4 white
342343 , required
344+ , ripple: lighten 0.31 >>> saturate 0.00 $ darkBg
343345 , success: successDark
344346 , successText
345347 , text: textDark
@@ -367,7 +369,7 @@ defaultColours =
367369
368370 highlightDarkBase = Color.hsla 265.0 1.00 0.6
369371
370− highlightDark = Color.hsla 265.0 1.00 0.57 1.0
372+ highlightDark = Color.hsla 262.0 1.00 0.63 1.0
371373
372374 highlightText = Color.rgb 0xFF 0xFF 0xFF
373375
@@ -463,6 +465,7 @@ type FlatTheme a =
463465 , successText ∷ a
464466 , invalid ∷ a
465467 , invalidText ∷ a
468+ , ripple ∷ a
466469 , required ∷ a
467470 , text ∷ a
468471 , textPaler1 ∷ a
src/Yoga/Block/Hook/UseDrip.pursnew file+54
@@ -0,0 +1,54 @@
1+module Yoga.Block.Hook.UseDrip where
2+
3+import Yoga.Prelude.View
4+
5+import Data.Newtype (class Newtype)
6+import React.Basic.DOM.Events (clientX, clientY)
7+import React.Basic.Hooks as React
8+
9+type DripReturn =
10+ { visible :: Boolean
11+ , x :: Number
12+ , y :: Number
13+ , onClick :: EventHandler
14+ , onComplete :: Effect Unit
15+ }
16+
17+newtype UseDrip hooks = UseDrip
18+ (UseState Number (UseState Number (UseState Boolean hooks)))
19+
20+derive instance Newtype (UseDrip hooks) _
21+
22+useDrip :: NodeRef -> Hook UseDrip DripReturn
23+useDrip ref = coerceHook React.do
24+ visible /\ setVisible <- React.useState' false
25+ x /\ setX <- React.useState' zero
26+ y /\ setY <- React.useState' zero
27+
28+ let
29+ dripCompletedHandle = do
30+ setVisible false
31+ setX zero
32+ setY zero
33+
34+ let
35+ clickHandler = handler (merge { clientX, clientY }) \{ clientX, clientY } -> do
36+ bbʔ <- getBoundingBoxFromRef ref
37+ let
38+ valuesʔ = ado
39+ { left, top } <- bbʔ
40+ cx <- clientX
41+ cy <- clientY
42+ in { left, top, cx, cy }
43+ for_ valuesʔ \{ cx, cy, left, top } -> do
44+ setVisible true
45+ setX (cx - left)
46+ setY (cy - top)
47+
48+ pure
49+ { visible: visible
50+ , x: x
51+ , y: y
52+ , onClick: clickHandler
53+ , onComplete: dripCompletedHandle
54+ }
src/Yoga/Block/Molecule/Sheet/Story.purs+1−1
@@ -21,7 +21,7 @@ import Web.DOM.NonElementParentNode (getElementById)
2121import Web.HTML (window)
2222import Web.HTML.HTMLDocument (toNonElementParentNode)
2323import Web.HTML.Window (document)
24−import Yoga ((/>), (</))
24+import Yoga ((/>))
2525import Yoga.Block as Block
2626import Yoga.Block.Atom.Button.Types as ButtonType
2727import Yoga.Block.Container.Style as Styles
src/Yoga/Block/Quark/Drip.pursnew file+1
@@ -0,0 +1 @@
1+module Yoga.Block.Quark.Drip where
src/Yoga/Block/Quark/Drip/Style.pursnew file+33
@@ -0,0 +1,33 @@
1+module Yoga.Block.Quark.Drip.Style where
2+
3+import Yoga.Prelude.Style
4+
5+import Fahrtwind (positionAbsolute, transform)
6+import Fahrtwind as F
7+
8+expandAnimation ∷ StyleProperty
9+expandAnimation = keyframes
10+ { "0%": F.opacity 0 <> transform "scale(0.25)"
11+ , "30%": F.opacity 100
12+ , "80%": F.opacity 50
13+ , "100%": transform "scale(28)" <> F.opacity 0
14+ }
15+
16+drip ∷ Style
17+drip =
18+ positionAbsolute
19+ <> F.left 0
20+ <> F.right 0
21+ <> F.top 0
22+ <> F.bottom 0
23+ <> css
24+ { "& svg": nested
25+ $ positionAbsolute
26+ <> css
27+ { animation: str "350ms linear x"
28+ , animationName: expandAnimation
29+ , animationFillMode: str "forwards"
30+ , width: var "--s-1"
31+ , height: var "--s-1"
32+ }
33+ }
src/Yoga/Block/Quark/Drip/View.pursnew file+89
@@ -0,0 +1,89 @@
1+module Yoga.Block.Quark.Drip.View where
2+
3+import Yoga.Prelude.View
4+
5+import Data.Number (nan)
6+import Effect.Unsafe (unsafePerformEffect)
7+import React.Basic.DOM as R
8+import React.Basic.DOM.SVG as SVG
9+import React.Basic.Hooks (reactComponent)
10+import React.Basic.Hooks as React
11+import Web.DOM.Node (toEventTarget)
12+import Web.Event.Event (EventType(..))
13+import Web.Event.EventTarget (addEventListener, eventListener, removeEventListener)
14+import Yoga.Block.Quark.Drip.Style as Style
15+
16+-- This is taken from Next UI
17+
18+type Props =
19+ { visible :: Boolean
20+ , x :: Number
21+ , y :: Number
22+ , onComplete :: Effect Unit
23+ , colour :: String
24+ , className :: String
25+ }
26+
27+defaultProps ∷ ∀ (a6 ∷ Type) (a7 ∷ Type). Semiring a6 ⇒ Semiring a7 ⇒ { className ∷ String, visible ∷ Boolean, x ∷ a6, y ∷ a7 }
28+defaultProps =
29+ { visible: false
30+ , x: zero
31+ , y: zero
32+ , className: ""
33+ }
34+
35+component :: ReactComponent Props
36+component = unsafePerformEffect $ reactComponent "Drip" \(props :: Props) -> React.do
37+ dripRef <- useRef null
38+ let left = if props.x == nan then zero else props.x - 10.0
39+ let top = if props.y == nan then zero else props.y - 10.0
40+
41+ useEffectAlways do
42+ nʔ <- React.readRefMaybe dripRef
43+ nʔ # foldMap \n -> do
44+ let target = toEventTarget n
45+ let et = EventType "animationend"
46+ listener <- eventListener (const props.onComplete)
47+ addEventListener et listener false target
48+ pure $ removeEventListener et listener false target
49+
50+ pure $ guard props.visible do
51+ R.div'
52+ </*
53+ { css: Style.drip
54+ , className: "ry-drip"
55+ , ref: dripRef
56+ }
57+ />
58+ [ SVG.svg'
59+ </
60+ { width: "20"
61+ , height: "20"
62+ , viewBox: "0 0 20 20"
63+ , style: R.css { top, left }
64+ }
65+ />
66+ [ SVG.g'
67+ </
68+ { stroke: "none"
69+ , strokeWidth: "1"
70+ , fill: "none"
71+ , fillRule: "evenodd"
72+ }
73+ />
74+ [ SVG.g'
75+ </
76+ { className: "ry-drip-filler"
77+ , fill: props.colour
78+ }
79+ />
80+ [ SVG.rect
81+ { width: "100%"
82+ , height: "100%"
83+ , rx: "10"
84+ }
85+ ]
86+ ]
87+ ]
88+
89+ ]