Adds accordion v1 (copied from the monolith)

This commit is contained in:
Tessa Kelly 2019-10-24 10:23:19 -07:00
parent 5e08b015d1
commit 4cd17b5e63
6 changed files with 391 additions and 7 deletions

View File

@ -6,6 +6,7 @@
"version": "7.8.0",
"exposed-modules": [
"Nri.Ui",
"Nri.Ui.Accordion.V1",
"Nri.Ui.Alert.V2",
"Nri.Ui.Alert.V3",
"Nri.Ui.Alert.V4",
@ -110,4 +111,4 @@
"avh4/elm-program-test": "3.1.0 <= v < 4.0.0",
"elm-explorations/test": "1.2.0 <= v < 2.0.0"
}
}
}

237
src/Nri/Ui/Accordion/V1.elm Normal file
View File

@ -0,0 +1,237 @@
module Nri.Ui.Accordion.V1 exposing (view, viewKeyed, viewCaret, AccordionOptions, StyleOptions, Caret(..))
{-|
@docs view, viewKeyed, viewCaret, AccordionOptions, StyleOptions, Caret
-}
import Accessibility.Styled.Role as Role
import Css exposing (..)
import Css.Global
import Html.Styled exposing (Attribute, Html, div, text)
import Html.Styled.Attributes as Attributes
import Html.Styled.Events exposing (onClick)
import Html.Styled.Keyed
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.DisclosureIndicator.V2 as DisclosureIndicator
import Nri.Ui.Fonts.V1 as Fonts
{-| -}
type alias AccordionOptions entry msg =
{ entries : List ( entry, Bool )
, viewHeader : entry -> Html msg
, viewContent : entry -> Html msg
, customStyles : Maybe (entry -> StyleOptions)
, caret : Caret
, toggle : entry -> Bool -> msg
}
{-| -}
type alias StyleOptions =
{ entryStyles : List Style
, entryExpandedStyles : List Style
, entryClosedStyles : List Style
, headerStyles : List Style
, headerExpandedStyles : List Style
, headerClosedStyles : List Style
, contentStyles : List Style
}
defaultStyleOptions : StyleOptions
defaultStyleOptions =
{ entryStyles =
[ position relative
, marginBottom (px 10)
]
, entryExpandedStyles = []
, entryClosedStyles = []
, headerStyles =
[ displayFlex
, alignItems center
, boxSizing borderBox
, minWidth (pct 100)
, overflow hidden
, padding2 (px 8) (px 15)
, backgroundColor Colors.gray96
, Fonts.baseFont
, fontSize (px 16)
, fontWeight (int 600)
, cursor pointer
, lineHeight (num 1.2)
]
, headerExpandedStyles =
[ color Colors.navy
, borderRadius4 (px 8) (px 8) (px 0) (px 0)
]
, headerClosedStyles =
[ color Colors.azure
, borderRadius (px 8)
]
, contentStyles = [ overflow hidden ]
}
{-| DefaultCaret is the blue caret
-}
type Caret
= DefaultCaret
| WhiteCaret
| NoneCaret
{-| -}
view : AccordionOptions entry msg -> Html msg
view { entries, viewHeader, viewContent, customStyles, caret, toggle } =
div
[ Attributes.class "accordion"
, Attributes.attribute "role" "tablist"
, Attributes.attribute "aria-live" "polite"
]
(List.map (viewEntry viewHeader viewContent customStyles caret toggle) entries)
{-| If your accordion's rows can be moved around, use viewKeyed. It prevents
the caret's animation from firing off incorrectly when rows move.
-}
viewKeyed : AccordionOptions entry msg -> (entry -> String) -> Html msg
viewKeyed { entries, viewHeader, viewContent, customStyles, caret, toggle } identifier =
div
[ Attributes.class "accordion"
, Attributes.attribute "role" "tablist"
, Attributes.attribute "aria-live" "polite"
]
[ Html.Styled.Keyed.node "div"
[]
(List.map
(\( entry, isExpanded ) ->
( identifier entry
, viewEntry viewHeader viewContent customStyles caret toggle ( entry, isExpanded )
)
)
entries
)
]
viewEntry :
(entry -> Html msg)
-> (entry -> Html msg)
-> Maybe (entry -> StyleOptions)
-> Caret
-> (entry -> Bool -> msg)
-> ( entry, Bool )
-> Html msg
viewEntry viewHeader viewContent styleOptions caret toggle ( entry, expanded ) =
let
newStyleOptions =
case Maybe.map (\styles_ -> styles_ entry) styleOptions of
Just { entryStyles, entryExpandedStyles, entryClosedStyles, headerStyles, headerExpandedStyles, headerClosedStyles, contentStyles } ->
{ entryStyles = defaultStyleOptions.entryStyles ++ entryStyles
, entryExpandedStyles = defaultStyleOptions.entryExpandedStyles ++ entryExpandedStyles
, entryClosedStyles = defaultStyleOptions.entryClosedStyles ++ entryClosedStyles
, headerStyles = defaultStyleOptions.headerStyles ++ headerStyles
, headerExpandedStyles = defaultStyleOptions.headerExpandedStyles ++ headerExpandedStyles
, headerClosedStyles = defaultStyleOptions.headerClosedStyles ++ headerClosedStyles
, contentStyles = defaultStyleOptions.contentStyles ++ contentStyles
}
Nothing ->
defaultStyleOptions
styles =
if expanded then
{ entry = newStyleOptions.entryStyles ++ newStyleOptions.entryExpandedStyles
, header = newStyleOptions.headerStyles ++ newStyleOptions.headerExpandedStyles
, content = newStyleOptions.contentStyles
}
else
{ entry = newStyleOptions.entryStyles ++ newStyleOptions.entryClosedStyles
, header = newStyleOptions.headerStyles ++ newStyleOptions.headerClosedStyles
, content = [ maxHeight (px 0) ]
}
in
div
[ entryClass expanded
, Attributes.attribute "role" "tab"
, Attributes.css styles.entry
]
[ entryHeader expanded viewHeader styles.header caret toggle entry
, entryPanel expanded viewContent styles.content entry
]
{-| Used for tests that rely on classnames, and couple edge cases where we need to override styles using sass
-}
entryClass : Bool -> Attribute msg
entryClass expanded =
Attributes.classList
[ ( "accordion-entry", True )
, ( "accordion-entry-state-expanded", expanded )
, ( "accordion-entry-state-collapsed", not expanded )
]
entryHeader :
Bool
-> (entry -> Html msg)
-> List Style
-> Caret
-> (entry -> Bool -> msg)
-> entry
-> Html msg
entryHeader expanded viewHeader styles caret toggle entry =
div
[ Attributes.class "accordion-entry-header"
, onClick (toggle entry (not expanded))
, Attributes.css styles
, Role.button
, Attributes.tabindex 0
]
[ viewCaret expanded caret
, viewHeader entry
]
-- TODO change content/body instances to panel
entryPanel : Bool -> (entry -> Html msg) -> List Style -> entry -> Html msg
entryPanel expanded viewContent styles entry =
div
[ Attributes.class "accordion-entry-panel"
, Attributes.hidden (not expanded)
, Attributes.attribute "role" "tabpanel"
, Attributes.css styles
]
[ viewContent entry ]
{-| Just the caret!
-}
viewCaret : Bool -> Caret -> Html msg
viewCaret expanded caret =
case caret of
DefaultCaret ->
DisclosureIndicator.large
[ marginRight (px 8)
, minWidth (px 17)
]
expanded
WhiteCaret ->
DisclosureIndicator.large
[ marginRight (px 8)
, minWidth (px 17)
, Css.Global.descendants
[ Css.Global.everything [ color Colors.white ] ]
]
expanded
NoneCaret ->
text ""

