@@ -3,7 +3,7 @@ module Yoga.Block.Atom.Popover.Story where
33import Prelude
44import Data.Array as Array
55import Data.Foldable (for_, traverse_)
6−import Data.Maybe (Maybe(..))
6+import Data.Maybe (Maybe(..), maybe)
77import Data.Monoid (guard)
88import Data.Nullable (null)
99import Data.String as String
@@ -13,13 +13,14 @@ import Data.Tuple.Nested ((/\))
1313import Effect (Effect)
1414import Effect.Uncurried (mkEffectFn1)
1515import Effect.Unsafe (unsafePerformEffect)
16+import Foreign.Object as Object
1617import Framer.Motion (animatePresence)
1718import Framer.Motion as Motion
18−import React.Basic (JSX, ReactComponent, element, elementKeyed, fragment)
19+import React.Basic (JSX, ReactComponent, element, fragment)
1920import React.Basic.DOM (CSS, css)
2021import React.Basic.DOM as R
2122import React.Basic.DOM.Events (preventDefault, targetValue)
22−import React.Basic.Emotion (str)
23+import React.Basic.Emotion (elementKeyed, str)
2324import React.Basic.Emotion as E
2425import React.Basic.Events (handler, handler_)
2526import React.Basic.Hooks (reactComponent, readRefMaybe)
@@ -30,7 +31,7 @@ import React.Basic.Popper.Placement.Types as Placement
3031import Unsafe.Coerce (unsafeCoerce)
3132import Web.HTML.HTMLElement (focus)
3233import Web.HTML.HTMLElement as HTMLElement
33−import Yoga ((/>), (</), (</*), (</>))
34+import Yoga ((/>), (</), (</>))
3435import Yoga.Block as Block
3536import Yoga.Block.Atom.Icon as Icon
3637import Yoga.Block.Atom.Input as Input
@@ -38,8 +39,9 @@ import Yoga.Block.Atom.Input.Types as HTMLInput
3839import Yoga.Block.Atom.Popover as Popover
3940import Yoga.Block.Container.Style (colour)
4041import Yoga.Block.Container.Style as Styles
42+import Yoga.Block.Hook.Key as KeyCode
43+import Yoga.Block.Hook.UseKeyDown (useKeyDown)
4144import Yoga.Block.Icon.SVG as Icons
42−import Yoga.Block.Internal.CSS (nest)
4345import Yoga.Block.Layout.Box as Box
4446import Yoga.Block.Layout.Stack as Stack
4547
@@ -78,19 +80,46 @@ popover = do
7880 referenceElement /\ setReferenceElement <- React.useState' nullRef
7981 inputRef <- React.useRef null
8082 text /\ setText <- React.useState' ""
83+ active /\ modifyActive <- React.useState Nothing
8184 clicking /\ setClicking <- React.useState' false
8285 visible /\ setVisible <- React.useState' false
83− selectedAuthors /\ modSelectedAuthors' <- React.useState []
86+ selectedAuthors /\ modSelectedAuthors <- React.useState []
8487 let
85− modSelectedAuthors f = modSelectedAuthors' (f >>> Array.sort)
88+ addToSelected a = modSelectedAuthors (_ `Array.union` [ a ])
8689 matchingAuthors =
8790 authors
8891 # (_ Array.\\ selectedAuthors)
8992 # Array.filter
90− ( \a ->
91− String.contains (String.Pattern (String.toLower text))
92− (String.toLower a)
93+ ( String.toLower
94+ >>> String.contains (String.Pattern (String.toLower text))
9395 )
96+ useKeyDown
97+ $ case _ of
98+ KeyCode.Down ->
99+ when visible do
100+ let first = Array.head matchingAuthors
101+ modifyActive case _ of
102+ Just elem ->
103+ maybe first Just do
104+ idx <- Array.findIndex (eq elem) matchingAuthors
105+ Array.index matchingAuthors (idx + 1)
106+ Nothing -> first
107+ KeyCode.Up ->
108+ when visible do
109+ let last = Array.last matchingAuthors
110+ modifyActive case _ of
111+ Just elem ->
112+ maybe last Just do
113+ idx <- Array.findIndex (eq elem) matchingAuthors
114+ Array.index matchingAuthors (idx - 1)
115+ Nothing -> last
116+ KeyCode.Return ->
117+ when visible do
118+ for_ active addToSelected
119+ KeyCode.Backspace ->
120+ when (text == "") do
121+ modSelectedAuthors (Array.dropEnd 1)
122+ _ -> mempty
94123 let
95124 inputWrapper = R.div' </ { ref: unsafeCoerce (mkEffectFn1 setReferenceElement) }
96125 pill t =
@@ -102,7 +131,8 @@ popover = do
102131 { fontSize: "var(--s-1)"
103132 , borderRadius: "var(--s-2)"
104133 , padding: "var(--s-2)"
105− , background: colour.highlightAlpha33
134+ , background: colour.highlightAlpha50
135+ , userSelect: "none"
106136 }
107137 , space: "4px"
108138 , justify: "flex-end"
@@ -133,6 +163,12 @@ popover = do
133163 }
134164 {}
135165 /> pills
166+ trailing =
167+ R.div'
168+ </ { style: css { fontSize: "var(--s1)", paddingBottom: "4px", userSelect: "none" }
169+ , onClick: handler_ (setVisible true)
170+ }
171+ /> [ R.text "▾" ]
136172 input =
137173 Input.component
138174 </> { id: "author"
@@ -150,19 +186,21 @@ popover = do
150186 else
151187 text
152188 , onChange: handler targetValue $ traverse_ setText
153− , onBlur: handler preventDefault (const (setVisible false))
189+ , onBlur: handler preventDefault (const $ unless clicking (setVisible false))
154190 , onFocus: handler_ (setVisible true)
155191 , leading
192+ , trailing
193+ , _data: Object.singleton "has-focus" (show visible)
156194 , autoComplete: "off"
157195 , css: E.css { width: E.percent 100.0 }
158196 }
159197 pop children =
160− elementKeyed motionPopover
198+ Motion.elementKeyed motionPopover
161199 $ Motion.motion
162200 { initial:
163− css { maxHeight: 0, opacity: 1.0 }
201+ css { maxHeight: 0, opacity: 0.0 }
164202 , exit:
165− css { maxHeight: 0, opacity: 1.0 }
203+ css { maxHeight: 0, opacity: 0.0 }
166204 , animate:
167205 css
168206 { maxHeight: 400
@@ -192,7 +230,7 @@ popover = do
192230 , padding: "0"
193231 }
194232 , key: "popover"
195− , children
233+ , children: [ R.ul' </ {} /> children ]
196234 }
197235 box =
198236 Box.component
@@ -209,33 +247,34 @@ popover = do
209247 , exit: Motion.exit containerVariant.exit
210248 }
211249 entry a =
212− Motion.div
213− </* { key: a
214− , variants: Motion.variants itemVariants
215− , css:
216− E.css
217− { "&:hover":
218− nest
219− { borderLeft: E.str $ "calc(var(--s-1) / 2) solid " <> colour.highlight
220− , paddingLeft: E.str "calc(var(--s-1) / 2)"
221− , background: E.str colour.backgroundLayer1
222− }
223− , padding: E.str "var(--s-1)"
224− , borderBottom: E.str $ "solid 1px " <> colour.backgroundLayer1
225− , background: E.str colour.backgroundLayer3
226− , margin: E.str "0"
227− , fontSize: E.str "calc(var(--s0) * 0.75)"
228− }
229− , className: "item"
230− , onClick:
231− handler_ do
232− maybeRefElem <- readRefMaybe inputRef
233− for_ (maybeRefElem >>= HTMLElement.fromNode) focus
234− modSelectedAuthors (_ `Array.union` [ a ])
235− , onMouseDown: handler_ (setClicking true)
236− , onMouseUp: handler_ (setClicking false)
237− }
238− /> [ R.text a ]
250+ elementKeyed Motion.li
251+ { key: a
252+ , variants: Motion.variants itemVariants
253+ , css:
254+ E.css
255+ { padding: E.str "var(--s-1)"
256+ , borderBottom: E.str $ "solid 1px " <> colour.backgroundLayer1
257+ , background: E.str colour.backgroundLayer3
258+ , margin: E.str "0"
259+ , fontSize: E.str "calc(var(--s0) * 0.75)"
260+ }
261+ <> ( guard (Just a == active)
262+ $ E.css
263+ { borderLeft: E.str $ "calc(var(--s-1) / 2) solid " <> colour.highlight
264+ , paddingLeft: E.str "calc(var(--s-1) / 2)"
265+ , background: E.str colour.backgroundLayer1
266+ }
267+ )
268+ , className: "item"
269+ , onClick:
270+ handler_ do
271+ maybeRefElem <- readRefMaybe inputRef
272+ addToSelected a
273+ for_ (maybeRefElem >>= HTMLElement.fromNode) focus
274+ , onMouseDown: handler_ (setClicking true)
275+ , onMouseUp: handler_ (setClicking false)
276+ , children: [ R.text a ]
277+ }
239278 pure
240279 $ fragment
241280 [ inputWrapper [ input ]