remove unused modules

This commit is contained in:
Brian Hicks 2020-03-31 14:52:13 -05:00
parent afa37f1f20
commit b309421d3d
30 changed files with 1 additions and 9302 deletions

View File

@ -7,30 +7,17 @@
"exposed-modules": [
"Nri.Ui",
"Nri.Ui.Accordion.V1",
"Nri.Ui.Alert.V2",
"Nri.Ui.Alert.V3",
"Nri.Ui.Alert.V4",
"Nri.Ui.AssetPath",
"Nri.Ui.AssignmentIcon.V1",
"Nri.Ui.BannerAlert.V2",
"Nri.Ui.BannerAlert.V3",
"Nri.Ui.BannerAlert.V4",
"Nri.Ui.BannerAlert.V5",
"Nri.Ui.BannerAlert.V6",
"Nri.Ui.Button.V3",
"Nri.Ui.Button.V4",
"Nri.Ui.Button.V5",
"Nri.Ui.Button.V6",
"Nri.Ui.Button.V7",
"Nri.Ui.Button.V8",
"Nri.Ui.Button.V9",
"Nri.Ui.Button.V10",
"Nri.Ui.Callout.V1",
"Nri.Ui.Checkbox.V3",
"Nri.Ui.Checkbox.V4",
"Nri.Ui.Checkbox.V5",
"Nri.Ui.ClickableText.V1",
"Nri.Ui.ClickableText.V2",
"Nri.Ui.ClickableText.V3",
"Nri.Ui.ClickableSvg.V1",
"Nri.Ui.Colors.Extra",
@ -42,7 +29,6 @@
"Nri.Ui.Dropdown.V2",
"Nri.Ui.Effects.V1",
"Nri.Ui.Fonts.V1",
"Nri.Ui.Heading.V1",
"Nri.Ui.Heading.V2",
"Nri.Ui.Html.Attributes.V2",
"Nri.Ui.Html.V3",
@ -52,26 +38,14 @@
"Nri.Ui.InputStyles.V2",
"Nri.Ui.Logo.V1",
"Nri.Ui.MasteryIcon.V1",
"Nri.Ui.Modal.V2",
"Nri.Ui.Modal.V3",
"Nri.Ui.Modal.V4",
"Nri.Ui.Modal.V5",
"Nri.Ui.Modal.V6",
"Nri.Ui.Modal.V7",
"Nri.Ui.Modal.V8",
"Nri.Ui.Outline.V2",
"Nri.Ui.Page.V2",
"Nri.Ui.Page.V3",
"Nri.Ui.Palette.V1",
"Nri.Ui.Pennant.V1",
"Nri.Ui.Pennant.V2",
"Nri.Ui.PremiumCheckbox.V1",
"Nri.Ui.PremiumCheckbox.V2",
"Nri.Ui.PremiumCheckbox.V3",
"Nri.Ui.PremiumCheckbox.V4",
"Nri.Ui.PremiumCheckbox.V5",
"Nri.Ui.PremiumCheckbox.V6",
"Nri.Ui.SegmentedControl.V6",
"Nri.Ui.SegmentedControl.V7",
"Nri.Ui.SegmentedControl.V8",
"Nri.Ui.Select.V5",
@ -85,13 +59,10 @@
"Nri.Ui.Svg.V1",
"Nri.Ui.Table.V4",
"Nri.Ui.Table.V5",
"Nri.Ui.Tabs.V3",
"Nri.Ui.Tabs.V4",
"Nri.Ui.Text.V2",
"Nri.Ui.Text.V3",
"Nri.Ui.Text.V4",
"Nri.Ui.Text.Writing.V1",
"Nri.Ui.TextArea.V3",
"Nri.Ui.TextArea.V4",
"Nri.Ui.TextInput.V3",
"Nri.Ui.TextInput.V4",
@ -119,4 +90,4 @@
"avh4/elm-program-test": "3.1.0 <= v < 4.0.0",
"elm-explorations/test": "1.2.0 <= v < 2.0.0"
}
}
}

View File

@ -1,121 +0,0 @@
module Nri.Ui.Alert.V2 exposing
( error
, success
, tip
, warning
)
{-| UI components that highlight information to the user.
@docs error
@docs success
@docs tip
@docs warning
-}
import Accessibility.Styled as Html exposing (Html)
import Css
import Css.Global
import Html.Styled exposing (fromUnstyled)
import Html.Styled.Attributes exposing (css)
import Markdown
import Nri.Ui
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Icon.V3 as Icon
{-| -}
error : { r | exclamation : String } -> String -> Html msg
error assets content =
alert
[ iconContainer [ Css.color Colors.purple ]
(Icon.decorativeIcon (Icon.exclamation assets))
, viewAlertContent Colors.purpleDark content
]
{-| -}
success : { r | checkmark : String } -> String -> Html msg
success assets content =
alert
[ iconContainer
[ Css.color Colors.white
, Css.backgroundColor Colors.green
, Css.Global.children [ Css.Global.svg [ Css.maxWidth (Css.px 12) ] ]
]
(Icon.decorativeIcon (Icon.checkMarkSvg assets))
, viewAlertContent Colors.greenDarkest content
]
{-| -}
tip : { r | bulb : String } -> String -> Html msg
tip assets content =
alert
[ iconContainer [ Css.color Colors.yellow ]
(Icon.decorativeIcon (Icon.bulb assets))
, viewAlertContent Colors.navy content
]
{-| -}
warning : { r | exclamation : String } -> String -> Html msg
warning assets content =
alert
[ iconContainer [ Css.color Colors.red ]
(Icon.decorativeIcon (Icon.exclamation assets))
, viewAlertContent Colors.red content
]
alert : List (Html msg) -> Html msg
alert =
Nri.Ui.styled Html.div
"Nri-Ui-Alert-V2__alert"
[ Css.displayFlex
, Css.justifyContent Css.start
, Css.alignItems Css.center
, Css.paddingTop (Css.px 6)
, Css.paddingBottom (Css.px 8)
]
[]
iconContainer : List Css.Style -> Html msg -> Html msg
iconContainer styles icon =
Nri.Ui.styled Html.div
"Nri-Ui-Alert-V2__iconContainer"
(styles
++ [ -- Content positioning
Css.displayFlex
, Css.justifyContent Css.center
, Css.alignItems Css.center
, Css.marginRight (Css.px 5)
-- Size
, Css.borderRadius (Css.px 13)
, Css.maxHeight (Css.px 20)
, Css.maxWidth (Css.px 20)
, Css.minHeight (Css.px 20)
, Css.minWidth (Css.px 20)
]
)
[]
[ icon ]
viewAlertContent : Css.ColorValue compatible -> String -> Html.Styled.Html msg
viewAlertContent color content =
Nri.Ui.styled Html.div
"Nri-Ui-Alert-V2__viewAlertContent"
[ Css.color color
, Fonts.baseFont
, Css.fontSize (Css.px 13)
, Css.lineHeight (Css.num 1.2)
, Css.listStyleType Css.none
, Css.Global.descendants [ Css.Global.p [ Css.margin Css.zero ] ]
]
[]
(Markdown.toHtml Nothing content |> List.map fromUnstyled)

View File

@ -1,156 +0,0 @@
module Nri.Ui.Alert.V3 exposing
( error
, success
, tip
, warning
)
{-| UI components that highlight information to the user.
@docs error
@docs success
@docs tip
@docs warning
-}
import Accessibility.Styled as Html exposing (Html)
import Css
import Css.Global
import Html.Styled exposing (fromUnstyled)
import Html.Styled.Attributes exposing (css)
import Markdown
import Nri.Ui
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Icon.V3 as Icon
import Nri.Ui.SpriteSheet exposing (bulb, checkmark, exclamationMark)
import Nri.Ui.Svg.V1 as NriSvg exposing (Svg)
{-| -}
error : String -> Html msg
error content =
alert
[ exclamation Colors.purple
, viewAlertContent Colors.purpleDark content
]
{-| -}
success : String -> Html msg
success content =
alert
[ iconContainer
[ Css.color Colors.white
, Css.backgroundColor Colors.green
]
(Html.div
[ css
[ Css.width (Css.px 12)
, Css.height (Css.px 12)
, Css.margin Css.auto
]
]
[ NriSvg.toHtml checkmark ]
)
, viewAlertContent Colors.greenDarkest content
]
{-| -}
tip : String -> Html msg
tip content =
alert
[ iconContainer [ Css.color Colors.yellow ] (NriSvg.toHtml bulb)
, viewAlertContent Colors.navy content
]
{-| -}
warning : String -> Html msg
warning content =
alert
[ exclamation Colors.red
, viewAlertContent Colors.red content
]
alert : List (Html msg) -> Html msg
alert =
Nri.Ui.styled Html.div
"Nri-Ui-Alert-V3__alert"
[ Css.displayFlex
, Css.justifyContent Css.start
, Css.alignItems Css.center
, Css.paddingTop (Css.px 6)
, Css.paddingBottom (Css.px 8)
]
[]
exclamation : Css.Color -> Html msg
exclamation backgroundColor =
iconContainer
[ Css.color Colors.white
, Css.backgroundColor backgroundColor
]
(Html.div
[ css [ Css.marginTop (Css.px 1), Css.height (Css.px 13) ] ]
[ NriSvg.toHtml exclamationMark ]
)
iconContainer : List Css.Style -> Html msg -> Html msg
iconContainer styles icon =
Nri.Ui.styled Html.div
"Nri-Ui-Alert-V3__iconContainer"
(styles
++ [ -- Content positioning
Css.marginRight (Css.px 5)
-- Size
, Css.borderRadius (Css.px 13)
, Css.lineHeight iconContainerSize
, Css.maxHeight iconContainerSize
, Css.maxWidth iconContainerSize
, Css.minHeight iconContainerSize
, Css.minWidth iconContainerSize
]
)
[]
[ icon ]
viewAlertContent : Css.ColorValue compatible -> String -> Html.Styled.Html msg
viewAlertContent color content =
Nri.Ui.styled Html.div
"Nri-Ui-Alert-V3__viewAlertContent"
[ Css.color color
, Fonts.baseFont
, Css.fontSize (Css.px 13)
, Css.lineHeight iconContainerSize
, Css.listStyleType Css.none
-- This global selector and overrides are necessary due to
-- old stylesheets used on the monolith that set the
-- `.txt p { font-size: 18px; }` -- without these overrides,
-- we may see giant ugly alerts.
-- Remove these if you want to! but be emotionally prepped
-- to deal with visual regressions. 🙏
, Css.Global.descendants
[ Css.Global.p
[ Css.margin Css.zero
, Css.lineHeight iconContainerSize
, Css.fontSize (Css.px 13)
, Fonts.baseFont
]
]
]
[]
(Markdown.toHtml Nothing content |> List.map fromUnstyled)
iconContainerSize : Css.Px
iconContainerSize =
Css.px 20

View File

@ -1,114 +0,0 @@
module Nri.Ui.BannerAlert.V2 exposing
( error
, neutral
, success
)
{-|
@docs error
@docs neutral
@docs success
-}
import Accessibility.Styled as Accessibility
import Css exposing (..)
import Css.Global exposing (Snippet, children, descendants, everything, selector)
import Html.Styled as Html exposing (Html)
import Nri.Ui.Colors.V1
import Nri.Ui.Fonts.V1
{-| A banner to show error alerts
-}
error : String -> Html msg
error =
banner errorStyles
{-| A banner to show neutral alerts
-}
neutral : String -> Html msg
neutral =
banner neutralStyles
{-| A banner for success alerts
-}
success : String -> Html msg
success =
banner successStyles
banner : Css.Style -> String -> Html msg
banner bannerType alertMessage =
Html.styled Accessibility.div
[ bannerStyles, bannerType ]
[]
[ notification alertMessage ]
notification : String -> Html msg
notification message =
Html.styled Html.div [ alertMessageStyles ] [] [ Accessibility.text message ]
type CssClasses
= AlertMessage
| Banner
| Error
| Neutral
| Success
alertMessageStyles : Style
alertMessageStyles =
batch
[ Css.fontSize (Css.px 20)
, Css.fontWeight (Css.int 700)
, Css.lineHeight (Css.px 25)
, Css.maxWidth (Css.px 600)
, Nri.Ui.Fonts.V1.baseFont
]
bannerStyles : Style
bannerStyles =
batch
[ Css.alignItems Css.center
, Css.displayFlex
, Css.justifyContent Css.center
, Css.padding (Css.px 20)
, Css.width (Css.pct 100)
, Css.Global.children
[ Css.Global.button
[ Css.position Css.absolute
, Css.right (Css.px 15)
]
]
]
errorStyles : Style
errorStyles =
batch
[ Css.backgroundColor Nri.Ui.Colors.V1.purpleLight
, Css.color Nri.Ui.Colors.V1.purpleDark
]
neutralStyles : Style
neutralStyles =
batch
[ Css.backgroundColor Nri.Ui.Colors.V1.frost
, Css.color Nri.Ui.Colors.V1.navy
]
successStyles : Style
successStyles =
batch
[ Css.backgroundColor Nri.Ui.Colors.V1.greenLightest
, Css.color Nri.Ui.Colors.V1.greenDarkest
]

View File

@ -1,138 +0,0 @@
module Nri.Ui.BannerAlert.V3 exposing (error, neutral, success)
{-|
@docs error, neutral, success
-}
import Accessibility.Styled as Html exposing (Html)
import Css
import Css.Global
import Html.Styled.Attributes as Attributes exposing (css)
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1
import Nri.Ui.SpriteSheet exposing (bulb, checkmark, exclamationMark)
import Nri.Ui.Svg.V1 as NriSvg exposing (Svg)
{-| A banner to show error alerts
-}
error : String -> Html msg
error =
banner
{ backgroundColor = Colors.purpleLight
, color = Colors.purpleDark
, icon =
{ backgroundColor = Colors.purple
, height = Css.px 25
, asset = exclamationMark
}
}
{-| A banner to show neutral alerts
-}
neutral : String -> Html msg
neutral =
banner
{ backgroundColor = Colors.frost
, color = Colors.navy
, icon =
{ backgroundColor = Colors.navy
, height = Css.px 32
, asset = bulb
}
}
{-| A banner for success alerts
-}
success : String -> Html msg
success =
banner
{ backgroundColor = Colors.greenLightest
, color = Colors.greenDarkest
, icon =
{ backgroundColor = Colors.green
, height = Css.px 20
, asset = checkmark
}
}
type alias Config =
{ color : Css.Color
, backgroundColor : Css.Color
, icon : IconConfig
}
banner : Config -> String -> Html msg
banner config alertMessage =
Html.div
[ css
[ Css.alignItems Css.center
, Css.displayFlex
, Css.justifyContent Css.center
, Css.padding (Css.px 20)
, Css.width (Css.pct 100)
, Css.Global.children
[ Css.Global.button
[ Css.position Css.absolute
, Css.right (Css.px 15)
]
]
, Css.backgroundColor config.backgroundColor
, Css.color config.color
]
]
[ icon config.icon
, notification alertMessage
]
type alias IconConfig =
{ backgroundColor : Css.Color
, height : Css.Px
, asset : Svg
}
icon : IconConfig -> Html msg
icon config =
Html.div
[ css
[ Css.boxSizing Css.borderBox
, Css.borderRadius (Css.pct 50)
, Css.color Colors.white
, Css.displayFlex
, Css.alignItems Css.center
, Css.justifyContent Css.center
, Css.width (Css.px 50)
, Css.height (Css.px 50)
, Css.marginRight (Css.px 20)
, Css.padding (Css.px 8)
, Css.flexShrink (Css.num 0)
, Css.backgroundColor config.backgroundColor
]
]
[ Html.div
[ css [ Css.height config.height ]
]
[ NriSvg.toHtml config.asset ]
]
notification : String -> Html msg
notification message =
Html.div
[ css
[ Css.fontSize (Css.px 20)
, Css.fontWeight (Css.int 700)
, Css.lineHeight (Css.px 25)
, Css.maxWidth (Css.px 600)
, Nri.Ui.Fonts.V1.baseFont
]
]
[ Html.text message ]

View File

@ -1,199 +0,0 @@
module Nri.Ui.BannerAlert.V4 exposing (alert, error, neutral, success)
{-|
@docs alert, error, neutral, success
-}
import Accessibility.Styled as Html exposing (Html)
import Accessibility.Styled.Widget as Widget
import Css
import Css.Global
import Html.Styled.Attributes as Attributes exposing (css)
import Html.Styled.Events
import Nri.Ui
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1
import Nri.Ui.SpriteSheet exposing (bulb, checkmark, exclamationMark, xSvg)
import Nri.Ui.Svg.V1 as NriSvg exposing (Svg)
{-| A banner to show error alerts
-}
alert : String -> Maybe msg -> Html msg
alert =
banner
{ backgroundColor = Colors.sunshine
, color = Colors.navy
, icon =
{ backgroundColor = Colors.ochre
, height = Css.px 25
, asset = exclamationMark
}
}
{-| A banner to show error alerts
-}
error : String -> Maybe msg -> Html msg
error =
banner
{ backgroundColor = Colors.purpleLight
, color = Colors.purpleDark
, icon =
{ backgroundColor = Colors.purple
, height = Css.px 25
, asset = exclamationMark
}
}
{-| A banner to show neutral alerts
-}
neutral : String -> Maybe msg -> Html msg
neutral =
banner
{ backgroundColor = Colors.frost
, color = Colors.navy
, icon =
{ backgroundColor = Colors.navy
, height = Css.px 32
, asset = bulb
}
}
{-| A banner for success alerts
-}
success : String -> Maybe msg -> Html msg
success =
banner
{ backgroundColor = Colors.greenLightest
, color = Colors.greenDarkest
, icon =
{ backgroundColor = Colors.green
, height = Css.px 20
, asset = checkmark
}
}
type alias Config =
{ color : Css.Color
, backgroundColor : Css.Color
, icon : IconConfig
}
banner : Config -> String -> Maybe msg -> Html msg
banner config alertMessage dismissMsg =
let
maybeDismissButton =
case dismissMsg of
Nothing ->
Html.text ""
Just msg ->
dismissButton msg
in
Html.div
[ css
[ Css.displayFlex
, Css.justifyContent Css.center
, Css.alignItems Css.center
, Css.backgroundColor config.backgroundColor
, Css.color config.color
]
]
[ Html.span
[ css
[ Css.alignItems Css.center
, Css.displayFlex
, Css.justifyContent Css.center
, Css.padding (Css.px 20)
, Css.width (Css.pct 100)
, Css.Global.children
[ Css.Global.button
[ Css.position Css.relative
, Css.right (Css.px 15)
]
]
]
]
[ icon config.icon
, notification alertMessage
]
, maybeDismissButton
]
dismissButton : msg -> Html msg
dismissButton msg =
Nri.Ui.styled Html.div
"dismiss-button-container"
[ Css.padding (Css.px 25)
]
[]
[ Html.button
[ Html.Styled.Events.onClick msg
, Widget.label "Dismiss banner"
, css
[ Css.borderWidth Css.zero
, Css.backgroundColor Css.unset
, Css.color Colors.azure
, Css.width (Css.px 30)
, Css.height (Css.px 30)
, Css.padding2 Css.zero (Css.px 7)
, Css.cursor Css.pointer
]
]
[ NriSvg.toHtml xSvg
]
]
type alias IconConfig =
{ backgroundColor : Css.Color
, height : Css.Px
, asset : Svg
}
icon : IconConfig -> Html msg
icon config =
Html.div
[ css
[ Css.boxSizing Css.borderBox
, Css.borderRadius (Css.pct 50)
, Css.color Colors.white
, Css.displayFlex
, Css.alignItems Css.center
, Css.justifyContent Css.center
, Css.width (Css.px 50)
, Css.height (Css.px 50)
, Css.marginRight (Css.px 20)
, Css.padding (Css.px 8)
, Css.flexShrink (Css.num 0)
, Css.backgroundColor config.backgroundColor
]
]
[ Html.div
[ css [ Css.height config.height ]
]
[ NriSvg.toHtml config.asset ]
]
notification : String -> Html msg
notification message =
Nri.Ui.styled Html.div
"banner-alert-notification"
[ Css.fontSize (Css.px 20)
, Css.fontWeight (Css.int 700)
, Css.lineHeight (Css.px 27)
, Css.maxWidth (Css.px 600)
, Nri.Ui.Fonts.V1.baseFont
]
[]
[ Html.text message ]

View File

