mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-12-20 20:21:33 +03:00
adds 'SideNav.V2'
This commit is contained in:
parent
c6af58aa8c
commit
e5845c860f
450
src/Nri/Ui/SideNav/V2.elm
Normal file
450
src/Nri/Ui/SideNav/V2.elm
Normal file
@ -0,0 +1,450 @@
|
||||
module Nri.Ui.SideNav.V2 exposing
|
||||
( view, Config
|
||||
, entry, entryWithChildren, html, Entry
|
||||
, icon, custom, css, nriDescription, testId, id
|
||||
, onClick
|
||||
, href, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
|
||||
, primary, secondary
|
||||
, premiumDisplay
|
||||
)
|
||||
|
||||
{-| Changes from V1:
|
||||
|
||||
- Use PremiumDisplay instead of PremiumLevel
|
||||
|
||||
@docs view, Config
|
||||
@docs entry, entryWithChildren, html, Entry
|
||||
@docs icon, custom, css, nriDescription, testId, id
|
||||
|
||||
|
||||
## Behavior
|
||||
|
||||
@docs onClick
|
||||
@docs href, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
|
||||
|
||||
|
||||
## Change the color scheme
|
||||
|
||||
@docs primary, secondary
|
||||
|
||||
|
||||
## Change the state
|
||||
|
||||
@docs premiumDisplay
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled exposing (..)
|
||||
import Accessibility.Styled.Style as Style
|
||||
import ClickableAttributes exposing (ClickableAttributes)
|
||||
import Css exposing (..)
|
||||
import Html.Styled
|
||||
import Html.Styled.Attributes as Attributes
|
||||
import Html.Styled.Events as Events
|
||||
import Nri.Ui
|
||||
import Nri.Ui.ClickableText.V3 as ClickableText
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Data.PremiumDisplay as PremiumDisplay exposing (PremiumDisplay)
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.Html.Attributes.V2 as ExtraAttributes
|
||||
import Nri.Ui.Html.V3 exposing (viewJust)
|
||||
import Nri.Ui.Svg.V1 as Svg exposing (Svg)
|
||||
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||
|
||||
|
||||
{-| Use `entry` to create a sidebar entry.
|
||||
-}
|
||||
type Entry route msg
|
||||
= Entry (List (Entry route msg)) (EntryConfig route msg)
|
||||
| Html (List (Html msg))
|
||||
|
||||
|
||||
{-| -}
|
||||
entry : String -> List (Attribute route msg) -> Entry route msg
|
||||
entry title attributes =
|
||||
attributes
|
||||
|> List.foldl (\(Attribute attribute) b -> attribute b) (build title)
|
||||
|> Entry []
|
||||
|
||||
|
||||
{-| -}
|
||||
entryWithChildren : String -> List (Attribute route msg) -> List (Entry route msg) -> Entry route msg
|
||||
entryWithChildren title attributes children =
|
||||
attributes
|
||||
|> List.foldl (\(Attribute attribute) b -> attribute b) (build title)
|
||||
|> Entry children
|
||||
|
||||
|
||||
{-| -}
|
||||
html : List (Html msg) -> Entry route msg
|
||||
html =
|
||||
Html
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Config route msg =
|
||||
{ isCurrentRoute : route -> Bool
|
||||
, routeToString : route -> String
|
||||
, onSkipNav : msg
|
||||
, css : List Style
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
view : Config route msg -> List (Entry route msg) -> Html msg
|
||||
view config entries =
|
||||
styled nav
|
||||
[ flexBasis (px 250)
|
||||
, flexShrink (num 0)
|
||||
, borderRadius (px 8)
|
||||
, backgroundColor Colors.gray96
|
||||
, padding (px 20)
|
||||
, marginRight (px 20)
|
||||
, batch config.css
|
||||
]
|
||||
[]
|
||||
(viewSkipLink config.onSkipNav
|
||||
:: List.map (viewSidebarEntry config []) entries
|
||||
)
|
||||
|
||||
|
||||
viewSkipLink : msg -> Html msg
|
||||
viewSkipLink onSkip =
|
||||
ClickableText.button "Skip to main content"
|
||||
[ ClickableText.icon UiIcon.arrowPointingRight
|
||||
, ClickableText.small
|
||||
, ClickableText.css
|
||||
[ Css.pseudoClass "not(:focus)"
|
||||
[ Style.invisibleStyle
|
||||
]
|
||||
]
|
||||
, ClickableText.onClick onSkip
|
||||
]
|
||||
|
||||
|
||||
viewSidebarEntry : Config route msg -> List Css.Style -> Entry route msg -> Html msg
|
||||
viewSidebarEntry config extraStyles entry_ =
|
||||
case entry_ of
|
||||
Entry children entryConfig ->
|
||||
if entryConfig.premiumDisplay == PremiumDisplay.PremiumLocked then
|
||||
viewLockedEntry extraStyles entryConfig
|
||||
|
||||
else if anyLinkDescendants (isCurrentRoute config) children then
|
||||
div [ Attributes.css extraStyles ]
|
||||
(styled span
|
||||
(sharedEntryStyles
|
||||
++ [ backgroundColor Colors.gray92
|
||||
, color Colors.navy
|
||||
, fontWeight bold
|
||||
, cursor default
|
||||
, marginBottom (px 10)
|
||||
]
|
||||
)
|
||||
[]
|
||||
[ text entryConfig.title ]
|
||||
:: List.map (viewSidebarEntry config [ marginLeft (px 20) ]) children
|
||||
)
|
||||
|
||||
else
|
||||
viewSidebarLeaf config extraStyles entryConfig
|
||||
|
||||
Html html_ ->
|
||||
div [ Attributes.css extraStyles ] html_
|
||||
|
||||
|
||||
isCurrentRoute : Config route msg -> EntryConfig route msg -> Bool
|
||||
isCurrentRoute config { route } =
|
||||
Maybe.map config.isCurrentRoute route
|
||||
|> Maybe.withDefault False
|
||||
|
||||
|
||||
anyLinkDescendants : (EntryConfig route msg -> Bool) -> List (Entry route msg) -> Bool
|
||||
anyLinkDescendants f children =
|
||||
List.any
|
||||
(\entry_ ->
|
||||
case entry_ of
|
||||
Entry children_ entryConfig ->
|
||||
f entryConfig || anyLinkDescendants f children_
|
||||
|
||||
Html _ ->
|
||||
False
|
||||
)
|
||||
children
|
||||
|
||||
|
||||
viewSidebarLeaf :
|
||||
Config route msg
|
||||
-> List Style
|
||||
-> EntryConfig route msg
|
||||
-> Html msg
|
||||
viewSidebarLeaf config extraStyles entryConfig =
|
||||
let
|
||||
( linkFunctionName, attributes ) =
|
||||
ClickableAttributes.toLinkAttributes config.routeToString
|
||||
entryConfig.clickableAttributes
|
||||
in
|
||||
Nri.Ui.styled Html.Styled.a
|
||||
("Nri-Ui-SideNav-" ++ linkFunctionName)
|
||||
(sharedEntryStyles
|
||||
++ extraStyles
|
||||
++ (if isCurrentRoute config entryConfig then
|
||||
[ backgroundColor Colors.glacier
|
||||
, color Colors.navy
|
||||
, fontWeight bold
|
||||
, visited [ color Colors.navy ]
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
++ entryConfig.customStyles
|
||||
)
|
||||
(attributes ++ entryConfig.customAttributes)
|
||||
[ viewJust
|
||||
(\icon_ ->
|
||||
icon_
|
||||
|> Svg.withWidth (px 20)
|
||||
|> Svg.withHeight (px 20)
|
||||
|> Svg.withCss [ marginRight (px 5) ]
|
||||
|> Svg.toHtml
|
||||
)
|
||||
entryConfig.icon
|
||||
, text entryConfig.title
|
||||
]
|
||||
|
||||
|
||||
viewLockedEntry : List Style -> EntryConfig route msg -> Html msg
|
||||
viewLockedEntry extraStyles entryConfig =
|
||||
styled Html.Styled.button
|
||||
[ batch sharedEntryStyles
|
||||
, important (color Colors.gray45)
|
||||
, borderWidth zero
|
||||
, batch extraStyles
|
||||
]
|
||||
(case entryConfig.onLockedContent of
|
||||
Just event ->
|
||||
Events.onClick event :: entryConfig.customAttributes
|
||||
|
||||
Nothing ->
|
||||
entryConfig.customAttributes
|
||||
)
|
||||
[ UiIcon.premiumLock
|
||||
|> Svg.withWidth (px 17)
|
||||
|> Svg.withHeight (px 25)
|
||||
|> Svg.withCss [ marginRight (px 10), minWidth (px 17) ]
|
||||
|> Svg.toHtml
|
||||
, text entryConfig.title
|
||||
]
|
||||
|
||||
|
||||
sharedEntryStyles : List Style
|
||||
sharedEntryStyles =
|
||||
[ paddingLeft (px 20)
|
||||
, paddingRight (px 20)
|
||||
, height (px 45)
|
||||
, displayFlex
|
||||
, borderRadius (px 8)
|
||||
, alignItems center
|
||||
, Fonts.baseFont
|
||||
, color Colors.navy
|
||||
, backgroundColor transparent
|
||||
, textDecoration none
|
||||
, fontSize (px 15)
|
||||
, fontWeight (int 600)
|
||||
, textAlign left
|
||||
, cursor pointer
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- Entry Customization helpers
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias EntryConfig route msg =
|
||||
{ icon : Maybe Svg
|
||||
, title : String
|
||||
, route : Maybe route
|
||||
, clickableAttributes : ClickableAttributes route msg
|
||||
, customAttributes : List (Html.Styled.Attribute msg)
|
||||
, customStyles : List Style
|
||||
, premiumDisplay : PremiumDisplay
|
||||
, onLockedContent : Maybe msg
|
||||
}
|
||||
|
||||
|
||||
build : String -> EntryConfig route msg
|
||||
build title =
|
||||
{ icon = Nothing
|
||||
, title = title
|
||||
, route = Nothing
|
||||
, clickableAttributes = ClickableAttributes.init
|
||||
, customAttributes = []
|
||||
, customStyles = []
|
||||
, premiumDisplay = PremiumDisplay.Free
|
||||
, onLockedContent = Nothing
|
||||
}
|
||||
|
||||
|
||||
type Attribute route msg
|
||||
= Attribute (EntryConfig route msg -> EntryConfig route msg)
|
||||
|
||||
|
||||
{-| -}
|
||||
icon : Svg -> Attribute route msg
|
||||
icon icon_ =
|
||||
Attribute (\attributes -> { attributes | icon = Just icon_ })
|
||||
|
||||
|
||||
{-| -}
|
||||
premiumDisplay : PremiumDisplay -> msg -> Attribute route msg
|
||||
premiumDisplay display ifLocked =
|
||||
Attribute
|
||||
(\attributes ->
|
||||
{ attributes
|
||||
| premiumDisplay = display
|
||||
, onLockedContent = Just ifLocked
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| Use this helper to add custom attributes.
|
||||
|
||||
Do NOT use this helper to add css styles, as they may not be applied the way
|
||||
you want/expect if underlying Button styles change.
|
||||
Instead, please use the `css` helper.
|
||||
|
||||
-}
|
||||
custom : List (Html.Styled.Attribute msg) -> Attribute route msg
|
||||
custom attributes =
|
||||
Attribute
|
||||
(\config ->
|
||||
{ config
|
||||
| customAttributes = List.append config.customAttributes attributes
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
nriDescription : String -> Attribute route msg
|
||||
nriDescription description =
|
||||
custom [ ExtraAttributes.nriDescription description ]
|
||||
|
||||
|
||||
{-| -}
|
||||
testId : String -> Attribute route msg
|
||||
testId id_ =
|
||||
custom [ ExtraAttributes.testId id_ ]
|
||||
|
||||
|
||||
{-| -}
|
||||
id : String -> Attribute route msg
|
||||
id id_ =
|
||||
custom [ Attributes.id id_ ]
|
||||
|
||||
|
||||
{-| -}
|
||||
css : List Style -> Attribute route msg
|
||||
css styles =
|
||||
Attribute
|
||||
(\config ->
|
||||
{ config
|
||||
| customStyles = List.append config.customStyles styles
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
primary : Attribute route msg
|
||||
primary =
|
||||
Attribute (\attributes -> { attributes | customStyles = [] })
|
||||
|
||||
|
||||
{-| -}
|
||||
secondary : Attribute route msg
|
||||
secondary =
|
||||
Attribute
|
||||
(\attributes ->
|
||||
{ attributes
|
||||
| customStyles =
|
||||
[ backgroundColor Colors.white
|
||||
, boxShadow3 zero (px 2) Colors.gray75
|
||||
, border3 (px 1) solid Colors.gray75
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
-- LINKING, CLICKING, and TRACKING BEHAVIOR
|
||||
|
||||
|
||||
setClickableAttributes :
|
||||
Maybe route
|
||||
-> (ClickableAttributes route msg -> ClickableAttributes route msg)
|
||||
-> Attribute route msg
|
||||
setClickableAttributes route apply =
|
||||
Attribute
|
||||
(\attributes ->
|
||||
{ attributes
|
||||
| route =
|
||||
case route of
|
||||
Just r ->
|
||||
Just r
|
||||
|
||||
Nothing ->
|
||||
attributes.route
|
||||
, clickableAttributes = apply attributes.clickableAttributes
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
onClick : msg -> Attribute route msg
|
||||
onClick msg =
|
||||
setClickableAttributes Nothing (ClickableAttributes.onClick msg)
|
||||
|
||||
|
||||
{-| -}
|
||||
href : route -> Attribute route msg
|
||||
href route =
|
||||
setClickableAttributes (Just route) (ClickableAttributes.href route)
|
||||
|
||||
|
||||
{-| Use this link for routing within a single page app.
|
||||
|
||||
This will make a normal <a> tag, but change the Events.onClick behavior to avoid reloading the page.
|
||||
|
||||
See <https://github.com/elm-lang/html/issues/110> for details on this implementation.
|
||||
|
||||
-}
|
||||
linkSpa : route -> Attribute route msg
|
||||
linkSpa route =
|
||||
setClickableAttributes (Just route)
|
||||
(ClickableAttributes.linkSpa route)
|
||||
|
||||
|
||||
{-| -}
|
||||
linkWithMethod : { method : String, url : route } -> Attribute route msg
|
||||
linkWithMethod config =
|
||||
setClickableAttributes (Just config.url)
|
||||
(ClickableAttributes.linkWithMethod config)
|
||||
|
||||
|
||||
{-| -}
|
||||
linkWithTracking : { track : msg, url : route } -> Attribute route msg
|
||||
linkWithTracking config =
|
||||
setClickableAttributes (Just config.url)
|
||||
(ClickableAttributes.linkWithTracking config)
|
||||
|
||||
|
||||
{-| -}
|
||||
linkExternal : String -> Attribute route msg
|
||||
linkExternal url =
|
||||
setClickableAttributes Nothing (ClickableAttributes.linkExternal url)
|
||||
|
||||
|
||||
{-| -}
|
||||
linkExternalWithTracking : { track : msg, url : String } -> Attribute route msg
|
||||
linkExternalWithTracking config =
|
||||
setClickableAttributes Nothing (ClickableAttributes.linkExternalWithTracking config)
|
Loading…
Reference in New Issue
Block a user