This commit is contained in:
Mark Eibes 2022-11-20 17:45:10 +01:00
parent ca8b7f10ff
commit c0c9705851
49 changed files with 11571 additions and 11121 deletions

View File

@ -1,7 +1,7 @@
import { indexer } from './purescript-indexer'
export default {
staticDirs: ['../public'],
staticDirs: ['../assets'],
addons: ["@storybook/addon-essentials", "@storybook/addon-interactions"],
stories: ["../output/Story.*/index.js"],
framework: "@storybook/react-webpack5",

View File

@ -1,5 +1,4 @@
<link href="https://rsms.me/inter/inter.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/victormono@latest/dist/index.min.css">
<style>
#storybook-root {
height: 100%;

11
.tidyrc.json Normal file
View File

@ -0,0 +1,11 @@
{
"importSort": "source",
"importWrap": "source",
"indent": 2,
"operatorsFile": null,
"ribbon": 1,
"typeArrowPlacement": "last",
"unicode": "always",
"width": 80
}

BIN
assets/mat.webm Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 KiB

BIN
assets/unsplash-mat-320.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
assets/unsplash-mat-640.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -8,7 +8,6 @@
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.18.0",
"storybook": "^7.0.0-alpha.48",
"@storybook/addon-essentials": "^7.0.0-alpha.48",
"@storybook/addon-interactions": "^7.0.0-alpha.48",
"@storybook/react-webpack5": "^7.0.0-alpha.48",
@ -23,8 +22,7 @@
"jsdom-global": "^3.0.2",
"react-refresh": "^0.13.0",
"rimraf": "^3.0.2",
"source-map": "GerHobbelt/source-map#patch-8",
"source-map-loader": "^3.0.1",
"storybook": "^7.0.0-alpha.48",
"svg2psreact": "^2.1.0",
"webpack": "^5.72.1",
"webpack-cli": "^4.9.2",
@ -54,12 +52,15 @@
"@dnd-kit/utilities": "^3.2.0",
"@emotion/react": "^11.9.0",
"@popperjs/core": "^2.11.5",
"@react-aria/interactions": "^3.13.0",
"@react-aria/utils": "^3.14.1",
"downshift": "^6.1.7",
"framer-motion": "^6.3.3",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-intersection-observer": "^9.1.0",
"react-popper": "^2.3.0",
"react-syntax-highlighter": "^15.5.0"
"react-syntax-highlighter": "^15.5.0",
"react-virtuoso": "^3.1.4"
}
}

View File

@ -1,113 +0,0 @@
module Plumage.Hooks.UseResize2 where
import Prelude
import Data.Foldable (for_)
import Data.Int (toNumber)
import Data.Maybe (Maybe(..))
import Data.Newtype (class Newtype)
import Data.Ord (abs)
import Data.Time.Duration (class Duration)
import Data.Time.Duration as Milliseconds
import Effect (Effect)
import Effect.Aff (Aff, Fiber, delay, error, killFiber, launchAff, launchAff_)
import Effect.Class (liftEffect)
import React.Basic.Hooks (Hook, UseEffect, UseLayoutEffect, UseRef, UseState, coerceHook, useEffectOnce, useLayoutEffect, (/\))
import React.Basic.Hooks as React
import Web.Event.Event (EventType(..))
import Web.Event.EventTarget (EventListener, EventTarget, addEventListener, eventListener, removeEventListener)
import Web.HTML as HTML
import Web.HTML.Window as Window
eventType ∷ EventType
eventType = EventType "resize"
registerListener ∷ EventListener → Effect (Effect Unit)
registerListener listener = do
target ← HTML.window <#> Window.toEventTarget
addEventListener eventType listener false target
pure $ removeEventListener eventType listener false target
type Sizes =
{ innerWidth ∷ Number, innerHeight ∷ Number }
newtype UseResize hooks = UseResize
(UseLayoutEffect Unit (UseState Sizes hooks))
derive instance ntUseResize ∷ Newtype (UseResize hooks) _
useResize ∷ Hook UseResize Sizes
useResize =
coerceHook React.do
size /\ setSize ← React.useState' zero
useLayoutEffect unit do
setSizeFromWindow setSize
listener ← makeListener setSize
registerListener listener
pure size
setSizeFromWindow ∷ (Sizes → Effect Unit) → Effect Unit
setSizeFromWindow setSize = do
window ← HTML.window
innerWidth ← Window.innerWidth window <#> toNumber
innerHeight ← Window.innerHeight window <#> toNumber
setSize { innerWidth, innerHeight }
makeListener ∷ (Sizes → Effect Unit) → Effect EventListener
makeListener setSize = do
eventListener
$ const (setSizeFromWindow setSize)
newtype UseOnResize hooks = UseOnResize
( UseEffect Unit
( UseRef Sizes
(UseRef (Maybe (Fiber Unit)) hooks)
)
)
derive instance ntUseOnResize ∷ Newtype (UseOnResize hooks) _
useOnResize ∷
∀ d.
Duration d ⇒
d →
( { innerWidth ∷ Number
, innerHeight ∷ Number
, deltaWidth ∷ Number
, deltaHeight ∷ Number
} →
Effect Unit
) →
Hook UseOnResize Unit
useOnResize debounceBy callback =
coerceHook React.do
fiberRef ← React.useRef Nothing
sizeRef ← React.useRef (zero ∷ Sizes)
let
layoutEffect ∷ Effect (Effect Unit)
layoutEffect = do
setSizeFromWindow (React.writeRef sizeRef ∷ Sizes → Effect Unit)
listener ∷ EventListener ←
makeListener \(dimensions ∷ Sizes) → do
let
aff ∷ Aff Unit
aff = do
fiberʔ ← React.readRef fiberRef # liftEffect
for_ fiberʔ (killFiber (error "Fiber cancelled"))
delay (Milliseconds.fromDuration debounceBy)
let { innerWidth, innerHeight } = dimensions
size ← React.readRef sizeRef # liftEffect
let deltaWidth = abs (size.innerWidth - innerWidth)
let deltaHeight = abs (size.innerHeight - innerWidth)
React.writeRef sizeRef dimensions # liftEffect
callback { innerWidth, innerHeight, deltaWidth, deltaHeight } #
liftEffect
pure unit
fiber ∷ Fiber _ ← launchAff aff
React.writeRef fiberRef (Just fiber)
target ∷ EventTarget ← HTML.window <#> Window.toEventTarget
addEventListener eventType listener false target
pure $ launchAff_ do
fiberʔ ← React.readRef fiberRef # liftEffect
for_ fiberʔ (killFiber (error "Fiber cancelled"))
useEffectOnce layoutEffect

View File