@ -1,258 +0,0 @@
module Nri.Ui.BannerAlert.V5 exposing (alert, error, neutral, success, LinkConfig, BannerContent(..), Target(..))
{-|
@docs alert, error, neutral, success, LinkConfig, BannerContent, Target
-}
import Accessibility.Styled as Html exposing (Html)
import Accessibility.Styled.Widget as Widget
import Css
import Css.Global
import Html.Styled.Attributes as Attributes exposing (css)
import Html.Styled.Events
import Nri.Ui
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1
import Nri.Ui.SpriteSheet exposing (bulb, checkmark, exclamationMark, xSvg)
import Nri.Ui.Svg.V1 as NriSvg exposing (Svg)
{-| A type to capture either plain content, or a string which will include a url link.
-}
type BannerContent
= Plain String
| WithLink LinkConfig
{-| A banner to show error alerts
-}
alert : BannerContent -> Maybe msg -> Html msg
alert =
banner
{ backgroundColor = Colors.sunshine
, color = Colors.navy
, icon =
{ backgroundColor = Colors.ochre
, height = Css.px 25
, asset = exclamationMark
}
}
{-| A banner to show error alerts
-}
error : BannerContent -> Maybe msg -> Html msg
error =
banner
{ backgroundColor = Colors.purpleLight
, color = Colors.purpleDark
, icon =
{ backgroundColor = Colors.purple
, height = Css.px 25
, asset = exclamationMark
}
}
{-| A banner to show neutral alerts
-}
neutral : BannerContent -> Maybe msg -> Html msg
neutral =
banner
{ backgroundColor = Colors.frost
, color = Colors.navy
, icon =
{ backgroundColor = Colors.navy
, height = Css.px 32
, asset = bulb
}
}
{-| A banner for success alerts
-}
success : BannerContent -> Maybe msg -> Html msg
success =
banner
{ backgroundColor = Colors.greenLightest
, color = Colors.greenDarkest
, icon =
{ backgroundColor = Colors.green
, height = Css.px 20
, asset = checkmark
}
}
type alias StyleConfig =
{ color : Css.Color
, backgroundColor : Css.Color
, icon : IconConfig
}
banner : StyleConfig -> BannerContent -> Maybe msg -> Html msg
banner config bannerContent dismissMsg =
let
maybeDismissButton =
case dismissMsg of
Nothing ->
Html.text ""
Just msg ->
dismissButton msg
alertMessage =
case bannerContent of
Plain string ->
Html.text string
WithLink linkConfig ->
Html.div
[ css
[ Css.fontSize (Css.px 20)
, Css.fontWeight (Css.int 700)
, Css.lineHeight (Css.px 25)
, Css.maxWidth (Css.px 600)
, Nri.Ui.Fonts.V1.baseFont
]
]
[ Html.text linkConfig.prefixText
, Html.a
[ Attributes.href <| linkConfig.linkUrl
, targetToAttribute linkConfig.target
]
[ Html.text linkConfig.linkText ]
, Html.text linkConfig.postfixText
]
in
Html.div
[ css
[ Css.displayFlex
, Css.justifyContent Css.center
, Css.alignItems Css.center
, Css.backgroundColor config.backgroundColor
, Css.color config.color
]
]
[ Html.span
[ css
[ Css.alignItems Css.center
, Css.displayFlex
, Css.justifyContent Css.center
, Css.padding (Css.px 20)
, Css.width (Css.pct 100)
, Css.Global.children
[ Css.Global.button
[ Css.position Css.relative
, Css.right (Css.px 15)
]
]
]
]
[ icon config.icon
, notification alertMessage
]
, maybeDismissButton
]
dismissButton : msg -> Html msg
dismissButton msg =
Nri.Ui.styled Html.div
"dismiss-button-container"
[ Css.padding (Css.px 25)
]
[]
[ Html.button
[ Html.Styled.Events.onClick msg
, Widget.label "Dismiss banner"
, css
[ Css.borderWidth Css.zero
, Css.backgroundColor Css.unset
, Css.color Colors.azure
, Css.width (Css.px 30)
, Css.height (Css.px 30)
, Css.padding2 Css.zero (Css.px 7)
, Css.cursor Css.pointer
]
]
[ NriSvg.toHtml xSvg
]
]
type alias IconConfig =
{ backgroundColor : Css.Color
, height : Css.Px
, asset : Svg
}
icon : IconConfig -> Html msg
icon config =
Html.div
[ css
[ Css.boxSizing Css.borderBox
, Css.borderRadius (Css.pct 50)
, Css.color Colors.white
, Css.displayFlex
, Css.alignItems Css.center
, Css.justifyContent Css.center
, Css.width (Css.px 50)
, Css.height (Css.px 50)
, Css.marginRight (Css.px 20)
, Css.padding (Css.px 8)
, Css.flexShrink (Css.num 0)
, Css.backgroundColor config.backgroundColor
]
]
[ Html.div
[ css [ Css.height config.height ]
]
[ NriSvg.toHtml config.asset ]
]
notification : Html msg -> Html msg
notification message =
Nri.Ui.styled Html.div
"banner-alert-notification"
[ Css.fontSize (Css.px 20)
, Css.fontWeight (Css.int 700)
, Css.lineHeight (Css.px 27)
, Css.maxWidth (Css.px 600)
, Nri.Ui.Fonts.V1.baseFont
]
[]
[ message ]
{-| Config describing a message containing an embedded link.
-}
type alias LinkConfig =
{ prefixText : String
, linkText : String
, linkUrl : String
, postfixText : String
, target : Target
}
{-| The link target. Corresponds to values of "_blank" and "_self"
-}
type Target
= Blank
| Self
targetToAttribute : Target -> Html.Attribute msg
targetToAttribute linkTarget =
case linkTarget of
Blank ->
Attributes.target "_blank"
Self ->
Attributes.target "_self"

View File

