mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-13 07:48:26 +03:00
Adds accordion v1 (copied from the monolith)
This commit is contained in:
parent
5e08b015d1
commit
4cd17b5e63
3
elm.json
3
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
237
src/Nri/Ui/Accordion/V1.elm
Normal file
237
src/Nri/Ui/Accordion/V1.elm
Normal 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 ""
|
125
styleguide-app/Examples/Accordion.elm
Normal file
125
styleguide-app/Examples/Accordion.elm
Normal 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
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user