mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-24 08:53:33 +03:00
Merge remote-tracking branch 'origin/master' into callout
This commit is contained in:
commit
9c8ef672ca
1
elm.json
1
elm.json
@ -49,6 +49,7 @@
|
||||
"Nri.Ui.Modal.V4",
|
||||
"Nri.Ui.Modal.V5",
|
||||
"Nri.Ui.Modal.V6",
|
||||
"Nri.Ui.Modal.V7",
|
||||
"Nri.Ui.Outline.V2",
|
||||
"Nri.Ui.Page.V2",
|
||||
"Nri.Ui.Page.V3",
|
||||
|
381
src/Accessibility/Modal/Copy.elm
Normal file
381
src/Accessibility/Modal/Copy.elm
Normal file
@ -0,0 +1,381 @@
|
||||
module Accessibility.Modal.Copy exposing
|
||||
( Model, init, subscriptions
|
||||
, update, Msg, close, open
|
||||
, view
|
||||
, Attribute
|
||||
, multipleFocusableElementView, onlyFocusableElementView
|
||||
, autofocusOnLastElement
|
||||
, overlayColor, custom, titleStyles
|
||||
)
|
||||
|
||||
{-| COPIED from <https://package.elm-lang.org/packages/tesk9/modal/latest/> for
|
||||
clean monolith upgrades. Remove this and go back to using tesk9/modal when possible!
|
||||
|
||||
import Accessibility.Modal as Modal
|
||||
import Html.Styled exposing (..)
|
||||
import Html.Styled.Events exposing (onClick)
|
||||
|
||||
view : Html Modal.Msg
|
||||
view =
|
||||
Modal.view identity
|
||||
"Example modal"
|
||||
[ Modal.onlyFocusableElementView
|
||||
(\onlyFocusableElementAttributes ->
|
||||
div []
|
||||
[ text "Welcome to this modal! I'm so happy to have you here with me."
|
||||
, button
|
||||
(onClick Modal.close :: onlyFocusableElementAttributes)
|
||||
[ text "Close Modal" ]
|
||||
]
|
||||
)
|
||||
]
|
||||
modal
|
||||
|
||||
@docs Model, init, subscriptions
|
||||
@docs update, Msg, close, open
|
||||
@docs view
|
||||
|
||||
@docs Attribute
|
||||
@docs multipleFocusableElementView, onlyFocusableElementView
|
||||
@docs autofocusOnLastElement
|
||||
@docs overlayColor, custom, titleStyles
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled exposing (..)
|
||||
import Accessibility.Styled.Aria as Aria
|
||||
import Accessibility.Styled.Key as Key
|
||||
import Accessibility.Styled.Role as Role
|
||||
import Browser
|
||||
import Browser.Dom as Dom
|
||||
import Browser.Events
|
||||
import Css exposing (..)
|
||||
import Html.Styled as Root
|
||||
import Html.Styled.Attributes as Attributes exposing (css, id)
|
||||
import Html.Styled.Events exposing (onClick)
|
||||
import Task
|
||||
|
||||
|
||||
{-| -}
|
||||
type Model
|
||||
= Opened String
|
||||
| Closed
|
||||
|
||||
|
||||
{-| -}
|
||||
init : Model
|
||||
init =
|
||||
Closed
|
||||
|
||||
|
||||
type By
|
||||
= EscapeKey
|
||||
| OverlayClick
|
||||
| Other
|
||||
|
||||
|
||||
{-| -}
|
||||
type Msg
|
||||
= OpenModal String
|
||||
| CloseModal By
|
||||
| Focus String
|
||||
| Focused (Result Dom.Error ())
|
||||
|
||||
|
||||
{-| -}
|
||||
update : { dismissOnEscAndOverlayClick : Bool } -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update { dismissOnEscAndOverlayClick } msg model =
|
||||
case msg of
|
||||
OpenModal returnFocusTo ->
|
||||
( Opened returnFocusTo
|
||||
, Dom.focus autofocusId
|
||||
|> Task.onError (\_ -> Dom.focus firstId)
|
||||
|> Task.attempt Focused
|
||||
)
|
||||
|
||||
CloseModal by ->
|
||||
let
|
||||
closeModal returnFocusTo =
|
||||
( Closed, Task.attempt Focused (Dom.focus returnFocusTo) )
|
||||
in
|
||||
case ( model, by, dismissOnEscAndOverlayClick ) of
|
||||
( Opened returnFocusTo, _, True ) ->
|
||||
closeModal returnFocusTo
|
||||
|
||||
( Opened returnFocusTo, Other, False ) ->
|
||||
closeModal returnFocusTo
|
||||
|
||||
_ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
Focus id ->
|
||||
( model, Task.attempt Focused (Dom.focus id) )
|
||||
|
||||
Focused _ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
type Autofocus
|
||||
= Default
|
||||
| Last
|
||||
|
||||
|
||||
type alias Config msg =
|
||||
{ overlayColor : Color
|
||||
, wrapMsg : Msg -> msg
|
||||
, modalStyle : Style
|
||||
, titleString : String
|
||||
, titleStyles : List Style
|
||||
, autofocusOn : Autofocus
|
||||
, content :
|
||||
{ onlyFocusableElement : List (Accessibility.Styled.Attribute msg)
|
||||
, firstFocusableElement : List (Accessibility.Styled.Attribute msg)
|
||||
, lastFocusableElement : List (Accessibility.Styled.Attribute msg)
|
||||
, autofocusOn : Accessibility.Styled.Attribute msg
|
||||
}
|
||||
-> Html msg
|
||||
}
|
||||
|
||||
|
||||
defaults : (Msg -> msg) -> String -> Config msg
|
||||
defaults wrapMsg t =
|
||||
{ overlayColor = rgba 128 0 70 0.7
|
||||
, wrapMsg = wrapMsg
|
||||
, modalStyle =
|
||||
batch
|
||||
[ backgroundColor (rgb 255 255 255)
|
||||
, borderRadius (px 8)
|
||||
, border3 (px 2) solid (rgb 127 0 127)
|
||||
, margin2 (px 80) auto
|
||||
, padding (px 20)
|
||||
, maxWidth (px 600)
|
||||
, minHeight (vh 40)
|
||||
]
|
||||
, titleString = t
|
||||
, titleStyles = []
|
||||
, autofocusOn = Default
|
||||
, content = \_ -> text ""
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type Attribute msg
|
||||
= Attribute (Config msg -> Config msg)
|
||||
|
||||
|
||||
{-| -}
|
||||
overlayColor : Color -> Attribute msg
|
||||
overlayColor color =
|
||||
Attribute (\config -> { config | overlayColor = color })
|
||||
|
||||
|
||||
{-| -}
|
||||
title : String -> Attribute msg
|
||||
title t =
|
||||
Attribute (\config -> { config | titleString = t })
|
||||
|
||||
|
||||
{-| -}
|
||||
titleStyles : List Style -> Attribute msg
|
||||
titleStyles styles =
|
||||
Attribute (\config -> { config | titleStyles = styles })
|
||||
|
||||
|
||||
{-| -}
|
||||
custom : List Style -> Attribute msg
|
||||
custom styles =
|
||||
Attribute (\config -> { config | modalStyle = batch styles })
|
||||
|
||||
|
||||
{-| -}
|
||||
autofocusOnLastElement : Attribute msg
|
||||
autofocusOnLastElement =
|
||||
Attribute (\config -> { config | autofocusOn = Last })
|
||||
|
||||
|
||||
{-| -}
|
||||
onlyFocusableElementView : (List (Accessibility.Styled.Attribute msg) -> Html msg) -> Attribute msg
|
||||
onlyFocusableElementView v =
|
||||
Attribute (\config -> { config | content = \{ onlyFocusableElement } -> v onlyFocusableElement })
|
||||
|
||||
|
||||
{-| -}
|
||||
multipleFocusableElementView :
|
||||
({ firstFocusableElement : List (Accessibility.Styled.Attribute msg)
|
||||
, lastFocusableElement : List (Accessibility.Styled.Attribute msg)
|
||||
, autofocusElement : Accessibility.Styled.Attribute msg
|
||||
}
|
||||
-> Html msg
|
||||
)
|
||||
-> Attribute msg
|
||||
multipleFocusableElementView v =
|
||||
Attribute
|
||||
(\config ->
|
||||
{ config
|
||||
| content =
|
||||
\{ firstFocusableElement, lastFocusableElement, autofocusOn } ->
|
||||
v
|
||||
{ firstFocusableElement = firstFocusableElement
|
||||
, lastFocusableElement = lastFocusableElement
|
||||
, autofocusElement = autofocusOn
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
view :
|
||||
(Msg -> msg)
|
||||
-> String
|
||||
-> List (Attribute msg)
|
||||
-> Model
|
||||
-> Html msg
|
||||
view wrapMsg ti attributes model =
|
||||
let
|
||||
config =
|
||||
List.foldl (\(Attribute f) acc -> f acc) (defaults wrapMsg ti) attributes
|
||||
in
|
||||
case model of
|
||||
Opened _ ->
|
||||
div
|
||||
[ css
|
||||
[ position fixed
|
||||
, top zero
|
||||
, left zero
|
||||
, width (pct 100)
|
||||
, height (pct 100)
|
||||
]
|
||||
]
|
||||
[ viewBackdrop config
|
||||
, div
|
||||
[ css [ position relative, config.modalStyle ] ]
|
||||
[ viewModal config ]
|
||||
, Root.node "style" [] [ Root.text "body {overflow: hidden;} " ]
|
||||
]
|
||||
|
||||
Closed ->
|
||||
text ""
|
||||
|
||||
|
||||
viewBackdrop :
|
||||
{ a | wrapMsg : Msg -> msg, overlayColor : Color }
|
||||
-> Html msg
|
||||
viewBackdrop config =
|
||||
Root.div
|
||||
-- We use Root html here in order to allow clicking to exit out of
|
||||
-- the overlay. This behavior is available to non-mouse users as
|
||||
-- well via the ESC key, so imo it's fine to have this div
|
||||
-- be clickable but not focusable.
|
||||
[ css
|
||||
[ position absolute
|
||||
, width (pct 100)
|
||||
, height (pct 100)
|
||||
, backgroundColor config.overlayColor
|
||||
]
|
||||
, onClick (config.wrapMsg (CloseModal OverlayClick))
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
viewModal : Config msg -> Html msg
|
||||
viewModal config =
|
||||
section
|
||||
[ Role.dialog
|
||||
, Aria.labeledBy modalTitleId
|
||||
]
|
||||
[ h1 [ id modalTitleId, css config.titleStyles ] [ text config.titleString ]
|
||||
, config.content
|
||||
(case config.autofocusOn of
|
||||
Last ->
|
||||
{ onlyFocusableElement =
|
||||
[ Key.onKeyDown
|
||||
[ Key.tabBack (Focus firstId)
|
||||
, Key.tab (Focus firstId)
|
||||
]
|
||||
, id firstId
|
||||
]
|
||||
|> List.map (Attributes.map config.wrapMsg)
|
||||
, firstFocusableElement =
|
||||
[ Key.onKeyDown [ Key.tabBack (Focus autofocusId) ]
|
||||
, id firstId
|
||||
]
|
||||
|> List.map (Attributes.map config.wrapMsg)
|
||||
, lastFocusableElement =
|
||||
[ Key.onKeyDown [ Key.tab (Focus firstId) ]
|
||||
, id autofocusId
|
||||
]
|
||||
|> List.map (Attributes.map config.wrapMsg)
|
||||
, autofocusOn =
|
||||
id autofocusId
|
||||
|> Attributes.map config.wrapMsg
|
||||
}
|
||||
|
||||
_ ->
|
||||
{ onlyFocusableElement =
|
||||
[ Key.onKeyDown
|
||||
[ Key.tabBack (Focus firstId)
|
||||
, Key.tab (Focus firstId)
|
||||
]
|
||||
, id firstId
|
||||
]
|
||||
|> List.map (Attributes.map config.wrapMsg)
|
||||
, firstFocusableElement =
|
||||
[ Key.onKeyDown [ Key.tabBack (Focus lastId) ]
|
||||
, id firstId
|
||||
]
|
||||
|> List.map (Attributes.map config.wrapMsg)
|
||||
, lastFocusableElement =
|
||||
[ Key.onKeyDown [ Key.tab (Focus firstId) ]
|
||||
, id lastId
|
||||
]
|
||||
|> List.map (Attributes.map config.wrapMsg)
|
||||
, autofocusOn =
|
||||
id autofocusId
|
||||
|> Attributes.map config.wrapMsg
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
modalTitleId : String
|
||||
modalTitleId =
|
||||
"modal__title"
|
||||
|
||||
|
||||
firstId : String
|
||||
firstId =
|
||||
"modal__first-focusable-element"
|
||||
|
||||
|
||||
lastId : String
|
||||
lastId =
|
||||
"modal__last-focusable-element"
|
||||
|
||||
|
||||
autofocusId : String
|
||||
autofocusId =
|
||||
"modal__autofocus-element"
|
||||
|
||||
|
||||
{-| Pass the id of the element that should receive focus when the modal closes.
|
||||
-}
|
||||
open : String -> Msg
|
||||
open =
|
||||
OpenModal
|
||||
|
||||
|
||||
{-| -}
|
||||
close : Msg
|
||||
close =
|
||||
CloseModal Other
|
||||
|
||||
|
||||
{-| -}
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
case model of
|
||||
Opened _ ->
|
||||
Browser.Events.onKeyDown (Key.escape (CloseModal EscapeKey))
|
||||
|
||||
Closed ->
|
||||
Sub.none
|
352
src/Nri/Ui/Modal/V7.elm
Normal file
352
src/Nri/Ui/Modal/V7.elm
Normal file
@ -0,0 +1,352 @@
|
||||
module Nri.Ui.Modal.V7 exposing
|
||||
( Model, init
|
||||
, Msg, update, subscriptions
|
||||
, open, close
|
||||
, info, warning
|
||||
, viewContent, viewFooter
|
||||
, Attribute
|
||||
, multipleFocusableElementView, onlyFocusableElementView
|
||||
, autofocusOnLastElement
|
||||
, closeButton
|
||||
)
|
||||
|
||||
{-| Changes from V6:
|
||||
|
||||
- Modal starts a new stacking context, to prevent non-normal-flow elements from showing through the backdrop
|
||||
- Scrollable content shows a shadow
|
||||
|
||||
```
|
||||
import Html.Styled exposing (..)
|
||||
import Nri.Ui.Button.V9 as Button
|
||||
import Nri.Ui.Modal.V7 as Modal
|
||||
|
||||
type Msg
|
||||
= ModalMsg Modal.Msg
|
||||
| DoSomthing
|
||||
|
||||
view : Modal.Model -> Html Msg
|
||||
view state =
|
||||
Modal.info
|
||||
{ title = "Modal Header"
|
||||
, visibleTitle = True
|
||||
, wrapMsg = ModalMsg
|
||||
}
|
||||
[ Modal.onlyFocusableElementView
|
||||
(\{ onlyFocusableElement } ->
|
||||
div []
|
||||
[ Modal.viewContent [ text "Content goes here!" ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Continue"
|
||||
[ Button.primary
|
||||
, Button.onClick DoSomthing
|
||||
, Button.custom onlyFocusableElement
|
||||
]
|
||||
, text "`onlyFocusableElement` will trap the focus on the 'Continue' button."
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
state
|
||||
|
||||
subscriptions : Modal.Model -> Sub Msg
|
||||
subscriptions state =
|
||||
Modal.subscriptions state
|
||||
|
||||
view init
|
||||
--> text "" -- a closed modal
|
||||
```
|
||||
|
||||
|
||||
## State and updates
|
||||
|
||||
@docs Model, init
|
||||
@docs Msg, update, subscriptions
|
||||
|
||||
@docs open, close
|
||||
|
||||
|
||||
## Views
|
||||
|
||||
|
||||
### Modals
|
||||
|
||||
@docs info, warning
|
||||
|
||||
|
||||
### View containers
|
||||
|
||||
@docs viewContent, viewFooter
|
||||
|
||||
|
||||
### Attributes
|
||||
|
||||
@docs Attribute
|
||||
@docs multipleFocusableElementView, onlyFocusableElementView
|
||||
@docs autofocusOnLastElement
|
||||
|
||||
|
||||
## X icon
|
||||
|
||||
@docs closeButton
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Modal.Copy as Modal
|
||||
import Accessibility.Style
|
||||
import Accessibility.Styled as Html exposing (..)
|
||||
import Accessibility.Styled.Widget as Widget
|
||||
import Color
|
||||
import Color.Transparent
|
||||
import Css
|
||||
import Css.Global
|
||||
import Html as Root
|
||||
import Html.Attributes exposing (style)
|
||||
import Html.Styled.Attributes as Attributes exposing (css)
|
||||
import Html.Styled.Events exposing (onClick)
|
||||
import Nri.Ui
|
||||
import Nri.Ui.Colors.Extra
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.SpriteSheet
|
||||
import Nri.Ui.Svg.V1
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Model =
|
||||
Modal.Model
|
||||
|
||||
|
||||
{-| -}
|
||||
init : Model
|
||||
init =
|
||||
Modal.init
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Msg =
|
||||
Modal.Msg
|
||||
|
||||
|
||||
{-| Include the subscription if you want the modal to dismiss on `Esc`.
|
||||
-}
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions =
|
||||
Modal.subscriptions
|
||||
|
||||
|
||||
{-| -}
|
||||
update : { dismissOnEscAndOverlayClick : Bool } -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update config msg model =
|
||||
Modal.update config msg model
|
||||
|
||||
|
||||
{-| -}
|
||||
close : Msg
|
||||
close =
|
||||
Modal.close
|
||||
|
||||
|
||||
{-| Pass the id of the element that focus should return to when the modal closes.
|
||||
-}
|
||||
open : String -> Msg
|
||||
open =
|
||||
Modal.open
|
||||
|
||||
|
||||
{-| -}
|
||||
info :
|
||||
{ visibleTitle : Bool
|
||||
, title : String
|
||||
, wrapMsg : Msg -> msg
|
||||
}
|
||||
-> List (Modal.Attribute msg)
|
||||
-> Model
|
||||
-> Html msg
|
||||
info config model =
|
||||
view { overlayColor = Colors.navy, titleColor = Colors.navy } config model
|
||||
|
||||
|
||||
{-| -}
|
||||
warning :
|
||||
{ visibleTitle : Bool
|
||||
, title : String
|
||||
, wrapMsg : Msg -> msg
|
||||
}
|
||||
-> List (Modal.Attribute msg)
|
||||
-> Model
|
||||
-> Html msg
|
||||
warning config model =
|
||||
view { overlayColor = Colors.gray20, titleColor = Colors.red } config model
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Attribute msg =
|
||||
Modal.Attribute msg
|
||||
|
||||
|
||||
{-| -}
|
||||
autofocusOnLastElement : Modal.Attribute msg
|
||||
autofocusOnLastElement =
|
||||
Modal.autofocusOnLastElement
|
||||
|
||||
|
||||
{-| -}
|
||||
multipleFocusableElementView :
|
||||
({ firstFocusableElement : List (Html.Attribute msg)
|
||||
, lastFocusableElement : List (Html.Attribute msg)
|
||||
, autofocusElement : Html.Attribute msg
|
||||
}
|
||||
-> Html msg
|
||||
)
|
||||
-> Modal.Attribute msg
|
||||
multipleFocusableElementView =
|
||||
Modal.multipleFocusableElementView
|
||||
|
||||
|
||||
{-| -}
|
||||
onlyFocusableElementView : (List (Html.Attribute msg) -> Html msg) -> Modal.Attribute msg
|
||||
onlyFocusableElementView =
|
||||
Modal.onlyFocusableElementView
|
||||
|
||||
|
||||
view :
|
||||
{ overlayColor : Css.Color, titleColor : Css.Color }
|
||||
->
|
||||
{ visibleTitle : Bool
|
||||
, title : String
|
||||
, wrapMsg : Msg -> msg
|
||||
}
|
||||
-> List (Modal.Attribute msg)
|
||||
-> Model
|
||||
-> Html msg
|
||||
view { overlayColor, titleColor } config attributes model =
|
||||
Modal.view config.wrapMsg
|
||||
config.title
|
||||
([ Modal.overlayColor (Nri.Ui.Colors.Extra.withAlpha 0.9 overlayColor)
|
||||
, Modal.titleStyles
|
||||
(if config.visibleTitle then
|
||||
titleStyles titleColor
|
||||
|
||||
else
|
||||
invisibleTitleStyles
|
||||
)
|
||||
, Modal.custom modalStyles
|
||||
]
|
||||
++ attributes
|
||||
)
|
||||
model
|
||||
|> List.singleton
|
||||
|> div [ css [ Css.position Css.relative, Css.zIndex (Css.int 1) ] ]
|
||||
|
||||
|
||||
modalStyles : List Css.Style
|
||||
modalStyles =
|
||||
[ Css.property "width" "600px"
|
||||
, Css.property "padding" "40px 0 40px 0"
|
||||
, Css.property "margin" "75px auto"
|
||||
, Css.property "background-color" ((Color.toRGBString << Nri.Ui.Colors.Extra.fromCssColor) Colors.white)
|
||||
, Css.property "border-radius" "20px"
|
||||
, Css.property "box-shadow" "0 1px 10px 0 rgba(0, 0, 0, 0.35)"
|
||||
, Css.property "position" "relative" -- required for closeButtonContainer
|
||||
]
|
||||
|
||||
|
||||
titleStyles : Css.Color -> List Css.Style
|
||||
titleStyles color =
|
||||
[ Fonts.baseFont
|
||||
, Css.property "font-weight" "700"
|
||||
, Css.property "line-height" "27px"
|
||||
, Css.property "margin" "0 49px"
|
||||
, Css.property "font-size" "20px"
|
||||
, Css.property "text-align" "center"
|
||||
, Css.property "color" ((Color.toRGBString << Nri.Ui.Colors.Extra.fromCssColor) color)
|
||||
]
|
||||
|
||||
|
||||
invisibleTitleStyles : List Css.Style
|
||||
invisibleTitleStyles =
|
||||
[ Css.property "property" "clip rect(1px, 1px, 1px, 1px)"
|
||||
, Css.property "position" "absolute"
|
||||
, Css.property "height" "1px"
|
||||
, Css.property "width" "1px"
|
||||
, Css.property "overflow" "hidden"
|
||||
, Css.property "margin" "-1px"
|
||||
, Css.property "padding" "0"
|
||||
, Css.property "border" "0"
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
viewContent : List (Html msg) -> Html msg
|
||||
viewContent =
|
||||
Nri.Ui.styled div
|
||||
"modal-content"
|
||||
[ Css.overflowY Css.auto
|
||||
, Css.minHeight (Css.px 150)
|
||||
, Css.maxHeight (Css.calc (Css.vh 100) Css.minus (Css.px 360))
|
||||
, Css.padding2 (Css.px 30) (Css.px 40)
|
||||
, Css.width (Css.pct 100)
|
||||
, Css.boxSizing Css.borderBox
|
||||
|
||||
-- Shadows for indicating that the content is scrollable
|
||||
, Css.property "background"
|
||||
"""
|
||||
/* TOP shadow */
|
||||
|
||||
top linear-gradient(to top, rgb(255, 255, 255), rgb(255, 255, 255)) local,
|
||||
top linear-gradient(to top, rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.15)) scroll,
|
||||
|
||||
/* BOTTOM shadow */
|
||||
|
||||
bottom linear-gradient(to bottom, rgb(255, 255, 255), rgb(255, 255, 255)) local,
|
||||
bottom linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.15)) scroll
|
||||
"""
|
||||
, Css.backgroundSize2 (Css.pct 100) (Css.px 10)
|
||||
, Css.backgroundRepeat Css.noRepeat
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
{-| -}
|
||||
viewFooter : List (Html msg) -> Html msg
|
||||
viewFooter =
|
||||
Nri.Ui.styled div
|
||||
"modal-footer"
|
||||
[ Css.alignItems Css.center
|
||||
, Css.displayFlex
|
||||
, Css.flexDirection Css.column
|
||||
, Css.flexGrow (Css.int 2)
|
||||
, Css.flexWrap Css.noWrap
|
||||
, Css.margin4 (Css.px 20) Css.zero Css.zero Css.zero
|
||||
, Css.width (Css.pct 100)
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
|
||||
--BUTTONS
|
||||
|
||||
|
||||
{-| -}
|
||||
closeButton : (Msg -> msg) -> List (Html.Attribute msg) -> Html msg
|
||||
closeButton wrapMsg focusableElementAttrs =
|
||||
Nri.Ui.styled button
|
||||
"close-button-container"
|
||||
[ Css.position Css.absolute
|
||||
, Css.top Css.zero
|
||||
, Css.right Css.zero
|
||||
, Css.padding (Css.px 25)
|
||||
, Css.borderWidth Css.zero
|
||||
, Css.width (Css.px 75)
|
||||
, Css.backgroundColor Css.transparent
|
||||
, Css.cursor Css.pointer
|
||||
, Css.color Colors.azure
|
||||
, Css.hover [ Css.color Colors.azureDark ]
|
||||
, Css.property "transition" "color 0.1s"
|
||||
]
|
||||
(Widget.label "Close modal"
|
||||
:: Attributes.map wrapMsg (onClick Modal.close)
|
||||
:: focusableElementAttrs
|
||||
)
|
||||
[ Nri.Ui.Svg.V1.toHtml Nri.Ui.SpriteSheet.xSvg
|
||||
]
|
@ -6,7 +6,7 @@ module Examples.Modal exposing (Msg, State, example, init, update, subscriptions
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled as Html exposing (Html, div, h3, h4, p, text)
|
||||
import Accessibility.Styled as Html exposing (Html, div, h3, h4, p, span, text)
|
||||
import Css exposing (..)
|
||||
import Css.Global
|
||||
import Html as Root
|
||||
@ -14,8 +14,10 @@ import Html.Styled.Attributes exposing (css)
|
||||
import ModuleExample exposing (Category(..), ModuleExample)
|
||||
import Nri.Ui.Button.V9 as Button
|
||||
import Nri.Ui.Checkbox.V5 as Checkbox
|
||||
import Nri.Ui.ClickableText.V3 as ClickableText
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Modal.V6 as Modal
|
||||
import Nri.Ui.Modal.V7 as Modal
|
||||
import Nri.Ui.Text.V4 as Text
|
||||
|
||||
|
||||
{-| -}
|
||||
@ -27,6 +29,7 @@ type alias State =
|
||||
, showContinue : Bool
|
||||
, showSecondary : Bool
|
||||
, dismissOnEscAndOverlayClick : Bool
|
||||
, longContent : Bool
|
||||
}
|
||||
|
||||
|
||||
@ -38,18 +41,20 @@ init =
|
||||
, visibleTitle = True
|
||||
, showX = True
|
||||
, showContinue = True
|
||||
, showSecondary = False
|
||||
, showSecondary = True
|
||||
, dismissOnEscAndOverlayClick = True
|
||||
, longContent = True
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
example : (Msg -> msg) -> State -> ModuleExample msg
|
||||
example parentMessage state =
|
||||
{ name = "Nri.Ui.Modal.V6"
|
||||
{ name = "Nri.Ui.Modal.V7"
|
||||
, category = Modals
|
||||
, content =
|
||||
[ Button.button "Launch Info Modal"
|
||||
[ viewSettings state
|
||||
, Button.button "Launch Info Modal"
|
||||
[ Button.onClick (InfoModalMsg (Modal.open "launch-info-modal"))
|
||||
, Button.custom
|
||||
[ Html.Styled.Attributes.id "launch-info-modal"
|
||||
@ -68,23 +73,15 @@ example parentMessage state =
|
||||
{ title = "Modal.info"
|
||||
, visibleTitle = state.visibleTitle
|
||||
, wrapMsg = InfoModalMsg
|
||||
, content =
|
||||
viewContent state
|
||||
InfoModalMsg
|
||||
Button.primary
|
||||
Button.secondary
|
||||
}
|
||||
(viewContent state InfoModalMsg Button.primary)
|
||||
state.infoModal
|
||||
, Modal.warning
|
||||
{ title = "Modal.warning"
|
||||
, visibleTitle = state.visibleTitle
|
||||
, wrapMsg = WarningModalMsg
|
||||
, content =
|
||||
viewContent state
|
||||
WarningModalMsg
|
||||
Button.danger
|
||||
Button.secondary
|
||||
}
|
||||
(viewContent state WarningModalMsg Button.danger)
|
||||
state.warningModal
|
||||
]
|
||||
|> List.map (Html.map parentMessage)
|
||||
@ -95,105 +92,179 @@ viewContent :
|
||||
State
|
||||
-> (Modal.Msg -> Msg)
|
||||
-> Button.Attribute Msg
|
||||
-> Button.Attribute Msg
|
||||
-> Modal.FocusableElementAttrs Msg
|
||||
-> Html Msg
|
||||
viewContent state wrapMsg firstButtonStyle secondButtonStyle focusableElementAttrs =
|
||||
-> List (Modal.Attribute Msg)
|
||||
viewContent state wrapMsg firstButtonStyle =
|
||||
case ( state.showX, state.showContinue, state.showSecondary ) of
|
||||
( True, True, True ) ->
|
||||
div []
|
||||
[ Modal.closeButton wrapMsg focusableElementAttrs.firstFocusableElement
|
||||
, Modal.viewContent [ viewSettings state ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Continue"
|
||||
[ firstButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
[ Modal.multipleFocusableElementView
|
||||
(\focusableElementAttrs ->
|
||||
div []
|
||||
[ Modal.closeButton wrapMsg focusableElementAttrs.firstFocusableElement
|
||||
, Modal.viewContent [ viewModalContent state.longContent ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Continue"
|
||||
[ firstButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.large
|
||||
, Button.custom [ focusableElementAttrs.autofocusElement ]
|
||||
]
|
||||
, ClickableText.button "Close"
|
||||
[ ClickableText.onClick ForceClose
|
||||
, ClickableText.large
|
||||
, ClickableText.custom
|
||||
(css [ Css.marginTop (Css.px 20) ]
|
||||
:: focusableElementAttrs.lastFocusableElement
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
, Button.button "Close"
|
||||
[ secondButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.custom focusableElementAttrs.lastFocusableElement
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
( True, False, True ) ->
|
||||
div []
|
||||
[ Modal.closeButton wrapMsg focusableElementAttrs.firstFocusableElement
|
||||
, Modal.viewContent [ viewSettings state ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Close"
|
||||
[ secondButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.custom focusableElementAttrs.lastFocusableElement
|
||||
[ Modal.multipleFocusableElementView
|
||||
(\focusableElementAttrs ->
|
||||
div []
|
||||
[ Modal.closeButton wrapMsg focusableElementAttrs.firstFocusableElement
|
||||
, Modal.viewContent [ viewModalContent state.longContent ]
|
||||
, Modal.viewFooter
|
||||
[ ClickableText.button "Close"
|
||||
[ ClickableText.onClick ForceClose
|
||||
, ClickableText.large
|
||||
, ClickableText.custom
|
||||
(css [ Css.marginTop (Css.px 20) ]
|
||||
:: focusableElementAttrs.lastFocusableElement
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
( True, False, False ) ->
|
||||
div []
|
||||
[ Modal.closeButton wrapMsg focusableElementAttrs.firstFocusableElement
|
||||
, Modal.viewContent [ viewSettings state ]
|
||||
]
|
||||
[ Modal.multipleFocusableElementView
|
||||
(\focusableElementAttrs ->
|
||||
div []
|
||||
[ Modal.closeButton wrapMsg focusableElementAttrs.firstFocusableElement
|
||||
, Modal.viewContent [ viewModalContent state.longContent ]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
( True, True, False ) ->
|
||||
div []
|
||||
[ Modal.closeButton wrapMsg focusableElementAttrs.firstFocusableElement
|
||||
, Modal.viewContent [ viewSettings state ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Continue"
|
||||
[ firstButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.custom focusableElementAttrs.lastFocusableElement
|
||||
[ Modal.autofocusOnLastElement
|
||||
, Modal.multipleFocusableElementView
|
||||
(\focusableElementAttrs ->
|
||||
div []
|
||||
[ Modal.closeButton wrapMsg focusableElementAttrs.firstFocusableElement
|
||||
, Modal.viewContent [ viewModalContent state.longContent ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Continue"
|
||||
[ firstButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.custom focusableElementAttrs.lastFocusableElement
|
||||
, Button.large
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
( False, True, True ) ->
|
||||
div []
|
||||
[ Modal.viewContent [ viewSettings state ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Continue"
|
||||
[ firstButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.custom focusableElementAttrs.firstFocusableElement
|
||||
[ Modal.multipleFocusableElementView
|
||||
(\focusableElementAttrs ->
|
||||
div []
|
||||
[ Modal.viewContent [ viewModalContent state.longContent ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Continue"
|
||||
[ firstButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.custom focusableElementAttrs.firstFocusableElement
|
||||
, Button.large
|
||||
]
|
||||
, ClickableText.button "Close"
|
||||
[ ClickableText.onClick ForceClose
|
||||
, ClickableText.large
|
||||
, ClickableText.custom
|
||||
(css [ Css.marginTop (Css.px 20) ]
|
||||
:: focusableElementAttrs.lastFocusableElement
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
, Button.button "Close"
|
||||
[ secondButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.custom focusableElementAttrs.lastFocusableElement
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
( False, False, True ) ->
|
||||
div []
|
||||
[ Modal.viewContent [ viewSettings state ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Close"
|
||||
[ secondButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.custom focusableElementAttrs.lastFocusableElement
|
||||
[ Modal.autofocusOnLastElement
|
||||
, Modal.multipleFocusableElementView
|
||||
(\focusableElementAttrs ->
|
||||
div []
|
||||
[ Modal.viewContent [ viewModalContent state.longContent ]
|
||||
, Modal.viewFooter
|
||||
[ ClickableText.button "Close"
|
||||
[ ClickableText.onClick ForceClose
|
||||
, ClickableText.large
|
||||
, ClickableText.custom
|
||||
(css
|
||||
[ Css.marginTop
|
||||
(Css.px 20)
|
||||
]
|
||||
:: focusableElementAttrs.lastFocusableElement
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
( False, True, False ) ->
|
||||
div []
|
||||
[ Modal.viewContent [ viewSettings state ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Continue"
|
||||
[ firstButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.custom focusableElementAttrs.lastFocusableElement
|
||||
[ Modal.autofocusOnLastElement
|
||||
, Modal.multipleFocusableElementView
|
||||
(\focusableElementAttrs ->
|
||||
div []
|
||||
[ Modal.viewContent [ viewModalContent state.longContent ]
|
||||
, Modal.viewFooter
|
||||
[ Button.button "Continue"
|
||||
[ firstButtonStyle
|
||||
, Button.onClick ForceClose
|
||||
, Button.custom [ focusableElementAttrs.autofocusElement ]
|
||||
, Button.large
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
( False, False, False ) ->
|
||||
div []
|
||||
[ Modal.viewContent [ viewSettings state ]
|
||||
]
|
||||
[ Modal.multipleFocusableElementView
|
||||
(\focusableElementAttrs ->
|
||||
div []
|
||||
[ Modal.viewContent [ viewModalContent state.longContent ]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
viewModalContent : Bool -> Html msg
|
||||
viewModalContent longContent =
|
||||
Text.mediumBody
|
||||
[ span [ css [ whiteSpace preLine ] ]
|
||||
[ if longContent then
|
||||
"""
|
||||
Soufflé pastry chocolate cake danish muffin. Candy wafer pastry ice cream cheesecake toffee cookie cake carrot cake. Macaroon pie jujubes gummies cookie pie. Gummi bears brownie pastry carrot cake cotton candy. Jelly-o sweet roll biscuit cake soufflé lemon drops tiramisu marshmallow macaroon. Chocolate jelly halvah marzipan macaroon cupcake sweet cheesecake carrot cake.
|
||||
|
||||
Sesame snaps pastry muffin cookie. Powder powder sweet roll toffee cake icing. Chocolate cake sweet roll gingerbread icing chupa chups sweet roll sesame snaps. Chocolate croissant chupa chups jelly beans toffee. Jujubes sweet wafer marshmallow halvah jelly. Liquorice sesame snaps sweet.
|
||||
|
||||
Tootsie roll icing jelly danish ice cream tiramisu sweet roll. Fruitcake ice cream dragée. Bear claw sugar plum sweet jelly beans bonbon dragée tart. Gingerbread chocolate sweet. Apple pie danish toffee sugar plum jelly beans donut. Chocolate cake croissant caramels chocolate bar. Jelly beans caramels toffee chocolate cake liquorice. Toffee pie sugar plum cookie toffee muffin. Marzipan marshmallow marzipan liquorice tiramisu.
|
||||
"""
|
||||
|> text
|
||||
|
||||
else
|
||||
"Ice cream tootsie roll donut sweet cookie liquorice sweet donut. Sugar plum danish apple pie sesame snaps chocolate bar biscuit. Caramels macaroon jelly gummies sweet tootsie roll tiramisu apple pie. Dessert chocolate bar lemon drops dragée jelly powder cheesecake chocolate."
|
||||
|> text
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewSettings : State -> Html Msg
|
||||
@ -239,6 +310,14 @@ viewSettings state =
|
||||
, disabled = False
|
||||
, theme = Checkbox.Square
|
||||
}
|
||||
, Checkbox.viewWithLabel
|
||||
{ identifier = "long-content"
|
||||
, label = "Display longer content"
|
||||
, selected = Checkbox.selectedFromBool state.longContent
|
||||
, setterMsg = SetLongContent
|
||||
, disabled = False
|
||||
, theme = Checkbox.Square
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@ -252,6 +331,7 @@ type Msg
|
||||
| SetShowContinue Bool
|
||||
| SetShowSecondary Bool
|
||||
| SetDismissOnEscAndOverlayClick Bool
|
||||
| SetLongContent Bool
|
||||
|
||||
|
||||
{-| -}
|
||||
@ -299,6 +379,9 @@ update msg state =
|
||||
SetDismissOnEscAndOverlayClick value ->
|
||||
( { state | dismissOnEscAndOverlayClick = value }, Cmd.none )
|
||||
|
||||
SetLongContent value ->
|
||||
( { state | longContent = value }, Cmd.none )
|
||||
|
||||
|
||||
{-| -}
|
||||
subscriptions : State -> Sub Msg
|
||||
|
Loading…
Reference in New Issue
Block a user