git.iko.soy/rowtype-yoga/ry-blocks
on main help sign in
Refactoring input
2b690c5Mark Eibes
src/Yoga.purs+3−3
@@ -10,13 +10,13 @@ import React.Basic.Hooks (JSX, ReactComponent)
10import React.Basic.Hooks as Hooks
10import React.Basic.Hooks as Hooks
11import Record as Record
11import Record as Record
12
12
13−div :: forall attrs attrs_. Union attrs attrs_ Props_div => ReactComponent (Record attrs)
13+div ∷ ∀ attrs attrs_. Union attrs attrs_ Props_div => ReactComponent (Record attrs)
14div = R.div'
14div = R.div'
15
15
16−span :: forall attrs attrs_. Union attrs attrs_ Props_span => ReactComponent (Record attrs)
16+span ∷ ∀ attrs attrs_. Union attrs attrs_ Props_span => ReactComponent (Record attrs)
17span = R.span'
17span = R.span'
18
18
19−button :: forall attrs attrs_. Union attrs attrs_ Props_button => ReactComponent (Record attrs)
19+button ∷ ∀ attrs attrs_. Union attrs attrs_ Props_button => ReactComponent (Record attrs)
20button = R.button'
20button = R.button'
21
21
22el ∷
22el ∷
src/Yoga/Block/Atom/Button/Style.purs+3−4
@@ -55,7 +55,8 @@ button =
55 , """&[data-button-shape="pill"]""":
55 , """&[data-button-shape="pill"]""":
56 nest
56 nest
57 { borderRadius: str "calc(var(--s1) * 0.85)"
57 { borderRadius: str "calc(var(--s1) * 0.85)"
58− , padding: str "calc(var(--s-1) * 0.9) var(--s0)"
58+ , padding: str "calc(var(--s-1) * 0.85) var(--s0)"
59+ , paddingBottom: str "calc(var(--s-1))"
59 }
60 }
60 , """&[data-button-type="primary"]""":
61 , """&[data-button-type="primary"]""":
61 nest
62 nest
@@ -95,9 +96,7 @@ button =
95 }
96 }
96 , """&:active""":
97 , """&:active""":
97 nest
98 nest
98− { boxShadow:
99− str
100− $ "inset 0 1px var(--s-1) rgba(0,0,0,0.20)"
99+ { boxShadow: str $ "inset 0 1px var(--s-1) rgba(0,0,0,0.20)"
101 , borderTop: str $ i "1px solid " colour.interfaceBackgroundShadow
100 , borderTop: str $ i "1px solid " colour.interfaceBackgroundShadow
102 , borderBottom: str $ i "1px solid " colour.interfaceBackgroundShadow
101 , borderBottom: str $ i "1px solid " colour.interfaceBackgroundShadow
103 , transform: str "scale3d(0.95,0.95,0.95)"
102 , transform: str "scale3d(0.95,0.95,0.95)"
src/Yoga/Block/Atom/Input/Story.purs+8−2
@@ -10,7 +10,7 @@ import React.Basic (JSX, element, fragment)
10import React.Basic.DOM as R
10import React.Basic.DOM as R
11import React.Basic.Emotion as E
11import React.Basic.Emotion as E
12import React.Basic.Events (handler_)
12import React.Basic.Events (handler_)
13−import Yoga (el, (</), (/>), (</>))
13+import Yoga ((/>), (</), (</>))
14import Yoga.Block as Block
14import Yoga.Block as Block
15import Yoga.Block.Atom.Input as Input
15import Yoga.Block.Atom.Input as Input
16import Yoga.Block.Atom.Input.Types as HTMLInput
16import Yoga.Block.Atom.Input.Types as HTMLInput
@@ -58,11 +58,17 @@ input = do
58 , element Input.component { label: nes (SProxy ∷ _ "Pig nose"), leading: R.text "🌭" }
58 , element Input.component { label: nes (SProxy ∷ _ "Pig nose"), leading: R.text "🌭" }
59 , element Input.component { label: nes (SProxy ∷ _ "Pig nose"), leading: R.text "⭐", trailing: R.text "🔮" }
59 , element Input.component { label: nes (SProxy ∷ _ "Pig nose"), leading: R.text "⭐", trailing: R.text "🔮" }
60 ]
60 ]
61− , R.h2_ [ R.text "[BUG] Overflowing label" ]
61+ , R.h2_ [ R.text "Overflowing label" ]
62 , element Input.component
62 , element Input.component
63 { type: HTMLInput.Text
63 { type: HTMLInput.Text
64 , label: nes (SProxy ∷ _ "Is undefined really a function?")
64 , label: nes (SProxy ∷ _ "Is undefined really a function?")
65 }
65 }
66+ , Input.component </> { type: HTMLInput.Text, label: nes (SProxy ∷ _ "Now this is really just excessively long and you should avoid it.") }
67+ , Input.component
68+ </> { css: E.css { width: E.str "500px" }
69+ , type: HTMLInput.Text
70+ , label: nes (SProxy ∷ _ "You can avoid this problem by setting a custom width")
71+ }
66 , R.h2_ [ R.text "Password" ]
72 , R.h2_ [ R.text "Password" ]
67 , element Input.component { type: HTMLInput.Password }
73 , element Input.component { type: HTMLInput.Password }
68 , R.h2_ [ R.text "Text Input" ]
74 , R.h2_ [ R.text "Text Input" ]
src/Yoga/Block/Atom/Input/Style.purs+66−50
@@ -57,6 +57,7 @@ labelSmall =
57 { fontSize: var "--s-1"
57 { fontSize: var "--s-1"
58 , marginTop: str "calc(var(--s-3) * -1)"
58 , marginTop: str "calc(var(--s-3) * -1)"
59 , marginLeft: var "--s-2"
59 , marginLeft: var "--s-2"
60+ , userSelect: none
60 , """&[data-required="true"] > span:after""":
61 , """&[data-required="true"] > span:after""":
61 nest
62 nest
62 { content: str "'*'"
63 { content: str "'*'"
@@ -64,6 +65,7 @@ labelSmall =
64 , "& > span":
65 , "& > span":
65 nest
66 nest
66 { fontWeight: str "500"
67 { fontWeight: str "500"
68+ , whiteSpace: str "nowrap"
67 , background: str colour.inputBackground
69 , background: str colour.inputBackground
68 , color: str colour.text
70 , color: str colour.text
69 , borderRadius: var "--s-4"
71 , borderRadius: var "--s-4"
@@ -94,11 +96,15 @@ labelSmall =
94 }
96 }
95 }
97 }
96
98
97−labelLarge ∷ { leftIconWidth ∷ Maybe Number } -> Style
99+labelLarge ∷ { leftIconWidth ∷ Maybe Number, inputWidth ∷ Number } -> Style
98−labelLarge { leftIconWidth } =
100+labelLarge { leftIconWidth, inputWidth } =
99 css
101 css
100 { fontSize: str "calc(var(--s0) * 0.85)"
102 { fontSize: str "calc(var(--s0) * 0.85)"
101 , padding: _0
103 , padding: _0
104+ , whiteSpace: nowrap -- force on one line
105+ , overflow: str "hidden"
106+ , maxWidth: str $ i "calc(" inputWidth "px)"
107+ , textOverflow: str "ellipsis"
102 , marginTop: str "calc(var(--s-1) + var(--s-5))"
108 , marginTop: str "calc(var(--s-1) + var(--s-5))"
103 , marginBottom: str "calc(var(--s-1) + var(--s-5))"
109 , marginBottom: str "calc(var(--s-1) + var(--s-5))"
104 , marginLeft:
110 , marginLeft:
@@ -108,7 +114,6 @@ labelLarge { leftIconWidth } =
108 , marginRight: str "var(--input-side-padding)"
114 , marginRight: str "var(--input-side-padding)"
109 , color: str colour.placeholderText
115 , color: str colour.placeholderText
110 , fontWeight: str "400"
116 , fontWeight: str "400"
111− , whiteSpace: nowrap -- force on one line
112 , """&[data-required="true"]:after""":
117 , """&[data-required="true"]:after""":
113 nest
118 nest
114 { content: str "'*'"
119 { content: str "'*'"
@@ -148,46 +153,57 @@ leftIconContainer =
148 }
153 }
149 }
154 }
150
155
156+inputContainer ∷ ∀ r. { | Props OptionalProp r } -> Style
157+inputContainer props = theCss <>? props.css
158+ where
159+ theCss =
160+ css
161+ { position: relative
162+ , boxSizing: borderBox
163+ , backgroundColor: str colour.inputBackground
164+ , display: flex
165+ , width: str "calc(var(--s4) * 2)"
166+ , "--left-icon-size": var "--s0"
167+ , "--right-icon-size": str "calc(var(--s0) * 1.2)"
168+ , "--input-border-radius": var "--s-1"
169+ , "--input-side-padding": var "--s-1"
170+ , "--input-top-padding": var "--s-5"
171+ , "--input-bottom-padding": var "--s-5"
172+ , alignItems: center
173+ , justifyContent: center
174+ , paddingLeft: str "calc(var(--input-side-padding) - var(--border-width))"
175+ , paddingRight: str "calc(var(--input-side-padding) - var(--border-width))"
176+ , paddingTop: str "calc(var(--input-top-padding) - var(--border-width))"
177+ , paddingBottom: str "calc(var(--input-bottom-padding) - var(--border-width))"
178+ , gap: str "calc(var(--input-side-padding) / 2)"
179+ , "--border-width": str "1px"
180+ , border: str $ "var(--border-width) solid " <> colour.inputBorder
181+ , borderRadius: var "--input-border-radius"
182+ , """&[data-invalid="false"]""":
183+ nest
184+ { borderColor: str colour.success
185+ , "--border-width": str "var(--s-5)"
186+ }
187+ , """&[data-invalid="true"]""":
188+ nest
189+ { borderColor: str colour.invalid
190+ , "--border-width": str "var(--s-5)"
191+ }
192+ , "&:focus-within":
193+ nest
194+ { "--border-width": str "var(--s-5)"
195+ , borderColor: str colour.highlight
196+ , transition: str "border-color 0s linear 0.1s"
197+ }
198+ }
199+
151inputWrapper ∷ Style
200inputWrapper ∷ Style
152inputWrapper =
201inputWrapper =
153 css
202 css
154− { position: relative
203+ { margin: _0
155− , boxSizing: borderBox
204+ , minWidth: _0
156− , backgroundColor: str colour.inputBackground
205+ , padding: _0
157− , display: flex
206+ , flex: str "1"
158− , minWidth: str "calc(var(--s4) *2 )"
159− , "--left-icon-size": var "--s0"
160− , "--right-icon-size": str "calc(var(--s0) * 1.2)"
161− , "--input-border-radius": var "--s-1"
162− , "--input-side-padding": var "--s-1"
163− , "--input-top-padding": var "--s-5"
164− , "--input-bottom-padding": var "--s-5"
165− , alignItems: center
166− , justifyContent: center
167− , paddingLeft: str "calc(var(--input-side-padding) - var(--border-width))"
168− , paddingRight: str "calc(var(--input-side-padding) - var(--border-width))"
169− , paddingTop: str "calc(var(--input-top-padding) - var(--border-width))"
170− , paddingBottom: str "calc(var(--input-bottom-padding) - var(--border-width))"
171− , gap: str "calc(var(--input-side-padding) / 2)"
172− , "--border-width": str "1px"
173− , border: str $ "var(--border-width) solid " <> colour.inputBorder
174− , borderRadius: var "--input-border-radius"
175− , """&[data-invalid="false"]""":
176− nest
177− { borderColor: str colour.success
178− , "--border-width": str "var(--s-5)"
179− }
180− , """&[data-invalid="true"]""":
181− nest
182− { borderColor: str colour.invalid
183− , "--border-width": str "var(--s-5)"
184− }
185− , "&:focus-within":
186− nest
187− { "--border-width": str "var(--s-5)"
188− , borderColor: str colour.highlight
189− , transition: str "border-color 0s linear 0.1s"
190− }
191 }
207 }
192
208
193input ∷ Style
209input ∷ Style
@@ -195,8 +211,16 @@ input =
195 css
211 css
196 { "&[type=text],&[type=search],&[type=password],&[type=number],&:not([type])":
212 { "&[type=text],&[type=search],&[type=password],&[type=number],&:not([type])":
197 nest
213 nest
214+ { background: str "transparent"
198− { color: str colour.text
215+ , color: str colour.text
199− , flex: str "2"
216+ , width: _100percent
217+ , minWidth: _0
218+ , margin: _0
219+ , paddingTop: var "--padding-top"
220+ , paddingBottom: var "--padding-bottom"
221+ , paddingLeft: _0
222+ , paddingRight: _0
223+ , fontSize: str "calc(var(--s0) * 0.85)"
200 , "--padding-top": var "--s-1"
224 , "--padding-top": var "--s-1"
201 , "--padding-bottom": var "--s-1"
225 , "--padding-bottom": var "--s-1"
202 , "&[aria-labelledby]":
226 , "&[aria-labelledby]":
@@ -204,14 +228,6 @@ input =
204 { paddingTop: str "calc(var(--padding-top) + (var(--s-5)/2))"
228 { paddingTop: str "calc(var(--padding-top) + (var(--s-5)/2))"
205 , paddingBottom: str "calc(var(--padding-bottom) - (var(--s-5)/2))"
229 , paddingBottom: str "calc(var(--padding-bottom) - (var(--s-5)/2))"
206 }
230 }
207− , background: str "transparent"
208− , margin: _0
209− , paddingTop: var "--padding-top"
210− , paddingBottom: var "--padding-bottom"
211− , paddingLeft: _0
212− , paddingRight: _0
213− , fontSize: str "calc(var(--s0) * 0.85)"
214− , gap: str gapSize
215 , border: none
231 , border: none
216 }
232 }
217 , "&[type=search]":
233 , "&[type=search]":
src/Yoga/Block/Atom/Input/View.purs+181−91
@@ -5,13 +5,15 @@ import Data.Array as Array
5import Data.Interpolate (i)
5import Data.Interpolate (i)
6import Data.String.NonEmpty (NonEmptyString)
6import Data.String.NonEmpty (NonEmptyString)
7import Data.String.NonEmpty as NonEmptyString
7import Data.String.NonEmpty as NonEmptyString
8−import Data.Symbol (SProxy(..))
8+import Effect.Unsafe (unsafePerformEffect)
9import Foreign.Object (Object)
9import Foreign.Object (Object)
10import Foreign.Object as Object
10import Foreign.Object as Object
11import Framer.Motion as M
11import Framer.Motion as M
12import Partial.Unsafe (unsafeCrashWith)
12import Partial.Unsafe (unsafeCrashWith)
13−import React.Basic.DOM (CSS, css)
13+import React.Basic.DOM (CSS, Props_input, css)
14import React.Basic.DOM as R
14import React.Basic.DOM as R
15+import React.Basic.Emotion (Style)
16+import React.Basic.Hooks (reactComponent)
15import React.Basic.Hooks as React
17import React.Basic.Hooks as React
16import Web.HTML.HTMLInputElement as InputElement
18import Web.HTML.HTMLInputElement as InputElement
17import Yoga.Block.Atom.Icon as Icon
19import Yoga.Block.Atom.Icon as Icon
@@ -57,7 +59,7 @@ containerVariants =
57 { focussed:
59 { focussed:
58 css
60 css
59 { clipPath
61 { clipPath
60− , transition: { duration: 0.7 }
62+ , transition: { duration: 0.6 }
61 }
63 }
62 , blurred:
64 , blurred:
63 css
65 css
@@ -71,9 +73,6 @@ containerVariants =
71type Point =
73type Point =
72 { x ∷ Int, y ∷ Int }
74 { x ∷ Int, y ∷ Int }
73
75
74−p ∷ Int -> Int -> Point
75−p x y = { x, y }
76−
77drawPathUntil ∷ Int -> Array Point -> String
76drawPathUntil ∷ Int -> Array Point -> String
78drawPathUntil idx thePath = do
77drawPathUntil idx thePath = do
79 let fn { x, y } = i x "%" " " y "%"
78 let fn { x, y } = i x "%" " " y "%"
@@ -83,7 +82,7 @@ drawPathUntil idx thePath = do
83 i "polygon(" rendered ")"
82 i "polygon(" rendered ")"
84
83
85path ∷ Array Point
84path ∷ Array Point
86−path = mkPath 3 7
85+path = mkPath 4 8
87
86
88mkPath ∷ Int -> Int -> Array Point
87mkPath ∷ Int -> Int -> Array Point
89mkPath borderX borderY = do
88mkPath borderX borderY = do
@@ -96,13 +95,22 @@ mkPath borderX borderY = do
96 , {- ⌜ -} p borderX borderY
95 , {- ⌜ -} p borderX borderY
97 ]
96 ]
98 outside =
97 outside =
99− [ {- ⌜ -} p 0 0
98+ [ {-⌜ -} p 0 0
100− , {- ⌞ -} p 0 100
99+ , {-⌞ -} p 0 100
100+ , {- . -} p 25 100
101+ , {- . -} p 50 100
102+ , {- . -} p 75 100
101− , {- ⌟ -} p 100 100
103+ , {- ⌟-} p 100 100
102− , {- ⌝ -} p 100 0
104+ , {- ⌝-} p 100 0
105+ , {- ^ -} p 75 0
106+ , {- ^ -} p 50 0
107+ , {- ^ -} p 25 0
103− , {- ⌜ -} p 0 0
108+ , {-⌜ -} p 0 0
104 ]
109 ]
105 inside <> outside <> (Array.reverse inside)
110 inside <> outside <> (Array.reverse inside)
111+ where
112+ p ∷ Int -> Int -> Point
113+ p x y = { x, y }
106
114
107containerVariantLabels ∷
115containerVariantLabels ∷
108 { blurred ∷ M.VariantLabel
116 { blurred ∷ M.VariantLabel
@@ -110,31 +118,67 @@ containerVariantLabels ∷
110 }
118 }
111containerVariantLabels = M.makeVariantLabels containerVariants
119containerVariantLabels = M.makeVariantLabels containerVariants
112
120
113−rawComponent ∷ ∀ p. ReactComponent { | p }
121+rawInput ∷ ∀ p. ReactComponent { | p }
114−rawComponent =
122+rawInput =
123+ mkForwardRefComponent "Input" do
124+ \(props ∷ { | PropsOptional }) ref -> React.do
125+ let
126+ result = inputWrapper [ input ]
127+ inputWrapper = div </* { className: "ry-input-wrapper", css: Style.inputWrapper }
128+ input = emotionInput ref inputProps { className: "ry-input", css: Style.input }
129+ inputProps = props { type = HTMLInput.toString <$> props.type # unsafeUnOptional }
130+ pure result
131+
132+rawContainer ∷ ∀ p. ReactComponent { | p }
133+rawContainer =
115 mkForwardRefComponent "Input" do
134 mkForwardRefComponent "Input" do
135+ \( props ∷
136+ { children ∷ Array JSX
137+ , label ∷ JSX
138+ , css ∷ OptionalProp Style
139+ , hasFocus ∷ Boolean
140+ , inputProps ∷ { | InputPropsF OptionalProp () }
141+ }
142+ ) propsRef -> React.do
143+ -- Left icon width to correctly place the label
144+ let
145+ aria ∷ Object String
146+ aria = props.inputProps._aria # opToMaybe # fold
147+ inputContainer =
148+ M.div
149+ </* M.motion
150+ { variants: M.variants containerVariants
151+ , animate: M.animate if props.hasFocus then containerVariantLabels.focussed else containerVariantLabels.blurred
152+ }
153+ { className: "ry-input-container"
154+ , css: Style.inputContainer props
155+ , _data:
156+ Object.fromHomogeneous
157+ { "invalid": aria # Object.lookup "invalid" # fromMaybe ""
158+ }
159+ }
160+ /> props.children
161+ pure
162+ $ div
163+ </* { className: "ry-label-and-input-wrapper"
164+ , css: Style.labelAndInputWrapper
165+ }
166+ /> [ inputContainer, props.label ]
167+
168+rawComponent ∷ ∀ p. ReactComponent (Record p)
169+rawComponent =
170+ mkForwardRefComponent "InputContainer" do
116 \(props ∷ { | PropsOptional }) propsRef -> React.do
171 \(props ∷ { | PropsOptional }) propsRef -> React.do
117− inputBbox /\ setInputBbox <- useState' (zero ∷ DOMRect)
118 hasFocus /\ setHasFocus <- useState' false
172 hasFocus /\ setHasFocus <- useState' false
119 backupRef ∷ NodeRef <- useRef null -- [TODO] test this
173 backupRef ∷ NodeRef <- useRef null -- [TODO] test this
120 let ref = forwardedRefAsMaybe propsRef # fromMaybe backupRef
174 let ref = forwardedRefAsMaybe propsRef # fromMaybe backupRef
121− useEffectOnce do
122− maybeBBox <- getBoundingBoxFromRef ref
123− for_ maybeBBox setInputBbox
124− mempty
125 let
175 let
126− focusInput = do
176+ focusInput =
127 unless hasFocus do
177 unless hasFocus do
128 maybeHTMLElement <- getHTMLElementFromRef ref
178 maybeHTMLElement <- getHTMLElementFromRef ref
129 for_ maybeHTMLElement focus
179 for_ maybeHTMLElement focus
130 -- Left icon width to correctly place the label
180 -- Left icon width to correctly place the label
131 leftIconRef ∷ NodeRef <- useRef null
181 leftIconRef ∷ NodeRef <- useRef null
132− leftIconBbox /\ setLeftIconBbox <- useState' Nothing
133− useEffectAlways do
134− when (isJust (props.leading # opToMaybe) && leftIconBbox == Nothing) do
135− maybeBBox <- getBoundingBoxFromRef leftIconRef
136− for_ maybeBBox (setLeftIconBbox <<< Just)
137− mempty
138 let (maybeValue ∷ Maybe String) = cast props.value # opToMaybe
182 let (maybeValue ∷ Maybe String) = cast props.value # opToMaybe
139 hasValue /\ setHasValue <- useState' ((maybeValue # isJust) && (maybeValue /= Just ""))
183 hasValue /\ setHasValue <- useState' ((maybeValue # isJust) && (maybeValue /= Just ""))
140 let
184 let
@@ -148,39 +192,18 @@ rawComponent =
148 maybeLabelText = props.label # opToMaybe
192 maybeLabelText = props.label # opToMaybe
149 mkLabel ∷ NonEmptyString -> JSX
193 mkLabel ∷ NonEmptyString -> JSX
150 mkLabel labelText =
194 mkLabel labelText =
151− R.div'
195+ inputLabel
152− </* { className: "ry-input-label-container", css: Style.labelContainer }
153− /> [ M.animateSharedLayout </ { type: M.switch }
154− /> [ guard (inputBbox /= zero)
155− $ M.div
156− </* { className: if renderLargeLabel then "ry-input-label-large" else "ry-input-label-small"
157− , layoutId: M.layoutId "ry-input-label"
158− , css:
159− if renderLargeLabel then
160− Style.labelLarge { leftIconWidth: leftIconBbox <#> _.width }
161− else
162− Style.labelSmall
163− , layout: M.layout true
164− , transition: M.transition { duration: 0.18, ease: "easeOut" }
165− , _data:
166− Object.fromHomogeneous
167− { "has-focus": show hasFocus
168− , "invalid": aria # Object.lookup "invalid" # fromMaybe ""
169− , "required": aria # Object.lookup "required" # fromMaybe ""
170− }
171− , initial: M.initial false
172− }
173− /> [ M.span
174− </ { onClick: handler preventDefault (const focusInput)
196+ </> { onClickLargeLabel: handler preventDefault (const focusInput)
175− , layout: M.layout true
197+ , isFocussed: hasFocus
176− , layoutId: M.layoutId "ry-input-label-text"
198+ , isRequired: aria # Object.lookup "required" # (_ == Just "true")
177− , htmlFor: props.id
199+ , isInvalid: aria # Object.lookup "invalid" # (_ == Just "true")
200+ , renderLargeLabel
201+ , leftIconRef
178− , id: labelId
202+ , labelId
179− }
203+ , inputId: props.id
204+ , inputRef: ref
180− /> [ R.text $ NonEmptyString.toString labelText ]
205+ , labelText
181− ]
206+ }
182− ]
183− ]
184 leading ∷ Maybe JSX
207 leading ∷ Maybe JSX
185 leading =
208 leading =
186 opToMaybe props.leading
209 opToMaybe props.leading
@@ -205,51 +228,118 @@ rawComponent =
205 setHasValue (v /= "")
228 setHasValue (v /= "")
206 )
229 )
207 onFocus = handler preventDefault (const $ unless hasFocus $ setHasFocus true)
230 onFocus = handler preventDefault (const $ unless hasFocus $ setHasFocus true)
208− inputWrapper =
231+ inputContainer =
209 M.div
232 M.div
210 </* M.motion
233 </* M.motion
211 { variants: M.variants containerVariants
234 { variants: M.variants containerVariants
212 , animate: M.animate if hasFocus then containerVariantLabels.focussed else containerVariantLabels.blurred
235 , animate: M.animate if hasFocus then containerVariantLabels.focussed else containerVariantLabels.blurred
213 }
236 }
214− { className: "ry-input-wrapper"
237+ { className: "ry-input-container"
215− , css: Style.inputWrapper
238+ , css: Style.inputContainer props
216 , _data:
239 , _data:
217 Object.fromHomogeneous
240 Object.fromHomogeneous
218 { "invalid": aria # Object.lookup "invalid" # fromMaybe ""
241 { "invalid": aria # Object.lookup "invalid" # fromMaybe ""
219 }
242 }
220 }
243 }
221 /> [ leading # foldMap \l -> div </ { ref: leftIconRef } /> [ l ]
244 /> [ leading # foldMap \l -> div </ { ref: leftIconRef } /> [ l ]
222− , emotionInput
245+ , rawInput
246+ </> ( props
247+ { onFocus = composeHandler props.onFocus onFocus
248+ , onBlur = composeHandler props.onBlur onBlur
249+ , ref = ref
250+ , placeholder = maybePlaceholder # maybeToOp # unsafeUnOptional
251+ , _aria =
252+ if props.label # opToMaybe # isJust then
253+ aria # Object.insert "labelledby" labelId
254+ else
255+ aria
256+ }
257+ )
223− ref
258+ -- ref
224− ( props { type = HTMLInput.toString <$> props.type # unsafeUnOptional }
259+ -- ( props { type = HTMLInput.toString <$> props.type # unsafeUnOptional }
225− # setOrDelete (SProxy ∷ _ "placeholder") (maybePlaceholder # maybeToOp)
260+ -- # setOrDelete (SProxy ∷ _ "placeholder") (maybePlaceholder # maybeToOp)
226− # setOrDelete (SProxy ∷ _ "value") (maybeValue # maybeToOp)
261+ -- # setOrDelete (SProxy ∷ _ "value") (maybeValue # maybeToOp)
227− )
262+ -- )
228− { className: "ry-input"
263+ -- { className: "ry-input"
229− , css: Style.input
264+ -- , css: Style.input
230− , onFocus: composeHandler props.onFocus onFocus
231− , onBlur: composeHandler props.onBlur onBlur
232− , _aria:
233− if props.label # opToMaybe # isJust then
234− aria # Object.insert "labelledby" labelId
235− else
236− aria
237− }
265+ -- }
238 , trailing # foldMap \t -> div </ {} /> [ t ]
266 , trailing # foldMap \t -> div </ {} /> [ t ]
239 ]
267 ]
240 pure
268 pure
241 $ case props.type # opToMaybe of
269 $ case props.type # opToMaybe of
242 Just HTMLInput.Password -> password </> props
270 Just HTMLInput.Password -> password </> props
243 _ -> case maybeLabelText of
271 _ -> case maybeLabelText of
244− Nothing -> inputWrapper
272+ Nothing -> inputContainer
245 Just labelText ->
273 Just labelText ->
246 div
274 div
247 </* { className: "ry-label-and-input-wrapper"
275 </* { className: "ry-label-and-input-wrapper"
248 , css: Style.labelAndInputWrapper
276 , css: Style.labelAndInputWrapper
249 }
277 }
250− /> [ inputWrapper
278+ /> [ inputContainer, mkLabel labelText ]
251− , mkLabel $ labelText
279+
280+inputLabel ∷
281+ ReactComponent
282+ { onClickLargeLabel ∷ EventHandler
283+ , isRequired ∷ Boolean
284+ , isInvalid ∷ Boolean
285+ , isFocussed ∷ Boolean
286+ , renderLargeLabel ∷ Boolean
287+ , leftIconRef ∷ NodeRef
288+ , inputRef ∷ NodeRef
289+ , labelId ∷ String
290+ , inputId ∷ String
291+ , labelText ∷ NonEmptyString
292+ }
293+inputLabel =
294+ unsafePerformEffect
295+ $ reactComponent "Password" \props -> React.do
296+ inputBbox /\ setInputBbox <- useState' (zero ∷ DOMRect)
297+ useEffectOnce do
298+ maybeBBox <- getBoundingBoxFromRef props.inputRef
299+ for_ maybeBBox setInputBbox
300+ mempty
301+ -- Left Icon
302+ leftIconBbox /\ setLeftIconBbox <- useState' Nothing
303+ useEffectAlways do
304+ when (leftIconBbox == Nothing) do
305+ maybeBBox <- getBoundingBoxFromRef props.leftIconRef
306+ for_ maybeBBox (setLeftIconBbox <<< Just)
307+ mempty
308+ let container = div </* { className: "ry-input-label-container", css: Style.labelContainer }
309+ let
310+ labelSpan =
311+ M.span
312+ </ { onClick: props.onClickLargeLabel
313+ , layout: M.layout true
314+ , layoutId: M.layoutId "ry-input-label-text"
315+ , htmlFor: props.inputId
316+ , id: props.labelId
317+ }
318+ pure
319+ $ container
320+ [ M.animateSharedLayout </ { type: M.switch }
321+ /> [ guard (inputBbox /= zero)
322+ $ M.div
323+ </* { className: if props.renderLargeLabel then "ry-input-label-large" else "ry-input-label-small"
324+ , layoutId: M.layoutId "ry-input-label"
325+ , css:
326+ if props.renderLargeLabel then
327+ Style.labelLarge { leftIconWidth: leftIconBbox <#> _.width, inputWidth: inputBbox.width }
328+ else
329+ Style.labelSmall
330+ , layout: M.layout true
331+ , transition: M.transition { duration: 0.18, ease: "easeOut" }
332+ , _data:
333+ Object.fromHomogeneous
334+ { "has-focus": show props.isFocussed
335+ , "invalid": show props.isInvalid
336+ , "required": show props.isRequired
337+ }
338+ , initial: M.initial false
339+ }
340+ /> [ labelSpan [ R.text $ NonEmptyString.toString props.labelText ] ]
252 ]
341 ]
342+ ]
253
343
254password ∷ ∀ p. ReactComponent { | p }
344password ∷ ∀ p. ReactComponent { | p }
255password =
345password =
@@ -298,16 +388,16 @@ password =
298 let leading = props.leading ?|| mempty
388 let leading = props.leading ?|| mempty
299 pure
389 pure
300 $ div
390 $ div
301− </* { className: "ry-input-wrapper"
391+ </* { className: "ry-input-wrapper", css: Style.inputContainer props }
302− , css: Style.inputWrapper
303− }
304 /> [ leading
392 /> [ leading
393+ , R.div' </ {}
305− , emotionInput
394+ /> [ emotionInput
306− ref
395+ ref
307− props { type = HTMLInput.toString <$> props.type # unsafeUnOptional }
396+ props { type = HTMLInput.toString <$> props.type # unsafeUnOptional }
308− { className: "ry-input"
397+ { className: "ry-input"
309− , css: Style.input
398+ , css: Style.input
310− , type: if hidePassword then "password" else "text"
399+ , type: if hidePassword then "password" else "text"
311− }
400+ }
401+ ]
312 , trailing
402 , trailing
313 ]
403 ]
src/Yoga/Block/Container/Style.purs+11
@@ -96,6 +96,17 @@ mkGlobal maybeMode =
96 nested
96 nested
97 $ css
97 $ css
98 { boxSizing: str "inherit"
98 { boxSizing: str "inherit"
99+ , fontFeatureSettings:
100+ str
101+ $ intercalate ","
102+ $ show
103+ <$> [ "ss03" -- curved r
104+ , "cv03" -- open six
105+ , "cv04" -- open nine
106+ -- , "cv05" -- lower case l with tail
107+ , "cv07" -- German double-s
108+ , "cv09" -- Flat top three
109+ ]
99 }
110 }
100 }
111 }
101
112