mirror of
https://github.com/rowtype-yoga/ry-blocks.git
synced 2024-09-11 21:38:06 +03:00
Popover work
This commit is contained in:
parent
c32928933f
commit
189267c594
@ -1,5 +1,5 @@
|
||||
for f in (ls src/Yoga/Block/Icon/SVG/*.svg)
|
||||
npx svgo -i $f
|
||||
svgo -i $f
|
||||
end
|
||||
node convert-svgs.js
|
||||
for f in (ls src/Yoga/Block/Icon/SVG/*.purs)
|
||||
|
@ -119,12 +119,12 @@ let additions =
|
||||
|
||||
|
||||
let upstream =
|
||||
https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20201217/packages.dhall sha256:f46d45e29977f3b57717b56d20a5ceac12532224516eea3012a4688f22ac1539
|
||||
https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20201222/packages.dhall sha256:620d0e4090cf1216b3bcbe7dd070b981a9f5578c38e810bbd71ece1794bfe13b
|
||||
|
||||
let overrides =
|
||||
{ spec-discovery = upstream.spec-discovery // { version = "master" }
|
||||
, react-basic = upstream.react-basic // { version = "main" }
|
||||
{ react-basic = upstream.react-basic // { version = "main" }
|
||||
, react-basic-hooks = upstream.react-basic-hooks // { version = "main " }
|
||||
, react-testing-library = upstream.react-testing-library // { version = "main" }
|
||||
}
|
||||
|
||||
let additions =
|
||||
@ -166,27 +166,6 @@ let additions =
|
||||
, repo = "https://github.com/jvliwanag/purescript-untagged-union.git"
|
||||
, version = "master"
|
||||
}
|
||||
, react-testing-library =
|
||||
{ dependencies =
|
||||
[ "aff-promise"
|
||||
, "console"
|
||||
, "debug"
|
||||
, "effect"
|
||||
, "foreign"
|
||||
, "foreign-object"
|
||||
, "psci-support"
|
||||
, "react-basic-dom"
|
||||
, "react-basic-hooks"
|
||||
, "remotedata"
|
||||
, "run"
|
||||
, "simple-json"
|
||||
, "spec"
|
||||
, "spec-discovery"
|
||||
]
|
||||
, repo =
|
||||
"https://github.com/i-am-the-slime/purescript-react-testing-library.git"
|
||||
, version = "7726ad443ad8c67f94932ab8934640df0ea39fb2"
|
||||
}
|
||||
, react-basic-dom =
|
||||
{ dependencies =
|
||||
[ "effect"
|
||||
|
@ -5,6 +5,7 @@ You can edit this file as you like.
|
||||
{ name = "ry-blocks"
|
||||
, dependencies =
|
||||
[ "console"
|
||||
, "debug"
|
||||
, "effect"
|
||||
, "heterogeneous"
|
||||
, "interpolate"
|
||||
|
@ -1,6 +1,7 @@
|
||||
const framerMotion = require("framer-motion")
|
||||
|
||||
exports.divImpl = framerMotion.motion.div
|
||||
exports.spanImpl = framerMotion.motion.span
|
||||
exports.buttonImpl = framerMotion.motion.button
|
||||
exports.h1Impl = framerMotion.motion.h1
|
||||
exports.svgImpl = framerMotion.motion.svg
|
||||
|
@ -7,7 +7,10 @@ module Framer.Motion
|
||||
, class EffectFnMaker
|
||||
, toEffectFn
|
||||
, OnHoverStart
|
||||
, span
|
||||
, onHoverStart
|
||||
, LayoutId
|
||||
, layoutId
|
||||
, customProp
|
||||
, OnHoverEnd
|
||||
, onHoverEnd
|
||||
@ -94,7 +97,7 @@ import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmapW
|
||||
import Literals.Undefined (Undefined)
|
||||
import Prim.Row (class Nub, class Union)
|
||||
import React.Basic (JSX, ReactComponent)
|
||||
import React.Basic.DOM (CSS, Props_div, Props_h1, Props_button, css)
|
||||
import React.Basic.DOM (CSS, Props_button, Props_div, Props_h1, Props_span, css)
|
||||
import React.Basic.DOM.Internal (SharedSVGProps)
|
||||
import React.Basic.DOM.SVG (Props_svg, Props_rect, Props_path)
|
||||
import React.Basic.Events (EventHandler)
|
||||
@ -111,6 +114,8 @@ import Yoga.Block.Internal (Id)
|
||||
|
||||
foreign import divImpl ∷ ∀ a. ReactComponent { | a }
|
||||
|
||||
foreign import spanImpl ∷ ∀ a. ReactComponent { | a }
|
||||
|
||||
foreign import buttonImpl ∷ ∀ a. ReactComponent { | a }
|
||||
|
||||
foreign import h1Impl ∷ ∀ a. ReactComponent { | a }
|
||||
@ -285,6 +290,12 @@ onDrag fn2 = cast (mkEffectFn2 fn2)
|
||||
customProp ∷ ∀ a. a -> Foreign
|
||||
customProp = unsafeToForeign
|
||||
|
||||
type LayoutId =
|
||||
String |+| Undefined
|
||||
|
||||
layoutId ∷ ∀ a. Castable a LayoutId => a -> LayoutId
|
||||
layoutId = cast
|
||||
|
||||
type MotionPropsF f r =
|
||||
( initial ∷ f Initial
|
||||
, animate ∷ f Animate
|
||||
@ -300,7 +311,7 @@ type MotionPropsF f r =
|
||||
, variants ∷ f Variants
|
||||
, transition ∷ f Transition
|
||||
, layout ∷ f Layout
|
||||
, layoutId ∷ f String |+| Undefined
|
||||
, layoutId ∷ f LayoutId
|
||||
, whileTap ∷ f WhileTap
|
||||
, onTap ∷ f OnTap
|
||||
, onTapStart ∷ f OnTapStart
|
||||
@ -360,6 +371,9 @@ makeVariantLabels = hmapWithIndex MakeVariantLabel
|
||||
div ∷ ∀ attrs attrs_. Union attrs attrs_ (MotionProps + Props_div) => ReactComponent { | attrs }
|
||||
div = divImpl
|
||||
|
||||
span ∷ ∀ attrs attrs_. Union attrs attrs_ (MotionProps + Props_span) => ReactComponent { | attrs }
|
||||
span = spanImpl
|
||||
|
||||
button ∷ ∀ attrs attrs_. Union attrs attrs_ (MotionProps + Props_button) => ReactComponent { | attrs }
|
||||
button = buttonImpl
|
||||
|
||||
|
@ -37,4 +37,5 @@ modifierOffset { x, y } = unsafeCoerce { name: "offset", options: { offset: [ x,
|
||||
type Options =
|
||||
( modifiers ∷ Array Modifier
|
||||
, strategy ∷ String
|
||||
, placement ∷ String
|
||||
)
|
||||
|
@ -19,6 +19,9 @@ span props =
|
||||
css
|
||||
{ "--stroke-colour": (props.stroke <|> props.colour) ?|| (str colour.text)
|
||||
, "--fill-colour": (props.fill <|> props.colour) ?|| (str "transparent")
|
||||
, display: inlineFlex
|
||||
, justifyContent: center
|
||||
, alignItems: center
|
||||
, "& > svg":
|
||||
nest
|
||||
{ width: (props.width <|> props.size) ?|| (str "auto")
|
||||
|
36
src/Yoga/Block/Atom/Input/Placement/Types.purs
Normal file
36
src/Yoga/Block/Atom/Input/Placement/Types.purs
Normal file
@ -0,0 +1,36 @@
|
||||
module Yoga.Block.Atom.Input.Placement.Types (render, Placement(..), Primary(..), Secondary(..)) where
|
||||
|
||||
import Prelude
|
||||
import Data.Foldable (foldMap)
|
||||
import Data.Maybe (Maybe)
|
||||
|
||||
data Placement
|
||||
= Placement Primary (Maybe Secondary)
|
||||
|
||||
render ∷ Placement -> String
|
||||
render (Placement primary maybeSecondary) = do
|
||||
renderPrimary primary <> foldMap ("-" <> _) (renderSecondary <$> maybeSecondary)
|
||||
|
||||
data Primary
|
||||
= Auto
|
||||
| Left
|
||||
| Right
|
||||
| Top
|
||||
| Bottom
|
||||
|
||||
data Secondary
|
||||
= Start
|
||||
| End
|
||||
|
||||
renderPrimary ∷ Primary -> String
|
||||
renderPrimary = case _ of
|
||||
Auto -> "auto"
|
||||
Left -> "left"
|
||||
Right -> "right"
|
||||
Top -> "top"
|
||||
Bottom -> "bottom"
|
||||
|
||||
renderSecondary ∷ Secondary -> String
|
||||
renderSecondary = case _ of
|
||||
Start -> "start"
|
||||
End -> "end"
|
@ -1,13 +1,19 @@
|
||||
module Yoga.Block.Atom.Input.Story where
|
||||
|
||||
import Prelude
|
||||
import Data.String.NonEmpty.Internal (NonEmptyString(..), nes)
|
||||
import Data.Symbol (SProxy(..))
|
||||
import Effect (Effect)
|
||||
import Effect.Unsafe (unsafePerformEffect)
|
||||
import Foreign.Object as Object
|
||||
import React.Basic (JSX, element, fragment)
|
||||
import React.Basic.DOM as R
|
||||
import React.Basic.Emotion as E
|
||||
import React.Basic.Events (handler_)
|
||||
import Yoga (el)
|
||||
import Yoga.Block as Block
|
||||
import Yoga.Block.Atom.Input as Input
|
||||
import Yoga.Block.Atom.Input.Types as HTMLInput
|
||||
import Yoga.Block.Container.Style as Styles
|
||||
|
||||
default ∷
|
||||
@ -30,27 +36,56 @@ input = do
|
||||
pure
|
||||
$ fragment
|
||||
[ R.div_
|
||||
[ R.h2_ [ R.text "Generic Input" ]
|
||||
[ R.h1_ [ R.text "Input Examples" ]
|
||||
, R.h2_ [ R.text "Generic Input" ]
|
||||
, element Input.component { value: "A Generic Input", onChange: handler_ mempty }
|
||||
, R.h2_ [ R.text "Validation on text Input" ]
|
||||
, element Input.component { type: HTMLInput.Text, label: nes (SProxy ∷ _ "Is undefined a function?"), value: "Yes, why not?", onChange: handler_ mempty, _aria: Object.singleton "invalid" "true" }
|
||||
, element Input.component { type: HTMLInput.Text, label: nes (SProxy ∷ _ "What's the type of null?"), value: "object", onChange: handler_ mempty, _aria: Object.singleton "invalid" "false" }
|
||||
, R.h2_ [ R.text "Required fields" ]
|
||||
, element Input.component
|
||||
{ type: HTMLInput.Text
|
||||
, label: nes (SProxy ∷ _ "I am so important")
|
||||
, _aria: Object.singleton "required" "true"
|
||||
}
|
||||
, R.h3_ [ R.text "With a label" ]
|
||||
, el Block.cluster {}
|
||||
[ el R.form' {}
|
||||
[ element Input.component { label: nes (SProxy ∷ _ "This has a label"), value: "And text", onChange: handler_ mempty }
|
||||
, element Input.component { label: nes (SProxy ∷ _ "This has a label"), value: "", onChange: handler_ mempty }
|
||||
, element Input.component { label: nes (SProxy ∷ _ "This has a label"), placeholder: "A very long placeholder, too..." }
|
||||
, element Input.component { label: nes (SProxy ∷ _ "Pig nose"), leading: R.text "🐽🤣" }
|
||||
, element Input.component { label: nes (SProxy ∷ _ "Pig nose"), trailing: R.text "🤫" }
|
||||
, element Input.component { label: nes (SProxy ∷ _ "Pig nose"), leading: R.text "🌭" }
|
||||
, element Input.component { label: nes (SProxy ∷ _ "Pig nose"), leading: R.text "⭐", trailing: R.text "🔮" }
|
||||
]
|
||||
]
|
||||
, R.h2_ [ R.text "[BUG] Overflowing label" ]
|
||||
, element Input.component
|
||||
{ type: HTMLInput.Text
|
||||
, label: nes (SProxy ∷ _ "Is undefined really a function?")
|
||||
}
|
||||
, R.h2_ [ R.text "Password" ]
|
||||
, element Input.component { type: "password" }
|
||||
, element Input.component { type: HTMLInput.Password }
|
||||
, R.h2_ [ R.text "Text Input" ]
|
||||
, element Input.component { type: "text", value: "Some text", onChange: handler_ mempty }
|
||||
, element Input.component { type: HTMLInput.Text, value: "Some text", onChange: handler_ mempty }
|
||||
, element Input.component { type: HTMLInput.Text, placeholder: "Placeholder", onChange: handler_ mempty }
|
||||
, element Input.component { type: HTMLInput.Text, value: "", label: NonEmptyString "Heinzi", onChange: handler_ mempty }
|
||||
, R.h2_ [ R.text "Search Input" ]
|
||||
, element Input.component { type: "search", value: "Search...", onChange: handler_ mempty }
|
||||
, element Input.component { type: HTMLInput.Search, placeholder: "Search...", onChange: handler_ mempty }
|
||||
, R.h2_ [ R.text "Button" ]
|
||||
, element Input.component { type: "button", value: "A button", onChange: handler_ mempty }
|
||||
, element Input.component { type: HTMLInput.Button, value: "A button", onChange: handler_ mempty }
|
||||
, R.h2_ [ R.text "Submit" ]
|
||||
, element Input.component { type: "submit" }
|
||||
, R.h2_ [ R.text "Radio" ]
|
||||
, element Input.component { type: "radio" }
|
||||
, R.h2_ [ R.text "Checkbox" ]
|
||||
, element Input.component { type: "checkbox" }
|
||||
, R.h2_ [ R.text "File" ]
|
||||
, element Input.component { type: "file" }
|
||||
, R.h2_ [ R.text "Image" ]
|
||||
, element Input.component { type: "image" }
|
||||
, R.h2_ [ R.text "Number" ]
|
||||
, element Input.component { type: "number" }
|
||||
-- , element Input.component { type: "submit" }
|
||||
-- , R.h2_ [ R.text "Radio" ]
|
||||
-- , element Input.component { type: "radio" }
|
||||
-- , R.h2_ [ R.text "Checkbox" ]
|
||||
-- , element Input.component { type: "checkbox" }
|
||||
-- , R.h2_ [ R.text "File" ]
|
||||
-- , element Input.component { type: "file" }
|
||||
-- , R.h2_ [ R.text "Image" ]
|
||||
-- , element Input.component { type: "image" }
|
||||
-- , R.h2_ [ R.text "Number" ]
|
||||
-- , element Input.component { type: "number" }
|
||||
]
|
||||
]
|
||||
|
@ -1,8 +1,8 @@
|
||||
module Yoga.Block.Atom.Input.Style where
|
||||
|
||||
import Yoga.Prelude.Style
|
||||
import Data.Interpolate (i)
|
||||
import Yoga.Block.Container.Style (colour)
|
||||
import Yoga.Prelude.Style as Color
|
||||
|
||||
type Props f r =
|
||||
( css ∷ f Style
|
||||
@ -15,41 +15,164 @@ leftIconSize = var "--left-icon-size"
|
||||
rightIconSize ∷ StyleProperty
|
||||
rightIconSize = var "--right-icon-size"
|
||||
|
||||
leftIconStyle ∷ Style
|
||||
leftIconStyle =
|
||||
gapSize ∷ String
|
||||
gapSize = "var(--s-3)"
|
||||
|
||||
iconContainer ∷ Style
|
||||
iconContainer =
|
||||
css
|
||||
{ "--stroke-colour": str colour.interfaceTextDisabled
|
||||
, marginTop: str "4px"
|
||||
, marginLeft: str "-4px"
|
||||
{ display: inlineFlex
|
||||
, alignItems: center
|
||||
-- , padding: str "0px 10px"
|
||||
-- , background: str $ colour.interfaceBackground
|
||||
-- , borderLeft: str $ "1px solid " <> colour.interfaceBackgroundShadow
|
||||
}
|
||||
|
||||
rightIconStyle ∷ Style
|
||||
rightIconStyle =
|
||||
labelAndInputWrapper ∷ Style
|
||||
labelAndInputWrapper =
|
||||
css
|
||||
{ "--stroke-colour": str colour.interfaceTextDisabled
|
||||
, paddingTop: str "6px"
|
||||
, marginRight: str "6px"
|
||||
{ position: relative
|
||||
, display: inlineBlock
|
||||
, "--left-icon-size": var "--s0"
|
||||
, "--right-icon-size": str "calc(var(--s0) * 1.2)"
|
||||
, "--input-border-radius": var "--s-1"
|
||||
, "--input-side-padding": var "--s-1"
|
||||
}
|
||||
|
||||
textWrapper ∷ ∀ r. { | Props OptionalProp r } -> Style
|
||||
textWrapper props =
|
||||
labelContainer ∷ Style
|
||||
labelContainer =
|
||||
css
|
||||
{ fontFamily: var "--main-font"
|
||||
{ position: absolute
|
||||
, top: _0
|
||||
, left: _0
|
||||
, display: inlineBlock
|
||||
, zIndex: str "2"
|
||||
, pointerEvents: none
|
||||
}
|
||||
|
||||
labelSmall ∷ Style
|
||||
labelSmall =
|
||||
css
|
||||
{ fontSize: var "--s-1"
|
||||
, marginTop: str "calc(var(--s-3) * -1)"
|
||||
, marginLeft: var "--s-2"
|
||||
, """&[data-required="true"] > span:after""":
|
||||
nest
|
||||
{ content: str "'*'"
|
||||
}
|
||||
, "& > span":
|
||||
nest
|
||||
{ fontWeight: str "500"
|
||||
, background: str colour.inputBackground
|
||||
, color: str colour.text
|
||||
, borderRadius: var "--s-4"
|
||||
, paddingLeft: var "--s-4"
|
||||
, paddingRight: var "--s-4"
|
||||
, paddingTop: var "--s-5"
|
||||
, paddingBottom: var "--s-5"
|
||||
}
|
||||
, """&[data-invalid="true"] > span""":
|
||||
nest
|
||||
{ background: str colour.invalid
|
||||
, color: str colour.invalidText
|
||||
}
|
||||
, """&[data-has-focus="true"] > span""":
|
||||
nest
|
||||
{ background: str colour.highlight
|
||||
, color: str colour.highlightText
|
||||
}
|
||||
}
|
||||
|
||||
labelLarge ∷ { leftIconWidth ∷ Maybe Number } -> Style
|
||||
labelLarge { leftIconWidth } =
|
||||
css
|
||||
{ fontSize: str "calc(var(--s0) * 0.85)"
|
||||
, padding: _0
|
||||
, marginTop: str "calc(var(--s-1) + var(--s-5))"
|
||||
, marginBottom: str "calc(var(--s-1) + var(--s-5))"
|
||||
, marginLeft:
|
||||
case leftIconWidth of
|
||||
Nothing -> var "--input-side-padding"
|
||||
Just size -> str $ i "calc(var(--input-side-padding) + " gapSize " + " size "px)"
|
||||
, marginRight: str "var(--input-side-padding)"
|
||||
, color: str colour.placeholderText
|
||||
, fontWeight: str "400"
|
||||
, whiteSpace: nowrap -- force on one line
|
||||
, """&[data-required="true"]:after""":
|
||||
nest
|
||||
{ content: str "'*'"
|
||||
, color: str colour.required
|
||||
, fontFamily: str "Helvetica, Arial, Inter, sans-serif"
|
||||
, fontSize: str "calc(var(--s0))"
|
||||
}
|
||||
}
|
||||
|
||||
rightIconContainer ∷ Style
|
||||
rightIconContainer =
|
||||
iconContainer
|
||||
<> css
|
||||
{ display: inlineFlex
|
||||
, alignItems: center
|
||||
, justifyContent: center
|
||||
, "& > *":
|
||||
nest
|
||||
{ display: inlineFlex
|
||||
, alignItems: center
|
||||
, justifyContent: center
|
||||
}
|
||||
, ".ry-icon":
|
||||
nest
|
||||
{ "--stroke-colour": str colour.highlight
|
||||
}
|
||||
}
|
||||
|
||||
leftIconContainer ∷ Style
|
||||
leftIconContainer =
|
||||
iconContainer
|
||||
<> css
|
||||
{ borderRadius: str "var(--input-border-radius) 0 0 var(--input-border-radius)"
|
||||
, ".ry-icon":
|
||||
nest
|
||||
{ "--stroke-colour": str colour.text
|
||||
}
|
||||
}
|
||||
|
||||
inputWrapper ∷ ∀ r. { | Props OptionalProp r } -> Style
|
||||
inputWrapper props =
|
||||
css
|
||||
{ position: relative
|
||||
, boxSizing: borderBox
|
||||
, backgroundColor: str $ colour.background07
|
||||
, backgroundColor: str colour.inputBackground
|
||||
, display: inlineFlex
|
||||
, "--left-icon-size": var "--s0"
|
||||
, "--right-icon-size": str "calc(var(--s0) * 1.2)"
|
||||
, "--input-border-radius": var "--s-1"
|
||||
, "--input-side-padding": var "--s-1"
|
||||
, "--input-top-padding": var "--s-5"
|
||||
, "--input-bottom-padding": var "--s-5"
|
||||
, alignItems: center
|
||||
, justifyContent: flexStart
|
||||
, paddingLeft: var "--s-1"
|
||||
, gap: var "--s-3"
|
||||
, "--border-width": var "--s-5"
|
||||
, paddingLeft: str "calc(var(--input-side-padding) - var(--border-width))"
|
||||
, paddingRight: str "calc(var(--input-side-padding) - var(--border-width))"
|
||||
, paddingTop: str "calc(var(--input-top-padding) - var(--border-width))"
|
||||
, paddingBottom: str "calc(var(--input-bottom-padding) - var(--border-width))"
|
||||
, gap: str "calc(var(--input-side-padding) / 2)"
|
||||
, "--border-width": str "1px"
|
||||
, border: str $ "var(--border-width) solid " <> colour.inputBorder
|
||||
, borderRadius: var "--s-1"
|
||||
, borderRadius: var "--input-border-radius"
|
||||
, """&[data-invalid="false"]""":
|
||||
nest
|
||||
{ borderColor: str colour.success
|
||||
}
|
||||
, """&[data-invalid="true"]""":
|
||||
nest
|
||||
{ borderColor: str colour.invalid
|
||||
}
|
||||
, "&:focus-within":
|
||||
nest
|
||||
{}
|
||||
{ "--border-width": str "var(--s-5)"
|
||||
, borderColor: str colour.highlight
|
||||
}
|
||||
}
|
||||
|
||||
input ∷ ∀ r. { | Props OptionalProp r } -> Style
|
||||
@ -58,15 +181,23 @@ input props =
|
||||
{ "&[type=text],&[type=search],&[type=password],&[type=number],&:not([type])":
|
||||
nest
|
||||
{ color: str colour.text
|
||||
, width: _100percent
|
||||
, flex: str "1"
|
||||
, "--padding-top": var "--s-1"
|
||||
, "--padding-bottom": var "--s-1"
|
||||
, "&[aria-labelledby]":
|
||||
nest
|
||||
{ paddingTop: str "calc(var(--padding-top) + (var(--s-5)/2))"
|
||||
, paddingBottom: str "calc(var(--padding-bottom) - (var(--s-5)/2))"
|
||||
}
|
||||
, background: str "transparent"
|
||||
, alignSelf: stretch
|
||||
, "--padding-top": str "var(--s-1)"
|
||||
, "--padding-bottom": str "calc(var(--padding-top) * 0.85)"
|
||||
, margin: _0
|
||||
, paddingTop: var "--padding-top"
|
||||
, paddingBottom: var "--padding-bottom"
|
||||
, paddingLeft: _0
|
||||
, paddingRight: _0
|
||||
-- , background: str colour.inputBackground
|
||||
, fontSize: str "calc(var(--s0) * 0.85)"
|
||||
, gap: str gapSize
|
||||
, border: none
|
||||
}
|
||||
, "&[type=search]":
|
||||
|
54
src/Yoga/Block/Atom/Input/Types.purs
Normal file
54
src/Yoga/Block/Atom/Input/Types.purs
Normal file
@ -0,0 +1,54 @@
|
||||
module Yoga.Block.Atom.Input.Types where
|
||||
|
||||
import Prelude
|
||||
|
||||
data HTMLInput
|
||||
= Button
|
||||
| Checkbox
|
||||
| Color
|
||||
| Date
|
||||
| DatetimeLocal
|
||||
| Email
|
||||
| File
|
||||
| Hidden
|
||||
| Image
|
||||
| Month
|
||||
| Number
|
||||
| Password
|
||||
| Radio
|
||||
| Range
|
||||
| Reset
|
||||
| Search
|
||||
| Submit
|
||||
| Tel
|
||||
| Text
|
||||
| Time
|
||||
| Url
|
||||
| Week
|
||||
|
||||
derive instance eqHTMLInput ∷ Eq HTMLInput
|
||||
|
||||
toString ∷ HTMLInput -> String
|
||||
toString = case _ of
|
||||
Button -> "button"
|
||||
Checkbox -> "checkbox"
|
||||
Color -> "color"
|
||||
Date -> "date"
|
||||
DatetimeLocal -> "datetime-local"
|
||||
Email -> "email"
|
||||
File -> "file"
|
||||
Hidden -> "hidden"
|
||||
Image -> "image"
|
||||
Month -> "month"
|
||||
Number -> "number"
|
||||
Password -> "password"
|
||||
Radio -> "radio"
|
||||
Range -> "range"
|
||||
Reset -> "reset"
|
||||
Search -> "search"
|
||||
Submit -> "submit"
|
||||
Tel -> "tel"
|
||||
Text -> "text"
|
||||
Time -> "time"
|
||||
Url -> "url"
|
||||
Week -> "week"
|
@ -1,21 +1,27 @@
|
||||
module Yoga.Block.Atom.Input.View where
|
||||
|
||||
import Yoga.Prelude.View
|
||||
import Data.String.NonEmpty (NonEmptyString)
|
||||
import Data.String.NonEmpty as NonEmptyString
|
||||
import Data.Symbol (SProxy(..))
|
||||
import Foreign.Object (Object)
|
||||
import Foreign.Object as Object
|
||||
import Framer.Motion as M
|
||||
import React.Basic.DOM (css)
|
||||
import React.Basic.DOM as R
|
||||
import React.Basic.DOM.SVG as SVG
|
||||
import React.Basic.Hooks as React
|
||||
import Record.Extra (pick)
|
||||
import Unsafe.Coerce (unsafeCoerce)
|
||||
import Web.HTML.HTMLInputElement as InputElement
|
||||
import Yoga.Block.Atom.Icon as Icon
|
||||
import Yoga.Block.Atom.Input.Style as Style
|
||||
import Yoga.Block.Atom.Tooltip as Tooltip
|
||||
import Yoga.Block.Atom.Input.Types (HTMLInput)
|
||||
import Yoga.Block.Atom.Input.Types as HTMLInput
|
||||
import Yoga.Block.Icon.SVG as SVGIcon
|
||||
|
||||
type PropsF f =
|
||||
( iconLeft ∷ f JSX
|
||||
, iconRight ∷ f JSX
|
||||
( leading ∷ f JSX
|
||||
, trailing ∷ f JSX
|
||||
, label ∷ f NonEmptyString
|
||||
, type ∷ f HTMLInput
|
||||
| Style.Props f InputProps
|
||||
)
|
||||
|
||||
@ -30,51 +36,154 @@ component = rawComponent
|
||||
|
||||
mkLeftIcon ∷ JSX -> JSX
|
||||
mkLeftIcon icon =
|
||||
styledLeaf
|
||||
Icon.component
|
||||
{ size: Style.leftIconSize
|
||||
, className: "ry-input-right-icon"
|
||||
, css: Style.leftIconStyle
|
||||
, icon
|
||||
}
|
||||
|
||||
mkRightIcon ∷ JSX -> JSX
|
||||
mkRightIcon icon =
|
||||
styledLeaf Icon.component
|
||||
{ size: Style.rightIconSize
|
||||
, className: "ry-input-left-icon"
|
||||
, css: Style.rightIconStyle
|
||||
, icon
|
||||
styled R.div'
|
||||
{ className: "ry-input-left-icon-container"
|
||||
, css: Style.leftIconContainer
|
||||
}
|
||||
[ el_
|
||||
Icon.component
|
||||
{ size: Style.leftIconSize
|
||||
, icon
|
||||
}
|
||||
]
|
||||
|
||||
rawComponent ∷ ∀ p. ReactComponent { | p }
|
||||
rawComponent =
|
||||
mkForwardRefComponent "Input" do
|
||||
\(props ∷ { | PropsOptional }) ref -> React.do
|
||||
\(props ∷ { | PropsOptional }) propsRef -> React.do
|
||||
inputBbox /\ setInputBbox <- useState' (zero ∷ DOMRect)
|
||||
hasFocus /\ setHasFocus <- useState' false
|
||||
backupRef ∷ NodeRef <- useRef null -- [TODO] test this
|
||||
let ref = forwardedRefAsMaybe propsRef # fromMaybe backupRef
|
||||
useEffectOnce do
|
||||
maybeBBox <- getBoundingBoxFromRef ref
|
||||
for_ maybeBBox setInputBbox
|
||||
mempty
|
||||
let
|
||||
iconLeft =
|
||||
props.iconLeft
|
||||
?|| if (((cast props.type) ?|| "") == "search") then mkLeftIcon SVGIcon.magnifyingGlass else mempty
|
||||
iconRight =
|
||||
props.iconRight
|
||||
?|| if (((cast props.type) ?|| "") == "password") then mkRightIcon SVGIcon.eyeOpen else mempty
|
||||
pure
|
||||
$ case props.type # cast # opToMaybe of
|
||||
Just "password" -> el_ password props
|
||||
_ ->
|
||||
styled R.div'
|
||||
{ className: "ry-input-wrapper"
|
||||
, css: Style.textWrapper props
|
||||
}
|
||||
[ iconLeft
|
||||
, emotionInput
|
||||
ref
|
||||
props
|
||||
{ className: "ry-input"
|
||||
, css: Style.input props
|
||||
}
|
||||
, iconRight
|
||||
focusInput = do
|
||||
unless hasFocus do
|
||||
maybeHTMLElement <- getHTMLElementFromRef ref
|
||||
for_ maybeHTMLElement focus
|
||||
-- Left icon width to correctly place the label
|
||||
leftIconRef ∷ NodeRef <- useRef null
|
||||
leftIconBbox /\ setLeftIconBbox <- useState' Nothing
|
||||
useEffectAlways do
|
||||
when (isJust (props.leading # opToMaybe) && leftIconBbox == Nothing) do
|
||||
maybeBBox <- getBoundingBoxFromRef leftIconRef
|
||||
for_ maybeBBox (setLeftIconBbox <<< Just)
|
||||
mempty
|
||||
let (maybeValue ∷ Maybe String) = cast props.value # opToMaybe
|
||||
hasValue /\ setHasValue <- useState' ((maybeValue # isJust) && (maybeValue /= Just ""))
|
||||
let
|
||||
aria ∷ Object String
|
||||
aria = props._aria # cast # opToMaybe # fold
|
||||
labelId ∷ String
|
||||
labelId = props.id # cast # opToMaybe # fold # (_ <> "-label")
|
||||
renderLargeLabel ∷ Boolean
|
||||
renderLargeLabel = not hasFocus && not hasValue
|
||||
maybeLabelText ∷ Maybe NonEmptyString
|
||||
maybeLabelText = props.label # opToMaybe
|
||||
mkLabel ∷ NonEmptyString -> JSX
|
||||
mkLabel labelText =
|
||||
styled R.div'
|
||||
{ className: "ry-input-label-container"
|
||||
, css: Style.labelContainer
|
||||
}
|
||||
[ el M.animateSharedLayout
|
||||
{ type: M.switch }
|
||||
[ guard (inputBbox /= zero)
|
||||
$ styled M.div
|
||||
{ className: if renderLargeLabel then "ry-input-label-large" else "ry-input-label-small"
|
||||
, layoutId: M.layoutId "ry-input-label"
|
||||
, css:
|
||||
if renderLargeLabel then
|
||||
Style.labelLarge { leftIconWidth: leftIconBbox <#> _.width }
|
||||
else
|
||||
Style.labelSmall
|
||||
, layout: M.layout true
|
||||
, transition: M.transition { duration: 0.18, ease: "easeOut" }
|
||||
, _data:
|
||||
Object.fromHomogeneous
|
||||
{ "has-focus": show hasFocus
|
||||
, "invalid": aria # Object.lookup "invalid" # fromMaybe ""
|
||||
, "required": aria # Object.lookup "required" # fromMaybe ""
|
||||
}
|
||||
, initial: M.initial false
|
||||
}
|
||||
[ el M.span
|
||||
{ onClick: handler preventDefault (const focusInput)
|
||||
, layout: M.layout true
|
||||
, layoutId: M.layoutId "ry-input-label-text"
|
||||
, htmlFor: props.id
|
||||
, id: labelId
|
||||
}
|
||||
[ R.text $ NonEmptyString.toString labelText ]
|
||||
]
|
||||
]
|
||||
]
|
||||
leading ∷ Maybe JSX
|
||||
leading =
|
||||
opToMaybe props.leading
|
||||
<|> if (props.type <#> (_ == HTMLInput.Search)) ?|| false then
|
||||
Just $ mkLeftIcon SVGIcon.magnifyingGlass
|
||||
else
|
||||
Nothing
|
||||
trailing ∷ Maybe JSX
|
||||
trailing = opToMaybe props.trailing
|
||||
maybePlaceholder ∷ Maybe String
|
||||
maybePlaceholder = do
|
||||
given <- props.placeholder # cast # opToMaybe
|
||||
if isJust maybeLabelText && hasFocus then Just given else Nothing
|
||||
inputWrapper =
|
||||
styled R.div'
|
||||
{ className: "ry-input-wrapper"
|
||||
, css: Style.inputWrapper props
|
||||
, _data:
|
||||
Object.fromHomogeneous
|
||||
{ "invalid": aria # Object.lookup "invalid" # fromMaybe ""
|
||||
}
|
||||
}
|
||||
[ leading # foldMap \l -> el R.div' { ref: leftIconRef } [ l ]
|
||||
, emotionInput
|
||||
ref
|
||||
( props { type = HTMLInput.toString <$> props.type # unsafeUnOptional }
|
||||
# setOrDelete (SProxy ∷ _ "placeholder") (maybePlaceholder # maybeToOp)
|
||||
# setOrDelete (SProxy ∷ _ "value") (maybeValue # maybeToOp)
|
||||
)
|
||||
{ className: "ry-input"
|
||||
, css: Style.input props
|
||||
, onFocus: handler preventDefault (const $ unless hasFocus $ setHasFocus true)
|
||||
, onBlur:
|
||||
handler preventDefault
|
||||
( const do
|
||||
when hasFocus $ setHasFocus false
|
||||
el <- getHTMLElementFromRef ref
|
||||
let inputEl = InputElement.fromHTMLElement =<< el
|
||||
for_ inputEl \ie -> do
|
||||
v <- InputElement.value ie
|
||||
setHasValue (v /= "")
|
||||
)
|
||||
, _aria:
|
||||
if props.label # opToMaybe # isJust then
|
||||
aria # Object.insert "labelledby" labelId
|
||||
else
|
||||
aria
|
||||
}
|
||||
, trailing # foldMap \t -> el R.div' {} [ t ]
|
||||
]
|
||||
pure
|
||||
$ case props.type # opToMaybe of
|
||||
Just HTMLInput.Password -> el_ password props
|
||||
_ -> case maybeLabelText of
|
||||
Nothing -> inputWrapper
|
||||
Just labelText ->
|
||||
styled R.div'
|
||||
{ className: "ry-label-and-input-wrapper"
|
||||
, css: Style.labelAndInputWrapper
|
||||
}
|
||||
[ inputWrapper
|
||||
, mkLabel $ labelText
|
||||
]
|
||||
|
||||
password ∷ ∀ p. ReactComponent { | p }
|
||||
password =
|
||||
@ -83,60 +192,56 @@ password =
|
||||
hidePassword /\ modifyHidePassword <- useState true
|
||||
let
|
||||
eyeCon =
|
||||
el_ Tooltip.component
|
||||
{ target:
|
||||
el R.div'
|
||||
{ onClick: handler preventDefault \_ -> modifyHidePassword not
|
||||
}
|
||||
[ el M.animatePresence
|
||||
{ exitBeforeEnter: true
|
||||
}
|
||||
[ if hidePassword then
|
||||
el M.div
|
||||
{ key: "eyeOpen"
|
||||
, initial: M.initial $ css { scaleY: 0 }
|
||||
, animate: M.animate $ css { scaleY: 1 }
|
||||
, exit: M.exit $ css { scaleY: 0.9 }
|
||||
, transition: M.transition { duration: 0.15 }
|
||||
}
|
||||
[ styledLeaf Icon.component
|
||||
{ size: Style.rightIconSize
|
||||
, className: "ry-input-left-icon"
|
||||
, css: Style.rightIconStyle
|
||||
, icon: SVGIcon.eyeOpen
|
||||
}
|
||||
]
|
||||
else
|
||||
el M.div
|
||||
{ key: "eyeClosed"
|
||||
, initial: M.initial $ css { scaleY: 0.0 }
|
||||
, animate: M.animate $ css { scaleY: 1 }
|
||||
, exit: M.exit $ css { scaleY: 0.9 }
|
||||
, transition: M.transition { duration: 0.15 }
|
||||
}
|
||||
[ styledLeaf Icon.component
|
||||
{ size: Style.rightIconSize
|
||||
, className: "ry-input-left-icon"
|
||||
, css: Style.rightIconStyle
|
||||
, icon: SVGIcon.eyeClosed
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
, theTip: R.text if hidePassword then "Show password" else "Hide password"
|
||||
styled R.div'
|
||||
{ onClick: handler preventDefault \_ -> modifyHidePassword not
|
||||
, className: "ry-input-right-icon-container"
|
||||
, css: Style.rightIconContainer
|
||||
}
|
||||
let iconRight = props.iconRight ?|| eyeCon
|
||||
[ el M.animatePresence
|
||||
{ exitBeforeEnter: true
|
||||
}
|
||||
[ if hidePassword then
|
||||
el M.div
|
||||
{ key: "eyeOpen"
|
||||
, initial: M.initial $ css { scaleY: 0.2 }
|
||||
, animate: M.animate $ css { scaleY: 1.0 }
|
||||
, exit: M.exit $ css {}
|
||||
, transition: M.transition { scaleY: { type: "spring", duration: 0.12, bounce: 0.00 } }
|
||||
}
|
||||
[ el_ Icon.component
|
||||
{ size: Style.rightIconSize
|
||||
, icon: SVGIcon.eyeOpen
|
||||
}
|
||||
]
|
||||
else
|
||||
el M.div
|
||||
{ key: "eyeClosed"
|
||||
, initial: M.initial $ css { scaleY: 1.0 }
|
||||
, animate: M.animate $ css { scaleY: 0.4 }
|
||||
, exit: M.exit $ css { scaleY: 0.2 }
|
||||
, transition: M.transition { scaleY: { type: "spring", duration: 0.12, bounce: 0.00 } }
|
||||
}
|
||||
[ el_ Icon.component
|
||||
{ size: Style.rightIconSize
|
||||
, icon: SVGIcon.eyeClosed
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
let trailing = props.trailing ?|| eyeCon
|
||||
let leading = props.leading ?|| mempty
|
||||
pure
|
||||
$ styled R.div'
|
||||
{ className: "ry-input-wrapper"
|
||||
, css: Style.textWrapper props
|
||||
, css: Style.inputWrapper props
|
||||
}
|
||||
[ emotionInput
|
||||
[ leading
|
||||
, emotionInput
|
||||
ref
|
||||
props
|
||||
props { type = HTMLInput.toString <$> props.type # unsafeUnOptional }
|
||||
{ className: "ry-input"
|
||||
, css: Style.input props
|
||||
, type: if hidePassword then "password" else "text"
|
||||
}
|
||||
, iconRight
|
||||
, trailing
|
||||
]
|
||||
|
5
src/Yoga/Block/Atom/Popover.purs
Normal file
5
src/Yoga/Block/Atom/Popover.purs
Normal file
@ -0,0 +1,5 @@
|
||||
module Yoga.Block.Atom.Popover
|
||||
( module Yoga.Block.Atom.Popover.View
|
||||
) where
|
||||
|
||||
import Yoga.Block.Atom.Popover.View (component, Props)
|
16
src/Yoga/Block/Atom/Popover/Spec.purs
Normal file
16
src/Yoga/Block/Atom/Popover/Spec.purs
Normal file
@ -0,0 +1,16 @@
|
||||
module Yoga.Block.Atom.Popover.Spec where
|
||||
|
||||
import Yoga.Prelude.Spec
|
||||
import React.Basic.DOM as R
|
||||
import Yoga.Block.Atom.Popover as Popover
|
||||
|
||||
spec ∷ Spec Unit
|
||||
spec =
|
||||
after_ cleanup do
|
||||
describe "The popover" do
|
||||
it "renders without errors" do
|
||||
void
|
||||
$ renderComponent Popover.component
|
||||
{ theTip: R.text "Tip"
|
||||
, target: R.text "Target"
|
||||
}
|
254
src/Yoga/Block/Atom/Popover/Story.purs
Normal file
254
src/Yoga/Block/Atom/Popover/Story.purs
Normal file
@ -0,0 +1,254 @@
|
||||
module Yoga.Block.Atom.Popover.Story where
|
||||
|
||||
import Prelude
|
||||
import Data.Array as Array
|
||||
import Data.Foldable (traverse_)
|
||||
import Data.Maybe (Maybe(..))
|
||||
import Data.String as String
|
||||
import Data.String.NonEmpty.Internal (nes)
|
||||
import Data.Symbol (SProxy(..))
|
||||
import Data.Tuple.Nested ((/\))
|
||||
import Effect (Effect)
|
||||
import Effect.Unsafe (unsafePerformEffect)
|
||||
import Framer.Motion (animatePresence, animateSharedLayout)
|
||||
import Framer.Motion as Motion
|
||||
import React.Basic (JSX, ReactComponent, element, fragment)
|
||||
import React.Basic.DOM (CSS, css)
|
||||
import React.Basic.DOM as R
|
||||
import React.Basic.DOM.Events (targetValue)
|
||||
import React.Basic.Emotion as E
|
||||
import React.Basic.Events (handler, handler_)
|
||||
import React.Basic.Hooks (reactComponent)
|
||||
import React.Basic.Hooks as React
|
||||
import Yoga (el, el_)
|
||||
import Yoga.Block.Atom.Input as Input
|
||||
import Yoga.Block.Atom.Input.Placement.Types (Placement(..))
|
||||
import Yoga.Block.Atom.Input.Placement.Types as Placement
|
||||
import Yoga.Block.Atom.Input.Types as HTMLInput
|
||||
import Yoga.Block.Atom.Popover as Popover
|
||||
import Yoga.Block.Container.Style as Styles
|
||||
import Yoga.Block.Layout.Box as Box
|
||||
import Yoga.Block.Layout.Stack as Stack
|
||||
|
||||
default ∷
|
||||
{ decorators ∷ Array (Effect JSX -> JSX)
|
||||
, title ∷ String
|
||||
}
|
||||
default =
|
||||
{ title: "Atom/Popover"
|
||||
, decorators:
|
||||
[ \storyFn ->
|
||||
R.div_
|
||||
[ element E.global { styles: Styles.global }
|
||||
, unsafePerformEffect storyFn
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
itemVariants ∷
|
||||
{ hidden ∷ CSS
|
||||
, visible ∷ CSS
|
||||
}
|
||||
itemVariants =
|
||||
{ visible:
|
||||
css
|
||||
{ left: 0
|
||||
}
|
||||
, hidden:
|
||||
css
|
||||
{ left: -20
|
||||
}
|
||||
}
|
||||
|
||||
containerVariants ∷
|
||||
{ hidden ∷ CSS
|
||||
, visible ∷ CSS
|
||||
}
|
||||
containerVariants =
|
||||
{ visible:
|
||||
css
|
||||
{ transition: { when: "beforeChildren" }
|
||||
, staggerChildren: 0.3
|
||||
, opacity: 0
|
||||
}
|
||||
, hidden:
|
||||
css
|
||||
{ transition: { when: "afterChildren" }
|
||||
}
|
||||
}
|
||||
|
||||
containerVariant ∷
|
||||
{ hidden ∷ Motion.VariantLabel
|
||||
, visible ∷ Motion.VariantLabel
|
||||
}
|
||||
containerVariant = Motion.makeVariantLabels containerVariants
|
||||
|
||||
popover ∷ Effect JSX
|
||||
popover = do
|
||||
autosuggest <- mkAutosuggest
|
||||
pure
|
||||
$ fragment
|
||||
[ R.div_
|
||||
[ R.h2_ [ R.text "Autosuggest" ]
|
||||
, el_ autosuggest {}
|
||||
]
|
||||
]
|
||||
where
|
||||
mkAutosuggest ∷ Effect (ReactComponent {})
|
||||
mkAutosuggest = do
|
||||
motionStack <- Motion.custom Stack.component
|
||||
reactComponent "PopoverExample" \props -> React.do
|
||||
text /\ setText <- React.useState' ""
|
||||
visible /\ setVisible <- React.useState' false
|
||||
let
|
||||
matchingAuthors =
|
||||
authors
|
||||
# Array.filter (\a -> String.contains (String.Pattern (String.toLower text)) (String.toLower a))
|
||||
let
|
||||
target =
|
||||
el_ Input.component
|
||||
{ type: HTMLInput.Text
|
||||
, id: "author"
|
||||
, label: nes (SProxy ∷ _ "Author")
|
||||
, placeholder: "e.g. William Shakespeare"
|
||||
, value: text
|
||||
, onChange: handler targetValue $ traverse_ setText
|
||||
, onBlur: handler_ (setVisible false)
|
||||
, onFocus: handler_ (setVisible true)
|
||||
}
|
||||
pop =
|
||||
el Popover.component
|
||||
{ target
|
||||
, placement: Placement Placement.Bottom (Just Placement.Start)
|
||||
}
|
||||
box =
|
||||
el Box.component
|
||||
{ style:
|
||||
css
|
||||
{ background: "rgba(128, 128, 128, 0.5)"
|
||||
, borderRadius: "5px"
|
||||
}
|
||||
}
|
||||
stack =
|
||||
el motionStack
|
||||
$ Motion.motion
|
||||
{ variants: Motion.variants containerVariants
|
||||
, animate: Motion.animate (if visible then containerVariant.visible else containerVariant.hidden)
|
||||
, layout: Motion.layout true
|
||||
}
|
||||
{}
|
||||
entry a =
|
||||
el Motion.div
|
||||
{ key: a
|
||||
, layout: Motion.layout true
|
||||
, variants: Motion.variants itemVariants
|
||||
}
|
||||
[ R.text a ]
|
||||
pure
|
||||
$ fragment
|
||||
[ pop
|
||||
[ box
|
||||
[ el animateSharedLayout {} [ stack (entry <$> matchingAuthors) ]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
authors =
|
||||
[ "William Shakespeare"
|
||||
, "Agatha Christie"
|
||||
, "Barbara Cartland"
|
||||
, "Danielle Steel"
|
||||
, "Harold Robbins"
|
||||
, "Georges Simenon"
|
||||
, "Enid Blyton"
|
||||
, "Sidney Sheldon"
|
||||
, "J. K. Rowling"
|
||||
, "Gilbert Patten"
|
||||
, "Dr. Seuss"
|
||||
, "Eiichiro Oda"
|
||||
, "Leo Tolstoy"
|
||||
, "Corín Tellado"
|
||||
, "Jackie Collins"
|
||||
, "Horatio Alger"
|
||||
, "R. L. Stine"
|
||||
, "Dean Koontz"
|
||||
, "Nora Roberts"
|
||||
, "Alexander Pushkin"
|
||||
, "Stephen King"
|
||||
, "Paulo Coelho"
|
||||
, "Jeffrey Archer"
|
||||
, "Louis L'Amour"
|
||||
, "Jirō Akagawa"
|
||||
, "René Goscinny"
|
||||
, "Erle Stanley Gardner"
|
||||
, "Edgar Wallace"
|
||||
, "Jin Yong"
|
||||
, "Janet Dailey"
|
||||
, "Robert Ludlum"
|
||||
, "Akira Toriyama"
|
||||
, "Osamu Tezuka"
|
||||
, "James Patterson"
|
||||
, "Frédéric Dard"
|
||||
, "Stan and Jan Berenstain"
|
||||
, "Roald Dahl"
|
||||
, "John Grisham"
|
||||
, "Zane Grey"
|
||||
, "Irving Wallace"
|
||||
, "J. R. R. Tolkien"
|
||||
, "Masashi Kishimoto"
|
||||
, "Karl May"
|
||||
, "Carter Brown"
|
||||
, "Mickey Spillane"
|
||||
, "C. S. Lewis"
|
||||
, "Kyotaro Nishimura"
|
||||
, "Mitsuru Adachi"
|
||||
, "Rumiko Takahashi"
|
||||
, "Gosho Aoyama"
|
||||
, "Dan Brown"
|
||||
, "Ann M. Martin"
|
||||
, "Ryōtarō Shiba"
|
||||
, "Arthur Hailey"
|
||||
, "Gérard de Villiers"
|
||||
, "Beatrix Potter"
|
||||
, "Michael Crichton"
|
||||
, "Richard Scarry"
|
||||
, "Clive Cussler"
|
||||
, "Alistair MacLean"
|
||||
, "Ken Follett"
|
||||
, "Astrid Lindgren"
|
||||
, "Debbie Macomber"
|
||||
, "EL James"
|
||||
, "Tite Kubo"
|
||||
, "Eiji Yoshikawa"
|
||||
, "Catherine Cookson"
|
||||
, "Stephenie Meyer"
|
||||
, "Norman Bridwell"
|
||||
, "David Baldacci"
|
||||
, "Nicholas Sparks"
|
||||
, "Hirohiko Araki"
|
||||
, "Evan Hunter"
|
||||
, "Andrew Neiderman"
|
||||
, "Roger Hargreaves"
|
||||
, "Anne Rice"
|
||||
, "Robin Cook"
|
||||
, "Wilbur Smith"
|
||||
, "Erskine Caldwell"
|
||||
, "Judith Krantz"
|
||||
, "Eleanor Hibbert"
|
||||
, "Lewis Carroll"
|
||||
, "Denise Robins"
|
||||
, "Cao Xueqin"
|
||||
, "Ian Fleming"
|
||||
, "Hermann Hesse"
|
||||
, "Rex Stout"
|
||||
, "Anne Golon"
|
||||
, "Frank G. Slaughter"
|
||||
, "Edgar Rice Burroughs"
|
||||
, "John Creasey"
|
||||
, "James A. Michener"
|
||||
, "Yasuo Uchida"
|
||||
, "Seiichi Morimura"
|
||||
, "Mary Higgins Clark"
|
||||
, "Penny Jordan"
|
||||
, "Patricia Cornwell"
|
||||
]
|
19
src/Yoga/Block/Atom/Popover/Style.purs
Normal file
19
src/Yoga/Block/Atom/Popover/Style.purs
Normal file
@ -0,0 +1,19 @@
|
||||
module Yoga.Block.Atom.Popover.Style where
|
||||
|
||||
import Yoga.Prelude.Style
|
||||
|
||||
type Props f r =
|
||||
( css ∷ f Style
|
||||
| r
|
||||
)
|
||||
|
||||
content ∷ Style
|
||||
content =
|
||||
css
|
||||
{ zIndex: str "-1"
|
||||
}
|
||||
|
||||
popper ∷ Style
|
||||
popper =
|
||||
css
|
||||
{}
|
82
src/Yoga/Block/Atom/Popover/View.purs
Normal file
82
src/Yoga/Block/Atom/Popover/View.purs
Normal file
@ -0,0 +1,82 @@
|
||||
module Yoga.Block.Atom.Popover.View (component, MandatoryProps, Props, PropsF) where
|
||||
|
||||
import Yoga.Prelude.View
|
||||
import Effect.Uncurried (mkEffectFn1)
|
||||
import Framer.Motion as Motion
|
||||
import React.Basic.DOM (css)
|
||||
import React.Basic.DOM as R
|
||||
import React.Basic.Hooks as React
|
||||
import React.Basic.Popper.Hook (usePopper)
|
||||
import React.Basic.Popper.Types (modifierOffset, nullRef)
|
||||
import Unsafe.Coerce (unsafeCoerce)
|
||||
import Yoga.Block.Atom.Input.Placement.Types (Placement)
|
||||
import Yoga.Block.Atom.Input.Placement.Types as Placement
|
||||
import Yoga.Block.Atom.Popover.Style as Style
|
||||
|
||||
type PropsF f =
|
||||
( className ∷ f String
|
||||
, placement ∷ f Placement
|
||||
| Style.Props f (MandatoryProps ())
|
||||
)
|
||||
|
||||
type MandatoryProps r =
|
||||
( children ∷ Array JSX
|
||||
, target ∷ JSX
|
||||
| r
|
||||
)
|
||||
|
||||
type Props =
|
||||
PropsF Id
|
||||
|
||||
type PropsOptional =
|
||||
PropsF OptionalProp
|
||||
|
||||
component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | MandatoryProps p }
|
||||
component = rawComponent
|
||||
|
||||
rawComponent ∷ ∀ p. ReactComponent { | p }
|
||||
rawComponent =
|
||||
mkForwardRefComponent "Popover" do
|
||||
\(props ∷ { | PropsOptional }) ref -> React.do
|
||||
-- Hooks
|
||||
referenceElement /\ setReferenceElement <- React.useState' nullRef
|
||||
popperElement /\ setPopperElement <- React.useState' nullRef
|
||||
{ styles, attributes } <-
|
||||
usePopper referenceElement popperElement
|
||||
{ modifiers:
|
||||
[ modifierOffset { x: 0.0, y: 0.0 }
|
||||
]
|
||||
, placement: props.placement <#> Placement.render # unsafeUnOptional
|
||||
}
|
||||
-- Handlers
|
||||
-- Elements
|
||||
let
|
||||
result =
|
||||
fragment
|
||||
$ [ refElem
|
||||
, popperEl
|
||||
[ content
|
||||
props.children
|
||||
]
|
||||
]
|
||||
content =
|
||||
styled R.div'
|
||||
{ className: "popper-element-content"
|
||||
, css: Style.content
|
||||
, key: "container"
|
||||
}
|
||||
popperEl =
|
||||
styled R.div'
|
||||
{ className: "popper-element"
|
||||
, css: Style.popper
|
||||
, ref: unsafeCoerce (mkEffectFn1 setPopperElement)
|
||||
, style: styles.popper
|
||||
, _data: attributes.popper
|
||||
}
|
||||
refElem =
|
||||
element Motion.div
|
||||
{ ref: unsafeCoerce (mkEffectFn1 setReferenceElement)
|
||||
, style: css { display: "inline-block" }
|
||||
, children: [ props.target ]
|
||||
}
|
||||
pure result
|
@ -41,7 +41,7 @@ mkGlobal maybeMode =
|
||||
}
|
||||
, ":root":
|
||||
nested $ variables
|
||||
<> fontVariables { main: "Inter, system-ui, sans-serif", mono: "Victor Mono, Menlo, Consolas, Monaco, Liberation Mono, Lucida Console" }
|
||||
<> fontVariables { main: "Inter", mono: "Victor Mono, Menlo, Consolas, Monaco, Liberation Mono, Lucida Console" }
|
||||
, html:
|
||||
nested
|
||||
$ css
|
||||
@ -63,24 +63,39 @@ mkGlobal maybeMode =
|
||||
nest
|
||||
{ fontFamily: str "var(--mono-font)"
|
||||
}
|
||||
, "h1,h2,h3,h4,h5":
|
||||
nest
|
||||
{ fontWeight: str "800"
|
||||
}
|
||||
, h1:
|
||||
nested $ css { fontSize: em 3.5 }
|
||||
nested
|
||||
$ css
|
||||
{ fontSize: var "--s4"
|
||||
, lineHeight: str "calc(var(--s4) * 1.4)"
|
||||
, fontWeight: str "900"
|
||||
, letterSpacing: str "calc(var(--s-5) * -1)"
|
||||
}
|
||||
, h2:
|
||||
nested $ css { fontSize: em 2.7 }
|
||||
nested
|
||||
$ css
|
||||
{ fontSize: var "--s3"
|
||||
, lineHeight: str "calc(var(--s3) * 1.4)"
|
||||
, fontWeight: str "800"
|
||||
, letterSpacing: str "calc(var(--s-5) * -0.9)"
|
||||
}
|
||||
, h3:
|
||||
nested
|
||||
$ css
|
||||
{ fontSize: var "--s2"
|
||||
, lineHeight: str "calc(var(--s2) * 1.4)"
|
||||
, fontWeight: str "700"
|
||||
, letterSpacing: str "calc(var(--s-5) * -0.8)"
|
||||
}
|
||||
, "::selection":
|
||||
nest
|
||||
{ background: str colour.highlight
|
||||
}
|
||||
, "*, *:before, *:after":
|
||||
nested
|
||||
$ css
|
||||
{ boxSizing: str "inherit"
|
||||
}
|
||||
, form:
|
||||
nested
|
||||
$ css
|
||||
{ backgroundColor: str colour.background07
|
||||
}
|
||||
}
|
||||
|
||||
defaultColours ∷ Colours
|
||||
@ -103,16 +118,23 @@ defaultColours =
|
||||
, background80: darken 0.8 lightBg
|
||||
, background90: darken 0.9 lightBg
|
||||
, background100: darken 1.0 lightBg
|
||||
, backgroundInverted: darken 0.85 lightBg
|
||||
, textInverted: lightBg
|
||||
, success
|
||||
, successText
|
||||
, invalid
|
||||
, invalidText
|
||||
, required
|
||||
, interfaceBackground: lightBg
|
||||
, interfaceTextDisabled: darken 0.33 lightBg
|
||||
, interfaceBackgroundHighlight: darken 0.07 lightBg
|
||||
, interfaceBackgroundShadow: darken 0.1 lightBg
|
||||
, inputBackground: darken 0.03 lightBg
|
||||
, inputBackground: lightBg
|
||||
, inputBorder: darken 0.1 lightBg
|
||||
, highlight: highlight
|
||||
, text: darkBg
|
||||
, highlight
|
||||
, highlightText
|
||||
, text: textLightTheme
|
||||
, placeholderText: lighten 0.4 darkBg
|
||||
}
|
||||
, dark:
|
||||
{ background0: darkBg
|
||||
@ -132,26 +154,44 @@ defaultColours =
|
||||
, background80: lighten 0.8 darkBg
|
||||
, background90: lighten 0.9 darkBg
|
||||
, background100: lighten 1.0 darkBg
|
||||
, textInverted: darkBg
|
||||
, backgroundInverted: lightBg
|
||||
, interfaceBackground: lighten 0.4 darkBg
|
||||
, interfaceTextDisabled: lighten 0.8 darkBg
|
||||
, interfaceBackgroundHighlight: lighten 0.5 darkBg
|
||||
, interfaceBackgroundShadow: lighten 0.4 darkBg
|
||||
, inputBackground: lighten 0.10 darkBg
|
||||
, inputBackground: darkBg
|
||||
, inputBorder: lighten 0.17 darkBg
|
||||
, success
|
||||
, successText
|
||||
, highlight
|
||||
, required
|
||||
, invalid
|
||||
, invalidText
|
||||
, highlight: highlightDark
|
||||
, highlightText
|
||||
, text: lightBg
|
||||
, placeholderText: darken 0.4 lightBg
|
||||
}
|
||||
}
|
||||
where
|
||||
highlight = Color.rgb 0x00 0x99 0xFF
|
||||
|
||||
highlightDark = Color.rgb 0x88 0x33 0xFF
|
||||
|
||||
highlightText = Color.rgb 0xFF 0xFF 0xFF
|
||||
|
||||
success = Color.rgb 20 200 60
|
||||
|
||||
successText = Color.rgb 250 250 250
|
||||
|
||||
-- highlight = Color.rgb 0x10 0x45 0x4A
|
||||
invalid = Color.rgb 220 40 70
|
||||
|
||||
invalidText = successText
|
||||
|
||||
required = Color.rgb 200 50 80
|
||||
|
||||
textLightTheme = Color.rgb 16 16 32
|
||||
|
||||
darkBg = Color.rgb 0 0 0
|
||||
|
||||
lightBg = Color.rgb 250 250 250
|
||||
@ -174,6 +214,7 @@ type FlatTheme a =
|
||||
, background80 ∷ a
|
||||
, background90 ∷ a
|
||||
, background100 ∷ a
|
||||
, backgroundInverted ∷ a
|
||||
, interfaceBackground ∷ a
|
||||
, interfaceTextDisabled ∷ a
|
||||
, interfaceBackgroundHighlight ∷ a
|
||||
@ -181,9 +222,15 @@ type FlatTheme a =
|
||||
, inputBackground ∷ a
|
||||
, inputBorder ∷ a
|
||||
, highlight ∷ a
|
||||
, highlightText ∷ a
|
||||
, success ∷ a
|
||||
, successText ∷ a
|
||||
, invalid ∷ a
|
||||
, invalidText ∷ a
|
||||
, required ∷ a
|
||||
, text ∷ a
|
||||
, textInverted ∷ a
|
||||
, placeholderText ∷ a
|
||||
}
|
||||
|
||||
type Colours =
|
||||
|
@ -1,7 +1,6 @@
|
||||
module Yoga.Block.Icon.SVG.EyeClosed where
|
||||
|
||||
import React.Basic (JSX)
|
||||
import React.Basic.DOM as R
|
||||
import React.Basic.DOM.SVG as SVG
|
||||
|
||||
eyeClosed ∷ JSX
|
||||
@ -19,12 +18,8 @@ eyeClosed =
|
||||
, d: "M0 0h100v100H0z"
|
||||
}
|
||||
, SVG.path
|
||||
{ d: "M5.492 63.902c-1.539-2.426-2.587-11.465-1.593-13.076.352.369 4.353-.86 4.732-.54 24.519 20.777 51.057 22.859 83.889.676.892-.603 2.312-1.948 3.567-.637.892 2.137 2.479 6.187 2.439 10.011-.858 3.046-4.013 8.107-6.554 10.666.091-3.033-1.654-9.054-2.787-11.919-1.169 1.053-4.237 3.361-5.557 4.507 2.021 6.439 3.704 10.343 4.342 13.77-1.418 2.716-6.433 6.382-8.172 6.837-.695-3.519-3.589-12.626-4.791-16.545-1.721 1.014-7.746 2.85-7.739 2.967.294 5.01 1.269 11.753 3.159 17.431-2.331 1.521-7.403 2.045-10.644 2.233-1.477-3.05-1.902-13.039-2.221-17.74-1.768.532-6.838.5-8.865.679-.682 5.829-.99 11.62-.47 17.094-2.634-.024-8.846-1.614-10.179-2.127-.34-.886.675-13.622.808-16.23-1.442-.074-7.906-2.268-8.801-2.513-.501 3.058-1.153 12.152-.578 14.598-1.516-.601-8.651-6.109-9.148-6.3-.617-3.543-.097-9.17.23-12.361-.919-.313-5.78-3.571-6.551-4.211-.87 8.919-.1 11.254.106 11.869-1.502-1.338-7.45-7.873-8.622-9.139z"
|
||||
{ d: "M50.173 21.996c23.346 0 42.3 27.978 42.3 27.978s-18.954 27.979-42.3 27.979c-23.346 0-42.301-27.979-42.301-27.979s18.955-27.978 42.301-27.978z"
|
||||
, fill: "var(--stroke-colour)"
|
||||
}
|
||||
, SVG.path
|
||||
{ d: "M50.026 26.996c23.346 0 42.3 24.595 42.3 24.595s-18.954 17.592-42.3 17.592c-23.347 0-42.301-17.592-42.301-17.592s18.954-24.595 42.301-24.595z"
|
||||
, fill: "none"
|
||||
, stroke: "var(--stroke-colour)"
|
||||
, strokeWidth: "8"
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
module Yoga.Block.Icon.SVG.EyeOpen where
|
||||
|
||||
import React.Basic (JSX)
|
||||
import React.Basic.DOM as R
|
||||
import React.Basic.DOM.SVG as SVG
|
||||
|
||||
eyeOpen ∷ JSX
|
||||
@ -27,18 +26,14 @@ eyeOpen =
|
||||
, SVG.circle
|
||||
{ cx: "49.963"
|
||||
, cy: "50.087"
|
||||
, r: "24.205"
|
||||
, r: "19.869"
|
||||
, fill: "none"
|
||||
, stroke: "var(--stroke-colour)"
|
||||
, strokeWidth: "6"
|
||||
}
|
||||
, SVG.circle
|
||||
{ cx: "49.963"
|
||||
, cy: "50.087"
|
||||
, r: "9.748"
|
||||
, SVG.path
|
||||
{ d: "M49.963 40.748c5.155 0 9.339 4.184 9.339 9.339 0 5.154-4.184 9.339-9.339 9.339-5.154 0-9.339-4.185-9.339-9.339 0-5.155 4.185-9.339 9.339-9.339zm2.405 2.279a3.517 3.517 0 013.516 3.515 3.517 3.517 0 01-3.516 3.516 3.517 3.517 0 010-7.031z"
|
||||
, fill: "var(--stroke-colour)"
|
||||
, stroke: "var(--stroke-colour)"
|
||||
, strokeWidth: "8"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ module Yoga.Block.Icon.SVG
|
||||
, module Yoga.Block.Icon.SVG.MagnifyingGlass
|
||||
, module Yoga.Block.Icon.SVG.EyeClosed
|
||||
, module Yoga.Block.Icon.SVG.EyeOpen
|
||||
, module Yoga.Block.Icon.SVG.Key
|
||||
, module Yoga.Block.Icon.SVG.SimpleKey
|
||||
) where
|
||||
|
||||
import Yoga.Block.Icon.SVG.On (on)
|
||||
@ -11,3 +13,5 @@ import Yoga.Block.Icon.SVG.Off (off)
|
||||
import Yoga.Block.Icon.SVG.MagnifyingGlass (magnifyingGlass)
|
||||
import Yoga.Block.Icon.SVG.EyeClosed (eyeClosed)
|
||||
import Yoga.Block.Icon.SVG.EyeOpen (eyeOpen)
|
||||
import Yoga.Block.Icon.SVG.Key (key)
|
||||
import Yoga.Block.Icon.SVG.SimpleKey (simpleKey)
|
||||
|
28
src/Yoga/Block/Icon/SVG/Key.purs
Normal file
28
src/Yoga/Block/Icon/SVG/Key.purs
Normal file
@ -0,0 +1,28 @@
|
||||
module Yoga.Block.Icon.SVG.Key where
|
||||
|
||||
import React.Basic (JSX)
|
||||
import React.Basic.DOM.SVG as SVG
|
||||
|
||||
key ∷ JSX
|
||||
key =
|
||||
SVG.svg
|
||||
{ viewBox: "0 0 100 100"
|
||||
, xmlns: "http://www.w3.org/2000/svg"
|
||||
, fillRule: "evenodd"
|
||||
, clipRule: "evenodd"
|
||||
, strokeLinejoin: "round"
|
||||
, strokeMiterlimit: "2"
|
||||
, children:
|
||||
[ SVG.g
|
||||
{ fill: "var(--stroke-colour)"
|
||||
, children:
|
||||
[ SVG.path
|
||||
{ d: "M30.65 43.906c14.689 0 26.614 11.937 26.614 26.64 0 14.702-11.925 26.639-26.614 26.639-14.688 0-26.614-11.937-26.614-26.639 0-14.703 11.926-26.64 26.614-26.64zm-9.215 28.741a6.454 6.454 0 010 12.907 6.454 6.454 0 010-12.907z"
|
||||
}
|
||||
, SVG.path
|
||||
{ d: "M79.123 7.855h10.701l.833.776-47.719 44.401 2.491 2.286 45.494-43.326v12.21l-8.681 3.927-2.311 10.777-12.195 6.78-6.678 14.839-17.216 8.651-20.256-9.646L79.123 7.855z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
31
src/Yoga/Block/Icon/SVG/SimpleKey.purs
Normal file
31
src/Yoga/Block/Icon/SVG/SimpleKey.purs
Normal file
@ -0,0 +1,31 @@
|
||||
module Yoga.Block.Icon.SVG.SimpleKey where
|
||||
|
||||
import React.Basic (JSX)
|
||||
import React.Basic.DOM.SVG as SVG
|
||||
|
||||
simpleKey ∷ JSX
|
||||
simpleKey =
|
||||
SVG.svg
|
||||
{ viewBox: "0 0 100 100"
|
||||
, xmlns: "http://www.w3.org/2000/svg"
|
||||
, fillRule: "evenodd"
|
||||
, clipRule: "evenodd"
|
||||
, strokeLinejoin: "round"
|
||||
, strokeMiterlimit: "2"
|
||||
, children:
|
||||
[ SVG.g
|
||||
{ fill: "var(--stroke-colour)"
|
||||
, children:
|
||||
[ SVG.ellipse
|
||||
{ cx: "30.65"
|
||||
, cy: "70.546"
|
||||
, rx: "26.614"
|
||||
, ry: "26.639"
|
||||
}
|
||||
, SVG.path
|
||||
{ d: "M79.123 7.855h10.701l.833.776.266 3.361v12.21l-8.681 3.927-2.311 10.777-12.195 6.78-6.678 14.839-17.216 8.651-20.256-9.646L79.123 7.855z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1 +1 @@
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.5"><path fill="none" d="M0 0h100v100H0z"/><path d="M5.492 63.902c-1.539-2.426-2.587-11.465-1.593-13.076.352.369 4.353-.86 4.732-.54 24.519 20.777 51.057 22.859 83.889.676.892-.603 2.312-1.948 3.567-.637.892 2.137 2.479 6.187 2.439 10.011-.858 3.046-4.013 8.107-6.554 10.666.091-3.033-1.654-9.054-2.787-11.919-1.169 1.053-4.237 3.361-5.557 4.507 2.021 6.439 3.704 10.343 4.342 13.77-1.418 2.716-6.433 6.382-8.172 6.837-.695-3.519-3.589-12.626-4.791-16.545-1.721 1.014-7.746 2.85-7.739 2.967.294 5.01 1.269 11.753 3.159 17.431-2.331 1.521-7.403 2.045-10.644 2.233-1.477-3.05-1.902-13.039-2.221-17.74-1.768.532-6.838.5-8.865.679-.682 5.829-.99 11.62-.47 17.094-2.634-.024-8.846-1.614-10.179-2.127-.34-.886.675-13.622.808-16.23-1.442-.074-7.906-2.268-8.801-2.513-.501 3.058-1.153 12.152-.578 14.598-1.516-.601-8.651-6.109-9.148-6.3-.617-3.543-.097-9.17.23-12.361-.919-.313-5.78-3.571-6.551-4.211-.87 8.919-.1 11.254.106 11.869-1.502-1.338-7.45-7.873-8.622-9.139z" fill="#333"/><path d="M50.026 26.996c23.346 0 42.3 24.595 42.3 24.595s-18.954 17.592-42.3 17.592c-23.347 0-42.301-17.592-42.301-17.592s18.954-24.595 42.301-24.595z" fill="none" stroke="#333" stroke-width="8"/></svg>
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.5"><path fill="none" d="M0 0h100v100H0z"/><path d="M50.173 21.996c23.346 0 42.3 27.978 42.3 27.978s-18.954 27.979-42.3 27.979c-23.346 0-42.301-27.979-42.301-27.979s18.955-27.978 42.301-27.978z" fill="#333" stroke="#333" stroke-width="8"/></svg>
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 391 B |
@ -1 +1 @@
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.5"><path fill="none" d="M0 0h100v100H0z"/><path d="M50.173 21.996c23.346 0 42.3 27.978 42.3 27.978s-18.954 27.979-42.3 27.979c-23.346 0-42.301-27.979-42.301-27.979s18.955-27.978 42.301-27.978z" fill="none" stroke="#333" stroke-width="8"/><circle cx="49.963" cy="50.087" r="24.205" fill="none" stroke="#333" stroke-width="6"/><circle cx="49.963" cy="50.087" r="9.748" fill="#333" stroke="#333" stroke-width="8"/></svg>
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.5"><path fill="none" d="M0 0h100v100H0z"/><path d="M50.173 21.996c23.346 0 42.3 27.978 42.3 27.978s-18.954 27.979-42.3 27.979c-23.346 0-42.301-27.979-42.301-27.979s18.955-27.978 42.301-27.978z" fill="none" stroke="#333" stroke-width="8"/><circle cx="49.963" cy="50.087" r="19.869" fill="none" stroke="#333" stroke-width="6"/><path d="M49.963 40.748c5.155 0 9.339 4.184 9.339 9.339 0 5.154-4.184 9.339-9.339 9.339-5.154 0-9.339-4.185-9.339-9.339 0-5.155 4.185-9.339 9.339-9.339zm2.405 2.279a3.517 3.517 0 013.516 3.515 3.517 3.517 0 01-3.516 3.516 3.517 3.517 0 010-7.031z" fill="#333"/></svg>
|
Before Width: | Height: | Size: 564 B After Width: | Height: | Size: 739 B |
1
src/Yoga/Block/Icon/SVG/key.svg
Normal file
1
src/Yoga/Block/Icon/SVG/key.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><path fill="none" d="M0 0h100v100H0z"/><g fill="#333"><path d="M30.65 43.906c14.689 0 26.614 11.937 26.614 26.64 0 14.702-11.925 26.639-26.614 26.639-14.688 0-26.614-11.937-26.614-26.639 0-14.703 11.926-26.64 26.614-26.64zm-9.215 28.741a6.454 6.454 0 010 12.907 6.454 6.454 0 010-12.907z"/><path d="M79.123 7.855h10.701l.833.776-47.719 44.401 2.491 2.286 45.494-43.326v12.21l-8.681 3.927-2.311 10.777-12.195 6.78-6.678 14.839-17.216 8.651-20.256-9.646L79.123 7.855z"/></g></svg>
|
After Width: | Height: | Size: 626 B |
1
src/Yoga/Block/Icon/SVG/simpleKey.svg
Normal file
1
src/Yoga/Block/Icon/SVG/simpleKey.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><path fill="none" d="M0 0h100v100H0z"/><g fill="#333"><ellipse cx="30.65" cy="70.546" rx="26.614" ry="26.639"/><path d="M79.123 7.855h10.701l.833.776.266 3.361v12.21l-8.681 3.927-2.311 10.777-12.195 6.78-6.678 14.839-17.216 8.651-20.256-9.646L79.123 7.855z"/></g></svg>
|
After Width: | Height: | Size: 417 B |
@ -1,6 +1,7 @@
|
||||
module Yoga.Block.Internal
|
||||
( mkForwardRefComponent
|
||||
, mkForwardRefComponentEffect
|
||||
, forwardedRefAsMaybe
|
||||
, unsafeEmotion
|
||||
, unsafeDiv
|
||||
, dangerous
|
||||
@ -8,6 +9,7 @@ module Yoga.Block.Internal
|
||||
, DivPropsF
|
||||
, InputProps
|
||||
, InputPropsF
|
||||
, NodeRef
|
||||
, emotionDiv
|
||||
, emotionInput
|
||||
, module Yoga.Block.Internal.OptionalProp
|
||||
@ -15,12 +17,18 @@ module Yoga.Block.Internal
|
||||
, unsafeUnionDroppingUndefined
|
||||
, unsafeMergeSecond
|
||||
, createRef
|
||||
, getBoundingBoxFromRef
|
||||
, getHTMLElementFromRef
|
||||
) where
|
||||
|
||||
import Prelude
|
||||
import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT)
|
||||
import Data.Array as Array
|
||||
import Data.Function.Uncurried (Fn3, runFn3)
|
||||
import Data.Maybe (Maybe)
|
||||
import Data.Nullable (Nullable)
|
||||
import Data.Nullable as Nullable
|
||||
import Data.Traversable (for)
|
||||
import Effect (Effect)
|
||||
import Effect.Unsafe (unsafePerformEffect)
|
||||
import Foreign.Object (Object)
|
||||
@ -31,12 +39,16 @@ import React.Basic.DOM (CSS, unsafeCreateDOMComponent)
|
||||
import React.Basic.Emotion (Style)
|
||||
import React.Basic.Emotion as E
|
||||
import React.Basic.Events (EventHandler)
|
||||
import React.Basic.Hooks (JSX, ReactComponent, Ref, Render)
|
||||
import React.Basic.Hooks (JSX, ReactComponent, Ref, Render, readRefMaybe)
|
||||
import Record.Extra (class Keys, keys)
|
||||
import Type.Data.Row (RProxy(..))
|
||||
import Unsafe.Coerce (unsafeCoerce)
|
||||
import Untagged.Union (UndefinedOr, uorToMaybe)
|
||||
import Web.DOM (Node)
|
||||
import Web.HTML.HTMLElement (DOMRect, HTMLElement, getBoundingClientRect)
|
||||
import Web.HTML.HTMLElement as HTMLElement
|
||||
import Yoga.Block.Internal.CSS (_0)
|
||||
import Yoga.Block.Internal.OptionalProp (Id, OptionalProp(..), appendIfDefined, getOr, getOrFlipped, ifTrue, isTruthy, maybeToOp, opToMaybe, unsafeUnOptional, (<>?), (?||))
|
||||
import Yoga.Block.Internal.OptionalProp (Id, OptionalProp(..), appendIfDefined, getOr, getOrFlipped, ifTrue, isTruthy, maybeToOp, opToMaybe, setOrDelete, unsafeUnMaybe, unsafeUnOptional, (<>?), (?||))
|
||||
|
||||
foreign import mkForwardRefComponent ∷
|
||||
∀ inputProps props a hooks.
|
||||
@ -52,6 +64,26 @@ foreign import mkForwardRefComponentEffect ∷
|
||||
|
||||
foreign import createRef ∷ ∀ a. Effect (Ref a)
|
||||
|
||||
type NodeRef =
|
||||
Ref (Nullable Node)
|
||||
|
||||
getBoundingBoxFromRef ∷ Ref (Nullable Node) -> Effect (Maybe DOMRect)
|
||||
getBoundingBoxFromRef itemRef = do
|
||||
htmlElem <- getHTMLElementFromRef itemRef
|
||||
for htmlElem getBoundingClientRect
|
||||
|
||||
getHTMLElementFromRef ∷ Ref (Nullable Node) -> Effect (Maybe HTMLElement)
|
||||
getHTMLElementFromRef itemRef =
|
||||
runMaybeT do
|
||||
node <- MaybeT $ readRefMaybe itemRef
|
||||
MaybeT $ pure $ HTMLElement.fromNode node
|
||||
|
||||
forwardedRefAsMaybe ∷ ∀ a. Ref a -> Maybe (Ref a)
|
||||
forwardedRefAsMaybe r = safelyWrapped # uorToMaybe >>= Nullable.toMaybe
|
||||
where
|
||||
safelyWrapped ∷ UndefinedOr (Nullable (Ref a))
|
||||
safelyWrapped = unsafeCoerce r
|
||||
|
||||
foreign import unsafeUnionDroppingUndefined ∷ ∀ r1 r2 r3. Record r1 -> Record r2 -> Record r3
|
||||
|
||||
foreign import unsafeMergeSecond ∷ ∀ r1 r2 r3. Record r1 -> Record r2 -> Record r3
|
||||
|
@ -3,8 +3,12 @@ module Yoga.Block.Internal.OptionalProp where
|
||||
import Prelude
|
||||
import Control.Alt (class Alt, (<|>))
|
||||
import Data.Foldable (class Foldable, foldMap, foldl, foldr)
|
||||
import Data.Maybe (Maybe, maybe)
|
||||
import Data.Maybe (Maybe(..), maybe)
|
||||
import Data.Newtype (class Newtype)
|
||||
import Data.Symbol (class IsSymbol, SProxy, reflectSymbol)
|
||||
import Prim.Row (class Cons)
|
||||
import Record (set)
|
||||
import Record.Unsafe (unsafeDelete)
|
||||
import Unsafe.Coerce (unsafeCoerce)
|
||||
import Untagged.Castable (class Castable)
|
||||
import Untagged.Union (UndefinedOr, defined, fromUndefinedOr, maybeToUor, uorToMaybe)
|
||||
@ -14,9 +18,24 @@ type Id a =
|
||||
|
||||
newtype OptionalProp a = OptionalProp (UndefinedOr a)
|
||||
|
||||
setOrDelete ∷
|
||||
∀ r a rNoA key.
|
||||
IsSymbol key =>
|
||||
Cons key a rNoA r =>
|
||||
SProxy key ->
|
||||
OptionalProp a ->
|
||||
{ | r } ->
|
||||
{ | r }
|
||||
setOrDelete key v = case opToMaybe v of
|
||||
Nothing -> unsafeDelete (reflectSymbol key)
|
||||
Just v' -> set key v'
|
||||
|
||||
unsafeUnOptional ∷ ∀ a. OptionalProp a -> a
|
||||
unsafeUnOptional = unsafeCoerce
|
||||
|
||||
unsafeUnMaybe ∷ ∀ a. Maybe a -> a
|
||||
unsafeUnMaybe = maybeToOp >>> unsafeUnOptional
|
||||
|
||||
opToMaybe ∷ ∀ a. OptionalProp a -> Maybe a
|
||||
opToMaybe (OptionalProp x) = uorToMaybe x
|
||||
|
||||
@ -44,7 +63,7 @@ instance functorOptionalProp ∷ Functor OptionalProp where
|
||||
instance altOptionalProp ∷ Alt OptionalProp where
|
||||
alt op1 op2 = maybeToOp $ (opToMaybe op1) <|> (opToMaybe op2)
|
||||
|
||||
instance coercibleOptionalProp ∷ Castable a (OptionalProp a)
|
||||
instance castableOptionalProp ∷ Castable a (OptionalProp a)
|
||||
|
||||
getOr ∷ ∀ a. a -> OptionalProp a -> a
|
||||
getOr default (OptionalProp o) = fromUndefinedOr default o
|
||||
|
@ -19,7 +19,7 @@ import Control.Alt ((<|>))
|
||||
import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT)
|
||||
import Control.Monad.Trans.Class (lift)
|
||||
import Data.Either (Either(..), note, hush)
|
||||
import Data.Foldable (foldMap, for_, intercalate, traverse_)
|
||||
import Data.Foldable (fold, foldMap, for_, intercalate, traverse_)
|
||||
import Data.FoldableWithIndex (foldMapWithIndex)
|
||||
import Data.FunctorWithIndex (mapWithIndex)
|
||||
import Data.Maybe (Maybe(..), fromMaybe, fromMaybe', isJust, maybe)
|
||||
|
@ -12,8 +12,6 @@ module Yoga.Prelude.View
|
||||
, module Web.HTML.HTMLElement
|
||||
, module Data.Nullable
|
||||
, module Untagged.Castable
|
||||
, NodeRef
|
||||
, getBoundingBoxFromRef
|
||||
) where
|
||||
|
||||
import Yoga.Prelude.Default
|
||||
@ -27,16 +25,5 @@ import Type.Row (type (+))
|
||||
import Untagged.Castable (cast)
|
||||
import Web.DOM (Node)
|
||||
import Web.HTML.HTMLElement (HTMLElement, DOMRect, blur, focus, getBoundingClientRect)
|
||||
import Web.HTML.HTMLElement as HTMLElement
|
||||
import Yoga (el, el_, styled, styledLeaf, yogaElement)
|
||||
import Yoga.Block.Internal (DivProps, DivPropsF, Id, InputProps, InputPropsF, OptionalProp(..), _0, appendIfDefined, createRef, dangerous, emotionDiv, emotionInput, getOr, getOrFlipped, ifTrue, isTruthy, maybeToOp, mkForwardRefComponent, mkForwardRefComponentEffect, opToMaybe, unsafeDiv, unsafeEmotion, unsafeUnOptional, (<>?), (?||))
|
||||
|
||||
type NodeRef =
|
||||
Ref (Nullable Node)
|
||||
|
||||
getBoundingBoxFromRef ∷ Ref (Nullable Node) -> Effect (Maybe DOMRect)
|
||||
getBoundingBoxFromRef itemRef =
|
||||
runMaybeT do
|
||||
node <- MaybeT $ readRefMaybe itemRef
|
||||
htmlElement <- MaybeT $ pure $ HTMLElement.fromNode node
|
||||
lift $ getBoundingClientRect htmlElement
|
||||
import Yoga.Block.Internal (DivProps, DivPropsF, Id, InputProps, InputPropsF, NodeRef, OptionalProp(..), _0, appendIfDefined, createRef, dangerous, emotionDiv, emotionInput, forwardedRefAsMaybe, getBoundingBoxFromRef, getHTMLElementFromRef, getOr, getOrFlipped, ifTrue, isTruthy, maybeToOp, mkForwardRefComponent, mkForwardRefComponentEffect, opToMaybe, unsafeDiv, unsafeEmotion, unsafeMergeSecond, unsafeUnMaybe, unsafeUnOptional, unsafeUnionDroppingUndefined, (<>?), (?||), setOrDelete)
|
||||
|
Loading…
Reference in New Issue
Block a user