@@ -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:
59 { clipPath
61 { clipPath
60− , transition: { duration: 0.7 }
62+ , transition: { duration: 0.6 }
62 , blurred:
64 , blurred:
@@ -71,9 +73,6 @@ containerVariants =
71type Point =
73type Point =
72 { x ∷ Int, y ∷ Int }
74 { x ∷ Int, y ∷ Int }
74−p ∷ Int -> Int -> Point
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 ")"
85path ∷ Array Point
84path ∷ Array Point
86−path = mkPath 3 7
85+path = mkPath 4 8
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
99− [ {- ⌜ -} p 0 0
98+ [ {-⌜ -} p 0 0
100− , {- ⌞ -} p 0 100
99+ , {-⌞ -} p 0 100
101− , {- ⌟ -} p 100 100
103+ , {- ⌟-} p 100 100
102− , {- ⌝ -} p 100 0
104+ , {- ⌝-} p 100 0
103− , {- ⌜ -} p 0 0
108+ , {-⌜ -} p 0 0
105 inside <> outside <> (Array.reverse inside)
110 inside <> outside <> (Array.reverse inside)
112+ p ∷ Int -> Int -> Point
107containerVariantLabels ∷
115containerVariantLabels ∷
108 { blurred ∷ M.VariantLabel
116 { blurred ∷ M.VariantLabel
@@ -110,31 +118,67 @@ containerVariantLabels ∷
111containerVariantLabels = M.makeVariantLabels containerVariants
119containerVariantLabels = M.makeVariantLabels containerVariants
113−rawComponent ∷ ∀ p. ReactComponent { | p }
121+rawInput ∷ ∀ p. ReactComponent { | p }
114−rawComponent =
122+rawInput =
123+ mkForwardRefComponent "Input" do
124+ \(props ∷ { | PropsOptional }) ref -> React.do
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 }
132+rawContainer ∷ ∀ p. ReactComponent { | p }
115 mkForwardRefComponent "Input" do
134 mkForwardRefComponent "Input" do
136+ { children ∷ Array JSX
138+ , css ∷ OptionalProp Style
139+ , hasFocus ∷ Boolean
140+ , inputProps ∷ { | InputPropsF OptionalProp () }
142+ ) propsRef -> React.do
143+ -- Left icon width to correctly place the label
145+ aria ∷ Object String
146+ aria = props.inputProps._aria # opToMaybe # fold
150+ { variants: M.variants containerVariants
151+ , animate: M.animate if props.hasFocus then containerVariantLabels.focussed else containerVariantLabels.blurred
153+ { className: "ry-input-container"
154+ , css: Style.inputContainer props
156+ Object.fromHomogeneous
157+ { "invalid": aria # Object.lookup "invalid" # fromMaybe ""
163+ </* { className: "ry-label-and-input-wrapper"
164+ , css: Style.labelAndInputWrapper
166+ /> [ inputContainer, props.label ]
168+rawComponent ∷ ∀ p. ReactComponent (Record p)
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
122− maybeBBox <- getBoundingBoxFromRef ref
123− for_ maybeBBox setInputBbox
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
134− when (isJust (props.leading # opToMaybe) && leftIconBbox == Nothing) do
135− maybeBBox <- getBoundingBoxFromRef leftIconRef
136− for_ maybeBBox (setLeftIconBbox <<< Just)
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 ""))
@@ -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)
156− </* { className: if renderLargeLabel then "ry-input-label-large" else "ry-input-label-small"
157− , layoutId: M.layoutId "ry-input-label"
159− if renderLargeLabel then
160− Style.labelLarge { leftIconWidth: leftIconBbox <#> _.width }
163− , layout: M.layout true
164− , transition: M.transition { duration: 0.18, ease: "easeOut" }
166− Object.fromHomogeneous
167− { "has-focus": show hasFocus
168− , "invalid": aria # Object.lookup "invalid" # fromMaybe ""
169− , "required": aria # Object.lookup "required" # fromMaybe ""
171− , initial: M.initial false
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")
178− , id: labelId
202+ , labelId
179− }
203+ , inputId: props.id
180− /> [ R.text $ NonEmptyString.toString labelText ]
205+ , labelText
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 /= "")
207 onFocus = handler preventDefault (const $ unless hasFocus $ setHasFocus true)
230 onFocus = handler preventDefault (const $ unless hasFocus $ setHasFocus true)
208− inputWrapper =
231+ inputContainer =
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
214− { className: "ry-input-wrapper"
237+ { className: "ry-input-container"
215− , css: Style.inputWrapper
238+ , css: Style.inputContainer props
217 Object.fromHomogeneous
240 Object.fromHomogeneous
218 { "invalid": aria # Object.lookup "invalid" # fromMaybe ""
241 { "invalid": aria # Object.lookup "invalid" # fromMaybe ""
221 /> [ leading # foldMap \l -> div </ { ref: leftIconRef } /> [ l ]
244 /> [ leading # foldMap \l -> div </ { ref: leftIconRef } /> [ l ]
222− , emotionInput
245+ , rawInput
247+ { onFocus = composeHandler props.onFocus onFocus
248+ , onBlur = composeHandler props.onBlur onBlur
250+ , placeholder = maybePlaceholder # maybeToOp # unsafeUnOptional
252+ if props.label # opToMaybe # isJust then
253+ aria # Object.insert "labelledby" labelId
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)
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
233− if props.label # opToMaybe # isJust then
234− aria # Object.insert "labelledby" labelId
238 , trailing # foldMap \t -> div </ {} /> [ t ]
266 , trailing # foldMap \t -> div </ {} /> [ t ]
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 ->
247 </* { className: "ry-label-and-input-wrapper"
275 </* { className: "ry-label-and-input-wrapper"
248 , css: Style.labelAndInputWrapper
276 , css: Style.labelAndInputWrapper
250− /> [ inputWrapper
278+ /> [ inputContainer, mkLabel labelText ]
251− , mkLabel $ labelText
279+
282+ { onClickLargeLabel ∷ EventHandler
283+ , isRequired ∷ Boolean
284+ , isInvalid ∷ Boolean
285+ , isFocussed ∷ Boolean
286+ , renderLargeLabel ∷ Boolean
287+ , leftIconRef ∷ NodeRef
288+ , inputRef ∷ NodeRef
291+ , labelText ∷ NonEmptyString
295+ $ reactComponent "Password" \props -> React.do
296+ inputBbox /\ setInputBbox <- useState' (zero ∷ DOMRect)
298+ maybeBBox <- getBoundingBoxFromRef props.inputRef
299+ for_ maybeBBox setInputBbox
302+ leftIconBbox /\ setLeftIconBbox <- useState' Nothing
304+ when (leftIconBbox == Nothing) do
305+ maybeBBox <- getBoundingBoxFromRef props.leftIconRef
306+ for_ maybeBBox (setLeftIconBbox <<< Just)
308+ let container = div </* { className: "ry-input-label-container", css: Style.labelContainer }
312+ </ { onClick: props.onClickLargeLabel
313+ , layout: M.layout true
314+ , layoutId: M.layoutId "ry-input-label-text"
315+ , htmlFor: props.inputId
320+ [ M.animateSharedLayout </ { type: M.switch }
321+ /> [ guard (inputBbox /= zero)
323+ </* { className: if props.renderLargeLabel then "ry-input-label-large" else "ry-input-label-small"
324+ , layoutId: M.layoutId "ry-input-label"
326+ if props.renderLargeLabel then
327+ Style.labelLarge { leftIconWidth: leftIconBbox <#> _.width, inputWidth: inputBbox.width }
330+ , layout: M.layout true
331+ , transition: M.transition { duration: 0.18, ease: "easeOut" }
333+ Object.fromHomogeneous
334+ { "has-focus": show props.isFocussed
335+ , "invalid": show props.isInvalid
336+ , "required": show props.isRequired
338+ , initial: M.initial false
340+ /> [ labelSpan [ R.text $ NonEmptyString.toString props.labelText ] ]
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
301− </* { className: "ry-input-wrapper"
391+ </* { className: "ry-input-wrapper", css: Style.inputContainer props }
302− , css: Style.inputWrapper
304 /> [ leading
392 /> [ leading
305− , emotionInput
394+ /> [ emotionInput
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"
312 , trailing
402 , trailing