View File

@ -0,0 +1,125 @@
module Examples.Accordion exposing
( example
, Msg, State, init, update
)
{-|
@docs example, styles
-}
import Css exposing (..)
import Dict exposing (Dict)
import Html.Styled as Html
import Html.Styled.Attributes exposing (css)
import ModuleExample exposing (Category(..), ModuleExample)
import Nri.Ui.Accordion.V1 as Accordion
import Nri.Ui.Colors.V1 as Colors
{-| -}
example : (Msg -> msg) -> State -> ModuleExample msg
example parentMessage model =
{ name = "Nri.Ui.Accordion.V1"
, category = Layout
, content =
[ Html.h5 [] [ Html.text "Accordion.view with default styles" ]
, Accordion.view
{ entries =
[ { id = 1, title = "Entry 1", content = "Content for the first accordion" }
, { id = 2, title = "Entry 2", content = "Content for the second accordion" }
, { id = 3, title = "Super long entry that is very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long", content = "Content for the third accordion" }
]
|> List.map
(\entry ->
( entry, Dict.get entry.id model |> Maybe.withDefault False )
)
, viewHeader = .title >> Html.text
, viewContent = .content >> Html.text
, customStyles = Nothing
, toggle = \entry toExpand -> Toggle entry.id toExpand
, caret = Accordion.DefaultCaret
}
, Html.h5 [] [ Html.text "Accordion.view with custom styles from peer reviews" ]
, Accordion.view
{ entries =
[ { id = 4
, title = "Firstname Lastname"
, content =
Html.div [ css [ fontSize (px 13) ] ] [ Html.text "has not started writing" ]
}
, { id = 5
, title = "LongFirstnameAnd EvenLongerLastname"
, content =
Html.div [ css [ fontSize (px 13) ] ] [ Html.text "has started writing" ]
}
]
|> List.map
(\entry ->
( entry, Dict.get entry.id model |> Maybe.withDefault False )
)
, viewHeader = .title >> Html.text
, viewContent = .content
, customStyles =
Just
(\_ ->
{ entryStyles =
[ borderTop3 (px 1) solid Colors.gray75
, marginBottom zero
, width (px 284)
]
, entryExpandedStyles = []
, entryClosedStyles = []
, headerStyles =
[ height (px 46)
, paddingLeft (px 8)
, paddingRight (px 8)
, Css.alignItems Css.center
]
, headerExpandedStyles =
[ backgroundColor Colors.gray96
, borderRadius zero
]
, headerClosedStyles = [ backgroundColor transparent ]
, contentStyles =
[ backgroundColor Colors.gray96
, paddingLeft (px 8)
, paddingRight (px 8)
, paddingBottom (px 8)
]
}
)
, toggle = \entry toExpand -> Toggle entry.id toExpand
, caret = Accordion.DefaultCaret
}
]
|> List.map (Html.map parentMessage)
}
type Msg
= Toggle Int Bool
{-| -}
init : State
init =
Dict.fromList
[ ( 1, False )
, ( 2, False )
, ( 3, False )
, ( 4, False )
, ( 5, False )
]
{-| -}
type alias State =
Dict Int Bool
{-| -}
update : Msg -> State -> State
update (Toggle id toExpanded) model =
Dict.insert id toExpanded model

