git.iko.soy/rowtype-yoga/ry-blocks
on main help sign in
Better inputs
commit5fe49e671d52443b67e8af761636f26a6e6d4df9 authorMark Eibes <mark.eibes@gmail.com> dateThu 7 Jul 2022, 14:12 (4 years ago) parent6e5c067
9 files changed+185−151
src/Yoga/Block/Atom/Checkbox/Story.purs+1−9 src/Yoga/Block/Atom/Input/Story.purs+5−2 src/Yoga/Block/Atom/Input/Style.purs+91−73 src/Yoga/Block/Atom/Input/View.purs+5−3 src/Yoga/Block/Atom/Input/View/Container.purs+37−22 src/Yoga/Block/Atom/Input/View/Label.purs+10−8 src/Yoga/Block/Container/View.purs+17−15 src/Yoga/Block/Hook/UseStateEq.purs+11−11 src/Yoga/Block/Molecule/TableOfContents/Style.purs+8−8
src/Yoga/Block/Atom/Checkbox/Story.purs+1−9
@@ -2,22 +2,14 @@ module Yoga.Block.Atom.Checkbox.Story (default, checkbox) where
22
33import Prelude hiding (div)
44
5−import Color (cssStringRGBA)
65import Effect (Effect)
76import Effect.Unsafe (unsafePerformEffect)
8−import Fahrtwind as F
9−import React.Basic (JSX, element, fragment)
7+import React.Basic (JSX, element)
108import React.Basic.DOM as R
11−import React.Basic.Emotion (str)
129import React.Basic.Emotion as E
1310import Yoga ((</>))
14−import Yoga.Block as Block
15−import Yoga.Block.Atom.Button.Types as ButtonType
1611import Yoga.Block.Atom.Checkbox.View as Checkbox
17−import Yoga.Block.Container.Style (colour, size)
1812import Yoga.Block.Container.Style as Styles
19−import Yoga.Block.Icon.SVG as Icon
20−import Yoga.Block.Internal.CSS (nest)
2113
2214default
2315 ∷ { decorators ∷ Array (Effect JSX -> JSX)
src/Yoga/Block/Atom/Input/Story.purs+5−2
@@ -18,6 +18,7 @@ import Type.Proxy (Proxy(..))
1818import Yoga ((</>))
1919import Yoga.Block as Block
2020import Yoga.Block.Atom.Input as Input
21+import Yoga.Block.Atom.Input.Style (SizeVariant(..))
2122import Yoga.Block.Atom.Input.Types as HTMLInput
2223import Yoga.Block.Atom.Input.View (passwordIcon)
2324import Yoga.Block.Container.Style (colour)
@@ -47,11 +48,13 @@ input = do
4748 , R.h3_ [ R.text "With a label" ]
4849 , Block.cluster {}
4950 [ element Input.component { label: nes (Proxy ∷ _ "This has a label"), value: "And text", onChange: handler_ mempty }
50− , element Input.component { label: nes (Proxy ∷ _ "This has a label"), value: "", onChange: handler_ mempty }
51− , element Input.component { label: nes (Proxy ∷ _ "This has a label"), placeholder: "A very long placeholder, too..." }
51+ , element Input.component { label: nes (Proxy ∷ _ "This z` gggj"), value: "", onChange: handler_ mempty }
52+ , element Input.component { label: nes (Proxy ∷ _ "This has a label"), placeholder: "A j very g long y placeholder, too..." }
5253 , element Input.component { label: nes (Proxy ∷ _ "Pig nose"), leading: R.text "🐽🤣" }
5354 , element Input.component { label: nes (Proxy ∷ _ "Pig nose"), trailing: R.text "🤫" }
5455 , element Input.component { label: nes (Proxy ∷ _ "Pig nose"), leading: R.text "🌭" }
56+ , element Input.component { sizeVariant: SizeSmall, label: nes (Proxy ∷ _ "Small") }
57+ , element Input.component { sizeVariant: SizeSmall, placeholder: "Small without label" }
5558 , Block.box
5659 { background: colour.backgroundLayer3
5760 }
src/Yoga/Block/Atom/Input/Style.purs+91−73
@@ -4,15 +4,23 @@ import Yoga.Prelude.Style
44import Data.Interpolate (i)
55import Yoga.Block.Container.Style (colour)
66
7+data SizeVariant = SizeMedium | SizeSmall
8+
9+sizeVariantToFactor ∷ SizeVariant → String
10+sizeVariantToFactor = case _ of
11+ SizeMedium -> "1"
12+ SizeSmall -> "0.8"
13+
714type Props :: forall k. (Type -> k) -> Row k -> Row k
8−type Props f r
9− = ( css ∷ f Style
10− , background ∷ f String
11− , textColour ∷ f String
12− , placeholderColour ∷ f String
13− , borderColour ∷ f String
14− | r
15− )
15+type Props f r =
16+ ( css ∷ f Style
17+ , sizeVariant :: f SizeVariant
18+ , background ∷ f String
19+ , textColour ∷ f String
20+ , placeholderColour ∷ f String
21+ , borderColour ∷ f String
22+ | r
23+ )
1624
1725leftIconSize ∷ StyleProperty
1826leftIconSize = var "--left-icon-size"
@@ -38,9 +46,9 @@ labelAndInputWrapper =
3846 css
3947 { position: relative
4048 -- , display: inlineBlock
41− , "--left-icon-size": var "--s0"
42− , "--right-icon-size": str "calc(var(--s0) * 1.2)"
43− , "--input-border-radius": var "--s-1"
49+ , "--left-icon-size": str "calc(var(--s0) * var(--input-size-factor))"
50+ , "--right-icon-size": str "calc(var(--s0) * var(--input-size-factor)) * 1.2)"
51+ , "--input-border-radius": str "calc(var(--s-1) * var(--input-size-factor))"
4452 , "--input-side-padding": var "--s-1"
4553 , width: inherit
4654 , margin: _0
@@ -51,8 +59,8 @@ labelContainer =
5159 css
5260 { position: absolute
5361 , overflow: visible
54− , top: _0
5562 , left: _0
63+ , top: _0
5664 , display: inlineBlock
5765 , zIndex: str "2"
5866 , pointerEvents: none
@@ -62,7 +70,7 @@ labelSmall ∷ String -> String -> Style
6270labelSmall background textColour =
6371 css
6472 { fontSize: var "--s-1"
65− , marginTop: str "calc(var(--s-3) * -1)"
73+ , marginTop: str "calc(var(--s-2) * var(--input-size-factor) * -1)"
6674 , marginLeft: var "--s-2"
6775 , userSelect: none
6876 , """&[data-required="true"] > span:after""":
@@ -73,7 +81,7 @@ labelSmall background textColour =
7381 nest
7482 { fontWeight: str "500"
7583 , whiteSpace: str "nowrap"
76− , background: str $ i "linear-gradient(" background " 51%, transparent 51%, transparent)"
84+ , background: str $ i "linear-gradient(" background " 61%, transparent 61%, transparent)"
7785 , color: str textColour
7886 , borderRadius: var "--s-4"
7987 , paddingLeft: var "--s-4"
@@ -109,32 +117,30 @@ labelSmallFocusBackground =
109117labelLarge ∷ { left ∷ Number, width ∷ Number } -> Style
110118labelLarge { left, width } =
111119 css
112− { fontSize: str "var(--s0)"
120+ { fontSize: str "calc(var(--input-size-factor) * 16px)"
113121 , padding: _0
114122 , whiteSpace: nowrap -- force on one line
115− , height: str "calc(var(--s0) * 1.2)"
123+ -- , height: str "calc(var(--s0) * 1.2)"
116124 , letterSpacing: em (-0.011)
117125 , maxWidth: str $ i "calc(" width "px - 2ch)"
118− , marginTop: str "calc(var(--s-1))"
126+ , marginTop: str "calc(var(--s-1) * var(--input-size-factor))"
119127 , marginLeft: str $ i left "px"
120128 , marginRight: var "--input-side-padding"
121129 , color: str colour.textPaler2
122130 , fontWeight: str "400"
123131 , overflowX: str "hidden"
124132 , textOverflow: str "ellipsis"
125− , overflowY: str "visible"
126− , "MsOverflowStyle": none
133+ , overflowY: visible
127134 , scrollbarWidth: none
128135 , "&::-webkit-scrollbar": nested $ css
129− { display: none }
136+ { display: none }
130137 -- , fontWeight: str "600"
131138 , """&[data-required="true"]:after""":
132139 nest
133140 { content: str "'*'"
134141 , color: str colour.required
135142 , fontFamily: str "Helvetica, Arial, Inter, sans-serif"
136− , fontSize: str "calc(var(--s0))"
137− , lineHeight: str "calc(var(--s0) * 0.85)"
143+ , lineHeight: str "calc(var(--s0) * 0.85 * var(--input-size-factor))"
138144 }
139145 }
140146
@@ -142,31 +148,31 @@ rightIconContainer ∷ Style
142148rightIconContainer =
143149 iconContainer
144150 <> css
145− { display: inlineFlex
146− , alignItems: center
147− , justifyContent: center
148− , "& > *":
149− nest
150− { display: inlineFlex
151− , alignItems: center
152− , justifyContent: center
153− }
154− , ".ry-icon":
155− nest
156− { "--stroke-colour": str colour.text
157− }
158− }
151+ { display: inlineFlex
152+ , alignItems: center
153+ , justifyContent: center
154+ , "& > *":
155+ nest
156+ { display: inlineFlex
157+ , alignItems: center
158+ , justifyContent: center
159+ }
160+ , ".ry-icon":
161+ nest
162+ { "--stroke-colour": str colour.text
163+ }
164+ }
159165
160166leftIconContainer ∷ Style
161167leftIconContainer =
162168 iconContainer
163169 <> css
164− { borderRadius: str "var(--input-border-radius) 0 0 var(--input-border-radius)"
165− , ".ry-icon":
166− nest
167− { "--stroke-colour": str colour.text
168− }
169− }
170+ { borderRadius: str "var(--input-border-radius) 0 0 var(--input-border-radius)"
171+ , ".ry-icon":
172+ nest
173+ { "--stroke-colour": str colour.text
174+ }
175+ }
170176
171177inputContainer ∷ ∀ r. { | Props OptionalProp r } -> Style
172178inputContainer props = theCss <>? props.css
@@ -175,9 +181,10 @@ inputContainer props = theCss <>? props.css
175181 css
176182 { "--left-icon-size": var "--s0"
177183 , "--right-icon-size": str "calc(var(--s0) * 1.2)"
178− , "--input-side-padding": str "calc(var(--s-1) * 1.2)"
179− , "--input-top-padding": var "--s-5"
180− , "--input-bottom-padding": var "--s-5"
184+ , "--input-side-padding": str "calc(var(--s-1) * var(--input-size-factor) * 1.2)"
185+ , "--input-top-padding": str "calc(var(--s-5) * var(--input-size-factor))"
186+ , "--input-bottom-padding": str "calc(var(--s-5) * var(--input-size-factor))"
187+ , "--input-size-factor": str ((props.sizeVariant ?|| SizeMedium) # sizeVariantToFactor)
181188 , letterSpacing: em (-0.011)
182189 , position: relative
183190 , cursor: str "text"
@@ -200,41 +207,50 @@ inputContainer props = theCss <>? props.css
200207 { "--border-width": str "2px"
201208 , borderColor: str colour.highlight
202209 , marginTop: str "-1px"
203− , transition: str "border-color 0s linear 0.1s"
204− , animation: plopAnimation <> str " 260ms ease-in"
205210 }
206211 , alignItems: center
207212 , justifyContent: center
208213 , border: str $ "var(--border-width) solid " <> colour.inputBorder
209− , paddingLeft: str "calc(var(--input-side-padding) - var(--border-width))"
210− , paddingRight: str "calc(var(--input-side-padding) - var(--border-width))"
211− , paddingTop: str "calc(var(--input-top-padding) - var(--border-width))"
212− , paddingBottom: str "calc(var(--input-bottom-padding) - var(--border-width))"
214+ , paddingLeft: str "calc((var(--input-side-padding) - var(--border-width)) * var(--input-size-factor))"
215+ , paddingRight: str "calc((var(--input-side-padding) - var(--border-width)) * var(--input-size-factor))"
216+ , paddingTop: str "calc((var(--input-top-padding) - var(--border-width)) * var(--input-size-factor))"
217+ , paddingBottom: str "calc((var(--input-bottom-padding)) - var(--border-width))"
213218 , gap: str "calc(var(--input-side-padding) / 2)"
214219 , borderRadius: var "--input-border-radius"
220+ , overflow: visible
215221 }
216222
223+ploppedFocusWithin ∷ Style
224+ploppedFocusWithin = css
225+ { "&:focus-within":
226+ nest
227+ { animation: str "plop 180ms ease"
228+ , animationName: plopAnimation
229+ }
230+ }
231+
217232containerBackground ∷ ∀ r. { | Props OptionalProp r } -> Style
218−containerBackground props = css {
219− background: str (props.background ?|| colour.inputBackground)
220− , boxShadow: str """
221− 0 1px 1px rgba(200,200,255,0.2),
222− 0 2px 2px rgba(200,200,255,0.1),
223− 0 4px 8px rgba(200,200,255,0.03)"""
233+containerBackground props = css
234+ { background: str (props.background ?|| colour.inputBackground)
235+ -- , boxShadow: str
236+ -- """
237+ -- 0 1px 1px rgba(200,200,255,0.2),
238+ -- 0 2px 2px rgba(200,200,255,0.1),
239+ -- 0 4px 8px rgba(200,200,255,0.03)"""
224240 , border: str $ "var(--border-width) solid " <> colour.inputBorder
225241 , borderRadius: var "--input-border-radius"
226242
227−}
243+ }
228244
229245containerContainer ∷ ∀ r. { | Props OptionalProp r } -> Style
230−containerContainer props = css {
231− "& > *": nested $ css {
232− gridColumn: int 1,
233− gridRow: int 1
234− }, width: str "calc(var(--s4) * 2)"
235− , height: str "calc(var(--s2) - var(--border-width) * 2)"
246+containerContainer props = css
247+ { "& > *": nested $ css
248+ { gridColumn: int 1
249+ , gridRow: int 1
250+ }
236251 , "--border-width": str "1px"
237− , "--input-border-radius": var "--s-1"
252+ , "--input-size-factor": str ((props.sizeVariant ?|| SizeMedium) # sizeVariantToFactor)
253+ , "--input-border-radius": str "calc(var(--s-1) * var(--input-size-factor))"
238254 , borderRadius: var "--input-border-radius"
239255 , display: grid
240256 , overflow: visible
@@ -253,7 +269,8 @@ inputWrapper =
253269input ∷ ∀ r. { | Props OptionalProp r } -> Style
254270input props =
255271 css
256− { "&[type=text],&[type=search],&[type=password],&[type=number],&:not([type])":
272+ { "--input-size-factor": str ((props.sizeVariant ?|| SizeMedium) # sizeVariantToFactor)
273+ , "&[type=text],&[type=search],&[type=password],&[type=number],&:not([type])":
257274 nest
258275 { background: str "transparent"
259276 , touchAction: manipulation
@@ -262,14 +279,14 @@ input props =
262279 , minWidth: _0
263280 , margin: _0
264281 , overflowY: visible
282+ , "--padding-top": str "calc(var(--s-1) * var(--input-size-factor))"
283+ , "--padding-bottom": str "calc(var(--s-1) * var(--input-size-factor))"
265284 , paddingTop: var "--padding-top"
266285 , paddingBottom: var "--padding-bottom"
267286 , paddingLeft: _0
268287 , paddingRight: _0
269− -- , fontSize: str "calc(var(--s0) * 0.85)"
270− , fontSize: str "calc(var(--s0))"
271− , "--padding-top": var "--s-1"
272− , "--padding-bottom": var "--s-1"
288+ , fontSize: str "calc(var(--input-size-factor) * 16px)"
289+ -- , fontSize: str "calc(var(--s0))"
273290 , "&::placeholder":
274291 nest
275292 { color: str (props.placeholderColour ?|| colour.placeholderText)
@@ -297,8 +314,9 @@ input props =
297314plopAnimation ∷ StyleProperty
298315plopAnimation =
299316 keyframes
300− $ { "from": css { transform: str "scale3d(1.01,1.2,1)" }
301− , "67.7%": css { transform: str "scale3d(0.993,0.9,1)" }
302− , "86.7%": css { transform: str "scale3d(1.002,1.05,1)" }
317+ $
318+ { "from": css { transform: str "scale3d(1.03,1.02,1)" }
319+ , "67.7%": css { transform: str "scale3d(0.997,0.98,1)" }
320+ , "86.7%": css { transform: str "scale3d(1.004,1.01,1)" }
303321 , "to": css { transform: str "scale3d(1,1,1)" }
304322 }
src/Yoga/Block/Atom/Input/View.purs+5−3
@@ -14,10 +14,11 @@ import Record.Builder as RB
1414import Type.Prelude (Proxy(..))
1515import Web.HTML.HTMLInputElement as InputElement
1616import Yoga.Block.Atom.Icon as Icon
17+import Yoga.Block.Atom.Input.Style (SizeVariant(..))
1718import Yoga.Block.Atom.Input.Style as Style
1819import Yoga.Block.Atom.Input.Types (HTMLInputType)
1920import Yoga.Block.Atom.Input.Types as InputTypes
20−import Yoga.Block.Atom.Input.View.Container (rawContainer)
21+import Yoga.Block.Atom.Input.View.Container as Container
2122import Yoga.Block.Atom.Input.View.HTMLInput as HTMLInput
2223import Yoga.Block.Atom.Input.View.Label as Label
2324import Yoga.Block.Container.Style (colour)
@@ -99,7 +100,7 @@ rawComponent =
99100 , labelText
100101 , background: props.background ?|| colour.interfaceBackground
101102 , textColour: props.textColour ?|| colour.text
102−
103+ , sizeVariant: props.sizeVariant ?|| SizeMedium
103104 }
104105
105106 leading ∷ Maybe JSX
@@ -162,7 +163,7 @@ rawComponent =
162163 ) # deleteUndefineds
163164 )
164165 inputContainer =
165− rawContainer
166+ Container.rawContainer
166167 </
167168 { hasFocus: hasFocus
168169 , isInvalid: aria # Object.lookup "invalid" <#> (_ == "true") # maybeToOp
@@ -171,6 +172,7 @@ rawComponent =
171172 , borderColour: props.borderColour
172173 , textColor: props.textColour
173174 , ref
175+ , hasLabel: props.label # opToMaybe # isJust
174176 }
175177 />
176178 [ leading # foldMap \l -> div </ {} /> [ l ]
src/Yoga/Block/Atom/Input/View/Container.purs+37−22
@@ -24,6 +24,7 @@ type PropsF f =
2424type MandatoryProps r =
2525 ( children ∷ Array JSX
2626 , hasFocus ∷ Boolean
27+ , hasLabel :: Boolean
2728 | r
2829 )
2930
@@ -49,30 +50,44 @@ rawContainer =
4950 let
5051 containerVariants = mkContainerVariants dimensions
5152 containerVariantLabels = mkContainerVariantLabels containerVariants
53+ animated =
54+ M.div
55+ </*
56+ ( { variants: M.variants containerVariants
57+ , initial: M.initial false
58+ , animate: M.animate
59+ if props.hasFocus then
60+ containerVariantLabels.focussed
61+ else
62+ containerVariantLabels.blurred
63+ , className: "ry-input-container"
64+ , key: show sizes.innerWidth -- redraw
65+ , css: Style.inputContainer props
66+ , ref
67+ } `unsafeAddProps`
68+ { "data-invalid":
69+ props.isInvalid
70+ # opToMaybe
71+ # maybe (unsafeCoerce undefined) show
72+ }
73+ )
74+ static =
75+ R.div'
76+ </*
77+ ( { className: "ry-input-container"
78+ , key: show sizes.innerWidth -- redraw
79+ , css: Style.inputContainer props <> Style.ploppedFocusWithin
80+ , ref
81+ } `unsafeAddProps`
82+ { "data-invalid":
83+ props.isInvalid
84+ # opToMaybe
85+ # maybe (unsafeCoerce undefined) show
86+ }
87+ )
5288 result = R.div' </* { css: Style.containerContainer props } />
5389 [ R.div' </*> { className: "container-background", css: Style.containerBackground props }
54− , M.div
55− </*
56− ( { variants: M.variants containerVariants
57− , initial: M.initial false
58− , animate:
59− M.animate
60− if props.hasFocus then
61− containerVariantLabels.focussed
62− else
63− containerVariantLabels.blurred
64− , className: "ry-input-container"
65− , key: show sizes.innerWidth -- redraw
66− , css: Style.inputContainer props
67− , ref
68− } `unsafeAddProps`
69− { "data-invalid":
70− props.isInvalid
71− # opToMaybe
72− # maybe (unsafeCoerce undefined) show
73− }
74− )
75− /> props.children
90+ , (if props.hasLabel then animated else static) props.children
7691 ]
7792 pure result
7893
src/Yoga/Block/Atom/Input/View/Label.purs+10−8
@@ -1,6 +1,7 @@
11module Yoga.Block.Atom.Input.View.Label where
22
33import Yoga.Prelude.View
4+
45import Data.String.NonEmpty (NonEmptyString)
56import Data.String.NonEmpty as NonEmptyString
67import Effect.Class.Console (warn)
@@ -11,6 +12,7 @@ import React.Basic.DOM as R
1112import React.Basic.Hooks (reactComponent)
1213import React.Basic.Hooks as React
1314import Record.Extra (sequenceRecord)
15+import Yoga.Block.Atom.Input.Style (SizeVariant, sizeVariantToFactor)
1416import Yoga.Block.Atom.Input.Style as Style
1517
1618type Props =
@@ -25,6 +27,7 @@ type Props =
2527 , labelText ∷ NonEmptyString
2628 , background ∷ String
2729 , textColour ∷ String
30+ , sizeVariant :: SizeVariant
2831 }
2932
3033component ∷ ReactComponent Props
@@ -64,14 +67,13 @@ component =
6467 -- UI
6568 let
6669 text = R.text $ NonEmptyString.toString props.labelText
67− result =
68− container
69− [ labelContainer
70− [ labelSpan
71− [ text ]
72− ]
73− ]
74− container = div </* { className: "ry-input-label-container", css: Style.labelContainer }
70+ result = container [ labelContainer [ labelSpan [ text ] ] ]
71+
72+ container = div </*
73+ { className: "ry-input-label-container"
74+ , style: R.css { "--input-size-factor": props.sizeVariant # sizeVariantToFactor }
75+ , css: Style.labelContainer
76+ }
7577 labelContainer =
7678 maybeOffsets
7779 # foldMap case _ of
src/Yoga/Block/Container/View.purs+17−15
@@ -7,7 +7,9 @@ module Yoga.Block.Container.View
77 ) where
88
99import Yoga.Prelude.View
10+
1011import Data.Array as Array
12+import Fahrtwind as F
1113import React.Basic.DOM as R
1214import React.Basic.Emotion as E
1315import React.Basic.Hooks as React
@@ -25,7 +27,7 @@ type PropsF f =
2527 )
2628
2729type Props =
28− ( | PropsF Id )
30+ (| PropsF Id)
2931
3032component ∷ ∀ p q. Union p q Props => ReactComponent { | p }
3133component = rawComponent
@@ -78,31 +80,31 @@ rawComponent =
7880 $ fragment
7981 [ R.div' </ { ref }
8082 /> Array.cons
81− ( element E.global
82− { styles: (_ <>? props.globalStyles)
83+ ( element E.global
84+ { styles: F.globalStyles <> (_ <>? props.globalStyles)
8385 case propsThemeVariant, systemThemeVariant of
8486 Nothing, Nothing -> Styles.global
8587 Just Styles.DarkMode, _ -> Styles.darkMode
8688 Just Styles.LightMode, _ -> Styles.lightMode
8789 Nothing, Just DarkMode -> Styles.darkMode
8890 Nothing, Just LightMode -> Styles.lightMode
89− }
90− )
91− children
91+ }
92+ )
93+ children
9294 , R.div { id: modalContainerId }
9395 , E.element R.div'
9496 { id: tooltipContainerId
9597 , className: tooltipContainerId
9698 , css:
97− E.css
98− { width: 100.0 # E.vw
99− , height: 100.0 # E.vh
100− , position: E.fixed
101− , top: E.str "0"
102− , left: E.str "0"
103− , pointerEvents: E.none
104− , zIndex: E.str "9999"
105− }
99+ E.css
100+ { width: 100.0 # E.vw
101+ , height: 100.0 # E.vh
102+ , position: E.fixed
103+ , top: E.str "0"
104+ , left: E.str "0"
105+ , pointerEvents: E.none
106+ , zIndex: E.str "9999"
107+ }
106108 }
107109 ]
108110
src/Yoga/Block/Hook/UseStateEq.purs+11−11
@@ -8,22 +8,22 @@ import Effect (Effect)
88import React.Basic.Hooks (UseState, Hook, coerceHook)
99import React.Basic.Hooks as React
1010
11+-- Thanks to
12+useStateEq :: forall a. Eq a => a -> Hook (UseStateEq a) (a /\ ((a -> a) -> Effect Unit))
13+useStateEq initialValue = coerceHook React.do
14+ value /\ setValue <- React.useState initialValue
15+ let
16+ updateEq f = setValue \value' -> do
17+ let newValue = f value'
18+ if newValue /= value' then newValue else value'
19+ pure (value /\ updateEq)
20+
1121newtype UseStateEq a hooks = UseStateEq (UseState a hooks)
1222
1323derive instance Newtype (UseStateEq a hooks) _
1424
15−-- [TODO] I'm too stupid to write this
16−-- useStateEq :: forall a. Eq a => a -> Hook (UseStateEq a) (a /\ ((a -> a) -> Effect Unit))
17−-- useStateEq initialValue = coerceHook React.do
18−-- value /\ update <- React.useState initialValue
19−-- let
20−-- updateEq modify = do
21−-- modified <- modify value
22−-- when (modified /= value) do update (const modified)
23−-- pure (value /\ updateEq)
24−
2525useStateEq' :: forall a. Eq a => a -> Hook (UseStateEq a) (a /\ (a -> Effect Unit))
2626useStateEq' initialValue = coerceHook React.do
2727 value /\ setValue <- React.useState' initialValue
28− let updateEq newValue = do when (newValue /= value) do setValue newValue
28+ let updateEq newValue = when (newValue /= value) (setValue newValue)
2929 pure (value /\ updateEq)
src/Yoga/Block/Molecule/TableOfContents/Style.purs+8−8
@@ -18,9 +18,9 @@ button =
1818 , "&:focus": nest { outline: str "none" }
1919 , border: str $ "1px solid " <> colour.backgroundLayer2
2020 , "&:focus-visible":
21− nest
22− { boxShadow: str $ "0 0 0 var(--s-4) " <> colour.highlight
23− }
21+ nest
22+ { boxShadow: str $ "0 0 0 var(--s-4) " <> colour.highlight
23+ }
2424 , borderRadius: str "calc(var(--s2) / 2)"
2525 , height: var "--s2"
2626 , width: str "calc(var(--s2) + var(--s1))"
@@ -57,8 +57,8 @@ tableOfContentsTextContainer =
5757 , fontWeight: str "bold"
5858 , position: absolute
5959 , top: str "1px"
60− , fontSize: str "calc(var(--s2)*0.5)"
61− , lineHeight: str "calc(var(--s2)*0.5)"
60+ , fontSize: str "calc(var(--s2) * 0.5)"
61+ , lineHeight: str "calc(var(--s2) * 0.5)"
6262 , textAlign: center
6363 , height: var "--s2"
6464 , display: flex
@@ -87,7 +87,7 @@ tableOfContentsText =
8787 , alignItems: center
8888 , color: disabledTextColour
8989 , "& > *":
90− nest
91− { color: successTextColour
92− }
90+ nest
91+ { color: successTextColour
92+ }
9393 }