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.SegmentedControl.V6",
|
||||||
"Nri.Ui.Select.V5",
|
"Nri.Ui.Select.V5",
|
||||||
"Nri.Ui.Svg.V1",
|
"Nri.Ui.Svg.V1",
|
||||||
|
"Nri.Ui.Slide.V1",
|
||||||
"Nri.Ui.SlideModal.V1",
|
"Nri.Ui.SlideModal.V1",
|
||||||
|
"Nri.Ui.SlideModal.V2",
|
||||||
"Nri.Ui.Table.V3",
|
"Nri.Ui.Table.V3",
|
||||||
"Nri.Ui.Table.V4",
|
"Nri.Ui.Table.V4",
|
||||||
"Nri.Ui.Tabs.V3",
|
"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 ModuleExample exposing (Category(..), ModuleExample)
|
||||||
import Nri.Ui.Button.V8 as Button
|
import Nri.Ui.Button.V8 as Button
|
||||||
import Nri.Ui.Colors.V1 as Colors
|
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 exposing (..)
|
||||||
import Svg.Attributes exposing (..)
|
import Svg.Attributes exposing (..)
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ type alias State =
|
|||||||
{-| -}
|
{-| -}
|
||||||
example : (Msg -> msg) -> State -> ModuleExample msg
|
example : (Msg -> msg) -> State -> ModuleExample msg
|
||||||
example parentMessage state =
|
example parentMessage state =
|
||||||
{ filename = "Nri.Ui.SlideModal.V1.elm"
|
{ filename = "Nri.Ui.SlideModal.V2.elm"
|
||||||
, category = Modals
|
, category = Modals
|
||||||
, content =
|
, content =
|
||||||
[ viewModal state.modal
|
[ viewModal state.modal
|
||||||
|
@ -15,6 +15,7 @@ import Examples.Modal
|
|||||||
import Examples.Page
|
import Examples.Page
|
||||||
import Examples.SegmentedControl
|
import Examples.SegmentedControl
|
||||||
import Examples.Select
|
import Examples.Select
|
||||||
|
import Examples.Slide
|
||||||
import Examples.SlideModal
|
import Examples.SlideModal
|
||||||
import Examples.Table
|
import Examples.Table
|
||||||
import Examples.Tabs
|
import Examples.Tabs
|
||||||
@ -41,6 +42,7 @@ type alias ModuleStates =
|
|||||||
, disclosureIndicatorExampleState : Examples.DisclosureIndicator.State
|
, disclosureIndicatorExampleState : Examples.DisclosureIndicator.State
|
||||||
, modalExampleState : Examples.Modal.State
|
, modalExampleState : Examples.Modal.State
|
||||||
, slideModalExampleState : Examples.SlideModal.State
|
, slideModalExampleState : Examples.SlideModal.State
|
||||||
|
, slideExampleState : Examples.Slide.State
|
||||||
, tabsExampleState : Examples.Tabs.Tab
|
, tabsExampleState : Examples.Tabs.Tab
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +61,7 @@ init =
|
|||||||
, disclosureIndicatorExampleState = Examples.DisclosureIndicator.init
|
, disclosureIndicatorExampleState = Examples.DisclosureIndicator.init
|
||||||
, modalExampleState = Examples.Modal.init
|
, modalExampleState = Examples.Modal.init
|
||||||
, slideModalExampleState = Examples.SlideModal.init
|
, slideModalExampleState = Examples.SlideModal.init
|
||||||
|
, slideExampleState = Examples.Slide.init
|
||||||
, tabsExampleState = Examples.Tabs.First
|
, tabsExampleState = Examples.Tabs.First
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +80,7 @@ type Msg
|
|||||||
| DisclosureIndicatorExampleMsg Examples.DisclosureIndicator.Msg
|
| DisclosureIndicatorExampleMsg Examples.DisclosureIndicator.Msg
|
||||||
| ModalExampleMsg Examples.Modal.Msg
|
| ModalExampleMsg Examples.Modal.Msg
|
||||||
| SlideModalExampleMsg Examples.SlideModal.Msg
|
| SlideModalExampleMsg Examples.SlideModal.Msg
|
||||||
|
| SlideExampleMsg Examples.Slide.Msg
|
||||||
| TabsExampleMsg Examples.Tabs.Tab
|
| TabsExampleMsg Examples.Tabs.Tab
|
||||||
| NoOp
|
| NoOp
|
||||||
|
|
||||||
@ -197,6 +201,15 @@ update outsideMsg moduleStates =
|
|||||||
, Cmd.map SlideModalExampleMsg cmd
|
, 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 ->
|
TabsExampleMsg tab ->
|
||||||
( { moduleStates | tabsExampleState = tab }
|
( { moduleStates | tabsExampleState = tab }
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
@ -246,6 +259,7 @@ nriThemedModules model =
|
|||||||
, Examples.Colors.example
|
, Examples.Colors.example
|
||||||
, Examples.Modal.example ModalExampleMsg model.modalExampleState
|
, Examples.Modal.example ModalExampleMsg model.modalExampleState
|
||||||
, Examples.SlideModal.example SlideModalExampleMsg model.slideModalExampleState
|
, Examples.SlideModal.example SlideModalExampleMsg model.slideModalExampleState
|
||||||
|
, Examples.Slide.example SlideExampleMsg model.slideExampleState
|
||||||
, Examples.Tabs.example TabsExampleMsg model.tabsExampleState
|
, 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