Merge remote-tracking branch 'origin/master' into premium-checkbox-v6

This commit is contained in:
brookeangel 2019-07-24 10:18:27 -07:00
commit 8041530e72
11 changed files with 2229 additions and 1313 deletions

View File

@ -31,7 +31,7 @@ styleguide-app/bundle.js: lib/index.js styleguide-app/manifest.js styleguide-app
npx browserify --entry styleguide-app/manifest.js --outfile styleguide-app/bundle.js
styleguide-app/elm.js: styleguide-app/bundle.js $(shell find src styleguide-app -type f -name '*.elm')
cd styleguide-app; npx elm make Main.elm --output=$(@F)
cd styleguide-app && npx elm make Main.elm --output=$(@F)
# for publishing styleguide

View File

@ -3,7 +3,7 @@
"name": "NoRedInk/noredink-ui",
"summary": "UI Widgets we use at NRI",
"license": "BSD-3-Clause",
"version": "6.24.1",
"version": "6.25.0",
"exposed-modules": [
"Nri.Ui.Alert.V2",
"Nri.Ui.Alert.V3",
@ -21,6 +21,7 @@
"Nri.Ui.Button.V6",
"Nri.Ui.Button.V7",
"Nri.Ui.Button.V8",
"Nri.Ui.Button.V9",
"Nri.Ui.Checkbox.V3",
"Nri.Ui.Checkbox.V4",
"Nri.Ui.Checkbox.V5",
@ -33,6 +34,7 @@
"Nri.Ui.Dropdown.V2",
"Nri.Ui.Effects.V1",
"Nri.Ui.Fonts.V1",
"Nri.Ui.Heading.V1",
"Nri.Ui.Html.Attributes.V2",
"Nri.Ui.Html.V3",
"Nri.Ui.Icon.V3",

1977
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -27,8 +27,9 @@
"homepage": "https://github.com/NoRedInk/NoRedInk-ui#readme",
"devDependencies": {
"browserify": "16.2.3",
"elm": "0.19.0",
"elm": "^0.19.0-no-deps",
"elm-format": "0.8.1",
"elm-test": "0.19.0-rev6"
"elm-test": "0.19.0-rev6",
"request": "^2.88.0"
}
}

934
src/Nri/Ui/Button/V9.elm Normal file
View File

