@@ -3,7 +3,7 @@ module Lumi.Components.Modal where
33import Prelude
44
55import Color (cssStringHSLA , hsla , toHSLA )
6- import Data.Foldable (foldMap )
6+ import Data.Foldable (foldMap , for_ )
77import Data.Maybe (Maybe (..))
88import Data.Monoid (guard )
99import Data.Nullable (Nullable , toMaybe , toNullable )
@@ -18,12 +18,17 @@ import Lumi.Components.Link as Link
1818import Lumi.Components.Size (Size (..))
1919import Lumi.Components.Text (sectionHeader_ , subsectionHeader , text )
2020import Lumi.Components.ZIndex (ziModal )
21+ import Prim.Row (class Nub , class Union )
2122import React.Basic (Component , JSX , ReactComponent , createComponent , element , empty , make , makeStateless , toReactComponent )
2223import React.Basic.DOM as R
24+ import React.Basic.DOM.Components.GlobalEvents (windowEvent )
2325import React.Basic.DOM.Events (currentTarget , stopPropagation , target )
2426import React.Basic.Events as Events
2527import 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
2833foreign 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