mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-12-02 23:52:22 +03:00
Merge pull request #249 from NoRedInk/lab/slidimations
Lab/slidimations
This commit is contained in:
commit
a8d16eec72
2
elm.json
2
elm.json
@ -45,7 +45,9 @@
|
||||
"Nri.Ui.SegmentedControl.V6",
|
||||
"Nri.Ui.Select.V5",
|
||||
"Nri.Ui.Svg.V1",
|
||||
"Nri.Ui.Slide.V1",
|
||||
"Nri.Ui.SlideModal.V1",
|
||||
"Nri.Ui.SlideModal.V2",
|
||||
"Nri.Ui.Table.V3",
|
||||
"Nri.Ui.Table.V4",
|
||||
"Nri.Ui.Tabs.V3",
|
||||
|
107
src/Nri/Ui/Slide/V1.elm
Normal file
107
src/Nri/Ui/Slide/V1.elm
Normal file
@ -0,0 +1,107 @@
|
||||
module Nri.Ui.Slide.V1 exposing
|
||||
( AnimationDirection(..)
|
||||
, withSlidingContents
|
||||
, animateIn, animateOut
|
||||
)
|
||||
|
||||
{-| Note: You'll almost certainly want to used keyed nodes if you're
|
||||
using this module.
|
||||
|
||||
@docs AnimationDirection
|
||||
@docs withSlidingContents
|
||||
@docs animateIn, animateOut
|
||||
|
||||
-}
|
||||
|
||||
import Css
|
||||
import Css.Animations
|
||||
|
||||
|
||||
{-| Slide from right to left or from left to right.
|
||||
-}
|
||||
type AnimationDirection
|
||||
= FromRTL
|
||||
| FromLTR
|
||||
|
||||
|
||||
translateXBy : Float
|
||||
translateXBy =
|
||||
700
|
||||
|
||||
|
||||
slideDuration : Css.Style
|
||||
slideDuration =
|
||||
Css.animationDuration (Css.ms 700)
|
||||
|
||||
|
||||
slideTimingFunction : Css.Style
|
||||
slideTimingFunction =
|
||||
Css.property "animation-timing-function" "ease-in-out"
|
||||
|
||||
|
||||
{-| Add this class to the container whose descendents are sliding.
|
||||
-}
|
||||
withSlidingContents : Css.Style
|
||||
withSlidingContents =
|
||||
Css.batch
|
||||
[ Css.position Css.relative
|
||||
, Css.overflowX Css.hidden
|
||||
]
|
||||
|
||||
|
||||
{-| Add this style to the element you want to animate in.
|
||||
-}
|
||||
animateIn : AnimationDirection -> Css.Style
|
||||
animateIn direction =
|
||||
let
|
||||
( start, end ) =
|
||||
case direction of
|
||||
FromRTL ->
|
||||
( Css.px translateXBy, Css.zero )
|
||||
|
||||
FromLTR ->
|
||||
( Css.px -translateXBy, Css.zero )
|
||||
in
|
||||
Css.batch
|
||||
[ slideDuration
|
||||
, slideTimingFunction
|
||||
, Css.property "animation-delay" "-50ms"
|
||||
, Css.animationName
|
||||
(Css.Animations.keyframes
|
||||
[ ( 0, [ Css.Animations.transform [ Css.translateX start ] ] )
|
||||
, ( 100, [ Css.Animations.transform [ Css.translateX end ] ] )
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
{-| Add this style to the element you want to animate out.
|
||||
Note: this will absolutely position the element.
|
||||
You must add `withSlidingContents` to one of its ancestors.
|
||||
-}
|
||||
animateOut : AnimationDirection -> Css.Style
|
||||
animateOut direction =
|
||||
let
|
||||
( start, end ) =
|
||||
case direction of
|
||||
FromRTL ->
|
||||
( Css.zero, Css.px -translateXBy )
|
||||
|
||||
FromLTR ->
|
||||
( Css.zero, Css.px translateXBy )
|
||||
in
|
||||
Css.batch
|
||||
[ Css.position Css.absolute
|
||||
, Css.transform (Css.translate2 end Css.zero)
|
||||
, Css.property "animation-delay" "-50ms"
|
||||
, Css.batch
|
||||
[ slideDuration
|
||||
, slideTimingFunction
|
||||
, Css.animationName
|
||||
(Css.Animations.keyframes
|
||||
[ ( 0, [ Css.Animations.transform [ Css.translateX start ] ] )
|
||||
, ( 100, [ Css.Animations.transform [ Css.translateX end ] ] )
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
411
src/Nri/Ui/SlideModal/V2.elm
Normal file
411
src/Nri/Ui/SlideModal/V2.elm
Normal file
@ -0,0 +1,411 @@
|
||||
module Nri.Ui.SlideModal.V2 exposing
|
||||
( Config, Panel
|
||||
, State, closed, open
|
||||
, view
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Config, Panel
|
||||
@docs State, closed, open
|
||||
@docs view
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled as Html exposing (..)
|
||||
import Accessibility.Styled.Aria exposing (labelledBy)
|
||||
import Accessibility.Styled.Role as Role
|
||||
import Accessibility.Styled.Style
|
||||
import Accessibility.Styled.Widget as Widget
|
||||
import Color
|
||||
import Css
|
||||
import Css.Animations
|
||||
import Css.Global
|
||||
import Html.Styled
|
||||
import Html.Styled.Attributes exposing (css)
|
||||
import Html.Styled.Events exposing (onClick)
|
||||
import Html.Styled.Keyed as Keyed
|
||||
import Nri.Ui
|
||||
import Nri.Ui.AssetPath exposing (Asset(..))
|
||||
import Nri.Ui.Button.V8 as Button
|
||||
import Nri.Ui.Colors.Extra
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.Icon.V3 as Icon
|
||||
import Nri.Ui.Slide.V1 as Slide exposing (AnimationDirection(..))
|
||||
import Nri.Ui.Text.V2 as Text
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Config msg =
|
||||
{ panels : List Panel
|
||||
, height : Css.Vh
|
||||
, parentMsg : State -> msg
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type State
|
||||
= State
|
||||
{ currentPanelIndex : Maybe Int
|
||||
, previousPanel : Maybe ( AnimationDirection, Panel )
|
||||
}
|
||||
|
||||
|
||||
{-| Create the open state for the modal (the first panel will show).
|
||||
-}
|
||||
open : State
|
||||
open =
|
||||
State
|
||||
{ currentPanelIndex = Just 0
|
||||
, previousPanel = Nothing
|
||||
}
|
||||
|
||||
|
||||
{-| Close the modal.
|
||||
-}
|
||||
closed : State
|
||||
closed =
|
||||
State
|
||||
{ currentPanelIndex = Nothing
|
||||
, previousPanel = Nothing
|
||||
}
|
||||
|
||||
|
||||
{-| View the modal (includes the modal backdrop).
|
||||
-}
|
||||
view : Config msg -> State -> Html msg
|
||||
view config ((State { currentPanelIndex }) as state) =
|
||||
case Maybe.andThen (summarize config.panels) currentPanelIndex of
|
||||
Just summary ->
|
||||
viewBackdrop
|
||||
(viewModal config state summary)
|
||||
|
||||
Nothing ->
|
||||
Html.text ""
|
||||
|
||||
|
||||
type alias Summary =
|
||||
{ current : Panel
|
||||
, upcoming : List ( State, String )
|
||||
, previous : List ( State, String )
|
||||
}
|
||||
|
||||
|
||||
summarize : List Panel -> Int -> Maybe Summary
|
||||
summarize panels current =
|
||||
let
|
||||
indexedPanels =
|
||||
List.indexedMap (\i { title } -> ( i, title )) panels
|
||||
|
||||
toOtherPanel direction currentPanel ( i, title ) =
|
||||
( State
|
||||
{ currentPanelIndex = Just i
|
||||
, previousPanel =
|
||||
Just
|
||||
( direction
|
||||
, { currentPanel | content = currentPanel.content }
|
||||
)
|
||||
}
|
||||
, title
|
||||
)
|
||||
in
|
||||
case List.drop current panels of
|
||||
currentPanel :: rest ->
|
||||
Just
|
||||
{ current = currentPanel
|
||||
, upcoming =
|
||||
indexedPanels
|
||||
|> List.drop (current + 1)
|
||||
|> List.map (toOtherPanel FromRTL currentPanel)
|
||||
, previous =
|
||||
indexedPanels
|
||||
|> List.take current
|
||||
|> List.map (toOtherPanel FromLTR currentPanel)
|
||||
}
|
||||
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
|
||||
viewModal : Config msg -> State -> Summary -> Html msg
|
||||
viewModal config (State { previousPanel }) summary =
|
||||
Keyed.node "div"
|
||||
[ css
|
||||
[ Css.boxSizing Css.borderBox
|
||||
, Css.margin2 (Css.px 75) Css.auto
|
||||
, Css.backgroundColor Colors.white
|
||||
, Css.borderRadius (Css.px 20)
|
||||
, Css.property "box-shadow" "0 1px 10px 0 rgba(0, 0, 0, 0.35)"
|
||||
, Slide.withSlidingContents
|
||||
]
|
||||
, Role.dialog
|
||||
, Widget.modal True
|
||||
, labelledBy (panelId summary.current)
|
||||
]
|
||||
(case previousPanel of
|
||||
Just ( direction, panelView ) ->
|
||||
( panelId panelView
|
||||
, panelContainer config.height
|
||||
[ Slide.animateOut direction ]
|
||||
[ viewIcon panelView.icon
|
||||
, Text.subHeading
|
||||
[ span [ Html.Styled.Attributes.id (panelId panelView) ] [ Html.text panelView.title ]
|
||||
]
|
||||
, viewContent panelView.content
|
||||
]
|
||||
)
|
||||
:: viewModalContent config summary [ Slide.animateIn direction ]
|
||||
|
||||
Nothing ->
|
||||
viewModalContent config summary []
|
||||
)
|
||||
|
||||
|
||||
viewModalContent : Config msg -> Summary -> List Css.Style -> List ( String, Html msg )
|
||||
viewModalContent config summary styles =
|
||||
[ ( panelId summary.current
|
||||
, panelContainer config.height
|
||||
styles
|
||||
[ viewIcon summary.current.icon
|
||||
, Text.subHeading
|
||||
[ span [ Html.Styled.Attributes.id (panelId summary.current) ]
|
||||
[ Html.text summary.current.title ]
|
||||
]
|
||||
, viewContent summary.current.content
|
||||
]
|
||||
)
|
||||
, ( panelId summary.current ++ "-footer"
|
||||
, viewActiveFooter summary
|
||||
|> Html.map config.parentMsg
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
viewBackdrop : Html msg -> Html msg
|
||||
viewBackdrop modal =
|
||||
Nri.Ui.styled div
|
||||
"modal-backdrop-container"
|
||||
(Css.backgroundColor (Nri.Ui.Colors.Extra.withAlpha 0.9 Colors.navy)
|
||||
:: [ Css.height (Css.vh 100)
|
||||
, Css.left Css.zero
|
||||
, Css.overflow Css.hidden
|
||||
, Css.position Css.fixed
|
||||
, Css.top Css.zero
|
||||
, Css.width (Css.pct 100)
|
||||
, Css.zIndex (Css.int 200)
|
||||
, Css.displayFlex
|
||||
, Css.alignItems Css.center
|
||||
, Css.justifyContent Css.center
|
||||
]
|
||||
)
|
||||
[]
|
||||
[ -- This global <style> node sets overflow to hidden on the body element,
|
||||
-- thereby preventing the page from scrolling behind the backdrop when the modal is
|
||||
-- open (and this node is present on the page).
|
||||
Css.Global.global [ Css.Global.body [ Css.overflow Css.hidden ] ]
|
||||
, modal
|
||||
]
|
||||
|
||||
|
||||
{-| Configuration for a single modal view in the sequence of modal views.
|
||||
-}
|
||||
type alias Panel =
|
||||
{ icon : Html Never
|
||||
, title : String
|
||||
, content : Html Never
|
||||
, buttonLabel : String
|
||||
}
|
||||
|
||||
|
||||
panelContainer : Css.Vh -> List Css.Style -> List (Html msg) -> Html msg
|
||||
panelContainer height animationStyles panel =
|
||||
div
|
||||
[ css
|
||||
[ -- Layout
|
||||
Css.minHeight (Css.px 400)
|
||||
, Css.minHeight (Css.px 360)
|
||||
, Css.maxHeight <| Css.calc (Css.vh 100) Css.minus (Css.px 100)
|
||||
, Css.height height
|
||||
, Css.width (Css.px 600)
|
||||
, Css.margin3 (Css.px 35) (Css.px 21) Css.zero
|
||||
|
||||
-- Interior positioning
|
||||
, Css.displayFlex
|
||||
, Css.alignItems Css.center
|
||||
, Css.flexDirection Css.column
|
||||
, Css.flexWrap Css.noWrap
|
||||
|
||||
-- Styles
|
||||
, Fonts.baseFont
|
||||
, Css.batch animationStyles
|
||||
]
|
||||
]
|
||||
panel
|
||||
|
||||
|
||||
panelId : Panel -> String
|
||||
panelId { title } =
|
||||
"modal-header__" ++ String.replace " " "-" title
|
||||
|
||||
|
||||
viewContent : Html Never -> Html msg
|
||||
viewContent content =
|
||||
Nri.Ui.styled div
|
||||
"modal-content"
|
||||
[ Css.overflowY Css.auto
|
||||
, Css.padding2 (Css.px 30) (Css.px 45)
|
||||
, Css.width (Css.pct 100)
|
||||
, Css.marginBottom Css.auto
|
||||
, Css.boxSizing Css.borderBox
|
||||
]
|
||||
[]
|
||||
[ Html.map never content ]
|
||||
|
||||
|
||||
viewIcon : Html Never -> Html msg
|
||||
viewIcon svg =
|
||||
div
|
||||
[ css
|
||||
[ Css.width (Css.px 100)
|
||||
, Css.height (Css.px 100)
|
||||
, Css.flexShrink Css.zero
|
||||
, Css.displayFlex
|
||||
, Css.alignItems Css.center
|
||||
, Css.justifyContent Css.center
|
||||
, Css.Global.children
|
||||
[ Css.Global.svg
|
||||
[ Css.maxHeight (Css.px 100)
|
||||
, Css.width (Css.px 100)
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
[ svg ]
|
||||
|> Html.map never
|
||||
|
||||
|
||||
viewActiveFooter : Summary -> Html State
|
||||
viewActiveFooter { previous, current, upcoming } =
|
||||
let
|
||||
nextPanel =
|
||||
List.head upcoming
|
||||
|> Maybe.map Tuple.first
|
||||
|> Maybe.withDefault closed
|
||||
|
||||
dots =
|
||||
List.map (uncurry Inactive) previous
|
||||
++ Active
|
||||
:: List.map (uncurry InactiveDisabled) upcoming
|
||||
in
|
||||
viewFlexibleFooter
|
||||
{ buttonLabel = current.buttonLabel
|
||||
, buttonMsg = nextPanel
|
||||
, buttonState = Button.Enabled
|
||||
}
|
||||
dots
|
||||
|
||||
|
||||
viewFlexibleFooter :
|
||||
{ buttonLabel : String
|
||||
, buttonMsg : msg
|
||||
, buttonState : Button.ButtonState
|
||||
}
|
||||
-> List (Dot msg)
|
||||
-> Html msg
|
||||
viewFlexibleFooter { buttonLabel, buttonMsg, buttonState } dotList =
|
||||
Nri.Ui.styled div
|
||||
"modal-footer"
|
||||
[ Css.flexShrink Css.zero
|
||||
, Css.displayFlex
|
||||
, Css.flexDirection Css.column
|
||||
, Css.alignItems Css.center
|
||||
, Css.margin4 (Css.px 20) Css.zero (Css.px 25) Css.zero
|
||||
]
|
||||
[]
|
||||
[ Button.button
|
||||
{ onClick = buttonMsg
|
||||
, size = Button.Large
|
||||
, style = Button.Primary
|
||||
, width = Button.WidthExact 230
|
||||
}
|
||||
{ label = buttonLabel
|
||||
, state = buttonState
|
||||
, icon = Nothing
|
||||
}
|
||||
, dotList
|
||||
|> List.map dot
|
||||
|> div [ css [ Css.marginTop (Css.px 16) ] ]
|
||||
]
|
||||
|
||||
|
||||
uncurry : (a -> b -> c) -> ( a, b ) -> c
|
||||
uncurry f ( a, b ) =
|
||||
f a b
|
||||
|
||||
|
||||
type Dot msg
|
||||
= Active
|
||||
| Inactive msg String
|
||||
| InactiveDisabled msg String
|
||||
|
||||
|
||||
dot : Dot msg -> Html.Html msg
|
||||
dot type_ =
|
||||
let
|
||||
styles ( startColor, endColor ) cursor =
|
||||
css
|
||||
[ Css.height (Css.px 10)
|
||||
, Css.width (Css.px 10)
|
||||
, Css.borderRadius (Css.px 5)
|
||||
, Css.margin2 Css.zero (Css.px 2)
|
||||
, Css.display Css.inlineBlock
|
||||
, Css.verticalAlign Css.middle
|
||||
, Css.cursor cursor
|
||||
|
||||
-- Color
|
||||
, Css.animationDuration (Css.ms 600)
|
||||
, Css.property "animation-timing-function" "linear"
|
||||
, Css.animationName
|
||||
(Css.Animations.keyframes
|
||||
[ ( 0, [ animateBackgroundColor startColor ] )
|
||||
, ( 100, [ animateBackgroundColor endColor ] )
|
||||
]
|
||||
)
|
||||
, Css.backgroundColor endColor
|
||||
|
||||
-- resets
|
||||
, Css.borderWidth Css.zero
|
||||
, Css.padding Css.zero
|
||||
, Css.hover [ Css.outline Css.none ]
|
||||
]
|
||||
|
||||
animateBackgroundColor color =
|
||||
Nri.Ui.Colors.Extra.toCoreColor color
|
||||
|> Color.toCssString
|
||||
|> Css.Animations.property "background-color"
|
||||
in
|
||||
case type_ of
|
||||
Active ->
|
||||
Html.div
|
||||
[ styles ( Colors.gray75, Colors.azure ) Css.auto
|
||||
]
|
||||
[]
|
||||
|
||||
Inactive goTo title ->
|
||||
Html.button
|
||||
[ styles ( Colors.gray75, Colors.gray75 ) Css.pointer
|
||||
, onClick goTo
|
||||
]
|
||||
[ span Accessibility.Styled.Style.invisible
|
||||
[ text ("Go to " ++ title) ]
|
||||
]
|
||||
|
||||
InactiveDisabled goTo title ->
|
||||
Html.button
|
||||
[ styles ( Colors.gray75, Colors.gray75 ) Css.auto
|
||||
, Html.Styled.Attributes.disabled True
|
||||
]
|
||||
[ span Accessibility.Styled.Style.invisible
|
||||
[ text ("Go to " ++ title) ]
|
||||
]
|
155
styleguide-app/Examples/Slide.elm
Normal file
155
styleguide-app/Examples/Slide.elm
Normal file
@ -0,0 +1,155 @@
|
||||
module Examples.Slide exposing (Msg, State, example, init, update)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Msg, State, example, init, update
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled as Html
|
||||
import Css
|
||||
import Html.Styled.Attributes exposing (css)
|
||||
import Html.Styled.Keyed as Keyed
|
||||
import List.Zipper as Zipper exposing (Zipper(..))
|
||||
import ModuleExample exposing (Category(..), ModuleExample)
|
||||
import Nri.Ui.Button.V8 as Button
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Slide.V1 as Slide
|
||||
|
||||
|
||||
{-| -}
|
||||
type Msg
|
||||
= TriggerAnimation Slide.AnimationDirection
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias State =
|
||||
{ direction : Slide.AnimationDirection
|
||||
, panels : Zipper Panel
|
||||
, previous : Maybe Panel
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
example : (Msg -> msg) -> State -> ModuleExample msg
|
||||
example parentMessage state =
|
||||
{ filename = "Nri.Ui.Slide.V1.elm"
|
||||
, category = Behaviors
|
||||
, content =
|
||||
[ Keyed.node "div"
|
||||
[ css
|
||||
[ Slide.withSlidingContents
|
||||
, Css.border3 (Css.px 3) Css.solid Colors.gray75
|
||||
, Css.padding (Css.px 20)
|
||||
, Css.width (Css.px 600)
|
||||
]
|
||||
]
|
||||
(case state.previous of
|
||||
Just previousPanel ->
|
||||
[ viewPanel previousPanel (Slide.animateOut state.direction)
|
||||
, viewPanel (Zipper.current state.panels) (Slide.animateIn state.direction)
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
[ viewPanel (Zipper.current state.panels) (Css.batch [])
|
||||
]
|
||||
)
|
||||
, Html.div
|
||||
[ css
|
||||
[ Css.displayFlex
|
||||
, Css.justifyContent Css.spaceBetween
|
||||
, Css.marginTop (Css.px 20)
|
||||
, Css.width (Css.px 300)
|
||||
]
|
||||
]
|
||||
[ triggerAnimation Slide.FromLTR "Left-to-right"
|
||||
, triggerAnimation Slide.FromRTL "Right-to-left"
|
||||
]
|
||||
]
|
||||
|> List.map (Html.map parentMessage)
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
init : State
|
||||
init =
|
||||
{ direction = Slide.FromRTL
|
||||
, panels = Zipper [] One [ Two, Three ]
|
||||
, previous = Nothing
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
update : Msg -> State -> ( State, Cmd Msg )
|
||||
update msg state =
|
||||
case msg of
|
||||
TriggerAnimation direction ->
|
||||
( { state
|
||||
| direction = direction
|
||||
, panels =
|
||||
case direction of
|
||||
Slide.FromRTL ->
|
||||
Zipper.next state.panels
|
||||
|> Maybe.withDefault (Zipper.first state.panels)
|
||||
|
||||
Slide.FromLTR ->
|
||||
Zipper.previous state.panels
|
||||
|> Maybe.withDefault (Zipper.last state.panels)
|
||||
, previous = Just (Zipper.current state.panels)
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
|
||||
-- INTERNAL
|
||||
|
||||
|
||||
type Panel
|
||||
= One
|
||||
| Two
|
||||
| Three
|
||||
|
||||
|
||||
viewPanel : Panel -> Css.Style -> ( String, Html.Html msg )
|
||||
viewPanel panel animation =
|
||||
let
|
||||
( color, text, key ) =
|
||||
case panel of
|
||||
One ->
|
||||
( Colors.red, "Panel One", "panel-1" )
|
||||
|
||||
Two ->
|
||||
( Colors.yellow, "Panel Two", "panel-2" )
|
||||
|
||||
Three ->
|
||||
( Colors.green, "Panel Three", "panel-3" )
|
||||
in
|
||||
( key
|
||||
, Html.div
|
||||
[ css
|
||||
[ Css.border3 (Css.px 2) Css.dashed color
|
||||
, Css.color color
|
||||
, Css.padding (Css.px 10)
|
||||
, Css.width (Css.px 100)
|
||||
, Css.textAlign Css.center
|
||||
, animation
|
||||
]
|
||||
]
|
||||
[ Html.text text
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
triggerAnimation : Slide.AnimationDirection -> String -> Html.Html Msg
|
||||
triggerAnimation direction label =
|
||||
Button.button
|
||||
{ onClick = TriggerAnimation direction
|
||||
, size = Button.Small
|
||||
, style = Button.Secondary
|
||||
, width = Button.WidthUnbounded
|
||||
}
|
||||
{ label = label
|
||||
, state = Button.Enabled
|
||||
, icon = Nothing
|
||||
}
|
@ -14,7 +14,7 @@ import Html.Styled.Attributes exposing (css)
|
||||
import ModuleExample exposing (Category(..), ModuleExample)
|
||||
import Nri.Ui.Button.V8 as Button
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.SlideModal.V1 as SlideModal
|
||||
import Nri.Ui.SlideModal.V2 as SlideModal
|
||||
import Svg exposing (..)
|
||||
import Svg.Attributes exposing (..)
|
||||
|
||||
@ -32,7 +32,7 @@ type alias State =
|
||||
{-| -}
|
||||
example : (Msg -> msg) -> State -> ModuleExample msg
|
||||
example parentMessage state =
|
||||
{ filename = "Nri.Ui.SlideModal.V1.elm"
|
||||
{ filename = "Nri.Ui.SlideModal.V2.elm"
|
||||
, category = Modals
|
||||
, content =
|
||||
[ viewModal state.modal
|
||||
|
@ -15,6 +15,7 @@ import Examples.Modal
|
||||
import Examples.Page
|
||||
import Examples.SegmentedControl
|
||||
import Examples.Select
|
||||
import Examples.Slide
|
||||
import Examples.SlideModal
|
||||
import Examples.Table
|
||||
import Examples.Tabs
|
||||
@ -41,6 +42,7 @@ type alias ModuleStates =
|
||||
, disclosureIndicatorExampleState : Examples.DisclosureIndicator.State
|
||||
, modalExampleState : Examples.Modal.State
|
||||
, slideModalExampleState : Examples.SlideModal.State
|
||||
, slideExampleState : Examples.Slide.State
|
||||
, tabsExampleState : Examples.Tabs.Tab
|
||||
}
|
||||
|
||||
@ -59,6 +61,7 @@ init =
|
||||
, disclosureIndicatorExampleState = Examples.DisclosureIndicator.init
|
||||
, modalExampleState = Examples.Modal.init
|
||||
, slideModalExampleState = Examples.SlideModal.init
|
||||
, slideExampleState = Examples.Slide.init
|
||||
, tabsExampleState = Examples.Tabs.First
|
||||
}
|
||||
|
||||
@ -77,6 +80,7 @@ type Msg
|
||||
| DisclosureIndicatorExampleMsg Examples.DisclosureIndicator.Msg
|
||||
| ModalExampleMsg Examples.Modal.Msg
|
||||
| SlideModalExampleMsg Examples.SlideModal.Msg
|
||||
| SlideExampleMsg Examples.Slide.Msg
|
||||
| TabsExampleMsg Examples.Tabs.Tab
|
||||
| NoOp
|
||||
|
||||
@ -197,6 +201,15 @@ update outsideMsg moduleStates =
|
||||
, Cmd.map SlideModalExampleMsg cmd
|
||||
)
|
||||
|
||||
SlideExampleMsg msg ->
|
||||
let
|
||||
( slideExampleState, cmd ) =
|
||||
Examples.Slide.update msg moduleStates.slideExampleState
|
||||
in
|
||||
( { moduleStates | slideExampleState = slideExampleState }
|
||||
, Cmd.map SlideExampleMsg cmd
|
||||
)
|
||||
|
||||
TabsExampleMsg tab ->
|
||||
( { moduleStates | tabsExampleState = tab }
|
||||
, Cmd.none
|
||||
@ -246,6 +259,7 @@ nriThemedModules model =
|
||||
, Examples.Colors.example
|
||||
, Examples.Modal.example ModalExampleMsg model.modalExampleState
|
||||
, Examples.SlideModal.example SlideModalExampleMsg model.slideModalExampleState
|
||||
, Examples.Slide.example SlideExampleMsg model.slideExampleState
|
||||
, Examples.Tabs.example TabsExampleMsg model.tabsExampleState
|
||||
]
|
||||
|
||||
|
157
tests/Spec/Nri/Ui/SlideModal/V2.elm
Normal file
157
tests/Spec/Nri/Ui/SlideModal/V2.elm
Normal file
@ -0,0 +1,157 @@
|
||||
module Spec.Nri.Ui.SlideModal.V2 exposing (all)
|
||||
|
||||
import Css
|
||||
import Expect exposing (Expectation)
|
||||
import Html.Styled as Html
|
||||
import Json.Encode
|
||||
import Nri.Ui.SlideModal.V2 as SlideModal
|
||||
import Test exposing (..)
|
||||
import Test.Html.Event as Event
|
||||
import Test.Html.Query as Query
|
||||
import Test.Html.Selector exposing (..)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "Nri.Ui.SlideModal.V2"
|
||||
[ test "shows first panel when open" <|
|
||||
\() ->
|
||||
SlideModal.open
|
||||
|> SlideModal.view
|
||||
{ panels = threePanels
|
||||
, height = Css.vh 60
|
||||
, parentMsg = identity
|
||||
}
|
||||
|> Html.toUnstyled
|
||||
|> Query.fromHtml
|
||||
|> Expect.all
|
||||
[ Query.has [ text "Title1", text "Content1" ]
|
||||
, Query.hasNot [ text "Title2", text "Content2" ]
|
||||
, Query.hasNot [ text "Title3", text "Content3" ]
|
||||
]
|
||||
, test "shows no panel when closed" <|
|
||||
\() ->
|
||||
SlideModal.closed
|
||||
|> SlideModal.view
|
||||
{ panels = threePanels
|
||||
, height = Css.vh 60
|
||||
, parentMsg = identity
|
||||
}
|
||||
|> Html.toUnstyled
|
||||
|> Query.fromHtml
|
||||
|> Expect.all
|
||||
[ Query.hasNot [ text "Title1", text "Content1" ]
|
||||
, Query.hasNot [ text "Title2", text "Content2" ]
|
||||
, Query.hasNot [ text "Title3", text "Content3" ]
|
||||
]
|
||||
, test "can click through" <|
|
||||
\() ->
|
||||
{ panels = threePanels
|
||||
, height = Css.vh 60
|
||||
, parentMsg = identity
|
||||
}
|
||||
|> initTest
|
||||
|> click "Continue1"
|
||||
|> click "Continue2"
|
||||
|> click "Continue3"
|
||||
|> assertAndFinish
|
||||
[ Query.hasNot [ text "Title1", text "Content1" ]
|
||||
, Query.hasNot [ text "Title2", text "Content2" ]
|
||||
, Query.hasNot [ text "Title3", text "Content3" ]
|
||||
]
|
||||
, test "can navigate back using the dots" <|
|
||||
\() ->
|
||||
{ panels = threePanels
|
||||
, height = Css.vh 60
|
||||
, parentMsg = identity
|
||||
}
|
||||
|> initTest
|
||||
|> click "Continue1"
|
||||
|> click "Go to Title1"
|
||||
|> assertAndFinish
|
||||
[ Query.has [ text "Title1", text "Content1" ]
|
||||
, Query.hasNot [ text "Title2", text "Content2" ]
|
||||
, Query.hasNot [ text "Title3", text "Content3" ]
|
||||
]
|
||||
, test "cannot navigate forward using the dots" <|
|
||||
\() ->
|
||||
{ panels = threePanels
|
||||
, height = Css.vh 60
|
||||
, parentMsg = identity
|
||||
}
|
||||
|> initTest
|
||||
|> click "Continue1"
|
||||
|> assertAndFinish
|
||||
[ Query.has [ tag "button", containing [ text "Go to Title1" ] ]
|
||||
, Query.hasNot [ tag "button", containing [ text "Go to Title2" ] ]
|
||||
, Query.has [ tag "button", disabled True, containing [ text "Go to Title3" ] ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
threePanels : List SlideModal.Panel
|
||||
threePanels =
|
||||
[ { icon = Html.text "Icon1"
|
||||
, title = "Title1"
|
||||
, content = Html.text "Content1"
|
||||
, buttonLabel = "Continue1"
|
||||
}
|
||||
, { icon = Html.text "Icon2"
|
||||
, title = "Title2"
|
||||
, content = Html.text "Content 2"
|
||||
, buttonLabel = "Continue2"
|
||||
}
|
||||
, { icon = Html.text "Icon3"
|
||||
, title = "Title3"
|
||||
, content = Html.text "Content 3"
|
||||
, buttonLabel = "Continue3"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
type alias TestContext =
|
||||
{ view : SlideModal.State -> Query.Single SlideModal.State
|
||||
, state : Result String SlideModal.State
|
||||
}
|
||||
|
||||
|
||||
initTest : SlideModal.Config SlideModal.State -> TestContext
|
||||
initTest config =
|
||||
{ view = SlideModal.view config >> Html.toUnstyled >> Query.fromHtml
|
||||
, state = Ok SlideModal.open
|
||||
}
|
||||
|
||||
|
||||
click : String -> TestContext -> TestContext
|
||||
click buttonText =
|
||||
simulate
|
||||
(Query.find [ tag "button", containing [ text buttonText ] ])
|
||||
Event.click
|
||||
|
||||
|
||||
simulate :
|
||||
(Query.Single SlideModal.State -> Query.Single SlideModal.State)
|
||||
-> ( String, Json.Encode.Value )
|
||||
-> TestContext
|
||||
-> TestContext
|
||||
simulate findElement event testContext =
|
||||
{ testContext
|
||||
| state =
|
||||
Result.andThen
|
||||
(testContext.view
|
||||
>> findElement
|
||||
>> Event.simulate event
|
||||
>> Event.toResult
|
||||
)
|
||||
testContext.state
|
||||
}
|
||||
|
||||
|
||||
assertAndFinish : List (Query.Single SlideModal.State -> Expectation) -> TestContext -> Expectation
|
||||
assertAndFinish expectations { view, state } =
|
||||
case Result.map view state of
|
||||
Ok query ->
|
||||
Expect.all expectations query
|
||||
|
||||
Err err ->
|
||||
Expect.fail err
|
Loading…
Reference in New Issue
Block a user