NoRedInk/noredink-ui/18.0.1

NOTE: the latest version is 35.9.3

Nri.Ui.Modal.V11

Patch changes:

  • adds testId helper
  • adds data-nri-descriptions to the header, content, and footer
  • use Shadows

Changes from V10:

  • remove initOpen
  • change open, close to return (Model, Cmd Msg) rather than Msg
  • make info and warning themes
  • adds custom helper for adding arbitrary html attributes (primarily useful to make limiting the scope of selectors in tests easier by adding ids to modals)
  • tab and tabback events stop propagation and prevent default
import Browser exposing (element)
import Browser.Dom as Dom
import Css exposing (padding, px)
import Html.Styled exposing (..)
import Html.Styled.Attributes exposing (id)
import Html.Styled.Events as Events
import Nri.Ui.FocusTrap.V1 as FocusTrap exposing (FocusTrap)
import Nri.Ui.Modal.V11 as Modal
import Task

main : Program () Modal.Model Msg
main =
    Browser.element
        { init = \_ -> init
        , view = toUnstyled << view
        , update = update
        , subscriptions = \model -> Sub.map ModalMsg (Modal.subscriptions model)
        }

init : ( Modal.Model, Cmd Msg )
init =
    let
        ( model, cmd ) =
            -- When we load the page with a modal already open, we should return
            -- the focus someplace sensible when the modal closes.
            -- [This article](https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/) recommends
            -- focusing the main or body.
            Modal.open
                { startFocusOn = Modal.closeButtonId
                , returnFocusTo = "maincontent"
                }
    in
    ( model, Cmd.map ModalMsg cmd )

type Msg
    = OpenModal String
    | ModalMsg Modal.Msg
    | CloseModal
    | Focus String
    | Focused (Result Dom.Error ())

update : Msg -> Modal.Model -> ( Modal.Model, Cmd Msg )
update msg model =
    case msg of
        OpenModal returnFocusTo ->
            let
                ( newModel, cmd ) =
                    Modal.open
                        { startFocusOn = Modal.closeButtonId
                        , returnFocusTo = returnFocusTo
                        }
            in
            ( newModel, Cmd.map ModalMsg cmd )

        ModalMsg modalMsg ->
            let
                ( newModel, cmd ) =
                    Modal.update
                        { dismissOnEscAndOverlayClick = True }
                        modalMsg
                        model
            in
            ( newModel, Cmd.map ModalMsg cmd )

        CloseModal ->
            let
                ( newModel, cmd ) =
                    Modal.close model
            in
            ( newModel, Cmd.map ModalMsg cmd )

        Focus id ->
            ( model, Task.attempt Focused (Dom.focus id) )

        Focused _ ->
            ( model, Cmd.none )

view : Modal.Model -> Html Msg
view model =
    main_ [ id "maincontent" ]
        [ button
            [ id "open-modal"
            , Events.onClick (OpenModal "open-modal")
            ]
            [ text "Open Modal" ]
        , Modal.view
            { title = "First kind of modal"
            , wrapMsg = ModalMsg
            , content = [ text "Modal Content" ]
            , footer =
                [ button
                    [ Events.onClick CloseModal
                    , id "last-element-id"
                    ]
                    [ text "Close" ]
                ]
            , focusTrap =
                { focus = Focus
                , firstId = Modal.closeButtonId
                , lastId = "last-element-id"
                }
            }
            [ Modal.hideTitle
            , Modal.css [ padding (px 10) ]
            , Modal.custom [ id "first-modal" ]
            , Modal.closeButton
            ]
            model
        ]
{ title : String
, wrapMsg : Msg -> msg
, focusTrap : FocusTrap msg
, content : List (Html msg)
, footer : List (Html msg)
}
-> List Attribute
-> Model
-> Html msg

FocusTrap comes from Nri.Ui.FocusTrap.V1.

Include the close button.

closeButtonId : String
type Model
{ startFocusOn : String
, returnFocusTo : String
}
-> ( Model, Cmd Msg )

Pass the id of the element that should receive focus when the modal closes.

...if a dialog was opened on page load, then focus could be placed on either the body or main element. If the trigger was removed from the DOM, then placing focus as close to the trigger’s DOM location would be ideal.

https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/

close : Model -> ( Model, Cmd Msg )
type Msg
{ dismissOnEscAndOverlayClick : Bool }
-> Msg
-> Model
-> ( Model, Cmd Msg )

Include the subscription if you want the modal to dismiss on Esc.

Attributes

This is the default theme.

This is the default setting.

testId : String -> Attribute
css : List Style -> Attribute
custom : List (Attribute Never) -> Attribute

Do NOT use this function for attaching styles -- use the css helper instead.

import Html.Styled.Attribute exposing (id)


Modal.view
    { title = "Some Great Modal"
    , wrapMsg = ModalMsg
    , content = []
    , footer = []
    }
    [ Modal.custom [ id "my-modal" ]]
    modalState

State checks

isOpen : Model -> Bool