View File

@ -34,6 +34,7 @@ type Category
| Buttons
| Icons
| Widgets
| Layout
| Messaging
| Modals
| Colors
@ -56,6 +57,9 @@ categoryFromString string =
"Widgets" ->
Ok Widgets
"Layout" ->
Ok Layout
"Buttons" ->
Ok Buttons
@ -96,6 +100,9 @@ categoryForDisplay category =
Widgets ->
"Widgets"
Layout ->
"Layout"
Buttons ->
"Buttons and Links"

View File

@ -1,6 +1,7 @@
module NriModules exposing (ModuleStates, Msg, init, nriThemedModules, subscriptions, update)
import Assets exposing (assets)
import Examples.Accordion
import Examples.Alert
import Examples.AssignmentIcon
import Examples.BannerAlert
@ -36,7 +37,8 @@ import Url exposing (Url)
type alias ModuleStates =
{ buttonExampleState : Examples.Button.State Msg
{ accordionExampleState : Examples.Accordion.State
, buttonExampleState : Examples.Button.State Msg
, bannerAlertExampleState : Examples.BannerAlert.State
, clickableTextExampleState : Examples.ClickableText.State
, checkboxExampleState : Examples.Checkbox.State
@ -58,7 +60,8 @@ type alias ModuleStates =
init : ModuleStates
init =
{ buttonExampleState = Examples.Button.init assets
{ accordionExampleState = Examples.Accordion.init
, buttonExampleState = Examples.Button.init assets
, bannerAlertExampleState = Examples.BannerAlert.init
, clickableTextExampleState = Examples.ClickableText.init assets
, checkboxExampleState = Examples.Checkbox.init
@ -79,7 +82,8 @@ init =
type Msg
= ButtonExampleMsg (Examples.Button.Msg Msg)
= AccordionExampleMsg Examples.Accordion.Msg
| ButtonExampleMsg (Examples.Button.Msg Msg)
| BannerAlertExampleMsg Examples.BannerAlert.Msg
| ClickableTextExampleMsg Examples.ClickableText.Msg
| CheckboxExampleMsg Examples.Checkbox.Msg
@ -103,6 +107,14 @@ type Msg
update : Msg -> ModuleStates -> ( ModuleStates, Cmd Msg )
update outsideMsg moduleStates =
case outsideMsg of
AccordionExampleMsg msg ->
( { moduleStates
| accordionExampleState =
Examples.Accordion.update msg moduleStates.accordionExampleState
}
, Cmd.none
)
ButtonExampleMsg msg ->
let
( buttonExampleState, cmd ) =
@ -284,6 +296,7 @@ container width children =
nriThemedModules : ModuleStates -> List (ModuleExample Msg)
nriThemedModules model =
[ Examples.Alert.example
, Examples.Accordion.example AccordionExampleMsg model.accordionExampleState
, Examples.BannerAlert.example BannerAlertExampleMsg model.bannerAlertExampleState
, Examples.Button.example (exampleMessages ButtonExampleMsg) model.buttonExampleState
, Examples.Callout.example

View File

@ -133,17 +133,18 @@ navigation route =
, (categoryLink (route == Routes.All) "#" "All"
:: List.map
navLink
[ Messaging
, Animations
[ Animations
, Buttons
, Colors
, Pages
, Icons
, Inputs
, Layout
, Modals
, Pages
, Tables
, Text
, Widgets
, Messaging
]
)
|> List.map toNavLi