mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-23 16:32:11 +03:00
Merge pull request #794 from NoRedInk/hack-day/tessa/sidebar
Adds general sidenav component
This commit is contained in:
commit
524dd55301
1
elm.json
1
elm.json
@ -47,6 +47,7 @@
|
|||||||
"Nri.Ui.RadioButton.V3",
|
"Nri.Ui.RadioButton.V3",
|
||||||
"Nri.Ui.SegmentedControl.V14",
|
"Nri.Ui.SegmentedControl.V14",
|
||||||
"Nri.Ui.Select.V8",
|
"Nri.Ui.Select.V8",
|
||||||
|
"Nri.Ui.SideNav.V1",
|
||||||
"Nri.Ui.Slide.V1",
|
"Nri.Ui.Slide.V1",
|
||||||
"Nri.Ui.SlideModal.V2",
|
"Nri.Ui.SlideModal.V2",
|
||||||
"Nri.Ui.SortableTable.V2",
|
"Nri.Ui.SortableTable.V2",
|
||||||
|
@ -23,9 +23,10 @@ import Nri.Ui.Html.Attributes.V2 as AttributeExtras exposing (targetBlank)
|
|||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
type alias ClickableAttributes msg =
|
type alias ClickableAttributes route msg =
|
||||||
{ linkType : Link
|
{ linkType : Link
|
||||||
, url : String
|
, url : Maybe route
|
||||||
|
, urlString : Maybe String
|
||||||
, onClick : Maybe msg
|
, onClick : Maybe msg
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,58 +41,67 @@ type Link
|
|||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
init : ClickableAttributes msg
|
init : ClickableAttributes route msg
|
||||||
init =
|
init =
|
||||||
{ linkType = Default
|
{ linkType = Default
|
||||||
, url = "#"
|
, url = Nothing
|
||||||
|
, urlString = Nothing
|
||||||
, onClick = Nothing
|
, onClick = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
onClick : msg -> ClickableAttributes msg -> ClickableAttributes msg
|
onClick : msg -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||||
onClick msg clickableAttributes =
|
onClick msg clickableAttributes =
|
||||||
{ clickableAttributes | onClick = Just msg }
|
{ clickableAttributes | onClick = Just msg }
|
||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
href : String -> ClickableAttributes msg -> ClickableAttributes msg
|
href : route -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||||
href url clickableAttributes =
|
href url clickableAttributes =
|
||||||
{ clickableAttributes | url = url }
|
{ clickableAttributes | url = Just url }
|
||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
linkSpa : String -> ClickableAttributes msg -> ClickableAttributes msg
|
linkSpa : route -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||||
linkSpa url clickableAttributes =
|
linkSpa url clickableAttributes =
|
||||||
{ clickableAttributes | linkType = SinglePageApp, url = url }
|
{ clickableAttributes | linkType = SinglePageApp, url = Just url }
|
||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
linkWithMethod : { method : String, url : String } -> ClickableAttributes msg -> ClickableAttributes msg
|
linkWithMethod : { method : String, url : route } -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||||
linkWithMethod { method, url } clickableAttributes =
|
linkWithMethod { method, url } clickableAttributes =
|
||||||
{ clickableAttributes | linkType = WithMethod method, url = url }
|
{ clickableAttributes | linkType = WithMethod method, url = Just url }
|
||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
linkWithTracking : { track : msg, url : String } -> ClickableAttributes msg -> ClickableAttributes msg
|
linkWithTracking : { track : msg, url : route } -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||||
linkWithTracking { track, url } _ =
|
linkWithTracking { track, url } clickableAttributes =
|
||||||
{ linkType = WithTracking, url = url, onClick = Just track }
|
{ clickableAttributes
|
||||||
|
| linkType = WithTracking
|
||||||
|
, url = Just url
|
||||||
|
, onClick = Just track
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
linkExternal : String -> ClickableAttributes msg -> ClickableAttributes msg
|
linkExternal : String -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||||
linkExternal url clickableAttributes =
|
linkExternal url clickableAttributes =
|
||||||
{ clickableAttributes | linkType = External, url = url }
|
{ clickableAttributes | linkType = External, urlString = Just url }
|
||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
linkExternalWithTracking : { track : msg, url : String } -> ClickableAttributes msg -> ClickableAttributes msg
|
linkExternalWithTracking : { track : msg, url : String } -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||||
linkExternalWithTracking { track, url } _ =
|
linkExternalWithTracking { track, url } clickableAttributes =
|
||||||
{ linkType = ExternalWithTracking, url = url, onClick = Just track }
|
{ clickableAttributes
|
||||||
|
| linkType = ExternalWithTracking
|
||||||
|
, urlString = Just url
|
||||||
|
, onClick = Just track
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
toButtonAttributes : ClickableAttributes msg -> List (Attribute msg)
|
toButtonAttributes : ClickableAttributes route msg -> List (Attribute msg)
|
||||||
toButtonAttributes clickableAttributes =
|
toButtonAttributes clickableAttributes =
|
||||||
case clickableAttributes.onClick of
|
case clickableAttributes.onClick of
|
||||||
Just handler ->
|
Just handler ->
|
||||||
@ -102,12 +112,24 @@ toButtonAttributes clickableAttributes =
|
|||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
toLinkAttributes : ClickableAttributes msg -> ( String, List (Attribute msg) )
|
toLinkAttributes : (route -> String) -> ClickableAttributes route msg -> ( String, List (Attribute msg) )
|
||||||
toLinkAttributes clickableAttributes =
|
toLinkAttributes routeToString clickableAttributes =
|
||||||
|
let
|
||||||
|
stringUrl =
|
||||||
|
case ( clickableAttributes.urlString, clickableAttributes.url ) of
|
||||||
|
( Just url, _ ) ->
|
||||||
|
url
|
||||||
|
|
||||||
|
( _, Just route ) ->
|
||||||
|
routeToString route
|
||||||
|
|
||||||
|
( Nothing, Nothing ) ->
|
||||||
|
"#"
|
||||||
|
in
|
||||||
case clickableAttributes.linkType of
|
case clickableAttributes.linkType of
|
||||||
Default ->
|
Default ->
|
||||||
( "link"
|
( "link"
|
||||||
, [ Attributes.href clickableAttributes.url
|
, [ Attributes.href stringUrl
|
||||||
, Attributes.target "_self"
|
, Attributes.target "_self"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -116,17 +138,17 @@ toLinkAttributes clickableAttributes =
|
|||||||
( "linkSpa"
|
( "linkSpa"
|
||||||
, case clickableAttributes.onClick of
|
, case clickableAttributes.onClick of
|
||||||
Just handler ->
|
Just handler ->
|
||||||
[ Attributes.href clickableAttributes.url
|
[ Attributes.href stringUrl
|
||||||
, EventExtras.onClickPreventDefaultForLinkWithHref handler
|
, EventExtras.onClickPreventDefaultForLinkWithHref handler
|
||||||
]
|
]
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
[ Attributes.href clickableAttributes.url ]
|
[ Attributes.href stringUrl ]
|
||||||
)
|
)
|
||||||
|
|
||||||
WithMethod method ->
|
WithMethod method ->
|
||||||
( "linkWithMethod"
|
( "linkWithMethod"
|
||||||
, [ Attributes.href clickableAttributes.url
|
, [ Attributes.href stringUrl
|
||||||
, Attributes.attribute "data-method" method
|
, Attributes.attribute "data-method" method
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -135,18 +157,18 @@ toLinkAttributes clickableAttributes =
|
|||||||
( "linkWithTracking"
|
( "linkWithTracking"
|
||||||
, case clickableAttributes.onClick of
|
, case clickableAttributes.onClick of
|
||||||
Just track ->
|
Just track ->
|
||||||
[ Attributes.href clickableAttributes.url
|
[ Attributes.href stringUrl
|
||||||
, Events.preventDefaultOn "click"
|
, Events.preventDefaultOn "click"
|
||||||
(Json.Decode.succeed ( track, True ))
|
(Json.Decode.succeed ( track, True ))
|
||||||
]
|
]
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
[ Attributes.href clickableAttributes.url ]
|
[ Attributes.href stringUrl ]
|
||||||
)
|
)
|
||||||
|
|
||||||
External ->
|
External ->
|
||||||
( "linkExternal"
|
( "linkExternal"
|
||||||
, Attributes.href clickableAttributes.url
|
, Attributes.href stringUrl
|
||||||
:: targetBlank
|
:: targetBlank
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -154,13 +176,13 @@ toLinkAttributes clickableAttributes =
|
|||||||
( "linkExternalWithTracking"
|
( "linkExternalWithTracking"
|
||||||
, case clickableAttributes.onClick of
|
, case clickableAttributes.onClick of
|
||||||
Just handler ->
|
Just handler ->
|
||||||
[ Attributes.href clickableAttributes.url
|
[ Attributes.href stringUrl
|
||||||
, Events.onClick handler
|
, Events.onClick handler
|
||||||
, Events.on "auxclick" (Json.Decode.succeed handler)
|
, Events.on "auxclick" (Json.Decode.succeed handler)
|
||||||
]
|
]
|
||||||
++ targetBlank
|
++ targetBlank
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
Attributes.href clickableAttributes.url
|
Attributes.href stringUrl
|
||||||
:: targetBlank
|
:: targetBlank
|
||||||
)
|
)
|
||||||
|
@ -187,7 +187,7 @@ css styles =
|
|||||||
|
|
||||||
|
|
||||||
setClickableAttributes :
|
setClickableAttributes :
|
||||||
(ClickableAttributes msg -> ClickableAttributes msg)
|
(ClickableAttributes String msg -> ClickableAttributes String msg)
|
||||||
-> Attribute msg
|
-> Attribute msg
|
||||||
setClickableAttributes apply =
|
setClickableAttributes apply =
|
||||||
set
|
set
|
||||||
@ -467,7 +467,7 @@ type ButtonOrLink msg
|
|||||||
|
|
||||||
|
|
||||||
type alias ButtonOrLinkAttributes msg =
|
type alias ButtonOrLinkAttributes msg =
|
||||||
{ clickableAttributes : ClickableAttributes msg
|
{ clickableAttributes : ClickableAttributes String msg
|
||||||
, size : ButtonSize
|
, size : ButtonSize
|
||||||
, style : ColorPalette
|
, style : ColorPalette
|
||||||
, width : ButtonWidth
|
, width : ButtonWidth
|
||||||
@ -523,7 +523,7 @@ renderLink ((ButtonOrLink config) as link_) =
|
|||||||
getColorPalette link_
|
getColorPalette link_
|
||||||
|
|
||||||
( linkFunctionName, attributes ) =
|
( linkFunctionName, attributes ) =
|
||||||
ClickableAttributes.toLinkAttributes config.clickableAttributes
|
ClickableAttributes.toLinkAttributes identity config.clickableAttributes
|
||||||
in
|
in
|
||||||
Nri.Ui.styled Styled.a
|
Nri.Ui.styled Styled.a
|
||||||
(styledName linkFunctionName)
|
(styledName linkFunctionName)
|
||||||
|
@ -87,7 +87,7 @@ link name icon attributes =
|
|||||||
|
|
||||||
|
|
||||||
setClickableAttributes :
|
setClickableAttributes :
|
||||||
(ClickableAttributes msg -> ClickableAttributes msg)
|
(ClickableAttributes String msg -> ClickableAttributes String msg)
|
||||||
-> Attribute msg
|
-> Attribute msg
|
||||||
setClickableAttributes apply =
|
setClickableAttributes apply =
|
||||||
set
|
set
|
||||||
@ -384,7 +384,7 @@ type ButtonOrLink msg
|
|||||||
|
|
||||||
|
|
||||||
type alias ButtonOrLinkAttributes msg =
|
type alias ButtonOrLinkAttributes msg =
|
||||||
{ clickableAttributes : ClickableAttributes msg
|
{ clickableAttributes : ClickableAttributes String msg
|
||||||
, label : String
|
, label : String
|
||||||
, icon : Svg
|
, icon : Svg
|
||||||
, disabled : Bool
|
, disabled : Bool
|
||||||
@ -435,7 +435,7 @@ renderLink : ButtonOrLink msg -> Html msg
|
|||||||
renderLink ((ButtonOrLink config) as link_) =
|
renderLink ((ButtonOrLink config) as link_) =
|
||||||
let
|
let
|
||||||
( linkFunctionName, extraAttrs ) =
|
( linkFunctionName, extraAttrs ) =
|
||||||
ClickableAttributes.toLinkAttributes config.clickableAttributes
|
ClickableAttributes.toLinkAttributes identity config.clickableAttributes
|
||||||
|
|
||||||
theme =
|
theme =
|
||||||
if config.disabled then
|
if config.disabled then
|
||||||
|
@ -178,7 +178,7 @@ css styles =
|
|||||||
|
|
||||||
|
|
||||||
setClickableAttributes :
|
setClickableAttributes :
|
||||||
(ClickableAttributes msg -> ClickableAttributes msg)
|
(ClickableAttributes String msg -> ClickableAttributes String msg)
|
||||||
-> Attribute msg
|
-> Attribute msg
|
||||||
setClickableAttributes apply =
|
setClickableAttributes apply =
|
||||||
set
|
set
|
||||||
@ -269,7 +269,7 @@ link label_ attributes =
|
|||||||
|> List.foldl (\(Attribute attribute) l -> attribute l) defaults
|
|> List.foldl (\(Attribute attribute) l -> attribute l) defaults
|
||||||
|
|
||||||
( name, clickableAttributes ) =
|
( name, clickableAttributes ) =
|
||||||
ClickableAttributes.toLinkAttributes config.clickableAttributes
|
ClickableAttributes.toLinkAttributes identity config.clickableAttributes
|
||||||
in
|
in
|
||||||
Nri.Ui.styled Html.a
|
Nri.Ui.styled Html.a
|
||||||
(dataDescriptor name)
|
(dataDescriptor name)
|
||||||
@ -366,7 +366,7 @@ dataDescriptor descriptor =
|
|||||||
|
|
||||||
|
|
||||||
type alias ClickableTextAttributes msg =
|
type alias ClickableTextAttributes msg =
|
||||||
{ clickableAttributes : ClickableAttributes msg
|
{ clickableAttributes : ClickableAttributes String msg
|
||||||
, label : String
|
, label : String
|
||||||
, size : Size
|
, size : Size
|
||||||
, icon : Maybe Svg
|
, icon : Maybe Svg
|
||||||
|
462
src/Nri/Ui/SideNav/V1.elm
Normal file
462
src/Nri/Ui/SideNav/V1.elm
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
module Nri.Ui.SideNav.V1 exposing
|
||||||
|
( view, Config
|
||||||
|
, entry, entryWithChildren, html, Entry
|
||||||
|
, icon, custom, css, nriDescription, testId, id
|
||||||
|
, onClick
|
||||||
|
, href, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
|
||||||
|
, primary, secondary
|
||||||
|
, 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 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
|
||||||
|
import String exposing (toLower)
|
||||||
|
import String.Extra exposing (dasherize)
|
||||||
|
|
||||||
|
|
||||||
|
{-| 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 =
|
||||||
|
{ userPremiumLevel : PremiumLevel
|
||||||
|
, 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)"
|
||||||
|
-- TODO: use Accessibility.Styled.Style.invisibleStyle
|
||||||
|
-- when we're on a higher version of tesk9/accessible-html-with-css
|
||||||
|
-- than 2.2.1
|
||||||
|
[ Css.property "clip" "rect(1px, 1px, 1px, 1px)"
|
||||||
|
, Css.position Css.absolute
|
||||||
|
, Css.height (Css.px 1)
|
||||||
|
, Css.width (Css.px 1)
|
||||||
|
, Css.overflow Css.hidden
|
||||||
|
, Css.margin (Css.px -1)
|
||||||
|
, Css.padding Css.zero
|
||||||
|
, Css.border Css.zero
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, 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 PremiumLevel.allowedFor entryConfig.premiumLevel config.userPremiumLevel then
|
||||||
|
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
|
||||||
|
|
||||||
|
else
|
||||||
|
viewLockedEntry 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
|
||||||
|
, premiumLevel : PremiumLevel
|
||||||
|
, onLockedContent : Maybe msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
build : String -> EntryConfig route msg
|
||||||
|
build title =
|
||||||
|
{ icon = Nothing
|
||||||
|
, title = title
|
||||||
|
, route = Nothing
|
||||||
|
, clickableAttributes = ClickableAttributes.init
|
||||||
|
, customAttributes = []
|
||||||
|
, customStyles = []
|
||||||
|
, premiumLevel = PremiumLevel.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_ })
|
||||||
|
|
||||||
|
|
||||||
|
{-| -}
|
||||||
|
premiumLevel : PremiumLevel -> msg -> Attribute route msg
|
||||||
|
premiumLevel level ifLocked =
|
||||||
|
Attribute
|
||||||
|
(\attributes ->
|
||||||
|
{ attributes
|
||||||
|
| premiumLevel = level
|
||||||
|
, 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)
|
@ -4,7 +4,7 @@ import Accessibility.Styled as Html exposing (Html)
|
|||||||
import Browser exposing (Document, UrlRequest(..))
|
import Browser exposing (Document, UrlRequest(..))
|
||||||
import Browser.Dom
|
import Browser.Dom
|
||||||
import Browser.Navigation exposing (Key)
|
import Browser.Navigation exposing (Key)
|
||||||
import Category
|
import Category exposing (Category)
|
||||||
import Css exposing (..)
|
import Css exposing (..)
|
||||||
import Css.Media exposing (withMedia)
|
import Css.Media exposing (withMedia)
|
||||||
import Dict exposing (Dict)
|
import Dict exposing (Dict)
|
||||||
@ -16,10 +16,13 @@ import Html.Styled.Events as Events
|
|||||||
import Nri.Ui.ClickableText.V3 as ClickableText
|
import Nri.Ui.ClickableText.V3 as ClickableText
|
||||||
import Nri.Ui.Colors.V1 as Colors
|
import Nri.Ui.Colors.V1 as Colors
|
||||||
import Nri.Ui.CssVendorPrefix.V1 as VendorPrefixed
|
import Nri.Ui.CssVendorPrefix.V1 as VendorPrefixed
|
||||||
|
import Nri.Ui.Data.PremiumLevel as PremiumLevel
|
||||||
import Nri.Ui.Fonts.V1 as Fonts
|
import Nri.Ui.Fonts.V1 as Fonts
|
||||||
import Nri.Ui.Heading.V2 as Heading
|
import Nri.Ui.Heading.V2 as Heading
|
||||||
import Nri.Ui.MediaQuery.V1 exposing (mobile, notMobile)
|
import Nri.Ui.MediaQuery.V1 exposing (mobile, notMobile)
|
||||||
import Nri.Ui.Page.V3 as Page
|
import Nri.Ui.Page.V3 as Page
|
||||||
|
import Nri.Ui.SideNav.V1 as SideNav
|
||||||
|
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||||
import Routes as Routes exposing (Route(..))
|
import Routes as Routes exposing (Route(..))
|
||||||
import Sort.Set as Set exposing (Set)
|
import Sort.Set as Set exposing (Set)
|
||||||
import Task
|
import Task
|
||||||
@ -145,7 +148,12 @@ view_ model =
|
|||||||
Routes.Doodad doodad ->
|
Routes.Doodad doodad ->
|
||||||
case List.head (examples (\m -> m.name == doodad)) of
|
case List.head (examples (\m -> m.name == doodad)) of
|
||||||
Just example ->
|
Just example ->
|
||||||
Html.main_ []
|
Html.main_
|
||||||
|
[ css
|
||||||
|
[ maxWidth (Css.px 1400)
|
||||||
|
, margin auto
|
||||||
|
]
|
||||||
|
]
|
||||||
[ Example.view model.previousRoute example
|
[ Example.view model.previousRoute example
|
||||||
|> Html.map (UpdateModuleStates example.name)
|
|> Html.map (UpdateModuleStates example.name)
|
||||||
]
|
]
|
||||||
@ -182,6 +190,8 @@ withSideNav currentRoute content =
|
|||||||
[ displayFlex
|
[ displayFlex
|
||||||
, withMedia [ mobile ] [ flexDirection column, alignItems stretch ]
|
, withMedia [ mobile ] [ flexDirection column, alignItems stretch ]
|
||||||
, alignItems flexStart
|
, alignItems flexStart
|
||||||
|
, maxWidth (Css.px 1400)
|
||||||
|
, margin auto
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
[ navigation currentRoute
|
[ navigation currentRoute
|
||||||
@ -221,92 +231,29 @@ viewPreviews containerId examples =
|
|||||||
|
|
||||||
|
|
||||||
navigation : Route -> Html Msg
|
navigation : Route -> Html Msg
|
||||||
navigation route =
|
navigation currentRoute =
|
||||||
let
|
let
|
||||||
isActive category =
|
categoryNavLinks : List (SideNav.Entry Route Msg)
|
||||||
case route of
|
categoryNavLinks =
|
||||||
Routes.Category routeCategory ->
|
List.map
|
||||||
category == routeCategory
|
(\category ->
|
||||||
|
SideNav.entry (Category.forDisplay category)
|
||||||
_ ->
|
[ SideNav.href (Routes.Category category) ]
|
||||||
False
|
)
|
||||||
|
Category.all
|
||||||
link active hash displayName =
|
|
||||||
ClickableText.link displayName
|
|
||||||
[ ClickableText.small
|
|
||||||
, ClickableText.css
|
|
||||||
[ Css.color Colors.navy
|
|
||||||
, Css.display Css.block
|
|
||||||
, Css.padding (Css.px 8)
|
|
||||||
, Css.borderRadius (Css.px 8)
|
|
||||||
, if active then
|
|
||||||
Css.backgroundColor Colors.glacier
|
|
||||||
|
|
||||||
else
|
|
||||||
Css.batch []
|
|
||||||
]
|
|
||||||
, ClickableText.href hash
|
|
||||||
]
|
|
||||||
|
|
||||||
navLink category =
|
|
||||||
link (isActive category)
|
|
||||||
(Routes.toString (Routes.Category category))
|
|
||||||
(Category.forDisplay category)
|
|
||||||
|
|
||||||
toNavLi element =
|
|
||||||
Html.li
|
|
||||||
[ css
|
|
||||||
[ margin zero
|
|
||||||
, listStyle none
|
|
||||||
, textDecoration none
|
|
||||||
]
|
|
||||||
]
|
|
||||||
[ element ]
|
|
||||||
in
|
in
|
||||||
Html.nav
|
SideNav.view
|
||||||
[ css
|
{ userPremiumLevel = PremiumLevel.Free
|
||||||
[ backgroundColor Colors.gray96
|
, isCurrentRoute = (==) currentRoute
|
||||||
, withMedia [ notMobile ]
|
, routeToString = Routes.toString
|
||||||
|
, onSkipNav = SkipToMainContent
|
||||||
|
, css =
|
||||||
|
[ withMedia [ notMobile ]
|
||||||
[ VendorPrefixed.value "position" "sticky"
|
[ VendorPrefixed.value "position" "sticky"
|
||||||
, top (px 55)
|
, top (px 55)
|
||||||
, flexShrink zero
|
|
||||||
, borderRadius (px 8)
|
|
||||||
, marginRight (px 40)
|
|
||||||
, padding (px 20)
|
|
||||||
, flexBasis (px 200)
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
, attribute "aria-label" "Main Navigation"
|
}
|
||||||
]
|
(SideNav.entry "All" [ SideNav.href Routes.All ]
|
||||||
[ Html.button
|
:: categoryNavLinks
|
||||||
[ css
|
)
|
||||||
[ backgroundColor transparent
|
|
||||||
, borderStyle none
|
|
||||||
, textDecoration none
|
|
||||||
, color Colors.azure
|
|
||||||
, Fonts.baseFont
|
|
||||||
, Css.marginBottom (px 20)
|
|
||||||
, Css.pseudoClass "not(:focus)"
|
|
||||||
[ Css.property "clip" "rect(1px, 1px, 1px, 1px)"
|
|
||||||
, Css.position Css.absolute
|
|
||||||
, Css.height (Css.px 1)
|
|
||||||
, Css.width (Css.px 1)
|
|
||||||
, Css.overflow Css.hidden
|
|
||||||
, Css.margin (Css.px -1)
|
|
||||||
, Css.padding Css.zero
|
|
||||||
, Css.border Css.zero
|
|
||||||
]
|
|
||||||
]
|
|
||||||
, Events.onClick SkipToMainContent
|
|
||||||
, id "skip"
|
|
||||||
]
|
|
||||||
[ Html.text "Skip to main content" ]
|
|
||||||
, (link (route == Routes.All) "#/" "All"
|
|
||||||
:: List.map navLink Category.all
|
|
||||||
)
|
|
||||||
|> List.map toNavLi
|
|
||||||
|> Html.ul
|
|
||||||
[ css [ margin zero, padding zero ]
|
|
||||||
, id "categories"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"Nri.Ui.RadioButton.V3",
|
"Nri.Ui.RadioButton.V3",
|
||||||
"Nri.Ui.SegmentedControl.V14",
|
"Nri.Ui.SegmentedControl.V14",
|
||||||
"Nri.Ui.Select.V8",
|
"Nri.Ui.Select.V8",
|
||||||
|
"Nri.Ui.SideNav.V1",
|
||||||
"Nri.Ui.Slide.V1",
|
"Nri.Ui.Slide.V1",
|
||||||
"Nri.Ui.SlideModal.V2",
|
"Nri.Ui.SlideModal.V2",
|
||||||
"Nri.Ui.SortableTable.V2",
|
"Nri.Ui.SortableTable.V2",
|
||||||
|
Loading…
Reference in New Issue
Block a user