Use ClickableAttribute helpers

This commit is contained in:
Tessa Kelly 2021-12-03 10:47:40 -08:00
parent 5d1dbdb367
commit de8903eeee
2 changed files with 245 additions and 69 deletions

View File

@ -1,26 +1,50 @@
module Nri.Ui.SideNav.V1 exposing
( view, Config, Entry
, entry, EntryConfig
, withBorderStyles
( view, Config
, entry, Entry
, icon, custom, css, nriDescription, testId, id
, onClick
, href, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
, primary, secondary
, premiumLevel
)
{-|
@docs view, Config, Entry
@docs entry, EntryConfig
@docs view, Config
@docs entry, 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 premiumLevel
-}
import Accessibility.Styled exposing (..)
import ClickableAttributes exposing (ClickableAttributes)
import Css exposing (..)
import Css.Media as Media
import Html.Styled
import Html.Styled.Attributes as Attributes exposing (css)
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.PremiumLevel as PremiumLevel exposing (PremiumLevel)
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
@ -35,20 +59,11 @@ type Entry route msg
{-| -}
type alias EntryConfig route msg =
{ icon : Maybe Svg
, title : String
, route : route
, attributes : List (Html.Styled.Attribute msg)
, children : List (Entry route msg)
, premiumLevel : PremiumLevel
}
{-| -}
entry : EntryConfig route msg -> Entry route msg
entry =
Entry
entry : String -> route -> List (Attribute route msg) -> Entry route msg
entry title route attributes =
attributes
|> List.foldl (\(Attribute attribute) b -> attribute b) (build title route)
|> Entry
{-| -}
@ -106,7 +121,7 @@ viewSidebarEntry : Config route msg -> List Css.Style -> Entry route msg -> Html
viewSidebarEntry config extraStyles (Entry entry_) =
if PremiumLevel.allowedFor entry_.premiumLevel config.userPremiumLevel then
if anyLinkDescendants (.route >> config.isCurrentRoute) entry_ then
div [ css extraStyles ]
div [ Attributes.css extraStyles ]
(styled span
(sharedEntryStyles
++ [ backgroundColor Colors.gray92
@ -139,11 +154,16 @@ viewSidebarLeaf :
-> List Style
-> EntryConfig route msg
-> Html msg
viewSidebarLeaf config extraStyles { icon, title, route, attributes } =
styled Html.Styled.a
viewSidebarLeaf config extraStyles entryConfig =
let
( linkFunctionName, attributes ) =
ClickableAttributes.toLinkAttributes entryConfig.clickableAttributes
in
Nri.Ui.styled Html.Styled.a
("Nri-Ui-SideNav-" ++ linkFunctionName)
(sharedEntryStyles
++ extraStyles
++ (if config.isCurrentRoute route then
++ (if config.isCurrentRoute entryConfig.route then
[ backgroundColor Colors.glacier
, color Colors.navy
, fontWeight bold
@ -153,8 +173,9 @@ viewSidebarLeaf config extraStyles { icon, title, route, attributes } =
else
[]
)
++ entryConfig.customStyles
)
attributes
(attributes ++ entryConfig.customAttributes)
[ viewJust
(\icon_ ->
icon_
@ -163,8 +184,8 @@ viewSidebarLeaf config extraStyles { icon, title, route, attributes } =
|> Svg.withCss [ marginRight (px 5) ]
|> Svg.toHtml
)
icon
, text title
entryConfig.icon
, text entryConfig.title
]
@ -212,10 +233,177 @@ sharedEntryStyles =
]
withBorderStyles : List Style
withBorderStyles =
-- TODO: add a convenient way to use these styels
[ backgroundColor Colors.white
, boxShadow3 zero (px 2) Colors.gray75
, border3 (px 1) solid Colors.gray75
]
-- Entry Customization helpers
{-| -}
type alias EntryConfig route msg =
{ icon : Maybe Svg
, title : String
, route : route
, clickableAttributes : ClickableAttributes msg
, customAttributes : List (Html.Styled.Attribute msg)
, customStyles : List Style
, children : List (Entry route msg)
, premiumLevel : PremiumLevel
}
build : String -> route -> EntryConfig route msg
build title route =
{ icon = Nothing
, title = title
, route = route
, clickableAttributes = ClickableAttributes.init
, customAttributes = []
, customStyles = []
, children = []
, premiumLevel = PremiumLevel.Free
}
type Attribute route msg
= Attribute (EntryConfig route msg -> EntryConfig route msg)
{-| -}
icon : Svg -> Attribute route msg
icon icon_ =
Attribute (\attributes -> { attributes | icon = Just icon_ })
{-| -}
premiumLevel : PremiumLevel -> msg -> Attribute route msg
premiumLevel level ifLocked =
-- TODO: adds the lock click behavior
Attribute (\attributes -> { attributes | premiumLevel = level })
{-| 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 :
(ClickableAttributes msg -> ClickableAttributes msg)
-> Attribute route msg
setClickableAttributes apply =
Attribute
(\attributes ->
{ attributes | clickableAttributes = apply attributes.clickableAttributes }
)
{-| -}
onClick : msg -> Attribute route msg
onClick msg =
setClickableAttributes (ClickableAttributes.onClick msg)
{-| -}
href : String -> Attribute route msg
href url =
setClickableAttributes (ClickableAttributes.href url)
{-| 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 : String -> Attribute route msg
linkSpa url =
setClickableAttributes (ClickableAttributes.linkSpa url)
{-| -}
linkWithMethod : { method : String, url : String } -> Attribute route msg
linkWithMethod config =
setClickableAttributes (ClickableAttributes.linkWithMethod config)
{-| -}
linkWithTracking : { track : msg, url : String } -> Attribute route msg
linkWithTracking config =
setClickableAttributes (ClickableAttributes.linkWithTracking config)
{-| -}
linkExternal : String -> Attribute route msg
linkExternal url =
setClickableAttributes (ClickableAttributes.linkExternal url)
{-| -}
linkExternalWithTracking : { track : msg, url : String } -> Attribute route msg
linkExternalWithTracking config =
setClickableAttributes (ClickableAttributes.linkExternalWithTracking config)

View File

@ -226,46 +226,34 @@ viewPreviews containerId examples =
navigation : Route -> Html Msg
navigation currentRoute =
let
toNavLinkConfig : Category -> SideNav.EntryConfig Route Msg
toNavLinkConfig : Category -> SideNav.Entry Route Msg
toNavLinkConfig category =
{ icon = Nothing
, title = Category.forDisplay category
, route = Routes.Category category
, attributes = [ href (Routes.toString (Routes.Category category)) ]
, children = []
, premiumLevel = PremiumLevel.Free
}
SideNav.entry
(Category.forDisplay category)
(Routes.Category category)
[ -- TODO: we shouldn't require manually adding the href
SideNav.href (Routes.toString (Routes.Category category))
]
navLinks : List (SideNav.Entry Route Msg)
navLinks =
SideNav.entry
{ icon = Nothing
, title = "All"
, route = Routes.All
, attributes = [ href (Routes.toString Routes.All) ]
, children = []
, premiumLevel = PremiumLevel.Free
}
:: List.map (toNavLinkConfig >> SideNav.entry) Category.all
++ [ SideNav.entry
{ icon = Nothing
, title = "Example of Locked Premium content"
, route = Routes.All
, attributes = [ href (Routes.toString Routes.All) ]
, children = []
, premiumLevel = PremiumLevel.PremiumWithWriting
}
, SideNav.entry
{ icon = Just UiIcon.gear
, title = "Create your own"
, route = Routes.All
, attributes =
[ href (Routes.toString Routes.All)
, css SideNav.withBorderStyles
]
, children = []
, premiumLevel = PremiumLevel.Free
}
SideNav.entry "All"
Routes.All
[ SideNav.href (Routes.toString Routes.All)
]
:: List.map toNavLinkConfig Category.all
++ [ SideNav.entry "Example of Locked Premium content"
Routes.All
[ SideNav.premiumLevel PremiumLevel.PremiumWithWriting
NoOp
]
, SideNav.entry "Create your own"
-- TODO: support _no_ route
Routes.All
[ SideNav.icon UiIcon.gear
, SideNav.secondary
, SideNav.linkExternal "external-link"
]
]
in
SideNav.view