mirror of
https://github.com/rowtype-yoga/ry-blocks.git
synced 2024-10-26 22:01:39 +03:00
Hä
This commit is contained in:
parent
ca8b7f10ff
commit
c0c9705851
@ -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",
|
||||
|
@ -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
11
.tidyrc.json
Normal 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
BIN
assets/mat.webm
Normal file
Binary file not shown.
BIN
assets/unsplash-mat-2400.jpg
Normal file
BIN
assets/unsplash-mat-2400.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1014 KiB |
BIN
assets/unsplash-mat-320.jpg
Normal file
BIN
assets/unsplash-mat-320.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 129 KiB |
BIN
assets/unsplash-mat-4160.jpg
Normal file
BIN
assets/unsplash-mat-4160.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 MiB |
BIN
assets/unsplash-mat-640.jpg
Normal file
BIN
assets/unsplash-mat-640.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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 </*>
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
5
src/Yoga/Block/Atom/Checkbox.purs
Normal file
5
src/Yoga/Block/Atom/Checkbox.purs
Normal 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)
|
@ -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 </> {}
|
@ -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)
|
||||
|
||||
|
@ -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" }
|
||||
]
|
||||
]
|
||||
]
|
||||
|
@ -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)" }
|
||||
]
|
||||
]
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
5
src/Yoga/Block/Atom/Modal.purs
Normal file
5
src/Yoga/Block/Atom/Modal.purs
Normal 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)
|
@ -12,9 +12,4 @@ clickAway =
|
||||
<> acceptClicks
|
||||
|
||||
modal ∷ Style
|
||||
modal =
|
||||
positionFixed
|
||||
<> left' (50.0 # percent)
|
||||
<> top zero
|
||||
<> translate "-50%" "0"
|
||||
<> acceptClicks
|
||||
modal = acceptClicks
|
||||
|
@ -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 ]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
)
|
||||
)
|
||||
|
12
src/Yoga/Block/Quark/ClickAway/Style.purs
Normal file
12
src/Yoga/Block/Quark/ClickAway/Style.purs
Normal 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
|
34
src/Yoga/Block/Quark/ClickAway/View.purs
Normal file
34
src/Yoga/Block/Quark/ClickAway/View.purs
Normal 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
|
||||
}
|
||||
)
|
@ -1 +0,0 @@
|
||||
module Yoga.Block.Quark.Drip where
|
5
src/Yoga/Block/Quark/Layer.purs
Normal file
5
src/Yoga/Block/Quark/Layer.purs
Normal 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)
|
12
src/Yoga/Block/Quark/Layer/Style.purs
Normal file
12
src/Yoga/Block/Quark/Layer/Style.purs
Normal 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
|
24
src/Yoga/Block/Quark/Layer/View.purs
Normal file
24
src/Yoga/Block/Quark/Layer/View.purs
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
83
stories/Story/Yoga/Block/Atom/Checkbox.purs
Normal file
83
stories/Story/Yoga/Block/Atom/Checkbox.purs
Normal 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 ]
|
||||
]
|
53
stories/Story/Yoga/Block/Atom/Icon.purs
Normal file
53
stories/Story/Yoga/Block/Atom/Icon.purs
Normal 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)" }
|
56
stories/Story/Yoga/Block/Atom/Image.purs
Normal file
56
stories/Story/Yoga/Block/Atom/Image.purs
Normal 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"
|
76
stories/Story/Yoga/Block/Atom/Modal.purs
Normal file
76
stories/Story/Yoga/Block/Atom/Modal.purs
Normal 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" ] ]
|
||||
]
|
||||
]
|
@ -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 ]
|
||||
]
|
||||
|
90
stories/Story/Yoga/Block/Molecule/Typeahead.purs
Normal file
90
stories/Story/Yoga/Block/Molecule/Typeahead.purs
Normal 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")
|
||||
}
|
||||
}
|
10400
yarn-error.log
10400
yarn-error.log
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user