From de8903eeeede8f7d10af448471c13b4c5e401c0a Mon Sep 17 00:00:00 2001 From: Tessa Kelly Date: Fri, 3 Dec 2021 10:47:40 -0800 Subject: [PATCH] Use ClickableAttribute helpers --- src/Nri/Ui/SideNav/V1.elm | 254 +++++++++++++++++++++++++++++++++----- styleguide-app/Main.elm | 60 ++++----- 2 files changed, 245 insertions(+), 69 deletions(-) diff --git a/src/Nri/Ui/SideNav/V1.elm b/src/Nri/Ui/SideNav/V1.elm index 5b1d61f1..dddb7520 100644 --- a/src/Nri/Ui/SideNav/V1.elm +++ b/src/Nri/Ui/SideNav/V1.elm @@ -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 tag, but change the Events.onClick behavior to avoid reloading the page. + +See 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) diff --git a/styleguide-app/Main.elm b/styleguide-app/Main.elm index 959a09cd..4660d318 100644 --- a/styleguide-app/Main.elm +++ b/styleguide-app/Main.elm @@ -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