@ -0,0 +1,934 @@
module Nri.Ui.Button.V9 exposing
( button, link
, Attribute
, icon, custom
, onClick
, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
, small, medium, large
, exactWidth, unboundedWidth, fillContainerWidth
, primary, secondary, danger, premium
, enabled, unfulfilled, disabled, error, loading, success
, delete
, toggleButton
)
{-|
# Changes from V8:
- Changes API to be attribute-based, rather than config-based
# Create a button or link
@docs button, link
@docs Attribute
@docs icon, custom
## Behavior
@docs onClick
@docs linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
## Sizing
@docs small, medium, large
@docs exactWidth, unboundedWidth, fillContainerWidth
## Change the color scheme
@docs primary, secondary, danger, premium
## Change the state (buttons only)
@docs enabled, unfulfilled, disabled, error, loading, success
# Commonly-used buttons
@docs delete
@docs toggleButton
-}
import Accessibility.Styled as Html exposing (Attribute, Html)
import Accessibility.Styled.Role as Role
import Accessibility.Styled.Widget as Widget
import Css exposing (Style)
import Css.Global
import EventExtras.Styled as EventExtras
import Html as RootHtml
import Html.Styled as Styled
import Html.Styled.Attributes as Attributes
import Html.Styled.Events as Events
import Json.Decode
import Markdown.Block
import Markdown.Inline
import Nri.Ui
import Nri.Ui.AssetPath as AssetPath exposing (Asset)
import Nri.Ui.Colors.Extra as ColorsExtra
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1
import Nri.Ui.Html.Attributes.V2 as AttributesExtra
import Nri.Ui.Svg.V1 as NriSvg exposing (Svg)
import Svg
import Svg.Attributes
styledName : String -> String
styledName suffix =
"Nri-Ui-Button-V9-" ++ suffix
{-|
Button.button "My great button!"
[ Button.onClick ()
, Button.enabled
]
By default, the button is enabled, Medium sized, with primary colors, and an unbounded width.
-}
button : String -> List (Attribute msg) -> Html msg
button name attributes =
(label name :: attributes)
|> List.foldl (\(Attribute attribute) b -> attribute b) build
|> renderButton
{-|
Button.link "My great link!"
[ Button.href "My href"
, Button.secondary
]
By default, the link is Medium sized, with primary colors, and an unbounded width.
-}
link : String -> List (Attribute msg) -> Html msg
link name attributes =
(label name :: attributes)
|> List.foldl (\(Attribute attribute) l -> attribute l) build
|> renderLink
{-| -}
label : String -> Attribute msg
label label_ =
set (\attributes -> { attributes | label = label_ })
{-| -}
icon : Svg -> Attribute msg
icon icon_ =
set (\attributes -> { attributes | icon = Just icon_ })
{-| -}
custom : List (Html.Attribute msg) -> Attribute msg
custom attributes =
set
(\config ->
{ config
| customAttributes = List.append config.customAttributes attributes
}
)
-- LINKING, CLICKING, and TRACKING BEHAVIOR
{-| -}
onClick : msg -> Attribute msg
onClick msg =
set (\attributes -> { attributes | onClick = Just msg })
type Link
= Default
| WithTracking
| SinglePageApp
| WithMethod String
| External
| ExternalWithTracking
{-| 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 : String -> Attribute msg
linkSpa url =
set
(\attributes ->
{ attributes
| linkType = SinglePageApp
, url = url
}
)
{-| Wrap some text so it looks like a button, but actually is wrapped in an anchor to
some url, and it's an HTTP request (Rails includes JS to make this use the given HTTP method)
-}
linkWithMethod : { method : String, url : String } -> Attribute msg
linkWithMethod { method, url } =
set
(\attributes ->
{ attributes
| linkType = WithMethod method
, url = url
}
)
{-| Wrap some text so it looks like a button, but actually is wrapped in an anchor to some url.
This should only take in messages that result in a Msg that triggers Analytics.trackAndRedirect.
For buttons that trigger other effects on the page, please use Nri.Button.button instead.
-}
linkWithTracking : { track : msg, url : String } -> Attribute msg
linkWithTracking { track, url } =
set
(\attributes ->
{ attributes
| linkType = WithTracking
, url = url
, onClick = Just track
}
)
{-| Wrap some text so it looks like a button, but actually is wrapped in an anchor to
some url and have it open to an external site
-}
linkExternal : String -> Attribute msg
linkExternal url =
set
(\attributes ->
{ attributes
| linkType = External
, url = url
}
)
{-| Wrap some text so it looks like a button, but actually is wrapped in an anchor to some url and have it open to an external site.
-}
linkExternalWithTracking : { track : msg, url : String } -> Attribute msg
linkExternalWithTracking { track, url } =
set
(\attributes ->
{ attributes
| linkType = ExternalWithTracking
, url = url
, onClick = Just track
}
)
-- BUTTON SIZING
{-| -}
small : Attribute msg
small =
set (\attributes -> { attributes | size = Small })
{-| -}
medium : Attribute msg
medium =
set (\attributes -> { attributes | size = Medium })
{-| -}
large : Attribute msg
large =
set (\attributes -> { attributes | size = Large })
-- BUTTON WIDTH
type ButtonWidth
= WidthExact Int
| WidthUnbounded
| WidthFillContainer
{-| Sizes for buttons and links that have button classes
-}
type ButtonSize
= Small
| Medium
| Large
{-| Define a size in `px` for the button's total width.
-}
exactWidth : Int -> Attribute msg
exactWidth inPx =
set (\attributes -> { attributes | width = WidthExact inPx })
{-| Leave the maxiumum width unbounded (there is a minimum width).
-}
unboundedWidth : Attribute msg
unboundedWidth =
set (\attributes -> { attributes | width = WidthUnbounded })
{-| -}
fillContainerWidth : Attribute msg
fillContainerWidth =
set (\attributes -> { attributes | width = WidthFillContainer })
-- COLOR SCHEMES
{-| -}
primary : Attribute msg
primary =
set
(\attributes ->
{ attributes | style = primaryColors }
)
{-| -}
secondary : Attribute msg
secondary =
set
(\attributes ->
{ attributes | style = secondaryColors }
)
{-| -}
danger : Attribute msg
danger =
set
(\attributes ->
{ attributes
| style =
{ background = Colors.red
, hover = Colors.redDark
, text = Colors.white
, border = Nothing
, shadow = Colors.redDark
}
}
)
{-| -}
premium : Attribute msg
premium =
set
(\attributes ->
{ attributes
| style =
{ background = Colors.yellow
, hover = Colors.ochre
, text = Colors.navy
, border = Nothing
, shadow = Colors.ochre
}
}
)
-- BUTTON STATE
type ButtonState
= Enabled
| Unfulfilled
| Disabled
| Error
| Loading
| Success
{-| -}
enabled : Attribute msg
enabled =
set (\attributes -> { attributes | state = Enabled })
{-| Shows inactive styles.
-}
unfulfilled : Attribute msg
unfulfilled =
set (\attributes -> { attributes | state = Unfulfilled })
{-| Shows inactive styling. If a button, this attribute will disable it.
-}
disabled : Attribute msg
disabled =
set (\attributes -> { attributes | state = Disabled })
{-| Shows error styling. If a button, this attribute will disable it.
-}
error : Attribute msg
error =
set (\attributes -> { attributes | state = Error })
{-| Shows loading styling. If a button, this attribute will disable it.
-}
loading : Attribute msg
loading =
set (\attributes -> { attributes | state = Loading })
{-| Shows success styling. If a button, this attribute will disable it.
-}
success : Attribute msg
success =
set (\attributes -> { attributes | state = Success })
{-| -}
type Attribute msg
= Attribute (ButtonOrLink msg -> ButtonOrLink msg)
-- INTERNALS
set :
(ButtonOrLinkAttributes msg -> ButtonOrLinkAttributes msg)
-> Attribute msg
set with =
Attribute (\(ButtonOrLink config) -> ButtonOrLink (with config))
build : ButtonOrLink msg
build =
ButtonOrLink
{ onClick = Nothing
, url = "#"
, linkType = Default
, size = Medium
, style = primaryColors
, width = WidthUnbounded
, label = ""
, state = Enabled
, icon = Nothing
, customAttributes = []
}
type ButtonOrLink msg
= ButtonOrLink (ButtonOrLinkAttributes msg)
type alias ButtonOrLinkAttributes msg =
{ onClick : Maybe msg
, url : String
, linkType : Link
, size : ButtonSize
, style : ColorPalette
, width : ButtonWidth
, label : String
, state : ButtonState
, icon : Maybe Svg
, customAttributes : List (Html.Attribute msg)
}
renderButton : ButtonOrLink msg -> Html msg
renderButton ((ButtonOrLink config) as button_) =
let
buttonStyle_ =
getColorPalette button_
isDisabled =
case config.state of
Enabled ->
False
Disabled ->
True
Error ->
True
Unfulfilled ->
False
Loading ->
True
Success ->
True
in
Nri.Ui.styled Html.button
(styledName "customButton")
[ buttonStyles config.size config.width buttonStyle_ ]
((Maybe.map Events.onClick config.onClick
|> Maybe.withDefault AttributesExtra.none
)
:: Attributes.disabled isDisabled
:: Attributes.type_ "button"
:: config.customAttributes
)
[ viewLabel config.icon config.label ]
renderLink : ButtonOrLink msg -> Html msg
renderLink ((ButtonOrLink config) as link_) =
let
colorPalette =
getColorPalette link_
linkBase linkFunctionName extraAttrs =
Nri.Ui.styled Styled.a
(styledName linkFunctionName)
[ buttonStyles config.size config.width colorPalette ]
(Attributes.href config.url :: extraAttrs)
[ viewLabel config.icon config.label ]
in
case config.linkType of
Default ->
linkBase "link"
(Attributes.target "_self" :: config.customAttributes)
SinglePageApp ->
linkBase "linkSpa"
((Maybe.map EventExtras.onClickPreventDefaultForLinkWithHref config.onClick
|> Maybe.withDefault AttributesExtra.none
)
:: config.customAttributes
)
WithMethod method ->
linkBase "linkWithMethod"
(Attributes.attribute "data-method" method
:: config.customAttributes
)
WithTracking ->
linkBase
"linkWithTracking"
((Maybe.map
(\msg ->
Events.preventDefaultOn "click"
(Json.Decode.succeed ( msg, True ))
)
config.onClick
|> Maybe.withDefault AttributesExtra.none
)
:: config.customAttributes
)
External ->
linkBase "linkExternal"
(Attributes.target "_blank" :: config.customAttributes)
ExternalWithTracking ->
linkBase "linkExternalWithTracking"
(List.append
[ Attributes.target "_blank"
, Maybe.map EventExtras.onClickForLinkWithHref config.onClick
|> Maybe.withDefault AttributesExtra.none
]
config.customAttributes
)
-- DELETE BUTTON
{-| A delete button (blue X)
-}
delete : { label : String, onClick : msg } -> Html msg
delete config =
Nri.Ui.styled Html.button
(styledName "delete")
[ Css.display Css.inlineBlock
, Css.backgroundRepeat Css.noRepeat
, Css.backgroundColor Css.transparent
, Css.backgroundPosition Css.center
, Css.backgroundSize Css.contain
, Css.border Css.zero
, Css.width (Css.px 15)
, Css.height (Css.px 15)
, Css.padding Css.zero
, Css.margin2 Css.zero (Css.px 6)
, Css.cursor Css.pointer
, Css.color Colors.azure
]
[ Events.onClick config.onClick
, Attributes.type_ "button"
, -- reference: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role#Labeling_buttons
Widget.label config.label
]
[ Svg.svg [ Svg.Attributes.viewBox "0 0 25 25" ]
[ Svg.title [] [ RootHtml.text "Delete" ]
, Svg.path
[ Svg.Attributes.fill "#146aff" -- TODO: this should be azure, but css colors aren't extractable afaik
, Svg.Attributes.d "M1.067 6.015c-1.423-1.422-1.423-3.526 0-4.948 1.422-1.423 3.526-1.423 4.948 0l6.371 6.37 6.371-6.37c1.422-1.423 3.783-1.423 5.176 0 1.423 1.422 1.423 3.782 0 5.176l-6.37 6.37 6.37 6.372c1.423 1.422 1.423 3.526 0 4.948-1.422 1.423-3.526 1.423-4.948 0l-6.371-6.37-6.371 6.37c-1.422 1.423-3.783 1.423-5.176 0-1.423-1.422-1.423-3.782 0-5.176l6.37-6.143-6.37-6.599z"
]
[]
]
|> Styled.fromUnstyled
]
-- TOGGLE BUTTON
{-| A button that can be toggled into a pressed state and back again.
-}
toggleButton :
{ label : String
, onSelect : msg
, onDeselect : msg
, pressed : Bool
}
-> Html msg
toggleButton config =
let
toggledStyles =
if config.pressed then
Css.batch
[ Css.color Colors.gray20
, Css.backgroundColor Colors.glacier
, Css.boxShadow5 Css.inset Css.zero (Css.px 3) Css.zero (ColorsExtra.withAlpha 0.2 Colors.gray20)
, Css.border3 (Css.px 1) Css.solid Colors.azure
, Css.fontWeight Css.bold
]
else
Css.batch
[]
in
Nri.Ui.styled Html.button
(styledName "toggleButton")
[ buttonStyles Medium WidthUnbounded secondaryColors
, toggledStyles
]
[ Events.onClick
(if config.pressed then
config.onDeselect
else
config.onSelect
)
, Widget.pressed <| Just config.pressed
-- reference: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role#Labeling_buttons
, Role.button
-- Note: setting type: 'button' removes the default behavior of submit
-- equivalent to preventDefaultBehavior = false
-- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-name
, Attributes.type_ "button"
]
[ viewLabel Nothing config.label ]
buttonStyles : ButtonSize -> ButtonWidth -> ColorPalette -> Style
buttonStyles size width colors =
Css.batch
[ buttonStyle
, sizeStyle size width
, colorStyle colors
]
viewLabel : Maybe Svg -> String -> Html msg
viewLabel maybeSvg label_ =
Nri.Ui.styled Html.span
"button-label-span"
[ Css.overflow Css.hidden -- Keep scrollbars out of our button
, Css.overflowWrap Css.breakWord -- Ensure that words that exceed the button width break instead of disappearing
, Css.padding2 (Css.px 2) Css.zero -- Without a bit of bottom padding, text that extends below the baseline, like "g" gets cut off
]
[]
(case maybeSvg of
Nothing ->
renderMarkdown label_
Just svg ->
NriSvg.toHtml svg :: renderMarkdown label_
)
renderMarkdown : String -> List (Html msg)
renderMarkdown markdown =
case Markdown.Block.parse Nothing markdown of
-- It seems to be always first wrapped in a `Paragraph` and never directly a `PlainInline`
[ Markdown.Block.Paragraph _ inlines ] ->
List.map (Markdown.Inline.toHtml >> Styled.fromUnstyled) inlines
_ ->
[ Html.text markdown ]
-- STYLES
buttonStyle : Style
buttonStyle =
Css.batch
[ Css.cursor Css.pointer
, -- Specifying the font can and should go away after bootstrap is removed from application.css
Nri.Ui.Fonts.V1.baseFont
, Css.textOverflow Css.ellipsis
, Css.overflow Css.hidden
, Css.textDecoration Css.none
, Css.backgroundImage Css.none
, Css.textShadow Css.none
, Css.property "transition" "background-color 0.2s, color 0.2s, box-shadow 0.2s, border 0.2s, border-width 0s"
, Css.boxShadow Css.none
, Css.border Css.zero
, Css.marginBottom Css.zero
, Css.hover [ Css.textDecoration Css.none ]
, Css.disabled [ Css.cursor Css.notAllowed ]
, Css.display Css.inlineFlex
, Css.alignItems Css.center
, Css.justifyContent Css.center
]
-- COLORS
type alias ColorPalette =
{ background : Css.Color
, hover : Css.Color
, text : Css.Color
, border : Maybe Css.Color
, shadow : Css.Color
}
primaryColors : ColorPalette
primaryColors =
{ background = Colors.azure
, hover = Colors.azureDark
, text = Colors.white
, border = Nothing
, shadow = Colors.azureDark
}
secondaryColors : ColorPalette
secondaryColors =
{ background = Colors.white
, hover = Colors.glacier
, text = Colors.azure
, border = Just <| Colors.azure
, shadow = Colors.azure
}
getColorPalette : ButtonOrLink msg -> ColorPalette
getColorPalette (ButtonOrLink config) =
case config.state of
Enabled ->
config.style
Disabled ->
{ background = Colors.gray92
, hover = Colors.gray92
, text = Colors.gray45
, border = Nothing
, shadow = Colors.gray92
}
Error ->
{ background = Colors.purple
, hover = Colors.purple
, text = Colors.white
, border = Nothing
, shadow = Colors.purple
}
Unfulfilled ->
{ background = Colors.gray92
, hover = Colors.gray92
, text = Colors.gray45
, border = Nothing
, shadow = Colors.gray92
}
Loading ->
{ background = Colors.glacier
, hover = Colors.glacier
, text = Colors.navy
, border = Nothing
, shadow = Colors.glacier
}
Success ->
{ background = Colors.greenDark
, hover = Colors.greenDark
, text = Colors.white
, border = Nothing
, shadow = Colors.greenDark
}
colorStyle : ColorPalette -> Style
colorStyle colorPalette =
Css.batch
[ Css.color colorPalette.text
, Css.backgroundColor colorPalette.background
, Css.fontWeight (Css.int 700)
, Css.textAlign Css.center
, case colorPalette.border of
Nothing ->
Css.borderStyle Css.none
Just color ->
Css.batch
[ Css.borderColor color
, Css.borderStyle Css.solid
]
, Css.borderBottomStyle Css.solid
, Css.borderBottomColor colorPalette.shadow
, Css.fontStyle Css.normal
, Css.hover
[ Css.color colorPalette.text
, Css.backgroundColor colorPalette.hover
, Css.disabled [ Css.backgroundColor colorPalette.background ]
]
, Css.visited [ Css.color colorPalette.text ]
]
sizeStyle : ButtonSize -> ButtonWidth -> Style
sizeStyle size width =
let
config =
case size of
Small ->
{ fontSize = 15
, height = 36
, imageHeight = 15
, shadowHeight = 2
, minWidth = 75
}
Medium ->
{ fontSize = 17
, height = 45
, imageHeight = 15
, shadowHeight = 3
, minWidth = 100
}
Large ->
{ fontSize = 20
, height = 56
, imageHeight = 20
, shadowHeight = 4
, minWidth = 200
}
sizingAttributes =
let
verticalPaddingPx =
2
in
[ Css.minHeight (Css.px config.height)
, Css.paddingTop (Css.px verticalPaddingPx)
, Css.paddingBottom (Css.px verticalPaddingPx)
]
widthAttributes =
case width of
WidthExact pxWidth ->
[ Css.maxWidth (Css.pct 100)
, Css.width (Css.px <| toFloat pxWidth)
, Css.paddingRight (Css.px 4)
, Css.paddingLeft (Css.px 4)
]
WidthUnbounded ->
[ Css.paddingLeft (Css.px 16)
, Css.paddingRight (Css.px 16)
, Css.minWidth (Css.px config.minWidth)
]
WidthFillContainer ->
[ Css.paddingLeft (Css.px 16)
, Css.paddingRight (Css.px 16)
, Css.minWidth (Css.px config.minWidth)
, Css.width (Css.pct 100)
]
lineHeightPx =
case size of
Small ->
15
Medium ->
19
Large ->
22
in
Css.batch
[ Css.fontSize (Css.px config.fontSize)
, Css.borderRadius (Css.px 8)
, Css.lineHeight (Css.px lineHeightPx)
, Css.boxSizing Css.borderBox
, Css.borderWidth (Css.px 1)
, Css.borderBottomWidth (Css.px config.shadowHeight)
, Css.batch sizingAttributes
, Css.batch widthAttributes
, Css.Global.descendants
[ Css.Global.img
[ Css.height (Css.px config.imageHeight)
, Css.marginRight (Css.px <| config.imageHeight / 6)
, Css.position Css.relative
, Css.bottom (Css.px 2)
, Css.verticalAlign Css.middle
]
, Css.Global.svg
[ Css.height (Css.px config.imageHeight) |> Css.important
, Css.width (Css.px config.imageHeight) |> Css.important
, Css.marginRight (Css.px <| config.imageHeight / 6)
, Css.position Css.relative
, Css.bottom (Css.px 2)
, Css.verticalAlign Css.middle
]
, Css.Global.svg
[ Css.important <| Css.height (Css.px config.imageHeight)
, Css.important <| Css.width Css.auto
, Css.maxWidth (Css.px (config.imageHeight * 1.25))
, Css.paddingRight (Css.px <| config.imageHeight / 6)
, Css.position Css.relative
, Css.bottom (Css.px 2)
, Css.verticalAlign Css.middle
]
]
]

190
src/Nri/Ui/Heading/V1.elm Normal file
View File

@ -0,0 +1,190 @@
module Nri.Ui.Heading.V1 exposing
( Heading, heading
, withVisualLevel, VisualLevel(..)
, withDocumentLevel, DocumentLevel(..)
, view
)
{-| Headings such as you'd find in Nri.Ui.Text.V3, but customization options for
accessibility.
@docs Heading, heading
@docs withVisualLevel, VisualLevel
@docs withDocumentLevel, DocumentLevel
@docs view
-}
import Css exposing (..)
import Html.Styled exposing (..)
import Html.Styled.Attributes exposing (css)
import Nri.Ui.Colors.V1 exposing (..)
import Nri.Ui.Fonts.V1 as Fonts
{-| -}
type Heading msg
= Heading (List (Html msg)) VisualLevel DocumentLevel
{-| start a heading. Render it with `view`.
-}
heading : List (Html msg) -> Heading msg
heading content =
Heading content Top H1
-- VISUAL LEVEL
{-| -}
type VisualLevel
= Top
| Tagline
| Subhead
| Small
{-| Customize how the heading looks. The visual hierarchy should go Top ->
Tagline -> Subhead -> Small.
heading [ text "Hello, World!" ]
|> withVisualLevel Top
|> view
-}
withVisualLevel : VisualLevel -> Heading msg -> Heading msg
withVisualLevel visualLevel (Heading content _ documentLevel) =
Heading content visualLevel documentLevel
getStyles : VisualLevel -> Style
getStyles visualLevel =
case visualLevel of
Top ->
headingStyles
{ font = Fonts.baseFont
, color = navy
, size = 30
, lineHeight = 38
, weight = 700
}
Tagline ->
headingStyles
{ font = Fonts.baseFont
, color = gray45
, size = 20
, lineHeight = 30
, weight = 400
}
Subhead ->
headingStyles
{ font = Fonts.baseFont
, color = navy
, size = 20
, lineHeight = 26
, weight = 700
}
Small ->
Css.batch
[ headingStyles
{ font = Fonts.baseFont
, color = gray20
, size = 16
, lineHeight = 21
, weight = 700
}
, letterSpacing (px -0.13)
]
headingStyles :
{ color : Color
, font : Style
, lineHeight : Float
, size : Float
, weight : Int
}
-> Style
headingStyles config =
Css.batch
[ config.font
, fontSize (px config.size)
, color config.color
, lineHeight (px config.lineHeight)
, fontWeight (int config.weight)
, padding zero
, textAlign left
, margin zero
]
-- DOCUMENT LEVEL
{-| -}
type DocumentLevel
= H1
| H2
| H3
| H4
| H5
| H6
{-| Customize the document level of the heading. For accessibility reasons, you
should have exactly one H1, and only increase the level by one. You can use a
tool like [axe](https://www.deque.com/axe/) to check this.
heading [ text "Hello, World!" ]
|> withDocumentLevel H1
|> view
-}
withDocumentLevel : DocumentLevel -> Heading msg -> Heading msg
withDocumentLevel documentLevel (Heading content visualLevel _) =
Heading content visualLevel documentLevel
getTag : DocumentLevel -> (List (Attribute msg) -> List (Html msg) -> Html msg)
getTag documentLevel =
case documentLevel of
H1 ->
h1
H2 ->
h2
H3 ->
h3
H4 ->
h4
H5 ->
h5
H6 ->
h6
-- VIEW
{-| render a Heading to Html. See the other docs in this module for
customizations.
-}
view : Heading msg -> Html msg
view (Heading content visualLevel documentLevel) =
getTag documentLevel
[ css [ getStyles visualLevel ] ]
content

View File

@ -24,6 +24,9 @@ module Nri.Ui.Text.V3 exposing
## Heading styles
Please use `Nri.Ui.Heading.V1` instead of these in new code. If you're here to
make a new Text version, please remove them.
@docs heading, subHeading, smallHeading, tagline
@ -48,73 +51,47 @@ import Html.Styled exposing (..)
import Html.Styled.Attributes exposing (css)
import Nri.Ui.Colors.V1 exposing (..)
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Heading.V1 as Heading
{-| This is a Page Heading.
-}
heading : List (Html msg) -> Html msg
heading content =
h1
[ headingStyles
{ font = Fonts.baseFont
, color = navy
, size = 30
, lineHeight = 38
, weight = 700
}
]
content
Heading.heading content
|> Heading.withVisualLevel Heading.Top
|> Heading.withDocumentLevel Heading.H1
|> Heading.view
{-| This is a tagline for a page heading.
-}
tagline : List (Html msg) -> Html msg
tagline content =
h2
[ headingStyles
{ font = Fonts.baseFont
, color = gray45
, size = 20
, lineHeight = 30
, weight = 400
}
]
content
Heading.heading content
|> Heading.withVisualLevel Heading.Tagline
|> Heading.withDocumentLevel Heading.H2
|> Heading.view
{-| This is a subhead.
-}
subHeading : List (Html msg) -> Html msg
subHeading content =
h3
[ headingStyles
{ font = Fonts.baseFont
, color = navy
, size = 20
, lineHeight = 26
, weight = 700
}
]
content
Heading.heading content
|> Heading.withVisualLevel Heading.Subhead
|> Heading.withDocumentLevel Heading.H3
|> Heading.view
{-| This is a small Page Heading.
-}
smallHeading : List (Html msg) -> Html msg
smallHeading content =
h4
[ headingStyles
{ font = Fonts.baseFont
, color = gray20
, size = 16
, lineHeight = 21
, weight = 700
}
, css
[ letterSpacing (px -0.13)
]
]
content
Heading.heading content
|> Heading.withVisualLevel Heading.Small
|> Heading.withDocumentLevel Heading.H4
|> Heading.view
{-| This is some medium body copy.
@ -168,19 +145,6 @@ smallBodyGray content =
content
headingStyles config =
css
[ config.font
, fontSize (px config.size)
, color config.color
, lineHeight (px config.lineHeight)
, fontWeight (int config.weight)
, padding zero
, textAlign left
, margin zero
]
paragraphStyles config =
css
[ config.font

View File

@ -1,7 +1,9 @@
module Examples.Button exposing (Msg, State, example, init, update)
{- \
@docs Msg, State, example, init, update,
{-|
@docs Msg, State, example, init, update
-}
import Css exposing (middle, verticalAlign)
@ -11,20 +13,15 @@ import Html.Styled exposing (..)
import Html.Styled.Attributes exposing (css, id)
import ModuleExample as ModuleExample exposing (Category(..), ModuleExample, ModuleMessages)
import Nri.Ui.AssetPath exposing (Asset)
import Nri.Ui.Button.V8 as Button
import Nri.Ui.Button.V9 as Button
import Nri.Ui.Icon.V5 as Icon
import Nri.Ui.Svg.V1 as NriSvg exposing (Svg)
import Nri.Ui.Text.V3 as Text
{-| -}
type Msg
= SetState State
{-| -}
type State
= State (Control Model)
type State parentMsg
= State (Control (Model parentMsg))
{-| -}
@ -35,96 +32,107 @@ type ButtonType
{-| -}
example :
(String -> ModuleMessages Msg parentMsg)
-> State
(String -> ModuleMessages (Msg parentMsg) parentMsg)
-> State parentMsg
-> ModuleExample parentMsg
example unnamedMessages state =
let
messages =
unnamedMessages "ButtonExample"
in
{ name = "Nri.Ui.Button.V8"
{ name = "Nri.Ui.Button.V9"
, category = Buttons
, content =
[ viewButtonExamples messages state ]
, content = [ viewButtonExamples messages state ]
}
{-| -}
init : { r | performance : String, lock : String } -> State
init assets =
Control.record Model
|> Control.field "label" (Control.string "Button")
|> Control.field "icon"
(Control.maybe False <|
Control.choice
( "Performance"
, Icon.performance assets
|> Icon.decorativeIcon
|> NriSvg.fromHtml
|> Control.value
)
[ ( "Lock"
, Icon.lock assets
|> Icon.decorativeIcon
|> NriSvg.fromHtml
|> Control.value
)
]
)
|> Control.field "width"
(Control.choice
( "Nri.Ui.Button.V7.WidthExact 120", Control.value <| Button.WidthExact 120 )
[ ( "Nri.Ui.Button.V7.WidthExact 70", Control.value <| Button.WidthExact 70 )
, ( "Nri.Ui.Button.V7.WidthUnbounded", Control.value <| Button.WidthUnbounded )
, ( "Nri.Ui.Button.V7.WidthFillContainer", Control.value <| Button.WidthFillContainer )
]
)
|> Control.field "button type"
(Control.choice
( "Nri.Ui.Button.V7.button", Control.value Button )
[ ( "Nri.Ui.Button.V7.link", Control.value Link )
]
)
|> Control.field "state (button only)"
(Control.choice
( Debug.toString Button.Enabled, Control.value Button.Enabled )
(List.map (\x -> ( Debug.toString x, Control.value x ))
[ Button.Disabled
, Button.Error
, Button.Unfulfilled
, Button.Loading
, Button.Success
]
)
)
|> State
type Msg parentMsg
= SetState (State parentMsg)
| NoOp
{-| -}
update : Msg -> State -> ( State, Cmd Msg )
update : Msg msg -> State msg -> ( State msg, Cmd (Msg msg) )
update msg state =
case msg of
SetState newState ->
( newState, Cmd.none )
NoOp ->
( state, Cmd.none )
-- INTERNAL
type alias Model =
type alias Model msg =
{ label : String
, icon : Maybe Svg
, width : Button.ButtonWidth
, buttonType : ButtonType
, state : Button.ButtonState
, width : Button.Attribute msg
, state : Button.Attribute msg
}
{-| -}
init : { r | performance : String, lock : String } -> State msg
init assets =
Control.record Model
|> Control.field "label" (Control.string "Label")
|> Control.field "icon" (iconChoice assets)
|> Control.field "button type"
(Control.choice
( "button", Control.value Button )
[ ( "link", Control.value Link )
]
)
|> Control.field "width"
(Control.choice
( "exactWidth 120", Control.value (Button.exactWidth 120) )
[ ( "exactWidth 70", Control.value (Button.exactWidth 70) )
, ( "unboundedWidth", Control.value Button.unboundedWidth )
, ( "fillContainerWidth", Control.value Button.fillContainerWidth )
]
)
|> Control.field "state (button only)"
(Control.choice
( "enabled", Control.value Button.enabled )
[ ( "disabled", Control.value Button.disabled )
, ( "error", Control.value Button.error )
, ( "unfulfilled", Control.value Button.unfulfilled )
, ( "loading", Control.value Button.loading )
, ( "success", Control.value Button.success )
]
)
|> State
iconChoice : { r | performance : String, lock : String } -> Control.Control (Maybe Svg)
iconChoice assets =
Control.choice
( "Nothing", Control.value Nothing )
[ ( "Just Performance"
, Icon.performance assets
|> Icon.decorativeIcon
|> NriSvg.fromHtml
|> Just
|> Control.value
)
, ( "Just Lock"
, Icon.lock assets
|> Icon.decorativeIcon
|> NriSvg.fromHtml
|> Just
|> Control.value
)
]
viewButtonExamples :
ModuleMessages Msg parentMsg
-> State
ModuleMessages (Msg parentMsg) parentMsg
-> State parentMsg
-> Html parentMsg
viewButtonExamples messages (State control) =
let
@ -139,80 +147,73 @@ viewButtonExamples messages (State control) =
{ label = "Delete Something"
, onClick = messages.showItWorked "delete"
}
, Button.linkExternalWithTracking
(messages.showItWorked "linkExternalWithTracking clicked")
{ size = Button.Medium
, style = Button.Secondary
, width = Button.WidthUnbounded
, label = "linkExternalWithTracking"
, icon = Nothing
, url = "#"
}
, Button.link "linkExternalWithTracking"
[ Button.unboundedWidth
, Button.secondary
, Button.linkExternalWithTracking
{ url = "#"
, track = messages.showItWorked "linkExternalWithTracking clicked"
}
]
]
|> div []
sizes : List Button.ButtonSize
sizes =
[ Button.Small
, Button.Medium
, Button.Large
]
allStyles : List Button.ButtonStyle
allStyles =
[ Button.Primary
, Button.Secondary
, Button.Danger
, Button.Premium
]
buttons :
ModuleMessages Msg parentMsg
-> Model
ModuleMessages (Msg parentMsg) parentMsg
-> Model parentMsg
-> Html parentMsg
buttons messages model =
let
exampleRow style =
sizes =
[ ( Button.small, "small" )
, ( Button.medium, "medium" )
, ( Button.large, "large" )
]
styles =
[ ( Button.primary, "primary" )
, ( Button.secondary, "secondary" )
, ( Button.danger, "danger" )
, ( Button.premium, "premium" )
]
exampleRow ( style, styleName ) =
List.concat
[ [ td
[ css
[ verticalAlign middle
]
]
[ text <| Debug.toString style ]
[ text styleName ]
]
, sizes
|> List.map (exampleCell style)
, List.map (Tuple.first >> exampleCell style) sizes
]
|> tr []
exampleCell style size =
(case model.buttonType of
buttonOrLink =
case model.buttonType of
Link ->
Button.link
{ size = size
, style = style
, label = model.label
, icon = model.icon
, url = ""
, width = model.width
}
Button ->
Button.button
{ size = size
, style = style
, onClick = messages.showItWorked (Debug.toString ( style, size ))
, width = model.width
}
{ label = model.label
, icon = model.icon
, state = model.state
}
)
exampleCell setStyle setSize =
buttonOrLink model.label
[ setSize
, setStyle
, model.width
, model.state
, Button.custom [ Html.Styled.Attributes.class "styleguide-button" ]
, Button.onClick (messages.showItWorked "Button clicked!")
, case model.icon of
Just icon ->
Button.icon icon
Nothing ->
Button.custom []
]
|> List.singleton
|> td
[ css
@ -223,16 +224,15 @@ buttons messages model =
in
List.concat
[ [ sizes
|> List.map (\size -> th [] [ text <| Debug.toString size ])
|> List.map (\( _, sizeName ) -> th [] [ text sizeName ])
|> (\cells -> tr [] (th [] [] :: cells))
]
, allStyles
|> List.map exampleRow
, List.map exampleRow styles
]
|> table []
toggleButtons : ModuleMessages Msg parentMsg -> Html parentMsg
toggleButtons : ModuleMessages (Msg parentMsg) parentMsg -> Html parentMsg
toggleButtons messages =
div []
[ Headings.h3 [ text "Button toggle" ]

View File

@ -11,8 +11,10 @@ import Html.Styled as Html
import Html.Styled.Attributes exposing (css)
import Html.Styled.Events exposing (onClick)
import ModuleExample as ModuleExample exposing (Category(..), ModuleExample)
import Nri.Ui.Button.V8 as Button
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.DisclosureIndicator.V2 as DisclosureIndicator
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Text.V2 as Text
{-| -}
@ -28,61 +30,36 @@ example parentMessage state =
{ name = "Nri.Ui.DisclosureIndicator.V2"
, category = Widgets
, content =
[ viewExample ToggleLarge
state.largeState
(Css.px 17)
(DisclosureIndicator.large [ Css.marginRight (Css.px 10) ] state.largeState)
("DisclosureIndicator.large [ Css.marginRight (Css.px 10) ] True"
++ "\nI'm a 17px caret icon."
)
, viewExample ToggleMedium
state.mediumState
(Css.px 15)
(DisclosureIndicator.medium [ Css.paddingRight (Css.px 8) ] state.mediumState)
("DisclosureIndicator.medium [ Css.paddingRight (Css.px 8) ] True"
++ "\nI'm a 15px caret icon."
)
[ Text.smallBodyGray [ Html.text "The disclosure indicator is only the caret. It is NOT a button -- you must create a button or clickabletext yourself!" ]
, Html.div [ css [ Css.displayFlex, Css.padding (Css.px 8) ] ]
[ toggleButton ToggleLarge "Toggle large indicator"
, toggleButton ToggleMedium "Toggle medium indicator"
]
, Html.div [ css [ Css.displayFlex, Css.alignItems Css.center, Css.marginBottom (Css.px 8) ] ]
[ DisclosureIndicator.large [ Css.marginRight (Css.px 10) ] state.largeState
, Html.text "I'm a 17px caret icon."
]
, Html.div [ css [ Css.displayFlex, Css.alignItems Css.center, Css.marginBottom (Css.px 8) ] ]
[ DisclosureIndicator.medium [ Css.paddingRight (Css.px 8) ] state.mediumState
, Html.text "I'm a 15px caret icon."
]
]
|> List.map (Html.map parentMessage)
}
viewExample : msg -> Bool -> Css.Px -> Html.Html msg -> String -> Html.Html msg
viewExample toggle isOpen fontSize disclosureView disclosureCode =
Html.div [ css [ Css.margin2 (Css.px 16) Css.zero ] ]
[ Html.div []
[ Html.button
[ css
[ Css.displayFlex
, Css.alignItems Css.center
, Css.borderStyle Css.none
, Css.outline Css.none
, Fonts.baseFont
, Css.fontSize fontSize
]
, onClick toggle
]
[ disclosureView
, Html.text "Toggle me!"
]
]
, if isOpen then
code disclosureCode
else
Html.text ""
]
code : String -> Html.Html msg
code copy =
Html.code
[ css
[ Css.fontSize (Css.px 12)
, Css.whiteSpace Css.pre
]
]
[ Html.text copy ]
toggleButton : msg -> String -> Html.Html msg
toggleButton msg label =
Button.button
{ onClick = msg
, size = Button.Small
, style = Button.Secondary
, width = Button.WidthUnbounded
}
{ label = label
, state = Button.Enabled
, icon = Nothing
}
{-| -}

View File

@ -8,13 +8,14 @@ module Examples.Text exposing (example)
import Html.Styled as Html
import ModuleExample as ModuleExample exposing (Category(..), ModuleExample)
import Nri.Ui.Heading.V1 as Heading exposing (DocumentLevel(..), VisualLevel(..), heading, withDocumentLevel, withVisualLevel)
import Nri.Ui.Text.V3 as Text
{-| -}
example : ModuleExample msg
example =
{ name = "Nri.Ui.Text.V3"
{ name = "Nri.Ui.Text.V3 (with headers from Nri.Ui.Heading.V1)"
, category = Text
, content =
let
@ -26,10 +27,22 @@ example =
"""
in
[ Html.text "NOTE: When using these styles, please read the documentation in the Elm module about \"Understanding spacing\""
, Text.heading [ Html.text "This is the main page heading." ]
, Text.tagline [ Html.text "This is a tagline" ]
, Text.subHeading [ Html.text "This is a subHeading" ]
, Text.smallHeading [ Html.text "This is a smallHeading" ]
, heading [ Html.text "This is the main page heading." ]
|> withVisualLevel Top
|> withDocumentLevel H1
|> Heading.view
, heading [ Html.text "This is a tagline" ]
|> withVisualLevel Tagline
|> withDocumentLevel H2
|> Heading.view
, heading [ Html.text "This is a subHeading" ]
|> withVisualLevel Subhead
|> withDocumentLevel H3
|> Heading.view
, heading [ Html.text "This is a smallHeading" ]
|> withVisualLevel Small
|> withDocumentLevel H4
|> Heading.view
, Html.hr [] []
, Text.heading [ Html.text "Paragraph styles" ]
, Text.mediumBody [ Html.text <| "This is a mediumBody. " ++ longerBody ]

View File

@ -31,7 +31,7 @@ import Url exposing (Url)
type alias ModuleStates =
{ buttonExampleState : Examples.Button.State
{ buttonExampleState : Examples.Button.State Msg
, bannerAlertExampleState : Examples.BannerAlert.State
, clickableTextExampleState : Examples.ClickableText.State
, checkboxExampleState : Examples.Checkbox.State
@ -72,7 +72,7 @@ init =
type Msg
= ButtonExampleMsg Examples.Button.Msg
= ButtonExampleMsg (Examples.Button.Msg Msg)
| BannerAlertExampleMsg Examples.BannerAlert.Msg
| ClickableTextExampleMsg Examples.ClickableText.Msg
| CheckboxExampleMsg Examples.Checkbox.Msg