mirror of
https://github.com/rowtype-yoga/ry-blocks.git
synced 2024-09-17 16:27:33 +03:00
Add popper
This commit is contained in:
parent
93cb3b5fd5
commit
774db186ef
@ -15,7 +15,6 @@
|
|||||||
"@testing-library/react": "^11.0.4",
|
"@testing-library/react": "^11.0.4",
|
||||||
"@testing-library/user-event": "^12.1.7",
|
"@testing-library/user-event": "^12.1.7",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
"source-map": "GerHobbelt/source-map#patch-8",
|
|
||||||
"clean-webpack-plugin": "^3.0.0",
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
"compression-webpack-plugin": "^6.0.3",
|
"compression-webpack-plugin": "^6.0.3",
|
||||||
"framer-motion": "^2.7.0",
|
"framer-motion": "^2.7.0",
|
||||||
@ -25,6 +24,7 @@
|
|||||||
"purescript": "^0.13.8",
|
"purescript": "^0.13.8",
|
||||||
"react-refresh": "^0.8.3",
|
"react-refresh": "^0.8.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
|
"source-map": "GerHobbelt/source-map#patch-8",
|
||||||
"source-map-loader": "^1.1.2",
|
"source-map-loader": "^1.1.2",
|
||||||
"spago": "^0.16.0",
|
"spago": "^0.16.0",
|
||||||
"svg2psreact": "^2.1.0",
|
"svg2psreact": "^2.1.0",
|
||||||
@ -50,8 +50,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/core": "^10.0.28",
|
"@emotion/core": "^10.0.28",
|
||||||
|
"@popperjs/core": "^2.6.0",
|
||||||
"react": "^16.0.0",
|
"react": "^16.0.0",
|
||||||
"react-dom": "^16.0.0",
|
"react-dom": "^16.0.0",
|
||||||
|
"react-popper": "^2.2.4",
|
||||||
"react-syntax-highlighter": "^15.3.1"
|
"react-syntax-highlighter": "^15.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ zip (TwoOrMore as) (TwoOrMore bs) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
findIndex ∷ ∀ a. (a -> Boolean) -> TwoOrMore a -> Maybe Int
|
findIndex ∷ ∀ a. (a -> Boolean) -> TwoOrMore a -> Maybe Int
|
||||||
findIndex f (TwoOrMore { first, second, rest }) =
|
findIndex f (TwoOrMore { first, second, rest }) = do
|
||||||
if f first then
|
if f first then
|
||||||
Just 0
|
Just 0
|
||||||
else
|
else
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const framerMotion = require("framer-motion")
|
const framerMotion = require("framer-motion")
|
||||||
|
|
||||||
exports.divImpl = framerMotion.motion.div
|
exports.divImpl = framerMotion.motion.div
|
||||||
|
exports.buttonImpl = framerMotion.motion.button
|
||||||
exports.h1Impl = framerMotion.motion.h1
|
exports.h1Impl = framerMotion.motion.h1
|
||||||
exports.svgImpl = framerMotion.motion.svg
|
exports.svgImpl = framerMotion.motion.svg
|
||||||
exports.pathImpl = framerMotion.motion.path
|
exports.pathImpl = framerMotion.motion.path
|
||||||
|
@ -1,6 +1,21 @@
|
|||||||
module Framer.Motion
|
module Framer.Motion
|
||||||
( animate
|
( animate
|
||||||
, makeVariantLabels
|
, makeVariantLabels
|
||||||
|
, WhileTap
|
||||||
|
, whileTap
|
||||||
|
, callback
|
||||||
|
, class EffectFnMaker
|
||||||
|
, toEffectFn
|
||||||
|
, OnTap
|
||||||
|
, TapInfo
|
||||||
|
, OnTapStart
|
||||||
|
, OnTapEnd
|
||||||
|
, onTapStart
|
||||||
|
, onTapEnd
|
||||||
|
, onTapCancel
|
||||||
|
, onTap
|
||||||
|
, OnTapCancel
|
||||||
|
, TargetAndTransition
|
||||||
, MakeVariantLabel
|
, MakeVariantLabel
|
||||||
, div
|
, div
|
||||||
, h1
|
, h1
|
||||||
@ -14,6 +29,7 @@ module Framer.Motion
|
|||||||
, prop
|
, prop
|
||||||
, Drag
|
, Drag
|
||||||
, DragMomentum
|
, DragMomentum
|
||||||
|
, button
|
||||||
, dragMomentum
|
, dragMomentum
|
||||||
, DragPropagation
|
, DragPropagation
|
||||||
, DragElastic
|
, DragElastic
|
||||||
@ -63,13 +79,13 @@ import Prelude
|
|||||||
import Data.Nullable (Nullable)
|
import Data.Nullable (Nullable)
|
||||||
import Data.Symbol (class IsSymbol, SProxy, reflectSymbol)
|
import Data.Symbol (class IsSymbol, SProxy, reflectSymbol)
|
||||||
import Effect (Effect)
|
import Effect (Effect)
|
||||||
import Effect.Uncurried (EffectFn2, mkEffectFn2)
|
import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, mkEffectFn2)
|
||||||
import Foreign.Object (Object)
|
import Foreign.Object (Object)
|
||||||
import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmapWithIndex)
|
import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmapWithIndex)
|
||||||
import Literals.Undefined (Undefined)
|
import Literals.Undefined (Undefined)
|
||||||
import Prim.Row (class Nub, class Union)
|
import Prim.Row (class Nub, class Union)
|
||||||
import React.Basic (JSX, ReactComponent)
|
import React.Basic (JSX, ReactComponent)
|
||||||
import React.Basic.DOM (CSS, Props_div, Props_h1, css)
|
import React.Basic.DOM (CSS, Props_div, Props_h1, Props_button, css)
|
||||||
import React.Basic.DOM.Internal (SharedSVGProps)
|
import React.Basic.DOM.Internal (SharedSVGProps)
|
||||||
import React.Basic.DOM.SVG (Props_svg, Props_rect, Props_path)
|
import React.Basic.DOM.SVG (Props_svg, Props_rect, Props_path)
|
||||||
import React.Basic.Events (EventHandler)
|
import React.Basic.Events (EventHandler)
|
||||||
@ -85,6 +101,8 @@ import Yoga.Block.Internal (Id)
|
|||||||
|
|
||||||
foreign import divImpl ∷ ∀ a. ReactComponent { | a }
|
foreign import divImpl ∷ ∀ a. ReactComponent { | a }
|
||||||
|
|
||||||
|
foreign import buttonImpl ∷ ∀ a. ReactComponent { | a }
|
||||||
|
|
||||||
foreign import h1Impl ∷ ∀ a. ReactComponent { | a }
|
foreign import h1Impl ∷ ∀ a. ReactComponent { | a }
|
||||||
|
|
||||||
foreign import svgImpl ∷ ∀ a. ReactComponent { | a }
|
foreign import svgImpl ∷ ∀ a. ReactComponent { | a }
|
||||||
@ -171,14 +189,63 @@ type OnDragEnd =
|
|||||||
type OnDrag =
|
type OnDrag =
|
||||||
(EffectFn2 Event PanInfo Unit |+| Undefined)
|
(EffectFn2 Event PanInfo Unit |+| Undefined)
|
||||||
|
|
||||||
|
type WhileTap =
|
||||||
|
TargetAndTransition |+| String |+| Undefined
|
||||||
|
|
||||||
|
type OnTap =
|
||||||
|
(EffectFn2 Event TapInfo Unit |+| Undefined)
|
||||||
|
|
||||||
|
type OnTapStart =
|
||||||
|
(EffectFn2 Event TapInfo Unit |+| Undefined)
|
||||||
|
|
||||||
|
type OnTapEnd =
|
||||||
|
(EffectFn2 Event TapInfo Unit |+| Undefined)
|
||||||
|
|
||||||
|
type OnTapCancel =
|
||||||
|
(EffectFn2 Event TapInfo Unit |+| Undefined)
|
||||||
|
|
||||||
|
onTapStart ∷ (Event -> TapInfo -> Effect Unit) -> OnTapStart
|
||||||
|
onTapStart = cast <<< toEffectFn
|
||||||
|
|
||||||
|
onTapEnd ∷ (Event -> TapInfo -> Effect Unit) -> OnTapEnd
|
||||||
|
onTapEnd = cast <<< toEffectFn
|
||||||
|
|
||||||
|
onTap ∷ (Event -> TapInfo -> Effect Unit) -> OnTap
|
||||||
|
onTap fn2 = cast (mkEffectFn2 fn2)
|
||||||
|
|
||||||
|
onTapCancel ∷ (Event -> TapInfo -> Effect Unit) -> OnTap
|
||||||
|
onTapCancel fn2 = cast (mkEffectFn2 fn2)
|
||||||
|
|
||||||
|
type TapInfo =
|
||||||
|
{ x ∷ Number, y ∷ Number }
|
||||||
|
|
||||||
|
-- Can contain "transition" and "transitionEnd"
|
||||||
|
type TargetAndTransition =
|
||||||
|
CSS
|
||||||
|
|
||||||
type DragPropagation =
|
type DragPropagation =
|
||||||
Boolean |+| Undefined
|
Boolean |+| Undefined
|
||||||
|
|
||||||
|
whileTap ∷ ∀ c. Castable c WhileTap => c -> WhileTap
|
||||||
|
whileTap = cast
|
||||||
|
|
||||||
|
class EffectFnMaker fn effectFn | fn -> effectFn where
|
||||||
|
toEffectFn ∷ fn -> effectFn
|
||||||
|
|
||||||
|
instance callbackableEffectFn2 ∷ EffectFnMaker (a -> b -> Effect c) (EffectFn2 a b c) where
|
||||||
|
toEffectFn = mkEffectFn2
|
||||||
|
|
||||||
|
instance callbackableEffectFn1 ∷ EffectFnMaker (a -> Effect b) (EffectFn1 a b) where
|
||||||
|
toEffectFn = mkEffectFn1
|
||||||
|
|
||||||
|
callback ∷ ∀ a c f. Castable c a => EffectFnMaker f c => f -> a
|
||||||
|
callback = cast <<< toEffectFn
|
||||||
|
|
||||||
onDragStart ∷ (Event -> PanInfo -> Effect Unit) -> OnDragStart
|
onDragStart ∷ (Event -> PanInfo -> Effect Unit) -> OnDragStart
|
||||||
onDragStart fn2 = cast (mkEffectFn2 fn2)
|
onDragStart = cast <<< toEffectFn
|
||||||
|
|
||||||
onDragEnd ∷ (Event -> PanInfo -> Effect Unit) -> OnDragEnd
|
onDragEnd ∷ (Event -> PanInfo -> Effect Unit) -> OnDragEnd
|
||||||
onDragEnd fn2 = cast (mkEffectFn2 fn2)
|
onDragEnd = cast <<< toEffectFn
|
||||||
|
|
||||||
onDrag ∷ (Event -> PanInfo -> Effect Unit) -> OnDrag
|
onDrag ∷ (Event -> PanInfo -> Effect Unit) -> OnDrag
|
||||||
onDrag fn2 = cast (mkEffectFn2 fn2)
|
onDrag fn2 = cast (mkEffectFn2 fn2)
|
||||||
@ -198,6 +265,11 @@ type MotionPropsF f r =
|
|||||||
, transition ∷ f Transition
|
, transition ∷ f Transition
|
||||||
, layout ∷ f Layout
|
, layout ∷ f Layout
|
||||||
, layoutId ∷ f String |+| Undefined
|
, layoutId ∷ f String |+| Undefined
|
||||||
|
, whileTap ∷ f WhileTap
|
||||||
|
, onTap ∷ f OnTap
|
||||||
|
, onTapStart ∷ f OnTapStart
|
||||||
|
, onTapEnd ∷ f OnTapEnd
|
||||||
|
, onTapCancel ∷ f OnTapCancel
|
||||||
, exit ∷ f Exit
|
, exit ∷ f Exit
|
||||||
| r
|
| r
|
||||||
)
|
)
|
||||||
@ -249,6 +321,9 @@ makeVariantLabels = hmapWithIndex MakeVariantLabel
|
|||||||
div ∷ ∀ attrs attrs_. Union attrs attrs_ (MotionProps + Props_div) => ReactComponent { | attrs }
|
div ∷ ∀ attrs attrs_. Union attrs attrs_ (MotionProps + Props_div) => ReactComponent { | attrs }
|
||||||
div = divImpl
|
div = divImpl
|
||||||
|
|
||||||
|
button ∷ ∀ attrs attrs_. Union attrs attrs_ (MotionProps + Props_button) => ReactComponent { | attrs }
|
||||||
|
button = buttonImpl
|
||||||
|
|
||||||
svg ∷ ∀ attrs attrs_. Union attrs attrs_ (MotionProps + (SharedSVGProps Props_svg)) => ReactComponent { | attrs }
|
svg ∷ ∀ attrs attrs_. Union attrs attrs_ (MotionProps + (SharedSVGProps Props_svg)) => ReactComponent { | attrs }
|
||||||
svg = svgImpl
|
svg = svgImpl
|
||||||
|
|
||||||
|
@ -6,4 +6,5 @@ exports.setImpl = v => render => mv => () => {
|
|||||||
mv.set(v, render)
|
mv.set(v, render)
|
||||||
}
|
}
|
||||||
exports.isAnimating = mv => () => { return mv.isAnimating() }
|
exports.isAnimating = mv => () => { return mv.isAnimating() }
|
||||||
exports.stop = mv => () => { return mv.stop() }
|
exports.stop = mv => () => { return mv.stop() }
|
||||||
|
exports.onChangeImpl = callback => mv => { return () => mv.onChange(callback); }
|
@ -2,7 +2,7 @@ module MotionValue where
|
|||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
import Effect (Effect)
|
import Effect (Effect)
|
||||||
import Effect.Uncurried (EffectFn1, runEffectFn1)
|
import Effect.Uncurried (EffectFn1, mkEffectFn1, runEffectFn1)
|
||||||
import React.Basic.Hooks (Hook, unsafeHook)
|
import React.Basic.Hooks (Hook, unsafeHook)
|
||||||
|
|
||||||
foreign import data MotionValue ∷ Type -> Type
|
foreign import data MotionValue ∷ Type -> Type
|
||||||
@ -21,9 +21,14 @@ setButDoNotRender v = setImpl v false
|
|||||||
set ∷ ∀ a. a -> MotionValue a -> Effect Unit
|
set ∷ ∀ a. a -> MotionValue a -> Effect Unit
|
||||||
set v = setImpl v true
|
set v = setImpl v true
|
||||||
|
|
||||||
foreign import isAnimating ∷ ∀ a. (MotionValue a) -> Effect Boolean
|
foreign import isAnimating ∷ ∀ a. MotionValue a -> Effect Boolean
|
||||||
|
|
||||||
foreign import stop ∷ ∀ a. (MotionValue a) -> Effect Unit
|
foreign import stop ∷ ∀ a. MotionValue a -> Effect Unit
|
||||||
|
|
||||||
|
foreign import onChangeImpl ∷ ∀ a. EffectFn1 a Unit -> MotionValue a -> Effect (Effect Unit)
|
||||||
|
|
||||||
|
onChange ∷ ∀ a. (a -> Effect Unit) -> MotionValue a -> Effect (Effect Unit)
|
||||||
|
onChange = mkEffectFn1 >>> onChangeImpl
|
||||||
|
|
||||||
useMotionValue ∷ ∀ a. a -> Hook (UseMotionValue a) (MotionValue a)
|
useMotionValue ∷ ∀ a. a -> Hook (UseMotionValue a) (MotionValue a)
|
||||||
useMotionValue initialValue =
|
useMotionValue initialValue =
|
||||||
|
3
src/React/Basic/Popper/Hook.js
Normal file
3
src/React/Basic/Popper/Hook.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
const reactPopper = require('react-popper')
|
||||||
|
|
||||||
|
exports.usePopperImpl = reactPopper.usePopper
|
64
src/React/Basic/Popper/Hook.purs
Normal file
64
src/React/Basic/Popper/Hook.purs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
module React.Basic.Popper.Hook where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
import Data.String as String
|
||||||
|
import Data.Tuple (Tuple(..))
|
||||||
|
import Effect.Uncurried (EffectFn3, runEffectFn3)
|
||||||
|
import Foreign.Object (Object)
|
||||||
|
import Foreign.Object as Object
|
||||||
|
import Prim.Row (class Union)
|
||||||
|
import React.Basic.DOM (CSS)
|
||||||
|
import Unsafe.Coerce (unsafeCoerce)
|
||||||
|
import Yoga.Prelude.View (Hook, NodeRef, unsafeHook)
|
||||||
|
|
||||||
|
foreign import data UsePopper ∷ Type -> Type -> Type
|
||||||
|
|
||||||
|
type ReferenceElement =
|
||||||
|
NodeRef
|
||||||
|
|
||||||
|
type PopperElement =
|
||||||
|
NodeRef
|
||||||
|
|
||||||
|
type ArrowElement =
|
||||||
|
NodeRef
|
||||||
|
|
||||||
|
type DataAttributes =
|
||||||
|
{ popper ∷ Object String, arrow ∷ Object String }
|
||||||
|
|
||||||
|
type Styles =
|
||||||
|
{ popper ∷ CSS, arrow ∷ CSS }
|
||||||
|
|
||||||
|
type PopperData =
|
||||||
|
{ styles ∷ Styles, attributes ∷ DataAttributes }
|
||||||
|
|
||||||
|
foreign import data Modifier ∷ Type
|
||||||
|
|
||||||
|
modifierArrow ∷ ArrowElement -> Modifier
|
||||||
|
modifierArrow element = unsafeCoerce { name: "arrow", options: { element } }
|
||||||
|
|
||||||
|
modifierOffset ∷ { x ∷ Number, y ∷ Number } -> Modifier
|
||||||
|
modifierOffset { x, y } = unsafeCoerce { name: "offset", options: { offset: [ x, y ] } }
|
||||||
|
|
||||||
|
type Options =
|
||||||
|
( modifiers ∷ Array Modifier
|
||||||
|
, strategy ∷ String
|
||||||
|
)
|
||||||
|
|
||||||
|
foreign import usePopperImpl ∷ ∀ opts. EffectFn3 ReferenceElement PopperElement { | opts } PopperData
|
||||||
|
|
||||||
|
toDataAttributes ∷ Object String -> Object String
|
||||||
|
toDataAttributes o = Object.toArrayWithKey (Tuple <<< dropPrefix) o # Object.fromFoldable
|
||||||
|
where
|
||||||
|
dropPrefix = String.drop (String.length "data-")
|
||||||
|
|
||||||
|
usePopper ∷ ∀ opts opts_. Union opts opts_ Options => ReferenceElement -> PopperElement -> { | opts } -> Hook (UsePopper { | opts }) PopperData
|
||||||
|
usePopper elemRef popperRef opts =
|
||||||
|
unsafeHook do
|
||||||
|
result <- runEffectFn3 usePopperImpl elemRef popperRef opts
|
||||||
|
pure
|
||||||
|
$ result
|
||||||
|
{ attributes
|
||||||
|
{ popper = toDataAttributes result.attributes.popper
|
||||||
|
, arrow = toDataAttributes result.attributes.arrow
|
||||||
|
}
|
||||||
|
}
|
205
src/React/Basic/Popper/Story.purs
Normal file
205
src/React/Basic/Popper/Story.purs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
module React.Basic.Popper.Story where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
import Color as Color
|
||||||
|
import Data.Maybe (Maybe(..))
|
||||||
|
import Data.Nullable (null)
|
||||||
|
import Data.Tuple.Nested ((/\))
|
||||||
|
import Effect (Effect)
|
||||||
|
import Effect.Uncurried (mkEffectFn1)
|
||||||
|
import Effect.Unsafe (unsafePerformEffect)
|
||||||
|
import Framer.Motion (onTap)
|
||||||
|
import Framer.Motion as Motion
|
||||||
|
import React.Basic (JSX, element, fragment)
|
||||||
|
import React.Basic.DOM as R
|
||||||
|
import React.Basic.Emotion as E
|
||||||
|
import React.Basic.Hooks as React
|
||||||
|
import React.Basic.Popper.Hook (modifierArrow, modifierOffset, usePopper)
|
||||||
|
import Unsafe.Coerce (unsafeCoerce)
|
||||||
|
import Yoga.Block as Block
|
||||||
|
import Yoga.Block.Atom.Toggle as Toggle
|
||||||
|
import Yoga.Block.Container.Style (DarkOrLightMode(..))
|
||||||
|
import Yoga.Block.Container.Style as Styles
|
||||||
|
import Yoga.Block.Internal.CSS (nest)
|
||||||
|
import Yoga.Prelude.View (NodeRef, el, styled, styledLeaf)
|
||||||
|
|
||||||
|
default ∷
|
||||||
|
{ decorators ∷ Array (Effect JSX -> JSX)
|
||||||
|
, title ∷ String
|
||||||
|
}
|
||||||
|
default =
|
||||||
|
{ title: "Popper"
|
||||||
|
, decorators:
|
||||||
|
[ \storyFn ->
|
||||||
|
R.div_
|
||||||
|
[ element E.global { styles: Styles.global }
|
||||||
|
, unsafePerformEffect storyFn
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
popper ∷ Effect JSX
|
||||||
|
popper = do
|
||||||
|
example <- mkBasicExample
|
||||||
|
pure
|
||||||
|
$ fragment
|
||||||
|
[ R.div_
|
||||||
|
[ R.h2_ [ R.text "Some Popper" ]
|
||||||
|
, element example {}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
where
|
||||||
|
nullRef ∷ NodeRef
|
||||||
|
nullRef = unsafeCoerce null
|
||||||
|
|
||||||
|
mkBasicExample =
|
||||||
|
React.reactComponent "Popper example" \p -> React.do
|
||||||
|
referenceElement /\ setReferenceElement <- React.useState' nullRef
|
||||||
|
popperElement /\ setPopperElement <- React.useState' nullRef
|
||||||
|
arrowElement /\ setArrowElement <- React.useState' nullRef
|
||||||
|
{ styles, attributes } <-
|
||||||
|
usePopper referenceElement popperElement
|
||||||
|
{ modifiers:
|
||||||
|
[ modifierArrow arrowElement
|
||||||
|
, modifierOffset { x: 0.0, y: 8.0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
pure
|
||||||
|
$ fragment
|
||||||
|
$ [ element R.button'
|
||||||
|
{ type: "button"
|
||||||
|
, ref: unsafeCoerce (mkEffectFn1 setReferenceElement)
|
||||||
|
, children: [ R.text "Reference element" ]
|
||||||
|
}
|
||||||
|
, styled R.div'
|
||||||
|
{ className: "popper-element"
|
||||||
|
, css:
|
||||||
|
E.css
|
||||||
|
{ background: E.str "darkslateblue"
|
||||||
|
, borderRadius: E.str "8px"
|
||||||
|
, display: E.str "block"
|
||||||
|
, padding: E.str "4px 8px"
|
||||||
|
, "&[data-popper-placement^='top'] > .popper-arrow":
|
||||||
|
nest { bottom: E.str "-4px" }
|
||||||
|
, "&[data-popper-placement^='bottom'] > .popper-arrow":
|
||||||
|
nest { top: E.str "-4px" }
|
||||||
|
, "&[data-popper-placement^='left'] > .popper-arrow":
|
||||||
|
nest { right: E.str "-4px" }
|
||||||
|
, "&[data-popper-placement^='right'] > .popper-arrow":
|
||||||
|
nest { left: E.str "-4px" }
|
||||||
|
}
|
||||||
|
, ref: unsafeCoerce (mkEffectFn1 setPopperElement)
|
||||||
|
, style: styles.popper
|
||||||
|
, _data: attributes.popper
|
||||||
|
}
|
||||||
|
[ R.text "Popper Element"
|
||||||
|
, styledLeaf R.div'
|
||||||
|
{ className: "popper-arrow"
|
||||||
|
, id: "arrow"
|
||||||
|
, css:
|
||||||
|
E.css
|
||||||
|
{ position: E.str "absolute"
|
||||||
|
, width: E.str "8px"
|
||||||
|
, height: E.str "8px"
|
||||||
|
, zIndex: E.str "-1"
|
||||||
|
, "&::before":
|
||||||
|
nest
|
||||||
|
{ position: E.str "absolute"
|
||||||
|
, width: E.str "8px"
|
||||||
|
, height: E.str "8px"
|
||||||
|
, zIndex: E.str "-1"
|
||||||
|
, content: E.str "''"
|
||||||
|
, transform: E.str "rotate(45deg)"
|
||||||
|
, background: E.str "darkslateblue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, ref: unsafeCoerce (mkEffectFn1 setArrowElement)
|
||||||
|
, style: styles.arrow
|
||||||
|
, _data: attributes.arrow
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
animatedPopper ∷ Effect JSX
|
||||||
|
animatedPopper = do
|
||||||
|
example <- mkBasicExample
|
||||||
|
pure
|
||||||
|
$ fragment
|
||||||
|
[ R.div_
|
||||||
|
[ R.h2_ [ R.text "Some Popper" ]
|
||||||
|
, element example {}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
where
|
||||||
|
nullRef ∷ NodeRef
|
||||||
|
nullRef = unsafeCoerce null
|
||||||
|
|
||||||
|
mkBasicExample =
|
||||||
|
React.reactComponent "Popper example" \p -> React.do
|
||||||
|
referenceElement /\ setReferenceElement <- React.useState' nullRef
|
||||||
|
popperElement /\ setPopperElement <- React.useState' nullRef
|
||||||
|
arrowElement /\ setArrowElement <- React.useState' nullRef
|
||||||
|
{ styles, attributes } <-
|
||||||
|
usePopper referenceElement popperElement
|
||||||
|
{ modifiers:
|
||||||
|
[ modifierArrow arrowElement
|
||||||
|
, modifierOffset { x: 0.0, y: 8.0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
pure
|
||||||
|
$ fragment
|
||||||
|
$ [ element R.div'
|
||||||
|
{ ref: unsafeCoerce (mkEffectFn1 setReferenceElement)
|
||||||
|
, children: [ R.text "Reference element" ]
|
||||||
|
}
|
||||||
|
, el Motion.animatePresence {}
|
||||||
|
[ styled Motion.div
|
||||||
|
{ className: "popper-element"
|
||||||
|
, key: "heinz"
|
||||||
|
, css:
|
||||||
|
E.css
|
||||||
|
{ background: E.str "darkslateblue"
|
||||||
|
, borderRadius: E.str "8px"
|
||||||
|
, display: E.str "block"
|
||||||
|
, padding: E.str "4px 8px"
|
||||||
|
, "&[data-popper-placement^='top'] > .popper-arrow":
|
||||||
|
nest { bottom: E.str "-4px" }
|
||||||
|
, "&[data-popper-placement^='bottom'] > .popper-arrow":
|
||||||
|
nest { top: E.str "-4px" }
|
||||||
|
, "&[data-popper-placement^='left'] > .popper-arrow":
|
||||||
|
nest { right: E.str "-4px" }
|
||||||
|
, "&[data-popper-placement^='right'] > .popper-arrow":
|
||||||
|
nest { left: E.str "-4px" }
|
||||||
|
}
|
||||||
|
, ref: unsafeCoerce (mkEffectFn1 setPopperElement)
|
||||||
|
, style: styles.popper
|
||||||
|
, _data: attributes.popper
|
||||||
|
}
|
||||||
|
[ R.text "Popper Element"
|
||||||
|
, styledLeaf R.div'
|
||||||
|
{ className: "popper-arrow"
|
||||||
|
, id: "arrow"
|
||||||
|
, css:
|
||||||
|
E.css
|
||||||
|
{ position: E.str "absolute"
|
||||||
|
, width: E.str "8px"
|
||||||
|
, height: E.str "8px"
|
||||||
|
, zIndex: E.str "-1"
|
||||||
|
, "&::before":
|
||||||
|
nest
|
||||||
|
{ position: E.str "absolute"
|
||||||
|
, width: E.str "8px"
|
||||||
|
, height: E.str "8px"
|
||||||
|
, zIndex: E.str "-1"
|
||||||
|
, content: E.str "''"
|
||||||
|
, transform: E.str "rotate(45deg)"
|
||||||
|
, background: E.str "darkslateblue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, ref: unsafeCoerce (mkEffectFn1 setArrowElement)
|
||||||
|
, style: styles.arrow
|
||||||
|
, _data: attributes.arrow
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
@ -28,6 +28,7 @@ el_ x props = Hooks.element x props
|
|||||||
styled ∷
|
styled ∷
|
||||||
∀ props.
|
∀ props.
|
||||||
Lacks "children" props =>
|
Lacks "children" props =>
|
||||||
|
Lacks "keyp" props =>
|
||||||
ReactComponent { className ∷ String, children ∷ Array JSX | props } ->
|
ReactComponent { className ∷ String, children ∷ Array JSX | props } ->
|
||||||
{ className ∷ String, css ∷ Emotion.Style | props } -> Array JSX -> JSX
|
{ className ∷ String, css ∷ Emotion.Style | props } -> Array JSX -> JSX
|
||||||
styled x props children = Emotion.element x (Record.insert (SProxy ∷ SProxy "children") children props)
|
styled x props children = Emotion.element x (Record.insert (SProxy ∷ SProxy "children") children props)
|
||||||
|
@ -22,7 +22,7 @@ centre = Centre.component
|
|||||||
cluster ∷ ∀ p q. Union p q Cluster.Props => ReactComponent { | p }
|
cluster ∷ ∀ p q. Union p q Cluster.Props => ReactComponent { | p }
|
||||||
cluster = Cluster.component
|
cluster = Cluster.component
|
||||||
|
|
||||||
container ∷ ReactComponent Container.Props
|
container ∷ ∀ p q. Union p q Container.Props => ReactComponent { | p }
|
||||||
container = Container.component
|
container = Container.component
|
||||||
|
|
||||||
imposter ∷ ∀ p q. Union p q Imposter.Props => ReactComponent { | p }
|
imposter ∷ ∀ p q. Union p q Imposter.Props => ReactComponent { | p }
|
||||||
@ -34,7 +34,7 @@ modal = Modal.component
|
|||||||
range ∷ ∀ p q. Union p q Range.Props => ReactComponent { | p }
|
range ∷ ∀ p q. Union p q Range.Props => ReactComponent { | p }
|
||||||
range = Range.component
|
range = Range.component
|
||||||
|
|
||||||
segmented ∷ ReactComponent Segmented.ComponentProps
|
segmented ∷ ReactComponent Segmented.Props
|
||||||
segmented = Segmented.component
|
segmented = Segmented.component
|
||||||
|
|
||||||
sidebar ∷ ∀ p q. Union p q Sidebar.Props => ReactComponent { | p }
|
sidebar ∷ ∀ p q. Union p q Sidebar.Props => ReactComponent { | p }
|
||||||
|
@ -2,7 +2,6 @@ module Yoga.Block.Atom.Range.Style where
|
|||||||
|
|
||||||
import Yoga.Prelude.Style
|
import Yoga.Prelude.Style
|
||||||
import Data.Interpolate (i)
|
import Data.Interpolate (i)
|
||||||
import React.Basic.Emotion (inlineBlock)
|
|
||||||
import Yoga.Block.Container.Style (colour)
|
import Yoga.Block.Container.Style (colour)
|
||||||
|
|
||||||
type Props f r =
|
type Props f r =
|
||||||
|
@ -2,4 +2,4 @@ module Yoga.Block.Atom.Segmented
|
|||||||
( module Yoga.Block.Atom.Segmented.View
|
( module Yoga.Block.Atom.Segmented.View
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yoga.Block.Atom.Segmented.View (component, Props, ComponentProps)
|
import Yoga.Block.Atom.Segmented.View (component, Props, Item)
|
||||||
|
@ -15,6 +15,7 @@ cluster =
|
|||||||
{ overflow: auto
|
{ overflow: auto
|
||||||
, flex: str "1"
|
, flex: str "1"
|
||||||
, display: str "inline-flex"
|
, display: str "inline-flex"
|
||||||
|
, userSelect: none
|
||||||
}
|
}
|
||||||
|
|
||||||
segmented ∷ Style
|
segmented ∷ Style
|
||||||
@ -30,27 +31,25 @@ segmented = styles
|
|||||||
, minHeight: str "min-content"
|
, minHeight: str "min-content"
|
||||||
, background: str colour.background10
|
, background: str colour.background10
|
||||||
, boxShadow: str "inset 0 1 0 rgba(0,0,0,0.1)"
|
, boxShadow: str "inset 0 1 0 rgba(0,0,0,0.1)"
|
||||||
, borderRadius: str "10px"
|
, borderRadius: var "--s-1"
|
||||||
, border: str $ i "1px solid " colour.background15
|
, border: str $ i "1px solid " colour.background15
|
||||||
, borderBottom: str $ i "1px solid " colour.background20
|
, borderBottom: str $ i "1px solid " colour.background20
|
||||||
, padding: _0
|
, padding: _0
|
||||||
, overflow: scroll
|
, overflow: scroll
|
||||||
|
, userSelect: none
|
||||||
}
|
}
|
||||||
|
|
||||||
activeElement ∷ Style
|
activeElement ∷ Style
|
||||||
activeElement =
|
activeElement =
|
||||||
css
|
css
|
||||||
{ position: absolute
|
{ position: absolute
|
||||||
, borderRadius: str "8px"
|
, borderRadius: str "calc(var(--s-1) * 0.75)"
|
||||||
, background: str colour.interfaceBackground
|
, background: str colour.interfaceBackground
|
||||||
, border: str $ i "1px solid " colour.interfaceBackgroundShadow
|
|
||||||
, borderTop: str $ i "1px solid " colour.interfaceBackgroundHighlight
|
, borderTop: str $ i "1px solid " colour.interfaceBackgroundHighlight
|
||||||
, borderBottom: str $ i "1px solid " colour.interfaceBackgroundShadow
|
, borderBottom: str $ i "1px solid " colour.interfaceBackgroundShadow
|
||||||
, boxShadow: str "0 0 1px rgba(0,0,0,0.55)"
|
, boxShadow: str "0 1px 2px rgba(20,20,20,0.67)"
|
||||||
, margin: _0
|
|
||||||
, padding: _0
|
, padding: _0
|
||||||
, zIndex: str "3"
|
, zIndex: str "3"
|
||||||
, cursor: str "grab"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button ∷ { isFirst ∷ Boolean, isLast ∷ Boolean } -> Style
|
button ∷ { isFirst ∷ Boolean, isLast ∷ Boolean } -> Style
|
||||||
@ -60,17 +59,21 @@ button { isFirst, isLast } =
|
|||||||
, appearance: none
|
, appearance: none
|
||||||
, color: str colour.text
|
, color: str colour.text
|
||||||
, border: none
|
, border: none
|
||||||
, margin: _0
|
|
||||||
, padding: _0
|
, padding: _0
|
||||||
, fontSize: str "var(--s0)"
|
, fontSize: str "var(--s0)"
|
||||||
|
, marginLeft: str if isFirst then "1px" else "0"
|
||||||
|
, marginRight: str if isLast then "1px" else "0"
|
||||||
, boxSizing: borderBox
|
, boxSizing: borderBox
|
||||||
, zIndex: str "3"
|
, zIndex: str "3"
|
||||||
|
, minWidth: var "--s3"
|
||||||
, "&:active": nest { outline: str "0" }
|
, "&:active": nest { outline: str "0" }
|
||||||
, "&:focus": nest { outline: none }
|
, "&:focus": nest { outline: none }
|
||||||
, "&:focus > .ry-segmented-button__content":
|
, "&:focus > .ry-segmented-button__content":
|
||||||
nest
|
nest
|
||||||
{ borderColor: str colour.highlight
|
{ borderColor: str colour.highlight
|
||||||
|
, transition: str "all 0.083s ease 0.083s"
|
||||||
}
|
}
|
||||||
|
, userSelect: none
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonContent ∷ { isFirst ∷ Boolean, isLast ∷ Boolean } -> Style
|
buttonContent ∷ { isFirst ∷ Boolean, isLast ∷ Boolean } -> Style
|
||||||
@ -78,16 +81,18 @@ buttonContent { isFirst, isLast } =
|
|||||||
css
|
css
|
||||||
{ "&:active": nest { outline: str "0" }
|
{ "&:active": nest { outline: str "0" }
|
||||||
, "&:focus": nest { outline: none }
|
, "&:focus": nest { outline: none }
|
||||||
|
, paddingTop: str "1px"
|
||||||
|
, paddingBottom: str "1px"
|
||||||
, paddingLeft: str if isFirst then edgePadding else inBetweenPadding
|
, paddingLeft: str if isFirst then edgePadding else inBetweenPadding
|
||||||
, paddingRight: str if isLast then edgePadding else inBetweenPadding
|
, paddingRight: str if isLast then edgePadding else inBetweenPadding
|
||||||
, borderRadius: str "8px"
|
, borderRadius: str "calc(var(--s-1) * 0.75)"
|
||||||
, border: str $ i borderSize " solid transparent"
|
, border: str $ i borderSize " solid transparent"
|
||||||
, display: flex
|
, display: flex
|
||||||
, alignItems: center
|
, alignItems: center
|
||||||
|
, outlineRight: str "1px solid red"
|
||||||
, justifyContent: center
|
, justifyContent: center
|
||||||
, margin: _0
|
|
||||||
, minHeight: _100percent
|
|
||||||
, overflow: visible
|
, overflow: visible
|
||||||
|
, userSelect: none
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
borderSize = "var(--s-4)"
|
borderSize = "var(--s-4)"
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
module Yoga.Block.Atom.Segmented.View (component, Props, Item, MandatoryProps, PropsF, ComponentProps) where
|
module Yoga.Block.Atom.Segmented.View (component, Item, Props) where
|
||||||
|
|
||||||
import Yoga.Prelude.View
|
import Yoga.Prelude.View
|
||||||
import Control.Alt ((<|>))
|
|
||||||
import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT)
|
|
||||||
import Control.Monad.Trans.Class (lift)
|
|
||||||
import Control.MonadZero as MZ
|
|
||||||
import Data.Array as A
|
import Data.Array as A
|
||||||
import Data.FoldableWithIndex (foldMapWithIndex)
|
|
||||||
import Data.FunctorWithIndex (mapWithIndex)
|
|
||||||
import Data.Maybe (fromMaybe', isJust, maybe)
|
|
||||||
import Data.Newtype (wrap)
|
import Data.Newtype (wrap)
|
||||||
import Data.Time.Duration (Milliseconds(..))
|
import Data.Time.Duration (Milliseconds(..))
|
||||||
import Data.Traversable (traverse)
|
import Data.Traversable (traverse)
|
||||||
@ -17,15 +10,9 @@ import Data.TwoOrMore as TwoOrMore
|
|||||||
import Effect.Aff (delay)
|
import Effect.Aff (delay)
|
||||||
import Effect.Unsafe (unsafePerformEffect)
|
import Effect.Unsafe (unsafePerformEffect)
|
||||||
import Foreign.Object as Object
|
import Foreign.Object as Object
|
||||||
import Framer.Motion (VariantLabel)
|
|
||||||
import Framer.Motion as Motion
|
|
||||||
import Hooks.Key as Key
|
import Hooks.Key as Key
|
||||||
import Hooks.Scroll (useScrollPosition)
|
|
||||||
import Hooks.UseResize (useResize)
|
import Hooks.UseResize (useResize)
|
||||||
import Literals.Undefined (undefined)
|
import Math as Math
|
||||||
import MotionValue (useMotionValue)
|
|
||||||
import MotionValue as MotionValue
|
|
||||||
import Partial.Unsafe (unsafeCrashWith)
|
|
||||||
import React.Basic.DOM (css)
|
import React.Basic.DOM (css)
|
||||||
import React.Basic.DOM as R
|
import React.Basic.DOM as R
|
||||||
import React.Basic.Emotion as E
|
import React.Basic.Emotion as E
|
||||||
@ -33,214 +20,23 @@ import React.Basic.Extra.Hooks.UseKeyDown (useKeyDown)
|
|||||||
import React.Basic.Hooks (reactComponent)
|
import React.Basic.Hooks (reactComponent)
|
||||||
import React.Basic.Hooks as React
|
import React.Basic.Hooks as React
|
||||||
import React.Basic.Hooks.Aff (useAff)
|
import React.Basic.Hooks.Aff (useAff)
|
||||||
import Unsafe.Coerce (unsafeCoerce)
|
|
||||||
import Untagged.Castable (cast)
|
|
||||||
import Web.HTML.HTMLElement (HTMLElement, blur, focus, getBoundingClientRect)
|
|
||||||
import Web.HTML.HTMLElement as HTMLElement
|
import Web.HTML.HTMLElement as HTMLElement
|
||||||
import Yoga.Block.Atom.Segmented.Style as Style
|
import Yoga.Block.Atom.Segmented.Style as Style
|
||||||
|
import Yoga.Block.Atom.Segmented.View.ActiveIndicator as ActiveIndicator
|
||||||
type PropsF f =
|
|
||||||
(
|
|
||||||
| MandatoryProps + Style.Props f ()
|
|
||||||
)
|
|
||||||
|
|
||||||
type MandatoryProps r =
|
|
||||||
( activeItemRefs ∷ TwoOrMore (Ref (Nullable Node))
|
|
||||||
, activeItemIndex ∷ Int
|
|
||||||
, updateActiveIndex ∷ Int -> Effect Unit
|
|
||||||
| r
|
|
||||||
)
|
|
||||||
|
|
||||||
type Props =
|
|
||||||
PropsF Id
|
|
||||||
|
|
||||||
activeComponent ∷ ∀ p p_. Union (MandatoryProps p) p_ Props => ReactComponent { | MandatoryProps p }
|
|
||||||
activeComponent = rawActiveComponent
|
|
||||||
|
|
||||||
indexToVariant ∷ Int -> VariantLabel
|
|
||||||
indexToVariant = show >>> unsafeCoerce
|
|
||||||
|
|
||||||
type BBox =
|
|
||||||
{ top ∷ Number, left ∷ Number, width ∷ Number, height ∷ Number }
|
|
||||||
|
|
||||||
findOverlapping ∷ Int -> TwoOrMore BBox -> Number -> Int
|
|
||||||
findOverlapping activeIndex styles x =
|
|
||||||
fromMaybe activeIndex do
|
|
||||||
curr <- styles TwoOrMore.!! activeIndex
|
|
||||||
let fst = TwoOrMore.head styles
|
|
||||||
let lst = TwoOrMore.last styles
|
|
||||||
let inside e = (e.left < x) && (e.left + e.width) >= x
|
|
||||||
let tooFarLeft = MZ.guard (x <= fst.left + fst.width) $> 0
|
|
||||||
let tooFarRight = MZ.guard (x >= lst.left) $> TwoOrMore.length styles - 1
|
|
||||||
TwoOrMore.findIndex inside styles <|> tooFarLeft <|> tooFarRight
|
|
||||||
|
|
||||||
handleDrag ∷
|
|
||||||
{ activeItemIndex ∷ Int
|
|
||||||
, animationVariants ∷ TwoOrMore BBox
|
|
||||||
, x ∷ Number
|
|
||||||
} ->
|
|
||||||
{ left ∷ Number, width ∷ Number }
|
|
||||||
handleDrag { x, activeItemIndex, animationVariants } = do
|
|
||||||
let idx = findOverlapping activeItemIndex animationVariants x
|
|
||||||
let av = animationVariants
|
|
||||||
let firstVariant = av # TwoOrMore.head
|
|
||||||
let lastVariant = av # TwoOrMore.last
|
|
||||||
let baseVariant = av TwoOrMore.!! idx # fromMaybe' \_ -> unsafeCrashWith "shit"
|
|
||||||
let
|
|
||||||
closestVariant =
|
|
||||||
if x >= (baseVariant.left + (baseVariant.width / 2.0)) then
|
|
||||||
av TwoOrMore.!! (idx + 1) # fromMaybe lastVariant
|
|
||||||
else
|
|
||||||
av TwoOrMore.!! (idx - 1) # fromMaybe firstVariant
|
|
||||||
let
|
|
||||||
greater /\ smaller =
|
|
||||||
if baseVariant.left > closestVariant.left then
|
|
||||||
baseVariant /\ closestVariant
|
|
||||||
else
|
|
||||||
closestVariant /\ baseVariant
|
|
||||||
-- Total
|
|
||||||
let rangeStart = smaller.left + (smaller.width / 2.0)
|
|
||||||
let rangeEnd = greater.left + (greater.width / 2.0)
|
|
||||||
let range = rangeEnd - rangeStart
|
|
||||||
let ratio = ((x - rangeStart) / range)
|
|
||||||
let interpolatedWidth = (greater.width * ratio) + smaller.width * (1.0 - ratio)
|
|
||||||
-- Right
|
|
||||||
let rangeStartRight = smaller.left + smaller.width
|
|
||||||
let rangeEndRight = greater.left + greater.width
|
|
||||||
let rangeRight = rangeEndRight - rangeStartRight
|
|
||||||
let ratioRight = ((x + (interpolatedWidth / 2.0) - rangeStartRight) / rangeRight)
|
|
||||||
-- Left
|
|
||||||
let rangeStartLeft = smaller.left
|
|
||||||
let rangeEndLeft = greater.left
|
|
||||||
let rangeLeft = rangeEndLeft - rangeStartLeft
|
|
||||||
let ratioLeft = (((x - (interpolatedWidth / 2.0)) - rangeStartLeft) / rangeLeft)
|
|
||||||
-- Individual
|
|
||||||
let left = rangeStartLeft + (ratioLeft * rangeLeft)
|
|
||||||
let right = rangeStartRight + (ratioRight * rangeRight)
|
|
||||||
let width = right - left
|
|
||||||
if x < firstVariant.left then
|
|
||||||
{ left: firstVariant.left, width: firstVariant.width }
|
|
||||||
else
|
|
||||||
if x >= lastVariant.left + lastVariant.width then do
|
|
||||||
{ left: lastVariant.left, width: lastVariant.width }
|
|
||||||
else do
|
|
||||||
{ left, width }
|
|
||||||
|
|
||||||
rawActiveComponent ∷ ∀ p. ReactComponent { | p }
|
|
||||||
rawActiveComponent =
|
|
||||||
mkForwardRefComponent "SegmentedActive" do
|
|
||||||
\(props ∷ { | Props }) ref -> React.do
|
|
||||||
maybeAnimationVariants /\ setVariants <- useState' Nothing
|
|
||||||
maybeDragX /\ setDragX <- useState' Nothing
|
|
||||||
{ scrollX } <- useScrollPosition
|
|
||||||
activeLeft <- useMotionValue 0.0
|
|
||||||
activeWidth <- useMotionValue 0.0
|
|
||||||
useEffectAlways do
|
|
||||||
_ <-
|
|
||||||
runMaybeT do
|
|
||||||
rawStyles <- traverse getStyle props.activeItemRefs
|
|
||||||
let styles = rawStyles <#> \s -> s { left = s.left + scrollX }
|
|
||||||
unless (maybeAnimationVariants == Just styles) do
|
|
||||||
setVariants (Just styles) # lift
|
|
||||||
mempty
|
|
||||||
useLayoutEffect maybeDragX do
|
|
||||||
case maybeDragX, maybeAnimationVariants of
|
|
||||||
Just x, Just animationVariants -> do
|
|
||||||
let { left, width } = handleDrag { activeItemIndex: props.activeItemIndex, animationVariants, x }
|
|
||||||
activeLeft # MotionValue.set left
|
|
||||||
activeWidth # MotionValue.set width
|
|
||||||
_, _ -> mempty
|
|
||||||
mempty
|
|
||||||
let
|
|
||||||
variants ∷ Motion.Variants
|
|
||||||
variants = case maybeAnimationVariants of
|
|
||||||
Just animationVariants ->
|
|
||||||
animationVariants
|
|
||||||
# foldMapWithIndex (\i s -> Object.singleton (show i) (css s))
|
|
||||||
# Motion.variantsFromObject
|
|
||||||
Nothing -> (cast undefined) ∷ Motion.Variants
|
|
||||||
pure $ maybeAnimationVariants
|
|
||||||
# foldMap \animationVariants ->
|
|
||||||
styledLeaf Motion.div
|
|
||||||
{ css: Style.activeElement
|
|
||||||
, variants
|
|
||||||
, className: "ry-active-segmented-element"
|
|
||||||
, initial: Motion.initial (indexToVariant props.activeItemIndex)
|
|
||||||
, drag: Motion.drag "x"
|
|
||||||
, dragMomentum: Motion.dragMomentum false
|
|
||||||
, animate: Motion.animate $ indexToVariant props.activeItemIndex
|
|
||||||
, layout: Motion.layout true
|
|
||||||
, style:
|
|
||||||
css
|
|
||||||
{ left: activeLeft
|
|
||||||
, width: activeWidth
|
|
||||||
}
|
|
||||||
, onDragStart:
|
|
||||||
Motion.onDragStart \_ pi -> do
|
|
||||||
setDragX (Just pi.point.x)
|
|
||||||
, onDrag:
|
|
||||||
Motion.onDrag \_ pi -> do
|
|
||||||
when (isJust maybeDragX) $ setDragX (Just pi.point.x)
|
|
||||||
, onDragEnd:
|
|
||||||
Motion.onDragEnd \_ pi -> do
|
|
||||||
let x = maybeDragX # fromMaybe' \_ -> unsafeCrashWith "no x should not happen"
|
|
||||||
let newIdx = findOverlapping props.activeItemIndex animationVariants x
|
|
||||||
let v = animationVariants TwoOrMore.!! newIdx # fromMaybe' \_ -> unsafeCrashWith "omg"
|
|
||||||
setDragX Nothing
|
|
||||||
activeLeft # MotionValue.set v.left
|
|
||||||
activeWidth # MotionValue.set v.width
|
|
||||||
props.updateActiveIndex newIdx
|
|
||||||
, dragConstraints: Motion.dragConstraints { left: 0, right: 0 }
|
|
||||||
, dragElastic: Motion.dragElastic false
|
|
||||||
, transition:
|
|
||||||
Motion.transition
|
|
||||||
{ type: "tween", duration: if isJust maybeDragX then 0.0 else 0.167, ease: "easeOut" }
|
|
||||||
, _aria: Object.fromHomogeneous { hidden: "true" }
|
|
||||||
, ref
|
|
||||||
}
|
|
||||||
|
|
||||||
getStyle ∷
|
|
||||||
Ref (Nullable Node) ->
|
|
||||||
MaybeT Effect BBox
|
|
||||||
getStyle itemRef = do
|
|
||||||
node <- MaybeT $ readRefMaybe itemRef
|
|
||||||
htmlElement <- MaybeT $ pure $ HTMLElement.fromNode node
|
|
||||||
br <- lift $ getBoundingClientRect htmlElement
|
|
||||||
pure
|
|
||||||
{ width: br.width
|
|
||||||
, height: br.height
|
|
||||||
, left: br.left
|
|
||||||
, top: br.top
|
|
||||||
}
|
|
||||||
|
|
||||||
type Item =
|
type Item =
|
||||||
{ id ∷ String, value ∷ String }
|
{ id ∷ String, value ∷ String }
|
||||||
|
|
||||||
type ComponentProps =
|
type Props =
|
||||||
{ buttonContents ∷ TwoOrMore Item
|
{ buttonContents ∷ TwoOrMore Item
|
||||||
, activeIndex ∷ Int
|
, activeIndex ∷ Int
|
||||||
, updateActiveIndex ∷ Int -> Effect Unit
|
, updateActiveIndex ∷ Int -> Effect Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
getHTMLElementAtIndex ∷ Int -> TwoOrMore (NodeRef) -> Effect (Maybe HTMLElement)
|
component ∷ ReactComponent Props
|
||||||
getHTMLElementAtIndex idx refs =
|
|
||||||
runMaybeT do
|
|
||||||
ref <- refs TwoOrMore.!! idx # pure >>> wrap
|
|
||||||
node <- React.readRefMaybe ref # wrap
|
|
||||||
HTMLElement.fromNode node # pure >>> wrap
|
|
||||||
|
|
||||||
blurAtIndex ∷ Int -> TwoOrMore (Ref (Nullable Node)) -> Effect Unit
|
|
||||||
blurAtIndex idx refs = do
|
|
||||||
getHTMLElementAtIndex idx refs >>= traverse_ blur
|
|
||||||
|
|
||||||
focusAtIndex ∷ Int -> TwoOrMore (Ref (Nullable Node)) -> Effect Unit
|
|
||||||
focusAtIndex idx refs = do
|
|
||||||
getHTMLElementAtIndex idx refs >>= traverse_ focus
|
|
||||||
|
|
||||||
component ∷ ReactComponent ComponentProps
|
|
||||||
component =
|
component =
|
||||||
unsafePerformEffect
|
unsafePerformEffect
|
||||||
$ reactComponent "Segmented" \({ buttonContents, activeIndex, updateActiveIndex } ∷ ComponentProps) -> React.do
|
$ reactComponent "Segmented" \({ buttonContents, activeIndex, updateActiveIndex } ∷ Props) -> React.do
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
-- Store button refs for animation purposes
|
-- Store button refs for animation purposes
|
||||||
itemRefs /\ setItemRefs ∷ Maybe (TwoOrMore _) /\ _ <- useState' Nothing
|
itemRefs /\ setItemRefs ∷ Maybe (TwoOrMore _) /\ _ <- useState' Nothing
|
||||||
@ -248,6 +44,7 @@ component =
|
|||||||
refs <- traverse (const createRef) buttonContents
|
refs <- traverse (const createRef) buttonContents
|
||||||
setItemRefs (Just refs)
|
setItemRefs (Just refs)
|
||||||
mempty
|
mempty
|
||||||
|
windowWidth /\ setWindowWidth <- useState' 0.0
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
-- Support keyboard input
|
-- Support keyboard input
|
||||||
let
|
let
|
||||||
@ -272,10 +69,15 @@ component =
|
|||||||
-- Ensure redraw on window resize
|
-- Ensure redraw on window resize
|
||||||
windowSize <- useResize
|
windowSize <- useResize
|
||||||
useAff { windowSize } do
|
useAff { windowSize } do
|
||||||
delay (200.0 # Milliseconds)
|
delay
|
||||||
|
if Math.abs (windowWidth - windowSize.width) < 10.0 then
|
||||||
|
100.0 # Milliseconds
|
||||||
|
else
|
||||||
|
10.0 # Milliseconds
|
||||||
liftEffect do -- force rerender
|
liftEffect do -- force rerender
|
||||||
refs <- traverse (const createRef) buttonContents
|
refs <- traverse (const createRef) buttonContents
|
||||||
setItemRefs (Just refs)
|
setItemRefs (Just refs)
|
||||||
|
setWindowWidth windowSize.width
|
||||||
let
|
let
|
||||||
children ∷ Array JSX
|
children ∷ Array JSX
|
||||||
children = refsAndContents <#> mapWithIndex contentToChild # maybe mempty TwoOrMore.toArray
|
children = refsAndContents <#> mapWithIndex contentToChild # maybe mempty TwoOrMore.toArray
|
||||||
@ -320,12 +122,28 @@ component =
|
|||||||
, children:
|
, children:
|
||||||
itemRefs
|
itemRefs
|
||||||
# foldMap \activeItemRefs ->
|
# foldMap \activeItemRefs ->
|
||||||
React.element activeComponent
|
React.element ActiveIndicator.component
|
||||||
{ activeItemRefs
|
{ activeItemRefs
|
||||||
, activeItemIndex: activeIndex
|
, activeItemIndex: activeIndex
|
||||||
, updateActiveIndex
|
, updateActiveIndex
|
||||||
|
, windowWidth
|
||||||
}
|
}
|
||||||
A.: children
|
A.: children
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHTMLElementAtIndex ∷ Int -> TwoOrMore (NodeRef) -> Effect (Maybe HTMLElement)
|
||||||
|
getHTMLElementAtIndex idx refs =
|
||||||
|
runMaybeT do
|
||||||
|
ref <- refs TwoOrMore.!! idx # pure >>> wrap
|
||||||
|
node <- React.readRefMaybe ref # wrap
|
||||||
|
HTMLElement.fromNode node # pure >>> wrap
|
||||||
|
|
||||||
|
blurAtIndex ∷ Int -> TwoOrMore (Ref (Nullable Node)) -> Effect Unit
|
||||||
|
blurAtIndex idx refs = do
|
||||||
|
getHTMLElementAtIndex idx refs >>= traverse_ blur
|
||||||
|
|
||||||
|
focusAtIndex ∷ Int -> TwoOrMore (Ref (Nullable Node)) -> Effect Unit
|
||||||
|
focusAtIndex idx refs = do
|
||||||
|
getHTMLElementAtIndex idx refs >>= traverse_ focus
|
||||||
|
185
src/Yoga/Block/Atom/Segmented/View/ActiveIndicator.purs
Normal file
185
src/Yoga/Block/Atom/Segmented/View/ActiveIndicator.purs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
module Yoga.Block.Atom.Segmented.View.ActiveIndicator (Props, component) where
|
||||||
|
|
||||||
|
import Yoga.Prelude.View
|
||||||
|
import Control.MonadZero as MZ
|
||||||
|
import Data.Traversable (traverse)
|
||||||
|
import Data.TwoOrMore (TwoOrMore)
|
||||||
|
import Data.TwoOrMore as TwoOrMore
|
||||||
|
import Effect.Unsafe (unsafePerformEffect)
|
||||||
|
import Foreign.Object as Object
|
||||||
|
import Framer.Motion (VariantLabel)
|
||||||
|
import Framer.Motion as Motion
|
||||||
|
import Hooks.Scroll (useScrollPosition)
|
||||||
|
import Literals.Undefined (undefined)
|
||||||
|
import MotionValue (useMotionValue)
|
||||||
|
import MotionValue as MotionValue
|
||||||
|
import Partial.Unsafe (unsafeCrashWith)
|
||||||
|
import React.Basic.DOM (css)
|
||||||
|
import React.Basic.Emotion as Emotion
|
||||||
|
import React.Basic.Hooks (reactComponent)
|
||||||
|
import React.Basic.Hooks as React
|
||||||
|
import Unsafe.Coerce (unsafeCoerce)
|
||||||
|
import Yoga.Block.Atom.Segmented.Style as Style
|
||||||
|
|
||||||
|
type Props =
|
||||||
|
{ activeItemRefs ∷ TwoOrMore (Ref (Nullable Node))
|
||||||
|
, activeItemIndex ∷ Int
|
||||||
|
, updateActiveIndex ∷ Int -> Effect Unit
|
||||||
|
, windowWidth ∷ Number
|
||||||
|
}
|
||||||
|
|
||||||
|
component ∷ ReactComponent Props
|
||||||
|
component =
|
||||||
|
unsafePerformEffect
|
||||||
|
$ reactComponent "SegmentedActive" do
|
||||||
|
\(props ∷ Props) -> React.do
|
||||||
|
maybeAnimationVariants /\ setVariants <- useState' Nothing
|
||||||
|
maybeDragX /\ setDragX <- useState' Nothing
|
||||||
|
{ scrollX } <- useScrollPosition
|
||||||
|
activeLeft <- useMotionValue 0.0
|
||||||
|
activeWidth <- useMotionValue 0.0
|
||||||
|
useEffectAlways do
|
||||||
|
_ <-
|
||||||
|
runMaybeT do
|
||||||
|
rawStyles <- traverse getStyle props.activeItemRefs
|
||||||
|
let styles = rawStyles <#> \s -> s { left = s.left + scrollX }
|
||||||
|
unless (maybeAnimationVariants == Just styles) do
|
||||||
|
setVariants (Just styles) # lift
|
||||||
|
mempty
|
||||||
|
useLayoutEffect maybeDragX do
|
||||||
|
case maybeDragX, maybeAnimationVariants of
|
||||||
|
Just x, Just animationVariants -> do
|
||||||
|
let { left, width } = handleDrag { activeItemIndex: props.activeItemIndex, animationVariants, x }
|
||||||
|
activeLeft # MotionValue.set left
|
||||||
|
activeWidth # MotionValue.set width
|
||||||
|
_, _ -> mempty
|
||||||
|
mempty
|
||||||
|
let
|
||||||
|
variants ∷ Motion.Variants
|
||||||
|
variants = case maybeAnimationVariants of
|
||||||
|
Just animationVariants ->
|
||||||
|
animationVariants
|
||||||
|
# foldMapWithIndex (\i s -> Object.singleton (show i) (css s))
|
||||||
|
# Motion.variantsFromObject
|
||||||
|
Nothing -> (cast undefined) ∷ Motion.Variants
|
||||||
|
pure $ maybeAnimationVariants
|
||||||
|
# foldMap \animationVariants ->
|
||||||
|
Emotion.elementKeyed Motion.div
|
||||||
|
{ css: Style.activeElement
|
||||||
|
, key: show props.windowWidth -- to force rerender
|
||||||
|
, variants
|
||||||
|
, className: "ry-active-segmented-element"
|
||||||
|
, initial: Motion.initial (indexToVariant props.activeItemIndex)
|
||||||
|
, drag: Motion.drag "x"
|
||||||
|
, dragMomentum: Motion.dragMomentum false
|
||||||
|
, animate: Motion.animate $ indexToVariant props.activeItemIndex
|
||||||
|
, layout: Motion.layout true
|
||||||
|
, whileTap: Motion.whileTap $ css { scale: 0.9 }
|
||||||
|
, style:
|
||||||
|
css
|
||||||
|
{ left: activeLeft
|
||||||
|
, width: activeWidth
|
||||||
|
}
|
||||||
|
, onDragStart:
|
||||||
|
Motion.onDragStart \_ pi -> do
|
||||||
|
setDragX (Just pi.point.x)
|
||||||
|
, onDrag:
|
||||||
|
Motion.onDrag \_ pi -> do
|
||||||
|
when (isJust maybeDragX) $ setDragX (Just pi.point.x)
|
||||||
|
, onDragEnd:
|
||||||
|
Motion.onDragEnd \_ pi -> do
|
||||||
|
let x = maybeDragX # fromMaybe' \_ -> unsafeCrashWith "no x should not happen"
|
||||||
|
let newIdx = findOverlapping props.activeItemIndex animationVariants x
|
||||||
|
let v = animationVariants TwoOrMore.!! newIdx # fromMaybe' \_ -> unsafeCrashWith "omg"
|
||||||
|
setDragX Nothing
|
||||||
|
activeLeft # MotionValue.set v.left
|
||||||
|
activeWidth # MotionValue.set v.width
|
||||||
|
props.updateActiveIndex newIdx
|
||||||
|
, dragConstraints: Motion.dragConstraints { left: 0, right: 0 }
|
||||||
|
, dragElastic: Motion.dragElastic false
|
||||||
|
, transition:
|
||||||
|
Motion.transition
|
||||||
|
{ type: "tween", duration: if isJust maybeDragX then 0.0 else 0.167, ease: "easeOut" }
|
||||||
|
, _aria: Object.fromHomogeneous { hidden: "true" }
|
||||||
|
}
|
||||||
|
|
||||||
|
getStyle ∷
|
||||||
|
Ref (Nullable Node) ->
|
||||||
|
MaybeT Effect BBox
|
||||||
|
getStyle itemRef = do
|
||||||
|
br <- MaybeT $ getBoundingBoxFromRef itemRef
|
||||||
|
pure
|
||||||
|
{ width: br.width
|
||||||
|
, height: br.height
|
||||||
|
, left: br.left
|
||||||
|
, top: br.top
|
||||||
|
}
|
||||||
|
|
||||||
|
indexToVariant ∷ Int -> VariantLabel
|
||||||
|
indexToVariant = show >>> unsafeCoerce
|
||||||
|
|
||||||
|
type BBox =
|
||||||
|
{ top ∷ Number, left ∷ Number, width ∷ Number, height ∷ Number }
|
||||||
|
|
||||||
|
findOverlapping ∷ Int -> TwoOrMore BBox -> Number -> Int
|
||||||
|
findOverlapping activeIndex styles x =
|
||||||
|
fromMaybe activeIndex do
|
||||||
|
curr <- styles TwoOrMore.!! activeIndex
|
||||||
|
let fst = TwoOrMore.head styles
|
||||||
|
let lst = TwoOrMore.last styles
|
||||||
|
let inside e = (e.left < x) && (e.left + e.width) >= x
|
||||||
|
let tooFarLeft = MZ.guard (x <= fst.left + fst.width) $> 0
|
||||||
|
let tooFarRight = MZ.guard (x >= lst.left) $> TwoOrMore.length styles - 1
|
||||||
|
TwoOrMore.findIndex inside styles <|> tooFarLeft <|> tooFarRight
|
||||||
|
|
||||||
|
handleDrag ∷
|
||||||
|
{ activeItemIndex ∷ Int
|
||||||
|
, animationVariants ∷ TwoOrMore BBox
|
||||||
|
, x ∷ Number
|
||||||
|
} ->
|
||||||
|
{ left ∷ Number, width ∷ Number }
|
||||||
|
handleDrag { x, activeItemIndex, animationVariants } = do
|
||||||
|
let idx = findOverlapping activeItemIndex animationVariants x
|
||||||
|
let av = animationVariants
|
||||||
|
let firstVariant = av # TwoOrMore.head
|
||||||
|
let lastVariant = av # TwoOrMore.last
|
||||||
|
let baseVariant = av TwoOrMore.!! idx # fromMaybe' \_ -> unsafeCrashWith "shit"
|
||||||
|
let
|
||||||
|
closestVariant =
|
||||||
|
if x >= (baseVariant.left + (baseVariant.width / 2.0)) then
|
||||||
|
av TwoOrMore.!! (idx + 1) # fromMaybe lastVariant
|
||||||
|
else
|
||||||
|
av TwoOrMore.!! (idx - 1) # fromMaybe firstVariant
|
||||||
|
let
|
||||||
|
greater /\ smaller =
|
||||||
|
if baseVariant.left > closestVariant.left then
|
||||||
|
baseVariant /\ closestVariant
|
||||||
|
else
|
||||||
|
closestVariant /\ baseVariant
|
||||||
|
-- Total
|
||||||
|
let rangeStart = smaller.left + (smaller.width / 2.0)
|
||||||
|
let rangeEnd = greater.left + (greater.width / 2.0)
|
||||||
|
let range = rangeEnd - rangeStart
|
||||||
|
let ratio = ((x - rangeStart) / range)
|
||||||
|
let interpolatedWidth = (greater.width * ratio) + smaller.width * (1.0 - ratio)
|
||||||
|
-- Right
|
||||||
|
let rangeStartRight = smaller.left + smaller.width
|
||||||
|
let rangeEndRight = greater.left + greater.width
|
||||||
|
let rangeRight = rangeEndRight - rangeStartRight
|
||||||
|
let ratioRight = ((x + (interpolatedWidth / 2.0) - rangeStartRight) / rangeRight)
|
||||||
|
-- Left
|
||||||
|
let rangeStartLeft = smaller.left
|
||||||
|
let rangeEndLeft = greater.left
|
||||||
|
let rangeLeft = rangeEndLeft - rangeStartLeft
|
||||||
|
let ratioLeft = (((x - (interpolatedWidth / 2.0)) - rangeStartLeft) / rangeLeft)
|
||||||
|
-- Individual
|
||||||
|
let left = rangeStartLeft + (ratioLeft * rangeLeft)
|
||||||
|
let right = rangeStartRight + (ratioRight * rangeRight)
|
||||||
|
let width = right - left
|
||||||
|
if x < firstVariant.left then
|
||||||
|
{ left: firstVariant.left, width: firstVariant.width }
|
||||||
|
else
|
||||||
|
if x >= lastVariant.left + lastVariant.width then do
|
||||||
|
{ left: lastVariant.left, width: lastVariant.width }
|
||||||
|
else do
|
||||||
|
{ left, width }
|
5
src/Yoga/Block/Atom/Toggle.purs
Normal file
5
src/Yoga/Block/Atom/Toggle.purs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module Yoga.Block.Atom.Toggle
|
||||||
|
( module Yoga.Block.Atom.Toggle.View
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Yoga.Block.Atom.Toggle.View (component, Props)
|
15
src/Yoga/Block/Atom/Toggle/Spec.purs
Normal file
15
src/Yoga/Block/Atom/Toggle/Spec.purs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module Yoga.Block.Atom.Toggle.Spec where
|
||||||
|
|
||||||
|
import Yoga.Prelude.Spec
|
||||||
|
import Yoga.Block.Atom.Toggle as Toggle
|
||||||
|
|
||||||
|
spec ∷ Spec Unit
|
||||||
|
spec =
|
||||||
|
after_ cleanup do
|
||||||
|
describe "The toggle" do
|
||||||
|
it "renders without errors" do
|
||||||
|
void
|
||||||
|
$ renderComponent Toggle.component
|
||||||
|
{ value: true
|
||||||
|
, onToggle: mempty
|
||||||
|
}
|
71
src/Yoga/Block/Atom/Toggle/Story.purs
Normal file
71
src/Yoga/Block/Atom/Toggle/Story.purs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
module Yoga.Block.Atom.Toggle.Story where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
import Color as Color
|
||||||
|
import Data.Maybe (Maybe(..))
|
||||||
|
import Data.Tuple.Nested ((/\))
|
||||||
|
import Effect (Effect)
|
||||||
|
import React.Basic (JSX, element, fragment)
|
||||||
|
import React.Basic.DOM as R
|
||||||
|
import React.Basic.Hooks as React
|
||||||
|
import Yoga.Block as Block
|
||||||
|
import Yoga.Block.Atom.Toggle as Toggle
|
||||||
|
import Yoga.Block.Container.Style (DarkOrLightMode(..))
|
||||||
|
|
||||||
|
default ∷
|
||||||
|
{ title ∷ String
|
||||||
|
}
|
||||||
|
default =
|
||||||
|
{ title: "Atom/Toggle"
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle ∷ Effect JSX
|
||||||
|
toggle = do
|
||||||
|
example <- mkBasicExample
|
||||||
|
darkLight <- mkDarkLightToggle
|
||||||
|
pure
|
||||||
|
$ fragment
|
||||||
|
[ R.div_
|
||||||
|
[ R.h2_ [ R.text "Basics" ]
|
||||||
|
, element example {}
|
||||||
|
, R.h2_ [ R.text "Dark Light toggle" ]
|
||||||
|
, element darkLight {}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
where
|
||||||
|
mkBasicExample =
|
||||||
|
React.reactComponent "Toggle example" \p -> React.do
|
||||||
|
isOn /\ turnOnOrOff <- React.useState' false
|
||||||
|
pure
|
||||||
|
$ element Toggle.component
|
||||||
|
{ value: isOn
|
||||||
|
, onToggle: turnOnOrOff
|
||||||
|
}
|
||||||
|
|
||||||
|
mkDarkLightToggle =
|
||||||
|
React.reactComponent "Toggle dark night example" \p -> React.do
|
||||||
|
isOn /\ turnOnOrOff <- React.useState' false
|
||||||
|
theme /\ setTheme <- React.useState' Nothing
|
||||||
|
let
|
||||||
|
content =
|
||||||
|
element Toggle.component
|
||||||
|
{ value: isOn
|
||||||
|
, onToggle:
|
||||||
|
\b -> do
|
||||||
|
turnOnOrOff b
|
||||||
|
setTheme (Just if b then DarkMode else LightMode)
|
||||||
|
, on: R.text "🌒"
|
||||||
|
, off: R.text "🌞"
|
||||||
|
, backgroundOn:
|
||||||
|
Color.hsl 205.0 1.0 0.93
|
||||||
|
, backgroundOff:
|
||||||
|
Color.hsl 260.0 0.7 0.45
|
||||||
|
}
|
||||||
|
pure
|
||||||
|
$ case theme of
|
||||||
|
Nothing -> element Block.container { content }
|
||||||
|
Just themeVariant ->
|
||||||
|
element Block.container
|
||||||
|
{ content
|
||||||
|
, themeVariant: themeVariant
|
||||||
|
}
|
85
src/Yoga/Block/Atom/Toggle/Style.purs
Normal file
85
src/Yoga/Block/Atom/Toggle/Style.purs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
module Yoga.Block.Atom.Toggle.Style where
|
||||||
|
|
||||||
|
import Yoga.Prelude.Style
|
||||||
|
import Yoga.Block.Container.Style (colour)
|
||||||
|
|
||||||
|
type Props f r =
|
||||||
|
( css ∷ f Style
|
||||||
|
, backgroundOn ∷ f Color
|
||||||
|
, backgroundOff ∷ f Color
|
||||||
|
| r
|
||||||
|
)
|
||||||
|
|
||||||
|
button ∷ Style
|
||||||
|
button =
|
||||||
|
css
|
||||||
|
{ position: relative
|
||||||
|
, background: str colour.inputBackground
|
||||||
|
, border: str $ "1px solid " <> colour.inputBorder
|
||||||
|
, borderRadius: str "calc(var(--s2) / 2)"
|
||||||
|
, height: var "--s2"
|
||||||
|
, width: str "calc(1.15 * var(--s3))"
|
||||||
|
, margin: _0
|
||||||
|
, padding: _0
|
||||||
|
}
|
||||||
|
|
||||||
|
theToggle ∷ ∀ p. { | Props OptionalProp p } -> Style
|
||||||
|
theToggle props =
|
||||||
|
css
|
||||||
|
{ width: var "--s2"
|
||||||
|
, height: var "--s2"
|
||||||
|
, background: str $ colour.interfaceBackground
|
||||||
|
, border: none
|
||||||
|
, borderRadius: str $ "calc(var(--s2) / 2)"
|
||||||
|
, position: absolute
|
||||||
|
, top: str "-1px"
|
||||||
|
, left: _0
|
||||||
|
, margin: _0
|
||||||
|
, boxShadow: str "0 0.5px 3px rgba(0,0,0,0.50)"
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleTextContainer ∷ Style
|
||||||
|
toggleTextContainer =
|
||||||
|
css
|
||||||
|
{ width: _100percent
|
||||||
|
, border: none
|
||||||
|
, fontWeight: str "bold"
|
||||||
|
, position: absolute
|
||||||
|
, top: _0
|
||||||
|
, lineHeight: var "--s2"
|
||||||
|
, height: var "--s2"
|
||||||
|
, display: flex
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleText ∷ Style
|
||||||
|
toggleText =
|
||||||
|
css
|
||||||
|
{ textAlign: str "left"
|
||||||
|
, margin: _0
|
||||||
|
, width: str "50%"
|
||||||
|
, height: str "100%"
|
||||||
|
, display: flex
|
||||||
|
, justifyContent: center
|
||||||
|
, alignItems: center
|
||||||
|
, color: str colour.interfaceTextDisabled
|
||||||
|
, "&:first-child":
|
||||||
|
nest
|
||||||
|
{ color: str colour.successText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputDisabled ∷ Style
|
||||||
|
inputDisabled =
|
||||||
|
css
|
||||||
|
{ "input[type=toggle]::-webkit-slider-thumb": nested thumbStyleDisabled
|
||||||
|
, "input[type=toggle]::-moz-toggle-thumb": nested thumbStyleDisabled
|
||||||
|
}
|
||||||
|
where
|
||||||
|
thumbStyleDisabled =
|
||||||
|
css
|
||||||
|
{ background: str "#fcfcfc"
|
||||||
|
, boxShadow: str "0 calc(var(--s-4)/2) var(--s-3) rgba(88,88,88,0.2)"
|
||||||
|
}
|
||||||
|
|
||||||
|
disabled ∷ Style
|
||||||
|
disabled = css { backgroundColor: str colour.background30 }
|
203
src/Yoga/Block/Atom/Toggle/View.purs
Normal file
203
src/Yoga/Block/Atom/Toggle/View.purs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
module Yoga.Block.Atom.Toggle.View (component, MandatoryProps, Props, PropsF) where
|
||||||
|
|
||||||
|
import Yoga.Prelude.View
|
||||||
|
import Color as Color
|
||||||
|
import Data.Interpolate (i)
|
||||||
|
import Effect.Class.Console as Console
|
||||||
|
import Foreign.Object as Object
|
||||||
|
import Framer.Motion as Motion
|
||||||
|
import React.Basic.DOM (css)
|
||||||
|
import React.Basic.DOM as R
|
||||||
|
import React.Basic.Emotion as Emotion
|
||||||
|
import React.Basic.Hooks as React
|
||||||
|
import Yoga.Block.Atom.Toggle.Style as Style
|
||||||
|
import Yoga.Block.Container.Style (colour)
|
||||||
|
|
||||||
|
type PropsF f =
|
||||||
|
( className ∷ f String
|
||||||
|
, on ∷ f JSX
|
||||||
|
, off ∷ f JSX
|
||||||
|
| Style.Props f (MandatoryProps InputProps)
|
||||||
|
)
|
||||||
|
|
||||||
|
type MandatoryProps r =
|
||||||
|
( value ∷ Boolean
|
||||||
|
, onToggle ∷ Boolean -> Effect Unit
|
||||||
|
| r
|
||||||
|
)
|
||||||
|
|
||||||
|
data TappingState
|
||||||
|
= TapAllowed
|
||||||
|
| TapNotAllowed
|
||||||
|
|
||||||
|
data DragState
|
||||||
|
= NotDragging
|
||||||
|
| Dragging { startX ∷ Number }
|
||||||
|
| DragDone { startX ∷ Number, endX ∷ Number }
|
||||||
|
|
||||||
|
derive instance eqDragState ∷ Eq DragState
|
||||||
|
|
||||||
|
instance showDragState ∷ Show DragState where
|
||||||
|
show = case _ of
|
||||||
|
NotDragging -> "NotDragging"
|
||||||
|
Dragging x -> "Dragging " <> (show x)
|
||||||
|
DragDone x -> "DragDone " <> (show x)
|
||||||
|
|
||||||
|
type Props =
|
||||||
|
PropsF Id
|
||||||
|
|
||||||
|
type PropsOptional =
|
||||||
|
PropsF OptionalProp
|
||||||
|
|
||||||
|
component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | MandatoryProps p }
|
||||||
|
component = rawComponent
|
||||||
|
|
||||||
|
rawComponent ∷ ∀ p. ReactComponent (Record p)
|
||||||
|
rawComponent =
|
||||||
|
mkForwardRefComponent "Toggle" do
|
||||||
|
\(props ∷ { | PropsOptional }) ref -> React.do
|
||||||
|
let disabled = props.disabled
|
||||||
|
tapState <- useRef TapNotAllowed
|
||||||
|
dragState /\ setDragState <- React.useState' NotDragging
|
||||||
|
maxLeft /\ setMaxLeft <- useState' 0.0
|
||||||
|
buttonRef <- useRef null
|
||||||
|
toggleRef <- useRef null
|
||||||
|
leftState /\ setLeftState <- React.useState' 0.0
|
||||||
|
let
|
||||||
|
getWidth aRef = do
|
||||||
|
bbox <- getBoundingBoxFromRef aRef
|
||||||
|
pure $ bbox <#> _.width # fromMaybe 0.0
|
||||||
|
buttonWidth = getWidth buttonRef
|
||||||
|
toggleWidth = getWidth toggleRef
|
||||||
|
useLayoutEffectAlways do
|
||||||
|
when (maxLeft == 0.0) do
|
||||||
|
bw <- buttonWidth
|
||||||
|
tw <- toggleWidth
|
||||||
|
setMaxLeft (bw - tw)
|
||||||
|
mempty
|
||||||
|
let
|
||||||
|
toggleVariants =
|
||||||
|
{ off: { x: 0.0 }
|
||||||
|
, on: { x: maxLeft }
|
||||||
|
}
|
||||||
|
toggleVariant = Motion.makeVariantLabels toggleVariants
|
||||||
|
buttonVariants =
|
||||||
|
{ off: { backgroundColor: (Emotion.str <<< Color.cssStringRGBA <$> props.backgroundOn) ?|| Emotion.str colour.inputBackground }
|
||||||
|
, on: { backgroundColor: (Emotion.str <<< Color.cssStringRGBA <$> props.backgroundOff) ?|| Emotion.str colour.success }
|
||||||
|
}
|
||||||
|
buttonVariant = Motion.makeVariantLabels buttonVariants
|
||||||
|
hasFocus /\ setHasFocus <- useState' false
|
||||||
|
useEffect dragState do
|
||||||
|
case dragState of
|
||||||
|
NotDragging -> mempty
|
||||||
|
Dragging { startX } -> writeRef tapState TapNotAllowed
|
||||||
|
DragDone { startX, endX } -> do
|
||||||
|
maybeBbox <- getBoundingBoxFromRef buttonRef
|
||||||
|
for_ maybeBbox \bbox -> do
|
||||||
|
if endX - startX <= (bbox.left - startX) + (bbox.width / 2.0) then do
|
||||||
|
props.onToggle false
|
||||||
|
else do
|
||||||
|
props.onToggle true
|
||||||
|
mempty
|
||||||
|
pure
|
||||||
|
$ styled Motion.button
|
||||||
|
{ className: "ry-toggle"
|
||||||
|
, css: Style.button <> guard props.disabled Style.inputDisabled
|
||||||
|
, onFocus: handler_ $ setHasFocus true
|
||||||
|
, onBlur: handler_ $ setHasFocus false
|
||||||
|
, transition: Motion.transition { type: "tween", duration: 0.33, ease: "easeOut" }
|
||||||
|
, variants: Motion.variants buttonVariants
|
||||||
|
, animate: Motion.animate if props.value then buttonVariant.on else buttonVariant.off
|
||||||
|
, value: show props.value
|
||||||
|
, onClick: handler preventDefault \_ -> props.onToggle (not props.value)
|
||||||
|
, style: props.style
|
||||||
|
, _data: Object.singleton "testid" "toggle-testid"
|
||||||
|
, role: "switch"
|
||||||
|
, _aria: Object.singleton "checked" "switch"
|
||||||
|
, ref: buttonRef
|
||||||
|
}
|
||||||
|
[ styled R.div'
|
||||||
|
{ className: "ry-toggle-text"
|
||||||
|
, css: Style.toggleTextContainer
|
||||||
|
}
|
||||||
|
[ styled R.div'
|
||||||
|
{ className: "ry-toggle-text-on"
|
||||||
|
, css: Style.toggleText
|
||||||
|
}
|
||||||
|
[ el Motion.animatePresence {}
|
||||||
|
[ guard (props.value)
|
||||||
|
$ styled Motion.div
|
||||||
|
{ className: "ry-toggle-text-on-container"
|
||||||
|
, css: Style.toggleOnText
|
||||||
|
, key: "ry-toggle-text-on-container"
|
||||||
|
, initial: Motion.initial $ css { scale: 0, opacity: 0 }
|
||||||
|
, animate: Motion.animate $ css { scale: 1, opacity: 1 }
|
||||||
|
, exit: Motion.exit $ css { scale: 0, opacity: 0 }
|
||||||
|
}
|
||||||
|
[ props.on ?|| R.text "I" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, styled R.div'
|
||||||
|
{ className: "ry-toggle-text-off"
|
||||||
|
, css: Style.toggleText
|
||||||
|
}
|
||||||
|
[ el Motion.animatePresence {}
|
||||||
|
[ guard (not props.value)
|
||||||
|
$ styled Motion.div
|
||||||
|
{ className: "ry-toggle-text-on-container"
|
||||||
|
, css: Style.toggleOnText
|
||||||
|
, key: "ry-toggle-text-on-container"
|
||||||
|
, initial: Motion.initial $ css { scale: 0, opacity: 0 }
|
||||||
|
, animate: Motion.animate $ css { scale: 1, opacity: 1 }
|
||||||
|
, exit: Motion.exit $ css { scale: 0, opacity: 0 }
|
||||||
|
}
|
||||||
|
[ props.off ?|| R.text "O" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, styled Motion.div
|
||||||
|
{ className: "ry-toggle-toggle"
|
||||||
|
, css: Style.theToggle props
|
||||||
|
, layout: Motion.layout true
|
||||||
|
, onClick: handler stopPropagation mempty
|
||||||
|
, drag: Motion.drag "x"
|
||||||
|
, dragMomentum: Motion.dragMomentum false
|
||||||
|
, dragElastic: Motion.dragElastic false
|
||||||
|
, dragConstraints:
|
||||||
|
Motion.dragConstraints
|
||||||
|
{ left: if props.value then negate maxLeft else zero
|
||||||
|
, right: if props.value then zero else maxLeft
|
||||||
|
}
|
||||||
|
, variants: Motion.variants toggleVariants
|
||||||
|
, transition: Motion.transition { type: "tween", duration: 0.33, ease: "easeOut" }
|
||||||
|
, whileTap: Motion.prop $ css { scale: 0.85, transition: { type: "tween", duration: 0.10, ease: "easeInOut" } }
|
||||||
|
, onTapStart: Motion.onTapStart \_ _ -> writeRef tapState TapAllowed
|
||||||
|
, onTap:
|
||||||
|
Motion.onTap \_ pi -> do
|
||||||
|
ts <- readRef tapState
|
||||||
|
case ts of
|
||||||
|
TapAllowed -> props.onToggle $ not props.value
|
||||||
|
_ -> mempty
|
||||||
|
mempty
|
||||||
|
, onTapCancel:
|
||||||
|
Motion.onTapCancel \_ pi -> do
|
||||||
|
writeRef tapState TapNotAllowed
|
||||||
|
mempty
|
||||||
|
, animate: Motion.animate if props.value then toggleVariant.on else toggleVariant.off
|
||||||
|
, onDragStart:
|
||||||
|
Motion.onDragStart \_ pi -> do
|
||||||
|
maybeBBox <- getBoundingBoxFromRef toggleRef
|
||||||
|
let x = maybeBBox <#> \bbox -> bbox.left + (bbox.width / 2.0)
|
||||||
|
setDragState $ Dragging { startX: x # fromMaybe pi.point.x }
|
||||||
|
, onDragEnd:
|
||||||
|
Motion.onDragEnd \_ pi -> do
|
||||||
|
case dragState of
|
||||||
|
Dragging { startX } -> do
|
||||||
|
maybeBBox <- getBoundingBoxFromRef toggleRef
|
||||||
|
let x = maybeBBox <#> \bbox -> bbox.left + (bbox.width / 2.0)
|
||||||
|
setDragState (DragDone { startX, endX: x # fromMaybe pi.point.x })
|
||||||
|
other -> Console.warn $ i "Unexpected drag state " (show other) " in onDragEvent"
|
||||||
|
, ref: toggleRef
|
||||||
|
}
|
||||||
|
[]
|
||||||
|
]
|
@ -1,18 +1,18 @@
|
|||||||
module Yoga.Block.Container.Spec where
|
module Yoga.Block.Container.Spec where
|
||||||
|
|
||||||
import Yoga.Prelude.Spec
|
import Yoga.Prelude.Spec
|
||||||
import Yoga.Block.Container as Container
|
|
||||||
import React.Basic.DOM as R
|
import React.Basic.DOM as R
|
||||||
import React.Basic.Hooks (reactChildrenFromArray)
|
import React.Basic.Hooks (JSX)
|
||||||
|
import Yoga.Block.Container as Container
|
||||||
|
|
||||||
spec ∷ Spec Unit
|
spec ∷ Spec Unit
|
||||||
spec =
|
spec =
|
||||||
after_ cleanup do
|
after_ cleanup do
|
||||||
describe "The container" do
|
describe "The container" do
|
||||||
it "renders without errors" do
|
it "renders without errors" do
|
||||||
void $ renderComponent Container.component { children: reactChildrenFromArray [] }
|
void $ renderComponent Container.component { content: mempty ∷ JSX }
|
||||||
it "displays its children" do
|
it "displays its children" do
|
||||||
let children = reactChildrenFromArray [ R.text "Test Text" ]
|
let content = R.text "Test Text"
|
||||||
{ findByText } <- renderComponent Container.component { children }
|
{ findByText } <- renderComponent Container.component { content }
|
||||||
elem <- findByText "Test Text"
|
elem <- findByText "Test Text"
|
||||||
elem `textContentShouldEqual` "Test Text"
|
elem `textContentShouldEqual` "Test Text"
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
module Yoga.Block.Container.Story where
|
module Yoga.Block.Container.Story where
|
||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
import Yoga.Block.Layout.Cluster as Cluster
|
import Effect (Effect)
|
||||||
|
import React.Basic (JSX, element, fragment)
|
||||||
|
import React.Basic.DOM as R
|
||||||
|
import Yoga (el, styledLeaf)
|
||||||
import Yoga.Block.Container as Container
|
import Yoga.Block.Container as Container
|
||||||
import Yoga.Block.Container.Style (inputFocus)
|
import Yoga.Block.Container.Style (inputFocus)
|
||||||
|
import Yoga.Block.Layout.Cluster as Cluster
|
||||||
import Yoga.Block.Layout.Stack as Stack
|
import Yoga.Block.Layout.Stack as Stack
|
||||||
import Effect (Effect)
|
|
||||||
import React.Basic (JSX, element)
|
|
||||||
import React.Basic.DOM as R
|
|
||||||
import React.Basic.Hooks (reactChildrenFromArray)
|
|
||||||
import Yoga (el, styledLeaf)
|
|
||||||
|
|
||||||
default ∷ { title ∷ String }
|
default ∷ { title ∷ String }
|
||||||
default = { title: "Pages/Container" }
|
default = { title: "Pages/Container" }
|
||||||
@ -18,8 +17,8 @@ container ∷ Effect JSX
|
|||||||
container =
|
container =
|
||||||
pure
|
pure
|
||||||
( element Container.component
|
( element Container.component
|
||||||
{ children:
|
{ content:
|
||||||
reactChildrenFromArray
|
fragment
|
||||||
[ R.text "Content"
|
[ R.text "Content"
|
||||||
, el Stack.component {}
|
, el Stack.component {}
|
||||||
[ el Cluster.component {}
|
[ el Cluster.component {}
|
||||||
|
@ -8,8 +8,27 @@ import Foreign.Object as Object
|
|||||||
import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmap, hmapWithIndex)
|
import Heterogeneous.Mapping (class HMapWithIndex, class MappingWithIndex, hmap, hmapWithIndex)
|
||||||
import Unsafe.Coerce (unsafeCoerce)
|
import Unsafe.Coerce (unsafeCoerce)
|
||||||
|
|
||||||
|
data DarkOrLightMode
|
||||||
|
= DarkMode
|
||||||
|
| LightMode
|
||||||
|
|
||||||
|
lightModeStyle ∷ Style
|
||||||
|
lightModeStyle = unsafeCoerce lightModeVariables
|
||||||
|
|
||||||
|
darkModeStyle ∷ Style
|
||||||
|
darkModeStyle = unsafeCoerce darkModeVariables
|
||||||
|
|
||||||
|
darkMode ∷ Style
|
||||||
|
darkMode = mkGlobal (Just DarkMode)
|
||||||
|
|
||||||
|
lightMode ∷ Style
|
||||||
|
lightMode = mkGlobal (Just LightMode)
|
||||||
|
|
||||||
global ∷ Style
|
global ∷ Style
|
||||||
global =
|
global = mkGlobal Nothing
|
||||||
|
|
||||||
|
mkGlobal ∷ Maybe DarkOrLightMode -> Style
|
||||||
|
mkGlobal maybeMode =
|
||||||
css
|
css
|
||||||
{ "body, html":
|
{ "body, html":
|
||||||
nested
|
nested
|
||||||
@ -17,7 +36,8 @@ global =
|
|||||||
{ minHeight: 100.0 # vh
|
{ minHeight: 100.0 # vh
|
||||||
, minWidth: 100.0 # vw
|
, minWidth: 100.0 # vw
|
||||||
, lineHeight: str "1.15"
|
, lineHeight: str "1.15"
|
||||||
, "-webkit-text-size-adjust": _100percent
|
, "WebkitTextSizeAdjust": _100percent
|
||||||
|
, transition: str "background,color 0.33s ease-in"
|
||||||
}
|
}
|
||||||
, ":root":
|
, ":root":
|
||||||
nested $ variables
|
nested $ variables
|
||||||
@ -35,7 +55,10 @@ global =
|
|||||||
, color: str colour.text
|
, color: str colour.text
|
||||||
, margin: str "0"
|
, margin: str "0"
|
||||||
}
|
}
|
||||||
<> colourTheme defaultColours
|
<> case maybeMode of
|
||||||
|
Nothing -> autoSwitchColourTheme
|
||||||
|
Just DarkMode -> darkModeStyle
|
||||||
|
Just LightMode -> lightModeStyle
|
||||||
, "pre,code":
|
, "pre,code":
|
||||||
nest
|
nest
|
||||||
{ fontFamily: str "var(--monoFont)"
|
{ fontFamily: str "var(--monoFont)"
|
||||||
@ -81,7 +104,10 @@ defaultColours =
|
|||||||
, background80: darken 0.8 lightBg
|
, background80: darken 0.8 lightBg
|
||||||
, background90: darken 0.9 lightBg
|
, background90: darken 0.9 lightBg
|
||||||
, background100: darken 1.0 lightBg
|
, background100: darken 1.0 lightBg
|
||||||
|
, success
|
||||||
|
, successText
|
||||||
, interfaceBackground: lightBg
|
, interfaceBackground: lightBg
|
||||||
|
, interfaceTextDisabled: darken 0.33 lightBg
|
||||||
, interfaceBackgroundHighlight: darken 0.07 lightBg
|
, interfaceBackgroundHighlight: darken 0.07 lightBg
|
||||||
, interfaceBackgroundShadow: darken 0.1 lightBg
|
, interfaceBackgroundShadow: darken 0.1 lightBg
|
||||||
, inputBackground: darken 0.03 lightBg
|
, inputBackground: darken 0.03 lightBg
|
||||||
@ -108,10 +134,13 @@ defaultColours =
|
|||||||
, background90: lighten 0.9 darkBg
|
, background90: lighten 0.9 darkBg
|
||||||
, background100: lighten 1.0 darkBg
|
, background100: lighten 1.0 darkBg
|
||||||
, interfaceBackground: lighten 0.4 darkBg
|
, interfaceBackground: lighten 0.4 darkBg
|
||||||
|
, interfaceTextDisabled: lighten 0.8 darkBg
|
||||||
, interfaceBackgroundHighlight: lighten 0.5 darkBg
|
, interfaceBackgroundHighlight: lighten 0.5 darkBg
|
||||||
, interfaceBackgroundShadow: lighten 0.4 darkBg
|
, interfaceBackgroundShadow: lighten 0.4 darkBg
|
||||||
, inputBackground: lighten 0.10 darkBg
|
, inputBackground: lighten 0.10 darkBg
|
||||||
, inputBorder: lighten 0.17 darkBg
|
, inputBorder: lighten 0.17 darkBg
|
||||||
|
, success
|
||||||
|
, successText
|
||||||
, highlight
|
, highlight
|
||||||
, text: lightBg
|
, text: lightBg
|
||||||
}
|
}
|
||||||
@ -119,6 +148,10 @@ defaultColours =
|
|||||||
where
|
where
|
||||||
highlight = Color.rgb 0x00 0x99 0xFF
|
highlight = Color.rgb 0x00 0x99 0xFF
|
||||||
|
|
||||||
|
success = Color.rgb 20 200 60
|
||||||
|
|
||||||
|
successText = Color.rgb 250 250 250
|
||||||
|
|
||||||
-- highlight = Color.rgb 0x10 0x45 0x4A
|
-- highlight = Color.rgb 0x10 0x45 0x4A
|
||||||
darkBg = Color.rgb 0 0 0
|
darkBg = Color.rgb 0 0 0
|
||||||
|
|
||||||
@ -143,11 +176,14 @@ type FlatTheme a =
|
|||||||
, background90 ∷ a
|
, background90 ∷ a
|
||||||
, background100 ∷ a
|
, background100 ∷ a
|
||||||
, interfaceBackground ∷ a
|
, interfaceBackground ∷ a
|
||||||
|
, interfaceTextDisabled ∷ a
|
||||||
, interfaceBackgroundHighlight ∷ a
|
, interfaceBackgroundHighlight ∷ a
|
||||||
, interfaceBackgroundShadow ∷ a
|
, interfaceBackgroundShadow ∷ a
|
||||||
, inputBackground ∷ a
|
, inputBackground ∷ a
|
||||||
, inputBorder ∷ a
|
, inputBorder ∷ a
|
||||||
, highlight ∷ a
|
, highlight ∷ a
|
||||||
|
, success ∷ a
|
||||||
|
, successText ∷ a
|
||||||
, text ∷ a
|
, text ∷ a
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,8 +208,8 @@ colour =
|
|||||||
hmap (\x -> "var(" <> x <> ")")
|
hmap (\x -> "var(" <> x <> ")")
|
||||||
$ makeCSSVarLabels defaultColours.light
|
$ makeCSSVarLabels defaultColours.light
|
||||||
|
|
||||||
colourTheme ∷ Colours -> Style
|
autoSwitchColourTheme ∷ Style
|
||||||
colourTheme { dark, light } = lightT
|
autoSwitchColourTheme = lightT
|
||||||
where
|
where
|
||||||
darkT ∷ Style
|
darkT ∷ Style
|
||||||
darkT = unsafeCoerce darkObj
|
darkT = unsafeCoerce darkObj
|
||||||
@ -182,18 +218,30 @@ colourTheme { dark, light } = lightT
|
|||||||
darkObj =
|
darkObj =
|
||||||
Object.fromHomogeneous defaultColours.dark
|
Object.fromHomogeneous defaultColours.dark
|
||||||
# Object.foldMap \k v ->
|
# Object.foldMap \k v ->
|
||||||
Object.singleton ("--" <> k) (str (Color.toHexString v))
|
Object.singleton ("--" <> k) (str (Color.cssStringRGBA v))
|
||||||
|
|
||||||
lightObj ∷ Object StyleProperty
|
lightObj ∷ Object StyleProperty
|
||||||
lightObj =
|
lightObj =
|
||||||
Object.fromHomogeneous defaultColours.light
|
Object.fromHomogeneous defaultColours.light
|
||||||
# Object.foldMap \k v ->
|
# Object.foldMap \k v ->
|
||||||
Object.singleton ("--" <> k) (str (Color.toHexString v))
|
Object.singleton ("--" <> k) (str (Color.cssStringRGBA v))
|
||||||
# Object.insert "@media (prefers-color-scheme: dark)" (nested darkT)
|
# Object.insert "@media (prefers-color-scheme: dark)" (nested darkT)
|
||||||
|
|
||||||
lightT ∷ Style
|
lightT ∷ Style
|
||||||
lightT = unsafeCoerce lightObj
|
lightT = unsafeCoerce lightObj
|
||||||
|
|
||||||
|
lightModeVariables ∷ Object StyleProperty
|
||||||
|
lightModeVariables =
|
||||||
|
Object.fromHomogeneous defaultColours.light
|
||||||
|
# Object.foldMap \k v ->
|
||||||
|
Object.singleton ("--" <> k) (str (Color.cssStringRGBA v))
|
||||||
|
|
||||||
|
darkModeVariables ∷ Object StyleProperty
|
||||||
|
darkModeVariables =
|
||||||
|
Object.fromHomogeneous defaultColours.dark
|
||||||
|
# Object.foldMap \k v ->
|
||||||
|
Object.singleton ("--" <> k) (str (Color.cssStringRGBA v))
|
||||||
|
|
||||||
variables ∷ Style
|
variables ∷ Style
|
||||||
variables =
|
variables =
|
||||||
css
|
css
|
||||||
|
@ -1,22 +1,43 @@
|
|||||||
module Yoga.Block.Container.View (component, Props) where
|
module Yoga.Block.Container.View
|
||||||
|
( component, Props, PropsF
|
||||||
|
) where
|
||||||
|
|
||||||
import Yoga.Prelude.View
|
import Yoga.Prelude.View
|
||||||
import Data.Array as Array
|
import Data.Array as Array
|
||||||
import Effect.Unsafe (unsafePerformEffect)
|
import Effect.Unsafe (unsafePerformEffect)
|
||||||
import React.Basic.DOM as R
|
import React.Basic.DOM as R
|
||||||
import React.Basic.Emotion as E
|
import React.Basic.Emotion as E
|
||||||
import React.Basic.Hooks (reactComponentWithChildren)
|
import React.Basic.Hooks (reactComponent)
|
||||||
|
import Unsafe.Coerce (unsafeCoerce)
|
||||||
|
import Yoga.Block.Container.Style (DarkOrLightMode)
|
||||||
import Yoga.Block.Container.Style as Styles
|
import Yoga.Block.Container.Style as Styles
|
||||||
|
|
||||||
type Props =
|
type PropsF f =
|
||||||
{ children ∷ ReactChildren JSX }
|
( content ∷ JSX
|
||||||
|
, themeVariant ∷ f DarkOrLightMode
|
||||||
|
)
|
||||||
|
|
||||||
component ∷ ReactComponent Props
|
type Props =
|
||||||
component =
|
( | PropsF Id )
|
||||||
unsafePerformEffect
|
|
||||||
$ reactComponentWithChildren "Container" \({ children } ∷ Props) -> React.do
|
component ∷ ∀ p q. Union p q Props => ReactComponent { | p }
|
||||||
|
component = rawComponent
|
||||||
|
|
||||||
|
rawComponent ∷ ∀ p. ReactComponent { | p }
|
||||||
|
rawComponent =
|
||||||
|
unsafeCoerce
|
||||||
|
$ unsafePerformEffect
|
||||||
|
$ reactComponent "Container" \({ content, themeVariant } ∷ { | PropsF OptionalProp }) -> React.do
|
||||||
pure
|
pure
|
||||||
$ R.div_
|
$ R.div_
|
||||||
$ Array.cons
|
$ Array.cons
|
||||||
(element E.global { styles: Styles.global })
|
( element E.global
|
||||||
(reactChildrenToArray children)
|
{ styles:
|
||||||
|
case opToMaybe themeVariant of
|
||||||
|
Nothing -> Styles.global
|
||||||
|
Just Styles.DarkMode -> Styles.darkMode
|
||||||
|
Just Styles.LightMode -> Styles.lightMode
|
||||||
|
}
|
||||||
|
)
|
||||||
|
[ content
|
||||||
|
]
|
||||||
|
@ -1,17 +1,27 @@
|
|||||||
module Yoga.Prelude.Default
|
module Yoga.Prelude.Default
|
||||||
( module Prelude
|
( module Prelude
|
||||||
|
, module Control.Alt
|
||||||
|
, module Control.Monad.Maybe.Trans
|
||||||
|
, module Control.Monad.Trans.Class
|
||||||
, module Data.Maybe
|
, module Data.Maybe
|
||||||
, module Data.Either
|
, module Data.Either
|
||||||
, module Effect
|
, module Effect
|
||||||
, module Effect.Class
|
, module Effect.Class
|
||||||
, module Data.Monoid
|
, module Data.Monoid
|
||||||
, module Data.Foldable
|
, module Data.Foldable
|
||||||
|
, module Data.FoldableWithIndex
|
||||||
|
, module Data.FunctorWithIndex
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
|
import Control.Alt ((<|>))
|
||||||
|
import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT)
|
||||||
|
import Control.Monad.Trans.Class (lift)
|
||||||
import Data.Either (Either(..), note, hush)
|
import Data.Either (Either(..), note, hush)
|
||||||
import Data.Foldable (foldMap, for_, intercalate, traverse_)
|
import Data.Foldable (foldMap, for_, intercalate, traverse_)
|
||||||
import Data.Maybe (Maybe(..), fromMaybe)
|
import Data.FoldableWithIndex (foldMapWithIndex)
|
||||||
|
import Data.FunctorWithIndex (mapWithIndex)
|
||||||
|
import Data.Maybe (Maybe(..), fromMaybe, fromMaybe', isJust, maybe)
|
||||||
import Data.Monoid (guard)
|
import Data.Monoid (guard)
|
||||||
import Effect (Effect)
|
import Effect (Effect)
|
||||||
import Effect.Class (liftEffect)
|
import Effect.Class (liftEffect)
|
||||||
|
@ -9,21 +9,34 @@ module Yoga.Prelude.View
|
|||||||
, module React.Basic.DOM.Events
|
, module React.Basic.DOM.Events
|
||||||
, module Type.Row
|
, module Type.Row
|
||||||
, module Web.DOM
|
, module Web.DOM
|
||||||
|
, module Web.HTML.HTMLElement
|
||||||
, module Data.Nullable
|
, module Data.Nullable
|
||||||
|
, module Untagged.Castable
|
||||||
, NodeRef
|
, NodeRef
|
||||||
|
, getBoundingBoxFromRef
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yoga.Prelude.Default
|
import Yoga.Prelude.Default
|
||||||
import Data.Nullable (Nullable, notNull, null)
|
import Data.Nullable (Nullable, notNull, null)
|
||||||
import Prim.Row (class Union)
|
import Prim.Row (class Union)
|
||||||
import React.Basic.DOM (Props_div)
|
import React.Basic.DOM (Props_div)
|
||||||
import React.Basic.DOM.Events (preventDefault, targetValue)
|
import React.Basic.DOM.Events (stopPropagation, preventDefault, targetValue)
|
||||||
import React.Basic.Events (class Merge, EventFn, EventHandler, SyntheticEvent, handler, handler_, merge, mergeImpl, syntheticEvent, unsafeEventFn)
|
import React.Basic.Events (class Merge, EventFn, EventHandler, SyntheticEvent, handler, handler_, merge, mergeImpl, syntheticEvent, unsafeEventFn)
|
||||||
import React.Basic.Hooks (type (/\), Component, Hook, JSX, Pure, ReactChildren, ReactComponent, ReactContext, Ref, Render, UnsafeReference(..), UseContext, UseDebugValue, UseEffect, UseLayoutEffect, UseMemo, UseReducer, UseRef, UseState, coerceHook, consumer, contextConsumer, contextProvider, createContext, displayName, element, elementKeyed, empty, fragment, keyed, memo, provider, reactChildrenFromArray, reactChildrenToArray, reactComponentFromHook, readRef, readRefMaybe, unsafeHook, unsafeRenderEffect, useContext, useDebugValue, useEffect, useEffectAlways, useEffectOnce, useLayoutEffect, useLayoutEffectAlways, useLayoutEffectOnce, useMemo, useReducer, useRef, useState, useState', writeRef, (/\))
|
import React.Basic.Hooks (type (/\), Component, Hook, JSX, Pure, ReactChildren, ReactComponent, ReactContext, Ref, Render, UnsafeReference(..), UseContext, UseDebugValue, UseEffect, UseLayoutEffect, UseMemo, UseReducer, UseRef, UseState, coerceHook, consumer, contextConsumer, contextProvider, createContext, displayName, element, elementKeyed, empty, fragment, keyed, memo, provider, reactChildrenFromArray, reactChildrenToArray, reactComponentFromHook, readRef, readRefMaybe, unsafeHook, unsafeRenderEffect, useContext, useDebugValue, useEffect, useEffectAlways, useEffectOnce, useLayoutEffect, useLayoutEffectAlways, useLayoutEffectOnce, useMemo, useReducer, useRef, useState, useState', writeRef, (/\))
|
||||||
import Type.Row (type (+))
|
import Type.Row (type (+))
|
||||||
|
import Untagged.Castable (cast)
|
||||||
import Web.DOM (Node)
|
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 (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, (<>?), (?||))
|
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 =
|
type NodeRef =
|
||||||
Ref (Nullable Node)
|
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
|
||||||
|
@ -1160,6 +1160,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.5.4.tgz#de25b5da9f727985a3757fd59b5d028aba75841a"
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.5.4.tgz#de25b5da9f727985a3757fd59b5d028aba75841a"
|
||||||
integrity sha512-ZpKr+WTb8zsajqgDkvCEWgp6d5eJT6Q63Ng2neTbzBO76Lbe91vX/iVIW9dikq+Fs3yEo+ls4cxeXABD2LtcbQ==
|
integrity sha512-ZpKr+WTb8zsajqgDkvCEWgp6d5eJT6Q63Ng2neTbzBO76Lbe91vX/iVIW9dikq+Fs3yEo+ls4cxeXABD2LtcbQ==
|
||||||
|
|
||||||
|
"@popperjs/core@^2.6.0":
|
||||||
|
version "2.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.6.0.tgz#f022195afdfc942e088ee2101285a1d31c7d727f"
|
||||||
|
integrity sha512-cPqjjzuFWNK3BSKLm0abspP0sp/IGOli4p5I5fKFAzdS8fvjdOwDCfZqAaIiXd9lPkOWi3SUUfZof3hEb7J/uw==
|
||||||
|
|
||||||
"@reach/router@^1.3.3":
|
"@reach/router@^1.3.3":
|
||||||
version "1.3.4"
|
version "1.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c"
|
resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c"
|
||||||
|
Loading…
Reference in New Issue
Block a user