@ -1,33 +1,20 @@
"use strict"
export function resizeObserver(cb) {
return function () {
return new ResizeObserver(function (entries, observer) {
return cb(entries)(observer)()
})
}
}
export function _observe(element) {
return function (config) {
return function (observer) {
return function () {
return observer.observe(element, config)
}
export const resizeObserver = cb => () => {
return new ResizeObserver(
(entries, observer) => {
cb(entries)(observer)()
}
}
)
}
export function unobserve(observer) {
return function (element) {
return function () {
return observer.unobserve(element)
}
}
}
export const _observe =
config => observer => element => () =>
observer.observe(element, config)
export const unobserve =
observer => element => () =>
observer.unobserve(element)
export function disconnect(observer) {
return function () {
return observer.disconnect()
}
}
export const disconnect =
observer => () => observer.disconnect()

View File

@ -42,19 +42,26 @@ optionsToFFI ContentBox = "content-box"
optionsToFFI DevicePixelContentBox = "device-pixel-content-box"
foreign import resizeObserver
∷ (Array ResizeObserverEntry -> ResizeObserver -> Effect Unit)
∷ ( Array ResizeObserverEntry
-> ResizeObserver
-> Effect Unit
)
-> Effect ResizeObserver
foreign import _observe ∷ ∀ r. Element -> Record r -> ResizeObserver -> Effect Unit
foreign import _observe ∷ ∀ r. Record r -> ResizeObserver -> Element -> Effect Unit
observe
∷ ResizeObserverBoxOptions
-> ResizeObserver
-> Element
-> Effect Unit
observe options observer element =
_observe element { box: optionsToFFI options } observer
observe options = _observe { box: optionsToFFI options }
foreign import unobserve ∷ ResizeObserver -> Element -> Effect Unit
foreign import unobserve
∷ ResizeObserver
-> Element
-> Effect Unit
foreign import disconnect ∷ ResizeObserver -> Effect Unit
foreign import disconnect
∷ ResizeObserver
-> Effect Unit

View File

@ -11,22 +11,27 @@ import Record as Record
import Type.Proxy (Proxy(..))
import Unsafe.Coerce (unsafeCoerce)
div' ∷ ∀ attrs attrs_. Union attrs attrs_ Props_div ⇒ ReactComponent (Record attrs)
div' ∷
∀ attrs attrs_. Union attrs attrs_ Props_div ⇒ ReactComponent (Record attrs)
div' = R.div'
span' ∷ ∀ attrs attrs_. Union attrs attrs_ Props_span ⇒ ReactComponent (Record attrs)
span' ∷
∀ attrs attrs_. Union attrs attrs_ Props_span ⇒ ReactComponent (Record attrs)
span' = R.span'
button' ∷ ∀ attrs attrs_. Union attrs attrs_ Props_button ⇒ ReactComponent (Record attrs)
button' ∷
∀ attrs attrs_.
Union attrs attrs_ Props_button ⇒
ReactComponent (Record attrs)
button' = R.button'
el
∀ props
. Lacks "children" props
ReactComponent { children ∷ Array JSX | props }
Record props
Array JSX
JSX
el
∀ props.
Lacks "children" props
ReactComponent { children ∷ Array JSX | props }
Record props
Array JSX
JSX
el x props children =
(Hooks.element)
x
@ -36,35 +41,40 @@ infixl 5 el as </
infixr 0 DF.apply as />
leaf
∀ props
. Lacks "children" props
ReactComponent { | props }
Record props
JSX
leaf
∀ props.
Lacks "children" props
ReactComponent { | props }
Record props
JSX
leaf x props = Hooks.element x props
infixl 5 leaf as </>
styled
∀ props
. Lacks "children" props
ReactComponent { children ∷ Array JSX | props }
{ css ∷ Emotion.Style | props }
Array JSX
JSX
styled
∀ props.
Lacks "children" props
ReactComponent { children ∷ Array JSX | props }
{ css ∷ Emotion.Style | props }
Array JSX
JSX
styled x props children =
(unsafeCoerce Emotion.element ∷ ∀ r. ReactComponent { | r } → { css ∷ Emotion.Style | r } → JSX) x
( unsafeCoerce Emotion.element ∷
∀ r. ReactComponent { | r } → { css ∷ Emotion.Style | r } → JSX
) x
(Record.insert (Proxy ∷ Proxy "children") children props)
infixl 5 styled as </*
styledLeaf
∷ ∀ props
. ReactComponent { className ∷ String | props }
→ { className ∷ String, css ∷ Emotion.Style | props }
→ JSX
styledLeaf = Emotion.element
styledLeaf ∷
∀ props.
ReactComponent { | props } →
{ css ∷ Emotion.Style | props } →
JSX
styledLeaf x =
( unsafeCoerce Emotion.element ∷
∀ r. ReactComponent { | r } → { css ∷ Emotion.Style | r } → JSX
) x
infixl 5 styledLeaf as </*>

View File

@ -5,6 +5,7 @@ import React.Basic (JSX, ReactComponent)
import React.Basic as React
import Yoga (el)
import Yoga.Block.Atom.Button as Button
import Yoga.Block.Atom.Checkbox as Checkbox
import Yoga.Block.Atom.Icon as Icon
import Yoga.Block.Atom.Image as Image
import Yoga.Block.Atom.Input as Input
@ -21,13 +22,20 @@ import Yoga.Block.Layout.Imposter as Imposter
import Yoga.Block.Layout.Sidebar as Sidebar
import Yoga.Block.Layout.Stack as Stack
import Yoga.Block.Layout.Switcher as Switcher
import Yoga.Block.Molecule.Modal as Modal
import Yoga.Block.Atom.Modal as Modal
import Yoga.Block.Molecule.ReadMore as ReadMore
import Yoga.Block.Quark.Layer as Layer
box' ∷ ∀ p q. Union p q Box.Props ⇒ ReactComponent { | p }
box' = Box.component
box ∷ ∀ p q. Lacks "children" p ⇒ Union p q Box.PropsNoChildren ⇒ { | p } → Array JSX → JSX
box ∷
∀ p q.
Lacks "children" p ⇒
Union p q Box.PropsNoChildren ⇒
{ | p } →
Array JSX →
JSX
box = el box'
box_ ∷ Array JSX → JSX
@ -36,7 +44,13 @@ box_ = box {}
button' ∷ ∀ p q. Union p q Button.Props ⇒ ReactComponent { | p }
button' = Button.component
button ∷ ∀ p q. Lacks "children" p ⇒ Union p q Button.PropsNoChildren ⇒ { | p } → Array JSX → JSX
button ∷
∀ p q.
Lacks "children" p ⇒
Union p q Button.PropsNoChildren ⇒
{ | p } →
Array JSX →
JSX
button = el button'
button_ ∷ Array JSX → JSX
@ -45,7 +59,13 @@ button_ = button {}
centre' ∷ ∀ p q. Union p q Centre.Props ⇒ ReactComponent { | p }
centre' = Centre.component
centre ∷ ∀ p q. Lacks "children" p ⇒ Union p q Centre.PropsNoChildren ⇒ { | p } → Array JSX → JSX
centre ∷
∀ p q.
Lacks "children" p ⇒
Union p q Centre.PropsNoChildren ⇒
{ | p } →
Array JSX →
JSX
centre = el centre'
centre_ ∷ Array JSX → JSX
@ -54,7 +74,13 @@ centre_ = centre {}
cluster' ∷ ∀ p q. Union p q Cluster.Props ⇒ ReactComponent { | p }
cluster' = Cluster.component
cluster ∷ ∀ p q. Lacks "children" p ⇒ Union p q Cluster.PropsNoChildren ⇒ { | p } → Array JSX → JSX
cluster ∷
∀ p q.
Lacks "children" p ⇒
Union p q Cluster.PropsNoChildren ⇒
{ | p } →
Array JSX →
JSX
cluster = el cluster'
cluster_ ∷ Array JSX → JSX
@ -66,16 +92,34 @@ container = Container.component
cover' ∷ ∀ p q. Union p q Cover.Props ⇒ ReactComponent { | p }
cover' = Cover.component
cover ∷ ∀ p q. Lacks "children" p ⇒ Union p q Cover.PropsNoChildren ⇒ { | p } → Array JSX → JSX
cover ∷
∀ p q.
Lacks "children" p ⇒
Union p q Cover.PropsNoChildren ⇒
{ | p } →
Array JSX →
JSX
cover = el cover'
cover_ ∷ Array JSX → JSX
cover_ = cover {}
grid' ∷ ∀ p q. Union p q Grid.Props ⇒ ReactComponent { children ∷ Array JSX | p }
checkbox' ∷
∀ p q.
Union p q Checkbox.Props ⇒
ReactComponent { | Checkbox.MandatoryProps p }
checkbox' = Checkbox.component
checkbox ∷
∀ p q. Union p q Checkbox.Props ⇒ { | Checkbox.MandatoryProps p } → JSX
checkbox = React.element checkbox'
grid' ∷
∀ p q. Union p q Grid.Props ⇒ ReactComponent { children ∷ Array JSX | p }
grid' = Grid.component
grid ∷ ∀ p q. Lacks "children" p ⇒ Union p q Grid.Props ⇒ { | p } → Array JSX → JSX
grid ∷
∀ p q. Lacks "children" p ⇒ Union p q Grid.Props ⇒ { | p } → Array JSX → JSX
grid = el grid'
grid_ ∷ Array JSX → JSX
@ -87,14 +131,23 @@ icon = Icon.component
input ∷ ∀ p q. Union p q Input.Props ⇒ ReactComponent { | p }
input = Input.component
image :: forall p q. Union p q Image.Props => { | p } -> JSX
image ∷ ∀ p q. Union p q Image.Props ⇒ { | p } → JSX
image = React.element Image.component
imposter ∷ ∀ p q. Union p q Imposter.Props ⇒ ReactComponent { | p }
imposter = Imposter.component
modal ∷ ReactComponent Modal.Props
modal = Modal.component
modal' ∷ ReactComponent Modal.Props
modal' = Modal.component
modal ∷ Modal.Props → JSX
modal = React.element modal'
layer' ∷ ReactComponent { | Layer.Props }
layer' = Layer.component
layer ∷ { | Layer.Props } → JSX
layer = React.element layer'
range ∷ ∀ p q. Union p q Range.Props ⇒ ReactComponent { | p }
range = Range.component
@ -105,19 +158,30 @@ segmented' = Segmented.component
segmented ∷ Segmented.Props → JSX
segmented = React.element Segmented.component
sidebar' ∷ ∀ p q. Union p q Sidebar.Props ⇒ ReactComponent { sidebar ∷ JSX, children ∷ Array JSX | p }
sidebar' ∷
∀ p q.
Union p q Sidebar.Props ⇒
ReactComponent { sidebar ∷ JSX, children ∷ Array JSX | p }
sidebar' = Sidebar.component
sidebar ∷ ∀ p q. Lacks "children" p ⇒ Union p q Sidebar.Props ⇒ { sidebar ∷ JSX | p } → Array JSX → JSX
sidebar ∷
∀ p q.
Lacks "children" p ⇒
Union p q Sidebar.Props ⇒
{ sidebar ∷ JSX | p } →
Array JSX →
JSX
sidebar = el sidebar'
sidebar_ ∷ JSX → Array JSX → JSX
sidebar_ jsx = sidebar { sidebar: jsx }
stack' ∷ ∀ p q. Union p q Stack.Props ⇒ ReactComponent { children ∷ Array JSX | p }
stack' ∷
∀ p q. Union p q Stack.Props ⇒ ReactComponent { children ∷ Array JSX | p }
stack' = Stack.component
stack ∷ ∀ p q. Lacks "children" p ⇒ Union p q Stack.Props ⇒ { | p } → Array JSX → JSX
stack ∷
∀ p q. Lacks "children" p ⇒ Union p q Stack.Props ⇒ { | p } → Array JSX → JSX
stack = el stack'
stack_ ∷ Array JSX → JSX
@ -126,7 +190,13 @@ stack_ = stack {}
switcher' ∷ ∀ p q. Union p q Switcher.Props ⇒ ReactComponent { | p }
switcher' = Switcher.component
switcher ∷ ∀ p q. Lacks "children" p ⇒ Union p q Switcher.PropsNoChildren ⇒ { | p } → Array JSX → JSX
switcher ∷
∀ p q.
Lacks "children" p ⇒
Union p q Switcher.PropsNoChildren ⇒
{ | p } →
Array JSX →
JSX
switcher = el switcher'
switcher_ ∷ Array JSX → JSX
@ -137,3 +207,4 @@ toggle = Toggle.component
readMore ∷ ∀ p q. Union p q ReadMore.Props ⇒ ReactComponent (Record p)
readMore = ReadMore.component

View File

@ -42,6 +42,13 @@ backgroundAnimation =
varOr :: String -> StyleProperty -> StyleProperty
varOr varName alternative = str $ "var(" <> varName <> ", " <> unsafeCoerce alternative <> ")"
style
:: { background :: String
, borderCol :: String
, hoverBackgroundCol :: String
, textCol :: String
, width :: String
}
style =
{ background: prefix <> "background"
, textCol: prefix <> "color"
@ -54,8 +61,9 @@ style =
button ∷ Style
button =
inlineFlex <>
css
inlineFlex
<> cursorPointer
<> css
{ background: varOr style.background (str colour.backgroundLayer5)
, width: varOr style.width auto
, borderWidth: int 0
@ -114,13 +122,9 @@ button =
, borderColor: str "transparent"
, color: varOr style.textCol (str colour.highlightText)
, "&:focus-visible":
nest
{ borderColor: col.background
}
nest { borderColor: col.background }
, "&:active":
nest
{ boxShadow: str "inset 0 1px 6px rgba(0,0,0,0.40)"
}
nest { boxShadow: str "inset 0 1px 6px rgba(0,0,0,0.40)" }
}
, """&[data-button-type="dangerous"]""":
nest

View File

@ -0,0 +1,5 @@
module Yoga.Block.Atom.Checkbox
( module Yoga.Block.Atom.Checkbox.View
) where
import Yoga.Block.Atom.Checkbox.View (component, Props, MandatoryProps)

View File

@ -1,31 +0,0 @@
module Yoga.Block.Atom.Checkbox.Story (default, checkbox) where
import Prelude hiding (div)
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import React.Basic (JSX, element)
import React.Basic.DOM as R
import React.Basic.Emotion as E
import Yoga ((</>))
import Yoga.Block.Atom.Checkbox.View as Checkbox
import Yoga.Block.Container.Style as Styles
default
∷ { decorators ∷ Array (Effect JSX -> JSX)
, title ∷ String
}
default =
{ title: "Atom/Checkbox"
, decorators:
[ \storyFn ->
R.div_
[ element E.global { styles: Styles.global }
, unsafePerformEffect storyFn
]
]
}
checkbox ∷ Effect JSX
checkbox = do
pure $ Checkbox.component </> {}

View File

@ -5,20 +5,114 @@ import Yoga.Prelude.Style
type Props :: forall k. (Type -> k) -> Row k -> Row k
type Props f r =
( css ∷ f Style
, size ∷ f Number
, stroke :: f String
, strokeWidth :: f Number
| r
)
checkmark ∷ Style
checkmark =
block <>
css
block
<> widthFull
<> heightFull
<> css
{ path: nested $ css
{ transformOrigin: str "50% 50%"
, strokeDasharray: int 48
, strokeDashoffset: int 48
, strokeWidth: int 4
, fill: none
, stroke: str "currentColor"
}
}
checkmarkChecked =
block
<> widthFull
<> heightFull
<> css
{ path: nested $ css
{ transformOrigin: str "50% 50%"
, strokeDasharray: int 48
, strokeDashoffset: int 48
, fill: none
, stroke: str "currentColor"
, strokeWidth: int 4
, animation: str
"checkmarkAnimation .25s cubic-bezier(0.65, 0, 0.45, 1) forwards"
"checkmarkAnimation .25s cubic-bezier(0.35, 0, 0.45, 1) forwards"
, animationName: keyframes
{ to: css { strokeDashoffset: int 0 } }
, animationDelay: str "125ms"
}
}
}
checkmarkContainer :: Style
checkmarkContainer =
background' col.highlight
<> textCol' col.highlightText
<> positionAbsolute
<> boxSizingContentBox
<> boxSizingBorderBox
<> top (-checkboxBorder)
<> left (-checkboxBorder)
<> width' sizeStyle.l
<> height' sizeStyle.l
<> transition "clip-path 0.3s ease"
<> roundness
<> ignoreClicks
checkmarkContainerChecked :: Style
checkmarkContainerChecked =
css { clipPath: str "circle(100%)" }
checkmarkContainerNotChecked :: Style
checkmarkContainerNotChecked =
css { clipPath: str "circle(0%)" }
checkboxBorder = 2
roundness =
roundedDefault
container :: Style
container =
width' sizeStyle.l
<> height' sizeStyle.l
<> positionRelative
<> background' (str colour.inputBackground)
<> boxSizingBorderBox
<> overflowVisible
<> border checkboxBorder
<> borderSolid
<> borderCol' (str colour.inputBorder)
<> roundness
<> shadowSm
<> overflowVisible
<> focusWithin
( afterElement
( content "''"
<> roundness
<> css
{ boxShadow:
str $ "0px 1px 12px 7px " <> colourWithDarkLightAlpha.highlight { darkAlpha: 0.8, lightAlpha: 0.4 }
}
<> border 1
<> borderCol' (str $ colourWithAlpha.highlight 0.1)
<> background' (str $ colourWithAlpha.highlight 0.1)
<> ignoreClicks
<> positionAbsolute
<> widthFull
<> heightFull
<> top 0
<> left 0
<> overflowVisible
)
)
checkbox :: Style
checkbox = (css { appearance: none })
<> widthFull
<> heightFull
<> focus (outlineNone)

View File

@ -2,43 +2,65 @@ module Yoga.Block.Atom.Checkbox.View where
import Yoga.Prelude.View
import React.Basic.DOM as R
import React.Basic.DOM.Events (targetChecked)
import React.Basic.DOM.SVG as SVG
import React.Basic.Hooks as React
import Yoga.Block.Atom.Checkbox.Style as Style
type Props = PropsF Id
type MandatoryProps :: forall k. k -> k
type MandatoryProps r = (| r)
type MandatoryProps r =
( onChecked ∷ (Boolean → Effect Unit)
, id ∷ String
| r
)
type PropsF :: forall k. (Type -> k) -> Row k
type PropsF f =
( ticked ∷ f Boolean
, size ∷ f Number
, stroke :: f String
, strokeWidth :: f Number
( checked ∷ f Boolean
| Style.Props f (MandatoryProps ())
)
component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | MandatoryProps p }
component ∷ ∀ p p_. Union p p_ Props ReactComponent { | MandatoryProps p }
component = rawComponent
rawComponent :: forall p. ReactComponent { | p }
rawComponent = mkForwardRefComponent "Checkbox" \(props :: { | PropsF OptionalProp }) ref -> React.do
pure $ SVG.svg'
</*
{ ref
, className: "ry-checkmark"
, css: Style.checkmark
, width: show (props.size ?|| 24.0)
, xmlns: "http://www.w3.org/2000/svg"
, viewBox: "0 0 52 52"
}
/>
[ SVG.path' </>
{ stroke: props.stroke ?|| "currentColor"
, strokeWidth: show (props.strokeWidth ?|| 3.0)
, fill: "none"
, d: "M14.1 27.2l7.1 7.2 16.7-16.8"
rawComponent ∷ ∀ p. ReactComponent { | p }
rawComponent = mkForwardRefComponent "Checkbox"
\(props ∷ { | PropsF OptionalProp }) ref → React.do
checkedBackup /\ setChecked ← React.useState' false
let checked = props.checked ?|| checkedBackup
pure $ div "ry-checkbox-container" Style.container
[ R.input' </*>
{ className: "ry-checkbox"
, id: props.id
, css: Style.checkbox
, type: "checkbox"
, checked
, onChange: handler targetChecked
( traverse_ \isChecked → do
case props.checked # opToMaybe of
Nothing → setChecked isChecked
Just _ → mempty
props.onChecked isChecked
)
}
]
, div "ry-checkbox-checkmark-container"
( Style.checkmarkContainer <>
if checked then Style.checkmarkContainerChecked
else Style.checkmarkContainerNotChecked
)
[ SVG.svg'
</*
{ className: "ry-checkmark"
, css: Style.checkmark <>
if checked then
Style.checkmarkChecked
else Style.checkmark
, xmlns: "http://www.w3.org/2000/svg"
, viewBox: "0 0 52 52"
}
/>
[ SVG.path' </> { d: "M14.1 27.2l7.1 7.2 16.7-16.8" }
]
]
]

View File

@ -1,50 +0,0 @@
module Yoga.Block.Atom.Icon.Story where
import Prelude
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import React.Basic (JSX, element, fragment)
import React.Basic.DOM as R
import React.Basic.Emotion as E
import Yoga.Block.Atom.Icon as Icon
import Yoga.Block.Container.Style as Styles
import Yoga.Block.Icon.SVG as SVGIcon
default ∷
{ decorators ∷ Array (Effect JSX -> JSX)
, title ∷ String
}
default =
{ title: "Atom/Icon"
, decorators:
[ \storyFn ->
R.div_
[ element E.global { styles: Styles.global }
, unsafePerformEffect storyFn
]
]
}
icon ∷ Effect JSX
icon = do
pure
$ fragment
[ R.div_
[ R.h2_ [ R.text "Icon" ]
, element Icon.component { icon: SVGIcon.on, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.off, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.sun, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.moon, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.eyeClosed, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.eyeOpen, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.bin, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.cross, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.folder, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.key, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.magnifyingGlass, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.simpleKey, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.questionMark, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.plus, size: E.str "var(--s2)" }
, element Icon.component { icon: SVGIcon.warn, size: E.str "var(--s2)" }
]
]

View File

@ -8,36 +8,36 @@ import Yoga.Block.Atom.Image.FFI (setFallbackImgSrc)
import Yoga.Block.Atom.Image.Style as Style
import Yoga.Block.Atom.Image.Types (CrossOrigin, Decoding, Loading, ReferrerPolicy)
type PropsF :: forall k. (Type -> k) -> Row k -> Row k
type PropsF ∷ ∀ k. (Type → k) → Row k → Row k
type PropsF f r =
( alt :: f (Maybe String)
, decoding :: f Decoding
, loading :: f Loading
, onLoad :: f EventHandler
, crossOrigin :: f CrossOrigin
, referrerPolicy :: f ReferrerPolicy
, width :: f Int
, height :: f Int
, fallbackSrc :: f String
, sizes :: f (Array String)
, srcset :: f (Array String)
( alt ∷ f String
, decoding f Decoding
, loading f Loading
, onLoad f EventHandler
, crossOrigin f CrossOrigin
, referrerPolicy f ReferrerPolicy
, width f Int
, height f Int
, fallbackSrc f String
, sizes f (Array String)
, srcSet ∷ f (Array String)
| Style.Props f r
)
type PropsNoChildren = PropsF Id ()
type Props =
PropsF Id (src :: String)
PropsF Id (src String)
type PropsOptional =
PropsF OptionalProp ()
component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | p }
component ∷ ∀ p p_. Union p p_ Props ReactComponent { | p }
component = rawComponent
rawComponent ∷ ∀ p. ReactComponent { | p }
rawComponent = mkForwardRefComponent "Image" do
\(props :: { | PropsF OptionalProp Props }) ref -> pure
\(props ∷ { | PropsF OptionalProp Props }) ref → pure
$ img
</*>
( { className: "ry-img"
@ -48,12 +48,12 @@ rawComponent = mkForwardRefComponent "Image" do
, crossOrigin: props.crossOrigin
, width: props.width <#> show
, height: props.height <#> show
, alt: props.alt <#> fromMaybe ""
, onError: props.fallbackSrc <#>
\src -> handler target (setFallbackImgSrc src)
, alt: props.alt
, onError: props.fallbackSrc <#> \src → handler target
(setFallbackImgSrc src)
, sizes: props.sizes <#> intercalate ","
, src: props.src
, srcset: props.srcset <#> intercalate ","
, srcSet: props.srcSet <#> intercalate ","
, onLoad: props.onLoad
, ref
} # deleteUndefineds

View File

@ -12,14 +12,14 @@ derive instance Generic SizeVariant _
sizeVariantToFactor ∷ SizeVariant → String
sizeVariantToFactor = case _ of
SizeMedium -> "1"
SizeSmall -> "0.9"
SizeTiny -> "0.8"
SizeMedium "1"
SizeSmall "0.9"
SizeTiny "0.8"
type Props :: forall k. (Type -> k) -> Row k -> Row k
type Props ∷ ∀ k. (Type → k) → Row k → Row k
type Props f r =
( css ∷ f Style
, sizeVariant :: f SizeVariant
, sizeVariant f SizeVariant
, background ∷ f String
, textColour ∷ f String
, placeholderColour ∷ f String
@ -45,8 +45,10 @@ labelAndInputWrapper =
{ position: relative
-- , display: inlineBlock
, "--left-icon-size": str "calc(var(--s0) * var(--input-size-factor))"
, "--right-icon-size": str "calc(var(--s0) * var(--input-size-factor)) * 1.2)"
, "--input-border-radius": str "calc(var(--s-1) * var(--input-size-factor) * var(--input-size-factor))"
, "--right-icon-size": str
"calc(var(--s0) * var(--input-size-factor)) * 1.2)"
, "--input-border-radius": str
"calc(var(--s-1) * var(--input-size-factor) * var(--input-size-factor))"
, "--input-side-padding": var "--s-1"
, width: inherit
, margin: _0
@ -62,7 +64,7 @@ labelContainer =
<> left 0
<> top 0
labelSmall ∷ String -> String -> Style
labelSmall ∷ String → String → Style
labelSmall background textColour =
css
{ fontSize: var "--s-1"
@ -77,7 +79,8 @@ labelSmall background textColour =
nest
{ fontWeight: str "500"
, whiteSpace: str "nowrap"
, background: str $ i "linear-gradient(" background " 61%, transparent 61%, transparent)"
, background: str $ i "linear-gradient(" background
" 61%, transparent 61%, transparent)"
, color: str textColour
, borderRadius: var "--s-4"
, paddingLeft: var "--s-4"
@ -110,17 +113,17 @@ labelSmallFocusBackground =
colour.highlightRotatedForwards
")"
labelLarge ∷ { left ∷ Number, width ∷ Number } -> Style
labelLarge ∷ { left ∷ Number, width ∷ Number } Style
labelLarge { left, width } =
overflowXHidden <>
css
{ fontSize: str "calc(var(--input-size-factor) * 15px)"
, padding: _0
, whiteSpace: nowrap -- force on one line
-- , height: str "calc(var(--s0) * 1.2)"
, height: str "calc(var(--s0) * 1.2)"
, letterSpacing: em (-0.011)
, maxWidth: str $ i "calc(" width "px - 2ch)"
, marginTop: str "calc(7px * var(--input-size-factor))"
, marginTop: str "calc(10px * var(--input-size-factor))"
, marginLeft: str $ i left "px"
, marginRight: var "--input-side-padding"
, color: str colour.textPaler3
@ -135,7 +138,8 @@ labelLarge { left, width } =
{ content: str "'*'"
, color: str colour.required
, fontFamily: str "Helvetica, Arial, Inter, sans-serif"
, lineHeight: str "calc(var(--s0) * 0.85 * var(--input-size-factor))"
, lineHeight: str
"calc(var(--s0) * 0.85 * var(--input-size-factor))"
}
}
@ -158,14 +162,15 @@ leftIconContainer ∷ Style
leftIconContainer =
iconContainer
<> css
{ borderRadius: str "var(--input-border-radius) 0 0 var(--input-border-radius)"
{ borderRadius: str
"var(--input-border-radius) 0 0 var(--input-border-radius)"
, ".ry-icon":
nest
{ "--stroke-colour": str colour.text
}
}
inputContainer ∷ ∀ r. { | Props OptionalProp r } -> Style
inputContainer ∷ ∀ r. { | Props OptionalProp r } Style
inputContainer props = theCss <>? props.css
where
theCss =
@ -173,8 +178,10 @@ inputContainer props = theCss <>? props.css
css
{ "--left-icon-size": var "--s0"
, "--right-icon-size": str "calc(var(--s0) * 1.2)"
, "--input-size-factor": str ((props.sizeVariant ?|| SizeMedium) # sizeVariantToFactor)
, "--input-side-padding": str "calc(var(--s-1) * var(--input-size-factor) * 1.2)"
, "--input-size-factor": str
((props.sizeVariant ?|| SizeMedium) # sizeVariantToFactor)
, "--input-side-padding": str
"calc(var(--s-1) * var(--input-size-factor) * 1.2)"
, "--input-top-padding": str "calc(6px)"
, "--input-bottom-padding": str "calc(6px)"
, letterSpacing: em (-0.011)
@ -207,10 +214,14 @@ inputContainer props = theCss <>? props.css
, alignItems: center
, justifyContent: center
, border: str $ "var(--border-width) solid " <> colour.inputBorder
, 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))"
, 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)"
, borderRadius: var "--input-border-radius"
, overflow: str "visible"
@ -225,7 +236,7 @@ ploppedFocusWithin = css
}
containerBackground ∷ ∀ r. { | Props OptionalProp r } -> Style
containerBackground ∷ ∀ r. { | Props OptionalProp r } Style
containerBackground props = css
{ background: str (props.background ?|| colour.inputBackground)
-- , boxShadow: str
@ -238,18 +249,19 @@ containerBackground props = css
}
containerContainer ∷ ∀ r. { | Props OptionalProp r } -> Style
containerContainer ∷ ∀ r. { | Props OptionalProp r } Style
containerContainer props = css
{ "& > *": nested $ css
{ gridColumn: int 1
, gridRow: int 1
}
, "--border-width": str "1px"
, "--input-size-factor": str ((props.sizeVariant ?|| SizeMedium) # sizeVariantToFactor)
, "--input-size-factor": str
((props.sizeVariant ?|| SizeMedium) # sizeVariantToFactor)
, "--input-border-radius": case props.sizeVariant ?|| SizeMedium of
SizeMedium -> str "var(--s-1)"
SizeSmall -> str "var(--s-2)"
SizeTiny -> str "var(--s-3)"
SizeMedium str "var(--s-1)"
SizeSmall str "var(--s-2)"
SizeTiny str "var(--s-3)"
, borderRadius: var "--input-border-radius"
, boxSizing: contentBox
, display: grid
@ -266,10 +278,11 @@ inputWrapper =
, width: inherit
}
input ∷ ∀ r. { | Props OptionalProp r } -> Style
input ∷ ∀ r. { | Props OptionalProp r } Style
input props =
css
{ "--input-size-factor": str ((props.sizeVariant ?|| SizeMedium) # sizeVariantToFactor)
{ "--input-size-factor": str
((props.sizeVariant ?|| SizeMedium) # sizeVariantToFactor)
, "&[type=text],&[type=search],&[type=password],&[type=number],&:not([type])":
nest
{ background: str "transparent"
@ -289,12 +302,14 @@ input props =
, letterSpacing: var "--letter-spacing"
, "&::placeholder":
nest
{ color: str (props.placeholderColour ?|| colour.placeholderText)
{ color: str
(props.placeholderColour ?|| colour.placeholderText)
}
, "&[aria-labelledby]":
nest
{ paddingTop: str "calc(var(--padding-top) + (var(--s-5)/2))"
, paddingBottom: str "calc(var(--padding-bottom) - (var(--s-5)/2))"
, paddingBottom: str
"calc(var(--padding-bottom) - (var(--s-5)/2))"
}
, border: none
}

View File

@ -0,0 +1,5 @@
module Yoga.Block.Atom.Modal
( module Yoga.Block.Atom.Modal.View
) where
import Yoga.Block.Atom.Modal.View (component, Props)

View File

@ -12,9 +12,4 @@ clickAway =
<> acceptClicks
modal ∷ Style
modal =
positionFixed
<> left' (50.0 # percent)
<> top zero
<> translate "-50%" "0"
<> acceptClicks
modal = acceptClicks

View File

@ -2,44 +2,18 @@ module Yoga.Block.Atom.Modal.View where
import Yoga.Prelude.View
import Fahrtwind (acceptClicks)
import Effect.Unsafe (unsafePerformEffect)
import Fahrtwind.Style.Color.Background (background)
import Fahrtwind.Style.Color.Tailwind as TW
import Fahrtwind.Style.Color.Util (withAlpha)
import Fahrtwind.Style.Inset (left, left', top) as P
import Fahrtwind.Style.Position (positionFixed)
import Fahrtwind.Style.Size (heightScreen, widthScreen) as P
import Fahrtwind.Style.Transform (translate)
import Yoga.Block.Hook.UseRenderInPortal (useRenderInPortal)
import Framer.Motion as M
import React.Basic.DOM as R
import React.Basic.Emotion (Style)
import React.Basic.Emotion as E
import React.Basic.Hooks as React
import Yoga.Block.Atom.Modal.Style as Style
-- [TODO] Move out
mkClickAway
∷ React.Component
{ css ∷ Style
, hide ∷ Effect Unit
, isVisible ∷ Boolean
, clickAwayId ∷ String
}
mkClickAway = do
React.component "Clickaway"
\{ css, isVisible, hide, clickAwayId } →
React.do
renderInPortal ← useRenderInPortal clickAwayId
pure
$ guard isVisible
$ renderInPortal
$ R.div'
</*>
{ className: "click-away"
, css: Style.clickAway <> css
, onMouseUp: handler_ hide
, onTouchEnd: handler_ hide
}
import Yoga.Block.Hook.UseRenderInPortal (useRenderInPortal)
import Yoga.Block.Layout.Centre.View as Centre
import Yoga.Block.Layout.Cover.View as Cover
import Yoga.Block.Quark.ClickAway.View as ClickAway
type ModalIds = { clickAwayId ∷ String, modalContainerId ∷ String }
@ -52,10 +26,9 @@ type Props =
, modalContainerId ∷ String
}
mkModal ∷ React.Component Props
mkModal = do
clickAway ← mkClickAway
React.component "Modal" \props → React.do
component ∷ ReactComponent Props
component = unsafePerformEffect do
React.reactComponent "Modal" \props → React.do
let
{ hide
, isVisible
@ -65,12 +38,53 @@ mkModal = do
, modalContainerId
} = props
renderInPortal ← useRenderInPortal modalContainerId
let
child = div "ry-modal" Style.modal [ content ]
pure $ fragment
[ clickAway
[ ClickAway.component </>
{ css: background (TW.gray._900 # withAlpha 0.5)
, hide: if allowClickAway then hide else mempty
, isVisible
, clickAwayId
}
, renderInPortal (div "modal" Style.modal [ content ])
, renderInPortal
$ Cover.component
</ {}
/>
[ Centre.component </ {} />
[ M.animatePresence </ {} />
[ guard isVisible
$ M.div
</
{ key: "popOver"
, initial: M.initial
( R.css
{ opacity: 0.0
, scale: 0.3
}
)
, animate: M.animate
( R.css
{ opacity: 1
, scale: 1.0
, transition:
{ duration: 0.4, type: "spring" }
}
)
, exit: M.exit
( R.css
{ opacity: 0
, scale: 0.3
, transition: { duration: 0.2 }
}
)
-- , onAnimationComplete
-- , onAnimationStart
-- , ref: motionRef
}
/>
[ child ]
]
]
]
]

View File

@ -49,6 +49,7 @@ data KeyCode
| Down
| Delete
| Backspace
| Tab
derive instance Eq KeyCode
derive instance Ord KeyCode
@ -56,6 +57,7 @@ derive instance Ord KeyCode
keyCodeToInt ∷ KeyCode -> Int
keyCodeToInt = case _ of
Backspace -> 8
Tab -> 9
Return -> 13
Escape -> 27
End -> 35
@ -69,6 +71,7 @@ keyCodeToInt = case _ of
intToKeyCode ∷ Int -> Maybe KeyCode
intToKeyCode = case _ of
8 -> Just Backspace
9 -> Just Tab
13 -> Just Return
27 -> Just Escape
35 -> Just End

View File

@ -4,6 +4,7 @@ import Yoga.Prelude.View
import Data.Array as Array
import Data.Newtype (class Newtype)
import Debug (spy)
import React.Basic.Hooks as React
import Type.Function (type (#))
import Web.DOM.ResizeObserver (ResizeObserverBoxOptions(..), observe, resizeObserver, unobserve)
@ -17,19 +18,18 @@ derive instance Newtype (UseOnElementResizeWithRef hooks) _
type OnResize = { old :: DOMRect, new :: DOMRect } -> Effect Unit
useOnElementResizeWithRef ∷ NodeRef -> OnResize -> Hook UseOnElementResizeWithRef Unit
useOnElementResizeWithRef ref onResize =
coerceHook React.do
useLayoutEffectOnce do
elʔ <- getElementFromRef ref
case elʔ of
Nothing -> mempty
Just elem -> do
observer <- resizeObserver \entries _ ->
Array.head entries # traverse_ \{ contentRect: new } -> do
old <- getBoundingClientRect elem
useOnElementResizeWithRef ref onResize = coerceHook React.do
useLayoutEffectOnce do
elʔ <- getElementFromRef ref
case elʔ of
Nothing -> mempty
Just elem -> do
observer <- resizeObserver \entries _ -> do
Array.head entries # traverse_ \{ contentRect: new } -> do
getBoundingClientRect elem >>= \old ->
onResize { old, new }
elem # observe BorderBox observer
pure (elem # unobserve observer)
observe ContentBox observer elem
pure (unobserve observer elem)
newtype UseOnElementResize hooks = UseOnElementResize
(hooks # UseRef (Nullable Node) # UseOnElementResizeWithRef)

View File

@ -85,6 +85,7 @@ import Web.HTML.Window (document)
import Yoga.Block.Internal.CSS (_0)
import Yoga.Block.Internal.OptionalProp (OptionalProp(..), Id, appendIfDefined, asOptional, composeHandler, getOr, getOrFlipped, ifTrue, isTruthy, maybeToOp, opToMaybe, setOrDelete, unsafeUnMaybe, unsafeUnOptional, (<>?), (?||))
unsafeAddProps ∷ ∀ r s. { | r } → { | s } → { | r }
unsafeAddProps = unsafeUnion

View File

@ -2,7 +2,7 @@ module Yoga.Block.Layout.Centre.Style where
import Yoga.Prelude.Style
type Props :: forall k. (Type -> k) -> Row k -> Row k
type Props ∷ ∀ k. (Type → k) → Row k → Row k
type Props f r =
( css ∷ f Style
, maxWidth ∷ f StyleProperty
@ -12,7 +12,7 @@ type Props f r =
| r
)
centre ∷ ∀ p. { | Props OptionalProp p } -> Style
centre ∷ ∀ p. { | Props OptionalProp p } Style
centre props = styles <>? props.css
where
styles =
@ -20,11 +20,10 @@ centre props = styles <>? props.css
else textLeft
) <>
css
{ padding: props.padding ?|| (0 # px)
, boxSizing: contentBox
, marginLeft: auto
, marginRight: auto
, maxWidth: props.maxWidth ?|| (60.0 # ch)
, paddingLeft: props.gutters ?|| _0
, paddingRight: props.gutters ?|| _0
{ boxSizing: contentBox
, width: str "fit-content"
, marginInline: auto
, maxInlineSize: props.maxWidth ?|| (60.0 # ch)
, paddingInlineStart: props.gutters ?|| _0
, paddingInlineEnd: props.gutters ?|| _0
}

View File

@ -1,4 +1,10 @@
module Yoga.Block.Layout.Centre.View (component, Props, PropsF, PropsNoChildrenF, PropsNoChildren) where
module Yoga.Block.Layout.Centre.View
( component
, Props
, PropsF
, PropsNoChildrenF
, PropsNoChildren
) where
import Yoga.Prelude.View
import Yoga.Block.Internal (DivPropsNoChildren)
@ -23,13 +29,13 @@ type Props =
type PropsOptional =
PropsF OptionalProp
component ∷ ∀ p p_. Union p p_ Props => ReactComponent { | p }
component ∷ ∀ p p_. Union p p_ Props ReactComponent { | p }
component = rawComponent
rawComponent ∷ ∀ p. ReactComponent { | p }
rawComponent =
mkForwardRefComponent "Centre" do
\(props ∷ { | PropsOptional }) ref -> React.do
\(props ∷ { | PropsOptional }) ref React.do
pure
$ emotionDiv
ref

View File

@ -2,7 +2,7 @@ module Yoga.Block.Layout.Cover.Style where
import Yoga.Prelude.Style
type Props :: forall k. (Type -> k) -> Row k -> Row k
type Props ∷ ∀ k. (Type → k) → Row k → Row k
type Props f r =
( css ∷ f Style
, padding ∷ f StyleProperty
@ -11,29 +11,26 @@ type Props f r =
| r
)
defaultMargin :: StyleProperty
defaultMargin StyleProperty
defaultMargin = 1.0 # rem
cover ∷ ∀ p. { | Props OptionalProp p } -> Style
cover ∷ ∀ p. { | Props OptionalProp p } Style
cover props = styles <>? props.css
where
padding = props.padding ?|| _0
minHeight = props.minHeight ?|| (100.0 # percent)
styles =
flexCol <>
css
{ minHeight
, padding
}
styles = flexCol <> css { minHeight, padding }
header ∷ ∀ p. { | Props OptionalProp p } -> Style
header props = css { marginTop: _0, marginBottom: props.margin ?|| defaultMargin }
header ∷ ∀ p. { | Props OptionalProp p } → Style
header props = css
{ marginTop: _0, marginBottom: props.margin ?|| defaultMargin }
main ∷ Style
main = css { marginTop: auto, marginBottom: auto }
main = css { marginBlock: auto }
footer ∷ ∀ p. { | Props OptionalProp p } -> Style
footer props = css { marginBottom: _0, marginTop: props.margin ?|| defaultMargin }
footer ∷ ∀ p. { | Props OptionalProp p } → Style
footer props = css
{ marginBottom: _0, marginTop: props.margin ?|| defaultMargin }
invisible :: Style
invisible = css { visibility: hidden }
invisible Style
invisible = css { visibility: hidden }

View File

@ -2,23 +2,38 @@ module Yoga.Block.Molecule.Typeahead.Style where
import Yoga.Prelude.Style
type Props f r = (| r)
resultsContainer ∷ Style
resultsContainer =
background' col.inputBackground
<> pT 0
<> pX 0
<> flexCol
pXY 0
<> mT 4
<> mB 4
<> maxHeight' (50.0 # vh)
<> minHeight' (25.0 # vh)
<> height' (230 # px)
<> justifyEnd
<> itemsStart
<> gap 3
<> roundedLg
<> textXs
<> shadowLg
<> borderCol gray._200
<> roundedDefault
<> shadowXxl
<> overflowHidden
<> blurredBackground'
{ blurRadius: 4
, blurredCol: colourWithDarkLightAlpha.backgroundLayer5
{ darkAlpha: 0.5, lightAlpha: 0.5 }
, fallbackCol: colour.inputBackground
}
<> border 1
<> borderSolid
<> borderCol' col.inputBorder
<> css { ".TypeaheadList": nested (pXY 0 <> mXY 0) }
resultContainer ∷ Style
resultContainer = pY 2 <> cursorPointer <> overflowHidden
resultContainer =
pX 0
<> cursorPointer
<> overflowHidden
item ∷ Style
item =
@ -27,3 +42,7 @@ item =
<> textCol' col.highlightText
<> outlineNone
)
<> border 0
<> borderBottom 1
<> borderSolid
<> borderCol' col.backgroundLayer3

View File

@ -7,44 +7,59 @@ import Data.Array as Array
import Data.Function.Uncurried (mkFn3)
import Data.Time.Duration (Milliseconds(..))
import Effect.Aff (Aff, attempt, delay)
import Effect.Console as Console
import Effect.Exception (Error)
import Effect.Uncurried (mkEffectFn1, runEffectFn1)
import Fahrtwind (mX, minWidth, overflowHidden, textCol', widthAndHeight)
import Effect.Uncurried (mkEffectFn1, runEffectFn1, runEffectFn2)
import Fahrtwind (overflowHidden, textCol', widthAndHeight)
import Fahrtwind.Style.ScrollBar (scrollBar')
import Framer.Motion as M
import Literals.Undefined (Undefined, undefined)
import Network.RemoteData (RemoteData)
import Network.RemoteData as RemoteData
import Yoga.Block.Atom.PopOver.Types (Placement(..), PrimaryPlacement(..), SecondaryPlacement(..))
import Yoga.Block.Atom.PopOver.View (mkPopOverView)
import Yoga.Block.Molecule.Typeahead.Style as Style
import Prim.Row (class Lacks, class Nub)
import React.Aria.Interactions (useFocus, useFocusWithin)
import React.Aria.Utils (mergeProps)
import React.Basic.DOM as R
import React.Basic.DOM.Events (capture_)
import React.Basic.DOM.Events as SE
import React.Basic.Emotion as E
import React.Basic.Hooks as React
import React.Basic.Hooks.Aff (useAff)
import React.Virtuoso (virtuosoImpl)
import React.Virtuoso (VirtuosoInstance, virtuosoImpl)
import Record as Record
import Type.Proxy (Proxy(..))
import Unsafe.Coerce (unsafeCoerce)
import Untagged.Union (maybeToUor, uorToMaybe)
import Untagged.Union (UndefinedOr, maybeToUor, uorToMaybe)
import Web.DOM.Document (toNonElementParentNode)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
import Web.HTML.HTMLDocument (activeElement)
import Web.HTML.HTMLDocument as HTMLDocument
import Web.HTML.HTMLElement as HTMLElement
import Web.HTML.Window (document)
import Yoga.Block.Atom.Input as Input
import Yoga.Block.Container.Style (col)
import Yoga.Block.Atom.PopOver.Types (Placement(..), PrimaryPlacement(..), SecondaryPlacement(..))
import Yoga.Block.Atom.PopOver.View (mkPopOverView)
import Yoga.Block.Container.Style (col, sizeStyle)
import Yoga.Block.Hook.Key (KeyCode)
import Yoga.Block.Hook.Key as Key
import Yoga.Block.Hook.UseOnElementResize (useOnElementResizeWithRef)
import Yoga.Block.Hook.UseResize2 (useOnResize)
import Yoga.Block.Icon.SVG.Spinner (spinner)
import Yoga.Block.Internal (focusNodeRef)
import Yoga.Block.Molecule.Typeahead.Style as Style
type Props a = PropsF Id a
type PropsF f a =
( checked ∷ f Boolean
| Style.Props f (MandatoryProps () a)
)
type MandatoryProps r a =
( renderSuggestion ∷ a → JSX
, loadSuggestions ∷ String → Aff (Either Error (Array a))
| r
)
type Overscan = { main ∷ Int, reverse ∷ Int }
type ScrollSeekPlaceholder = ReactComponent { height ∷ Number, index ∷ Int }
@ -67,23 +82,22 @@ newtype InputProps = InputProps (∀ x. { | x })
inputProps ∷ ∀ p p_. Union p p_ Input.Props ⇒ { | p } → InputProps
inputProps = unsafeCoerce
type Props a =
type PropsOld a =
{ onSelected ∷
a → Effect { overrideInputValue ∷ Maybe String, dismiss ∷ Boolean }
, onRemoved ∷ a → Effect Unit
, renderSuggestion ∷ a → JSX
, loadSuggestions ∷ String → Aff (Either Error (Array a))
, onDismiss ∷ Effect Unit
, placeholder ∷ String
, inputProps ∷ InputProps
}
mkDefaultArgs
∀ a
. { suggestionToText ∷ a → String
, contextMenuLayerId ∷ String
}
Args a
mkDefaultArgs
∀ a.
{ suggestionToText ∷ a → String
, contextMenuLayerId ∷ String
}
Args a
mkDefaultArgs
{ suggestionToText
, contextMenuLayerId
@ -98,7 +112,7 @@ mkDefaultArgs
, itemStyle: Style.item
}
mkTypeahead ∷ ∀ a. Eq a ⇒ Args a → Effect (ReactComponent (Props a))
mkTypeahead ∷ ∀ a. Eq a ⇒ Args a → Effect (ReactComponent (PropsOld a))
mkTypeahead args = do
typeaheadView ← mkTypeaheadView
{ contextMenuLayerId: args.contextMenuLayerId
@ -108,7 +122,7 @@ mkTypeahead args = do
, containerStyle: args.containerStyle
, itemStyle: args.itemStyle
}
React.reactComponent "Typeahead" \(props ∷ Props a) → React.do
React.reactComponent "Typeahead" \(props ∷ PropsOld a) → React.do
input /\ setInput ← React.useState' ""
suggestions /\ setSuggestions ← React.useState' RemoteData.NotAsked
{ activeIndex, updatedByKeyboard } /\ updateActiveIndex ← React.useState
@ -133,7 +147,6 @@ mkTypeahead args = do
, onSelected: props.onSelected
, onRemoved: props.onRemoved
, onDismiss: setSuggestions RemoteData.NotAsked *> props.onDismiss
, placeholder: props.placeholder
, renderSuggestion: props.renderSuggestion
, inputProps: props.inputProps
, isLoading: suggestions # RemoteData.isLoading
@ -149,32 +162,31 @@ type ViewProps a =
, renderSuggestion ∷ a → JSX
, updateActiveIndex ∷
( { activeIndex ∷ Maybe Int
, updatedByKeyboard ∷ Boolean
} →
{ activeIndex ∷ Maybe Int
, updatedByKeyboard ∷ Boolean
}
→ { activeIndex ∷ Maybe Int
, updatedByKeyboard ∷ Boolean
}
)
→ Effect Unit
) →
Effect Unit
, onSelected ∷
a → Effect { overrideInputValue ∷ Maybe String, dismiss ∷ Boolean }
, onRemoved ∷ a → Effect Unit
, onDismiss ∷ Effect Unit
, placeholder ∷ String
, inputProps ∷ InputProps
}
mkTypeaheadView
∀ a
. Eq a
{ contextMenuLayerId ∷ String
, scrollSeekPlaceholderʔ ∷ Maybe ScrollSeekPlaceholder
, scrollSeekConfigurationʔ ∷ Maybe ScrollSeekConfiguration
, overscan ∷ Overscan
, containerStyle ∷ E.Style
, itemStyle ∷ E.Style
}
Effect (ReactComponent (ViewProps a))
mkTypeaheadView
∀ a.
Eq a
{ contextMenuLayerId ∷ String
, scrollSeekPlaceholderʔ ∷ Maybe ScrollSeekPlaceholder
, scrollSeekConfigurationʔ ∷ Maybe ScrollSeekConfiguration
, overscan ∷ Overscan
, containerStyle ∷ E.Style
, itemStyle ∷ E.Style
}
Effect (ReactComponent (ViewProps a))
mkTypeaheadView
args@{ contextMenuLayerId } = do
-- loader ← mkLoader
@ -183,7 +195,7 @@ mkTypeaheadView
Style.resultContainer
M.li
listCompo ∷ ReactComponent {} ← mkForwardRefComponentWithStyle "TypeaheadList"
(overflowHidden)
overflowHidden
R.ul'
React.reactComponent "TypeaheadView" \(props ∷ ViewProps a) →
@ -197,7 +209,6 @@ mkTypeaheadView
, activeIndex
, updatedByKeyboard
, updateActiveIndex
, placeholder
, isLoading
} = props
let (InputProps inputProps) = props.inputProps
@ -211,20 +222,28 @@ mkTypeaheadView
inputContainerRef ← React.useRef null
inputRef ← React.useRef null
virtuosoRef ← React.useRef null
width /\ setWidth ← React.useState' 210.0
let focusIsWithin = inputHasFocus || popupHasFocus
{ focusWithinProps } ←
useFocusWithin
{ onFocusWithin: handler_ (setPopupHasFocus true)
, onBlurWithin: handler_ (setPopupHasFocus false)
}
{ focusWithinProps } ← useFocusWithin
{ onFocusWithin: handler_ (setPopupHasFocus true)
, onBlurWithin: handler_ (setPopupHasFocus false)
}
{ focusProps } ←
useFocus
{ onFocus: handler_ (setInputHasFocus true)
, onBlur: handler_ (setInputHasFocus false)
}
{ focusProps } ← useFocus
{ onFocus: handler_ (setInputHasFocus true)
, onBlur: handler_ (setInputHasFocus false)
}
React.useEffectOnce do
getBoundingBoxFromRef inputContainerRef >>= traverse_
(_.width >>> setWidth)
mempty
useOnResize (150.0 # Milliseconds) \_ → do
getBoundingBoxFromRef inputContainerRef >>= traverse_
(_.width >>> setWidth)
-- We store the result whenever we have successful suggestions
React.useEffect (RemoteData.isSuccess suggestions) do
@ -241,17 +260,18 @@ mkTypeaheadView
RemoteData.Success suggs → suggs
focusInput ∷ Effect Unit
focusInput = do
maybeElem ← React.readRefMaybe inputRef
for_ (maybeElem >>= HTMLElement.fromNode) focus
focusInput = focusNodeRef inputRef
blurCurrentItem ∷ Effect Unit
blurCurrentItem = do
maybeActive ← window >>= document >>= activeElement
for_ maybeActive \active → blur active
focusActiveElement id { isAnimating, isScrolling, updatedByKeyboard }
focusActiveElement
id
{ isAnimating, isScrolling, updatedByKeyboard }
blurCurrentItem
virtuosoRef
activeIndex
let
onSelected i = do
@ -270,8 +290,11 @@ mkTypeaheadView
handleKeyUp =
mkHandleKeyUp
{ activeIndex
, updateActiveIndex: \update → updateActiveIndex \old →
{ activeIndex: update old.activeIndex, updatedByKeyboard: true }
, updateActiveIndex:
\update → updateActiveIndex \old →
{ activeIndex: update old.activeIndex
, updatedByKeyboard: true
}
, focusInput
, suggestions: suggestions # RemoteData.toMaybe # fromMaybe
prevSuggs
@ -283,8 +306,14 @@ mkTypeaheadView
[ inputElement
, popOver
{ hide: blurCurrentItem
, placement: Placement Below Start
, fallbackPlacements: [ Placement Below End, Placement Above Start, Placement Above End ]
, placement: Placement Below Centre
, fallbackPlacements:
[ Placement Below End
, Placement Below Start
, Placement Above End
, Placement Above Centre
, Placement Above Start
]
, placementRef: inputContainerRef
, dismissBehaviourʔ: Nothing
, onAnimationStateChange: setIsAnimating
@ -301,21 +330,19 @@ mkTypeaheadView
]
inputElement = React.element Input.component
( ( inputProps `mergeProps`
( ( inputProps # unsafeMergeSecond
{ id
, ref: inputContainerRef
, inputRef: inputRef
, inputRef
, spellCheck: false
, autoComplete: "off"
, placeholder
, value: input
, onChange: handler targetValue (traverse_ setInput)
, onMouseEnter: handler_
(when focusIsWithin focusInput)
, onKeyUp:
handler
SE.key
\e → e >>= parseKey # traverse_ handleKeyUp
, onKeyUp: handler
SE.key
\e → e >>= parseKey # traverse_ handleKeyUp
, onFocus: focusProps.onFocus
, onBlur: focusProps.onBlur
@ -335,12 +362,11 @@ mkTypeaheadView
, id: id <> "-suggestion-" <> show i
, css: args.itemStyle
, onMouseMove:
handler syntheticEvent \det → unless (activeIndex == Just i)
do
handler syntheticEvent
\det → unless (activeIndex == Just i) do
let
movementX = (unsafeCoerce det).movementX # uorToMaybe #
fromMaybe 0.0
let
movementY = (unsafeCoerce det).movementY # uorToMaybe #
fromMaybe 0.0
unless ((movementX == zero && movementY == zero)) do
@ -361,15 +387,10 @@ mkTypeaheadView
/> [ renderSuggestion suggestion ]
resultsContainer =
M.div
R.div'
</*
{ css: minWidth 210 <> mX 0 <> args.containerStyle
, initial: M.initial $ false
, animate: M.animate $ R.css
{ height:
if Array.length visibleData > 5 then 230
else 120
}
{ css: args.containerStyle
, style: R.css { width: show width <> "px", maxHeight: "50vh" }
}
/>
[ suggestionElements
@ -381,11 +402,11 @@ mkTypeaheadView
, className: "virtuoso"
, css:
scrollBar'
{ background: col.inputBackground
, col: col.textPaler2
, width: E.var "--s0"
, borderRadius: E.px 8
, borderWidth: E.px 4
{ background: col.backgroundBright2
, col: col.backgroundBright4
, width: sizeStyle.m
, borderRadius: E.px 5
, borderWidth: E.px 2
}
, scrollSeekConfiguration: args.scrollSeekConfigurationʔ #
maybeToUor
@ -398,7 +419,11 @@ mkTypeaheadView
}
, isScrolling: mkEffectFn1 setIsScrolling
, style: R.css
{ height: "100%", width: "100%" }
{ height: "100%"
, width: "100%"
, padding: "0"
, margin: "0"
}
, data: visibleData
, itemContent: mkFn3 wrapSuggestion
}
@ -417,6 +442,7 @@ mkTypeaheadView
id
{ isAnimating, isScrolling, updatedByKeyboard }
blurCurrentItem
virtuosoRef
activeIndex =
useEffect activeIndex do
unless (isAnimating || isScrolling) do
@ -427,10 +453,27 @@ mkTypeaheadView
( HTMLDocument.toDocument >>> toNonElementParentNode >>>
getElementById (id <> "-suggestion-" <> show i)
)
for_ (suggʔ >>= HTMLElement.fromElement)
(if updatedByKeyboard then focus else focusPreventScroll)
for_ (suggʔ >>= HTMLElement.fromElement) \el →
if updatedByKeyboard then do
React.readRefMaybe virtuosoRef >>= traverse_
( scrollToIndex
{ behavior: cast undefined
, index: i
, align: "center"
}
)
focus el
else
focusPreventScroll el
mempty
scrollToIndex ∷
{ behavior ∷ UndefinedOr String, index ∷ Int, align ∷ String } →
VirtuosoInstance →
Effect Unit
scrollToIndex options inst =
runEffectFn1 (unsafeCoerce inst).scrollToIndex options
-- https://caniuse.com/mdn-api_svgelement_focus_options_preventscroll_parameter
focusPreventScroll ∷ HTMLElement → Effect Unit
focusPreventScroll (htmlElement ∷ HTMLElement) = do
@ -444,19 +487,19 @@ parseKey = case _ of
"Enter" → Just Key.Return
_ → Nothing
mkHandleKeyUp
∀ a
. { activeIndex ∷ Maybe Int
, focusInput ∷ Effect Unit
, suggestions ∷ (Array a)
, updateActiveIndex ∷
(Maybe Int → Maybe Int)
Effect Unit
, onSelected ∷ a → Effect Unit
, onDismiss ∷ Effect Unit
}
KeyCode
Effect Unit
mkHandleKeyUp
∀ a.
{ activeIndex ∷ Maybe Int
, focusInput ∷ Effect Unit
, suggestions ∷ (Array a)
, updateActiveIndex ∷
(Maybe Int → Maybe Int)
Effect Unit
, onSelected ∷ a → Effect Unit
, onDismiss ∷ Effect Unit
}
KeyCode
Effect Unit
mkHandleKeyUp
{ activeIndex
, suggestions
@ -468,44 +511,46 @@ mkHandleKeyUp
key = do
let maxIndex = Array.length suggestions - 1
case key of
Key.Tab → do
updateActiveIndex (const Nothing)
when (activeIndex # isJust) do
focusInput
Key.Up → do
when (activeIndex == Just 0) focusInput
updateActiveIndex case _ of
Just 0 → Nothing
Just 0 → Just maxIndex
Nothing → Just maxIndex
Just i → Just (i - 1)
Key.Down → do
when (activeIndex == Just maxIndex) focusInput
when (activeIndex == Just (maxIndex - 1)) focusInput
updateActiveIndex case _ of
Just i | i == maxIndex → Nothing
Just i | i == maxIndex → Just 0
Nothing → Just 0
Just i → Just (i + 1)
-- [TODO] End and Home keys
Key.Return → do
Key.Return →
for_ activeIndex \i → do
for_ (suggestions !! i) onSelected
Key.Backspace → do
focusInput
Key.Escape → do
onDismiss
Key.Backspace → focusInput
Key.Escape → onDismiss
_ → mempty
mkForwardRefComponent
∀ ref props
. Lacks "ref" props
String
ReactComponent { ref ∷ React.Ref ref | props }
Effect (ReactComponent { | props })
mkForwardRefComponent
∀ ref props.
Lacks "ref" props
String
ReactComponent { ref ∷ React.Ref ref | props }
Effect (ReactComponent { | props })
mkForwardRefComponent name component = mkForwardRefComponentEffect name
\(props ∷ { | props }) ref → React.do
pure $ React.element component (Record.insert (Proxy ∷ _ "ref") ref props)
mkForwardRefEmotionComponent
∀ ref props
. Lacks "ref" props
String
ReactComponent { className ∷ String, ref ∷ React.Ref ref | props }
Effect (ReactComponent { className ∷ String, css ∷ E.Style | props })
mkForwardRefEmotionComponent
∀ ref props.
Lacks "ref" props
String
ReactComponent { className ∷ String, ref ∷ React.Ref ref | props }
Effect (ReactComponent { className ∷ String, css ∷ E.Style | props })
mkForwardRefEmotionComponent name component =
mkForwardRefComponentEffect name
\(props ∷ { className ∷ String, css ∷ E.Style | props }) ref → React.do
@ -513,25 +558,25 @@ mkForwardRefEmotionComponent name component =
( Record.insert (Proxy ∷ _ "ref") ref props
)
mkForwardRefComponentWithStyle
∀ ref props
. Lacks "ref" props
Lacks "className" props
Union props
(className ∷ String, css ∷ E.Style)
(className ∷ String, css ∷ E.Style | props)
Nub (className ∷ String, css ∷ E.Style | props)
(className ∷ String, css ∷ E.Style | props)
String
E.Style
ReactComponent { className ∷ String, ref ∷ React.Ref ref | props }
Effect (ReactComponent { | props })
mkForwardRefComponentWithStyle
∀ ref props.
Lacks "ref" props
Lacks "className" props
Union props
(className ∷ String, css ∷ E.Style)
(className ∷ String, css ∷ E.Style | props)
Nub (className ∷ String, css ∷ E.Style | props)
(className ∷ String, css ∷ E.Style | props)
String
E.Style
ReactComponent { className ∷ String, ref ∷ React.Ref ref | props }
Effect (ReactComponent { | props })
mkForwardRefComponentWithStyle name css component = mkForwardRefComponentEffect
name
\(props ∷ { | props }) ref → React.do
pure $ E.element component
( Record.insert (Proxy ∷ _ "ref") ref
( (props `Record.disjointUnion` { className: name, css })
{ className ∷ String, css ∷ E.Style | props }
( (props `Record.disjointUnion` { className: name, css })
{ className ∷ String, css ∷ E.Style | props }
)
)

View File

@ -0,0 +1,12 @@
module Yoga.Block.Quark.ClickAway.Style where
import Yoga.Prelude.Style
clickAway ∷ Style
clickAway =
widthScreen
<> heightScreen
<> positionFixed
<> left zero
<> top zero
<> acceptClicks

View File

@ -0,0 +1,34 @@
module Yoga.Block.Quark.ClickAway.View where
import Yoga.Prelude.View
import Effect.Unsafe (unsafePerformEffect)
import React.Basic.DOM as R
import React.Basic.Emotion (Style)
import React.Basic.Hooks as React
import Yoga.Block.Quark.ClickAway.Style as Style
import Yoga.Block.Hook.UseRenderInPortal (useRenderInPortal)
type Props =
{ css ∷ Style
, hide ∷ Effect Unit
, isVisible ∷ Boolean
, clickAwayId ∷ String
}
component ∷ ReactComponent Props
component = unsafePerformEffect do
React.reactComponent "Clickaway" \(props ∷ Props) → React.do
let { css, isVisible, hide, clickAwayId } = props
renderInPortal ← useRenderInPortal clickAwayId
pure
$ guard isVisible
( renderInPortal
$ R.div'
</*>
{ className: "click-away"
, css: Style.clickAway <> css
, onMouseUp: handler_ hide
, onTouchEnd: handler_ hide
}
)

View File

@ -1 +0,0 @@
module Yoga.Block.Quark.Drip where

View File

@ -0,0 +1,5 @@
module Yoga.Block.Quark.Layer
( module Yoga.Block.Quark.Layer.View
) where
import Yoga.Block.Quark.Layer.View (component, Props)

View File

@ -0,0 +1,12 @@
module Yoga.Block.Quark.Layer.Style where
import Yoga.Prelude.Style
fixed ∷ Style
fixed =
positionFixed
<> widthScreen
<> heightScreen
<> top 0
<> left 0
<> ignoreClicks

View File

@ -0,0 +1,24 @@
module Yoga.Block.Quark.Layer.View where
import Yoga.Prelude.View
import React.Basic.DOM as R
import Yoga.Block.Quark.Layer.Style as Style
type Props =
( id ∷ String
, zIndex ∷ Int
)
component ∷ ReactComponent { | Props }
component = mkForwardRefComponent "FixedLayer" \props ref → React.do
let { id, zIndex } = props
pure
$ div'
</*>
{ id
, style: R.css { zIndex }
, className: "ry-layer"
, css: Style.fixed
, ref
}

View File

@ -13,11 +13,6 @@ import Data.Traversable (for)
import Fahrtwind (acceptClicks, positionAbsolute)
import Fahrtwind.Style.BoxShadow (shadow)
import Framer.Motion as M
import Yoga.Block.Atom.Modal.View (mkClickAway)
import Yoga.Block.Atom.PopOver.Types (DismissBehaviour(..), Placement(..), PrimaryPlacement(..), SecondaryPlacement(..))
import Yoga.Block.Hook.UseRenderInPortal (useRenderInPortal)
import Yoga.Block.Hook.UseResize2 (useOnResize)
import Yoga.Prelude.Style (Style)
import React.Basic.DOM as R
import React.Basic.Hooks as React
import Unsafe.Reference (reallyUnsafeRefEq)
@ -27,6 +22,11 @@ import Web.HTML (window)
import Web.HTML.HTMLDocument as HTMLDocument
import Web.HTML.Window (document, innerHeight, innerWidth, requestAnimationFrame, scrollX, scrollY)
import Web.UIEvent.MouseEvent as MouseEvent
import Yoga.Block.Atom.PopOver.Types (DismissBehaviour(..), Placement(..), PrimaryPlacement(..), SecondaryPlacement(..))
import Yoga.Block.Hook.UseRenderInPortal (useRenderInPortal)
import Yoga.Block.Hook.UseResize2 (useOnResize)
import Yoga.Block.Quark.ClickAway.View as ClickAway
import Yoga.Prelude.Style (Style)
popOverShadow ∷ Style
popOverShadow =
@ -128,19 +128,27 @@ mkPopOverView = do
getBbsWidthAndHeight = ado
bbʔ ← getBoundingBoxFromRef contentRef
targetBbʔ <- getBoundingBoxFromRef props.placementRef
targetBbʔ getBoundingBoxFromRef props.placementRef
w ← window >>= innerWidth <#> Int.toNumber
h ← window >>= innerHeight <#> Int.toNumber
in { bbʔ, targetBbʔ, w, h }
getBestPlacement
∷ { bbʔ ∷ Maybe DOMRect, targetBbʔ ∷ Maybe DOMRect, w ∷ Number, h ∷ Number } → Placement -> Array Placement → Placement
getBestPlacement bbsWidthAndHeight oldPlacement fallbackPlacements = fromMaybe oldPlacement do
let { w, h } = bbsWidthAndHeight
targetBb <- bbsWidthAndHeight.targetBbʔ
bb <- bbsWidthAndHeight.bbʔ
(oldPlacement : fallbackPlacements) # Array.find \placement ->
isWithin { w, h } (placeAt targetBb bb placement)
getBestPlacement ∷
{ bbʔ ∷ Maybe DOMRect
, targetBbʔ ∷ Maybe DOMRect
, w ∷ Number
, h ∷ Number
} →
Placement →
Array Placement →
Placement
getBestPlacement bbsWidthAndHeight oldPlacement fallbackPlacements =
fromMaybe oldPlacement do
let { w, h } = bbsWidthAndHeight
targetBb ← bbsWidthAndHeight.targetBbʔ
bb ← bbsWidthAndHeight.bbʔ
(oldPlacement : fallbackPlacements) # Array.find \placement →
isWithin { w, h } (placeAt targetBb bb placement)
-- :: DOMRect -> { width :: Number, height :: Number } -> Placement
let
@ -237,7 +245,6 @@ toTransformOrigin (Placement primary secondary) = primaryOrigin <> " " <>
mkPopOver ∷ React.Component Props
mkPopOver = do
clickAway ← mkClickAway
React.component "popOver" \props → React.do
let { hide, isVisible, content, dismissBehaviourʔ, containerId } = props
refBB /\ setRefBB ← React.useState' (zero ∷ DOMRect)
@ -265,7 +272,7 @@ mkPopOver = do
pure $ fragment
[ case dismissBehaviourʔ of
Just (DismissOnClickAway { id, css }) →
clickAway { css, hide, isVisible, clickAwayId: id }
ClickAway.component </> { css, hide, isVisible, clickAwayId: id }
_ → mempty
, renderInPortal
( R.div'
@ -279,8 +286,10 @@ mkPopOver = do
)
]
isWithin ∷ { w :: Number, h :: Number } -> DOMRect → Boolean
isWithin { w, h } bb = bb.top >= 0.0 && bb.left >= 0.0 && bb.bottom <= h && bb.right <= w
isWithin ∷ { w ∷ Number, h ∷ Number } → DOMRect → Boolean
isWithin { w, h } bb = bb.top >= 0.0 && bb.left >= 0.0 && bb.bottom <= h
&& bb.right
<= w
toAbsoluteCSS ∷ DOMRect → Placement → R.CSS
toAbsoluteCSS bb (Placement primary secondary) =
@ -344,55 +353,66 @@ toAbsoluteCSS bb (Placement primary secondary) =
, transform: "translate(-100%, 0)"
}
placeAt :: forall r. DOMRect -> { width :: Number, height :: Number | r } -> Placement -> DOMRect
placeAt bb { width, height } (Placement primary secondary) = complete case primary, secondary of
Above, Centre →
{ x: bb.left + (bb.width / 2.0) - (width / 2.0)
, y: bb.top - (height / 2.0)
}
Above, Start →
{ x: bb.left
, y: bb.top - height
}
Above, End →
{ x: bb.right - width
, y: bb.top - height
}
RightOf, Centre →
{ x: bb.right
, y: bb.y + (bb.height / 2.0) - (height / 2.0)
}
RightOf, Start →
{ x: bb.right
, y: bb.top
}
RightOf, End →
{ x: bb.right
, y: bb.bottom - height
}
LeftOf, Centre →
{ x: bb.left - width
, y: bb.top + (bb.height / 2.0) - (height / 2.0)
}
LeftOf, Start →
{ x: bb.left - width
, y: bb.top
}
LeftOf, End →
{ x: bb.left - width
, y: bb.bottom - height
}
Below, Centre →
{ x: bb.left + (bb.width / 2.0) - (width / 2.0)
, y: bb.bottom
}
Below, Start →
{ x: bb.left
, y: bb.bottom
}
Below, End →
{ x: bb.right - width
, y: bb.bottom
}
placeAt ∷
∀ r. DOMRect → { width ∷ Number, height ∷ Number | r } → Placement → DOMRect
placeAt bb { width, height } (Placement primary secondary) = complete
case primary, secondary of
Above, Centre →
{ x: bb.left + (bb.width / 2.0) - (width / 2.0)
, y: bb.top - (height / 2.0)
}
Above, Start →
{ x: bb.left
, y: bb.top - height
}
Above, End →
{ x: bb.right - width
, y: bb.top - height
}
RightOf, Centre →
{ x: bb.right
, y: bb.y + (bb.height / 2.0) - (height / 2.0)
}
RightOf, Start →
{ x: bb.right
, y: bb.top
}
RightOf, End →
{ x: bb.right
, y: bb.bottom - height
}
LeftOf, Centre →
{ x: bb.left - width
, y: bb.top + (bb.height / 2.0) - (height / 2.0)
}
LeftOf, Start →
{ x: bb.left - width
, y: bb.top
}
LeftOf, End →
{ x: bb.left - width
, y: bb.bottom - height
}
Below, Centre →
{ x: bb.left + (bb.width / 2.0) - (width / 2.0)
, y: bb.bottom
}
Below, Start →
{ x: bb.left
, y: bb.bottom
}
Below, End →
{ x: bb.right - width
, y: bb.bottom
}
where
complete { x, y } = { x, y, width, height, left: x, top: y, right: x + width, bottom: y + height }
complete { x, y } =
{ x
, y
, width
, height
, left: x
, top: y
, right: x + width
, bottom: y + height
}

View File

@ -0,0 +1,83 @@
module Story.Yoga.Block.Atom.Checkbox
( default
, checkbox
, withLabel
)
-- (default, button, button2, test, customButton1, customButton2)
where
import Prelude hiding (div)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Fahrtwind (ignoreClicks, userSelectNone)
import Literals.Undefined (undefined)
import React.Basic (JSX, element, fragment)
import React.Basic.DOM as R
import React.Basic.Emotion as E
import React.Basic.Hooks (element) as React
import Storybook (Meta, Story, meta, metaDecorator, story)
import Storybook.Addon.Actions (action)
import Storybook.Addon.Docs.Types (LogAction(..), inferArgTypes)
import Untagged.Castable (cast)
import Untagged.Union (UndefinedOr)
import Yoga ((/>), (</*))
import Yoga.Block as Block
import Yoga.Block.Atom.Checkbox.View as Checkbox
import Yoga.Block.Container.Style (size)
import Yoga.Block.Container.Style as Styles
import Yoga.Block.Layout.Types (JustifyContent(..))
type Props =
{ checked :: UndefinedOr Boolean
, id :: String
, onChecked :: Boolean -> Effect Unit
}
default ∷ Meta Props
default = meta
{ title: "Atom/Checkbox"
, component: pure $ React.element Checkbox.rawComponent
, tags: [ "docsPage" ]
, decorators:
[ metaDecorator \storyFn ->
R.div_
[ element E.global { styles: Styles.global }
, storyFn
]
]
}
checkbox ∷ Story Props
checkbox = story args argTypes
where
args =
{ checked: cast undefined :: UndefinedOr Boolean
, id: "example"
, onChecked: LogAction \(x :: Boolean) -> "Checkbox is: " <> show x
}
argTypes = inferArgTypes args
-- # setType { children: "Array JSX" }
-- # setControl { ripple: ColorControl [ Color.white # withAlpha 0.2 ] }
-- # setRequired { buttonType: false }
-- # setRequired { buttonShape: false }
withLabel :: Effect JSX
withLabel = do
pure $ Block.cluster { space: size.xl, justifyContent: JEvenly }
[ Block.stack { space: E.str size.m } (toLabelled <$> [ "a", "b", "c" ])
, Block.stack { space: E.str size.m } (toReverseLabelled <$> [ "d", "e", "f" ])
]
where
toLabelled x =
Block.cluster { space: size.s }
[ R.label' </* { css: userSelectNone, htmlFor: x } /> [ R.text x ]
, Block.checkbox { id: x, onChecked: action ("clicked " <> show x) }
]
toReverseLabelled x =
Block.cluster { space: size.s }
[ Block.checkbox { id: x, onChecked: action ("clicked " <> show x) }
, R.label' </* { css: userSelectNone, htmlFor: x } /> [ R.text x ]
]

View File

@ -0,0 +1,53 @@
module Story.Yoga.Block.Atom.Icon.Story where
import Prelude
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import React.Basic (JSX, element, fragment)
import React.Basic as React
import React.Basic.DOM as R
import React.Basic.Emotion as E
import Storybook (Meta, meta, metaDecorator)
import Yoga ((</>))
import Yoga.Block.Atom.Icon as Icon
import Yoga.Block.Container.Style as Styles
import Yoga.Block.Icon.SVG as SVGIcon
default ∷ Meta { | Icon.Props }
default = meta
{ title: "Atom/Icon"
, component: (pure <<< React.element) Icon.component
, decorators:
[ metaDecorator \storyFn →
R.div_
[ element E.global { styles: Styles.global }
, storyFn
]
]
}
icon ∷ Effect JSX
icon = do
pure $
R.div_
[ R.h2_ [ R.text "Icon" ]
, icon SVGIcon.on
, icon SVGIcon.off
, icon SVGIcon.sun
, icon SVGIcon.moon
, icon SVGIcon.eyeClosed
, icon SVGIcon.eyeOpen
, icon SVGIcon.bin
, icon SVGIcon.cross
, icon SVGIcon.folder
, icon SVGIcon.key
, icon SVGIcon.magnifyingGlass
, icon SVGIcon.simpleKey
, icon SVGIcon.questionMark
, icon SVGIcon.plus
, icon SVGIcon.warn
]
where
icon svg = Icon.component </>
{ icon: svg, size: E.str "var(--s2)" }

View File

@ -0,0 +1,56 @@
module Story.Yoga.Block.Atom.Image.Story where
import Prelude
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import React.Basic (JSX, element, fragment)
import React.Basic as React
import React.Basic.DOM as R
import React.Basic.Emotion as E
import Record (merge)
import Storybook (Meta, meta, metaDecorator)
import Yoga ((</>))
import Yoga.Block.Atom.Image as Image
import Yoga.Block.Container.Style as Styles
default ∷ Meta { | Image.Props }
default = meta
{ title: "Atom/Image"
, component: (pure <<< React.element) Image.component
, decorators:
[ metaDecorator \storyFn →
R.div_
[ element E.global { styles: Styles.global }
, storyFn
]
]
}
image ∷ Effect JSX
image = do
pure $
R.div_
[ R.h2_ [ R.text "Image" ]
, Image.component </>
( baseProps # merge
{ sizes:
[ "(min-width: 2600px) 4160px"
, "(min-width: 2400px) 2400px"
, "(min-width: 640px) 640px"
, "100vw"
]
}
)
, Image.component </> (baseProps # merge { width: 200 })
]
where
baseProps =
{ src: imgName 640
, srcSet: widthImage <$> [ 320, 640, 2400, 4160 ]
, alt: "An image of a yoga mat"
}
imgName size = "/unsplash-mat-" <> show size <> ".jpg"
widthImage size = imgName size <> " " <> show size <> "w"

View File

@ -0,0 +1,76 @@
module Story.Yoga.Block.Atom.Modal.Story (default, modal) where
import Prelude
import Data.Monoid (guard)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
import Fahrtwind (background', mXY, pXY, roundedLg, textXl)
import React.Basic (JSX, element, fragment)
import React.Basic as React
import React.Basic.DOM as R
import React.Basic.Emotion as E
import React.Basic.Events (handler_)
import React.Basic.Hooks as React
import Record (merge)
import Storybook (Meta, meta, metaDecorator)
import Storybook.Addon.Actions (action)
import Yoga ((/>), (</*), (</>))
import Yoga.Block as Block
import Yoga.Block.Atom.Modal as Modal
import Yoga.Block.Container.Style (col, colour)
import Yoga.Block.Container.Style as Styles
import Yoga.Block.Icon.SVG as SVGIcon
import Yoga.Block.Layout.Types (JustifyContent(..))
default ∷ Meta Modal.Props
default = meta
{ title: "Atom/Modal"
, component: (pure <<< React.element) Modal.component
, decorators:
[ metaDecorator \storyFn →
fragment
[ element E.global { styles: Styles.global }
, storyFn
, Block.layer { id: "modals", zIndex: 10 }
, Block.layer { id: "clickaway", zIndex: 5 }
]
]
}
modal ∷ Effect JSX
modal = (_ $ unit) <$> React.component "ModalExample" \_ → React.do
open /\ setOpen ← React.useState' true
pure $
R.div_
[ R.h2_ [ R.text "Modal" ]
, Block.button { onClick: handler_ (setOpen true) }
[ R.text "Open modal" ]
, Block.modal
{ hide: setOpen false
, isVisible: open
, content: modalContent (setOpen false)
, allowClickAway: true
, clickAwayId: "clickaway"
, modalContainerId: "modals"
}
]
where
modalContent dismiss =
Block.box { css: background' col.backgroundBright4 <> roundedLg }
[ Block.stack { splitAfter: 2 }
[ Block.cluster { space: "0", justifyContent: JBetween }
[ R.h2' </* { css: textXl <> mXY 0 } /> [ R.text "Skandal!" ]
, Block.icon </> { icon: SVGIcon.cross }
]
, R.p_
[ R.text "This is the content of the modal it has a lot of text"
, R.text "This is the content of the modal it has a lot of text"
, R.text "This is the content of the modal it has a lot of text"
, R.text "This is the content of the modal it has a lot of text"
]
, Block.cluster { justifyContent: JEnd }
[ Block.button { onClick: handler_ dismiss } [ R.text "OK" ] ]
]
]

View File

@ -5,12 +5,16 @@ module Story.Yoga.Block.Atom.Layout
import Yoga.Prelude.Style
import Data.Tuple.Nested ((/\))
import Effect.Unsafe (unsafePerformEffect)
import Fahrtwind as F
import Fahrtwind as FW
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 Storybook (Meta, meta, metaDecorator)
import Yoga ((</>))
import Yoga.Block as Block
import Yoga.Block.Atom.Button.Types (ButtonShape(..), ButtonType(..))
import Yoga.Block.Container.Style as Styles
@ -21,7 +25,7 @@ default = meta
{ title: "layout"
, component: mempty
, decorators:
[ metaDecorator \storyFn ->
[ metaDecorator \storyFn
fragment
[ element E.global
{ styles: Styles.global
@ -36,72 +40,87 @@ bg = FW.background'
colBox co = Block.box { css: FW.roundedDefault <> bg co }
layout :: Effect JSX
layout = pure $ Block.box { css: heightFull <> background' col.backgroundBright3 }
[ Block.sidebar
{ sidebar: Block.stack
{ space: sizeStyle.xxs
, splitAfter: 1
mainContent = Block.stack
{ space: sizeStyle.s
, css: FW.background' col.backgroundLayer1
, splitAfter: 3
}
[ colBox col.backgroundLayer1
[ Block.cluster
{ space: size.m
, rowSpace: size.s
, justifyContent: JEnd
, alignItems: ACenter
}
[ Block.button
{ buttonShape: Flat
, css: widthFull
, backgroundCol: col.backgroundLayer4
, hoverBackgroundCol: col.backgroundLayer4
, textCol: str (colour.text)
{ buttonType: Primary
}
[ Block.box
{ css: textLeft <> widthFull
, padding: str "0"
}
[ R.text "Sidebar First" ]
]
, colBox col.backgroundLayer3 [ R.text "Sidebar Second" ]
[ R.text "Sign up" ]
, Block.button {} [ R.text "Sign in" ]
]
, space: "8px"
, sideWidth: size."3xl"
, css: heightFull
}
[ Block.stack
{ space: sizeStyle.s
, css: FW.background' col.backgroundLayer1
, splitAfter: 3
}
[ colBox col.backgroundLayer1
[ Block.cluster { space: size.m, rowSpace: size.s, justifyContent: JEnd, alignItems: ACenter }
[ Block.button
{ buttonType: Primary
}
[ R.text "Sign up" ]
, Block.button {} [ R.text "Sign in" ]
]
]
, Block.box_
[ Block.grid { min: "150px" }
[ colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
]
]
, Block.box_
[ Block.cluster { space: size.s, rowSpace: size.xs }
[ colBox col.backgroundLayer4 [ R.text "Child Row 2 1" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
]
]
, Block.box_
[ Block.switcher { rowGap: "8px" }
[ colBox col.backgroundLayer4 [ R.text "Switch me up" ]
, colBox col.backgroundLayer4 [ R.text "Switch me too" ]
, colBox col.backgroundLayer4 [ R.text "Switchy" ]
]
]
]
, Block.box_
[ Block.grid { min: "150px" }
[ colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
]
]
, Block.box_
[ Block.cluster { space: size.s, rowSpace: size.xs }
[ colBox col.backgroundLayer4 [ R.text "Child Row 2 1" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
, colBox col.backgroundLayer4 [ R.text "Child Row 2 2" ]
]
]
, Block.box_
[ Block.switcher { rowGap: "8px" }
[ colBox col.backgroundLayer4 [ R.text "Switch me up" ]
, colBox col.backgroundLayer4 [ R.text "Switch me too" ]
, colBox col.backgroundLayer4 [ R.text "Switchy" ]
]
]
]
leftSidebar = Block.sidebar
{ sidebar: Block.stack
{ space: sizeStyle.xxs
, splitAfter: 1
}
[ Block.button
{ buttonShape: Flat
, css: widthFull
, backgroundCol: col.backgroundLayer4
, hoverBackgroundCol: col.backgroundLayer5
, textCol: str (colour.text)
, ripple: colourWithAlpha.text 0.3
}
[ Block.box
{ css: textLeft <> widthFull
, padding: str "4px 0"
}
[ R.text "Sidebar First" ]
]
, colBox col.backgroundLayer3 [ R.text "Sidebar Second" ]
]
, space: "8px"
, sideWidth: size."3xl"
, css: heightFull
}
layout ∷ Effect JSX
layout = pure $ layoutCompo </> {}
layoutCompo =
unsafePerformEffect $ React.reactComponent "LayoutExample" \_ → React.do
modalVisible /\ setModalVisible ← React.useState false
pure $ Block.box
{ css: heightFull
<> background' col.backgroundBright3
}
[ leftSidebar [ mainContent ]
]

View File

@ -0,0 +1,90 @@
module Story.Yoga.Block.Molecule.Typeahead
( default
, typeahead
)
-- (default, button, button2, test, customButton1, customButton2)
where
import Prelude hiding (div)
import Data.Array as Array
import Data.Maybe (Maybe(..))
import Data.String as String
import Data.String.NonEmpty (nes)
import Effect (Effect)
import Fahrtwind as FW
import React.Basic (JSX, element)
import React.Basic.DOM as R
import React.Basic.Emotion as E
import React.Basic.Hooks (element) as React
import Storybook (Meta, meta, metaDecorator)
import Storybook.Addon.Actions (action)
import Type.Proxy (Proxy(..))
import Yoga ((</*>))
import Yoga.Block as Block
import Yoga.Block.Container.Style (sizeStyle)
import Yoga.Block.Container.Style as Styles
import Yoga.Block.Molecule.Typeahead.View (inputProps, mkTypeahead)
import Yoga.Block.Molecule.Typeahead.View as Typeahead
default ∷ Meta (Typeahead.PropsOld String)
default = meta
{ title: "Molecule/Typeahead"
, component: do
typeahead ←
Typeahead.mkDefaultArgs
{ suggestionToText: identity
, contextMenuLayerId: "ctx-menu"
} # mkTypeahead
pure $ React.element typeahead
, tags: [ "docsPage" ]
, decorators:
[ metaDecorator \storyFn →
R.div_
[ element E.global { styles: Styles.global }
, storyFn
, R.div' </*>
{ id: "ctx-menu"
, css: FW.positionFixed <> FW.widthFull <> FW.heightFull
<> FW.top 0
<> FW.left 0
<> FW.ignoreClicks
}
]
]
}
typeahead ∷ Effect JSX
typeahead = do
ta ←
Typeahead.mkDefaultArgs
{ suggestionToText: identity
, contextMenuLayerId: "ctx-menu"
} # mkTypeahead
pure $ React.element ta
{ onSelected: \s → do
pure { overrideInputValue: Nothing, dismiss: true }
, onRemoved: action "Removed"
, renderSuggestion: \s →
Block.box
{ padding: sizeStyle.s
, css: FW.textSm
}
[ R.text s ]
, loadSuggestions: \s →
pure $ pure $ Array.filter (String.contains (String.Pattern s))
[ "aardvark"
, "about"
, "after"
, "among"
, "again"
, "above"
, "along"
, "alone"
]
, onDismiss: action "dismissed" unit
, inputProps: inputProps
{ placeholder: "Heinz..."
, label: nes (Proxy ∷ _ "Test Typeahead")
}
}

File diff suppressed because it is too large Load Diff

10241
yarn.lock Normal file

File diff suppressed because it is too large Load Diff