This commit is contained in:
Mark Eibes 2020-12-29 17:59:51 +01:00
parent cbfdcbf46a
commit d0bb8baf48
3 changed files with 222 additions and 4 deletions

View File

@ -0,0 +1,130 @@
module Yoga.Block.Atom.Input.View.Container where
import Yoga.Prelude.View
import Data.Array as Array
import Data.Interpolate (i)
import Foreign.Object as Object
import Framer.Motion as M
import Partial.Unsafe (unsafeCrashWith)
import React.Basic.DOM (CSS, css)
import React.Basic.Emotion (Style)
import Yoga.Block.Atom.Input.Style as Style
type PropsF f =
( css ∷ f Style
, isInvalid ∷ f Boolean
| Style.Props f (MandatoryProps ())
)
type MandatoryProps r =
( children ∷ Array JSX
, hasFocus ∷ Boolean
| r
)
type Props =
PropsF Id
type PropsOptional =
PropsF OptionalProp
component ∷ ∀ p q. Union p q Props => ReactComponent { | MandatoryProps p }
component = rawContainer
type Propski =
{ css ∷ OptionalProp Style
, hasFocus ∷ Boolean
, isInvalid ∷ OptionalProp Boolean
, children ∷ Array JSX
}
rawContainer ∷ ∀ p. ReactComponent { | p }
rawContainer =
mkForwardRefComponent "InputContainer" do
\(props ∷ { | PropsOptional }) ref -> React.do
let
result =
M.div
</* M.motion
{ variants: M.variants containerVariants
, animate: M.animate if props.hasFocus then containerVariantLabels.focussed else containerVariantLabels.blurred
}
{ className: "ry-input-container"
, css: Style.inputContainer props
, _data:
props.isInvalid
# foldMap \isInvalid ->
Object.fromHomogeneous
{ "invalid": show isInvalid
}
, ref
}
/> props.children
pure result
drawPathUntil ∷ Int -> Array Point -> String
drawPathUntil idx thePath = do
let fn { x, y } = i x "%" " " y "%"
let firstFew = Array.take idx thePath
let lastFew = Array.drop idx thePath $> (Array.last firstFew # fromMaybe' \_ -> unsafeCrashWith "ogod")
let rendered = intercalate "," $ fn <$> (firstFew <> lastFew)
i "polygon(" rendered ")"
path ∷ Array Point
path = mkPath 4 8
mkPath ∷ Int -> Int -> Array Point
mkPath borderX borderY = do
let
inside =
[ {- ⌜ -} p borderX borderY
, {- ⌞ -} p borderX (100 - borderY)
, {- ⌟ -} p (100 - borderX) (100 - borderY)
, {- ⌝ -} p (100 - borderX) borderY
, {- ⌜ -} p borderX borderY
]
outside =
[ {-⌜ -} p 0 0
, {-⌞ -} p 0 100
, {- . -} p 25 100
, {- . -} p 50 100
, {- . -} p 75 100
, {- ⌟-} p 100 100
, {- ⌝-} p 100 0
, {- ^ -} p 75 0
, {- ^ -} p 50 0
, {- ^ -} p 25 0
, {-⌜ -} p 0 0
]
inside <> outside <> (Array.reverse inside)
where
p ∷ Int -> Int -> Point
p x y = { x, y }
containerVariantLabels ∷
{ blurred ∷ M.VariantLabel
, focussed ∷ M.VariantLabel
}
containerVariantLabels = M.makeVariantLabels containerVariants
containerVariants ∷
{ blurred ∷ CSS
, focussed ∷ CSS
}
containerVariants =
{ focussed:
css
{ clipPath
, transition: { duration: 0.6 }
}
, blurred:
css
{ clipPath:
drawPathUntil (Array.length path + 1) path
}
}
where
clipPath = 6 Array... (Array.length path) <#> \ln -> drawPathUntil ln path
type Point =
{ x ∷ Int, y ∷ Int }

View File

@ -0,0 +1,86 @@
module Yoga.Block.Atom.Input.View.Label where
import Yoga.Prelude.View
import Data.String.NonEmpty (NonEmptyString)
import Data.String.NonEmpty as NonEmptyString
import Effect.Unsafe (unsafePerformEffect)
import Foreign.Object as Object
import Framer.Motion as M
import React.Basic.DOM as R
import React.Basic.Hooks (reactComponent)
import React.Basic.Hooks as React
import Yoga.Block.Atom.Input.Style as Style
type Props =
{ onClickLargeLabel ∷ EventHandler
, isRequired ∷ Boolean
, isInvalid ∷ Boolean
, isFocussed ∷ Boolean
, renderLargeLabel ∷ Boolean
, leftIconRef ∷ NodeRef
, inputRef ∷ NodeRef
, labelId ∷ String
, inputId ∷ String
, labelText ∷ NonEmptyString
}
component ∷ ReactComponent Props
component =
unsafePerformEffect
$ reactComponent "Input Label" \props -> React.do
-- Track input bounding box
inputBbox /\ setInputBbox <- useState' (zero ∷ DOMRect)
useEffectOnce do
maybeBBox <- getBoundingBoxFromRef props.inputRef
for_ maybeBBox setInputBbox
mempty
-- Left icon
leftIconBbox /\ setLeftIconBbox <- useState' Nothing
useEffectAlways do
when (leftIconBbox == Nothing) do
maybeBBox <- getBoundingBoxFromRef props.leftIconRef
for_ maybeBBox (setLeftIconBbox <<< Just)
mempty
-- UI
let
result =
container
[ sharedLayout
[ labelContainer
[ labelSpan
[ text ]
]
]
]
text = R.text $ NonEmptyString.toString props.labelText
sharedLayout = M.animateSharedLayout </ { type: M.switch }
container = div </* { className: "ry-input-label-container", css: Style.labelContainer }
labelContainer =
guard (inputBbox /= zero)
$ M.div
</* { className: if props.renderLargeLabel then "ry-input-label-large" else "ry-input-label-small"
, layoutId: M.layoutId "ry-input-label"
, css:
if props.renderLargeLabel then
Style.labelLarge { leftIconWidth: leftIconBbox <#> _.width, inputWidth: inputBbox.width }
else
Style.labelSmall
, layout: M.layout true
, transition: M.transition { duration: 0.18, ease: "easeOut" }
, _data:
Object.fromHomogeneous
{ "has-focus": show props.isFocussed
, "invalid": show props.isInvalid
, "required": show props.isRequired
}
, initial: M.initial false
}
labelSpan =
M.span
</ { onClick: props.onClickLargeLabel
, layout: M.layout true
, layoutId: M.layoutId "ry-input-label-text"
, htmlFor: props.inputId
, id: props.labelId
}
pure result

View File

@ -1,8 +1,9 @@
module Yoga.Block.Layout.Cluster.Spec where
import Yoga.Prelude.Spec
import Yoga.Block.Layout.Cluster as Cluster
import Foreign.Object as Object
import React.Basic.DOM as R
import Yoga.Block.Layout.Cluster as Cluster
spec ∷ Spec Unit
spec =
@ -12,10 +13,11 @@ spec =
void
$ renderComponent Cluster.component {}
it "accepts div props" do
{ findByText } <-
{ findByTestId } <-
renderComponent Cluster.component
{ role: "Heinz"
, _data: Object.fromHomogeneous { testid: "cluster" }
, children: [ R.text "Find me!" ]
}
elem <- findByText "Find me!"
elem `shouldHaveAttribute` "role"
elem <- findByTestId "cluster"
shouldHaveAttributeWithValue elem "role" "Heinz"