Merge pull request #993 from NoRedInk/tessa/togglable-sidenav

Tessa/togglable sidenav
This commit is contained in:
Tessa 2022-07-21 09:43:30 -06:00 committed by GitHub
commit 32350457db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 859 additions and 40 deletions

View File

@ -1,6 +1,7 @@
Nri.Ui.Accordion.V1,upgrade to V3
Nri.Ui.Heading.V2,upgrade to V3
Nri.Ui.Menu.V1,upgrade to V3
Nri.Ui.SideNav.V3,upgrade to V4
Nri.Ui.SortableTable.V2,upgrade to V3
Nri.Ui.Table.V5,upgrade to V6
Nri.Ui.Tabs.V6,upgrade to V7

1 Nri.Ui.Accordion.V1 upgrade to V3
2 Nri.Ui.Heading.V2 upgrade to V3
3 Nri.Ui.Menu.V1 upgrade to V3
4 Nri.Ui.SideNav.V3 upgrade to V4
5 Nri.Ui.SortableTable.V2 upgrade to V3
6 Nri.Ui.Table.V5 upgrade to V6
7 Nri.Ui.Tabs.V6 upgrade to V7

View File

@ -51,6 +51,7 @@
"Nri.Ui.Select.V8",
"Nri.Ui.Shadows.V1",
"Nri.Ui.SideNav.V3",
"Nri.Ui.SideNav.V4",
"Nri.Ui.SortableTable.V2",
"Nri.Ui.SortableTable.V3",
"Nri.Ui.Sprite.V1",

View File

@ -117,6 +117,9 @@ hint = 'upgrade to V3'
[forbidden."Nri.Ui.SideNav.V2"]
hint = 'upgrade to V3'
[forbidden."Nri.Ui.SideNav.V3"]
hint = 'upgrade to V4'
[forbidden."Nri.Ui.SortableTable.V2"]
hint = 'upgrade to V3'

View File