@ -1,780 +0,0 @@
module Nri.Ui.Button.V3 exposing
( ButtonSize(..), ButtonStyle(..), ButtonState(..), ButtonContent
, ButtonConfig, button, customButton, delete, copyToClipboard, ToggleButtonConfig, toggleButton
, LinkConfig, link, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
)
{-|
# Changes from V2:
- Uses Html.Styled
- Removes buttonDeprecated
- Removes Tiny size
- Removes one-off Active hack
- Removes "submit" button - we just used that for forms that were partially in Elm
# About:
Common NoRedInk buttons. For accessibility purposes, buttons that perform an
action on the current page should be HTML `<button>` elements and are created here
with `*Button` functions. Buttons that take the user to a new page should be
HTML `<a>` elements and are created here with `*Link` functions. Both versions
should be able to use the same CSS class in all cases.
There will generally be a `*Button` and `*Link` version of each button style.
(These will be created as they are needed.)
## Common configs
@docs ButtonSize, ButtonStyle, ButtonState, ButtonContent
## `<button>` Buttons
@docs ButtonConfig, button, customButton, delete, copyToClipboard, ToggleButtonConfig, toggleButton
## `<a>` Buttons
@docs LinkConfig, link, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
-}
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.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.Icon.V3 as Icon exposing (IconType)
{-| Sizes for buttons and links that have button classes
-}
type ButtonSize
= Small
| Medium
| Large
{-| Styleguide-approved styles for your buttons!
Note on borderless buttons:
A borderless button that performs an action on the current page
This button is intended to look like a link.
Only use a borderless button when the clickable text in question follows the same layout/margin/padding as a bordered button
-}
type ButtonStyle
= Primary
| Secondary
| Borderless
| Danger
| Premium
{-| Describes the state of a button. Has consequences for appearance and disabled attribute.
- Enabled: An enabled button. Takes the appearance of ButtonStyle
- Unfulfilled: A button which appears with the InactiveColors palette but is not disabled.
- Disabled: A button which appears with the InactiveColors palette and is disabled.
- Error: A button which appears with the ErrorColors palette and is disabled.
- Loading: A button which appears with the LoadingColors palette and is disabled
- Success: A button which appears with the SuccessColors palette and is disabled
-}
type ButtonState
= Enabled
| Unfulfilled
| Disabled
| Error
| Loading
| Success
{-| The part of a button that remains constant through different button states
-}
type alias ButtonConfig msg =
{ onClick : msg
, size : ButtonSize
, style : ButtonStyle
, width : Maybe Int
}
{-| ButtonContent, often changes based on ButtonState. For example, a button in the "Success"
state may have a different label than a button in the "Error" state
-}
type alias ButtonContent =
{ label : String
, state : ButtonState
, icon : Maybe IconType
}
{-| A delightful button which can trigger an effect when clicked!
This button will trigger the passed-in message if the button state is:
- Enabled
- Unfulfilled
This button will be Disabled if the button state is:
- Disabled
- Error
- Loading
- Success
-}
button : ButtonConfig msg -> ButtonContent -> Html msg
button config content =
customButton [] config content
{-| Exactly the same as button but you can pass in a list of attributes
-}
customButton : List (Attribute msg) -> ButtonConfig msg -> ButtonContent -> Html msg
customButton attributes config content =
let
buttonStyle_ =
case content.state of
Enabled ->
styleToColorPalette config.style
Disabled ->
InactiveColors
Error ->
ErrorColors
Unfulfilled ->
InactiveColors
Loading ->
LoadingColors
Success ->
SuccessColors
disabled =
case content.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_ Button)
([ Events.onClick config.onClick
, Attributes.disabled disabled
, Attributes.type_ "button"
]
++ attributes
)
(viewLabel content.icon content.label)
-- COPY TO CLIPBOARD BUTTON
{-| Config for copyToClipboard
-}
type alias CopyToClipboardConfig =
{ size : ButtonSize
, style : ButtonStyle
, copyText : String
, buttonLabel : String
, withIcon : Bool
, width : Maybe Int
}
{-| See ui/src/Page/Teach/Courses/Assignments/index.coffee
You will need to hook this up to clipboard.js
-}
copyToClipboard : { r | teach_assignments_copyWhite_svg : Asset } -> CopyToClipboardConfig -> Html msg
copyToClipboard assets config =
let
maybeIcon =
if config.withIcon then
Just (Icon.copy assets)
else
Nothing
in
Nri.Ui.styled Html.button
(styledName "copyToClipboard")
(buttonStyles config.size config.width (styleToColorPalette config.style) Button)
[ Widget.label "Copy URL to clipboard"
, Attributes.attribute "data-clipboard-text" config.copyText
]
(viewLabel maybeIcon config.buttonLabel)
-- DELETE BUTTON
type alias DeleteButtonConfig msg =
{ label : String
, onClick : msg
}
{-| A delete button (blue X)
-}
delete : { r | x : String } -> DeleteButtonConfig msg -> Html msg
delete assets 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
]
[ Icon.icon { alt = "Delete", icon = Icon.xSvg assets } ]
-- TOGGLE BUTTON
{-| Buttons can be toggled into a pressed state and back again.
-}
type alias ToggleButtonConfig msg =
{ label : String
, onSelect : msg
, onDeselect : msg
, pressed : Bool
}
{-| -}
toggleButton : ToggleButtonConfig msg -> Html msg
toggleButton config =
let
toggledStyles =
if config.pressed then
[ 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
[]
in
Nri.Ui.styled Html.button
(styledName "toggleButton")
(buttonStyles Medium Nothing SecondaryColors Button
++ 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)
{-| Inputs can be a clickable thing used in a form
-}
type alias InputConfig =
{ content : Html Never
, name : String
, size : ButtonSize
, style : ButtonStyle
, value : String
}
-- LINKS THAT LOOK LIKE BUTTONS
{-| Links are clickable things with a url.
NOTE: Links do not support two-line labels.
-}
type alias LinkConfig =
{ label : String
, icon : Maybe IconType
, url : String
, size : ButtonSize
, style : ButtonStyle
, width : Maybe Int
}
{-| Wrap some text so it looks like a button, but actually is wrapped in an anchor to
some url
-}
link : LinkConfig -> Html msg
link =
linkBase "link" [ Attributes.target "_self" ]
{-| 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 -> String)
-> (route -> msg)
->
{ label : String
, icon : Maybe IconType
, size : ButtonSize
, style : ButtonStyle
, width : Maybe Int
, route : route
}
-> Html msg
linkSpa toUrl toMsg config =
linkBase
"linkSpa"
[ EventExtras.onClickPreventDefaultForLinkWithHref (toMsg config.route)
]
{ label = config.label
, icon = config.icon
, size = config.size
, style = config.style
, width = config.width
, url = toUrl config.route
}
{-| 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 : LinkConfig -> Html msg
linkExternal =
linkBase "linkExternal" [ Attributes.target "_blank" ]
{-| 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 : String -> LinkConfig -> Html msg
linkWithMethod method =
linkBase "linkWithMethod" [ Attributes.attribute "data-method" method ]
{-| 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 : msg -> LinkConfig -> Html msg
linkWithTracking onTrack =
linkBase
"linkWithTracking"
[ Events.preventDefaultOn "click"
(Json.Decode.succeed ( onTrack, True ))
]
{-| 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
This should only take in messages that result in tracking events. For buttons that trigger other effects on the page, please use Nri.Ui.Button.V2.button instead
-}
linkExternalWithTracking : msg -> LinkConfig -> Html msg
linkExternalWithTracking onTrack =
linkBase
"linkExternalWithTracking"
[ Attributes.target "_blank"
, EventExtras.onClickForLinkWithHref onTrack
]
{-| Helper function for building links with an arbitrary number of Attributes
-}
linkBase : String -> List (Attribute msg) -> LinkConfig -> Html msg
linkBase linkFunctionName extraAttrs config =
Nri.Ui.styled Styled.a
(styledName linkFunctionName)
(Css.whiteSpace Css.noWrap
:: buttonStyles config.size config.width (styleToColorPalette config.style) Anchor
)
(Attributes.href config.url
:: extraAttrs
)
(viewLabel config.icon config.label)
-- HELPERS
type ColorPalette
= PrimaryColors
| SecondaryColors
| BorderlessColors
| DangerColors
| PremiumColors
| InactiveColors
| LoadingColors
| SuccessColors
| ErrorColors
styleToColorPalette : ButtonStyle -> ColorPalette
styleToColorPalette style =
case style of
Primary ->
PrimaryColors
Secondary ->
SecondaryColors
Borderless ->
BorderlessColors
Danger ->
DangerColors
Premium ->
PremiumColors
buttonStyles : ButtonSize -> Maybe Int -> ColorPalette -> ElementType -> List Style
buttonStyles size width colorPalette elementType =
List.concat
[ buttonStyle
, colorStyle colorPalette
, sizeStyle size width elementType
]
viewLabel : Maybe IconType -> String -> List (Html msg)
viewLabel icn label =
case icn of
Nothing ->
renderMarkdown label
Just iconType ->
[ Html.span [] (Icon.decorativeIcon iconType :: 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 : List Style
buttonStyle =
[ Css.cursor Css.pointer
, Css.display Css.inlineBlock
, -- 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" "all 0.2s"
, Css.boxShadow Css.none
, Css.border Css.zero
, Css.marginBottom Css.zero
, Css.hover [ Css.textDecoration Css.none ]
, Css.disabled [ Css.cursor Css.notAllowed ]
]
colorStyle : ColorPalette -> List Style
colorStyle colorPalette =
let
( config, additionalStyles ) =
case colorPalette of
PrimaryColors ->
( { background = Colors.azure
, hover = Colors.azureDark
, text = Colors.white
, border = Nothing
, shadow = Colors.azureDark
}
, []
)
SecondaryColors ->
( { background = Colors.white
, hover = Colors.glacier
, text = Colors.azure
, border = Just <| Colors.azure
, shadow = Colors.azure
}
, []
)
BorderlessColors ->
( { background = Css.rgba 0 0 0 0
, hover = Css.rgba 0 0 0 0
, text = Colors.azure
, border = Nothing
, shadow = Css.rgba 0 0 0 0
}
, [ Css.hover
[ Css.textDecoration Css.underline
, Css.disabled [ Css.textDecoration Css.none ]
]
]
)
DangerColors ->
( { background = Colors.red
, hover = Colors.redDark
, text = Colors.white
, border = Nothing
, shadow = Colors.redDark
}
, []
)
PremiumColors ->
( { background = Colors.yellow
, hover = Colors.ochre
, text = Colors.navy
, border = Nothing
, shadow = Colors.ochre
}
, []
)
InactiveColors ->
( { background = Colors.gray92
, hover = Colors.gray92
, text = Colors.gray45
, border = Nothing
, shadow = Colors.gray92
}
, []
)
LoadingColors ->
( { background = Colors.glacier
, hover = Colors.glacier
, text = Colors.navy
, border = Nothing
, shadow = Colors.glacier
}
, []
)
SuccessColors ->
( { background = Colors.greenDark
, hover = Colors.greenDark
, text = Colors.white
, border = Nothing
, shadow = Colors.greenDark
}
, []
)
ErrorColors ->
( { background = Colors.purple
, hover = Colors.purple
, text = Colors.white
, border = Nothing
, shadow = Colors.purple
}
, []
)
in
[ Css.batch additionalStyles
, Css.color config.text
, Css.backgroundColor config.background
, Css.fontWeight (Css.int 700)
, Css.textAlign Css.center
, case config.border of
Nothing ->
Css.borderStyle Css.none
Just color ->
Css.batch
[ Css.borderColor color
, Css.borderStyle Css.solid
]
, Css.borderBottomStyle Css.solid
, Css.borderBottomColor config.shadow
, Css.fontStyle Css.normal
, Css.hover
[ Css.color config.text
, Css.backgroundColor config.hover
, Css.disabled [ Css.backgroundColor config.background ]
]
, Css.visited [ Css.color config.text ]
]
type ElementType
= Anchor
| Button
sizeStyle : ButtonSize -> Maybe Int -> ElementType -> List Style
sizeStyle size width elementType =
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
}
widthAttributes =
case width of
Just pxWidth ->
[ Css.maxWidth (Css.pct 100)
, Css.width (Css.px <| toFloat pxWidth)
]
Nothing ->
[ Css.padding2 Css.zero (Css.px 16)
, Css.minWidth (Css.px config.minWidth)
]
lineHeightPx =
case elementType of
Anchor ->
config.height
Button ->
case size of
Small ->
15
Medium ->
19
Large ->
22
in
[ Css.fontSize (Css.px config.fontSize)
, Css.borderRadius (Css.px 8)
, Css.height (Css.px config.height)
, Css.lineHeight (Css.px lineHeightPx)
, Css.boxSizing Css.borderBox
, Css.borderWidth (Css.px 1)
, Css.borderBottomWidth (Css.px config.shadowHeight)
, 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
]
]
]
styledName : String -> String
styledName suffix =
"Nri-Ui-Button-V3-" ++ suffix

View File

@ -1,811 +0,0 @@
module Nri.Ui.Button.V4 exposing
( ButtonSize(..), ButtonWidth(..), ButtonStyle(..), ButtonState(..), ButtonContent
, ButtonConfig, button, customButton, delete, copyToClipboard, ToggleButtonConfig, toggleButton
, LinkConfig, link, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
)
{-|
# Changes from V3:
- Adds `ButtonWidth`.
- Button now grows vertically to fit content.
To limit the height use attributes on its container or consider truncating content before rendering.
# About:
Common NoRedInk buttons. For accessibility purposes, buttons that perform an
action on the current page should be HTML `<button>` elements and are created here
with `*Button` functions. Buttons that take the user to a new page should be
HTML `<a>` elements and are created here with `*Link` functions. Both versions
should be able to use the same CSS class in all cases.
There will generally be a `*Button` and `*Link` version of each button style.
(These will be created as they are needed.)
In general a button should never truncate or obscure its contents. This could
make it difficult or impossible for a student or teacher to use the site, so in
general choose buttons that grow to fit their contents. It is better to risk
weird layout than to block users. Might this be a golden rule? Of course there
may be exceptions, for example if button content is supplied by an end-user.
## Common configs
@docs ButtonSize, ButtonWidth, ButtonStyle, ButtonState, ButtonContent
## `<button>` Buttons
@docs ButtonConfig, button, customButton, delete, copyToClipboard, ToggleButtonConfig, toggleButton
## `<a>` Buttons
@docs LinkConfig, link, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
-}
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.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.Icon.V3 as Icon exposing (IconType)
{-| Sizes for buttons and links that have button classes
-}
type ButtonSize
= Small
| Medium
| Large
{-| Width sizing behavior for buttons.
`WidthExact Int` defines a size in `px` for the button's total width, and
`WidthUnbounded` leaves the maxiumum width unbounded (there is a minimum width).
-}
type ButtonWidth
= WidthExact Int
| WidthUnbounded
{-| Styleguide-approved styles for your buttons!
Note on borderless buttons:
A borderless button that performs an action on the current page
This button is intended to look like a link.
Only use a borderless button when the clickable text in question follows the same layout/margin/padding as a bordered button
-}
type ButtonStyle
= Primary
| Secondary
| Borderless
| Danger
| Premium
{-| Describes the state of a button. Has consequences for appearance and disabled attribute.
- Enabled: An enabled button. Takes the appearance of ButtonStyle
- Unfulfilled: A button which appears with the InactiveColors palette but is not disabled.
- Disabled: A button which appears with the InactiveColors palette and is disabled.
- Error: A button which appears with the ErrorColors palette and is disabled.
- Loading: A button which appears with the LoadingColors palette and is disabled
- Success: A button which appears with the SuccessColors palette and is disabled
-}
type ButtonState
= Enabled
| Unfulfilled
| Disabled
| Error
| Loading
| Success
{-| The part of a button that remains constant through different button states
-}
type alias ButtonConfig msg =
{ onClick : msg
, size : ButtonSize
, style : ButtonStyle
, width : ButtonWidth
}
{-| ButtonContent, often changes based on ButtonState. For example, a button in the "Success"
state may have a different label than a button in the "Error" state
-}
type alias ButtonContent =
{ label : String
, state : ButtonState
, icon : Maybe IconType
}
{-| A delightful button which can trigger an effect when clicked!
This button will trigger the passed-in message if the button state is:
- Enabled
- Unfulfilled
This button will be Disabled if the button state is:
- Disabled
- Error
- Loading
- Success
-}
button : ButtonConfig msg -> ButtonContent -> Html msg
button config content =
customButton [] config content
{-| Exactly the same as button but you can pass in a list of attributes
-}
customButton : List (Attribute msg) -> ButtonConfig msg -> ButtonContent -> Html msg
customButton attributes config content =
let
buttonStyle_ =
case content.state of
Enabled ->
styleToColorPalette config.style
Disabled ->
InactiveColors
Error ->
ErrorColors
Unfulfilled ->
InactiveColors
Loading ->
LoadingColors
Success ->
SuccessColors
disabled =
case content.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_ Button)
([ Events.onClick config.onClick
, Attributes.disabled disabled
, Attributes.type_ "button"
]
++ attributes
)
(viewLabel content.icon content.label)
-- COPY TO CLIPBOARD BUTTON
{-| Config for copyToClipboard
-}
type alias CopyToClipboardConfig =
{ size : ButtonSize
, style : ButtonStyle
, copyText : String
, buttonLabel : String
, withIcon : Bool
, width : ButtonWidth
}
{-| See ui/src/Page/Teach/Courses/Assignments/index.coffee
You will need to hook this up to clipboard.js
-}
copyToClipboard : { r | teach_assignments_copyWhite_svg : Asset } -> CopyToClipboardConfig -> Html msg
copyToClipboard assets config =
let
maybeIcon =
if config.withIcon then
Just (Icon.copy assets)
else
Nothing
in
Nri.Ui.styled Html.button
(styledName "copyToClipboard")
(buttonStyles config.size config.width (styleToColorPalette config.style) Button)
[ Widget.label "Copy URL to clipboard"
, Attributes.attribute "data-clipboard-text" config.copyText
]
(viewLabel maybeIcon config.buttonLabel)
-- DELETE BUTTON
type alias DeleteButtonConfig msg =
{ label : String
, onClick : msg
}
{-| A delete button (blue X)
-}
delete : { r | x : String } -> DeleteButtonConfig msg -> Html msg
delete assets 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
]
[ Icon.icon { alt = "Delete", icon = Icon.xSvg assets } ]
-- TOGGLE BUTTON
{-| Buttons can be toggled into a pressed state and back again.
-}
type alias ToggleButtonConfig msg =
{ label : String
, onSelect : msg
, onDeselect : msg
, pressed : Bool
}
{-| -}
toggleButton : ToggleButtonConfig msg -> Html msg
toggleButton config =
let
toggledStyles =
if config.pressed then
[ 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
[]
in
Nri.Ui.styled Html.button
(styledName "toggleButton")
(buttonStyles Medium WidthUnbounded SecondaryColors Button
++ 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)
{-| Inputs can be a clickable thing used in a form
-}
type alias InputConfig =
{ content : Html Never
, name : String
, size : ButtonSize
, style : ButtonStyle
, value : String
}
-- LINKS THAT LOOK LIKE BUTTONS
{-| Links are clickable things with a url.
NOTE: Links do not support two-line labels.
-}
type alias LinkConfig =
{ label : String
, icon : Maybe IconType
, url : String
, size : ButtonSize
, style : ButtonStyle
, width : ButtonWidth
}
{-| Wrap some text so it looks like a button, but actually is wrapped in an anchor to
some url
-}
link : LinkConfig -> Html msg
link =
linkBase "link" [ Attributes.target "_self" ]
{-| 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 -> String)
-> (route -> msg)
->
{ label : String
, icon : Maybe IconType
, size : ButtonSize
, style : ButtonStyle
, width : ButtonWidth
, route : route
}
-> Html msg
linkSpa toUrl toMsg config =
linkBase
"linkSpa"
[ EventExtras.onClickPreventDefaultForLinkWithHref (toMsg config.route)
]
{ label = config.label
, icon = config.icon
, size = config.size
, style = config.style
, width = config.width
, url = toUrl config.route
}
{-| 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 : LinkConfig -> Html msg
linkExternal =
linkBase "linkExternal" [ Attributes.target "_blank" ]
{-| 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 : String -> LinkConfig -> Html msg
linkWithMethod method =
linkBase "linkWithMethod" [ Attributes.attribute "data-method" method ]
{-| 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 : msg -> LinkConfig -> Html msg
linkWithTracking onTrack =
linkBase
"linkWithTracking"
[ Events.preventDefaultOn "click"
(Json.Decode.succeed ( onTrack, True ))
]
{-| 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
This should only take in messages that result in tracking events. For buttons that trigger other effects on the page, please use Nri.Ui.Button.V2.button instead
-}
linkExternalWithTracking : msg -> LinkConfig -> Html msg
linkExternalWithTracking onTrack =
linkBase
"linkExternalWithTracking"
[ Attributes.target "_blank"
, EventExtras.onClickForLinkWithHref onTrack
]
{-| Helper function for building links with an arbitrary number of Attributes
-}
linkBase : String -> List (Attribute msg) -> LinkConfig -> Html msg
linkBase linkFunctionName extraAttrs config =
Nri.Ui.styled Styled.a
(styledName linkFunctionName)
(Css.whiteSpace Css.noWrap
:: buttonStyles config.size config.width (styleToColorPalette config.style) Anchor
)
(Attributes.href config.url
:: extraAttrs
)
(viewLabel config.icon config.label)
-- HELPERS
type ColorPalette
= PrimaryColors
| SecondaryColors
| BorderlessColors
| DangerColors
| PremiumColors
| InactiveColors
| LoadingColors
| SuccessColors
| ErrorColors
styleToColorPalette : ButtonStyle -> ColorPalette
styleToColorPalette style =
case style of
Primary ->
PrimaryColors
Secondary ->
SecondaryColors
Borderless ->
BorderlessColors
Danger ->
DangerColors
Premium ->
PremiumColors
buttonStyles : ButtonSize -> ButtonWidth -> ColorPalette -> ElementType -> List Style
buttonStyles size width colorPalette elementType =
List.concat
[ buttonStyle
, colorStyle colorPalette
, sizeStyle size width elementType
]
viewLabel : Maybe IconType -> String -> List (Html msg)
viewLabel icn label =
case icn of
Nothing ->
renderMarkdown label
Just iconType ->
[ Html.span [] (Icon.decorativeIcon iconType :: 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 : List Style
buttonStyle =
[ Css.cursor Css.pointer
, Css.display Css.inlineBlock
, -- 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" "all 0.2s"
, Css.boxShadow Css.none
, Css.border Css.zero
, Css.marginBottom Css.zero
, Css.hover [ Css.textDecoration Css.none ]
, Css.disabled [ Css.cursor Css.notAllowed ]
]
colorStyle : ColorPalette -> List Style
colorStyle colorPalette =
let
( config, additionalStyles ) =
case colorPalette of
PrimaryColors ->
( { background = Colors.azure
, hover = Colors.azureDark
, text = Colors.white
, border = Nothing
, shadow = Colors.azureDark
}
, []
)
SecondaryColors ->
( { background = Colors.white
, hover = Colors.glacier
, text = Colors.azure
, border = Just <| Colors.azure
, shadow = Colors.azure
}
, []
)
BorderlessColors ->
( { background = Css.rgba 0 0 0 0
, hover = Css.rgba 0 0 0 0
, text = Colors.azure
, border = Nothing
, shadow = Css.rgba 0 0 0 0
}
, [ Css.hover
[ Css.textDecoration Css.underline
, Css.disabled [ Css.textDecoration Css.none ]
]
]
)
DangerColors ->
( { background = Colors.red
, hover = Colors.redDark
, text = Colors.white
, border = Nothing
, shadow = Colors.redDark
}
, []
)
PremiumColors ->
( { background = Colors.yellow
, hover = Colors.ochre
, text = Colors.navy
, border = Nothing
, shadow = Colors.ochre
}
, []
)
InactiveColors ->
( { background = Colors.gray92
, hover = Colors.gray92
, text = Colors.gray45
, border = Nothing
, shadow = Colors.gray92
}
, []
)
LoadingColors ->
( { background = Colors.glacier
, hover = Colors.glacier
, text = Colors.navy
, border = Nothing
, shadow = Colors.glacier
}
, []
)
SuccessColors ->
( { background = Colors.greenDark
, hover = Colors.greenDark
, text = Colors.white
, border = Nothing
, shadow = Colors.greenDark
}
, []
)
ErrorColors ->
( { background = Colors.purple
, hover = Colors.purple
, text = Colors.white
, border = Nothing
, shadow = Colors.purple
}
, []
)
in
[ Css.batch additionalStyles
, Css.color config.text
, Css.backgroundColor config.background
, Css.fontWeight (Css.int 700)
, Css.textAlign Css.center
, case config.border of
Nothing ->
Css.borderStyle Css.none
Just color ->
Css.batch
[ Css.borderColor color
, Css.borderStyle Css.solid
]
, Css.borderBottomStyle Css.solid
, Css.borderBottomColor config.shadow
, Css.fontStyle Css.normal
, Css.hover
[ Css.color config.text
, Css.backgroundColor config.hover
, Css.disabled [ Css.backgroundColor config.background ]
]
, Css.visited [ Css.color config.text ]
]
type ElementType
= Anchor
| Button
sizeStyle : ButtonSize -> ButtonWidth -> ElementType -> List Style
sizeStyle size width elementType =
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 =
case elementType of
Button ->
let
verticalPaddingPx =
4
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)
]
WidthUnbounded ->
[ Css.paddingLeft (Css.px 16)
, Css.paddingRight (Css.px 16)
, Css.minWidth (Css.px config.minWidth)
]
lineHeightPx =
case elementType of
Anchor ->
config.height
Button ->
case size of
Small ->
15
Medium ->
19
Large ->
22
in
[ 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
]
]
]
styledName : String -> String
styledName suffix =
"Nri-Ui-Button-V4-" ++ suffix

View File

@ -1,810 +0,0 @@
module Nri.Ui.Button.V6 exposing
( ButtonSize(..), ButtonWidth(..), ButtonStyle(..), ButtonState(..), ButtonContent
, ButtonConfig, button, customButton, delete, copyToClipboard, ToggleButtonConfig, toggleButton
, LinkConfig, link, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
)
{-|
# Changes from V5:
- Update version of Nri.Ui.Icon to V4
# About:
Common NoRedInk buttons. For accessibility purposes, buttons that perform an
action on the current page should be HTML `<button>` elements and are created here
with `*Button` functions. Buttons that take the user to a new page should be
HTML `<a>` elements and are created here with `*Link` functions. Both versions
should be able to use the same CSS class in all cases.
There will generally be a `*Button` and `*Link` version of each button style.
(These will be created as they are needed.)
In general a button should never truncate or obscure its contents. This could
make it difficult or impossible for a student or teacher to use the site, so in
general choose buttons that grow to fit their contents. It is better to risk
weird layout than to block users. Might this be a golden rule? Of course there
may be exceptions, for example if button content is supplied by an end-user.
## Common configs
@docs ButtonSize, ButtonWidth, ButtonStyle, ButtonState, ButtonContent
## `<button>` Buttons
@docs ButtonConfig, button, customButton, delete, copyToClipboard, ToggleButtonConfig, toggleButton
## `<a>` Buttons
@docs LinkConfig, link, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
-}
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.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.Icon.V4 as Icon exposing (IconType)
{-| Sizes for buttons and links that have button classes
-}
type ButtonSize
= Small
| Medium
| Large
{-| Width sizing behavior for buttons.
`WidthExact Int` defines a size in `px` for the button's total width, and
`WidthUnbounded` leaves the maxiumum width unbounded (there is a minimum width).
-}
type ButtonWidth
= WidthExact Int
| WidthUnbounded
{-| Styleguide-approved styles for your buttons!
Note on borderless buttons:
A borderless button that performs an action on the current page
This button is intended to look like a link.
Only use a borderless button when the clickable text in question follows the same layout/margin/padding as a bordered button
-}
type ButtonStyle
= Primary
| Secondary
| Borderless
| Danger
| Premium
{-| Describes the state of a button. Has consequences for appearance and disabled attribute.
- Enabled: An enabled button. Takes the appearance of ButtonStyle
- Unfulfilled: A button which appears with the InactiveColors palette but is not disabled.
- Disabled: A button which appears with the InactiveColors palette and is disabled.
- Error: A button which appears with the ErrorColors palette and is disabled.
- Loading: A button which appears with the LoadingColors palette and is disabled
- Success: A button which appears with the SuccessColors palette and is disabled
-}
type ButtonState
= Enabled
| Unfulfilled
| Disabled
| Error
| Loading
| Success
{-| The part of a button that remains constant through different button states
-}
type alias ButtonConfig msg =
{ onClick : msg
, size : ButtonSize
, style : ButtonStyle
, width : ButtonWidth
}
{-| ButtonContent, often changes based on ButtonState. For example, a button in the "Success"
state may have a different label than a button in the "Error" state
-}
type alias ButtonContent =
{ label : String
, state : ButtonState
, icon : Maybe IconType
}
{-| A delightful button which can trigger an effect when clicked!
This button will trigger the passed-in message if the button state is:
- Enabled
- Unfulfilled
This button will be Disabled if the button state is:
- Disabled
- Error
- Loading
- Success
-}
button : ButtonConfig msg -> ButtonContent -> Html msg
button config content =
customButton [] config content
{-| Exactly the same as button but you can pass in a list of attributes
-}
customButton : List (Attribute msg) -> ButtonConfig msg -> ButtonContent -> Html msg
customButton attributes config content =
let
buttonStyle_ =
case content.state of
Enabled ->
styleToColorPalette config.style
Disabled ->
InactiveColors
Error ->
ErrorColors
Unfulfilled ->
InactiveColors
Loading ->
LoadingColors
Success ->
SuccessColors
disabled =
case content.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_ ]
([ Events.onClick config.onClick
, Attributes.disabled disabled
, Attributes.type_ "button"
]
++ attributes
)
[ viewLabel content.icon content.label ]
-- COPY TO CLIPBOARD BUTTON
{-| Config for copyToClipboard
-}
type alias CopyToClipboardConfig =
{ size : ButtonSize
, style : ButtonStyle
, copyText : String
, buttonLabel : String
, withIcon : Bool
, width : ButtonWidth
}
{-| See ui/src/Page/Teach/Courses/Assignments/index.coffee
You will need to hook this up to clipboard.js
-}
copyToClipboard : { r | teach_assignments_copyWhite_svg : Asset } -> CopyToClipboardConfig -> Html msg
copyToClipboard assets config =
let
maybeIcon =
if config.withIcon then
Just (Icon.copy assets)
else
Nothing
in
Nri.Ui.styled Html.button
(styledName "copyToClipboard")
[ buttonStyles config.size config.width (styleToColorPalette config.style) ]
[ Widget.label "Copy URL to clipboard"
, Attributes.attribute "data-clipboard-text" config.copyText
]
[ viewLabel maybeIcon config.buttonLabel ]
-- DELETE BUTTON
type alias DeleteButtonConfig msg =
{ label : String
, onClick : msg
}
{-| A delete button (blue X)
-}
delete : { r | x : String } -> DeleteButtonConfig msg -> Html msg
delete assets 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
]
[ Icon.icon { alt = "Delete", icon = Icon.xSvg assets } ]
-- TOGGLE BUTTON
{-| Buttons can be toggled into a pressed state and back again.
-}
type alias ToggleButtonConfig msg =
{ label : String
, onSelect : msg
, onDeselect : msg
, pressed : Bool
}
{-| -}
toggleButton : ToggleButtonConfig msg -> 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 ]
{-| Inputs can be a clickable thing used in a form
-}
type alias InputConfig =
{ content : Html Never
, name : String
, size : ButtonSize
, style : ButtonStyle
, value : String
}
-- LINKS THAT LOOK LIKE BUTTONS
{-| Links are clickable things with a url.
NOTE: Links do not support two-line labels.
-}
type alias LinkConfig =
{ label : String
, icon : Maybe IconType
, url : String
, size : ButtonSize
, style : ButtonStyle
, width : ButtonWidth
}
{-| Wrap some text so it looks like a button, but actually is wrapped in an anchor to
some url
-}
link : LinkConfig -> Html msg
link =
linkBase "link" [ Attributes.target "_self" ]
{-| 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 -> String)
-> (route -> msg)
->
{ label : String
, icon : Maybe IconType
, size : ButtonSize
, style : ButtonStyle
, width : ButtonWidth
, route : route
}
-> Html msg
linkSpa toUrl toMsg config =
linkBase
"linkSpa"
[ EventExtras.onClickPreventDefaultForLinkWithHref (toMsg config.route)
]
{ label = config.label
, icon = config.icon
, size = config.size
, style = config.style
, width = config.width
, url = toUrl config.route
}
{-| 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 : LinkConfig -> Html msg
linkExternal =
linkBase "linkExternal" [ Attributes.target "_blank" ]
{-| 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 : String -> LinkConfig -> Html msg
linkWithMethod method =
linkBase "linkWithMethod" [ Attributes.attribute "data-method" method ]
{-| 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 : msg -> LinkConfig -> Html msg
linkWithTracking onTrack =
linkBase
"linkWithTracking"
[ Events.preventDefaultOn "click"
(Json.Decode.succeed ( onTrack, True ))
]
{-| 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
This should only take in messages that result in tracking events. For buttons that trigger other effects on the page, please use Nri.Ui.Button.V2.button instead
-}
linkExternalWithTracking : msg -> LinkConfig -> Html msg
linkExternalWithTracking onTrack =
linkBase
"linkExternalWithTracking"
[ Attributes.target "_blank"
, EventExtras.onClickForLinkWithHref onTrack
]
{-| Helper function for building links with an arbitrary number of Attributes
-}
linkBase : String -> List (Attribute msg) -> LinkConfig -> Html msg
linkBase linkFunctionName extraAttrs config =
Nri.Ui.styled Styled.a
(styledName linkFunctionName)
[ buttonStyles config.size config.width (styleToColorPalette config.style) ]
(Attributes.href config.url
:: extraAttrs
)
[ viewLabel config.icon config.label ]
-- HELPERS
type ColorPalette
= PrimaryColors
| SecondaryColors
| BorderlessColors
| DangerColors
| PremiumColors
| InactiveColors
| LoadingColors
| SuccessColors
| ErrorColors
styleToColorPalette : ButtonStyle -> ColorPalette
styleToColorPalette style =
case style of
Primary ->
PrimaryColors
Secondary ->
SecondaryColors
Borderless ->
BorderlessColors
Danger ->
DangerColors
Premium ->
PremiumColors
buttonStyles : ButtonSize -> ButtonWidth -> ColorPalette -> Style
buttonStyles size width colorPalette =
Css.batch
[ buttonStyle
, colorStyle colorPalette
, sizeStyle size width
]
viewLabel : Maybe IconType -> String -> Html msg
viewLabel icn 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 icn of
Nothing ->
renderMarkdown label
Just iconType ->
Icon.decorativeIcon iconType :: 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
, Css.display Css.inlineBlock
, -- 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.displayFlex
, Css.alignItems Css.center
, Css.justifyContent Css.center
]
colorStyle : ColorPalette -> Style
colorStyle colorPalette =
let
( config, additionalStyles ) =
case colorPalette of
PrimaryColors ->
( { background = Colors.azure
, hover = Colors.azureDark
, text = Colors.white
, border = Nothing
, shadow = Colors.azureDark
}
, []
)
SecondaryColors ->
( { background = Colors.white
, hover = Colors.glacier
, text = Colors.azure
, border = Just <| Colors.azure
, shadow = Colors.azure
}
, []
)
BorderlessColors ->
( { background = Css.rgba 0 0 0 0
, hover = Css.rgba 0 0 0 0
, text = Colors.azure
, border = Nothing
, shadow = Css.rgba 0 0 0 0
}
, [ Css.hover
[ Css.textDecoration Css.underline
, Css.disabled [ Css.textDecoration Css.none ]
]
]
)
DangerColors ->
( { background = Colors.red
, hover = Colors.redDark
, text = Colors.white
, border = Nothing
, shadow = Colors.redDark
}
, []
)
PremiumColors ->
( { background = Colors.yellow
, hover = Colors.ochre
, text = Colors.navy
, border = Nothing
, shadow = Colors.ochre
}
, []
)
InactiveColors ->
( { background = Colors.gray92
, hover = Colors.gray92
, text = Colors.gray45
, border = Nothing
, shadow = Colors.gray92
}
, []
)
LoadingColors ->
( { background = Colors.glacier
, hover = Colors.glacier
, text = Colors.navy
, border = Nothing
, shadow = Colors.glacier
}
, []
)
SuccessColors ->
( { background = Colors.greenDark
, hover = Colors.greenDark
, text = Colors.white
, border = Nothing
, shadow = Colors.greenDark
}
, []
)
ErrorColors ->
( { background = Colors.purple
, hover = Colors.purple
, text = Colors.white
, border = Nothing
, shadow = Colors.purple
}
, []
)
in
Css.batch
[ Css.batch additionalStyles
, Css.color config.text
, Css.backgroundColor config.background
, Css.fontWeight (Css.int 700)
, Css.textAlign Css.center
, case config.border of
Nothing ->
Css.borderStyle Css.none
Just color ->
Css.batch
[ Css.borderColor color
, Css.borderStyle Css.solid
]
, Css.borderBottomStyle Css.solid
, Css.borderBottomColor config.shadow
, Css.fontStyle Css.normal
, Css.hover
[ Css.color config.text
, Css.backgroundColor config.hover
, Css.disabled [ Css.backgroundColor config.background ]
]
, Css.visited [ Css.color config.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)
]
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
]
]
]
styledName : String -> String
styledName suffix =
"Nri-Ui-Button-V6-" ++ suffix

View File

