From 4cd17b5e630590c316e64ae6dfc3d936eeb068ca Mon Sep 17 00:00:00 2001 From: Tessa Kelly Date: Thu, 24 Oct 2019 10:23:19 -0700 Subject: [PATCH] Adds accordion v1 (copied from the monolith) --- elm.json | 3 +- src/Nri/Ui/Accordion/V1.elm | 237 ++++++++++++++++++++++++++ styleguide-app/Examples/Accordion.elm | 125 ++++++++++++++ styleguide-app/ModuleExample.elm | 7 + styleguide-app/NriModules.elm | 19 ++- styleguide-app/View.elm | 7 +- 6 files changed, 391 insertions(+), 7 deletions(-) create mode 100644 src/Nri/Ui/Accordion/V1.elm create mode 100644 styleguide-app/Examples/Accordion.elm diff --git a/elm.json b/elm.json index 803e1759..7dd9279e 100644 --- a/elm.json +++ b/elm.json @@ -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" } -} \ No newline at end of file +} diff --git a/src/Nri/Ui/Accordion/V1.elm b/src/Nri/Ui/Accordion/V1.elm new file mode 100644 index 00000000..f5091ed7 --- /dev/null +++ b/src/Nri/Ui/Accordion/V1.elm @@ -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 "" diff --git a/styleguide-app/Examples/Accordion.elm b/styleguide-app/Examples/Accordion.elm new file mode 100644 index 00000000..16370bdb --- /dev/null +++ b/styleguide-app/Examples/Accordion.elm @@ -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 diff --git a/styleguide-app/ModuleExample.elm b/styleguide-app/ModuleExample.elm index 68f1b09b..eab292ef 100644 --- a/styleguide-app/ModuleExample.elm +++ b/styleguide-app/ModuleExample.elm @@ -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" diff --git a/styleguide-app/NriModules.elm b/styleguide-app/NriModules.elm index 3ca0e904..4311c003 100644 --- a/styleguide-app/NriModules.elm +++ b/styleguide-app/NriModules.elm @@ -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 diff --git a/styleguide-app/View.elm b/styleguide-app/View.elm index 992228f9..6492b6e2 100644 --- a/styleguide-app/View.elm +++ b/styleguide-app/View.elm @@ -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