mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-27 13:02:42 +03:00
commit
61a6608dd3
1
elm.json
1
elm.json
@ -11,6 +11,7 @@
|
||||
"Nri.Ui.AssetPath",
|
||||
"Nri.Ui.AssignmentIcon.V2",
|
||||
"Nri.Ui.Balloon.V1",
|
||||
"Nri.Ui.BreadCrumbs.V1",
|
||||
"Nri.Ui.Button.V10",
|
||||
"Nri.Ui.Checkbox.V5",
|
||||
"Nri.Ui.ClickableSvg.V2",
|
||||
|
367
src/Nri/Ui/BreadCrumbs/V1.elm
Normal file
367
src/Nri/Ui/BreadCrumbs/V1.elm
Normal file
@ -0,0 +1,367 @@
|
||||
module Nri.Ui.BreadCrumbs.V1 exposing
|
||||
( view, IconStyle(..)
|
||||
, BreadCrumbs, init
|
||||
, BreadCrumb, after
|
||||
, headerId
|
||||
, toPageTitle, toPageTitleWithSecondaryBreadCrumbs
|
||||
)
|
||||
|
||||
{-| Learn more about 'breadcrumbs' to help a user orient themselves within a site here: <https://www.w3.org/WAI/WCAG21/Techniques/general/G65>.
|
||||
|
||||
Wide Viewport (with Circled IconStyle):
|
||||
|
||||
Home
|
||||
|
||||
🏠 Home > 🟠 Category 1
|
||||
|
||||
🏠 > 🟠 Category 1 > 🟣 Sub-Category 2
|
||||
|
||||
Narrow Viewport (with Circled IconStyle):
|
||||
|
||||
Home
|
||||
|
||||
🏠 > 🟠 Category 1
|
||||
|
||||
🏠 > 🟠 > 🟣 Sub-Category 2
|
||||
|
||||
@docs view, IconStyle
|
||||
@docs BreadCrumbs, init
|
||||
@docs BreadCrumb, after
|
||||
@docs headerId
|
||||
@docs toPageTitle, toPageTitleWithSecondaryBreadCrumbs
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled exposing (..)
|
||||
import Accessibility.Styled.Aria as Aria
|
||||
import Accessibility.Styled.Style as Style
|
||||
import Accessibility.Styled.Widget as Widget
|
||||
import Css exposing (..)
|
||||
import Css.Global
|
||||
import Css.Media as Media
|
||||
import Html.Styled
|
||||
import Html.Styled.Attributes as Attributes exposing (css)
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.MediaQuery.V1 as MediaQuery
|
||||
import Nri.Ui.Svg.V1 as Svg
|
||||
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias BreadCrumb route =
|
||||
{ icon : Maybe Svg.Svg
|
||||
, iconStyle : IconStyle
|
||||
, id : String
|
||||
, text : String
|
||||
, route : route
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type BreadCrumbs route
|
||||
= BreadCrumbs (List (BreadCrumb route))
|
||||
|
||||
|
||||
{-| -}
|
||||
init : BreadCrumb route -> BreadCrumbs route
|
||||
init breadCrumb =
|
||||
BreadCrumbs [ breadCrumb ]
|
||||
|
||||
|
||||
{-| -}
|
||||
after : BreadCrumbs route -> BreadCrumb route -> BreadCrumbs route
|
||||
after (BreadCrumbs previous) new =
|
||||
BreadCrumbs (new :: previous)
|
||||
|
||||
|
||||
{-| -}
|
||||
headerId : BreadCrumbs route -> String
|
||||
headerId (BreadCrumbs list) =
|
||||
case list of
|
||||
{ id } :: _ ->
|
||||
id
|
||||
|
||||
_ ->
|
||||
-- It should be impossible to construct a BreadCrumbs without
|
||||
-- any elements.
|
||||
--
|
||||
""
|
||||
|
||||
|
||||
{-| -}
|
||||
type IconStyle
|
||||
= Circled
|
||||
| Default
|
||||
|
||||
|
||||
{-| Generate an HTML page title using the breadcrumbs,
|
||||
in the form "Sub-Category | Category | NoRedInk" for breadCrumbs like:
|
||||
|
||||
Category > Sub - Category
|
||||
|
||||
-}
|
||||
toPageTitle : BreadCrumbs a -> String
|
||||
toPageTitle (BreadCrumbs breadcrumbs) =
|
||||
String.join " | " (List.map .text breadcrumbs ++ [ "NoRedInk" ])
|
||||
|
||||
|
||||
{-| -}
|
||||
toPageTitleWithSecondaryBreadCrumbs : BreadCrumbs a -> String
|
||||
toPageTitleWithSecondaryBreadCrumbs (BreadCrumbs breadcrumbs) =
|
||||
(List.take 1 breadcrumbs |> List.map .text)
|
||||
++ [ "NoRedInk" ]
|
||||
|> String.join " | "
|
||||
|
||||
|
||||
{-| -}
|
||||
view :
|
||||
{ aTagAttributes : route -> List (Attribute msg)
|
||||
, isCurrentRoute : route -> Bool
|
||||
}
|
||||
-> BreadCrumbs route
|
||||
-> Html msg
|
||||
view config (BreadCrumbs breadCrumbs) =
|
||||
styled nav
|
||||
[ alignItems center
|
||||
, displayFlex
|
||||
, Media.withMedia [ MediaQuery.mobile ] [ marginBottom (px 10) ]
|
||||
]
|
||||
[ Widget.label "breadcrumbs" ]
|
||||
(viewBreadCrumbs config (List.reverse breadCrumbs))
|
||||
|
||||
|
||||
viewBreadCrumbs :
|
||||
{ aTagAttributes : route -> List (Attribute msg)
|
||||
, isCurrentRoute : route -> Bool
|
||||
}
|
||||
-> List (BreadCrumb route)
|
||||
-> List (Html msg)
|
||||
viewBreadCrumbs config breadCrumbs =
|
||||
let
|
||||
breadCrumbCount : Int
|
||||
breadCrumbCount =
|
||||
List.length breadCrumbs
|
||||
in
|
||||
List.indexedMap
|
||||
(\i ->
|
||||
viewBreadCrumb config
|
||||
{ isFirst = i == 0
|
||||
, isLast = (i + 1) == breadCrumbCount
|
||||
, isIconOnly =
|
||||
-- the first breadcrumb should collapse when there
|
||||
-- are 3 breadcrumbs or more
|
||||
--
|
||||
-- Hypothetically, if there were 4 breadcrumbs, then the
|
||||
-- first 2 breadcrumbs should collapse
|
||||
(breadCrumbCount - i) > 2
|
||||
}
|
||||
)
|
||||
breadCrumbs
|
||||
|> List.intersperse (Svg.toHtml arrowRight)
|
||||
|
||||
|
||||
viewBreadCrumb :
|
||||
{ config
|
||||
| aTagAttributes : route -> List (Attribute msg)
|
||||
, isCurrentRoute : route -> Bool
|
||||
}
|
||||
-> { isFirst : Bool, isLast : Bool, isIconOnly : Bool }
|
||||
-> BreadCrumb route
|
||||
-> Html msg
|
||||
viewBreadCrumb config iconConfig crumb =
|
||||
let
|
||||
isLink =
|
||||
not (config.isCurrentRoute crumb.route)
|
||||
|
||||
linkAttrs =
|
||||
if isLink then
|
||||
css
|
||||
[ hover
|
||||
[ Css.Global.descendants
|
||||
[ Css.Global.class circleIconClass
|
||||
[ backgroundColor Colors.glacier
|
||||
, borderColor Colors.azureDark
|
||||
, color Colors.azure
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
:: config.aTagAttributes crumb.route
|
||||
|
||||
else
|
||||
[]
|
||||
|
||||
withIconIfPresent viewIcon =
|
||||
case crumb.icon of
|
||||
Just icon ->
|
||||
[ viewIcon iconConfig.isFirst crumb.iconStyle icon
|
||||
, viewHeadingWithIcon iconConfig crumb.text
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
[ text crumb.text ]
|
||||
in
|
||||
case ( iconConfig.isLast, isLink ) of
|
||||
( True, False ) ->
|
||||
pageHeader crumb.id
|
||||
(withIconIfPresent viewIconForHeading)
|
||||
|
||||
( True, True ) ->
|
||||
pageHeader crumb.id
|
||||
[ Html.Styled.styled Html.Styled.a
|
||||
[]
|
||||
(css commonCss :: linkAttrs)
|
||||
(withIconIfPresent viewIconForLink)
|
||||
]
|
||||
|
||||
( False, _ ) ->
|
||||
Html.Styled.styled Html.Styled.a
|
||||
[ fontWeight normal ]
|
||||
(css commonCss :: Attributes.id crumb.id :: linkAttrs)
|
||||
(withIconIfPresent viewIconForLink)
|
||||
|
||||
|
||||
pageHeader : String -> List (Html msg) -> Html msg
|
||||
pageHeader id =
|
||||
styled h1
|
||||
[ fontWeight bold ]
|
||||
[ Aria.currentPage
|
||||
, Attributes.id id
|
||||
, Attributes.tabindex -1
|
||||
, css commonCss
|
||||
]
|
||||
|
||||
|
||||
viewIconForHeading : Bool -> IconStyle -> Svg.Svg -> Html msg
|
||||
viewIconForHeading isFirst iconStyle svg =
|
||||
case iconStyle of
|
||||
Circled ->
|
||||
text ""
|
||||
|
||||
Default ->
|
||||
withoutIconCircle isFirst svg
|
||||
|
||||
|
||||
viewIconForLink : Bool -> IconStyle -> Svg.Svg -> Html msg
|
||||
viewIconForLink isFirst iconStyle svg =
|
||||
case iconStyle of
|
||||
Circled ->
|
||||
withIconCircle svg
|
||||
|
||||
Default ->
|
||||
withoutIconCircle isFirst svg
|
||||
|
||||
|
||||
viewHeadingWithIcon : { config | isLast : Bool, isIconOnly : Bool } -> String -> Html msg
|
||||
viewHeadingWithIcon { isIconOnly, isLast } title =
|
||||
div
|
||||
(if isIconOnly then
|
||||
Style.invisible
|
||||
|
||||
else if isLast then
|
||||
[ css [ marginLeft horizontalSpacing ] ]
|
||||
|
||||
else
|
||||
[ css
|
||||
[ marginLeft horizontalSpacing
|
||||
, Media.withMedia [ MediaQuery.mobile ]
|
||||
[ Style.invisibleStyle
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
[ text title
|
||||
]
|
||||
|
||||
|
||||
commonCss : List Style
|
||||
commonCss =
|
||||
[ alignItems center
|
||||
, displayFlex
|
||||
, margin zero
|
||||
, fontSize (px 30)
|
||||
, Media.withMedia [ MediaQuery.mobile ] [ fontSize (px 25) ]
|
||||
, Fonts.baseFont
|
||||
, textDecoration none
|
||||
, color Colors.navy
|
||||
]
|
||||
|
||||
|
||||
circleIconClass : String
|
||||
circleIconClass =
|
||||
"Nri-BreadCrumb-base-circled-icon"
|
||||
|
||||
|
||||
withIconCircle : Svg.Svg -> Html msg
|
||||
withIconCircle icon =
|
||||
styled div
|
||||
[ borderRadius (pct 50)
|
||||
, border3 (px 1) solid Colors.azure
|
||||
, color Colors.azure
|
||||
, borderBottomWidth (px 2)
|
||||
, backgroundColor Colors.white
|
||||
, height largeIconSize
|
||||
, width largeIconSize
|
||||
, fontSize (px 16)
|
||||
, property "transition" "background-color 0.2s, color 0.2s"
|
||||
, displayFlex
|
||||
, alignItems center
|
||||
, justifyContent center
|
||||
]
|
||||
[ Attributes.class circleIconClass ]
|
||||
[ icon
|
||||
|> Svg.withWidth circledInnerIconSize
|
||||
|> Svg.withHeight circledInnerIconSize
|
||||
|> Svg.toHtml
|
||||
]
|
||||
|
||||
|
||||
withoutIconCircle : Bool -> Svg.Svg -> Html msg
|
||||
withoutIconCircle isFirst icon =
|
||||
let
|
||||
size =
|
||||
if isFirst then
|
||||
largeIconSize
|
||||
|
||||
else
|
||||
iconSize
|
||||
in
|
||||
icon
|
||||
|> Svg.withWidth size
|
||||
|> Svg.withHeight size
|
||||
|> Svg.withCss [ Css.flexShrink Css.zero ]
|
||||
|> Svg.toHtml
|
||||
|
||||
|
||||
horizontalSpacing : Css.Px
|
||||
horizontalSpacing =
|
||||
Css.px 10
|
||||
|
||||
|
||||
circledInnerIconSize : Css.Px
|
||||
circledInnerIconSize =
|
||||
Css.px 25
|
||||
|
||||
|
||||
largeIconSize : Css.Px
|
||||
largeIconSize =
|
||||
Css.px 40
|
||||
|
||||
|
||||
iconSize : Css.Px
|
||||
iconSize =
|
||||
Css.px 31
|
||||
|
||||
|
||||
arrowRight : Svg.Svg
|
||||
arrowRight =
|
||||
UiIcon.arrowRight
|
||||
|> Svg.withColor Colors.gray75
|
||||
|> Svg.withHeight (px 15)
|
||||
|> Svg.withWidth (px 15)
|
||||
|> Svg.withCss
|
||||
[ marginRight horizontalSpacing
|
||||
, marginLeft horizontalSpacing
|
||||
, flexShrink zero
|
||||
]
|
@ -1,6 +1,7 @@
|
||||
module App exposing (Effect(..), Model, Msg(..), init, perform, subscriptions, update, view)
|
||||
|
||||
import Accessibility.Styled as Html exposing (Html)
|
||||
import Accessibility.Styled.Key as Key
|
||||
import Browser exposing (Document, UrlRequest(..))
|
||||
import Browser.Dom
|
||||
import Browser.Navigation exposing (Key)
|
||||
@ -20,12 +21,16 @@ import Nri.Ui.Page.V3 as Page
|
||||
import Nri.Ui.SideNav.V3 as SideNav
|
||||
import Nri.Ui.Sprite.V1 as Sprite
|
||||
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||
import Routes exposing (Route)
|
||||
import Routes
|
||||
import Sort.Set as Set
|
||||
import Task
|
||||
import Url exposing (Url)
|
||||
|
||||
|
||||
type alias Route =
|
||||
Routes.Route Examples.State Examples.Msg
|
||||
|
||||
|
||||
type alias Model key =
|
||||
{ -- Global UI
|
||||
route : Route
|
||||
@ -38,11 +43,14 @@ type alias Model key =
|
||||
|
||||
init : () -> Url -> key -> ( Model key, Effect )
|
||||
init () url key =
|
||||
( { route = Routes.fromLocation url
|
||||
, previousRoute = Nothing
|
||||
, moduleStates =
|
||||
let
|
||||
moduleStates =
|
||||
Dict.fromList
|
||||
(List.map (\example -> ( example.name, example )) Examples.all)
|
||||
in
|
||||
( { route = Routes.fromLocation moduleStates url
|
||||
, previousRoute = Nothing
|
||||
, moduleStates = moduleStates
|
||||
, navigationKey = key
|
||||
, elliePackageDependencies = Ok Dict.empty
|
||||
}
|
||||
@ -73,11 +81,15 @@ update action model =
|
||||
example.update exampleMsg example.state
|
||||
|> Tuple.mapFirst
|
||||
(\newState ->
|
||||
let
|
||||
newExample =
|
||||
{ example | state = newState }
|
||||
in
|
||||
{ model
|
||||
| moduleStates =
|
||||
Dict.insert key
|
||||
{ example | state = newState }
|
||||
model.moduleStates
|
||||
| moduleStates = Dict.insert key newExample model.moduleStates
|
||||
, route =
|
||||
Maybe.withDefault model.route
|
||||
(Routes.updateExample newExample model.route)
|
||||
}
|
||||
)
|
||||
|> Tuple.mapSecond (Cmd.map (UpdateModuleStates key) >> Command)
|
||||
@ -95,7 +107,7 @@ update action model =
|
||||
|
||||
OnUrlChange route ->
|
||||
( { model
|
||||
| route = Routes.fromLocation route
|
||||
| route = Routes.fromLocation model.moduleStates route
|
||||
, previousRoute = Just model.route
|
||||
}
|
||||
, None
|
||||
@ -190,11 +202,6 @@ subscriptions model =
|
||||
view : Model key -> Document Msg
|
||||
view model =
|
||||
let
|
||||
findExampleByName name =
|
||||
Dict.values model.moduleStates
|
||||
|> List.filter (\m -> m.name == name)
|
||||
|> List.head
|
||||
|
||||
toBody view_ =
|
||||
List.map Html.toUnstyled
|
||||
[ view_
|
||||
@ -202,20 +209,20 @@ view model =
|
||||
]
|
||||
in
|
||||
case model.route of
|
||||
Routes.Doodad doodad ->
|
||||
case findExampleByName doodad of
|
||||
Just example ->
|
||||
{ title = example.name ++ " in the NoRedInk Style Guide"
|
||||
, body =
|
||||
viewExample model example
|
||||
|> Html.map (UpdateModuleStates example.name)
|
||||
|> toBody
|
||||
}
|
||||
Routes.Doodad example ->
|
||||
{ title = example.name ++ " in the NoRedInk Style Guide"
|
||||
, body = viewExample model example |> toBody
|
||||
}
|
||||
|
||||
Nothing ->
|
||||
{ title = "Not found in the NoRedInk Style Guide"
|
||||
, body = toBody notFound
|
||||
}
|
||||
Routes.CategoryDoodad _ example ->
|
||||
{ title = example.name ++ " in the NoRedInk Style Guide"
|
||||
, body = viewExample model example |> toBody
|
||||
}
|
||||
|
||||
Routes.NotFound name ->
|
||||
{ title = name ++ " was not found in the NoRedInk Style Guide"
|
||||
, body = toBody notFound
|
||||
}
|
||||
|
||||
Routes.Category category ->
|
||||
{ title = Category.forDisplay category ++ " Category in the NoRedInk Style Guide"
|
||||
@ -228,13 +235,11 @@ view model =
|
||||
}
|
||||
|
||||
|
||||
viewExample : Model key -> Example a msg -> Html msg
|
||||
viewExample : Model key -> Example a Examples.Msg -> Html Msg
|
||||
viewExample model example =
|
||||
Html.div [ css [ maxWidth (Css.px 1400), margin auto ] ]
|
||||
[ Example.view model.previousRoute
|
||||
{ packageDependencies = model.elliePackageDependencies }
|
||||
example
|
||||
]
|
||||
Example.view { packageDependencies = model.elliePackageDependencies } example
|
||||
|> Html.map (UpdateModuleStates example.name)
|
||||
|> withSideNav model.route
|
||||
|
||||
|
||||
notFound : Html Msg
|
||||
@ -247,17 +252,18 @@ notFound =
|
||||
|
||||
viewAll : Model key -> Html Msg
|
||||
viewAll model =
|
||||
withSideNav model.route
|
||||
[ mainContentHeader "All"
|
||||
, viewPreviews "all" (Dict.values model.moduleStates)
|
||||
]
|
||||
withSideNav model.route <|
|
||||
viewPreviews "all"
|
||||
{ navigate = Routes.Doodad >> ChangeRoute
|
||||
, exampleHref = Routes.Doodad >> Routes.toString
|
||||
}
|
||||
(Dict.values model.moduleStates)
|
||||
|
||||
|
||||
viewCategory : Model key -> Category -> Html Msg
|
||||
viewCategory model category =
|
||||
withSideNav model.route
|
||||
[ mainContentHeader (Category.forDisplay category)
|
||||
, model.moduleStates
|
||||
(model.moduleStates
|
||||
|> Dict.values
|
||||
|> List.filter
|
||||
(\doodad ->
|
||||
@ -266,10 +272,13 @@ viewCategory model category =
|
||||
category
|
||||
)
|
||||
|> viewPreviews (Category.forId category)
|
||||
]
|
||||
{ navigate = Routes.CategoryDoodad category >> ChangeRoute
|
||||
, exampleHref = Routes.CategoryDoodad category >> Routes.toString
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
withSideNav : Route -> List (Html Msg) -> Html Msg
|
||||
withSideNav : Route -> Html Msg -> Html Msg
|
||||
withSideNav currentRoute content =
|
||||
Html.div
|
||||
[ css
|
||||
@ -287,25 +296,28 @@ withSideNav currentRoute content =
|
||||
, margin2 (px 40) zero
|
||||
, Css.minHeight (Css.vh 100)
|
||||
]
|
||||
, id "maincontent"
|
||||
, Key.tabbable False
|
||||
]
|
||||
[ Html.div [ css [ Css.marginBottom (Css.px 30) ] ]
|
||||
[ Routes.viewBreadCrumbs currentRoute
|
||||
]
|
||||
, content
|
||||
]
|
||||
content
|
||||
]
|
||||
|
||||
|
||||
mainContentHeader : String -> Html msg
|
||||
mainContentHeader heading =
|
||||
Heading.h1
|
||||
[ Heading.customAttr (id "maincontent")
|
||||
, Heading.customAttr (tabindex -1)
|
||||
, Heading.css [ marginBottom (px 30) ]
|
||||
]
|
||||
[ Html.text heading ]
|
||||
|
||||
|
||||
viewPreviews : String -> List (Example state msg) -> Html Msg
|
||||
viewPreviews containerId examples =
|
||||
viewPreviews :
|
||||
String
|
||||
->
|
||||
{ navigate : Example Examples.State Examples.Msg -> Msg
|
||||
, exampleHref : Example Examples.State Examples.Msg -> String
|
||||
}
|
||||
-> List (Example Examples.State Examples.Msg)
|
||||
-> Html Msg
|
||||
viewPreviews containerId navConfig examples =
|
||||
examples
|
||||
|> List.map (\example -> Example.preview ChangeRoute example)
|
||||
|> List.map (Example.preview navConfig)
|
||||
|> Html.div
|
||||
[ id containerId
|
||||
, css
|
||||
|
@ -1,7 +1,7 @@
|
||||
module Category exposing
|
||||
( Category(..)
|
||||
, fromString
|
||||
, forDisplay, forId
|
||||
, forDisplay, forId, forRoute
|
||||
, all
|
||||
, sorter
|
||||
)
|
||||
@ -10,7 +10,7 @@ module Category exposing
|
||||
|
||||
@docs Category
|
||||
@docs fromString
|
||||
@docs forDisplay, forId
|
||||
@docs forDisplay, forId, forRoute
|
||||
@docs all
|
||||
@docs sorter
|
||||
|
||||
@ -107,6 +107,12 @@ forDisplay category =
|
||||
"Animations"
|
||||
|
||||
|
||||
{-| -}
|
||||
forRoute : Category -> String
|
||||
forRoute =
|
||||
Debug.toString
|
||||
|
||||
|
||||
{-| -}
|
||||
sorter : Sorter Category
|
||||
sorter =
|
||||
|
@ -1,6 +1,5 @@
|
||||
module Example exposing (Example, fullName, preview, view, wrapMsg, wrapState)
|
||||
|
||||
import Accessibility.Styled.Aria as Aria
|
||||
import Accessibility.Styled.Widget as Widget
|
||||
import Category exposing (Category)
|
||||
import Css exposing (..)
|
||||
@ -10,13 +9,9 @@ import Html.Styled.Attributes as Attributes
|
||||
import Html.Styled.Events as Events
|
||||
import Html.Styled.Lazy as Lazy
|
||||
import KeyboardSupport exposing (KeyboardSupport)
|
||||
import Nri.Ui.ClickableSvg.V2 as ClickableSvg
|
||||
import Nri.Ui.ClickableText.V3 as ClickableText
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Container.V2 as Container
|
||||
import Nri.Ui.Heading.V2 as Heading
|
||||
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||
import Routes exposing (Route)
|
||||
|
||||
|
||||
type alias Example state msg =
|
||||
@ -98,13 +93,23 @@ wrapState wrapState_ unwrapState example =
|
||||
}
|
||||
|
||||
|
||||
preview : (Route -> msg2) -> Example state msg -> Html msg2
|
||||
preview navigate =
|
||||
Lazy.lazy (preview_ navigate)
|
||||
preview :
|
||||
{ navigate : Example state msg -> msg2
|
||||
, exampleHref : Example state msg -> String
|
||||
}
|
||||
-> Example state msg
|
||||
-> Html msg2
|
||||
preview navConfig =
|
||||
Lazy.lazy (preview_ navConfig)
|
||||
|
||||
|
||||
preview_ : (Route -> msg2) -> Example state msg -> Html msg2
|
||||
preview_ navigate example =
|
||||
preview_ :
|
||||
{ navigate : Example state msg -> msg2
|
||||
, exampleHref : Example state msg -> String
|
||||
}
|
||||
-> Example state msg
|
||||
-> Html msg2
|
||||
preview_ { navigate, exampleHref } example =
|
||||
Container.view
|
||||
[ Container.gray
|
||||
, Container.css
|
||||
@ -114,7 +119,7 @@ preview_ navigate example =
|
||||
, Css.cursor Css.pointer
|
||||
]
|
||||
]
|
||||
, Container.custom [ Events.onClick (navigate (Routes.Doodad example.name)) ]
|
||||
, Container.custom [ Events.onClick (navigate example) ]
|
||||
, Container.html
|
||||
(ClickableText.link example.name
|
||||
[ ClickableText.href (exampleHref example)
|
||||
@ -134,26 +139,17 @@ preview_ navigate example =
|
||||
]
|
||||
|
||||
|
||||
view : Maybe Route -> EllieLink.Config -> Example state msg -> Html msg
|
||||
view previousRoute ellieLinkConfig example =
|
||||
Container.view
|
||||
[ Container.pillow
|
||||
, Container.css
|
||||
[ Css.position Css.relative
|
||||
, Css.margin (Css.px 10)
|
||||
, Css.minHeight (Css.calc (Css.vh 100) Css.minus (Css.px 20))
|
||||
, Css.boxSizing Css.borderBox
|
||||
]
|
||||
, Container.html (view_ previousRoute ellieLinkConfig example)
|
||||
, Container.custom [ Attributes.id (String.replace "." "-" example.name) ]
|
||||
]
|
||||
view : EllieLink.Config -> Example state msg -> Html msg
|
||||
view ellieLinkConfig example =
|
||||
Html.div [ Attributes.id (String.replace "." "-" example.name) ]
|
||||
(view_ ellieLinkConfig example)
|
||||
|
||||
|
||||
view_ : Maybe Route -> EllieLink.Config -> Example state msg -> List (Html msg)
|
||||
view_ previousRoute ellieLinkConfig example =
|
||||
view_ : EllieLink.Config -> Example state msg -> List (Html msg)
|
||||
view_ ellieLinkConfig example =
|
||||
let
|
||||
navMenu items =
|
||||
Html.nav [ Widget.label "Example" ]
|
||||
Html.nav [ Widget.label (fullName example) ]
|
||||
[ Html.ul
|
||||
[ Attributes.css
|
||||
[ margin zero
|
||||
@ -176,50 +172,20 @@ view_ previousRoute ellieLinkConfig example =
|
||||
)
|
||||
]
|
||||
in
|
||||
[ Html.header
|
||||
[ Html.div
|
||||
[ Attributes.css
|
||||
[ Css.paddingBottom (Css.px 10)
|
||||
, Css.marginBottom (Css.px 20)
|
||||
, Css.borderBottom3 (Css.px 1) Css.solid Colors.gray92
|
||||
]
|
||||
]
|
||||
[ navMenu
|
||||
[ Heading.h1
|
||||
[ Heading.custom [ Aria.currentPage ] ]
|
||||
[ Html.text (fullName example)
|
||||
]
|
||||
, docsLink example
|
||||
, srcLink example
|
||||
, closeExample previousRoute example
|
||||
]
|
||||
[ navMenu [ docsLink example, srcLink example ]
|
||||
]
|
||||
, KeyboardSupport.view example.keyboardSupport
|
||||
, Html.main_ [] (example.view ellieLinkConfig example.state)
|
||||
, Html.div [] (example.view ellieLinkConfig example.state)
|
||||
]
|
||||
|
||||
|
||||
closeExample : Maybe Route -> Example state msg -> Html msg
|
||||
closeExample previousRoute example =
|
||||
ClickableSvg.link ("Close " ++ example.name ++ " example")
|
||||
UiIcon.x
|
||||
[ ClickableSvg.href
|
||||
(Maybe.withDefault Routes.All previousRoute
|
||||
|> Routes.toString
|
||||
)
|
||||
, ClickableSvg.exactSize 20
|
||||
, ClickableSvg.css
|
||||
[ Css.position Css.absolute
|
||||
, Css.top (Css.px 15)
|
||||
, Css.right (Css.px 15)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
exampleHref : Example state msg -> String
|
||||
exampleHref example =
|
||||
Routes.toString (Routes.Doodad example.name)
|
||||
|
||||
|
||||
docsLink : Example state msg -> Html msg
|
||||
docsLink example =
|
||||
let
|
||||
@ -229,7 +195,6 @@ docsLink example =
|
||||
in
|
||||
ClickableText.link "Docs"
|
||||
[ ClickableText.linkExternal link
|
||||
, ClickableText.css [ Css.marginLeft (Css.px 20) ]
|
||||
]
|
||||
|
||||
|
||||
|
@ -4,6 +4,7 @@ import Example exposing (Example)
|
||||
import Examples.Accordion as Accordion
|
||||
import Examples.AssignmentIcon as AssignmentIcon
|
||||
import Examples.Balloon as Balloon
|
||||
import Examples.BreadCrumbs as BreadCrumbs
|
||||
import Examples.Button as Button
|
||||
import Examples.Checkbox as Checkbox
|
||||
import Examples.ClickableSvg as ClickableSvg
|
||||
@ -97,6 +98,25 @@ all =
|
||||
BalloonState childState ->
|
||||
Just childState
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
, BreadCrumbs.example
|
||||
|> Example.wrapMsg BreadCrumbsMsg
|
||||
(\msg ->
|
||||
case msg of
|
||||
BreadCrumbsMsg childMsg ->
|
||||
Just childMsg
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
|> Example.wrapState BreadCrumbsState
|
||||
(\msg ->
|
||||
case msg of
|
||||
BreadCrumbsState childState ->
|
||||
Just childState
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
@ -772,6 +792,7 @@ type State
|
||||
= AccordionState Accordion.State
|
||||
| AssignmentIconState AssignmentIcon.State
|
||||
| BalloonState Balloon.State
|
||||
| BreadCrumbsState BreadCrumbs.State
|
||||
| ButtonState Button.State
|
||||
| CheckboxState Checkbox.State
|
||||
| ClickableSvgState ClickableSvg.State
|
||||
@ -813,6 +834,7 @@ type Msg
|
||||
= AccordionMsg Accordion.Msg
|
||||
| AssignmentIconMsg AssignmentIcon.Msg
|
||||
| BalloonMsg Balloon.Msg
|
||||
| BreadCrumbsMsg BreadCrumbs.Msg
|
||||
| ButtonMsg Button.Msg
|
||||
| CheckboxMsg Checkbox.Msg
|
||||
| ClickableSvgMsg ClickableSvg.Msg
|
||||
|
39
styleguide-app/Examples/BreadCrumbs.elm
Normal file
39
styleguide-app/Examples/BreadCrumbs.elm
Normal file
@ -0,0 +1,39 @@
|
||||
module Examples.BreadCrumbs exposing (example, State, Msg)
|
||||
|
||||
{-|
|
||||
|
||||
@docs example, State, Msg
|
||||
|
||||
-}
|
||||
|
||||
import Category exposing (Category(..))
|
||||
import Example exposing (Example)
|
||||
import Nri.Ui.Text.V6 as Text
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias State =
|
||||
{}
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Msg =
|
||||
()
|
||||
|
||||
|
||||
{-| -}
|
||||
example : Example State Msg
|
||||
example =
|
||||
{ name = "BreadCrumbs"
|
||||
, version = 1
|
||||
, categories = [ Layout ]
|
||||
, keyboardSupport = []
|
||||
, state = {}
|
||||
, update = \_ m -> ( m, Cmd.none )
|
||||
, subscriptions = \_ -> Sub.none
|
||||
, preview = []
|
||||
, view =
|
||||
\ellieLinkConfig settings ->
|
||||
[ Text.mediumBody [ Text.plaintext "🚧 Example coming soon! 🚧" ]
|
||||
]
|
||||
}
|
@ -143,14 +143,14 @@ init =
|
||||
controlNavAttributes : Control (List ( String, SideNav.NavAttribute ))
|
||||
controlNavAttributes =
|
||||
ControlExtra.list
|
||||
|> ControlExtra.optionalListItem "navLabel"
|
||||
|> ControlExtra.optionalListItemDefaultChecked "navLabel"
|
||||
(Control.map
|
||||
(\val ->
|
||||
( "SideNav.navLabel \"" ++ val ++ "\""
|
||||
, SideNav.navLabel val
|
||||
)
|
||||
)
|
||||
(Control.string "Entries")
|
||||
(Control.string "Example")
|
||||
)
|
||||
|> ControlExtra.optionalListItem "navNotMobileCss"
|
||||
(Control.choice
|
||||
|
@ -24,7 +24,6 @@ import Nri.Ui.Tabs.V7 as Tabs exposing (Alignment(..), Tab)
|
||||
import Nri.Ui.Text.V6 as Text
|
||||
import Nri.Ui.Tooltip.V3 as Tooltip
|
||||
import Nri.Ui.UiIcon.V1 as UiIcon
|
||||
import Routes
|
||||
import Task
|
||||
|
||||
|
||||
@ -217,8 +216,7 @@ allTabs openTooltipId labelledBy =
|
||||
|> Svg.toHtml
|
||||
in
|
||||
[ Tabs.build { id = First, idString = "tab-0" }
|
||||
([ Tabs.spaHref <| Routes.toString (Routes.Doodad exampleName)
|
||||
, Tabs.tabString "1"
|
||||
([ Tabs.tabString "1"
|
||||
, Tabs.withTooltip
|
||||
[ Tooltip.plaintext "Link Example"
|
||||
, Tooltip.onToggle (ToggleTooltip First)
|
||||
|
@ -1,36 +1,64 @@
|
||||
module Routes exposing (Route(..), fromLocation, toString)
|
||||
module Routes exposing (Route(..), fromLocation, toString, updateExample, viewBreadCrumbs)
|
||||
|
||||
import Accessibility.Styled as Html exposing (Html)
|
||||
import Category
|
||||
import Dict exposing (Dict)
|
||||
import Example exposing (Example)
|
||||
import Html.Styled.Attributes as Attributes
|
||||
import Nri.Ui.BreadCrumbs.V1 as BreadCrumbs exposing (BreadCrumb, BreadCrumbs)
|
||||
import Nri.Ui.Util exposing (dashify)
|
||||
import Parser exposing ((|.), (|=), Parser)
|
||||
import Url exposing (Url)
|
||||
|
||||
|
||||
type Route
|
||||
= Doodad String
|
||||
type Route state msg
|
||||
= Doodad (Example state msg)
|
||||
| Category Category.Category
|
||||
| CategoryDoodad Category.Category (Example state msg)
|
||||
| All
|
||||
| NotFound String
|
||||
|
||||
|
||||
toString : Route -> String
|
||||
toString : Route state msg -> String
|
||||
toString route_ =
|
||||
case route_ of
|
||||
Doodad exampleName ->
|
||||
"#/doodad/" ++ exampleName
|
||||
Doodad example ->
|
||||
"#/doodad/" ++ example.name
|
||||
|
||||
Category c ->
|
||||
"#/category/" ++ Debug.toString c
|
||||
"#/category/" ++ Category.forRoute c
|
||||
|
||||
CategoryDoodad c example ->
|
||||
"#/category_doodad/" ++ Category.forRoute c ++ "/" ++ example.name
|
||||
|
||||
All ->
|
||||
"#/"
|
||||
|
||||
NotFound unmatchedRoute ->
|
||||
unmatchedRoute
|
||||
|
||||
route : Parser Route
|
||||
route =
|
||||
|
||||
route : Dict String (Example state msg) -> Parser (Route state msg)
|
||||
route examples =
|
||||
let
|
||||
findExample : (Example state msg -> Route state msg) -> String -> Route state msg
|
||||
findExample toRoute name =
|
||||
Dict.get name examples
|
||||
|> Maybe.map toRoute
|
||||
|> Maybe.withDefault (NotFound name)
|
||||
in
|
||||
Parser.oneOf
|
||||
[ Parser.succeed Category
|
||||
[ Parser.succeed (\cat -> findExample (CategoryDoodad cat))
|
||||
|. Parser.token "/category_doodad/"
|
||||
|= (Parser.getChompedString (Parser.chompWhile ((/=) '/'))
|
||||
|> Parser.andThen category
|
||||
)
|
||||
|. Parser.token "/"
|
||||
|= restOfPath
|
||||
, Parser.succeed Category
|
||||
|. Parser.token "/category/"
|
||||
|= (restOfPath |> Parser.andThen category)
|
||||
, Parser.succeed Doodad
|
||||
, Parser.succeed (findExample Doodad)
|
||||
|. Parser.token "/doodad/"
|
||||
|= restOfPath
|
||||
, Parser.succeed All
|
||||
@ -52,9 +80,91 @@ category string =
|
||||
Parser.problem e
|
||||
|
||||
|
||||
fromLocation : Url -> Route
|
||||
fromLocation location =
|
||||
updateExample : Example state msg -> Route state msg -> Maybe (Route state msg)
|
||||
updateExample example route_ =
|
||||
case route_ of
|
||||
Doodad _ ->
|
||||
Just (Doodad example)
|
||||
|
||||
CategoryDoodad cat _ ->
|
||||
Just (CategoryDoodad cat example)
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
|
||||
fromLocation : Dict String (Example state msg) -> Url -> Route state msg
|
||||
fromLocation examples location =
|
||||
location.fragment
|
||||
|> Maybe.withDefault ""
|
||||
|> Parser.run route
|
||||
|> Parser.run (route examples)
|
||||
|> Result.withDefault All
|
||||
|
||||
|
||||
viewBreadCrumbs : Route state msg -> Html msg2
|
||||
viewBreadCrumbs currentRoute =
|
||||
breadCrumbs currentRoute
|
||||
|> Maybe.map
|
||||
(BreadCrumbs.view
|
||||
{ aTagAttributes = \r -> [ Attributes.href ("/" ++ toString r) ]
|
||||
, isCurrentRoute = (==) currentRoute
|
||||
}
|
||||
)
|
||||
|> Maybe.withDefault (Html.text "")
|
||||
|
||||
|
||||
breadCrumbs : Route state msg -> Maybe (BreadCrumbs (Route state msg))
|
||||
breadCrumbs route_ =
|
||||
case route_ of
|
||||
All ->
|
||||
Just allBreadCrumb
|
||||
|
||||
Category category_ ->
|
||||
Just (categoryCrumb category_)
|
||||
|
||||
Doodad example ->
|
||||
Just
|
||||
(BreadCrumbs.after allBreadCrumb
|
||||
(doodadCrumb example)
|
||||
)
|
||||
|
||||
CategoryDoodad category_ example ->
|
||||
Just
|
||||
(BreadCrumbs.after (categoryCrumb category_)
|
||||
(doodadCrumb example)
|
||||
)
|
||||
|
||||
NotFound _ ->
|
||||
Nothing
|
||||
|
||||
|
||||
allBreadCrumb : BreadCrumbs (Route state msg)
|
||||
allBreadCrumb =
|
||||
BreadCrumbs.init
|
||||
{ icon = Nothing
|
||||
, iconStyle = BreadCrumbs.Default
|
||||
, id = "breadcrumbs__all"
|
||||
, text = "All"
|
||||
, route = All
|
||||
}
|
||||
|
||||
|
||||
categoryCrumb : Category.Category -> BreadCrumbs (Route state msg)
|
||||
categoryCrumb category_ =
|
||||
BreadCrumbs.after allBreadCrumb
|
||||
{ icon = Nothing
|
||||
, iconStyle = BreadCrumbs.Default
|
||||
, id = "breadcrumbs__" ++ Category.forId category_
|
||||
, text = Category.forDisplay category_
|
||||
, route = Category category_
|
||||
}
|
||||
|
||||
|
||||
doodadCrumb : Example state msg -> BreadCrumb (Route state msg)
|
||||
doodadCrumb example =
|
||||
{ icon = Nothing
|
||||
, iconStyle = BreadCrumbs.Default
|
||||
, id = "breadcrumbs__" ++ dashify example.name
|
||||
, text = Example.fullName example
|
||||
, route = Doodad example
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
module SwitchExampleSpec exposing (suite)
|
||||
|
||||
import Examples.Switch exposing (Msg, State, example)
|
||||
import ProgramTest exposing (..)
|
||||
import Routes exposing (Route)
|
||||
import Test exposing (..)
|
||||
@ -7,9 +8,9 @@ import Test.Html.Selector exposing (..)
|
||||
import TestApp exposing (app)
|
||||
|
||||
|
||||
route : Route
|
||||
route : Route State Msg
|
||||
route =
|
||||
Routes.Doodad "Switch"
|
||||
Routes.Doodad example
|
||||
|
||||
|
||||
suite : Test
|
||||
|
@ -7,6 +7,7 @@
|
||||
"Nri.Ui.AssetPath",
|
||||
"Nri.Ui.AssignmentIcon.V2",
|
||||
"Nri.Ui.Balloon.V1",
|
||||
"Nri.Ui.BreadCrumbs.V1",
|
||||
"Nri.Ui.Button.V10",
|
||||
"Nri.Ui.Checkbox.V5",
|
||||
"Nri.Ui.ClickableSvg.V2",
|
||||
|
Loading…
Reference in New Issue
Block a user