@ -6,9 +6,10 @@ module Nri.Ui.ClickableSvg.V2 exposing
, exactSize, exactWidth, exactHeight
, disabled
, withBorder
, primary, secondary, danger, dangerSecondary
, primary, secondary, tertiary, danger, dangerSecondary
, custom, nriDescription, testId, id
, css, notMobileCss, mobileCss, quizEngineMobileCss
, iconForMobile
, small, medium, large
)
@ -18,6 +19,7 @@ module Nri.Ui.ClickableSvg.V2 exposing
# Patch changes:
- adds `nriDescription`, `testId`, and `id` helpers
- adds `iconForMobile`
# Create a button or link
@ -45,7 +47,7 @@ module Nri.Ui.ClickableSvg.V2 exposing
## Customization
@docs withBorder
@docs primary, secondary, danger, dangerSecondary
@docs primary, secondary, tertiary, danger, dangerSecondary
@docs custom, nriDescription, testId, id
@ -53,6 +55,7 @@ module Nri.Ui.ClickableSvg.V2 exposing
### CSS
@docs css, notMobileCss, mobileCss, quizEngineMobileCss
@docs iconForMobile
### DEPRECATED
@ -247,6 +250,7 @@ withBorder =
type Theme
= Primary
| Secondary
| Tertiary
| Danger
| DangerSecondary
@ -257,7 +261,9 @@ type alias AppliedTheme =
, background : Color
, backgroundHovered : Color
, includeBorder : Bool
, borderColor : Color
, borderBottom : Color
, borderHover : Color
}
@ -268,7 +274,9 @@ disabledTheme =
, background = Colors.white
, backgroundHovered = Colors.white
, includeBorder = True
, borderColor = Colors.gray75
, borderBottom = Colors.gray75
, borderHover = Colors.gray75
}
@ -281,7 +289,9 @@ applyTheme theme =
, background = Colors.azure
, backgroundHovered = Colors.azureDark
, includeBorder = False
, borderColor = Colors.white
, borderBottom = Colors.azureDark
, borderHover = Colors.azureDark
}
Secondary ->
@ -290,7 +300,20 @@ applyTheme theme =
, background = Colors.white
, backgroundHovered = Colors.glacier
, includeBorder = True
, borderColor = Colors.azure
, borderBottom = Colors.azure
, borderHover = Colors.azure
}
Tertiary ->
{ main_ = Colors.gray45
, mainHovered = Colors.azure
, background = Colors.gray96
, backgroundHovered = Colors.glacier
, includeBorder = True
, borderColor = Colors.gray92
, borderBottom = Colors.gray92
, borderHover = Colors.azure
}
Danger ->
@ -299,7 +322,9 @@ applyTheme theme =
, background = Colors.red
, backgroundHovered = Colors.redDark
, includeBorder = False
, borderColor = Colors.white
, borderBottom = Colors.redDark
, borderHover = Colors.redDark
}
DangerSecondary ->
@ -308,7 +333,9 @@ applyTheme theme =
, background = Colors.white
, backgroundHovered = Colors.redLight
, includeBorder = True
, borderColor = Colors.red
, borderBottom = Colors.red
, borderHover = Colors.red
}
@ -327,6 +354,13 @@ secondary =
set (\attributes -> { attributes | theme = Secondary })
{-| Used to de-emphasize elements when not hovered.
-}
tertiary : Attribute msg
tertiary =
set (\attributes -> { attributes | theme = Tertiary })
{-| White/transparent icon on a red background.
-}
danger : Attribute msg
@ -420,6 +454,12 @@ quizEngineMobileCss styles =
css [ Css.Media.withMedia [ MediaQuery.quizEngineMobile ] styles ]
{-| -}
iconForMobile : Svg -> Attribute msg
iconForMobile icon =
set (\config -> { config | iconForMobile = Just icon })
-- INTERNALS
@ -437,6 +477,7 @@ build label icon =
{ clickableAttributes = ClickableAttributes.init
, label = label
, icon = icon
, iconForMobile = Nothing
, disabled = False
, size = Small
, width = Nothing
@ -456,6 +497,7 @@ type alias ButtonOrLinkAttributes msg =
{ clickableAttributes : ClickableAttributes String msg
, label : String
, icon : Svg
, iconForMobile : Maybe Svg
, disabled : Bool
, size : Size
, width : Maybe Float
@ -487,8 +529,7 @@ renderButton ((ButtonOrLink config) as button_) =
++ ClickableAttributes.toButtonAttributes config.clickableAttributes
++ config.customAttributes
)
[ renderIcon config theme.includeBorder
]
(renderIcons config theme.includeBorder)
renderLink : ButtonOrLink msg -> Html msg
@ -522,12 +563,11 @@ renderLink ((ButtonOrLink config) as link_) =
)
++ config.customAttributes
)
[ renderIcon config theme.includeBorder
]
(renderIcons config theme.includeBorder)
renderIcon : ButtonOrLinkAttributes msg -> Bool -> Html msg
renderIcon config includeBorder =
renderIcons : ButtonOrLinkAttributes msg -> Bool -> List (Html msg)
renderIcons config includeBorder =
let
size =
getSize config.size
@ -556,20 +596,43 @@ renderIcon config includeBorder =
else
Maybe.withDefault size config.height
in
config.icon
|> Svg.withCss
iconStyles =
[ Css.displayFlex
, Css.maxWidth (Css.px iconWidth)
, Css.maxHeight (Css.px iconHeight)
, Css.height (Css.pct 100)
, Css.margin Css.auto
]
|> Svg.toHtml
hideFor breakpoint =
Svg.withCss
[ Css.Media.withMedia [ breakpoint ]
[ Css.display Css.none
]
]
in
case config.iconForMobile of
Just iconForMobile_ ->
[ config.icon
|> Svg.withCss iconStyles
|> hideFor MediaQuery.mobile
|> Svg.toHtml
, iconForMobile_
|> Svg.withCss iconStyles
|> hideFor MediaQuery.notMobile
|> Svg.toHtml
]
Nothing ->
[ config.icon
|> Svg.withCss iconStyles
|> Svg.toHtml
]
buttonOrLinkStyles : ButtonOrLinkAttributes msg -> AppliedTheme -> List Style
buttonOrLinkStyles config { main_, mainHovered, background, backgroundHovered, borderBottom, includeBorder } =
buttonOrLinkStyles config { main_, mainHovered, background, backgroundHovered, borderColor, borderBottom, borderHover, includeBorder } =
let
cursor =
if config.disabled then
@ -600,7 +663,7 @@ buttonOrLinkStyles config { main_, mainHovered, background, backgroundHovered, b
, Css.batch <|
if config.hasBorder then
[ Css.borderRadius (Css.px 8)
, Css.borderColor main_
, Css.borderColor borderColor
, Css.borderBottomColor borderBottom
, Css.borderStyle Css.solid
, if includeBorder then
@ -615,7 +678,7 @@ buttonOrLinkStyles config { main_, mainHovered, background, backgroundHovered, b
, Css.borderBottomWidth (Css.px bordersAndPadding.bottomBorder)
, Css.backgroundColor background
, Css.hover
[ Css.borderColor borderBottom
[ Css.borderColor borderHover
, Css.backgroundColor backgroundHovered
]
, Css.padding4

View File

@ -17,10 +17,10 @@ Changes from V1:
-}
import Css exposing (Color)
import Css.Media exposing (withMediaQuery)
import Html.Styled as Html
import Html.Styled.Attributes as Attributes exposing (css)
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.MediaQuery.V1 as MediaQuery
import Particle exposing (Particle)
import Particle.System as ParticleSystem
import Random exposing (Generator)
@ -68,8 +68,7 @@ view (System system _) =
, Css.width (Css.pct 100)
, Css.height (Css.vh 100)
, Css.pointerEvents Css.none
, withMediaQuery [ "(prefers-reduced-motion)" ]
[ Css.display Css.none ]
, MediaQuery.prefersReducedMotion [ Css.display Css.none ]
]
]
)

