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.SegmentedControl.V14",
|
||||
"Nri.Ui.Select.V8",
|
||||
"Nri.Ui.SideNav.V1",
|
||||
"Nri.Ui.Slide.V1",
|
||||
"Nri.Ui.SlideModal.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
|
||||
, url : String
|
||||
, url : Maybe route
|
||||
, urlString : Maybe String
|
||||
, onClick : Maybe msg
|
||||
}
|
||||
|
||||
@ -40,58 +41,67 @@ type Link
|
||||
|
||||
|
||||
{-| -}
|
||||
init : ClickableAttributes msg
|
||||
init : ClickableAttributes route msg
|
||||
init =
|
||||
{ linkType = Default
|
||||
, url = "#"
|
||||
, url = Nothing
|
||||
, urlString = Nothing
|
||||
, onClick = Nothing
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
onClick : msg -> ClickableAttributes msg -> ClickableAttributes msg
|
||||
onClick : msg -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||
onClick msg clickableAttributes =
|
||||
{ clickableAttributes | onClick = Just msg }
|
||||
|
||||
|
||||
{-| -}
|
||||
href : String -> ClickableAttributes msg -> ClickableAttributes msg
|
||||
href : route -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||
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 =
|
||||
{ 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 =
|
||||
{ clickableAttributes | linkType = WithMethod method, url = url }
|
||||
{ clickableAttributes | linkType = WithMethod method, url = Just url }
|
||||
|
||||
|
||||
{-| -}
|
||||
linkWithTracking : { track : msg, url : String } -> ClickableAttributes msg -> ClickableAttributes msg
|
||||
linkWithTracking { track, url } _ =
|
||||
{ linkType = WithTracking, url = url, onClick = Just track }
|
||||
linkWithTracking : { track : msg, url : route } -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||
linkWithTracking { track, url } clickableAttributes =
|
||||
{ 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 =
|
||||
{ clickableAttributes | linkType = External, url = url }
|
||||
{ clickableAttributes | linkType = External, urlString = Just url }
|
||||
|
||||
|
||||
{-| -}
|
||||
linkExternalWithTracking : { track : msg, url : String } -> ClickableAttributes msg -> ClickableAttributes msg
|
||||
linkExternalWithTracking { track, url } _ =
|
||||
{ linkType = ExternalWithTracking, url = url, onClick = Just track }
|
||||
linkExternalWithTracking : { track : msg, url : String } -> ClickableAttributes route msg -> ClickableAttributes route msg
|
||||
linkExternalWithTracking { track, url } clickableAttributes =
|
||||
{ clickableAttributes
|
||||
| linkType = ExternalWithTracking
|
||||
, urlString = Just url
|
||||
, onClick = Just track
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
toButtonAttributes : ClickableAttributes msg -> List (Attribute msg)
|
||||
toButtonAttributes : ClickableAttributes route msg -> List (Attribute msg)
|
||||
toButtonAttributes clickableAttributes =
|
||||
case clickableAttributes.onClick of
|
||||
Just handler ->
|
||||
@ -102,12 +112,24 @@ toButtonAttributes clickableAttributes =
|
||||
|
||||
|
||||
{-| -}
|
||||
toLinkAttributes : ClickableAttributes msg -> ( String, List (Attribute msg) )
|
||||
toLinkAttributes clickableAttributes =
|
||||
toLinkAttributes : (route -> String) -> ClickableAttributes route msg -> ( String, List (Attribute msg) )
|
||||
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
|
||||
Default ->
|
||||
( "link"
|
||||
, [ Attributes.href clickableAttributes.url
|
||||
, [ Attributes.href stringUrl
|
||||
, Attributes.target "_self"
|
||||
]
|
||||
)
|
||||
@ -116,17 +138,17 @@ toLinkAttributes clickableAttributes =
|
||||
( "linkSpa"
|
||||
, case clickableAttributes.onClick of
|
||||
Just handler ->
|
||||
[ Attributes.href clickableAttributes.url
|
||||
[ Attributes.href stringUrl
|
||||
, EventExtras.onClickPreventDefaultForLinkWithHref handler
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
[ Attributes.href clickableAttributes.url ]
|
||||
[ Attributes.href stringUrl ]
|
||||
)
|
||||
|
||||
WithMethod method ->
|
||||
( "linkWithMethod"
|
||||
, [ Attributes.href clickableAttributes.url
|
||||
, [ Attributes.href stringUrl
|
||||
, Attributes.attribute "data-method" method
|
||||
]
|
||||
)
|
||||
@ -135,18 +157,18 @@ toLinkAttributes clickableAttributes =
|
||||
( "linkWithTracking"
|
||||
, case clickableAttributes.onClick of
|
||||
Just track ->
|
||||
[ Attributes.href clickableAttributes.url
|
||||
[ Attributes.href stringUrl
|
||||
, Events.preventDefaultOn "click"
|
||||
(Json.Decode.succeed ( track, True ))
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
[ Attributes.href clickableAttributes.url ]
|
||||
[ Attributes.href stringUrl ]
|
||||
)
|
||||
|
||||
External ->
|
||||
( "linkExternal"
|
||||
, Attributes.href clickableAttributes.url
|
||||
, Attributes.href stringUrl
|
||||
:: targetBlank
|
||||
)
|
||||
|
||||
@ -154,13 +176,13 @@ toLinkAttributes clickableAttributes =
|
||||
( "linkExternalWithTracking"
|
||||
, case clickableAttributes.onClick of
|
||||
Just handler ->
|
||||
[ Attributes.href clickableAttributes.url
|
||||
[ Attributes.href stringUrl
|
||||
, Events.onClick handler
|
||||
, Events.on "auxclick" (Json.Decode.succeed handler)
|
||||
]
|
||||
++ targetBlank
|
||||
|
||||
Nothing ->
|
||||
Attributes.href clickableAttributes.url
|
||||
Attributes.href stringUrl
|
||||
:: targetBlank
|
||||
)
|
||||
|
@ -187,7 +187,7 @@ css styles =
|
||||
|
||||
|
||||
setClickableAttributes :
|
||||
(ClickableAttributes msg -> ClickableAttributes msg)
|
||||
(ClickableAttributes String msg -> ClickableAttributes String msg)
|
||||
-> Attribute msg
|
||||
setClickableAttributes apply =
|
||||
set
|
||||
@ -467,7 +467,7 @@ type ButtonOrLink msg
|
||||
|
||||
|
||||
type alias ButtonOrLinkAttributes msg =
|
||||
{ clickableAttributes : ClickableAttributes msg
|
||||
{ clickableAttributes : ClickableAttributes String msg
|
||||
, size : ButtonSize
|
||||
, style : ColorPalette
|
||||
, width : ButtonWidth
|
||||
@ -523,7 +523,7 @@ renderLink ((ButtonOrLink config) as link_) =
|
||||
getColorPalette link_
|
||||
|
||||
( linkFunctionName, attributes ) =
|
||||
ClickableAttributes.toLinkAttributes config.clickableAttributes
|
||||
ClickableAttributes.toLinkAttributes identity config.clickableAttributes
|
||||
in
|
||||
Nri.Ui.styled Styled.a
|
||||
(styledName linkFunctionName)
|
||||
|
@ -87,7 +87,7 @@ link name icon attributes =
|
||||
|
||||
|
||||
setClickableAttributes :
|
||||
(ClickableAttributes msg -> ClickableAttributes msg)
|
||||
(ClickableAttributes String msg -> ClickableAttributes String msg)
|
||||
-> Attribute msg
|
||||
setClickableAttributes apply =
|
||||
set
|
||||
@ -384,7 +384,7 @@ type ButtonOrLink msg
|
||||
|
||||
|
||||
type alias ButtonOrLinkAttributes msg =
|
||||
{ clickableAttributes : ClickableAttributes msg
|
||||
{ clickableAttributes : ClickableAttributes String msg
|
||||
, label : String
|
||||
, icon : Svg
|
||||
, disabled : Bool
|
||||
@ -435,7 +435,7 @@ renderLink : ButtonOrLink msg -> Html msg
|
||||
renderLink ((ButtonOrLink config) as link_) =
|
||||
let
|
||||
( linkFunctionName, extraAttrs ) =
|
||||
ClickableAttributes.toLinkAttributes config.clickableAttributes
|
||||
ClickableAttributes.toLinkAttributes identity config.clickableAttributes
|
||||
|
||||
theme =
|
||||
if config.disabled then
|
||||
|
@ -178,7 +178,7 @@ css styles =
|
||||
|
||||
|
||||
setClickableAttributes :
|
||||
(ClickableAttributes msg -> ClickableAttributes msg)
|
||||
(ClickableAttributes String msg -> ClickableAttributes String msg)
|
||||
-> Attribute msg
|
||||
setClickableAttributes apply =
|
||||
set
|
||||
@ -269,7 +269,7 @@ link label_ attributes =
|
||||
|> List.foldl (\(Attribute attribute) l -> attribute l) defaults
|
||||
|
||||
( name, clickableAttributes ) =
|
||||
ClickableAttributes.toLinkAttributes config.clickableAttributes
|
||||
ClickableAttributes.toLinkAttributes identity config.clickableAttributes
|
||||
in
|
||||
Nri.Ui.styled Html.a
|
||||
(dataDescriptor name)
|
||||
@ -366,7 +366,7 @@ dataDescriptor descriptor =
|
||||
|
||||
|
||||
type alias ClickableTextAttributes msg =
|
||||
{ clickableAttributes : ClickableAttributes msg
|
||||
{ clickableAttributes : ClickableAttributes String msg
|
||||
, label : String
|
||||
, size : Size
|
||||
, 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.Dom
|
||||
import Browser.Navigation exposing (Key)
|
||||
import Category
|
||||
import Category exposing (Category)
|
||||
import Css exposing (..)
|
||||
import Css.Media exposing (withMedia)
|
||||
import Dict exposing (Dict)
|
||||
@ -16,10 +16,13 @@ import Html.Styled.Events as Events
|
||||
import Nri.Ui.ClickableText.V3 as ClickableText
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.CssVendorPrefix.V1 as VendorPrefixed
|
||||
import Nri.Ui.Data.PremiumLevel as PremiumLevel
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.Heading.V2 as Heading
|
||||
import Nri.Ui.MediaQuery.V1 exposing (mobile, notMobile)
|
||||
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 Sort.Set as Set exposing (Set)
|
||||
import Task
|
||||
@ -145,7 +148,12 @@ view_ model =
|
||||
Routes.Doodad doodad ->
|
||||
case List.head (examples (\m -> m.name == doodad)) of
|
||||
Just example ->
|
||||
Html.main_ []
|
||||
Html.main_
|
||||
[ css
|
||||
[ maxWidth (Css.px 1400)
|
||||
, margin auto
|
||||
]
|
||||
]
|
||||
[ Example.view model.previousRoute example
|
||||
|> Html.map (UpdateModuleStates example.name)
|
||||
]
|
||||
@ -182,6 +190,8 @@ withSideNav currentRoute content =
|
||||
[ displayFlex
|
||||
, withMedia [ mobile ] [ flexDirection column, alignItems stretch ]
|
||||
, alignItems flexStart
|
||||
, maxWidth (Css.px 1400)
|
||||
, margin auto
|
||||
]
|
||||
]
|
||||
[ navigation currentRoute
|
||||
@ -221,92 +231,29 @@ viewPreviews containerId examples =
|
||||
|
||||
|
||||
navigation : Route -> Html Msg
|
||||
navigation route =
|
||||
navigation currentRoute =
|
||||
let
|
||||
isActive category =
|
||||
case route of
|
||||
Routes.Category routeCategory ->
|
||||
category == routeCategory
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
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 ]
|
||||
categoryNavLinks : List (SideNav.Entry Route Msg)
|
||||
categoryNavLinks =
|
||||
List.map
|
||||
(\category ->
|
||||
SideNav.entry (Category.forDisplay category)
|
||||
[ SideNav.href (Routes.Category category) ]
|
||||
)
|
||||
Category.all
|
||||
in
|
||||
Html.nav
|
||||
[ css
|
||||
[ backgroundColor Colors.gray96
|
||||
, withMedia [ notMobile ]
|
||||
SideNav.view
|
||||
{ userPremiumLevel = PremiumLevel.Free
|
||||
, isCurrentRoute = (==) currentRoute
|
||||
, routeToString = Routes.toString
|
||||
, onSkipNav = SkipToMainContent
|
||||
, css =
|
||||
[ withMedia [ notMobile ]
|
||||
[ VendorPrefixed.value "position" "sticky"
|
||||
, top (px 55)
|
||||
, flexShrink zero
|
||||
, borderRadius (px 8)
|
||||
, marginRight (px 40)
|
||||
, padding (px 20)
|
||||
, flexBasis (px 200)
|
||||
]
|
||||
]
|
||||
, attribute "aria-label" "Main Navigation"
|
||||
]
|
||||
[ Html.button
|
||||
[ 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"
|
||||
]
|
||||
]
|
||||
}
|
||||
(SideNav.entry "All" [ SideNav.href Routes.All ]
|
||||
:: categoryNavLinks
|
||||
)
|
||||
|
@ -43,6 +43,7 @@
|
||||
"Nri.Ui.RadioButton.V3",
|
||||
"Nri.Ui.SegmentedControl.V14",
|
||||
"Nri.Ui.Select.V8",
|
||||
"Nri.Ui.SideNav.V1",
|
||||
"Nri.Ui.Slide.V1",
|
||||
"Nri.Ui.SlideModal.V2",
|
||||
"Nri.Ui.SortableTable.V2",
|
||||
|
Loading…
Reference in New Issue
Block a user