@ -1,818 +0,0 @@
module Nri.Ui.Button.V7 exposing
( ButtonSize(..), ButtonWidth(..), ButtonStyle(..), ButtonState(..), ButtonContent
, ButtonConfig, button, customButton, delete, copyToClipboard, ToggleButtonConfig, toggleButton
, LinkConfig, link, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
)
{-|
# Changes from V6:
- WidthUnbounded button displays inlineFlex, so that it doesn't accidentally take up the entire width
- Add WidthFillContainer, for when we intend to take up the full container
# About:
Common NoRedInk buttons. For accessibility purposes, buttons that perform an
action on the current page should be HTML `<button>` elements and are created here
with `*Button` functions. Buttons that take the user to a new page should be
HTML `<a>` elements and are created here with `*Link` functions. Both versions
should be able to use the same CSS class in all cases.
There will generally be a `*Button` and `*Link` version of each button style.
(These will be created as they are needed.)
In general a button should never truncate or obscure its contents. This could
make it difficult or impossible for a student or teacher to use the site, so in
general choose buttons that grow to fit their contents. It is better to risk
weird layout than to block users. Might this be a golden rule? Of course there
may be exceptions, for example if button content is supplied by an end-user.
## Common configs
@docs ButtonSize, ButtonWidth, ButtonStyle, ButtonState, ButtonContent
## `<button>` Buttons
@docs ButtonConfig, button, customButton, delete, copyToClipboard, ToggleButtonConfig, toggleButton
## `<a>` Buttons
@docs LinkConfig, link, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
-}
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.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.Icon.V4 as Icon exposing (IconType)
{-| Sizes for buttons and links that have button classes
-}
type ButtonSize
= Small
| Medium
| Large
{-| Width sizing behavior for buttons.
`WidthExact Int` defines a size in `px` for the button's total width, and
`WidthUnbounded` leaves the maxiumum width unbounded (there is a minimum width).
-}
type ButtonWidth
= WidthExact Int
| WidthUnbounded
| WidthFillContainer
{-| Styleguide-approved styles for your buttons!
Note on borderless buttons:
A borderless button that performs an action on the current page
This button is intended to look like a link.
Only use a borderless button when the clickable text in question follows the same layout/margin/padding as a bordered button
-}
type ButtonStyle
= Primary
| Secondary
| Borderless
| Danger
| Premium
{-| Describes the state of a button. Has consequences for appearance and disabled attribute.
- Enabled: An enabled button. Takes the appearance of ButtonStyle
- Unfulfilled: A button which appears with the InactiveColors palette but is not disabled.
- Disabled: A button which appears with the InactiveColors palette and is disabled.
- Error: A button which appears with the ErrorColors palette and is disabled.
- Loading: A button which appears with the LoadingColors palette and is disabled
- Success: A button which appears with the SuccessColors palette and is disabled
-}
type ButtonState
= Enabled
| Unfulfilled
| Disabled
| Error
| Loading
| Success
{-| The part of a button that remains constant through different button states
-}
type alias ButtonConfig msg =
{ onClick : msg
, size : ButtonSize
, style : ButtonStyle
, width : ButtonWidth
}
{-| ButtonContent, often changes based on ButtonState. For example, a button in the "Success"
state may have a different label than a button in the "Error" state
-}
type alias ButtonContent =
{ label : String
, state : ButtonState
, icon : Maybe IconType
}
{-| A delightful button which can trigger an effect when clicked!
This button will trigger the passed-in message if the button state is:
- Enabled
- Unfulfilled
This button will be Disabled if the button state is:
- Disabled
- Error
- Loading
- Success
-}
button : ButtonConfig msg -> ButtonContent -> Html msg
button config content =
customButton [] config content
{-| Exactly the same as button but you can pass in a list of attributes
-}
customButton : List (Attribute msg) -> ButtonConfig msg -> ButtonContent -> Html msg
customButton attributes config content =
let
buttonStyle_ =
case content.state of
Enabled ->
styleToColorPalette config.style
Disabled ->
InactiveColors
Error ->
ErrorColors
Unfulfilled ->
InactiveColors
Loading ->
LoadingColors
Success ->
SuccessColors
disabled =
case content.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_ ]
([ Events.onClick config.onClick
, Attributes.disabled disabled
, Attributes.type_ "button"
]
++ attributes
)
[ viewLabel content.icon content.label ]
-- COPY TO CLIPBOARD BUTTON
{-| Config for copyToClipboard
-}
type alias CopyToClipboardConfig =
{ size : ButtonSize
, style : ButtonStyle
, copyText : String
, buttonLabel : String
, withIcon : Bool
, width : ButtonWidth
}
{-| See ui/src/Page/Teach/Courses/Assignments/index.coffee
You will need to hook this up to clipboard.js
-}
copyToClipboard : { r | teach_assignments_copyWhite_svg : Asset } -> CopyToClipboardConfig -> Html msg
copyToClipboard assets config =
let
maybeIcon =
if config.withIcon then
Just (Icon.copy assets)
else
Nothing
in
Nri.Ui.styled Html.button
(styledName "copyToClipboard")
[ buttonStyles config.size config.width (styleToColorPalette config.style) ]
[ Widget.label "Copy URL to clipboard"
, Attributes.attribute "data-clipboard-text" config.copyText
]
[ viewLabel maybeIcon config.buttonLabel ]
-- DELETE BUTTON
type alias DeleteButtonConfig msg =
{ label : String
, onClick : msg
}
{-| A delete button (blue X)
-}
delete : { r | x : String } -> DeleteButtonConfig msg -> Html msg
delete assets 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
]
[ Icon.icon { alt = "Delete", icon = Icon.xSvg assets } ]
-- TOGGLE BUTTON
{-| Buttons can be toggled into a pressed state and back again.
-}
type alias ToggleButtonConfig msg =
{ label : String
, onSelect : msg
, onDeselect : msg
, pressed : Bool
}
{-| -}
toggleButton : ToggleButtonConfig msg -> 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 ]
{-| Inputs can be a clickable thing used in a form
-}
type alias InputConfig =
{ content : Html Never
, name : String
, size : ButtonSize
, style : ButtonStyle
, value : String
}
-- LINKS THAT LOOK LIKE BUTTONS
{-| Links are clickable things with a url.
NOTE: Links do not support two-line labels.
-}
type alias LinkConfig =
{ label : String
, icon : Maybe IconType
, url : String
, size : ButtonSize
, style : ButtonStyle
, width : ButtonWidth
}
{-| Wrap some text so it looks like a button, but actually is wrapped in an anchor to
some url
-}
link : LinkConfig -> Html msg
link =
linkBase "link" [ Attributes.target "_self" ]
{-| 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 -> String)
-> (route -> msg)
->
{ label : String
, icon : Maybe IconType
, size : ButtonSize
, style : ButtonStyle
, width : ButtonWidth
, route : route
}
-> Html msg
linkSpa toUrl toMsg config =
linkBase
"linkSpa"
[ EventExtras.onClickPreventDefaultForLinkWithHref (toMsg config.route)
]
{ label = config.label
, icon = config.icon
, size = config.size
, style = config.style
, width = config.width
, url = toUrl config.route
}
{-| 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 : LinkConfig -> Html msg
linkExternal =
linkBase "linkExternal" [ Attributes.target "_blank" ]
{-| 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 : String -> LinkConfig -> Html msg
linkWithMethod method =
linkBase "linkWithMethod" [ Attributes.attribute "data-method" method ]
{-| 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 : msg -> LinkConfig -> Html msg
linkWithTracking onTrack =
linkBase
"linkWithTracking"
[ Events.preventDefaultOn "click"
(Json.Decode.succeed ( onTrack, True ))
]
{-| 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
This should only take in messages that result in tracking events. For buttons that trigger other effects on the page, please use Nri.Ui.Button.V2.button instead
-}
linkExternalWithTracking : msg -> LinkConfig -> Html msg
linkExternalWithTracking onTrack =
linkBase
"linkExternalWithTracking"
[ Attributes.target "_blank"
, EventExtras.onClickForLinkWithHref onTrack
]
{-| Helper function for building links with an arbitrary number of Attributes
-}
linkBase : String -> List (Attribute msg) -> LinkConfig -> Html msg
linkBase linkFunctionName extraAttrs config =
Nri.Ui.styled Styled.a
(styledName linkFunctionName)
[ buttonStyles config.size config.width (styleToColorPalette config.style) ]
(Attributes.href config.url
:: extraAttrs
)
[ viewLabel config.icon config.label ]
-- HELPERS
type ColorPalette
= PrimaryColors
| SecondaryColors
| BorderlessColors
| DangerColors
| PremiumColors
| InactiveColors
| LoadingColors
| SuccessColors
| ErrorColors
styleToColorPalette : ButtonStyle -> ColorPalette
styleToColorPalette style =
case style of
Primary ->
PrimaryColors
Secondary ->
SecondaryColors
Borderless ->
BorderlessColors
Danger ->
DangerColors
Premium ->
PremiumColors
buttonStyles : ButtonSize -> ButtonWidth -> ColorPalette -> Style
buttonStyles size width colorPalette =
Css.batch
[ buttonStyle
, colorStyle colorPalette
, sizeStyle size width
]
viewLabel : Maybe IconType -> String -> Html msg
viewLabel icn 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 icn of
Nothing ->
renderMarkdown label
Just iconType ->
Icon.decorativeIcon iconType :: 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
]
colorStyle : ColorPalette -> Style
colorStyle colorPalette =
let
( config, additionalStyles ) =
case colorPalette of
PrimaryColors ->
( { background = Colors.azure
, hover = Colors.azureDark
, text = Colors.white
, border = Nothing
, shadow = Colors.azureDark
}
, []
)
SecondaryColors ->
( { background = Colors.white
, hover = Colors.glacier
, text = Colors.azure
, border = Just <| Colors.azure
, shadow = Colors.azure
}
, []
)
BorderlessColors ->
( { background = Css.rgba 0 0 0 0
, hover = Css.rgba 0 0 0 0
, text = Colors.azure
, border = Nothing
, shadow = Css.rgba 0 0 0 0
}
, [ Css.hover
[ Css.textDecoration Css.underline
, Css.disabled [ Css.textDecoration Css.none ]
]
]
)
DangerColors ->
( { background = Colors.red
, hover = Colors.redDark
, text = Colors.white
, border = Nothing
, shadow = Colors.redDark
}
, []
)
PremiumColors ->
( { background = Colors.yellow
, hover = Colors.ochre
, text = Colors.navy
, border = Nothing
, shadow = Colors.ochre
}
, []
)
InactiveColors ->
( { background = Colors.gray92
, hover = Colors.gray92
, text = Colors.gray45
, border = Nothing
, shadow = Colors.gray92
}
, []
)
LoadingColors ->
( { background = Colors.glacier
, hover = Colors.glacier
, text = Colors.navy
, border = Nothing
, shadow = Colors.glacier
}
, []
)
SuccessColors ->
( { background = Colors.greenDark
, hover = Colors.greenDark
, text = Colors.white
, border = Nothing
, shadow = Colors.greenDark
}
, []
)
ErrorColors ->
( { background = Colors.purple
, hover = Colors.purple
, text = Colors.white
, border = Nothing
, shadow = Colors.purple
}
, []
)
in
Css.batch
[ Css.batch additionalStyles
, Css.color config.text
, Css.backgroundColor config.background
, Css.fontWeight (Css.int 700)
, Css.textAlign Css.center
, case config.border of
Nothing ->
Css.borderStyle Css.none
Just color ->
Css.batch
[ Css.borderColor color
, Css.borderStyle Css.solid
]
, Css.borderBottomStyle Css.solid
, Css.borderBottomColor config.shadow
, Css.fontStyle Css.normal
, Css.hover
[ Css.color config.text
, Css.backgroundColor config.hover
, Css.disabled [ Css.backgroundColor config.background ]
]
, Css.visited [ Css.color config.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
]
]
]
styledName : String -> String
styledName suffix =
"Nri-Ui-Button-V7-" ++ suffix

View File

@ -1,947 +0,0 @@
module Nri.Ui.Button.V9 exposing
( button, link
, Attribute
, icon, custom
, onClick
, href, 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 href, 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 (Html)
import Accessibility.Styled.Role as Role
import Accessibility.Styled.Widget as Widget
import AttributeExtras exposing (targetBlank)
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
{-| -}
href : String -> Attribute msg
href url =
set (\attributes -> { attributes | url = url })
{-| 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"
(targetBlank ++ config.customAttributes)
ExternalWithTracking ->
linkBase "linkExternalWithTracking"
(List.concat
[ targetBlank
, config.onClick
|> Maybe.map
(\onClickMsg ->
[ Events.onClick onClickMsg
, Events.on "auxclick" (Json.Decode.succeed onClickMsg)
]
)
|> Maybe.withDefault []
, 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
]
]
]

View File

@ -1,168 +0,0 @@
module Nri.Ui.ClickableText.V1 exposing
( ButtonConfig, button
, LinkConfig, link
, Size(..)
)
{-|
# About:
ClickableText looks different from Nri.Ui.Button in that it displays without margin or padding.
ClickableText has the suave, traditional look of a "link"!
For accessibility purposes, buttons that perform an action on the current page should be HTML `<button>`
elements and are created here with `*Button` functions. Buttons that take the user to a new page should be
HTML `<a>` elements and are created here with `*Link` functions.
# `<button>` creators
@docs ButtonConfig, button
# `<a>` creators
@docs LinkConfig, link
# Config
@docs Size
-}
import Css
import Css.Global
import Html.Styled as Html exposing (..)
import Html.Styled.Attributes as Attributes
import Html.Styled.Events as Events
import Nri.Ui
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1
import Nri.Ui.Icon.V4 as Icon exposing (IconType)
{-| Sizes for the button
-}
type Size
= Small
| Medium
| Large
{-| Config for the button
-}
type alias ButtonConfig msg =
{ label : String
, size : Size
, icon : Maybe IconType
, onClick : msg
}
{-| Creates a `<button>` element
-}
button : ButtonConfig msg -> Html msg
button config =
let
fontSize =
sizeToPx config.size
in
Nri.Ui.styled Html.button
(dataDescriptor "button")
(clickableTextStyles fontSize)
[ Events.onClick config.onClick
]
[ icon fontSize config.icon
, text config.label
]
{-| Config for the link
-}
type alias LinkConfig =
{ label : String
, size : Size
, icon : Maybe IconType
, url : String
}
{-| Creates a `<a>` element
-}
link : LinkConfig -> List (Attribute msg) -> Html msg
link config additionalAttributes =
let
fontSize =
sizeToPx config.size
in
Nri.Ui.styled Html.a
(dataDescriptor "link")
(clickableTextStyles fontSize)
(Attributes.href config.url
:: additionalAttributes
)
[ icon fontSize config.icon
, text config.label
]
clickableTextStyles : Css.Px -> List Css.Style
clickableTextStyles fontSize =
[ 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.backgroundImage Css.none
, Css.textShadow Css.none
, Css.boxShadow Css.none
, Css.border Css.zero
, Css.disabled [ Css.cursor Css.notAllowed ]
, Css.color Colors.azure
, Css.backgroundColor Css.transparent
, Css.fontWeight (Css.int 600)
, Css.textAlign Css.left
, Css.borderStyle Css.none
, Css.textDecoration Css.none
, Css.hover [ Css.textDecoration Css.underline ]
, Css.fontSize fontSize
, Css.padding Css.zero
]
icon : Css.Px -> Maybe IconType -> Html msg
icon fontSize maybeIcon =
case maybeIcon of
Just iconType ->
-- TODO: We should never use an image here, only SVG
Nri.Ui.styled Html.span
(dataDescriptor "icon-holder")
[ Css.height fontSize
, Css.width fontSize
, Css.display Css.inlineBlock
, Css.marginRight (Css.px 5)
]
[]
[ Icon.decorativeIcon iconType ]
Nothing ->
text ""
sizeToPx : Size -> Css.Px
sizeToPx size =
case size of
Small ->
Css.px 15
Medium ->
Css.px 17
Large ->
Css.px 20
dataDescriptor : String -> String
dataDescriptor descriptor =
"clickable-text-v1-" ++ descriptor

View File

@ -1,176 +0,0 @@
module Nri.Ui.ClickableText.V2 exposing
( button
, link
, Size(..)
)
{-|
# Changes from V1
- Removes dependency on Icon that makes versioned assets hard to work with
- Fixes vertical alignment of icons
- Inlines configs to make parsing documentation easier
# About:
ClickableText looks different from Nri.Ui.Button in that it displays without margin or padding.
ClickableText has the suave, traditional look of a "link"!
For accessibility purposes, buttons that perform an action on the current page should be HTML `<button>`
elements and are created here with `*Button` functions. Buttons that take the user to a new page should be
HTML `<a>` elements and are created here with `*Link` functions.
# `<button>` creators
@docs button
# `<a>` creators
@docs link
# Config
@docs Size
-}
import Css
import Html.Styled as Html exposing (..)
import Html.Styled.Attributes as Attributes
import Html.Styled.Events as Events
import Nri.Ui
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1
import Nri.Ui.Svg.V1 as NriSvg exposing (Svg)
{-| Sizes for the button
-}
type Size
= Small
| Medium
| Large
{-| Creates a `<button>` element
-}
button :
{ label : String
, size : Size
, icon : Maybe Svg
, onClick : msg
}
-> Html msg
button config =
Nri.Ui.styled Html.button
(dataDescriptor "button")
clickableTextStyles
[ Events.onClick config.onClick
]
[ viewContent config ]
{-| Creates a `<a>` element
-}
link :
{ label : String
, size : Size
, icon : Maybe Svg
, url : String
}
-> List (Attribute msg)
-> Html msg
link config additionalAttributes =
Nri.Ui.styled Html.a
(dataDescriptor "link")
clickableTextStyles
(Attributes.href config.url :: additionalAttributes)
[ viewContent config ]
viewContent : { a | label : String, size : Size, icon : Maybe Svg } -> Html msg
viewContent config =
let
fontSize =
sizeToPx config.size
in
span [ Attributes.css [ Css.fontSize fontSize ] ]
(case config.icon of
Just icon ->
[ div
[ Attributes.css
[ Css.displayFlex
, Css.alignItems Css.center
, Css.property "line-height" "normal"
]
]
[ div
[ Attributes.css
[ Css.height fontSize
, Css.maxWidth fontSize
, Css.minWidth fontSize -- so it doesn't shrink when the label is long
, case config.size of
Small ->
Css.marginRight (Css.px 3)
Medium ->
Css.marginRight (Css.px 3)
Large ->
Css.marginRight (Css.px 4)
]
]
[ NriSvg.toHtml icon ]
, span [] [ text config.label ]
]
]
Nothing ->
[ text config.label ]
)
clickableTextStyles : List Css.Style
clickableTextStyles =
[ Css.cursor Css.pointer
, Nri.Ui.Fonts.V1.baseFont
, Css.backgroundImage Css.none
, Css.textShadow Css.none
, Css.boxShadow Css.none
, Css.border Css.zero
, Css.disabled [ Css.cursor Css.notAllowed ]
, Css.color Colors.azure
, Css.backgroundColor Css.transparent
, Css.fontWeight (Css.int 600)
, Css.textAlign Css.left
, Css.borderStyle Css.none
, Css.textDecoration Css.none
, Css.hover [ Css.textDecoration Css.underline ]
, Css.padding Css.zero
, Css.display Css.inlineBlock
, Css.verticalAlign Css.textBottom
]
sizeToPx : Size -> Css.Px
sizeToPx size =
case size of
Small ->
Css.px 15
Medium ->
Css.px 17
Large ->
Css.px 20
dataDescriptor : String -> String
dataDescriptor descriptor =
"clickable-text-v2-" ++ descriptor

View File

@ -1,199 +0,0 @@
module Nri.Ui.Heading.V1 exposing
( Heading, heading
, withVisualLevel, VisualLevel(..)
, withDocumentLevel, DocumentLevel(..)
, view
)
{-| Headings with customization options for accessibility.
## Understanding spacing
- All text styles have a specific line-height. This is set so that when text
in the given style is long enough to wrap, the spacing between wrapped lines
looks good.
- No heading styles have padding.
- **Heading styles** do not have margin. It is up to the caller to add
appropriate margin to the layout.
@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

@ -1,213 +0,0 @@
module Nri.Ui.Modal.V2 exposing
( Model
, info
, warning
)
{-| Changes from V1:
- Use Styled Html
@docs Model
@docs info
@docs warning
-}
import Accessibility.Styled as Html exposing (..)
import Accessibility.Styled.Role as Role
import Accessibility.Styled.Widget as Widget
import Css
import Css.Global exposing (Snippet, body, children, descendants, everything, selector)
import Html.Styled
import Html.Styled.Events exposing (onClick)
import Nri.Ui
import Nri.Ui.Colors.Extra
import Nri.Ui.Colors.V1
import Nri.Ui.Fonts.V1 as Fonts
{-|
- `onDismiss`: If `Nothing`, the modal will not be dismissable
- `visibleTitle`: If `False`, the title will still be used for screen readers
- `content`: This will be placed in a `width:100%` div in the main area of the modal
- `footerContent`: The optional items here will be stacked below the main content area and center-aligned.
Commonly you will either give a list of Nri.Ui.Buttons,
or an empty list.
-}
type alias Model msg =
{ title : String
, visibleTitle : Bool
, content : Html msg
, footerContent : List (Html msg)
, onDismiss : Maybe msg
, width : Maybe Int
}
type ModalType
= Info
| Warning
{-| -}
info : Model msg -> Html msg
info =
view Info
{-| -}
warning : Model msg -> Html msg
warning =
view Warning
view : ModalType -> Model msg -> Html msg
view modalType { title, visibleTitle, content, onDismiss, footerContent, width } =
Nri.Ui.styled div
"modal-backdrop-container"
((case modalType of
Info ->
Css.backgroundColor (Nri.Ui.Colors.Extra.withAlpha 0.9 Nri.Ui.Colors.V1.navy)
Warning ->
Css.backgroundColor (Nri.Ui.Colors.Extra.withAlpha 0.9 Nri.Ui.Colors.V1.gray20)
)
:: [ Css.height (Css.vh 100)
, Css.left Css.zero
, Css.overflow Css.hidden
, Css.position Css.fixed
, Css.top Css.zero
, Css.width (Css.pct 100)
, Css.zIndex (Css.int 200)
, Css.displayFlex
, Css.alignItems Css.center
, Css.justifyContent Css.center
]
)
[ Role.dialog
, Widget.label title
, Widget.modal True
]
[ Nri.Ui.styled Html.Styled.div
"modal-click-catcher"
[ Css.bottom Css.zero
, Css.left Css.zero
, Css.position Css.absolute
, Css.right Css.zero
, Css.top Css.zero
]
(case onDismiss of
Nothing ->
[]
Just msg ->
[ onClick msg ]
)
[]
, Nri.Ui.styled div
"modal-container"
[ Css.width (Css.px 600)
, Css.maxHeight <| Css.calc (Css.vh 100) Css.minus (Css.px 100)
, Css.padding4 (Css.px 35) Css.zero (Css.px 25) Css.zero
, Css.margin2 (Css.px 75) Css.auto
, Css.backgroundColor Nri.Ui.Colors.V1.white
, Css.borderRadius (Css.px 20)
, Css.property "box-shadow" "0 1px 10px 0 rgba(0, 0, 0, 0.35)"
, Css.position Css.relative -- required for closeButtonContainer
, Css.displayFlex
, Css.alignItems Css.center
, Css.flexDirection Css.column
, Css.flexWrap Css.noWrap
, Fonts.baseFont
]
[]
[ -- This global <style> node sets overflow to hidden on the body element,
-- thereby preventing the page from scrolling behind the backdrop when the modal is
-- open (and this node is present on the page).
Css.Global.global
[ Css.Global.body
[ Css.overflow Css.hidden ]
]
, if visibleTitle then
viewHeader modalType title
else
text ""
, viewContent modalType content
, viewFooter footerContent
]
]
viewHeader : ModalType -> String -> Html msg
viewHeader modalType title =
Nri.Ui.styled Html.h3
"modal-header"
((case modalType of
Info ->
Css.color Nri.Ui.Colors.V1.navy
Warning ->
Css.color Nri.Ui.Colors.V1.red
)
:: [ Css.fontWeight (Css.int 700)
, Css.lineHeight (Css.px 27)
, Css.margin2 Css.zero (Css.px 65)
, Css.fontSize (Css.px 20)
, Fonts.baseFont
]
)
[]
[ Html.text title
]
viewContent : ModalType -> Html msg -> Html msg
viewContent modalType content =
Nri.Ui.styled div
"modal-content"
[ Css.overflowY Css.scroll
, Css.padding2 (Css.px 30) (Css.px 45)
, Css.width (Css.pct 100)
, Css.minHeight (Css.px 150)
, Css.boxSizing Css.borderBox
]
[]
[ content ]
viewFooter : List (Html msg) -> Html msg
viewFooter footerContent =
case footerContent of
[] ->
Html.text ""
_ ->
Nri.Ui.styled div
"modal-footer"
[ Css.alignItems Css.center
, Css.displayFlex
, Css.flexDirection Css.column
, Css.flexGrow (Css.int 2)
, Css.flexWrap Css.noWrap
, Css.margin4 (Css.px 20) Css.zero Css.zero Css.zero
, Css.width (Css.pct 100)
]
[]
(List.map
(\x ->
Nri.Ui.styled div
"modal-footer-item"
[ Css.margin4 (Css.px 10) Css.zero Css.zero Css.zero
, Css.firstChild
[ Css.margin Css.zero
]
]
[]
[ x ]
)
footerContent
)

View File

@ -1,246 +0,0 @@
module Nri.Ui.Modal.V3 exposing
( Model
, info
, warning
)
{-| Changes from V2:
- Add assets for close button
@docs Model
@docs info
@docs warning
-}
import Accessibility.Styled as Html exposing (..)
import Accessibility.Styled.Role as Role
import Accessibility.Styled.Widget as Widget
import Css
import Css.Global exposing (Snippet, body, children, descendants, everything, selector)
import Html.Styled
import Html.Styled.Events exposing (onClick)
import Nri.Ui
import Nri.Ui.AssetPath exposing (Asset(..))
import Nri.Ui.Colors.Extra
import Nri.Ui.Colors.V1
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Icon.V3 as Icon
{-|
- `onDismiss`: If `Nothing`, the modal will not be dismissable
- `visibleTitle`: If `False`, the title will still be used for screen readers
- `content`: This will be placed in a `width:100%` div in the main area of the modal
- `footerContent`: The optional items here will be stacked below the main content area and center-aligned.
Commonly you will either give a list of Nri.Ui.Buttons,
or an empty list.
-}
type alias Model msg =
{ title : String
, visibleTitle : Bool
, content : Html msg
, footerContent : List (Html msg)
, onDismiss : Maybe msg
, width : Maybe Int
}
type alias Assets r =
{ r | icons_xBlue_svg : Asset }
type ModalType
= Info
| Warning
{-| -}
info : Assets r -> Model msg -> Html msg
info assets =
view assets Info
{-| -}
warning : Assets r -> Model msg -> Html msg
warning assets =
view assets Warning
view : Assets r -> ModalType -> Model msg -> Html msg
view assets modalType { title, visibleTitle, content, onDismiss, footerContent, width } =
Nri.Ui.styled div
"modal-backdrop-container"
((case modalType of
Info ->
Css.backgroundColor (Nri.Ui.Colors.Extra.withAlpha 0.9 Nri.Ui.Colors.V1.navy)
Warning ->
Css.backgroundColor (Nri.Ui.Colors.Extra.withAlpha 0.9 Nri.Ui.Colors.V1.gray20)
)
:: [ Css.height (Css.vh 100)
, Css.left Css.zero
, Css.overflow Css.hidden
, Css.position Css.fixed
, Css.top Css.zero
, Css.width (Css.pct 100)
, Css.zIndex (Css.int 200)
, Css.displayFlex
, Css.alignItems Css.center
, Css.justifyContent Css.center
]
)
[ Role.dialog
, Widget.label title
, Widget.modal True
]
[ Nri.Ui.styled Html.Styled.div
"modal-click-catcher"
[ Css.bottom Css.zero
, Css.left Css.zero
, Css.position Css.absolute
, Css.right Css.zero
, Css.top Css.zero
]
(case onDismiss of
Nothing ->
[]
Just msg ->
[ onClick msg ]
)
[]
, Nri.Ui.styled div
"modal-container"
[ Css.width (Css.px 600)
, Css.maxHeight <| Css.calc (Css.vh 100) Css.minus (Css.px 100)
, Css.padding4 (Css.px 40) Css.zero (Css.px 40) Css.zero
, Css.margin2 (Css.px 75) Css.auto
, Css.backgroundColor Nri.Ui.Colors.V1.white
, Css.borderRadius (Css.px 20)
, Css.property "box-shadow" "0 1px 10px 0 rgba(0, 0, 0, 0.35)"
, Css.position Css.relative -- required for closeButtonContainer
, Css.displayFlex
, Css.alignItems Css.center
, Css.flexDirection Css.column
, Css.flexWrap Css.noWrap
, Fonts.baseFont
]
[]
[ -- This global <style> node sets overflow to hidden on the body element,
-- thereby preventing the page from scrolling behind the backdrop when the modal is
-- open (and this node is present on the page).
Css.Global.global
[ Css.Global.body
[ Css.overflow Css.hidden ]
]
, case onDismiss of
Just msg ->
closeButton assets msg
Nothing ->
text ""
, if visibleTitle then
viewHeader modalType title
else
text ""
, viewContent modalType content
, viewFooter footerContent
]
]
closeButton : Assets r -> msg -> Html msg
closeButton assets msg =
Nri.Ui.styled div
"close-button-container"
[ Css.position Css.absolute
, Css.top Css.zero
, Css.right Css.zero
, Css.padding (Css.px 25)
]
[]
[ Icon.button
{ alt = "Close"
, msg = msg
, icon = Icon.close assets
, disabled = False
, size = Icon.Medium
}
]
viewHeader : ModalType -> String -> Html msg
viewHeader modalType title =
Nri.Ui.styled Html.h3
"modal-header"
((case modalType of
Info ->
Css.color Nri.Ui.Colors.V1.navy
Warning ->
Css.color Nri.Ui.Colors.V1.red
)
:: [ Css.fontWeight (Css.int 700)
, Css.lineHeight (Css.px 27)
, Css.margin2 Css.zero (Css.px 49)
, Css.fontSize (Css.px 20)
, Fonts.baseFont
, Css.textAlign Css.center
]
)
[]
[ Html.text title
]
viewContent : ModalType -> Html msg -> Html msg
viewContent modalType content =
Nri.Ui.styled div
"modal-content"
[ Css.overflowY Css.auto
, Css.padding2 (Css.px 30) (Css.px 40)
, Css.width (Css.pct 100)
, Css.minHeight (Css.px 150)
, Css.boxSizing Css.borderBox
]
[]
[ content ]
viewFooter : List (Html msg) -> Html msg
viewFooter footerContent =
case footerContent of
[] ->
Html.text ""
_ ->
Nri.Ui.styled div
"modal-footer"
[ Css.alignItems Css.center
, Css.displayFlex
, Css.flexDirection Css.column
, Css.flexGrow (Css.int 2)
, Css.flexWrap Css.noWrap
, Css.margin4 (Css.px 20) Css.zero Css.zero Css.zero
, Css.width (Css.pct 100)
]
[]
(List.map
(\x ->
Nri.Ui.styled div
"modal-footer-item"
[ Css.margin4 (Css.px 10) Css.zero Css.zero Css.zero
, Css.firstChild
[ Css.margin Css.zero
]
]
[]
[ x ]
)
footerContent
)

View File

@ -1,270 +0,0 @@
module Nri.Ui.Modal.V4 exposing
( Model, Dismissibility(..)
, info
, warning
)
{-| Changes from V3:
- changed `onDismiss` from `Maybe msg` to `Dismissibility msg`
Changes from V2:
- Add assets for close button
@docs Model, Dismissibility
@docs info
@docs warning
-}
import Accessibility.Styled as Html exposing (..)
import Accessibility.Styled.Role as Role
import Accessibility.Styled.Widget as Widget
import Css
import Css.Global exposing (Snippet, body, children, descendants, everything, selector)
import Html.Styled
import Html.Styled.Events exposing (onClick)
import Nri.Ui
import Nri.Ui.AssetPath exposing (Asset(..))
import Nri.Ui.Colors.Extra
import Nri.Ui.Colors.V1
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Icon.V3 as Icon
{-|
- `onDismiss`: If `Nothing`, the modal will not be dismissable
- `visibleTitle`: If `False`, the title will still be used for screen readers
- `content`: This will be placed in a `width:100%` div in the main area of the modal
- `footerContent`: The optional items here will be stacked below the main content area and center-aligned.
Commonly you will either give a list of Nri.Ui.Buttons,
or an empty list.
-}
type alias Model msg =
{ title : String
, visibleTitle : Bool
, content : Html msg
, footerContent : List (Html msg)
, onDismiss : Dismissibility msg
, width : Maybe Int
}
{-|
- `NotDismissible`
- `WithBackgroundOrX`: user can dismiss by clicking "X" (top right) or modal backdrop
- `WithOnlyX`: user can dismiss by clicking "X" (top right)
-}
type Dismissibility msg
= NotDismissible
| WithBackgroundOrX msg
| WithOnlyX msg
type alias Assets r =
{ r | icons_xBlue_svg : Asset }
type ModalType
= Info
| Warning
{-| -}
info : Assets r -> Model msg -> Html msg
info assets =
view assets Info
{-| -}
warning : Assets r -> Model msg -> Html msg
warning assets =
view assets Warning
view : Assets r -> ModalType -> Model msg -> Html msg
view assets modalType { title, visibleTitle, content, onDismiss, footerContent, width } =
Nri.Ui.styled div
"modal-backdrop-container"
((case modalType of
Info ->
Css.backgroundColor (Nri.Ui.Colors.Extra.withAlpha 0.9 Nri.Ui.Colors.V1.navy)
Warning ->
Css.backgroundColor (Nri.Ui.Colors.Extra.withAlpha 0.9 Nri.Ui.Colors.V1.gray20)
)
:: [ Css.height (Css.vh 100)
, Css.left Css.zero
, Css.overflow Css.hidden
, Css.position Css.fixed
, Css.top Css.zero
, Css.width (Css.pct 100)
, Css.zIndex (Css.int 200)
, Css.displayFlex
, Css.alignItems Css.center
, Css.justifyContent Css.center
]
)
[ Role.dialog
, Widget.label title
, Widget.modal True
]
[ Nri.Ui.styled Html.Styled.div
"modal-click-catcher"
[ Css.bottom Css.zero
, Css.left Css.zero
, Css.position Css.absolute
, Css.right Css.zero
, Css.top Css.zero
]
(case onDismiss of
NotDismissible ->
[]
WithBackgroundOrX msg ->
[ onClick msg ]
WithOnlyX msg ->
[]
)
[]
, Nri.Ui.styled div
"modal-container"
[ Css.width (Css.px 600)
, Css.maxHeight <| Css.calc (Css.vh 100) Css.minus (Css.px 100)
, Css.padding4 (Css.px 40) Css.zero (Css.px 40) Css.zero
, Css.margin2 (Css.px 75) Css.auto
, Css.backgroundColor Nri.Ui.Colors.V1.white
, Css.borderRadius (Css.px 20)
, Css.property "box-shadow" "0 1px 10px 0 rgba(0, 0, 0, 0.35)"
, Css.position Css.relative -- required for closeButtonContainer
, Css.displayFlex
, Css.alignItems Css.center
, Css.flexDirection Css.column
, Css.flexWrap Css.noWrap
, Fonts.baseFont
]
[]
[ -- This global <style> node sets overflow to hidden on the body element,
-- thereby preventing the page from scrolling behind the backdrop when the modal is
-- open (and this node is present on the page).
Css.Global.global
[ Css.Global.body
[ Css.overflow Css.hidden ]
]
, case onDismiss of
NotDismissible ->
text ""
WithBackgroundOrX msg ->
closeButton assets msg
WithOnlyX msg ->
closeButton assets msg
, if visibleTitle then
viewHeader modalType title
else
text ""
, viewContent modalType content
, viewFooter footerContent
]
]
closeButton : Assets r -> msg -> Html msg
closeButton assets msg =
Nri.Ui.styled div
"close-button-container"
[ Css.position Css.absolute
, Css.top Css.zero
, Css.right Css.zero
, Css.padding (Css.px 25)
]
[]
[ Icon.button
{ alt = "Close"
, msg = msg
, icon = Icon.close assets
, disabled = False
, size = Icon.Medium
}
]
viewHeader : ModalType -> String -> Html msg
viewHeader modalType title =
Nri.Ui.styled Html.h3
"modal-header"
((case modalType of
Info ->
Css.color Nri.Ui.Colors.V1.navy
Warning ->
Css.color Nri.Ui.Colors.V1.red
)
:: [ Css.fontWeight (Css.int 700)
, Css.lineHeight (Css.px 27)
, Css.margin2 Css.zero (Css.px 49)
, Css.fontSize (Css.px 20)
, Fonts.baseFont
, Css.textAlign Css.center
]
)
[]
[ Html.text title
]
viewContent : ModalType -> Html msg -> Html msg
viewContent modalType content =
Nri.Ui.styled div
"modal-content"
[ Css.overflowY Css.auto
, Css.padding2 (Css.px 30) (Css.px 40)
, Css.width (Css.pct 100)
, Css.minHeight (Css.px 150)
, Css.boxSizing Css.borderBox
]
[]
[ content ]
viewFooter : List (Html msg) -> Html msg
viewFooter footerContent =
case footerContent of
[] ->
Html.text ""
_ ->
Nri.Ui.styled div
"modal-footer"
[ Css.alignItems Css.center
, Css.displayFlex
, Css.flexDirection Css.column
, Css.flexGrow (Css.int 2)
, Css.flexWrap Css.noWrap
, Css.margin4 (Css.px 20) Css.zero Css.zero Css.zero
, Css.width (Css.pct 100)
, Css.minHeight (Css.px 125) -- so the footer doesn't compress on Safari
]
[]
(List.map
(\x ->
Nri.Ui.styled div
"modal-footer-item"
[ Css.margin4 (Css.px 10) Css.zero Css.zero Css.zero
, Css.firstChild
[ Css.margin Css.zero
]
]
[]
[ x ]
)
footerContent
)

View File

@ -1,421 +0,0 @@
module Nri.Ui.Modal.V5 exposing
( Model, init
, Msg, update, subscriptions
, info, warning, FocusableElementAttrs
, viewContent, viewFooter
, launchButton, closeButton
, primaryButton, secondaryButton, dangerButton
)
{-| Changes from V4:
- Remove dependence on Assets
- Adds keyboard support (escape key to exit, tabs contained within the modal)
These changes have required major API changes. Be sure to wire up subscriptions!
import Html.Styled exposing (..)
import Nri.Ui.Modal.V5 as Modal
view : Modal.State -> Html Msg
view state =
Modal.info
{ title = { title = "Modal Header", visibleTitle = True }
, wrapMsg = ModalMsg
, content =
\{ onlyFocusableElement } ->
div []
[ Modal.viewContent [ text "Content goes here!" ]
, Modal.viewFooter
[ Modal.primaryButton DoSomething "Continue" onlyFocusableElement
, text "`onlyFocusableElement` will trap the focus on the 'Continue' button."
]
]
}
state
subscriptions : Modal.State -> Sub Msg
subscriptions state =
Modal.subscriptions state
## State and updates
@docs Model, init
@docs Msg, update, subscriptions
## Views
### Modals
@docs info, warning, FocusableElementAttrs
### View containers
@docs viewContent, viewFooter
### Buttons
@docs launchButton, closeButton
@docs primaryButton, secondaryButton, dangerButton
-}
import Accessibility.Modal as Modal
import Accessibility.Style
import Accessibility.Styled as Html exposing (..)
import Accessibility.Styled.Style
import Accessibility.Styled.Widget as Widget
import Color
import Css
import Css.Global
import Html as Root
import Html.Attributes exposing (style)
import Html.Styled.Attributes exposing (css)
import Html.Styled.Events exposing (onClick)
import Nri.Ui
import Nri.Ui.Colors.Extra
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.SpriteSheet
import Nri.Ui.Svg.V1
{-| -}
type alias Model =
Modal.Model
{-| -}
init : Model
init =
Modal.init
{-| -}
type alias Msg =
Modal.Msg
{-| Include the subscription if you want the modal to dismiss on `Esc`.
-}
subscriptions : Model -> Sub Msg
subscriptions =
Modal.subscriptions
{-| -}
update : { dismissOnEscAndOverlayClick : Bool } -> Msg -> Model -> ( Model, Cmd Msg )
update config msg model =
Modal.update config msg model
{-| -}
type alias FocusableElementAttrs msg =
{ onlyFocusableElement : List (Root.Attribute msg)
, firstFocusableElement : List (Root.Attribute msg)
, lastFocusableElement : List (Root.Attribute msg)
}
{-| -}
info :
{ title : { visibleTitle : Bool, title : String }
, content : FocusableElementAttrs msg -> Html msg
, wrapMsg : Msg -> msg
}
-> Model
-> Html msg
info config model =
Modal.view
{ overlayColor = toOverlayColor Colors.navy
, wrapMsg = config.wrapMsg
, modalAttributes = modalStyles
, title = viewTitle Colors.navy config.title
, content = config.content >> toUnstyled
}
model
|> fromUnstyled
{-| -}
warning :
{ title : { visibleTitle : Bool, title : String }
, content : FocusableElementAttrs msg -> Html msg
, wrapMsg : Msg -> msg
}
-> Model
-> Html msg
warning config model =
Modal.view
{ overlayColor = toOverlayColor Colors.gray20
, wrapMsg = config.wrapMsg
, modalAttributes = modalStyles
, title = viewTitle Colors.red config.title
, content = config.content >> toUnstyled
}
model
|> fromUnstyled
toOverlayColor : Css.Color -> String
toOverlayColor color =
toCssString (Nri.Ui.Colors.Extra.withAlpha 0.9 color)
modalStyles : List (Root.Attribute Never)
modalStyles =
[ style "width" "600px"
, style "max-height" "calc(100vh - 100px)"
, style "padding" "40px 0 40px 0"
, style "margin" "75px auto"
, style "background-color" (toCssString Colors.white)
, style "border-radius" "20px"
, style "box-shadow" "0 1px 10px 0 rgba(0, 0, 0, 0.35)"
, style "position" "relative" -- required for closeButtonContainer
]
{-| -}
viewTitle : Css.Color -> { visibleTitle : Bool, title : String } -> ( String, List (Root.Attribute Never) )
viewTitle color { visibleTitle, title } =
( title
, if visibleTitle then
[ style "font-weight" "700"
, style "line-height" "27px"
, style "margin" "0 49px"
, style "font-size" "20px"
, style "text-align" "center"
, style "color" (toCssString color)
]
else
Accessibility.Style.invisible
)
toCssString : Css.Color -> String
toCssString =
Nri.Ui.Colors.Extra.fromCssColor >> Color.toRGBString
{-| -}
viewContent : List (Html msg) -> Html msg
viewContent =
Nri.Ui.styled div
"modal-content"
[ Css.overflowY Css.auto
, Css.padding2 (Css.px 30) (Css.px 40)
, Css.width (Css.pct 100)
, Css.minHeight (Css.px 150)
, Css.boxSizing Css.borderBox
]
[]
{-| -}
viewFooter : List (Html msg) -> Html msg
viewFooter =
Nri.Ui.styled div
"modal-footer"
[ Css.alignItems Css.center
, Css.displayFlex
, Css.flexDirection Css.column
, Css.flexGrow (Css.int 2)
, Css.flexWrap Css.noWrap
, Css.margin4 (Css.px 20) Css.zero Css.zero Css.zero
, Css.width (Css.pct 100)
]
[]
--BUTTONS
{-| -}
launchButton : (Msg -> msg) -> List Css.Style -> String -> Html msg
launchButton wrapMsg styles label =
button
(css styles
:: List.map Html.Styled.Attributes.fromUnstyled
(Modal.openOnClick wrapMsg (String.replace " " "-" label))
)
[ text label ]
{-| -}
closeButton : (Msg -> msg) -> List (Root.Attribute msg) -> Html msg
closeButton wrapMsg focusableElementAttrs =
Nri.Ui.styled button
"close-button-container"
[ Css.position Css.absolute
, Css.top Css.zero
, Css.right Css.zero
, Css.padding (Css.px 25)
, Css.borderWidth Css.zero
, Css.width (Css.px 75)
, Css.backgroundColor Css.transparent
, Css.cursor Css.pointer
, Css.color Colors.azure
, Css.hover [ Css.color Colors.azureDark ]
, Css.property "transition" "color 0.1s"
]
(Widget.label "Close modal"
:: Html.Styled.Attributes.map wrapMsg (onClick Modal.close)
:: List.map Html.Styled.Attributes.fromUnstyled focusableElementAttrs
)
[ Nri.Ui.Svg.V1.toHtml Nri.Ui.SpriteSheet.xSvg
]
{-| -}
primaryButton : msg -> String -> List (Root.Attribute msg) -> Html msg
primaryButton msg label focusableElementAttrs =
Nri.Ui.styled button
"modal__primary-button"
[ buttonStyle, colorStyle PrimaryColors, sizeStyle ]
(onClick msg :: List.map Html.Styled.Attributes.fromUnstyled focusableElementAttrs)
[ text label ]
{-| -}
secondaryButton : msg -> String -> List (Root.Attribute msg) -> Html msg
secondaryButton msg label focusableElementAttrs =
Nri.Ui.styled button
"modal__secondary-button"
[ buttonStyle
, colorStyle SecondaryColors
, Css.fontSize (Css.px 20)
, Css.marginTop (Css.px 30)
]
(onClick msg :: List.map Html.Styled.Attributes.fromUnstyled focusableElementAttrs)
[ text label ]
{-| -}
dangerButton : msg -> String -> List (Root.Attribute msg) -> Html msg
dangerButton msg label focusableElementAttrs =
Nri.Ui.styled button
"modal__warning-button"
[ buttonStyle, colorStyle DangerColors, sizeStyle ]
(onClick msg :: List.map Html.Styled.Attributes.fromUnstyled focusableElementAttrs)
[ text label ]
buttonStyle : Css.Style
buttonStyle =
Css.batch
[ Css.cursor Css.pointer
, -- Specifying the font can and should go away after bootstrap is removed from application.css
Fonts.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.display Css.inlineFlex
, Css.alignItems Css.center
, Css.justifyContent Css.center
]
type ColorPalette
= PrimaryColors
| SecondaryColors
| DangerColors
colorStyle : ColorPalette -> Css.Style
colorStyle colorPalette =
let
config =
case colorPalette of
PrimaryColors ->
{ background = Colors.azure
, hover = Colors.azureDark
, text = Colors.white
, shadow = Colors.azureDark
}
SecondaryColors ->
{ background = Colors.white
, hover = Colors.white
, text = Colors.azure
, shadow = Colors.white
}
DangerColors ->
{ background = Colors.red
, hover = Colors.redDark
, text = Colors.white
, shadow = Colors.redDark
}
in
Css.batch
[ Css.color config.text
, Css.backgroundColor config.background
, Css.fontWeight (Css.int 700)
, Css.textAlign Css.center
, Css.borderStyle Css.none
, Css.borderBottomStyle Css.solid
, Css.borderBottomColor config.shadow
, Css.fontStyle Css.normal
, Css.hover
[ Css.color config.text
, Css.backgroundColor config.hover
, Css.disabled [ Css.backgroundColor config.background ]
]
, Css.visited [ Css.color config.text ]
]
sizeStyle : Css.Style
sizeStyle =
let
config =
{ 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 =
[ Css.paddingLeft (Css.px 16)
, Css.paddingRight (Css.px 16)
, Css.minWidth (Css.px 230)
]
lineHeightPx =
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
]

View File

@ -1,302 +0,0 @@
module Nri.Ui.Modal.V6 exposing
( Model, init
, Msg, update, subscriptions
, open, close
, info, warning, FocusableElementAttrs
, viewContent, viewFooter
, closeButton
)
{-| Changes from V5:
- Removes button helpers, now that we can use Nri.Ui.Button.V9 directly
These changes have required major API changes. Be sure to wire up subscriptions!
import Html.Styled exposing (..)
import Nri.Ui.Button.V10 as Button
import Nri.Ui.Modal.V6 as Modal
type Msg
= ModalMsg Modal.Msg
| DoSomthing
view : Modal.Model -> Html Msg
view state =
Modal.info
{ title = "Modal Header"
, visibleTitle = True
, wrapMsg = ModalMsg
, content =
\{ onlyFocusableElement } ->
div []
[ Modal.viewContent [ text "Content goes here!" ]
, Modal.viewFooter
[ Button.button "Continue"
[ Button.primary
, Button.onClick DoSomthing
, Button.custom onlyFocusableElement
]
, text "`onlyFocusableElement` will trap the focus on the 'Continue' button."
]
]
}
state
subscriptions : Modal.Model -> Sub Msg
subscriptions state =
Modal.subscriptions state
view init
--> text "" -- a closed modal
## State and updates
@docs Model, init
@docs Msg, update, subscriptions
@docs open, close
## Views
### Modals
@docs info, warning, FocusableElementAttrs
### View containers
@docs viewContent, viewFooter
## X icon
@docs closeButton
-}
import Accessibility.Modal as Modal
import Accessibility.Style
import Accessibility.Styled as Html exposing (..)
import Accessibility.Styled.Style
import Accessibility.Styled.Widget as Widget
import Color
import Color.Transparent
import Css
import Css.Global
import Html as Root
import Html.Attributes exposing (style)
import Html.Styled.Attributes exposing (css)
import Html.Styled.Events exposing (onClick)
import Nri.Ui
import Nri.Ui.Colors.Extra
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.SpriteSheet
import Nri.Ui.Svg.V1
{-| -}
type alias Model =
Modal.Model
{-| -}
init : Model
init =
Modal.init
{-| -}
type alias Msg =
Modal.Msg
{-| Include the subscription if you want the modal to dismiss on `Esc`.
-}
subscriptions : Model -> Sub Msg
subscriptions =
Modal.subscriptions
{-| -}
update : { dismissOnEscAndOverlayClick : Bool } -> Msg -> Model -> ( Model, Cmd Msg )
update config msg model =
Modal.update config msg model
{-| -}
close : Msg
close =
Modal.close
{-| Pass the id of the element that focus should return to when the modal closes.
-}
open : String -> Msg
open =
Modal.open
{-| -}
type alias FocusableElementAttrs msg =
{ onlyFocusableElement : List (Attribute msg)
, firstFocusableElement : List (Attribute msg)
, lastFocusableElement : List (Attribute msg)
}
{-| -}
info :
{ visibleTitle : Bool
, title : String
, content : FocusableElementAttrs msg -> Html msg
, wrapMsg : Msg -> msg
}
-> Model
-> Html msg
info config model =
view { overlayColor = Colors.navy, titleColor = Colors.navy } config model
{-| -}
warning :
{ visibleTitle : Bool
, title : String
, content : FocusableElementAttrs msg -> Html msg
, wrapMsg : Msg -> msg
}
-> Model
-> Html msg
warning config model =
view { overlayColor = Colors.gray20, titleColor = Colors.red } config model
view :
{ overlayColor : Css.Color, titleColor : Css.Color }
->
{ visibleTitle : Bool
, title : String
, content : FocusableElementAttrs msg -> Html msg
, wrapMsg : Msg -> msg
}
-> Model
-> Html msg
view { overlayColor, titleColor } config model =
Modal.view
{ overlayColor = toOverlayColor overlayColor
, wrapMsg = config.wrapMsg
, modalAttributes = modalStyles
, title = viewTitle titleColor { title = config.title, visibleTitle = config.visibleTitle }
, content =
\{ onlyFocusableElement, firstFocusableElement, lastFocusableElement } ->
{ onlyFocusableElement = List.map Html.Styled.Attributes.fromUnstyled onlyFocusableElement
, firstFocusableElement = List.map Html.Styled.Attributes.fromUnstyled firstFocusableElement
, lastFocusableElement = List.map Html.Styled.Attributes.fromUnstyled lastFocusableElement
}
|> config.content
|> toUnstyled
}
model
|> fromUnstyled
toOverlayColor : Css.Color -> String
toOverlayColor color =
color
|> Nri.Ui.Colors.Extra.fromCssColor
|> Color.Transparent.fromColor (Color.Transparent.customOpacity 0.9)
|> Color.Transparent.toRGBAString
modalStyles : List (Root.Attribute Never)
modalStyles =
[ style "width" "600px"
, style "max-height" "calc(100vh - 100px)"
, style "padding" "40px 0 40px 0"
, style "margin" "75px auto"
, style "background-color" ((Color.toRGBString << Nri.Ui.Colors.Extra.fromCssColor) Colors.white)
, style "border-radius" "20px"
, style "box-shadow" "0 1px 10px 0 rgba(0, 0, 0, 0.35)"
, style "position" "relative" -- required for closeButtonContainer
]
{-| -}
viewTitle : Css.Color -> { visibleTitle : Bool, title : String } -> ( String, List (Root.Attribute Never) )
viewTitle color { visibleTitle, title } =
( title
, if visibleTitle then
[ style "font-weight" "700"
, style "line-height" "27px"
, style "margin" "0 49px"
, style "font-size" "20px"
, style "text-align" "center"
, style "color" ((Color.toRGBString << Nri.Ui.Colors.Extra.fromCssColor) color)
]
else
Accessibility.Style.invisible
)
{-| -}
viewContent : List (Html msg) -> Html msg
viewContent =
Nri.Ui.styled div
"modal-content"
[ Css.overflowY Css.auto
, Css.padding2 (Css.px 30) (Css.px 40)
, Css.width (Css.pct 100)
, Css.minHeight (Css.px 150)
, Css.boxSizing Css.borderBox
]
[]
{-| -}
viewFooter : List (Html msg) -> Html msg
viewFooter =
Nri.Ui.styled div
"modal-footer"
[ Css.alignItems Css.center
, Css.displayFlex
, Css.flexDirection Css.column
, Css.flexGrow (Css.int 2)
, Css.flexWrap Css.noWrap
, Css.margin4 (Css.px 20) Css.zero Css.zero Css.zero
, Css.width (Css.pct 100)
]
[]
--BUTTONS
{-| -}
closeButton : (Msg -> msg) -> List (Attribute msg) -> Html msg
closeButton wrapMsg focusableElementAttrs =
Nri.Ui.styled button
"close-button-container"
[ Css.position Css.absolute
, Css.top Css.zero
, Css.right Css.zero
, Css.padding (Css.px 25)
, Css.borderWidth Css.zero
, Css.width (Css.px 75)
, Css.backgroundColor Css.transparent
, Css.cursor Css.pointer
, Css.color Colors.azure
, Css.hover [ Css.color Colors.azureDark ]
, Css.property "transition" "color 0.1s"
]
(Widget.label "Close modal"
:: Html.Styled.Attributes.map wrapMsg (onClick Modal.close)
:: focusableElementAttrs
)
[ Nri.Ui.Svg.V1.toHtml Nri.Ui.SpriteSheet.xSvg
]

View File

@ -1,352 +0,0 @@
module Nri.Ui.Modal.V7 exposing
( Model, init
, Msg, update, subscriptions
, open, close
, info, warning
, viewContent, viewFooter
, Attribute
, multipleFocusableElementView, onlyFocusableElementView
, autofocusOnLastElement
, closeButton
)
{-| Changes from V6:
- Modal starts a new stacking context, to prevent non-normal-flow elements from showing through the backdrop
- Scrollable content shows a shadow
```
import Html.Styled exposing (..)
import Nri.Ui.Button.V10 as Button
import Nri.Ui.Modal.V7 as Modal
type Msg
= ModalMsg Modal.Msg
| DoSomthing
view : Modal.Model -> Html Msg
view state =
Modal.info
{ title = "Modal Header"
, visibleTitle = True
, wrapMsg = ModalMsg
}
[ Modal.onlyFocusableElementView
(\{ onlyFocusableElement } ->
div []
[ Modal.viewContent [ text "Content goes here!" ]
, Modal.viewFooter
[ Button.button "Continue"
[ Button.primary
, Button.onClick DoSomthing
, Button.custom onlyFocusableElement
]
, text "`onlyFocusableElement` will trap the focus on the 'Continue' button."
]
]
)
]
state
subscriptions : Modal.Model -> Sub Msg
subscriptions state =
Modal.subscriptions state
view init
--> text "" -- a closed modal
```
## State and updates
@docs Model, init
@docs Msg, update, subscriptions
@docs open, close
## Views
### Modals
@docs info, warning
### View containers
@docs viewContent, viewFooter
### Attributes
@docs Attribute
@docs multipleFocusableElementView, onlyFocusableElementView
@docs autofocusOnLastElement
## X icon
@docs closeButton
-}
import Accessibility.Modal.Copy as Modal
import Accessibility.Style
import Accessibility.Styled as Html exposing (..)
import Accessibility.Styled.Widget as Widget
import Color
import Color.Transparent
import Css
import Css.Global
import Html as Root
import Html.Attributes exposing (style)
import Html.Styled.Attributes as Attributes exposing (css)
import Html.Styled.Events exposing (onClick)
import Nri.Ui
import Nri.Ui.Colors.Extra
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.SpriteSheet
import Nri.Ui.Svg.V1
{-| -}
type alias Model =
Modal.Model
{-| -}
init : Model
init =
Modal.init
{-| -}
type alias Msg =
Modal.Msg
{-| Include the subscription if you want the modal to dismiss on `Esc`.
-}
subscriptions : Model -> Sub Msg
subscriptions =
Modal.subscriptions
{-| -}
update : { dismissOnEscAndOverlayClick : Bool } -> Msg -> Model -> ( Model, Cmd Msg )
update config msg model =
Modal.update config msg model
{-| -}
close : Msg
close =
Modal.close
{-| Pass the id of the element that focus should return to when the modal closes.
-}
open : String -> Msg
open =
Modal.open
{-| -}
info :
{ visibleTitle : Bool
, title : String
, wrapMsg : Msg -> msg
}
-> List (Modal.Attribute msg)
-> Model
-> Html msg
info config model =
view { overlayColor = Colors.navy, titleColor = Colors.navy } config model
{-| -}
warning :
{ visibleTitle : Bool
, title : String
, wrapMsg : Msg -> msg
}
-> List (Modal.Attribute msg)
-> Model
-> Html msg
warning config model =
view { overlayColor = Colors.gray20, titleColor = Colors.red } config model
{-| -}
type alias Attribute msg =
Modal.Attribute msg
{-| -}
autofocusOnLastElement : Modal.Attribute msg
autofocusOnLastElement =
Modal.autofocusOnLastElement
{-| -}
multipleFocusableElementView :
({ firstFocusableElement : List (Html.Attribute msg)
, lastFocusableElement : List (Html.Attribute msg)
, autofocusElement : Html.Attribute msg
}
-> Html msg
)
-> Modal.Attribute msg
multipleFocusableElementView =
Modal.multipleFocusableElementView
{-| -}
onlyFocusableElementView : (List (Html.Attribute msg) -> Html msg) -> Modal.Attribute msg
onlyFocusableElementView =
Modal.onlyFocusableElementView
view :
{ overlayColor : Css.Color, titleColor : Css.Color }
->
{ visibleTitle : Bool
, title : String
, wrapMsg : Msg -> msg
}
-> List (Modal.Attribute msg)
-> Model
-> Html msg
view { overlayColor, titleColor } config attributes model =
Modal.view config.wrapMsg
config.title
([ Modal.overlayColor (Nri.Ui.Colors.Extra.withAlpha 0.9 overlayColor)
, Modal.titleStyles
(if config.visibleTitle then
titleStyles titleColor
else
invisibleTitleStyles
)
, Modal.custom modalStyles
]
++ attributes
)
model
|> List.singleton
|> div [ css [ Css.position Css.relative, Css.zIndex (Css.int 1) ] ]
modalStyles : List Css.Style
modalStyles =
[ Css.property "width" "600px"
, Css.property "padding" "40px 0 40px 0"
, Css.property "margin" "75px auto"
, Css.property "background-color" ((Color.toRGBString << Nri.Ui.Colors.Extra.fromCssColor) Colors.white)
, Css.property "border-radius" "20px"
, Css.property "box-shadow" "0 1px 10px 0 rgba(0, 0, 0, 0.35)"
, Css.property "position" "relative" -- required for closeButtonContainer
]
titleStyles : Css.Color -> List Css.Style
titleStyles color =
[ Fonts.baseFont
, Css.property "font-weight" "700"
, Css.property "line-height" "27px"
, Css.property "margin" "0 49px"
, Css.property "font-size" "20px"
, Css.property "text-align" "center"
, Css.property "color" ((Color.toRGBString << Nri.Ui.Colors.Extra.fromCssColor) color)
]
invisibleTitleStyles : List Css.Style
invisibleTitleStyles =
[ Css.property "property" "clip rect(1px, 1px, 1px, 1px)"
, Css.property "position" "absolute"
, Css.property "height" "1px"
, Css.property "width" "1px"
, Css.property "overflow" "hidden"
, Css.property "margin" "-1px"
, Css.property "padding" "0"
, Css.property "border" "0"
]
{-| -}
viewContent : List (Html msg) -> Html msg
viewContent =
Nri.Ui.styled div
"modal-content"
[ Css.overflowY Css.auto
, Css.minHeight (Css.px 150)
, Css.maxHeight (Css.calc (Css.vh 100) Css.minus (Css.px 360))
, Css.padding2 (Css.px 30) (Css.px 40)
, Css.width (Css.pct 100)
, Css.boxSizing Css.borderBox
-- Shadows for indicating that the content is scrollable
, Css.property "background"
"""
/* TOP shadow */
top linear-gradient(to top, rgb(255, 255, 255), rgb(255, 255, 255)) local,
top linear-gradient(to top, rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.15)) scroll,
/* BOTTOM shadow */
bottom linear-gradient(to bottom, rgb(255, 255, 255), rgb(255, 255, 255)) local,
bottom linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.15)) scroll
"""
, Css.backgroundSize2 (Css.pct 100) (Css.px 10)
, Css.backgroundRepeat Css.noRepeat
]
[]
{-| -}
viewFooter : List (Html msg) -> Html msg
viewFooter =
Nri.Ui.styled div
"modal-footer"
[ Css.alignItems Css.center
, Css.displayFlex
, Css.flexDirection Css.column
, Css.flexGrow (Css.int 2)
, Css.flexWrap Css.noWrap
, Css.margin4 (Css.px 20) Css.zero Css.zero Css.zero
, Css.width (Css.pct 100)
]
[]
--BUTTONS
{-| -}
closeButton : (Msg -> msg) -> List (Html.Attribute msg) -> Html msg
closeButton wrapMsg focusableElementAttrs =
Nri.Ui.styled button
"close-button-container"
[ Css.position Css.absolute
, Css.top Css.zero
, Css.right Css.zero
, Css.padding (Css.px 25)
, Css.borderWidth Css.zero
, Css.width (Css.px 75)
, Css.backgroundColor Css.transparent
, Css.cursor Css.pointer
, Css.color Colors.azure
, Css.hover [ Css.color Colors.azureDark ]
, Css.property "transition" "color 0.1s"
]
(Widget.label "Close modal"
:: Attributes.map wrapMsg (onClick Modal.close)
:: focusableElementAttrs
)
[ Nri.Ui.Svg.V1.toHtml Nri.Ui.SpriteSheet.xSvg
]

View File

@ -1,408 +0,0 @@
module Nri.Ui.Outline.V2 exposing
( segment
, node
, NodeLayout
, NodeConfig
, config
, html
)
{-| A module for rendering outline layouts.
@docs segment
@docs node
@docs NodeLayout
@docs NodeConfig
@docs config
@docs html
-}
import Css
import Css.Global exposing (Snippet, children, descendants, everything, selector)
import Html.Styled as Html exposing (Attribute, Html)
import Html.Styled.Attributes as Attributes
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Effects.V1
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Palette.V1 exposing (Palette)
{-| A wrapper for a node rendered into Html. This type exists to prevent us
from accidentally wrapping a node in a container element before passing it as a
child to another node or segment. Such wrapping would break some of our
styling, which assumes nodes of the same level are sibblings in the Html tree.
-}
type NodeLayout msg
= NodeLayout (Html msg)
{-| A container to draw nodes in.
segment
[ node { config | label = "First Node" }
, node { config | label = "Second Node" }
]
-}
segment : List (NodeLayout msg) -> Html msg
segment children =
Html.styled Html.div
[ segmentStyles ]
[ Attributes.attribute "data-is-segment" "" ]
(List.map unlayout children)
{-| Wrap any html in a NodeLayout so you can use it as sibling content to nodes.
segment
[ node { config | label = "This is a node" }
, html (Html.text "This is some random Html content!")
]
-}
html : Html msg -> NodeLayout msg
html child =
Html.div
[ Attributes.attribute "data-is-custom-html" "" ]
[ child ]
|> NodeLayout
{-| Defines how a node should look.
-}
type alias NodeConfig msg =
-- The node's label.
{ label : Html msg
-- The content of the node (the part in the colored area).
, contents : Html msg
-- Child nodes (and other content) to be placed below the content.
, children : List (NodeLayout msg)
-- The node is selected. Draw a selection shadow around it.
, selected : Bool
-- The node is ghosted. Fade it out.
, ghosted : Bool
-- Addition attributes to be set on the top level node element.
, attrs : List (Html.Attribute msg)
}
{-| A default node configuration, allowing you to only set the properties you care about.
node { config | label = "Claim" }
-}
config : NodeConfig msg
config =
{ label = Html.text ""
, contents = Html.text ""
, children = []
, selected = False
, ghosted = False
, attrs = []
}
{-| Draw a node of an outline structure. You can draw other nodes inside it and
connecting lines will appear.
node { config | label = "Claim" }
-}
node : NodeConfig msg -> NodeLayout msg
node { label, contents, children, selected, attrs, ghosted } =
NodeLayout <|
-- We use a custom Html tag name here, to ensure we can find the first and
-- last node in a last-of-type selector.
Html.styled (Html.node "outline-node")
[ nodeStyles
, if ghosted then
ghostedNodeStyles
else
Css.batch []
]
(Attributes.attribute "data-is-node" "" :: attrs)
[ Html.styled Html.div
[ innerNodeStyles
, if selected then
selectedNodeStyles
else
Css.batch []
]
[ Attributes.attribute "data-is-inner-node" "" ]
(Html.styled Html.div [ labelStyles ] [ Attributes.attribute "data-is-label" "" ] [ label ]
:: Html.styled Html.div [ contentsStyles ] [ Attributes.attribute "data-is-contents" "" ] [ contents ]
:: List.map unlayout children
)
]
unlayout : NodeLayout msg -> Html msg
unlayout (NodeLayout html_) =
html_
type Style
= Segment
| Node
| InnerNode
| SelectedNode
| GhostedNode
| Label
| Contents
| CustomHtml
labelHeight : Float
labelHeight =
35
segmentStyles : Css.Style
segmentStyles =
Css.batch
[ Css.position Css.relative
, Css.zIndex (Css.int 0)
-- The overflow property cuts of connecting lines extending from
-- top level nodes.
, Css.overflow Css.auto
]
nodeStyles : Css.Style
nodeStyles =
Css.batch
-- The node's relative positioning allows the connecting line to
-- point upward relative from the node's bounding box.
[ Css.position Css.relative
, Css.display Css.block
-- This selects all nodes on a level but the first.
, Css.Global.generalSiblings
[ Css.Global.selector "[data-is-node]"
-- Add some spacing between nodes of the same level.
[ Css.marginTop (Css.px 20)
, Css.before
[ Css.property "content" "''"
, Css.borderLeft2 (Css.px 1) Css.solid
, Css.batch lineStyles
]
]
]
-- Child nodes have a connecting line and are indented.
, Css.Global.descendants
[ Css.Global.selector "[data-is-node]"
[ Css.marginTop (Css.px 20)
, Css.before
-- Draw the connect line. It is like an antenna pointing
-- upward in the direction of the parent node.
[ Css.property "content" "''"
, Css.borderLeft2 (Css.px 1) Css.solid
, Css.borderBottom2 (Css.px 1) Css.solid
, Css.batch lineStyles
]
, Css.Global.children
-- Indent this node relative to the parent.
[ Css.Global.selector "[data-is-inner-node]"
[ Css.marginLeft (Css.px 50)
]
]
]
, Css.Global.selector "[data-is-custom-html]"
[ Css.marginLeft (Css.px 50)
]
]
-- 1. Root level nodes are connected with one another using straight lines,
-- so they are never curved.
--
-- Root Node 1
-- │
-- Root Node 2
--
-- 2. Second level nodes have a curved line when they are the last node of
-- their level, and if their parent is the last root node. If either
-- case is not true, they are connected to a straight line that
-- continues beneath then. This is illustrated in the diagram below. As
-- you can see only 'Child Node 3' should have a curved connecting line.
--
-- Root Node 1
-- │
-- ├─ Child Node 1
-- │
-- Root Node 2
-- │
-- ├─ Child Node 2
-- │
-- ╰─ Child Node 3
--
, Css.lastOfType
[ Css.Global.descendants
[ Css.Global.selector "[data-is-node]"
[ Css.lastOfType
[ Css.before
[ Css.borderRadius (Css.px 8)
]
]
]
]
]
-- 3. Third and lower level nodes always have a curved line when they are
-- the last node on their level. This is illustrated in the diagram
-- below. Sub-Child Node 2 has a curved connecting line, even though it's
-- parent node is not the last node on its level.
--
-- Root Node
-- │
-- ├─ Child Node 1
-- │ │
-- │ ├─ Sub-Child Node 1
-- │ │
-- │ ╰─ Sub-Child Node 2
-- │
-- ╰─ Child Node 2
--
, Css.Global.descendants
[ Css.Global.selector "[data-is-node]"
[ Css.Global.descendants
[ Css.Global.selector "[data-is-node]"
[ Css.lastOfType
[ Css.before
[ Css.borderRadius (Css.px 8)
]
]
]
]
]
]
]
innerNodeStyles : Css.Style
innerNodeStyles =
Css.batch
[ Css.overflow Css.auto
-- The position and zIndex create a new stacking context. Connecting
-- lines in child nodes of this one will be drawn in this context.
, Css.position Css.relative
, Css.zIndex (Css.int 0)
-- Recursively assign color styles to the different nested levels of
-- the outline structure.
, Css.Global.descendants
(colorStyles
[ Nri.Ui.Palette.V1.cornflower
, Nri.Ui.Palette.V1.aqua
, Nri.Ui.Palette.V1.turquoise
, Nri.Ui.Palette.V1.green
]
)
]
ghostedNodeStyles : Css.Style
ghostedNodeStyles =
Css.batch
[ Css.opacity (Css.num 0.5)
, Css.zIndex (Css.int -1)
, Css.position Css.relative
]
labelStyles : Css.Style
labelStyles =
Css.batch
[ Css.border2 (Css.px 1) Css.solid
, Css.padding2 Css.zero (Css.px 15)
, Css.fontSize (Css.px 15)
, Css.borderRadius (Css.px labelHeight)
, Css.lineHeight (Css.px (labelHeight - 3))
, Css.height (Css.px labelHeight)
, Css.backgroundColor Colors.white
, Css.position Css.absolute
, Css.boxSizing Css.borderBox
, Css.top Css.zero
, Css.left Css.zero
, Css.fontSize (Css.px 15)
, Fonts.baseFont
, Css.color Colors.gray20
, Css.fontWeight Css.bold
]
contentsStyles : Css.Style
contentsStyles =
Css.batch
[ Css.borderRadius (Css.px 8)
, Css.marginTop (Css.px (labelHeight / 2))
, Css.marginLeft (Css.px (labelHeight / 2))
, Css.minHeight (Css.px 70)
-- Ensure there's some margin on all sides, so we have the option of
-- drawing a border around selected contents without it being cut of
-- by the surrounding inner node.
, Css.marginRight (Css.px 5)
, Css.marginBottom (Css.px 5)
]
selectedNodeStyles : Css.Style
selectedNodeStyles =
Css.batch
[ Css.Global.children
[ Css.Global.selector "[data-is-contents]"
[ Css.batch Nri.Ui.Effects.V1.selectionShadow
]
]
]
colorStyles : List Palette -> List Snippet
colorStyles palettes =
case palettes of
[] ->
[]
palette :: rest ->
[ Css.Global.selector "[data-is-inner-node]"
[ Css.Global.descendants (colorStyles rest)
]
, Css.Global.selector "[data-is-contents]"
[ Css.backgroundColor palette.background
]
, Css.Global.selector "[data-is-label]"
[ Css.color palette.primary
, Css.borderColor palette.border
]
]
lineStyles : List Css.Style
lineStyles =
[ Css.display Css.block
, Css.borderColor Colors.gray75
, Css.position Css.absolute
, Css.width (Css.px 30)
, Css.left (Css.px 30)
, Css.bottom (Css.calc (Css.pct 100) Css.minus (Css.px (labelHeight / 2)))
-- Ensure the connecting line is long enough. The containing element will
-- cut it to size.
, Css.height (Css.vh 10000)
-- Make the connecting line go beneath the parent node,
-- giving the impression it stops when touching the
-- parent.
, Css.zIndex (Css.int -100)
]

View File

@ -1,201 +0,0 @@
module Nri.Ui.Page.V2 exposing (DefaultPage, broken, blocked, notFound, noPermission)
{-| A styled NRI issue page!
@docs DefaultPage, broken, blocked, notFound, noPermission
-}
import Css exposing (..)
import Html.Styled as Html exposing (Html)
import Html.Styled.Attributes as Attributes
import Nri.Ui.Button.V5 as Button
import Nri.Ui.Text.V2 as Text
{-| The default page information is for the button
which will direct the user back to the main page of
the SPA. Specify it's name and the message which will
navigate to the page.
-}
type alias DefaultPage msg =
{ link : msg
, name : String
}
{-| For the not found page.
-}
notFound : DefaultPage msg -> Html msg
notFound defaultPage =
view
{ emoji = "\u{1F914}"
, title = "We couldnt find that!"
, subtitle = "Feel free to browse around, or check out our help center."
, defaultPage = Just defaultPage
, details = Nothing
}
{-| For HTTP errors and other broken states.
-}
broken : DefaultPage msg -> Html msg
broken defaultPage =
view
{ emoji = "😵"
, title = "There was a problem!"
, subtitle = "You can try again, or check out our help center."
, defaultPage = Just defaultPage
, details = Nothing
}
{-| For HTTP errors and other broken states, where link goes to "/".
-}
blocked : String -> Html msg
blocked details =
view
{ emoji = "😵"
, title = "There was a problem!"
, subtitle = "You can try again, or check out our help center."
, defaultPage = Nothing
, details = Just details
}
{-| For pages the user does not have access to.
-}
noPermission : DefaultPage msg -> Html msg
noPermission defaultPage =
view
{ emoji = "\u{1F910}"
, title = "You do not have access to this page!"
, subtitle = "Talk to a site administrator if you believe you should have access to this page."
, defaultPage = Just defaultPage
, details = Nothing
}
-- INTERNAL
type alias Config msg =
{ emoji : String
, title : String
, subtitle : String
, defaultPage : Maybe (DefaultPage msg)
, details : Maybe String
}
view : Config msg -> Html msg
view config =
viewContainer
[ viewEmoji [ Html.text config.emoji ]
, Text.heading [ Html.text config.title ]
, Text.tagline [ Html.text config.subtitle ]
, viewButton
[ viewExit config ]
, viewButton
[ Button.linkExternal
{ label = "Get help!"
, icon = Nothing
, url = "https://noredink.zendesk.com/hc/en-us"
, size = Button.Large
, style = Button.Secondary
, width = Button.WidthExact 260
}
]
, case config.details of
Just details ->
viewButton [ viewDetails details ]
Nothing ->
Html.text ""
]
viewExit : Config msg -> Html msg
viewExit config =
case config.defaultPage of
Just defaultPage ->
Button.button
{ onClick = defaultPage.link
, size = Button.Large
, style = Button.Primary
, width = Button.WidthExact 260
}
{ label = "Return to " ++ defaultPage.name
, state = Button.Enabled
, icon = Nothing
}
Nothing ->
Button.link
{ label = "Return to dashboard"
, icon = Nothing
, url = "/"
, size = Button.Large
, style = Button.Primary
, width = Button.WidthExact 260
}
viewDetails : String -> Html msg
viewDetails detailsForEngineers =
Html.div []
[ Html.styled Html.details
[ margin (px 10)
, maxWidth (px 700)
]
[]
[ Html.styled Html.summary
[ color (hex "8F8F8F") ]
[]
[ Html.text "Details for NoRedInk engineers" ]
, Html.styled Html.code
[ display block
, whiteSpace normal
, overflowWrap breakWord
, textAlign left
, marginTop (px 10)
]
[]
[ Html.text detailsForEngineers ]
]
]
viewContainer : List (Html msg) -> Html msg
viewContainer =
Html.styled Html.div
[ marginTop (px 80)
, displayFlex
, flexDirection column
, alignItems center
]
[ Attributes.attribute "data-page-container" "" ]
viewButton : List (Html msg) -> Html msg
viewButton children =
Html.styled Html.div
[ marginTop (px 15)
]
[]
[ Html.styled Html.div
[ textAlign center ]
[]
children
]
viewEmoji : List (Html msg) -> Html msg
viewEmoji =
Html.styled Html.div
[ fontSize (px 75)
, height (px 98)
, lineHeight (px 98)
]
[]

View File

@ -1,51 +0,0 @@
module Nri.Ui.Pennant.V1 exposing (premiumFlag)
{-| Used for indicating Premium content
@docs premiumFlag
-}
import Accessibility.Styled exposing (Html)
import Svg.Styled as Svg exposing (..)
import Svg.Styled.Attributes as SvgAttr exposing (..)
{-| -}
premiumFlag : Html msg
premiumFlag =
svg
[ version "1.1"
, id "Layer_1"
, SvgAttr.width "25"
, SvgAttr.height "19"
, SvgAttr.viewBox "0 0 25 19"
, SvgAttr.style "margin-left: 8px;"
]
[ Svg.title [] [ text "Premium" ]
, Svg.style [] [ text " .premium-flag-st0{fill:#FEC709;} .premium-flag-st1{fill:#146AFF;} " ]
, g [ id "Page-1" ]
[ g
[ id "icon_x2F_p-mini-pennant-yellow"
, SvgAttr.transform "translate(0.000000, -3.000000)"
]
[ g
[ id "Group"
, SvgAttr.transform "translate(0.000000, 3.750000)"
]
[ polygon
[ id "Fill-2"
, class "premium-flag-st0"
, points "12.7,0 0,0 0,13.8 0,15.8 0,17.5 7.3,17.5 24.8,17.5 19.4,8.1 24.8,0 "
]
[]
, Svg.path
[ id "P"
, class "premium-flag-st1"
, d "M7.5,3.8h4.2c1.1,0,1.9,0.3,2.5,0.8s0.9,1.2,0.9,2.1s-0.3,1.6-0.9,2.1c-0.6,0.5-1.4,0.8-2.5,0.8H9.3 v4.1H7.5V3.8z M11.5,8.1c0.6,0,1.1-0.1,1.4-0.4c0.3-0.3,0.5-0.6,0.5-1.1c0-0.5-0.2-0.9-0.5-1.1c-0.3-0.3-0.8-0.4-1.4-0.4H9.3v3 H11.5z"
]
[]
]
]
]
]

View File

@ -1,113 +0,0 @@
module Nri.Ui.PremiumCheckbox.V4 exposing (PremiumConfig, premium, Pennant(..))
{-|
@docs PremiumConfig, premium, Pennant
-}
import Accessibility.Styled as Html
import Css exposing (..)
import Html.Styled.Attributes as Attributes exposing (css)
import Nri.Ui.AssetPath exposing (Asset(..))
import Nri.Ui.AssetPath.Css
import Nri.Ui.Checkbox.V5 as Checkbox
{-|
- `onChange`: A message for when the user toggles the checkbox
- `onLockedClick`: A message for when the user clicks a checkbox they don't have PremiumLevel for.
If you get this message, you should show an `Nri.Ui.Premium.Model.view`
-}
type alias PremiumConfig msg =
{ label : String
, id : String
, selected : Checkbox.IsSelected
, disabled : Bool
, isLocked : Bool
, pennant : Maybe Pennant
, onChange : Bool -> msg
, onLockedClick : msg
}
{-| Premium is the yellow "P" pennant
PremiumWithWriting is the yellow "P+" pennant
-}
type Pennant
= Premium
| PremiumWithWriting
{-| A checkbox that should be used for premium content
-}
premium : Assets a -> PremiumConfig msg -> Html.Html msg
premium assets config =
Html.div
[ css
[ displayFlex
, alignItems center
]
]
[ Checkbox.viewWithLabel
{ identifier = config.id
, label = config.label
, setterMsg =
if config.isLocked then
\_ -> config.onLockedClick
else
config.onChange
, selected = config.selected
, disabled = config.disabled
, theme =
if config.isLocked then
Checkbox.Locked
else
Checkbox.Square
}
, case config.pennant of
Just pennant ->
Html.div
[ Attributes.class "premium-checkbox-V1__PremiumClass"
, css
[ property "content" "''"
, display inlineBlock
, width (px 26)
, height (px 24)
, marginLeft (px 8)
, backgroundImage
(case pennant of
Premium ->
assets.iconPremiumFlag_svg
PremiumWithWriting ->
assets.iconPremiumWithWritingFlag_svg
)
, backgroundRepeat noRepeat
, backgroundPosition center
]
]
[]
Nothing ->
Html.text ""
]
{-| The assets used in this module.
-}
type alias Assets r =
{ r
| iconPremiumFlag_svg : Asset
, iconPremiumWithWritingFlag_svg : Asset
}
backgroundImage : Asset -> Style
backgroundImage =
Nri.Ui.AssetPath.Css.url
>> property "background-image"

View File

@ -1,150 +0,0 @@
module Nri.Ui.PremiumCheckbox.V5 exposing (view, Pennant(..))
{-|
@docs view, Pennant
# Changes from V4
- inlines the Config
- renames render function from `premium` to `view`
- removes dependency on Assets
-}
import Accessibility.Styled as Html exposing (Html)
import Css exposing (..)
import Html.Styled exposing (fromUnstyled)
import Html.Styled.Attributes as Attributes exposing (css)
import Nri.Ui.Checkbox.V5 as Checkbox
import Svg exposing (..)
import Svg.Attributes exposing (..)
{-| Premium is the yellow "P" pennant
PremiumWithWriting is the yellow "P+" pennant
-}
type Pennant
= Premium
| PremiumWithWriting
{-| A checkbox that should be used for premium content
- `onChange`: A message for when the user toggles the checkbox
- `onLockedClick`: A message for when the user clicks a checkbox they don't have PremiumLevel for.
If you get this message, you should show an `Nri.Ui.Premium.Model.view`
-}
view :
{ label : String
, id : String
, selected : Checkbox.IsSelected
, disabled : Bool
, isLocked : Bool
, pennant : Pennant
, onChange : Bool -> msg
, onLockedClick : msg
}
-> Html msg
view config =
Html.div
[ css
[ displayFlex
, alignItems center
]
]
[ Checkbox.viewWithLabel
{ identifier = config.id
, label = config.label
, setterMsg =
if config.isLocked then
\_ -> config.onLockedClick
else
config.onChange
, selected = config.selected
, disabled = config.disabled
, theme =
if config.isLocked then
Checkbox.Locked
else
Checkbox.Square
}
, case config.pennant of
Premium ->
premiumFlag
PremiumWithWriting ->
premiumWithWritingFlag
]
premiumFlag : Html msg
premiumFlag =
svg
[ version "1.1"
, id "Layer_1"
, Svg.Attributes.width "25"
, Svg.Attributes.height "19"
, Svg.Attributes.viewBox "0 0 25 19"
, Svg.Attributes.style "margin-left: 8px;"
]
[ Svg.title [] [ text "Premium" ]
, Svg.style [] [ text " .premium-flag-st0{fill:#FEC709;} .premium-flag-st1{fill:#146AFF;} " ]
, g [ id "Page-1" ]
[ g
[ id "icon_x2F_p-mini-pennant-yellow"
, Svg.Attributes.transform "translate(0.000000, -3.000000)"
]
[ g
[ id "Group"
, Svg.Attributes.transform "translate(0.000000, 3.750000)"
]
[ polygon
[ id "Fill-2"
, class "premium-flag-st0"
, points "12.7,0 0,0 0,13.8 0,15.8 0,17.5 7.3,17.5 24.8,17.5 19.4,8.1 24.8,0 "
]
[]
, Svg.path
[ id "P"
, class "premium-flag-st1"
, d "M7.5,3.8h4.2c1.1,0,1.9,0.3,2.5,0.8s0.9,1.2,0.9,2.1s-0.3,1.6-0.9,2.1c-0.6,0.5-1.4,0.8-2.5,0.8H9.3 v4.1H7.5V3.8z M11.5,8.1c0.6,0,1.1-0.1,1.4-0.4c0.3-0.3,0.5-0.6,0.5-1.1c0-0.5-0.2-0.9-0.5-1.1c-0.3-0.3-0.8-0.4-1.4-0.4H9.3v3 H11.5z"
]
[]
]
]
]
]
|> fromUnstyled
premiumWithWritingFlag : Html msg
premiumWithWritingFlag =
svg
[ Svg.Attributes.width "25"
, Svg.Attributes.height "18"
, Svg.Attributes.viewBox "0 0 25 18"
, Svg.Attributes.style "margin-left: 8px;"
]
[ Svg.title [] [ text "Premium with Writing" ]
, g
[ Svg.Attributes.fill "none"
, fillRule "evenodd"
]
[ Svg.path
[ Svg.Attributes.fill "#FEC709"
, d "M12.662 0H0v17.5h24.777l-5.384-9.423L24.777 0z"
]
[]
, Svg.path
[ Svg.Attributes.fill "#146AFF"
, d "M2.5 3.75h4.249c1.054 0 1.874.254 2.461.763.587.509.88 1.203.88 2.083 0 .88-.296 1.577-.887 2.09-.591.514-1.41.77-2.454.77H4.274v4.084H2.5V3.75zm4.043 4.331c.614 0 1.079-.126 1.395-.378.316-.252.474-.616.474-1.093 0-.486-.155-.855-.467-1.107-.312-.252-.78-.378-1.402-.378h-2.27v2.956h2.27zM12.85 7h1.31c.152 0 .278.127.278.288V9.57h2.283c.152 0 .279.127.279.28v1.31a.281.281 0 0 1-.279.278h-2.283v2.283a.281.281 0 0 1-.278.279h-1.31a.281.281 0 0 1-.28-.279v-2.283h-2.282A.283.283 0 0 1 10 11.16V9.85c0-.153.127-.28.288-.28h2.282V7.288c0-.161.127-.288.28-.288z"
]
[]
]
]
|> fromUnstyled

View File

@ -1,150 +0,0 @@
module Nri.Ui.SegmentedControl.V6 exposing (Config, Icon, Option, Width(..), view)
{-|
@docs Config, Icon, Option, Width, view
-}
import Accessibility.Styled exposing (..)
import Accessibility.Styled.Role as Role
import Css exposing (..)
import Html.Styled as Html exposing (Html)
import Html.Styled.Attributes as Attr exposing (css)
import Html.Styled.Events as Events
import Nri.Ui
import Nri.Ui.Colors.Extra exposing (withAlpha)
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Fonts.V1 as Fonts
import Nri.Ui.Icon.V3 as Icon
{-| -}
type alias Config a msg =
{ onClick : a -> msg
, options : List (Option a)
, selected : a
, width : Width
}
{-| -}
type alias Option a =
{ value : a
, icon : Maybe Icon
, label : String
, id : String
}
{-| -}
type Width
= FitContent
| FillContainer
{-| -}
type alias Icon =
{ alt : String
, icon : Icon.IconType
}
{-| -}
view : Config a msg -> Html.Html msg
view config =
tabList <|
List.map (viewTab config) config.options
tabList : List (Html.Html msg) -> Html.Html msg
tabList =
Nri.Ui.styled div
"Nri-Ui-SegmentedControl-tabList"
[ displayFlex, cursor pointer ]
[ Role.tabList ]
viewTab : Config a msg -> Option a -> Html.Html msg
viewTab config option =
Html.div
[ Attr.id option.id
, Role.tab
, Events.onClick (config.onClick option.value)
, css sharedTabStyles
, css <|
if option.value == config.selected then
focusedTabStyles
else
unFocusedTabStyles
, css <|
case config.width of
FitContent ->
[]
FillContainer ->
expandingTabStyles
]
[ case option.icon of
Nothing ->
Html.text ""
Just icon ->
viewIcon icon
, Html.text option.label
]
viewIcon : Icon -> Html.Html msg
viewIcon icon =
Html.span
[ css [ marginRight (px 10) ] ]
[ Icon.icon icon ]
sharedTabStyles : List Style
sharedTabStyles =
[ padding2 (px 6) (px 20)
, height (px 45)
, Fonts.baseFont
, fontSize (px 15)
, color Colors.azure
, fontWeight bold
, lineHeight (px 30)
, firstOfType
[ borderTopLeftRadius (px 8)
, borderBottomLeftRadius (px 8)
, borderLeft3 (px 1) solid Colors.azure
]
, lastOfType
[ borderTopRightRadius (px 8)
, borderBottomRightRadius (px 8)
]
, border3 (px 1) solid Colors.azure
, borderLeft (px 0)
, boxSizing borderBox
]
focusedTabStyles : List Style
focusedTabStyles =
[ backgroundColor Colors.glacier
, boxShadow5 inset zero (px 3) zero (withAlpha 0.2 Colors.gray20)
, color Colors.gray20
]
unFocusedTabStyles : List Style
unFocusedTabStyles =
[ backgroundColor Colors.white
, boxShadow5 inset zero (px -2) zero Colors.azure
, color Colors.azure
]
expandingTabStyles : List Style
expandingTabStyles =
[ flexGrow (int 1)
, textAlign center
]

View File

@ -1,368 +0,0 @@
module Nri.Ui.Tabs.V3 exposing
( Alignment(..)
, Config
, LinkConfig
, Tab
, TabLink
, links
, view
, viewCustom
, viewTabDefault
)
{-|
@docs Alignment
@docs Config
@docs LinkConfig
@docs Tab
@docs TabLink
@docs links
@docs view
@docs viewCustom
## Defaults
@docs viewTabDefault
-}
import Accessibility.Aria
import Accessibility.Key
import Accessibility.Role
import Accessibility.Widget
import Css exposing (Style)
import Html.Styled as Html exposing (Attribute, Html)
import Html.Styled.Attributes as Attributes
import Html.Styled.Events as Events
import Json.Decode
import List.Zipper exposing (Zipper)
import List.Zipper.Extra
import Nri.Ui.Colors.Extra
import Nri.Ui.Colors.V1
import Nri.Ui.Fonts.V1
{-| -}
type alias Config id msg =
{ title : Maybe String
, onSelect : id -> msg
, tabs : Zipper (Tab id)
, content : id -> Html msg
, alignment : Alignment
}
{-| Determines whether tabs are centered or floating to the left or right.
-}
type Alignment
= Left
| Center
| Right
{-| -}
type alias Tab id =
{ label : String
, id : id
}
{-| -}
view : Config id msg -> Html msg
view config =
viewCustom config viewTabDefault
{-| -}
viewTabDefault : Tab id -> Html msg
viewTabDefault tab =
Html.text tab.label
{-| -}
viewCustom : Config id msg -> (Tab id -> Html msg) -> Html msg
viewCustom config viewInnerTab =
let
selected =
List.Zipper.current config.tabs
viewTabs =
List.Zipper.toList config.tabs
|> List.map (viewTab config viewInnerTab selected)
in
Html.div
[]
[ Html.styled Html.div
[ Css.displayFlex
, Css.alignItems Css.flexEnd
, Css.borderBottom (Css.px 1)
, Css.borderBottomStyle Css.solid
, Css.borderBottomColor Nri.Ui.Colors.V1.navy
, Nri.Ui.Fonts.V1.baseFont
]
[]
[ config.title
|> Maybe.map viewTitle
|> Maybe.withDefault (Html.text "")
, Html.styled Html.ul
(stylesTabsAligned config.alignment)
[ Attributes.fromUnstyled <| Accessibility.Role.tabList
]
viewTabs
]
, Html.div
[ Attributes.fromUnstyled <| Accessibility.Role.tabPanel
, Attributes.fromUnstyled <| Accessibility.Aria.labelledBy (tabToId selected)
, Attributes.fromUnstyled <| Accessibility.Widget.hidden False
, Attributes.id (tabToBodyId selected)
]
[ config.content selected.id ]
]
viewTitle : String -> Html msg
viewTitle title =
Html.styled Html.h1
[ Css.flexGrow (Css.int 2)
, Css.fontSize (Css.px 30)
, Css.fontWeight Css.bold
, Css.margin Css.zero
, Css.marginTop (Css.px 5)
, Css.marginBottom (Css.px 10)
, Css.color Nri.Ui.Colors.V1.navy
, Css.width (Css.px 430)
]
[]
[ Html.text title ]
viewTab : Config id msg -> (Tab id -> Html msg) -> Tab id -> Tab id -> Html msg
viewTab { onSelect, tabs } viewInnerTab selected tab =
let
isSelected =
selected.id == tab.id
in
Html.styled Html.li
(stylesTabSelectable isSelected)
[ Events.onClick (onSelect tab.id)
, Attributes.fromUnstyled <| Accessibility.Key.onKeyDown [ Accessibility.Key.enter (onSelect tab.id) ]
, Events.onFocus (onSelect tab.id)
, Attributes.tabindex 0
, Attributes.fromUnstyled <| Accessibility.Role.presentation
, Attributes.id (tabToId tab)
, Attributes.fromUnstyled <| Accessibility.Aria.controls (tabToBodyId tab)
, Attributes.fromUnstyled <| Accessibility.Widget.selected (selected.id == tab.id)
, Events.on "keyup" <|
Json.Decode.andThen
(\keyCode ->
if keyCode == 39 then
tabs
|> List.Zipper.next
|> Maybe.map (List.Zipper.current >> .id >> onSelect >> Json.Decode.succeed)
|> Maybe.withDefault (Json.Decode.fail "No next tab")
else if keyCode == 37 then
tabs
|> List.Zipper.previous
|> Maybe.map (List.Zipper.current >> .id >> onSelect >> Json.Decode.succeed)
|> Maybe.withDefault (Json.Decode.fail "No previous tab")
else
Json.Decode.fail "Wrong key code"
)
Events.keyCode
]
[ Html.styled Html.div
[ Css.color Nri.Ui.Colors.V1.navy
, Css.hover [ Css.textDecoration Css.none ]
, Css.focus [ Css.textDecoration Css.none ]
, Css.display Css.inlineBlock
, Css.padding4 (Css.px 14) (Css.px 20) (Css.px 12) (Css.px 20)
, Css.position Css.relative
, Css.textDecoration Css.none
, Css.property "background" "none"
, Css.fontFamily Css.inherit
, Css.fontSize Css.inherit
, Css.cursor Css.pointer
]
[ Attributes.fromUnstyled <| Accessibility.Role.tab
, Attributes.tabindex -1
]
[ viewInnerTab tab ]
]
{-| Describe a tab that is meant to link to another page
-}
type alias TabLink =
{ label : String
, href : Maybe String
}
{-| Configure a set a tab links
-}
type alias LinkConfig msg =
{ title : Maybe String
, tabs : Zipper TabLink
, content : Html msg
, alignment : Alignment
}
{-| View a set of tab links
-}
links : LinkConfig msg -> Html msg
links config =
Html.div []
[ Html.styled Html.nav
[ Css.displayFlex
, Css.alignItems Css.flexEnd
, Css.borderBottom (Css.px 1)
, Css.borderBottomStyle Css.solid
, Css.borderBottomColor Nri.Ui.Colors.V1.navy
, Nri.Ui.Fonts.V1.baseFont
]
[]
[ config.title
|> Maybe.map viewTitle
|> Maybe.withDefault (Html.text "")
, Html.styled Html.ul
(stylesTabsAligned config.alignment)
[]
(config.tabs
|> mapWithCurrent (viewTabLink config)
|> List.Zipper.toList
)
]
, Html.div [] [ config.content ]
]
viewTabLink : LinkConfig msg -> Bool -> TabLink -> Html msg
viewTabLink config isSelected tabLink =
Html.styled Html.li
(stylesTabSelectable isSelected)
[ Attributes.fromUnstyled <| Accessibility.Role.presentation
, Attributes.id (tabToId tabLink)
]
[ case tabLink.href of
Just href ->
Html.styled Html.a
[ Css.color Nri.Ui.Colors.V1.navy
, Css.display Css.inlineBlock
, Css.padding4 (Css.px 14) (Css.px 20) (Css.px 12) (Css.px 20)
, Css.textDecoration Css.none
]
[ Attributes.href href ]
[ Html.text tabLink.label ]
Nothing ->
Html.styled Html.button
[ Css.color Nri.Ui.Colors.V1.navy
, Css.display Css.inlineBlock
, Css.padding4 (Css.px 14) (Css.px 20) (Css.px 12) (Css.px 20)
, Css.textDecoration Css.none
, Css.fontFamily Css.inherit
, Css.fontSize Css.inherit
, Css.border Css.zero
, Css.property "background" "none"
, Css.lineHeight (Css.num 1)
]
[]
[ Html.text tabLink.label ]
]
-- HELP
tabToId : { a | label : String } -> String
tabToId tab =
tab.label
tabToBodyId : { a | label : String } -> String
tabToBodyId tab =
"tab-body-" ++ tab.label
mapWithCurrent : (Bool -> a -> b) -> Zipper a -> Zipper b
mapWithCurrent fn zipper =
List.Zipper.Extra.from
(List.map (fn False) (List.Zipper.before zipper))
(fn True (List.Zipper.current zipper))
(List.map (fn False) (List.Zipper.after zipper))
-- STYLES
stylesTabsAligned : Alignment -> List Style
stylesTabsAligned alignment =
let
alignmentStyles =
case alignment of
Left ->
[ Css.justifyContent Css.flexStart ]
Center ->
[ Css.justifyContent Css.center ]
Right ->
[ Css.justifyContent Css.flexEnd ]
in
stylesTabs ++ alignmentStyles
stylesTabs : List Style
stylesTabs =
[ Css.listStyle Css.none
, Css.margin Css.zero
, Css.fontSize (Css.px 19)
, Css.displayFlex
, Css.flexGrow (Css.int 1)
, Css.marginRight (Css.px 10)
]
stylesTabSelectable : Bool -> List Style
stylesTabSelectable isSelected =
let
stylesDynamic =
if isSelected then
[ Css.backgroundColor Nri.Ui.Colors.V1.white
, Css.borderBottom (Css.px 1)
, Css.borderBottomStyle Css.solid
, Css.borderBottomColor Nri.Ui.Colors.V1.white
]
else
[ Css.backgroundColor Nri.Ui.Colors.V1.frost
, Css.backgroundImage <|
Css.linearGradient2 Css.toTop
(Css.stop2 (Nri.Ui.Colors.Extra.withAlpha 0.25 Nri.Ui.Colors.V1.azure) (Css.pct 0))
(Css.stop2 (Nri.Ui.Colors.Extra.withAlpha 0 Nri.Ui.Colors.V1.azure) (Css.pct 25))
[ Css.stop2 (Nri.Ui.Colors.Extra.withAlpha 0 Nri.Ui.Colors.V1.azure) (Css.pct 100) ]
]
in
stylesTab ++ stylesDynamic
stylesTab : List Style
stylesTab =
[ Css.display Css.inlineBlock
, Css.borderTopLeftRadius (Css.px 10)
, Css.borderTopRightRadius (Css.px 10)
, Css.border3 (Css.px 1) Css.solid Nri.Ui.Colors.V1.navy
, Css.marginBottom (Css.px -1)
, Css.marginLeft (Css.px 10)
, Css.cursor Css.pointer
, Css.firstChild
[ Css.marginLeft Css.zero
]
]

View File

@ -1,130 +0,0 @@
module Nri.Ui.Text.V3 exposing
( heading, subHeading, smallHeading, tagline
, caption, mediumBody, smallBody, smallBodyGray
, ugMediumBody, ugSmallBody
, noWidow
)
{-| Changes from V2:
- Normalizes how margin is applied (see "Understanding spacing", below).
## Understanding spacing
- All text styles have a specific line-height. This is set so that when text in the given style
is long enough to wrap, the spacing between wrapped lines looks good.
- No text styles have padding.
- **Heading styles** do not have margin. It is up to the caller to add appropriate margin to the layout.
- **Paragraph styles** only have bottom margin, but with **:last-child bottom margin set to zero**.
This bottom margin is set to look good when multiple paragraphs of the same style follow one another.
- If you want content after the paragraph and don't want the margin, put the paragraph in a `div` so that it will be the last-child, which will get rid of the bottom margin.
- **User-authored content blocks** preserve line breaks and do not have margin.
## Heading styles
Please use `Nri.Ui.Heading.V2` instead of these in new code. If you're here to
make a new Text version, please remove them.
@docs heading, subHeading, smallHeading, tagline
## Paragraph styles
@docs caption, mediumBody, smallBody, smallBodyGray
## User-authored content blocks:
@docs ugMediumBody, ugSmallBody
## Modifying strings to display nicely:
@docs noWidow
-}
import Html.Styled exposing (Html)
import Nri.Ui.Heading.V2 as Heading
import Nri.Ui.Text.V4 as V4
{-| This is a Page Heading.
-}
heading : List (Html msg) -> Html msg
heading content =
Heading.h1 [] content
{-| This is a tagline for a page heading.
-}
tagline : List (Html msg) -> Html msg
tagline content =
Heading.h2 [] content
{-| This is a subhead.
-}
subHeading : List (Html msg) -> Html msg
subHeading content =
Heading.h3 [] content
{-| This is a small Page Heading.
-}
smallHeading : List (Html msg) -> Html msg
smallHeading content =
Heading.h4 [] content
{-| This is some medium body copy.
-}
mediumBody : List (Html msg) -> Html msg
mediumBody =
V4.mediumBody
{-| This is some small body copy.
-}
smallBody : List (Html msg) -> Html msg
smallBody =
V4.smallBody
{-| This is some small body copy but it's gray.
-}
smallBodyGray : List (Html msg) -> Html msg
smallBodyGray =
V4.smallBodyGray
{-| This is a little note or caption.
-}
caption : List (Html msg) -> Html msg
caption =
V4.caption
{-| User-generated text.
-}
ugMediumBody : List (Html msg) -> Html msg
ugMediumBody =
V4.ugMediumBody
{-| User-generated text.
-}
ugSmallBody : List (Html msg) -> Html msg
ugSmallBody =
V4.ugSmallBody
{-| Eliminate widows (single words on their own line caused by
wrapping) by inserting a non-breaking space if there are at least two
words.
-}
noWidow : String -> String
noWidow =
V4.noWidow

View File

@ -1,202 +0,0 @@
module Nri.Ui.TextArea.V3 exposing (view, writing, contentCreation, Height(..), HeightBehavior(..), Model, generateId)
{-|
## Upgrading to V3
- Do nothing! (This just uses new elm-css styles)
## The Nri styleguide-specified textarea with overlapping label
@docs view, writing, contentCreation, Height, HeightBehavior, Model, generateId
-}
import Accessibility.Styled.Style
import Css exposing (plus, px)
import Html.Styled as Html exposing (Html)
import Html.Styled.Attributes as Attributes
import Html.Styled.Events as Events
import Nri.Ui.InputStyles.V2 as InputStyles
exposing
( Theme(..)
, input
, label
)
import Nri.Ui.Util exposing (dashify, removePunctuation)
{-| -}
type alias Model msg =
{ value : String
, autofocus : Bool
, onInput : String -> msg
, isInError : Bool
, height : HeightBehavior
, placeholder : String
, label : String
, showLabel : Bool
}
{-| Control whether to auto-expand the height.
-}
type HeightBehavior
= Fixed
| AutoResize Height
{-| For specifying the actual height.
-}
type Height
= DefaultHeight
| SingleLine
{-| -}
view : Model msg -> Html msg
view model =
view_ Standard model
{-| Used for Writing Cycles
-}
writing : Model msg -> Html msg
writing model =
view_ Writing model
{-| Used for Content Creation
-}
contentCreation : Model msg -> Html msg
contentCreation model =
view_ ContentCreation model
{-| -}
view_ : Theme -> Model msg -> Html msg
view_ theme model =
let
autoresizeAttrs =
case model.height of
AutoResize _ ->
[ Attributes.attribute "data-autoresize" "" ]
Fixed ->
[]
heightForStyle =
case theme of
Standard ->
InputStyles.textAreaHeight
ContentCreation ->
InputStyles.textAreaHeight
Writing ->
InputStyles.writingMinHeight
in
Html.styled Html.div
[ Css.position Css.relative ]
[]
[ Html.styled (Html.node "nri-textarea-v3")
[ Css.display Css.block ]
autoresizeAttrs
[ Html.styled Html.textarea
[ InputStyles.input theme model.isInError
, Css.boxSizing Css.borderBox
, case model.height of
AutoResize minimumHeight ->
Css.minHeight (calculateMinHeight theme minimumHeight)
Fixed ->
Css.minHeight heightForStyle
]
[ Events.onInput model.onInput
, Attributes.value model.value
, Attributes.id (generateId model.label)
, Attributes.autofocus model.autofocus
, Attributes.placeholder model.placeholder
, Attributes.attribute "data-gramm" "false" -- disables grammarly to prevent https://github.com/NoRedInk/NoRedInk/issues/14859
, Attributes.class "override-sass-styles"
, Attributes.attribute "aria-invalid" <|
if model.isInError then
"true"
else
"false"
]
[]
, if not model.showLabel then
Html.label
([ Attributes.for (generateId model.label)
]
++ Accessibility.Styled.Style.invisible
)
[ Html.text model.label ]
else
Html.label
[ Attributes.for (generateId model.label)
, Attributes.css [ InputStyles.label theme model.isInError ]
]
[ Html.text model.label ]
]
]
calculateMinHeight : Theme -> Height -> Css.Px
calculateMinHeight textAreaStyle specifiedHeight =
{- On including padding in this calculation:
When the textarea is autoresized, TextArea.js updates the textarea's
height by taking its scrollHeight. Because scrollHeight's calculation
includes the element's padding no matter what [1], we need to set the
textarea's box-sizing to border-box in order to use the same measurement
for its height as scrollHeight.
So, min-height also needs to be specified in terms of padding + content
height.
[1] https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
-}
case specifiedHeight of
SingleLine ->
case textAreaStyle of
Standard ->
singleLineHeight
Writing ->
writingSingleLineHeight
ContentCreation ->
singleLineHeight
DefaultHeight ->
case textAreaStyle of
Standard ->
InputStyles.textAreaHeight
Writing ->
InputStyles.writingMinHeight
ContentCreation ->
InputStyles.textAreaHeight
singleLineHeight : Css.Px
singleLineHeight =
px (.numericValue InputStyles.inputPaddingVertical + .numericValue InputStyles.inputLineHeight + .numericValue InputStyles.inputPaddingVertical)
writingSingleLineHeight : Css.Px
writingSingleLineHeight =
px (.numericValue InputStyles.writingPaddingTop + .numericValue InputStyles.writingLineHeight + .numericValue InputStyles.writingPadding)
{-| -}
generateId : String -> String
generateId labelText =
"nri-ui-text-area-" ++ (dashify <| removePunctuation labelText)