View File

@ -1,5 +1,6 @@
module Nri.Ui.MediaQuery.V1 exposing
( mobile, notMobile
( anyMotion, prefersReducedMotion
, mobile, notMobile
, mobileBreakpoint
, quizEngineMobile
, quizEngineBreakpoint
@ -20,6 +21,8 @@ module Nri.Ui.MediaQuery.V1 exposing
[ Css.padding (Css.px 2)
]
@docs anyMotion, prefersReducedMotion
@docs mobile, notMobile
@docs mobileBreakpoint
@ -31,8 +34,20 @@ module Nri.Ui.MediaQuery.V1 exposing
-}
import Css exposing (px)
import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen)
import Css exposing (Style, px)
import Css.Media exposing (MediaQuery, maxWidth, minWidth, only, screen, withMediaQuery)
{-| -}
anyMotion : List Style -> Style
anyMotion =
withMediaQuery [ "(prefers-reduced-motion: no-preference)" ]
{-| -}
prefersReducedMotion : List Style -> Style
prefersReducedMotion =
withMediaQuery [ "(prefers-reduced-motion)" ]
{-| Styles using the `mobileBreakpoint` value as the maxWidth.

674
src/Nri/Ui/SideNav/V4.elm Normal file
View File

@ -0,0 +1,674 @@
module Nri.Ui.SideNav.V4 exposing
( view, Config, NavAttribute
, collapsible
, navLabel, navId
, navCss, navNotMobileCss, navMobileCss, navQuizEngineMobileCss
, entry, entryWithChildren, html, Entry, Attribute
, icon, custom, css, nriDescription, testId, id
, onClick
, href, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
, primary, secondary
, premiumDisplay
)
{-|
# Changes from V3
- make the nav configurably collapsible
@docs view, Config, NavAttribute
@docs collapsible
@docs navLabel, navId
@docs navCss, navNotMobileCss, navMobileCss, navQuizEngineMobileCss
## Entries
@docs entry, entryWithChildren, html, Entry, Attribute
@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.Aria as Aria
import Accessibility.Styled.Style as Style
import ClickableAttributes exposing (ClickableAttributes)
import Css exposing (..)
import Css.Media
import Html.Styled
import Html.Styled.Attributes as Attributes
import Html.Styled.Events as Events
import Nri.Ui
import Nri.Ui.ClickableSvg.V2 as ClickableSvg
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.MediaQuery.V1 as MediaQuery
import Nri.Ui.Svg.V1 as Svg exposing (Svg)
import Nri.Ui.Tooltip.V3 as Tooltip
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
}
{-| -}
type NavAttribute msg
= NavAttribute (NavAttributeConfig msg -> NavAttributeConfig msg)
type alias NavAttributeConfig msg =
{ navLabel : Maybe String
, navId : Maybe String
, css : List Style
, collapsible : Maybe (CollapsibleConfig msg)
}
defaultNavAttributeConfig : NavAttributeConfig msg
defaultNavAttributeConfig =
{ navLabel = Nothing
, navId = Nothing
, css = []
, collapsible = Nothing
}
{-| Give screenreader users context on what this particular sidenav is for.
If the nav is collapsible, this value will also be used for the sidenav tooltips.
-}
navLabel : String -> NavAttribute msg
navLabel str =
NavAttribute (\config -> { config | navLabel = Just str })
{-| -}
navId : String -> NavAttribute msg
navId str =
NavAttribute (\config -> { config | navId = Just str })
{-| These styles are included automatically in the nav container:
[ flexBasis (px 250)
, flexShrink (num 0)
, borderRadius (px 8)
, backgroundColor Colors.gray96
, padding (px 20)
, marginRight (px 20)
]
-}
navCss : List Style -> NavAttribute msg
navCss styles =
NavAttribute (\config -> { config | css = List.append config.css styles })
{-| -}
navNotMobileCss : List Style -> NavAttribute msg
navNotMobileCss styles =
navCss [ Css.Media.withMedia [ MediaQuery.notMobile ] styles ]
{-| -}
navMobileCss : List Style -> NavAttribute msg
navMobileCss styles =
navCss [ Css.Media.withMedia [ MediaQuery.mobile ] styles ]
{-| -}
navQuizEngineMobileCss : List Style -> NavAttribute msg
navQuizEngineMobileCss styles =
navCss [ Css.Media.withMedia [ MediaQuery.quizEngineMobile ] styles ]
{-| -}
type alias CollapsibleConfig msg =
{ isOpen : Bool
, toggle : Bool -> msg
, isTooltipOpen : Bool
, toggleTooltip : Bool -> msg
}
{-| -}
collapsible : CollapsibleConfig msg -> NavAttribute msg
collapsible collapsible_ =
NavAttribute (\config -> { config | collapsible = Just collapsible_ })
{-| -}
view : Config route msg -> List (NavAttribute msg) -> List (Entry route msg) -> Html msg
view config navAttributes entries =
let
appliedNavAttributes =
List.foldl (\(NavAttribute f) b -> f b) defaultNavAttributeConfig navAttributes
showNav =
Maybe.map .isOpen appliedNavAttributes.collapsible
|> Maybe.withDefault True
sidenavId =
Maybe.withDefault defaultSideNavId appliedNavAttributes.navId
defaultCss =
[ if showNav then
case appliedNavAttributes.collapsible of
Just _ ->
Css.batch
[ Css.flexBasis (Css.px 245)
, Css.padding4 (Css.px 25) (Css.px 25) (Css.px 20) (Css.px 20)
]
Nothing ->
Css.batch
[ Css.flexBasis (Css.px 250)
, Css.padding (Css.px 20)
]
else
Css.flexBasis (Css.px 5)
, flexShrink (num 0)
, marginRight (px 20)
, position relative
, borderRadius (px 8)
, backgroundColor Colors.gray96
]
in
div [ Attributes.css (defaultCss ++ appliedNavAttributes.css) ]
[ viewSkipLink config.onSkipNav
, viewJust (viewOpenCloseButton sidenavId appliedNavAttributes.navLabel) appliedNavAttributes.collapsible
, viewNav sidenavId config appliedNavAttributes entries showNav
]
defaultSideNavId : String
defaultSideNavId =
"sidenav"
viewOpenCloseButton : String -> Maybe String -> CollapsibleConfig msg -> Html msg
viewOpenCloseButton sidenavId navLabel_ { isOpen, toggle, isTooltipOpen, toggleTooltip } =
let
name =
Maybe.withDefault "sidebar" navLabel_
( action, icon_, attributes ) =
if isOpen then
( "Close " ++ name
, UiIcon.openClose
, [ ClickableSvg.css [ Css.padding (Css.px 5) ]
, ClickableSvg.iconForMobile UiIcon.x
]
)
else
( "Open " ++ name
, UiIcon.openClose
|> Svg.withCss [ Css.transform (rotate (deg 180)) ]
, [ ClickableSvg.withBorder
, ClickableSvg.iconForMobile UiIcon.hamburger
]
)
trigger tooltipAttributes =
ClickableSvg.button action
icon_
([ ClickableSvg.custom
[ Aria.controls [ sidenavId ]
, Aria.expanded isOpen
]
, ClickableSvg.custom tooltipAttributes
, ClickableSvg.onClick (toggle (not isOpen))
, ClickableSvg.tertiary
]
++ attributes
)
in
Tooltip.view
{ trigger = trigger
, id = "open-close-sidebar-tooltip"
}
[ Tooltip.open isTooltipOpen
, Tooltip.onToggle toggleTooltip
, Tooltip.plaintext action
, Tooltip.smallPadding
, Tooltip.fitToContent
, if isOpen then
Tooltip.onLeft
else
Tooltip.onRight
, Tooltip.onRightForMobile
, Tooltip.containerCss
(if isOpen then
[ Css.Media.withMedia [ MediaQuery.notMobile ]
[ Css.position Css.absolute
, Css.top Css.zero
, Css.right Css.zero
]
]
else
[]
)
]
viewNav : String -> Config route msg -> NavAttributeConfig msg -> List (Entry route msg) -> Bool -> Html msg
viewNav sidenavId config appliedNavAttributes entries showNav =
nav
([ Maybe.map Aria.label appliedNavAttributes.navLabel
, Just (Attributes.id sidenavId)
, if showNav then
Nothing
else
Just (Attributes.css [ Css.display Css.none ])
]
|> List.filterMap identity
)
(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
{ routeToString = config.routeToString
, isDisabled = False
}
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 =
[ padding2 (px 13) (px 20)
, Css.property "word-break" "normal"
, Css.property "overflow-wrap" "anywhere"
, 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)

View File

@ -36,13 +36,13 @@ import Accessibility.Styled as Html exposing (Html)
import Accessibility.Styled.Aria as Aria
import Css exposing (Color, Style)
import Css.Global as Global
import Css.Media
import Html.Styled.Attributes as Attributes
import Html.Styled.Events as Events
import Nri.Ui.Colors.Extra exposing (toCssString)
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Html.Attributes.V2 as Extra
import Nri.Ui.MediaQuery.V1 as MediaQuery
import Nri.Ui.Svg.V1 exposing (Svg)
import Svg.Styled as Svg
import Svg.Styled.Attributes as SvgAttributes
@ -373,6 +373,4 @@ stroke color =
transition : String -> Css.Style
transition transitionRules =
Css.Media.withMediaQuery
[ "(prefers-reduced-motion: no-preference)" ]
[ Css.property "transition" transitionRules ]
MediaQuery.anyMotion [ Css.property "transition" transitionRules ]

View File

@ -1,5 +1,5 @@
module Nri.Ui.UiIcon.V1 exposing
( seeMore, openClose, download, sort, gear, flipper
( seeMore, openClose, download, sort, gear, flipper, hamburger
, archive, unarchive
, playInCircle, pauseInCircle, stopInCircle
, play, skip
@ -33,7 +33,7 @@ module Nri.Ui.UiIcon.V1 exposing
{-| How to add new icons: <https://paper.dropbox.com/doc/How-to-create-a-new-SVG-icon-for-use-in-Elm--Ay9uhSLfGUAix0ERIiJ0Dm8dAg-8WNqtARdr4EgjmYEHPeYD>
@docs seeMore, openClose, download, sort, gear, flipper
@docs seeMore, openClose, download, sort, gear, flipper, hamburger
@docs archive, unarchive
@docs playInCircle, pauseInCircle, stopInCircle
@docs play, skip
@ -910,6 +910,37 @@ pauseInCircle =
]
{-| -}
hamburger : Nri.Ui.Svg.V1.Svg
hamburger =
Nri.Ui.Svg.V1.init "0 0 25 25"
[ Svg.rect
[ Attributes.x "0"
, Attributes.y "0"
, Attributes.width "25"
, Attributes.height "5"
, Attributes.rx "2.5"
]
[]
, Svg.rect
[ Attributes.x "0"
, Attributes.y "10"
, Attributes.width "25"
, Attributes.height "5"
, Attributes.rx "2.5"
]
[]
, Svg.rect
[ Attributes.x "0"
, Attributes.y "20"
, Attributes.width "25"
, Attributes.height "5"
, Attributes.rx "2.5"
]
[]
]
{-| -}
equals : Nri.Ui.Svg.V1.Svg
equals =

View File

@ -18,7 +18,7 @@ import Nri.Ui.CssVendorPrefix.V1 as VendorPrefixed
import Nri.Ui.Heading.V3 as Heading
import Nri.Ui.MediaQuery.V1 exposing (mobile)
import Nri.Ui.Page.V3 as Page
import Nri.Ui.SideNav.V3 as SideNav
import Nri.Ui.SideNav.V4 as SideNav
import Nri.Ui.Sprite.V1 as Sprite
import Nri.Ui.UiIcon.V1 as UiIcon
import Routes
@ -36,6 +36,8 @@ type alias Model key =
route : Route
, previousRoute : Maybe Route
, moduleStates : Dict String (Example Examples.State Examples.Msg)
, isSideNavOpen : Bool
, openTooltip : Maybe TooltipId
, navigationKey : key
, elliePackageDependencies : Result Http.Error (Dict String String)
}
@ -51,6 +53,8 @@ init () url key =
( { route = Routes.fromLocation moduleStates url
, previousRoute = Nothing
, moduleStates = moduleStates
, isSideNavOpen = True
, openTooltip = Nothing
, navigationKey = key
, elliePackageDependencies = Ok Dict.empty
}
@ -62,12 +66,18 @@ init () url key =
)
type TooltipId
= SideNavOpenCloseTooltip
type Msg
= UpdateModuleStates String Examples.Msg
| OnUrlRequest Browser.UrlRequest
| OnUrlChange Url
| ChangeRoute Route
| SkipToMainContent
| ToggleSideNav Bool
| ToggleTooltip TooltipId Bool
| LoadedPackages (Result Http.Error (Dict String String))
| Focused (Result Browser.Dom.Error ())
@ -125,6 +135,15 @@ update action model =
, FocusOn "maincontent"
)
ToggleSideNav isOpen ->
( { model | isSideNavOpen = isOpen }, None )
ToggleTooltip tooltipId True ->
( { model | openTooltip = Just tooltipId }, None )
ToggleTooltip _ False ->
( { model | openTooltip = Nothing }, None )
LoadedPackages newPackagesResult ->
let
-- Ellie gets really slow to compile if we include all the packages, unfortunately!
@ -280,10 +299,7 @@ viewCategory model category =
)
withSideNav :
{ model | route : Route, moduleStates : Dict String (Example Examples.State Examples.Msg) }
-> Html Msg
-> Html Msg
withSideNav : Model key -> Html Msg -> Html Msg
withSideNav model content =
Html.div
[ css
@ -333,10 +349,8 @@ viewPreviews containerId navConfig examples =
]
navigation :
{ model | route : Route, moduleStates : Dict String (Example Examples.State Examples.Msg) }
-> Html Msg
navigation { moduleStates, route } =
navigation : Model key -> Html Msg
navigation { moduleStates, route, isSideNavOpen, openTooltip } =
let
examples =
Dict.values moduleStates
@ -370,6 +384,14 @@ navigation { moduleStates, route } =
[ VendorPrefixed.value "position" "sticky"
, top (px 55)
]
, SideNav.collapsible
{ isOpen = isSideNavOpen
, toggle = ToggleSideNav
, isTooltipOpen = openTooltip == Just SideNavOpenCloseTooltip
, toggleTooltip = ToggleTooltip SideNavOpenCloseTooltip
}
, SideNav.navLabel "categories"
, SideNav.navId "sidenav__categories"
]
(SideNav.entry "Usage Guidelines"
[ SideNav.linkExternal "https://paper.dropbox.com/doc/UI-Style-Guide-and-Caveats--BhJHYronm1RGM1hRfnkvhrZMAg-PvOLxeX3oyujYEzdJx5pu"

View File

@ -187,6 +187,7 @@ viewExampleTable { label, icon, attributes } =
[ ( "primary", ClickableSvg.primary )
, ( "secondary", ClickableSvg.secondary )
, ( "danger", ClickableSvg.danger )
, ( "tertiary", ClickableSvg.tertiary )
, ( "dangerSecondary", ClickableSvg.dangerSecondary )
]
, Html.tfoot []
@ -295,4 +296,13 @@ initSettings =
{ moduleName = "ClickableSvg"
, use = ClickableSvg.notMobileCss
}
|> ControlExtra.optionalListItem "iconForMobile"
(Control.map
(\( name, icon ) ->
( "ClickableSvg.iconForMobile " ++ name
, ClickableSvg.iconForMobile icon
)
)
CommonControls.uiIcon
)
)

View File

@ -17,12 +17,12 @@ import EllieLink
import Example exposing (Example)
import Html.Styled.Attributes exposing (css)
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.SideNav.V3 as SideNav
import Nri.Ui.SideNav.V4 as SideNav
version : Int
version =
3
4
{-| -}
@ -124,7 +124,7 @@ type alias State =
type alias Settings =
{ currentRoute : String
, navAttributes : List ( String, SideNav.NavAttribute )
, navAttributes : List ( String, SideNav.NavAttribute Msg )
, entries : List ( String, SideNav.Entry String Msg )
}
@ -140,7 +140,7 @@ init =
}
controlNavAttributes : Control (List ( String, SideNav.NavAttribute ))
controlNavAttributes : Control (List ( String, SideNav.NavAttribute Msg ))
controlNavAttributes =
ControlExtra.list
|> ControlExtra.optionalListItemDefaultChecked "navLabel"

View File

@ -58,6 +58,7 @@ all =
, ( "download", UiIcon.download, [] )
, ( "sort", UiIcon.sort, [] )
, ( "gear", UiIcon.gear, [] )
, ( "hamburger", UiIcon.hamburger, [] )
]
)
, ( "Archive & Unarchive"

View File

@ -47,6 +47,7 @@
"Nri.Ui.Select.V8",
"Nri.Ui.Shadows.V1",
"Nri.Ui.SideNav.V3",
"Nri.Ui.SideNav.V4",
"Nri.Ui.SortableTable.V2",
"Nri.Ui.SortableTable.V3",
"Nri.Ui.Sprite.V1",