Skip to content

Commit 93882d8

Browse files
authored
[#120] pressing Esc should close modals (#121)
* WIP esc to close modal * windowEvent keyDown esc to close modal * cleanup * code review
1 parent 9046379 commit 93882d8

File tree

2 files changed

+84
-58
lines changed

2 files changed

+84
-58
lines changed

bower.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
"purescript-js-timers": "^4.0.1",
4545
"purescript-heterogeneous": ">=0.3.0 <0.5.0",
4646
"purescript-free": "^5.1.0",
47-
"purescript-colors": "^5.0.0"
47+
"purescript-colors": "^5.0.0",
48+
"purescript-web-uievents": "^2.0.0"
4849
},
4950
"devDependencies": {
5051
"purescript-debug": "^4.0.0",

src/Lumi/Components/Modal.purs

Lines changed: 82 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module Lumi.Components.Modal where
33
import Prelude
44

55
import Color (cssStringHSLA, hsla, toHSLA)
6-
import Data.Foldable (foldMap)
6+
import Data.Foldable (foldMap, for_)
77
import Data.Maybe (Maybe(..))
88
import Data.Monoid (guard)
99
import Data.Nullable (Nullable, toMaybe, toNullable)
@@ -18,12 +18,17 @@ import Lumi.Components.Link as Link
1818
import Lumi.Components.Size (Size(..))
1919
import Lumi.Components.Text (sectionHeader_, subsectionHeader, text)
2020
import Lumi.Components.ZIndex (ziModal)
21+
import Prim.Row (class Nub, class Union)
2122
import React.Basic (Component, JSX, ReactComponent, createComponent, element, empty, make, makeStateless, toReactComponent)
2223
import React.Basic.DOM as R
24+
import React.Basic.DOM.Components.GlobalEvents (windowEvent)
2325
import React.Basic.DOM.Events (currentTarget, stopPropagation, target)
2426
import React.Basic.Events as Events
2527
import Record (merge)
26-
import Prim.Row (class Nub, class Union)
28+
import Web.Event.Event (Event, EventType(..))
29+
import Web.Event.Event as E
30+
import Web.UIEvent.KeyboardEvent (fromEvent, key)
31+
2732

2833
foreign import toggleBodyClass :: EffectFn2 String Boolean Unit
2934

@@ -232,63 +237,80 @@ modalPortal = toReactComponent identity modalPortalComponent
232237
render { props, state } =
233238
if not props.modalOpen
234239
then empty
235-
else lumiModalContainer
236-
{ onClick: Events.handler (Events.merge { target, currentTarget })
237-
\{ target, currentTarget } -> do
240+
else windowEvent
241+
{ eventType: EventType "keydown"
242+
, options: { capture: false, once: false, passive: false }
243+
, handler: \e -> do
244+
keydownEventHandler e
245+
}
246+
$ lumiModalContainer
247+
{ onClick: Events.handler (Events.merge { target, currentTarget })
248+
\{ target, currentTarget } -> do
249+
props.requestClose
250+
closeModal
251+
, children:
252+
lumiModalOverlay
253+
{ "data-variant": props.variant
254+
, children:
255+
lumiModal
256+
{ "data-size": show props.size
257+
, onClick: Events.handler stopPropagation \_ -> pure unit
258+
, children:
259+
[ lumiModalHeader
260+
{ children:
261+
[ if props.variant == "dialog"
262+
then empty
263+
else lumiModalClose
264+
{ children: icon_ Remove
265+
, onClick: mkEffectFn1 \_ -> do
266+
props.requestClose
267+
closeModal
268+
}
269+
, if not null props.title
270+
then sectionHeader_ props.title
271+
else empty
272+
]
273+
}
274+
, lumiModalContent
275+
{ children: props.children
276+
, "data-internal-borders": props.internalBorders
277+
}
278+
, lumiModalFooter
279+
{ children:
280+
[ guard props.closeButton $
281+
Button.button Button.secondary
282+
{ title =
283+
case toMaybe props.onActionButtonClick of
284+
Nothing -> "Close"
285+
Just _ -> "Cancel"
286+
, onPress = mkEffectFn1 \_ -> do
287+
props.requestClose
288+
closeModal
289+
}
290+
, toMaybe props.onActionButtonClick # foldMap \actionFn ->
291+
Button.button Button.primary
292+
{ title = props.actionButtonTitle
293+
, buttonState = props.actionButtonState
294+
, onPress = mkEffectFn1 \_ -> actionFn
295+
, style = R.css { marginLeft: "12px" }
296+
}
297+
]
298+
}
299+
]
300+
}
301+
}
302+
}
303+
where
304+
keydownEventHandler :: Event -> Effect Unit
305+
keydownEventHandler e = do
306+
let mKey = eventKey e
307+
for_ mKey case _ of
308+
"Escape" -> do
309+
E.preventDefault e
310+
E.stopPropagation e
238311
props.requestClose
239312
closeModal
240-
, children:
241-
lumiModalOverlay
242-
{ "data-variant": props.variant
243-
, children:
244-
lumiModal
245-
{ "data-size": show props.size
246-
, onClick: Events.handler stopPropagation \_ -> pure unit
247-
, children:
248-
[ lumiModalHeader
249-
{ children:
250-
[ if props.variant == "dialog"
251-
then empty
252-
else lumiModalClose
253-
{ children: icon_ Remove
254-
, onClick: mkEffectFn1 \_ -> do
255-
props.requestClose
256-
closeModal
257-
}
258-
, if not null props.title
259-
then sectionHeader_ props.title
260-
else empty
261-
]
262-
}
263-
, lumiModalContent
264-
{ children: props.children
265-
, "data-internal-borders": props.internalBorders
266-
}
267-
, lumiModalFooter
268-
{ children:
269-
[ guard props.closeButton $
270-
Button.button Button.secondary
271-
{ title =
272-
case toMaybe props.onActionButtonClick of
273-
Nothing -> "Close"
274-
Just _ -> "Cancel"
275-
, onPress = mkEffectFn1 \_ -> do
276-
props.requestClose
277-
closeModal
278-
}
279-
, toMaybe props.onActionButtonClick # foldMap \actionFn ->
280-
Button.button Button.primary
281-
{ title = props.actionButtonTitle
282-
, buttonState = props.actionButtonState
283-
, onPress = mkEffectFn1 \_ -> actionFn
284-
, style = R.css { marginLeft: "12px" }
285-
}
286-
]
287-
}
288-
]
289-
}
290-
}
291-
}
313+
_ -> pure unit
292314

293315
lumiModalContainer = element (R.unsafeCreateDOMComponent "lumi-modal-container")
294316
lumiModalOverlay = element (R.unsafeCreateDOMComponent "lumi-modal-overlay")
@@ -466,3 +488,6 @@ styles = jss
466488
}
467489
where
468490
alpha a = toHSLA >>> \{ h, s, l } -> hsla h s l a
491+
492+
eventKey :: Event -> Maybe String
493+
eventKey e = map key (fromEvent e)

0 commit comments

Comments
 (0)