mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-28 10:17:49 +03:00
Merge pull request #277 from NoRedInk/tessa/kill-dead-code
💀 remove 0.18 modules
This commit is contained in:
commit
cc727ad70d
@ -2,10 +2,6 @@
|
||||
|
||||
UI widgets we use.
|
||||
|
||||
## Dual publishing notes
|
||||
|
||||
All internal consumers of this repository have been upgrade to Elm 0.19, so we will not be continuing to update the Elm 0.18 source.
|
||||
|
||||
## Versioning policy
|
||||
|
||||
We try to avoid breaking changes and the associated major version bumps in this package. The reason for that is to avoid the following scenario:
|
||||
|
@ -1,62 +0,0 @@
|
||||
{
|
||||
"version": "6.1.2",
|
||||
"summary": "UI Widgets we use at NRI",
|
||||
"repository": "https://github.com/NoRedInk/noredink-ui.git",
|
||||
"license": "BSD-3-Clause",
|
||||
"source-directories": [
|
||||
"src-0.18"
|
||||
],
|
||||
"exposed-modules": [
|
||||
"DEPRECATED.Nri.Ui.Styles.V2",
|
||||
"Nri.Ui.Alert.V2",
|
||||
"Nri.Ui.AssetPath",
|
||||
"Nri.Ui.BannerAlert.V2",
|
||||
"Nri.Ui.Button.V3",
|
||||
"Nri.Ui.Button.V4",
|
||||
"Nri.Ui.Button.V5",
|
||||
"Nri.Ui.Button.V6",
|
||||
"Nri.Ui.Checkbox.V3",
|
||||
"Nri.Ui.Colors.Extra",
|
||||
"Nri.Ui.Colors.V1",
|
||||
"Nri.Ui.DisclosureIndicator.V1",
|
||||
"Nri.Ui.Divider.V2",
|
||||
"Nri.Ui.Dropdown.V2",
|
||||
"Nri.Ui.Effects.V1",
|
||||
"Nri.Ui.Fonts.V1",
|
||||
"Nri.Ui.Html.Attributes.V2",
|
||||
"Nri.Ui.Html.V3",
|
||||
"Nri.Ui.Icon.V3",
|
||||
"Nri.Ui.Icon.V4",
|
||||
"Nri.Ui.InputStyles.V2",
|
||||
"Nri.Ui.Modal.V2",
|
||||
"Nri.Ui.Modal.V3",
|
||||
"Nri.Ui.Outline.V2",
|
||||
"Nri.Ui.Page.V2",
|
||||
"Nri.Ui.Page.V3",
|
||||
"Nri.Ui.Palette.V1",
|
||||
"Nri.Ui.PremiumCheckbox.V1",
|
||||
"Nri.Ui.PremiumCheckbox.V2",
|
||||
"Nri.Ui.SegmentedControl.V6",
|
||||
"Nri.Ui.Select.V5",
|
||||
"Nri.Ui.Select.V6",
|
||||
"Nri.Ui.Table.V3",
|
||||
"Nri.Ui.Table.V4",
|
||||
"Nri.Ui.Tabs.V3",
|
||||
"Nri.Ui.Text.V2",
|
||||
"Nri.Ui.Text.Writing.V1",
|
||||
"Nri.Ui.TextArea.V3",
|
||||
"Nri.Ui.TextInput.V3",
|
||||
"Nri.Ui"
|
||||
],
|
||||
"dependencies": {
|
||||
"elm-lang/core": "5.1.1 <= v < 6.0.0",
|
||||
"elm-lang/html": "2.0.0 <= v < 3.0.0",
|
||||
"elm-lang/svg": "2.0.0 <= v < 3.0.0",
|
||||
"lukewestby/accessible-html-with-css-temp": "1.0.1 <= v < 2.0.0",
|
||||
"pablohirafuji/elm-markdown": "2.0.4 <= v < 3.0.0",
|
||||
"rtfeldman/elm-css": "16.0.0 <= v < 17.0.0",
|
||||
"tesk9/accessible-html": "3.1.0 <= v < 4.0.0",
|
||||
"wernerdegroot/listzipper": "3.0.0 <= v < 4.0.0"
|
||||
},
|
||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Checks that `elm-package.json` contains an `exposed-modules` entry for every
|
||||
# Checks that `elm.json` contains an `exposed-modules` entry for every
|
||||
# module in `src/Nri` (recursively) named `Vn.elm`, where `n` is a decimal
|
||||
# number.
|
||||
#
|
||||
@ -56,14 +56,9 @@ def read_exposed_modules(path):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
available_18 = set(find_v_modules("src-0.18"))
|
||||
available_19 = set(find_v_modules("src"))
|
||||
exposed_18 = set(read_exposed_modules("elm-package.json"))
|
||||
exposed_19 = set(read_exposed_modules("elm.json"))
|
||||
# XXX: Do we need to check that parent modules are exposed too?
|
||||
missing_18 = available_18 - exposed_18
|
||||
missing_19 = available_19 - exposed_19
|
||||
missing = missing_18.union(missing_19)
|
||||
available = set(find_v_modules("src"))
|
||||
exposed = set(read_exposed_modules("elm.json"))
|
||||
missing = available - exposed
|
||||
for module in sorted(missing):
|
||||
print("Not exposed:", module, file=sys.stderr)
|
||||
raise SystemExit(
|
||||
|
@ -1,49 +0,0 @@
|
||||
module DEPRECATED.Nri.Ui.Styles.V2 exposing (Keyframe, keyframes, toString)
|
||||
|
||||
{-| DEPRECATED. Once we are on elm-css 15.1.0 or later, we should use its
|
||||
built-in keyframe functionality.
|
||||
|
||||
|
||||
### Keyframe animations
|
||||
|
||||
@docs Keyframe, keyframes, toString
|
||||
|
||||
-}
|
||||
|
||||
|
||||
{-| A CSS keyframe animation that will have vendor prefixes automatically added.
|
||||
-}
|
||||
type Keyframe
|
||||
= CompiledKeyframe String
|
||||
|
||||
|
||||
{-| Create a CSS keyframe animation with appropriate vendor prefixes
|
||||
-}
|
||||
keyframes : String -> List ( String, String ) -> Keyframe
|
||||
keyframes name stops =
|
||||
let
|
||||
stop ( when, what ) =
|
||||
when ++ " {" ++ what ++ "}"
|
||||
|
||||
x prefix =
|
||||
"@"
|
||||
++ prefix
|
||||
++ "keyframes "
|
||||
++ name
|
||||
++ " {\n"
|
||||
++ String.join "\n" (List.map stop stops)
|
||||
++ "\n}\n"
|
||||
in
|
||||
[ "-webkit-", "-moz-", "" ]
|
||||
|> List.map x
|
||||
|> String.join ""
|
||||
|> CompiledKeyframe
|
||||
|
||||
|
||||
{-| Turn a [`Keyframe`](#Keyframe) into a string that can be included in a CSS stylesheet.
|
||||
-}
|
||||
toString : Keyframe -> String
|
||||
toString keyframe =
|
||||
case keyframe of
|
||||
CompiledKeyframe compiled ->
|
||||
compiled
|
@ -1,41 +0,0 @@
|
||||
module EventExtras exposing (onClickPreventDefaultForLinkWithHref)
|
||||
|
||||
import Html
|
||||
import Html.Events exposing (..)
|
||||
import Json.Decode
|
||||
|
||||
|
||||
{-| This is necessary to use in single-page apps (SPA) when intercepting the
|
||||
`onClick` of `<a>` tags that trigger navigation within the app,
|
||||
as a normal `onClickPreventDefault` will prevent the user from opening the link
|
||||
in a new tab/window.
|
||||
|
||||
(From <https://github.com/elm-lang/html/issues/110>)
|
||||
|
||||
-}
|
||||
onClickPreventDefaultForLinkWithHref : msg -> Html.Attribute msg
|
||||
onClickPreventDefaultForLinkWithHref msg =
|
||||
let
|
||||
isSpecialClick : Json.Decode.Decoder Bool
|
||||
isSpecialClick =
|
||||
Json.Decode.map2
|
||||
(\isCtrl isMeta -> isCtrl || isMeta)
|
||||
(Json.Decode.field "ctrlKey" Json.Decode.bool)
|
||||
(Json.Decode.field "metaKey" Json.Decode.bool)
|
||||
|
||||
succeedIfFalse : a -> Bool -> Json.Decode.Decoder a
|
||||
succeedIfFalse msg preventDefault =
|
||||
case preventDefault of
|
||||
False ->
|
||||
Json.Decode.succeed msg
|
||||
|
||||
True ->
|
||||
Json.Decode.fail "succeedIfFalse: condition was True"
|
||||
in
|
||||
onWithOptions "click"
|
||||
{ stopPropagation = False
|
||||
, preventDefault = True
|
||||
}
|
||||
(isSpecialClick
|
||||
|> Json.Decode.andThen (succeedIfFalse msg)
|
||||
)
|
@ -1,77 +0,0 @@
|
||||
module EventExtras.Styled exposing (onClickForLinkWithHref, onClickPreventDefaultForLinkWithHref)
|
||||
|
||||
import Html.Styled as Html
|
||||
import Html.Styled.Events exposing (..)
|
||||
import Json.Decode
|
||||
|
||||
|
||||
{-| This is necessary to use in single-page apps (SPA) when intercepting the
|
||||
`onClick` of `<a>` tags that trigger navigation within the app,
|
||||
as a normal `onClickPreventDefault` will prevent the user from opening the link
|
||||
in a new tab/window.
|
||||
|
||||
(From <https://github.com/elm-lang/html/issues/110>)
|
||||
|
||||
-}
|
||||
onClickPreventDefaultForLinkWithHref : msg -> Html.Attribute msg
|
||||
onClickPreventDefaultForLinkWithHref msg =
|
||||
let
|
||||
isSpecialClick : Json.Decode.Decoder Bool
|
||||
isSpecialClick =
|
||||
Json.Decode.map2
|
||||
(\isCtrl isMeta -> isCtrl || isMeta)
|
||||
(Json.Decode.field "ctrlKey" Json.Decode.bool)
|
||||
(Json.Decode.field "metaKey" Json.Decode.bool)
|
||||
|
||||
succeedIfFalse : a -> Bool -> Json.Decode.Decoder a
|
||||
succeedIfFalse msg preventDefault =
|
||||
case preventDefault of
|
||||
False ->
|
||||
Json.Decode.succeed msg
|
||||
|
||||
True ->
|
||||
Json.Decode.fail "succeedIfFalse: condition was True"
|
||||
in
|
||||
onWithOptions "click"
|
||||
{ stopPropagation = False
|
||||
, preventDefault = True
|
||||
}
|
||||
(isSpecialClick
|
||||
|> Json.Decode.andThen (succeedIfFalse msg)
|
||||
)
|
||||
|
||||
|
||||
{-| This is necessary to use when intercepting the
|
||||
`onClick` of `<a>` tags that trigger navigation within the app,
|
||||
as a normal `onClick` will prevent the user from opening the link
|
||||
in a new tab/window.
|
||||
|
||||
(From <https://github.com/elm-lang/html/issues/110>)
|
||||
|
||||
-}
|
||||
onClickForLinkWithHref : msg -> Html.Attribute msg
|
||||
onClickForLinkWithHref msg =
|
||||
let
|
||||
isSpecialClick : Json.Decode.Decoder Bool
|
||||
isSpecialClick =
|
||||
Json.Decode.map2
|
||||
(\isCtrl isMeta -> isCtrl || isMeta)
|
||||
(Json.Decode.field "ctrlKey" Json.Decode.bool)
|
||||
(Json.Decode.field "metaKey" Json.Decode.bool)
|
||||
|
||||
succeedIfFalse : a -> Bool -> Json.Decode.Decoder a
|
||||
succeedIfFalse msg preventDefault =
|
||||
case preventDefault of
|
||||
False ->
|
||||
Json.Decode.succeed msg
|
||||
|
||||
True ->
|
||||
Json.Decode.fail "succeedIfFalse: condition was True"
|
||||
in
|
||||
onWithOptions "click"
|
||||
{ stopPropagation = False
|
||||
, preventDefault = False
|
||||
}
|
||||
(isSpecialClick
|
||||
|> Json.Decode.andThen (succeedIfFalse msg)
|
||||
)
|
@ -1,54 +0,0 @@
|
||||
module Nri.Ui exposing (styled)
|
||||
|
||||
{-| A collection of helpers for working with NoRedInk projects
|
||||
|
||||
@docs styled
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (Style)
|
||||
import Html.Styled exposing (Attribute, Html)
|
||||
import Html.Styled.Attributes exposing (attribute, css)
|
||||
|
||||
|
||||
{-| Wrapper around [`Html.Styled.style`](http://package.elm-lang.org/packages/rtfeldman/elm-css/13.1.1/Html-Styled#styled) which adds a data-nri-description attribute to make it easier to tell from Inspect Element where in our code that element was defined.
|
||||
|
||||
Takes a function that creates an element, and pre-applies styles and a `data-nri-description` attribution to it.
|
||||
|
||||
bigButton : List (Attribute msg) -> List (Html msg) -> Html msg
|
||||
bigButton =
|
||||
styled button
|
||||
"big button"
|
||||
[ padding (px 30)
|
||||
, fontWeight bold
|
||||
]
|
||||
|
||||
view : Model -> Html msg
|
||||
view model =
|
||||
[ text "These two buttons are identical:"
|
||||
, bigButton [] [ text "Hi!" ]
|
||||
, button [ css [ padding (px 30), fontWeight bold ] ] [] [ text "Hi!" ]
|
||||
]
|
||||
|
||||
Here, the bigButton function we've defined using styled button is identical to the normal button function, except that it has pre-applied the attribute of css [ padding (px 30), fontWeight bold ], as well as `(attribute "data-nri-description" "big button")`.
|
||||
|
||||
You can pass more attributes to bigButton as usual (including other css attributes). They will be applied after the pre-applied styles.
|
||||
|
||||
Note: normally `attributeMsg` will be the same as `msg`, but we need them to be different types for special cases when `fn` needs to do tricky things. For example, some elements from the Accessibility.Styled package use the following type signature:
|
||||
|
||||
div : List (Attribute Never) -> List (Html msg) -> Html msg
|
||||
|
||||
-}
|
||||
styled : (List (Attribute attributeMsg) -> List (Html msg) -> Html msg) -> String -> List Style -> List (Attribute attributeMsg) -> List (Html msg) -> Html msg
|
||||
styled fn description styles =
|
||||
-- Cache the computed css style so we only have to do the hashing once.
|
||||
-- Just like in https://github.com/rtfeldman/elm-css/pull/456
|
||||
let
|
||||
descriptionAttr =
|
||||
attribute "data-nri-description" description
|
||||
|
||||
cssAttr =
|
||||
css styles
|
||||
in
|
||||
\attrs children ->
|
||||
fn (descriptionAttr :: cssAttr :: attrs) children
|
@ -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)
|
@ -1,18 +0,0 @@
|
||||
module Nri.Ui.AssetPath exposing (Asset(Asset), url)
|
||||
|
||||
{-| Helpers for dealing with assets.
|
||||
|
||||
@docs Asset, url
|
||||
|
||||
-}
|
||||
|
||||
|
||||
{-| -}
|
||||
type Asset
|
||||
= Asset String
|
||||
|
||||
|
||||
{-| -}
|
||||
url : Asset -> String
|
||||
url (Asset url) =
|
||||
url
|
@ -1,17 +0,0 @@
|
||||
module Nri.Ui.AssetPath.Css exposing (url)
|
||||
|
||||
{-| Helper for constructing commonly-used CSS functions
|
||||
that reference assets.
|
||||
|
||||
@docs url
|
||||
|
||||
-}
|
||||
|
||||
import Nri.Ui.AssetPath as AssetPath exposing (Asset)
|
||||
|
||||
|
||||
{-| Given an `Asset`, wrap its URL in a call to `url()`.
|
||||
-}
|
||||
url : Asset -> String
|
||||
url asset =
|
||||
"url(" ++ AssetPath.url asset ++ ")"
|
@ -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
|
||||
]
|
@ -1,783 +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.onWithOptions "click"
|
||||
{ stopPropagation = False
|
||||
, preventDefault = True
|
||||
}
|
||||
(Json.Decode.succeed onTrack)
|
||||
]
|
||||
|
||||
|
||||
{-| 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
|
@ -1,814 +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.onWithOptions "click"
|
||||
{ stopPropagation = False
|
||||
, preventDefault = True
|
||||
}
|
||||
(Json.Decode.succeed onTrack)
|
||||
]
|
||||
|
||||
|
||||
{-| 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
|
@ -1,816 +0,0 @@
|
||||
module Nri.Ui.Button.V5 exposing
|
||||
( ButtonSize(..), ButtonWidth(..), ButtonStyle(..), ButtonState(..), ButtonContent
|
||||
, ButtonConfig, button, customButton, delete, copyToClipboard, ToggleButtonConfig, toggleButton
|
||||
, LinkConfig, link, linkSpa, linkExternal, linkWithMethod, linkWithTracking, linkExternalWithTracking
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
# Changes from V4:
|
||||
|
||||
- Allows links with words exceeding button-width to wrap
|
||||
- Standardizes vertical text alignment between buttons and links
|
||||
- Limit CSS transitions to a subset of properties to avoid unintended "zoom"
|
||||
effects.
|
||||
|
||||
|
||||
# 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 ]
|
||||
([ 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.onWithOptions "click"
|
||||
{ stopPropagation = False
|
||||
, preventDefault = True
|
||||
}
|
||||
(Json.Decode.succeed onTrack)
|
||||
]
|
||||
|
||||
|
||||
{-| 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-V5-" ++ suffix
|
@ -1,813 +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.onWithOptions "click"
|
||||
{ stopPropagation = False
|
||||
, preventDefault = True
|
||||
}
|
||||
(Json.Decode.succeed onTrack)
|
||||
]
|
||||
|
||||
|
||||
{-| 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
|
@ -1,330 +0,0 @@
|
||||
module Nri.Ui.Checkbox.V3 exposing
|
||||
( Model, Theme(..), IsSelected(..)
|
||||
, view, viewWithLabel, Assets
|
||||
, selectedFromBool
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Model, Theme, IsSelected
|
||||
|
||||
@docs view, viewWithLabel, Assets
|
||||
|
||||
@docs selectedFromBool
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled as Html
|
||||
import Accessibility.Styled.Aria as Aria
|
||||
import Accessibility.Styled.Style
|
||||
import Accessibility.Styled.Widget as Widget
|
||||
import Css exposing (..)
|
||||
import Css.Global exposing (Snippet, children, descendants, everything, selector)
|
||||
import Html.Events exposing (defaultOptions)
|
||||
import Html.Styled
|
||||
import Html.Styled.Attributes as Attributes exposing (css)
|
||||
import Html.Styled.Events as Events
|
||||
import Json.Decode
|
||||
import Nri.Ui.AssetPath exposing (Asset(..))
|
||||
import Nri.Ui.AssetPath.Css
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.Html.Attributes.V2 as ExtraAttributes
|
||||
import Nri.Ui.Html.V3 as HtmlExtra
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Model msg =
|
||||
{ identifier : String
|
||||
, label : String
|
||||
, setterMsg : Bool -> msg
|
||||
, selected : IsSelected
|
||||
, disabled : Bool
|
||||
, theme : Theme
|
||||
, noOpMsg : msg
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
= Selected -- Checked (rendered with a checkmark)
|
||||
| NotSelected -- Not Checked (rendered blank)
|
||||
| PartiallySelected -- Indeterminate (rendered dash)
|
||||
|
||||
-}
|
||||
type IsSelected
|
||||
= Selected
|
||||
| NotSelected
|
||||
| PartiallySelected
|
||||
|
||||
|
||||
{-| -}
|
||||
type Theme
|
||||
= Square
|
||||
| Locked
|
||||
|
||||
|
||||
{-| If your selectedness is always selected or not selected,
|
||||
you will likely store that state as a `Bool` in your model.
|
||||
`selectedFromBool` lets you easily convert that into an `IsSelected` value
|
||||
for use with `Nri.Ui.Checkbox`.
|
||||
-}
|
||||
selectedFromBool : Bool -> IsSelected
|
||||
selectedFromBool isSelected =
|
||||
case isSelected of
|
||||
True ->
|
||||
Selected
|
||||
|
||||
False ->
|
||||
NotSelected
|
||||
|
||||
|
||||
selectedToMaybe : IsSelected -> Maybe Bool
|
||||
selectedToMaybe selected =
|
||||
case selected of
|
||||
Selected ->
|
||||
Just True
|
||||
|
||||
NotSelected ->
|
||||
Just False
|
||||
|
||||
PartiallySelected ->
|
||||
Nothing
|
||||
|
||||
|
||||
{-| Shows a checkbox (the label is only used for accessibility hints)
|
||||
-}
|
||||
view : Assets a -> Model msg -> Html.Html msg
|
||||
view assets model =
|
||||
buildCheckbox assets model <|
|
||||
Html.span [ Accessibility.Styled.Style.invisible ]
|
||||
[ Html.text model.label ]
|
||||
|
||||
|
||||
{-| Shows a checkbox and its label text
|
||||
-}
|
||||
viewWithLabel : Assets a -> Model msg -> Html.Html msg
|
||||
viewWithLabel assets model =
|
||||
buildCheckbox assets model <|
|
||||
Html.span [] [ Html.text model.label ]
|
||||
|
||||
|
||||
buildCheckbox : Assets a -> Model msg -> Html.Html msg -> Html.Html msg
|
||||
buildCheckbox assets model labelContent =
|
||||
viewCheckbox model <|
|
||||
case model.theme of
|
||||
Square ->
|
||||
{ containerClasses = toClassList [ "SquareClass" ]
|
||||
, labelStyles =
|
||||
squareLabelStyles model <|
|
||||
case model.selected of
|
||||
Selected ->
|
||||
assets.checkboxChecked_svg
|
||||
|
||||
NotSelected ->
|
||||
assets.checkboxUnchecked_svg
|
||||
|
||||
PartiallySelected ->
|
||||
assets.checkboxCheckedPartially_svg
|
||||
, labelClasses = labelClass model.selected
|
||||
, labelContent = labelContent
|
||||
}
|
||||
|
||||
Locked ->
|
||||
{ containerClasses = toClassList [ "Locked" ]
|
||||
, labelStyles = lockLabelStyles model assets.checkboxLockOnInside_svg
|
||||
, labelClasses = labelClass model.selected
|
||||
, labelContent = labelContent
|
||||
}
|
||||
|
||||
|
||||
squareLabelStyles : { b | disabled : Bool } -> Asset -> Html.Styled.Attribute msg
|
||||
squareLabelStyles model image =
|
||||
let
|
||||
baseStyles =
|
||||
[ positioning
|
||||
, textStyle
|
||||
, outline none
|
||||
, addIcon image
|
||||
]
|
||||
in
|
||||
css
|
||||
(baseStyles
|
||||
++ (if model.disabled then
|
||||
[ cursor auto, checkboxImageSelector [ opacity (num 0.4) ] ]
|
||||
|
||||
else
|
||||
[ cursor pointer ]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
lockLabelStyles : { b | disabled : Bool } -> Asset -> Html.Styled.Attribute msg
|
||||
lockLabelStyles model image =
|
||||
let
|
||||
baseStyles =
|
||||
[ positioning
|
||||
, textStyle
|
||||
, outline none
|
||||
, addIcon image
|
||||
]
|
||||
in
|
||||
css
|
||||
(baseStyles
|
||||
++ (if model.disabled then
|
||||
[ cursor auto
|
||||
, checkboxImageSelector [ opacity (num 0.4) ]
|
||||
]
|
||||
|
||||
else
|
||||
[ cursor pointer ]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
positioning : Style
|
||||
positioning =
|
||||
batch
|
||||
[ display inlineBlock
|
||||
, padding4 (px 13) zero (px 13) (px 35)
|
||||
]
|
||||
|
||||
|
||||
textStyle : Style
|
||||
textStyle =
|
||||
batch
|
||||
[ Fonts.baseFont
|
||||
, fontSize (px 16)
|
||||
]
|
||||
|
||||
|
||||
addIcon : Asset -> Style
|
||||
addIcon icon =
|
||||
batch
|
||||
[ position relative
|
||||
, checkboxImageSelector
|
||||
[ backgroundImage icon
|
||||
, backgroundRepeat noRepeat
|
||||
, backgroundSize (px 24)
|
||||
, property "content" "''"
|
||||
, position absolute
|
||||
, left zero
|
||||
, top (px 10)
|
||||
, width (px 24)
|
||||
, height (px 24)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
checkboxImageSelector : List Style -> Style
|
||||
checkboxImageSelector =
|
||||
before
|
||||
|
||||
|
||||
labelClass : IsSelected -> Html.Styled.Attribute msg
|
||||
labelClass isSelected =
|
||||
case isSelected of
|
||||
Selected ->
|
||||
toClassList [ "Label", "Checked" ]
|
||||
|
||||
NotSelected ->
|
||||
toClassList [ "Label", "Unchecked" ]
|
||||
|
||||
PartiallySelected ->
|
||||
toClassList [ "Label", "Indeterminate" ]
|
||||
|
||||
|
||||
toClassList : List String -> Html.Styled.Attribute msg
|
||||
toClassList =
|
||||
List.map (\a -> ( "checkbox-V3__" ++ a, True )) >> Attributes.classList
|
||||
|
||||
|
||||
viewCheckbox :
|
||||
Model msg
|
||||
->
|
||||
{ containerClasses : Html.Attribute msg
|
||||
, labelStyles : Html.Attribute msg
|
||||
, labelClasses : Html.Attribute msg
|
||||
, labelContent : Html.Html msg
|
||||
}
|
||||
-> Html.Html msg
|
||||
viewCheckbox model config =
|
||||
let
|
||||
toggledValue =
|
||||
selectedToMaybe model.selected
|
||||
|> Maybe.withDefault False
|
||||
|> not
|
||||
in
|
||||
Html.Styled.span
|
||||
[ css
|
||||
[ display block
|
||||
, height inherit
|
||||
, descendants [ Css.Global.input [ display none ] ]
|
||||
]
|
||||
, config.containerClasses
|
||||
, Attributes.id <| model.identifier ++ "-container"
|
||||
, -- This is necessary to prevent event propagation.
|
||||
-- See https://github.com/elm-lang/html/issues/96
|
||||
Attributes.map (always model.noOpMsg) <|
|
||||
Events.onWithOptions "click"
|
||||
{ defaultOptions | stopPropagation = True }
|
||||
(Json.Decode.succeed "stop click propagation")
|
||||
]
|
||||
[ Html.checkbox model.identifier
|
||||
(selectedToMaybe model.selected)
|
||||
[ Widget.label model.label
|
||||
, Events.onClick (model.setterMsg toggledValue)
|
||||
, Attributes.id model.identifier
|
||||
, Attributes.disabled model.disabled
|
||||
]
|
||||
, viewLabel model config.labelContent config.labelClasses config.labelStyles
|
||||
]
|
||||
|
||||
|
||||
viewLabel : Model msg -> Html.Html msg -> Html.Attribute msg -> Html.Attribute msg -> Html.Html msg
|
||||
viewLabel model content class theme =
|
||||
Html.Styled.label
|
||||
[ Attributes.for model.identifier
|
||||
, Aria.controls model.identifier
|
||||
, Widget.disabled model.disabled
|
||||
, Widget.checked (selectedToMaybe model.selected)
|
||||
, if not model.disabled then
|
||||
Attributes.tabindex 0
|
||||
|
||||
else
|
||||
ExtraAttributes.none
|
||||
, if not model.disabled then
|
||||
--TODO: the accessibility keyboard module might make this a tad more readable.
|
||||
HtmlExtra.onKeyUp
|
||||
{ defaultOptions | preventDefault = True }
|
||||
(\keyCode ->
|
||||
-- 32 is the space bar, 13 is enter
|
||||
if (keyCode == 32 || keyCode == 13) && not model.disabled then
|
||||
Just <| model.setterMsg (Maybe.map not (selectedToMaybe model.selected) |> Maybe.withDefault True)
|
||||
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
|
||||
else
|
||||
ExtraAttributes.none
|
||||
, class
|
||||
, theme
|
||||
]
|
||||
[ content ]
|
||||
|
||||
|
||||
{-| The assets used in this module.
|
||||
-}
|
||||
type alias Assets r =
|
||||
{ r
|
||||
| checkboxUnchecked_svg : Asset
|
||||
, checkboxChecked_svg : Asset
|
||||
, checkboxCheckedPartially_svg : Asset
|
||||
, checkboxLockOnInside_svg : Asset
|
||||
}
|
||||
|
||||
|
||||
backgroundImage : Asset -> Style
|
||||
backgroundImage =
|
||||
Nri.Ui.AssetPath.Css.url
|
||||
>> property "background-image"
|
@ -1,30 +0,0 @@
|
||||
module Nri.Ui.Colors.Extra exposing (toCoreColor, withAlpha)
|
||||
|
||||
{-| Helpers for working with colors.
|
||||
|
||||
|
||||
# Conversions
|
||||
|
||||
@docs toCoreColor, withAlpha
|
||||
|
||||
-}
|
||||
|
||||
import Color
|
||||
import Css exposing (..)
|
||||
|
||||
|
||||
{-| Convert a Css.Color into a Color.Color
|
||||
toCoreColor (Css.hex "#FFFFFF") -- "RGBA 255 255 255 1 : Color.Color"
|
||||
-}
|
||||
toCoreColor : Css.Color -> Color.Color
|
||||
toCoreColor cssColor =
|
||||
Color.rgba cssColor.red cssColor.green cssColor.blue cssColor.alpha
|
||||
|
||||
|
||||
{-| Add an alpha property to a Css.Color
|
||||
grassland -- "{ value = "#56bf74", color = Compatible, red = 86, green = 191, blue = 116, alpha = 1, warnings = [] } : Css.Color"
|
||||
withAlpha 0.5 grassland -- "{ value = "rgba(86, 191, 116, 0.5)", color = Compatible, warnings = [], red = 86, green = 191, blue = 116, alpha = 0.5 } : Css.Color"
|
||||
-}
|
||||
withAlpha : Float -> Css.Color -> Css.Color
|
||||
withAlpha alpha { red, green, blue } =
|
||||
Css.rgba red green blue alpha
|
@ -1,507 +0,0 @@
|
||||
module Nri.Ui.Colors.V1 exposing
|
||||
( aqua, aquaDark, aquaLight, azure, azureDark
|
||||
, white
|
||||
, blue, blueDeep
|
||||
, cornflower, cornflowerDark, cornflowerLight, cyan
|
||||
, frost
|
||||
, gray20, gray45, gray75, gray92, gray96
|
||||
, glacier, grassland, green, greenDark, greenDarkest, greenLight, greenLightest
|
||||
, highlightLightBlue, highlightLightMagenta, highlightLightYellow, highlightBrown, highlightBrownDark
|
||||
, lichen
|
||||
, magenta
|
||||
, navy
|
||||
, orange, ochre
|
||||
, purple, purpleDark, purpleLight
|
||||
, red, redDark, redLight
|
||||
, sunshine
|
||||
, turquoise, turquoiseDark, turquoiseLight
|
||||
, yellow
|
||||
)
|
||||
|
||||
{-| Comprehensive list of named colors.
|
||||
|
||||
For helpers & conversions, see Nri.Ui.Colors.Extra, or
|
||||
consider [elm-color-extra](http://package.elm-lang.org/packages/eskimoblood/elm-color-extra/5.0.0/).
|
||||
|
||||
@docs aqua, aquaDark, aquaLight, azure, azureDark
|
||||
@docs white
|
||||
@docs blue, blueDeep
|
||||
@docs cornflower, cornflowerDark, cornflowerLight, cyan
|
||||
@docs frost
|
||||
@docs gray20, gray45, gray75, gray92, gray96
|
||||
@docs glacier, grassland, green, greenDark, greenDarkest, greenLight, greenLightest
|
||||
@docs highlightLightBlue, highlightLightMagenta, highlightLightYellow, highlightBrown, highlightBrownDark
|
||||
@docs lichen
|
||||
@docs magenta
|
||||
@docs navy
|
||||
@docs orange, ochre
|
||||
@docs purple, purpleDark, purpleLight
|
||||
@docs red, redDark, redLight
|
||||
@docs sunshine
|
||||
@docs turquoise, turquoiseDark, turquoiseLight
|
||||
@docs yellow
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (hex, rgba)
|
||||
import Nri.Ui.Colors.Extra exposing (withAlpha)
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #00cbeb">#00cbeb</p>
|
||||
|
||||
-}
|
||||
aqua : Css.Color
|
||||
aqua =
|
||||
hex "#00cbeb"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #008da3">#008da3</p>
|
||||
|
||||
-}
|
||||
aquaDark : Css.Color
|
||||
aquaDark =
|
||||
hex "#008da3"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #e6fcff">#e6fcff</p>
|
||||
|
||||
-}
|
||||
aquaLight : Css.Color
|
||||
aquaLight =
|
||||
hex "#e6fcff"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #146aff">#146aff</p>
|
||||
|
||||
-}
|
||||
azure : Css.Color
|
||||
azure =
|
||||
hex "#146aff"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #004cc9">#004cc9</p>
|
||||
|
||||
-}
|
||||
azureDark : Css.Color
|
||||
azureDark =
|
||||
hex "#004cc9"
|
||||
|
||||
|
||||
{-| TODO
|
||||
|
||||
<p style="font-size:2em; color: #40a8e4">#40a8e4</p>
|
||||
|
||||
-}
|
||||
blue : Css.Color
|
||||
blue =
|
||||
hex "#40a8e4"
|
||||
|
||||
|
||||
{-| TODO
|
||||
|
||||
<p style="font-size:2em; color: #4a79a7">#4a79a7</p>
|
||||
|
||||
-}
|
||||
blueDeep : Css.Color
|
||||
blueDeep =
|
||||
hex "#4a79a7"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #00aaff">#00aaff</p>
|
||||
|
||||
-}
|
||||
cornflower : Css.Color
|
||||
cornflower =
|
||||
hex "#00aaff"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #0074ad">#0074ad</p>
|
||||
|
||||
-}
|
||||
cornflowerDark : Css.Color
|
||||
cornflowerDark =
|
||||
hex "#0074ad"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #e6f7ff">#e6f7ff</p>
|
||||
|
||||
-}
|
||||
cornflowerLight : Css.Color
|
||||
cornflowerLight =
|
||||
hex "#e6f7ff"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #43dcff">#43dcff</p>
|
||||
|
||||
-}
|
||||
cyan : Css.Color
|
||||
cyan =
|
||||
hex "#43dcff"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #eef9ff">#eef9ff</p>
|
||||
|
||||
-}
|
||||
frost : Css.Color
|
||||
frost =
|
||||
hex "#eef9ff"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #d4f0ff">#d4f0ff</p>
|
||||
|
||||
-}
|
||||
glacier : Css.Color
|
||||
glacier =
|
||||
hex "#d4f0ff"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #56bf74">#56bf74</p>
|
||||
|
||||
-}
|
||||
grassland : Css.Color
|
||||
grassland =
|
||||
hex "#56bf74"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #333333">#333333</p>
|
||||
|
||||
-}
|
||||
gray20 : Css.Color
|
||||
gray20 =
|
||||
hex "#333333"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #727272">#727272</p>
|
||||
|
||||
-}
|
||||
gray45 : Css.Color
|
||||
gray45 =
|
||||
hex "#727272"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #bfbfbf">#bfbfbf</p>
|
||||
|
||||
-}
|
||||
gray75 : Css.Color
|
||||
gray75 =
|
||||
hex "#bfbfbf"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #ebebeb">#ebebeb</p>
|
||||
|
||||
-}
|
||||
gray92 : Css.Color
|
||||
gray92 =
|
||||
hex "#ebebeb"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #f5f5f5">#f5f5f5</p>
|
||||
|
||||
-}
|
||||
gray96 : Css.Color
|
||||
gray96 =
|
||||
hex "#f5f5f5"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #00d93e">#00d93e</p>
|
||||
|
||||
-}
|
||||
green : Css.Color
|
||||
green =
|
||||
hex "#00d93e"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #26a300">#26a300</p>
|
||||
|
||||
-}
|
||||
greenDark : Css.Color
|
||||
greenDark =
|
||||
hex "#26a300"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #228000">#228000</p>
|
||||
|
||||
-}
|
||||
greenDarkest : Css.Color
|
||||
greenDarkest =
|
||||
hex "#228000"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #b3ffc9">#b3ffc9</p>
|
||||
|
||||
-}
|
||||
greenLight : Css.Color
|
||||
greenLight =
|
||||
hex "#b3ffc9"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #e6ffed; background-color: black;">#e6ffed</p>
|
||||
|
||||
-}
|
||||
greenLightest : Css.Color
|
||||
greenLightest =
|
||||
hex "#e6ffed"
|
||||
|
||||
|
||||
{-| cyan with alpha of 0.75
|
||||
|
||||
<p style="font-size:2em; color: rgba(66, 219, 255, 0.75)">rgba(66, 219, 255, 0.75)</p>
|
||||
|
||||
-}
|
||||
highlightLightBlue : Css.Color
|
||||
highlightLightBlue =
|
||||
withAlpha 0.75 cyan
|
||||
|
||||
|
||||
{-| magenta with alpha of 0.5
|
||||
|
||||
<p style="font-size:2em; color: rgba(255, 0 ,189, 0.5)">rgba(255, 0 ,189, 0.5)</p>
|
||||
|
||||
-}
|
||||
highlightLightMagenta : Css.Color
|
||||
highlightLightMagenta =
|
||||
withAlpha 0.5 magenta
|
||||
|
||||
|
||||
{-| yellow with alpha of 0.75
|
||||
|
||||
<p style="font-size:2em; color: rgba(254, 199 ,9, 0.75)">rgba(254, 199 ,9, 0.75)</p>
|
||||
|
||||
-}
|
||||
highlightLightYellow : Css.Color
|
||||
highlightLightYellow =
|
||||
withAlpha 0.75 yellow
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; background-color: #ffc6a1">#ffc6a1</p>
|
||||
|
||||
-}
|
||||
highlightBrown : Css.Color
|
||||
highlightBrown =
|
||||
hex "#ffc6a1"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; background-color: #943b00">#943b00</p>
|
||||
|
||||
-}
|
||||
highlightBrownDark : Css.Color
|
||||
highlightBrownDark =
|
||||
hex "#943b00"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #99bfa4">#99bfa4</p>
|
||||
|
||||
-}
|
||||
lichen : Css.Color
|
||||
lichen =
|
||||
hex "#99bfa4"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #ff00bd">#ff00bd</p>
|
||||
|
||||
-}
|
||||
magenta : Css.Color
|
||||
magenta =
|
||||
hex "#ff00bd"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #004e95">#004e95</p>
|
||||
|
||||
-}
|
||||
navy : Css.Color
|
||||
navy =
|
||||
hex "#004e95"
|
||||
|
||||
|
||||
{-| -- TODO
|
||||
|
||||
<p style="font-size:2em; color: #f5a623">#f5a623</p>
|
||||
|
||||
-}
|
||||
orange : Css.Color
|
||||
orange =
|
||||
hex "#f5a623"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #e68800">#e68800</p>
|
||||
|
||||
-}
|
||||
ochre : Css.Color
|
||||
ochre =
|
||||
hex "#e68800"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #a839e7">#a839e7</p>
|
||||
|
||||
-}
|
||||
purple : Css.Color
|
||||
purple =
|
||||
hex "#a839e7"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #f7ebff">#f7ebff</p>
|
||||
|
||||
-}
|
||||
purpleLight : Css.Color
|
||||
purpleLight =
|
||||
hex "#f7ebff"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #7721a7">#7721a7</p>
|
||||
|
||||
-}
|
||||
purpleDark : Css.Color
|
||||
purpleDark =
|
||||
hex "#7721a7"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #f3336c">#f3336c</p>
|
||||
|
||||
-}
|
||||
red : Css.Color
|
||||
red =
|
||||
hex "#f3336c"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #ffe0e6">#ffe0e6</p>
|
||||
|
||||
-}
|
||||
redLight : Css.Color
|
||||
redLight =
|
||||
hex "#ffe0e6"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #c2003a">#c2003a</p>
|
||||
|
||||
-}
|
||||
redDark : Css.Color
|
||||
redDark =
|
||||
hex "#c2003a"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #fffadc">#fffadc</p>
|
||||
|
||||
-}
|
||||
sunshine : Css.Color
|
||||
sunshine =
|
||||
hex "#fffadc"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #00cfbe">#00cfbe</p>
|
||||
|
||||
-}
|
||||
turquoise : Css.Color
|
||||
turquoise =
|
||||
hex "#00cfbe"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #00a39b">#00a39b</p>
|
||||
|
||||
-}
|
||||
turquoiseDark : Css.Color
|
||||
turquoiseDark =
|
||||
hex "#00a39b"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #e0fffe">#e0fffe</p>
|
||||
|
||||
-}
|
||||
turquoiseLight : Css.Color
|
||||
turquoiseLight =
|
||||
hex "#e0fffe"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #ffffff; background-color: black;">#ffffff</p>
|
||||
|
||||
-}
|
||||
white : Css.Color
|
||||
white =
|
||||
hex "#ffffff"
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
<p style="font-size:2em; color: #fec709">#fec709</p>
|
||||
|
||||
-}
|
||||
yellow : Css.Color
|
||||
yellow =
|
||||
hex "#fec709"
|
@ -1,49 +0,0 @@
|
||||
module Nri.Ui.Css.VendorPrefixed exposing (property, value, complexProperty)
|
||||
|
||||
{-| Vendor prefixed css properties.
|
||||
|
||||
@docs property, value, complexProperty
|
||||
|
||||
-}
|
||||
|
||||
import Css
|
||||
|
||||
|
||||
{-| Css vendor prefixes
|
||||
-}
|
||||
prefixes : List String
|
||||
prefixes =
|
||||
[ "-webkit-", "-moz-", "-o-", "-ms-", "" ]
|
||||
|
||||
|
||||
{-| Same as Css.property but vendor prefixed.
|
||||
-}
|
||||
property : String -> String -> Css.Style
|
||||
property prop value =
|
||||
prefixes
|
||||
|> List.map
|
||||
(\prefix ->
|
||||
Css.property (prefix ++ prop) value
|
||||
)
|
||||
|> Css.batch
|
||||
|
||||
|
||||
{-| Same as Css.property but vendor prefixed.
|
||||
-}
|
||||
value : String -> String -> Css.Style
|
||||
value prop value =
|
||||
prefixes
|
||||
|> List.map
|
||||
(\prefix ->
|
||||
Css.property prop (prefix ++ value)
|
||||
)
|
||||
|> Css.batch
|
||||
|
||||
|
||||
{-| Used to build more complex Css styles
|
||||
-}
|
||||
complexProperty : (String -> Css.Style) -> Css.Style
|
||||
complexProperty buildProp =
|
||||
prefixes
|
||||
|> List.map buildProp
|
||||
|> Css.batch
|
@ -1,269 +0,0 @@
|
||||
module Nri.Ui.CssFlexBoxWithVendorPrefix exposing
|
||||
( displayFlex, displayInlineFlex, flexDirection, justifyContent, alignItems, alignSelf, flexBasis
|
||||
, flexGrow, flexShrink, row, rowReverse, column, columnReverse, flexStart, flexEnd, baseline, stretch, center, spaceBetween, spaceAround, flexWrap, nowrap, wrap, wrapReverse
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs displayFlex, displayInlineFlex, flexDirection, justifyContent, alignItems, alignSelf, flexBasis
|
||||
@docs flexGrow, flexShrink, row, rowReverse, column, columnReverse, flexStart, flexEnd, baseline, stretch, center, spaceBetween, spaceAround, flexWrap, nowrap, wrap, wrapReverse
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (Style, batch, property)
|
||||
|
||||
|
||||
{-| -}
|
||||
displayFlex : Style
|
||||
displayFlex =
|
||||
batch
|
||||
[ property "display" "-webkit-box" -- OLD - iOS 6-, Safari 3.1-6
|
||||
, property "display" "-moz-box" -- OLD - Firefox 19- (buggy but mostly works)
|
||||
, property "display" "-ms-flexbox" -- TWEENER - IE 10
|
||||
, property "display" "-webkit-flex" -- NEW - Chrome
|
||||
, property "display" "flex" -- NEW, Spec - Opera 12.1, Firefox 20+
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
displayInlineFlex : Style
|
||||
displayInlineFlex =
|
||||
batch
|
||||
[ property "display" "-webkit-inline-box" -- OLD - iOS 6-, Safari 3.1-6
|
||||
, property "display" "-moz-inline-box" -- OLD - Firefox 19- (buggy but mostly works)
|
||||
, property "display" "-ms-inline-flexbox" -- TWEENER - IE 10
|
||||
, property "display" "-webkit-inline-flex" -- NEW - Chrome
|
||||
, property "display" "inline-flex" -- NEW, Spec - Opera 12.1, Firefox 20+
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
flexDirection : Direction -> Style
|
||||
flexDirection direction =
|
||||
addPrefix "flex-direction" <|
|
||||
case direction of
|
||||
Row ->
|
||||
"row"
|
||||
|
||||
RowReverse ->
|
||||
"row-reverse"
|
||||
|
||||
Column ->
|
||||
"column"
|
||||
|
||||
ColumnReverse ->
|
||||
"column-reverse"
|
||||
|
||||
|
||||
type Direction
|
||||
= Row
|
||||
| RowReverse
|
||||
| Column
|
||||
| ColumnReverse
|
||||
|
||||
|
||||
{-| Direction row.
|
||||
-}
|
||||
row : Direction
|
||||
row =
|
||||
Row
|
||||
|
||||
|
||||
{-| Direction rowReverse.
|
||||
-}
|
||||
rowReverse : Direction
|
||||
rowReverse =
|
||||
RowReverse
|
||||
|
||||
|
||||
{-| Direction column.
|
||||
-}
|
||||
column : Direction
|
||||
column =
|
||||
Column
|
||||
|
||||
|
||||
{-| Direction columnReverse.
|
||||
-}
|
||||
columnReverse : Direction
|
||||
columnReverse =
|
||||
ColumnReverse
|
||||
|
||||
|
||||
{-| -}
|
||||
justifyContent : Alignment JustifyContent a -> Style
|
||||
justifyContent =
|
||||
addPrefix "justify-content" << alignmentToString
|
||||
|
||||
|
||||
{-| -}
|
||||
alignItems : Alignment a AlignItems -> Style
|
||||
alignItems =
|
||||
addPrefix "align-items" << alignmentToString
|
||||
|
||||
|
||||
{-| -}
|
||||
alignSelf : Alignment a AlignItems -> Style
|
||||
alignSelf =
|
||||
addPrefix "align-self" << alignmentToString
|
||||
|
||||
|
||||
{-| -}
|
||||
flexBasis : Css.Length compatible units -> Style
|
||||
flexBasis =
|
||||
addPrefix "flex-basis" << .value
|
||||
|
||||
|
||||
{-| -}
|
||||
flexGrow : Float -> Style
|
||||
flexGrow value =
|
||||
addPrefix "flex-grow" (toString value)
|
||||
|
||||
|
||||
{-| -}
|
||||
flexShrink : Float -> Style
|
||||
flexShrink value =
|
||||
addPrefix "flex-shrink" (toString value)
|
||||
|
||||
|
||||
{-| -}
|
||||
flexWrap : Wrap -> Style
|
||||
flexWrap value =
|
||||
addPrefix "flex-wrap" <|
|
||||
case value of
|
||||
Nowrap ->
|
||||
"nowrap"
|
||||
|
||||
Wrap ->
|
||||
"wrap"
|
||||
|
||||
WrapReverse ->
|
||||
"wrap-reverse"
|
||||
|
||||
|
||||
type Wrap
|
||||
= Nowrap
|
||||
| Wrap
|
||||
| WrapReverse
|
||||
|
||||
|
||||
{-| flex-wrap nowrap
|
||||
-}
|
||||
nowrap : Wrap
|
||||
nowrap =
|
||||
Nowrap
|
||||
|
||||
|
||||
{-| flex-wrap wrap
|
||||
-}
|
||||
wrap : Wrap
|
||||
wrap =
|
||||
Wrap
|
||||
|
||||
|
||||
{-| flex-wrap wrapReverse
|
||||
-}
|
||||
wrapReverse : Wrap
|
||||
wrapReverse =
|
||||
WrapReverse
|
||||
|
||||
|
||||
type Alignment justify align
|
||||
= FlexStart justify align
|
||||
| FlexEnd justify align
|
||||
| Center justify align
|
||||
| SpaceBetween justify
|
||||
| SpaceAround justify
|
||||
| Baseline align
|
||||
| Stretch align
|
||||
|
||||
|
||||
alignmentToString : Alignment a b -> String
|
||||
alignmentToString value =
|
||||
case value of
|
||||
FlexStart _ _ ->
|
||||
"flex-start"
|
||||
|
||||
FlexEnd _ _ ->
|
||||
"flex-end"
|
||||
|
||||
Center _ _ ->
|
||||
"center"
|
||||
|
||||
SpaceBetween _ ->
|
||||
"space-between"
|
||||
|
||||
SpaceAround _ ->
|
||||
"space-around"
|
||||
|
||||
Baseline _ ->
|
||||
"baseline"
|
||||
|
||||
Stretch _ ->
|
||||
"stretch"
|
||||
|
||||
|
||||
type JustifyContent
|
||||
= JustifyContent
|
||||
|
||||
|
||||
type AlignItems
|
||||
= AlignItems
|
||||
|
||||
|
||||
{-| align-items/justify-content flexStart
|
||||
-}
|
||||
flexStart : Alignment JustifyContent AlignItems
|
||||
flexStart =
|
||||
FlexStart JustifyContent AlignItems
|
||||
|
||||
|
||||
{-| align-items/justify-content flexEnd
|
||||
-}
|
||||
flexEnd : Alignment JustifyContent AlignItems
|
||||
flexEnd =
|
||||
FlexEnd JustifyContent AlignItems
|
||||
|
||||
|
||||
{-| align-items/justify-content center
|
||||
-}
|
||||
center : Alignment JustifyContent AlignItems
|
||||
center =
|
||||
Center JustifyContent AlignItems
|
||||
|
||||
|
||||
{-| justify-content spaceBetween
|
||||
-}
|
||||
spaceBetween : Alignment JustifyContent Never
|
||||
spaceBetween =
|
||||
SpaceBetween JustifyContent
|
||||
|
||||
|
||||
{-| justify-content spaceAround
|
||||
-}
|
||||
spaceAround : Alignment JustifyContent Never
|
||||
spaceAround =
|
||||
SpaceAround JustifyContent
|
||||
|
||||
|
||||
{-| align-items baseline
|
||||
-}
|
||||
baseline : Alignment Never AlignItems
|
||||
baseline =
|
||||
Baseline AlignItems
|
||||
|
||||
|
||||
{-| align-items stretch
|
||||
-}
|
||||
stretch : Alignment Never AlignItems
|
||||
stretch =
|
||||
Stretch AlignItems
|
||||
|
||||
|
||||
addPrefix : String -> String -> Style
|
||||
addPrefix propertyName value =
|
||||
batch
|
||||
[ property ("-webkit-" ++ propertyName) value
|
||||
, property propertyName value
|
||||
, property ("-ms-" ++ propertyName) value
|
||||
]
|
@ -1,53 +0,0 @@
|
||||
module Nri.Ui.Data.PremiumLevel exposing (PremiumLevel(..), allowedFor, highest, lowest)
|
||||
|
||||
{-|
|
||||
|
||||
@docs PremiumLevel, allowedFor, highest, lowest
|
||||
|
||||
-}
|
||||
|
||||
|
||||
{-| -}
|
||||
type PremiumLevel
|
||||
= Free
|
||||
| Premium
|
||||
| PremiumWithWriting
|
||||
|
||||
|
||||
{-| Is content of the required premium level accessbile by the actor?
|
||||
-}
|
||||
allowedFor : PremiumLevel -> PremiumLevel -> Bool
|
||||
allowedFor requirement actor =
|
||||
order requirement <= order actor
|
||||
|
||||
|
||||
{-| The highest premium level in a list
|
||||
-}
|
||||
highest : List PremiumLevel -> Maybe PremiumLevel
|
||||
highest privileges =
|
||||
privileges
|
||||
|> List.sortBy order
|
||||
|> List.reverse
|
||||
|> List.head
|
||||
|
||||
|
||||
{-| The lowest premium level in a list
|
||||
-}
|
||||
lowest : List PremiumLevel -> Maybe PremiumLevel
|
||||
lowest privileges =
|
||||
privileges
|
||||
|> List.sortBy order
|
||||
|> List.head
|
||||
|
||||
|
||||
order : PremiumLevel -> Int
|
||||
order privileges =
|
||||
case privileges of
|
||||
PremiumWithWriting ->
|
||||
2
|
||||
|
||||
Premium ->
|
||||
1
|
||||
|
||||
Free ->
|
||||
0
|
@ -1,35 +0,0 @@
|
||||
module Nri.Ui.DatePickerConstants exposing
|
||||
( datePickerTag
|
||||
, dialogTag
|
||||
, footerTag
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs datePickerTag
|
||||
@docs dialogTag
|
||||
@docs footerTag
|
||||
|
||||
-}
|
||||
|
||||
|
||||
{-| The class of the entire date picker
|
||||
-}
|
||||
datePickerTag : String
|
||||
datePickerTag =
|
||||
"date-time-picker"
|
||||
|
||||
|
||||
{-| The class of just the dialog that shows up when you open the datepicker
|
||||
-}
|
||||
dialogTag : String
|
||||
dialogTag =
|
||||
"date-time-picker-dialog"
|
||||
|
||||
|
||||
{-| The class of the footer in the dialog.
|
||||
This is where the pretty-printed date is displayed.
|
||||
-}
|
||||
footerTag : String
|
||||
footerTag =
|
||||
"date-time-picker-footer"
|
@ -1,89 +0,0 @@
|
||||
module Nri.Ui.DisclosureIndicator.V1 exposing (view, viewInline)
|
||||
|
||||
{-| A caret that indicates that a section can expand. When the isOpen attribute is passed in as True, it will rotate. A "disclosure indicator" is a standard term for something that indicates that section can expand.
|
||||
|
||||
@docs view, viewInline
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
import Html.Styled as Html exposing (..)
|
||||
import Html.Styled.Attributes as Attributes exposing (alt, type_)
|
||||
import Nri.Ui.AssetPath as AssetPath
|
||||
|
||||
|
||||
type alias Config =
|
||||
{ isOpen : Bool
|
||||
, label : String
|
||||
}
|
||||
|
||||
|
||||
type alias Assets r =
|
||||
{ r
|
||||
| icons_arrowDownBlue_svg : AssetPath.Asset
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
view : Assets a -> Config -> Html msg
|
||||
view =
|
||||
viewWithStyle headerStyle
|
||||
|
||||
|
||||
{-| The inline variant of the indicator is smaller and occupies
|
||||
less vertical space so it can be inlined in lists or tables
|
||||
without breaking text flow. Also, it rotates from right to
|
||||
down direction when expanding.
|
||||
-}
|
||||
viewInline : Assets a -> Config -> Html msg
|
||||
viewInline =
|
||||
viewWithStyle inlineStyle
|
||||
|
||||
|
||||
viewWithStyle : (Bool -> Css.Style) -> Assets a -> Config -> Html msg
|
||||
viewWithStyle style assets config =
|
||||
let
|
||||
label =
|
||||
if config.isOpen then
|
||||
"hide " ++ config.label
|
||||
|
||||
else
|
||||
"show " ++ config.label
|
||||
in
|
||||
img
|
||||
[ alt label
|
||||
, Attributes.src <| AssetPath.url <| assets.icons_arrowDownBlue_svg
|
||||
, Attributes.css [ style config.isOpen ]
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
headerStyle : Bool -> Css.Style
|
||||
headerStyle isOpen =
|
||||
Css.batch
|
||||
[ marginRight (px 10)
|
||||
, width (px 15)
|
||||
, height (px 15)
|
||||
, cursor pointer
|
||||
, property "transition" "transform 0.2s"
|
||||
, if isOpen then
|
||||
transform (rotate <| deg 0)
|
||||
|
||||
else
|
||||
transform (rotate <| deg -90)
|
||||
]
|
||||
|
||||
|
||||
inlineStyle : Bool -> Css.Style
|
||||
inlineStyle isOpen =
|
||||
Css.batch
|
||||
[ padding2 (px 0) (px 8)
|
||||
, height (px 9)
|
||||
, cursor pointer
|
||||
, property "transition" "transform 0.1s"
|
||||
, if isOpen then
|
||||
transform (rotate <| deg 0)
|
||||
|
||||
else
|
||||
transform (rotate <| deg -90)
|
||||
]
|
@ -1,67 +0,0 @@
|
||||
module Nri.Ui.Divider.V2 exposing (view)
|
||||
|
||||
{-| <https://staging.noredink.com/style_guide#ui/src/Nri/Divider.elm>
|
||||
|
||||
@docs view
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
import Html.Styled as Html exposing (..)
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
|
||||
|
||||
type alias Config =
|
||||
{ lineColor : Css.Color
|
||||
, textColor : Css.Color
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
view : String -> Html msg
|
||||
view text =
|
||||
Html.styled div
|
||||
[ containerStyles ]
|
||||
[]
|
||||
[ Html.styled div [ leftLineStyles ] [] []
|
||||
, Html.styled div [ titleStyles ] [] [ Html.text text ]
|
||||
, Html.styled div [ rightLineStyles ] [] []
|
||||
]
|
||||
|
||||
|
||||
containerStyles : Style
|
||||
containerStyles =
|
||||
batch
|
||||
[ Css.width (pct 100)
|
||||
, Css.displayFlex
|
||||
, Css.alignItems Css.center
|
||||
]
|
||||
|
||||
|
||||
leftLineStyles : Style
|
||||
leftLineStyles =
|
||||
batch
|
||||
[ Css.width (px 10)
|
||||
, Css.height (px 1)
|
||||
, backgroundColor Colors.gray75
|
||||
, marginTop (px 2)
|
||||
]
|
||||
|
||||
|
||||
rightLineStyles : Style
|
||||
rightLineStyles =
|
||||
batch
|
||||
[ Css.flexGrow (Css.int 1)
|
||||
, backgroundColor Colors.gray75
|
||||
, Css.height (px 1)
|
||||
, marginTop (px 2)
|
||||
]
|
||||
|
||||
|
||||
titleStyles : Style
|
||||
titleStyles =
|
||||
batch
|
||||
[ margin2 zero (px 5)
|
||||
, fontSize (px 12)
|
||||
, color Colors.gray45
|
||||
]
|
@ -1,163 +0,0 @@
|
||||
module Nri.Ui.Dropdown.V2 exposing
|
||||
( ViewOptionEntry
|
||||
, view
|
||||
, viewWithoutLabel
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs ViewOptionEntry
|
||||
@docs view
|
||||
@docs viewWithoutLabel
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled.Style exposing (invisible)
|
||||
import Css
|
||||
import Dict
|
||||
import Html.Styled as Html exposing (..)
|
||||
import Html.Styled.Attributes exposing (..)
|
||||
import Html.Styled.Events exposing (on, targetValue)
|
||||
import Json.Decode
|
||||
import Nri.Ui.Colors.V1
|
||||
import Nri.Ui.Util exposing (dashify)
|
||||
import String
|
||||
|
||||
|
||||
{-| This dropdown has atypical select tag behavior.
|
||||
|
||||
This dropdown, when closed, will display some default text, no matter
|
||||
what is actually selected.
|
||||
|
||||
When the dropdown is opened, the first option will display that default text,
|
||||
be selected, and disabled. The option the user has actually chosen's displayText
|
||||
won't show up at all.
|
||||
|
||||
-}
|
||||
type alias ViewOptionEntry a =
|
||||
{ isSelected : Bool
|
||||
, val : a
|
||||
, displayText : String
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
view : String -> List (ViewOptionEntry a) -> (a -> msg) -> Html msg
|
||||
view defaultDisplayText optionEntries onSelect =
|
||||
viewWithLabelMarkup True defaultDisplayText optionEntries onSelect
|
||||
|
||||
|
||||
{-| -}
|
||||
viewWithoutLabel : String -> List (ViewOptionEntry a) -> (a -> msg) -> Html msg
|
||||
viewWithoutLabel defaultDisplayText optionEntries onSelect =
|
||||
viewWithLabelMarkup False defaultDisplayText optionEntries onSelect
|
||||
|
||||
|
||||
viewWithLabelMarkup : Bool -> String -> List (ViewOptionEntry a) -> (a -> msg) -> Html msg
|
||||
viewWithLabelMarkup displayLabel defaultDisplayText optionEntries onSelect =
|
||||
let
|
||||
defaultOption =
|
||||
option
|
||||
[ selected True
|
||||
, disabled True
|
||||
]
|
||||
[ text defaultDisplayText ]
|
||||
|
||||
options =
|
||||
List.map (viewOption defaultDisplayText) optionEntries
|
||||
|
||||
identifier =
|
||||
dashify (String.toLower defaultDisplayText)
|
||||
|
||||
changeHandlers : List (Attribute msg)
|
||||
changeHandlers =
|
||||
case optionEntries of
|
||||
[] ->
|
||||
-- If we have no entries, there's no point in having
|
||||
-- a change handler; it could never fire anyway.
|
||||
[]
|
||||
|
||||
{ val } :: _ ->
|
||||
let
|
||||
-- When we get a `String` from the `onChange` event,
|
||||
-- look up the `msg` that goes with it.
|
||||
msgForValue : String -> msg
|
||||
msgForValue valString =
|
||||
case Dict.get valString msgsByVal of
|
||||
Just msg ->
|
||||
msg
|
||||
|
||||
Nothing ->
|
||||
-- If it's somehow not in the Dict
|
||||
-- (which should never happen),
|
||||
-- fall back on a known `msg` value:
|
||||
-- the first one in the list.
|
||||
onSelect val
|
||||
|
||||
msgsByVal : Dict.Dict String msg
|
||||
msgsByVal =
|
||||
optionEntries
|
||||
|> List.map (\{ val } -> ( toString val, onSelect val ))
|
||||
|> Dict.fromList
|
||||
in
|
||||
[ on "change" (Json.Decode.map msgForValue targetValue) ]
|
||||
in
|
||||
span []
|
||||
[ label
|
||||
(if displayLabel then
|
||||
[ for identifier ]
|
||||
|
||||
else
|
||||
[ for identifier, invisible ]
|
||||
)
|
||||
[ text defaultDisplayText ]
|
||||
, Html.styled select
|
||||
[ dropdownStyles ]
|
||||
([ id identifier
|
||||
, {-
|
||||
NOTE: form controls are also being styled on a global CSS that
|
||||
sets a margin.
|
||||
|
||||
It would be better to remove the margin from the component and
|
||||
decide whether we need it or not in each use case.
|
||||
|
||||
It will be really hard to track down and review all of those,
|
||||
so we reset the margin here as a workaround.
|
||||
-}
|
||||
style [ ( "margin", "0" ) ]
|
||||
]
|
||||
++ changeHandlers
|
||||
)
|
||||
(defaultOption :: options)
|
||||
]
|
||||
|
||||
|
||||
viewOption : String -> ViewOptionEntry a -> Html msg
|
||||
viewOption defaultDisplayText { isSelected, val, displayText } =
|
||||
if isSelected then
|
||||
option
|
||||
[ value <| toString val
|
||||
, selected isSelected
|
||||
, style [ ( "display", "none" ) ]
|
||||
]
|
||||
[ text defaultDisplayText ]
|
||||
|
||||
else
|
||||
option
|
||||
[ value <| toString val
|
||||
, selected isSelected
|
||||
]
|
||||
[ text displayText ]
|
||||
|
||||
|
||||
dropdownStyles : Css.Style
|
||||
dropdownStyles =
|
||||
Css.batch
|
||||
[ Css.backgroundColor Nri.Ui.Colors.V1.white
|
||||
, Css.border3 (Css.px 1) Css.solid Nri.Ui.Colors.V1.gray75
|
||||
, Css.borderRadius (Css.px 8)
|
||||
, Css.color Nri.Ui.Colors.V1.gray20
|
||||
, Css.cursor Css.pointer
|
||||
, Css.fontSize (Css.px 15)
|
||||
, Css.height (Css.px 45)
|
||||
]
|
@ -1,35 +0,0 @@
|
||||
module Nri.Ui.Effects.V1 exposing (selectionShadow)
|
||||
|
||||
{-| Css mixins reused across Nri modules.
|
||||
|
||||
@docs selectionShadow
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
import Nri.Ui.Colors.V1
|
||||
|
||||
|
||||
{-| Draw a 2 px thick ochre border around the element to indicate it is
|
||||
selected.
|
||||
|
||||
This uses a CSS box shadow to draw what looks like a border. Box shadows are
|
||||
perfect for this because they don't affect the elements positioning in any way.
|
||||
This means we can be sure switching the selection shadow on and off is not
|
||||
going to make the element jump.
|
||||
|
||||
-}
|
||||
selectionShadow : List Style
|
||||
selectionShadow =
|
||||
-- There should appear to be 2px of space between the element outline and
|
||||
-- the surrounding selection border. To accomplish this we use two box
|
||||
-- shadows, a inner white shadow and an outer ochre one.
|
||||
-- Elm-css does not support multiple box shadows, so we build up that the
|
||||
-- CSS value manually.
|
||||
[ Css.property "box-shadow" ("0 0 0 2px white, 0 0 0 4px " ++ colorToString Nri.Ui.Colors.V1.ochre)
|
||||
]
|
||||
|
||||
|
||||
colorToString : Css.Color -> String
|
||||
colorToString { red, green, blue } =
|
||||
String.concat [ "rgb(", toString red, ",", toString green, ",", toString blue, ")" ]
|
@ -1,30 +0,0 @@
|
||||
module Nri.Ui.Fonts.V1 exposing (baseFont, quizFont, ugFont)
|
||||
|
||||
{-| Fonts for NoRedInk projects
|
||||
|
||||
@docs baseFont, quizFont, ugFont
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
|
||||
|
||||
{-| Font for instructions, headers, and pretty much everything else
|
||||
-}
|
||||
baseFont : Style
|
||||
baseFont =
|
||||
fontFamilies [ qt "Muli", "Helvetica", "Arial", "sans-serif" ]
|
||||
|
||||
|
||||
{-| Font for question sentences, or most interactable or graded fields
|
||||
-}
|
||||
quizFont : Style
|
||||
quizFont =
|
||||
fontFamilies [ qt "Georgia", "serif" ]
|
||||
|
||||
|
||||
{-| Font for displaying user-generated content.
|
||||
-}
|
||||
ugFont : Style
|
||||
ugFont =
|
||||
fontFamilies [ qt "Georgia", "serif" ]
|
@ -1,39 +0,0 @@
|
||||
module Nri.Ui.Html.Attributes.Extra exposing (none, includeIf)
|
||||
|
||||
{-| Extras for working with Html.Attributes
|
||||
|
||||
@docs none, includeIf
|
||||
|
||||
-}
|
||||
|
||||
import Html exposing (Attribute)
|
||||
import Html.Attributes as Attributes
|
||||
import Json.Encode as Encode
|
||||
|
||||
|
||||
{-| Represents an attribute with no semantic meaning, useful for conditionals.
|
||||
|
||||
This is implemented such that whenever Html.Attributes.Extra.none is encountered
|
||||
by VirtualDom it will set a meaningless property on the element object itself to
|
||||
null:
|
||||
|
||||
domNode['Html.Attributes.Extra.none'] = null
|
||||
|
||||
It's totally safe and lets us clean up conditional and maybe attributes
|
||||
|
||||
-}
|
||||
none : Attribute msg
|
||||
none =
|
||||
Attributes.property "Html.Attributes.Extra.none" Encode.null
|
||||
|
||||
|
||||
{-| conditionally include an attribute. Useful for CSS classes generated with
|
||||
`UniqueClass`!
|
||||
-}
|
||||
includeIf : Bool -> Attribute msg -> Attribute msg
|
||||
includeIf cond attr =
|
||||
if cond then
|
||||
attr
|
||||
|
||||
else
|
||||
none
|
@ -1,41 +0,0 @@
|
||||
module Nri.Ui.Html.Attributes.V2 exposing (none, includeIf)
|
||||
|
||||
{-| Extras for working with Html.Attributes.
|
||||
|
||||
This is the new version of Nri.Ui.Html.Attributes.Extra.
|
||||
|
||||
@docs none, includeIf
|
||||
|
||||
-}
|
||||
|
||||
import Html.Styled exposing (Attribute)
|
||||
import Html.Styled.Attributes as Attributes
|
||||
import Json.Encode as Encode
|
||||
|
||||
|
||||
{-| Represents an attribute with no semantic meaning, useful for conditionals.
|
||||
|
||||
This is implemented such that whenever Html.Attributes.Extra.none is encountered
|
||||
by VirtualDom it will set a meaningless property on the element object itself to
|
||||
null:
|
||||
|
||||
domNode['Html.Attributes.Extra.none'] = null
|
||||
|
||||
It's totally safe and lets us clean up conditional and maybe attributes
|
||||
|
||||
-}
|
||||
none : Attribute msg
|
||||
none =
|
||||
Attributes.property "Html.Attributes.Extra.none" Encode.null
|
||||
|
||||
|
||||
{-| conditionally include an attribute. Useful for CSS classes generated with
|
||||
`UniqueClass`!
|
||||
-}
|
||||
includeIf : Bool -> Attribute msg -> Attribute msg
|
||||
includeIf cond attr =
|
||||
if cond then
|
||||
attr
|
||||
|
||||
else
|
||||
none
|
@ -1,136 +0,0 @@
|
||||
module Nri.Ui.Html.V3 exposing
|
||||
( role
|
||||
, onEsc, onEnter, onKeyUp, onEnterAndSpace
|
||||
, textFromList, oxfordifyWithHtml, nbsp
|
||||
)
|
||||
|
||||
{-| For all utils involving HTML. New version of Nri.Ui.Html.Extra.
|
||||
|
||||
@docs role
|
||||
|
||||
@docs onEsc, onEnter, onKeyUp, onEnterAndSpace
|
||||
|
||||
@docs textFromList, oxfordifyWithHtml, nbsp
|
||||
|
||||
-}
|
||||
|
||||
import Char
|
||||
import Html.Styled as Html exposing (Attribute, Html, span, text)
|
||||
import Html.Styled.Attributes exposing (..)
|
||||
import Html.Styled.Events exposing (..)
|
||||
import Json.Decode
|
||||
|
||||
|
||||
{-| Convenience for defining role attributes, e.g. <div role="tabpanel">
|
||||
-}
|
||||
role : String -> Attribute msg
|
||||
role =
|
||||
attribute "role"
|
||||
|
||||
|
||||
{-| -}
|
||||
onEsc : a -> a -> Attribute a
|
||||
onEsc onEscAction onOtherKey =
|
||||
on "keyup"
|
||||
(Json.Decode.map
|
||||
(\keyCode ->
|
||||
if keyCode == 27 then
|
||||
onEscAction
|
||||
|
||||
else
|
||||
onOtherKey
|
||||
)
|
||||
keyCode
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
onEnter : a -> Attribute a
|
||||
onEnter onEnterAction =
|
||||
onKeyUp defaultOptions
|
||||
(\keyCode ->
|
||||
if keyCode == 13 then
|
||||
Just onEnterAction
|
||||
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
|
||||
|
||||
{-| "Buttons" should trigger on Enter and on Space.
|
||||
-}
|
||||
onEnterAndSpace : msg -> Attribute msg
|
||||
onEnterAndSpace msg =
|
||||
onKeyUp defaultOptions
|
||||
(\keyCode ->
|
||||
if keyCode == 13 || keyCode == 32 then
|
||||
Just msg
|
||||
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
|
||||
|
||||
{-| Convert a keycode into a message on keyup
|
||||
-}
|
||||
onKeyUp : Options -> (Int -> Maybe a) -> Attribute a
|
||||
onKeyUp options toMaybeMsg =
|
||||
onWithOptions "keyup" options <|
|
||||
Json.Decode.andThen
|
||||
(\keyCode ->
|
||||
keyCode
|
||||
|> toMaybeMsg
|
||||
|> Maybe.map Json.Decode.succeed
|
||||
|> Maybe.withDefault (Json.Decode.fail (toString keyCode))
|
||||
)
|
||||
keyCode
|
||||
|
||||
|
||||
{-| Takes a list of strings, joins them with a space and returns it as a Html.text.
|
||||
textFromList ["Hello", "World"] == text [ String.join " " ["Hello", "World" ] ]
|
||||
-}
|
||||
textFromList : List String -> Html msg
|
||||
textFromList =
|
||||
String.join " " >> text
|
||||
|
||||
|
||||
{-| -}
|
||||
oxfordifyWithHtml : String -> String -> List (Html msg) -> List (Html msg)
|
||||
oxfordifyWithHtml pre post items =
|
||||
let
|
||||
textSpan string =
|
||||
span [] [ text string ]
|
||||
|
||||
final centrals =
|
||||
[ textSpan pre ] ++ centrals ++ [ textSpan post ]
|
||||
in
|
||||
case items of
|
||||
[] ->
|
||||
[]
|
||||
|
||||
[ single ] ->
|
||||
final [ single ]
|
||||
|
||||
[ first, second ] ->
|
||||
final [ first, textSpan " and ", second ]
|
||||
|
||||
many ->
|
||||
let
|
||||
beforeAnd =
|
||||
List.take (List.length many - 1) many
|
||||
|
||||
afterAnd =
|
||||
List.drop (List.length many - 1) many
|
||||
|> List.head
|
||||
|> Maybe.withDefault (textSpan "")
|
||||
in
|
||||
final (List.intersperse (textSpan ", ") beforeAnd ++ [ textSpan ", and ", afterAnd ])
|
||||
|
||||
|
||||
{-| Workaround for `Html.text " "` not working in elm.
|
||||
-}
|
||||
nbsp : Html msg
|
||||
nbsp =
|
||||
Char.fromCode 160
|
||||
|> String.fromChar
|
||||
|> Html.text
|
@ -1,900 +0,0 @@
|
||||
module Nri.Ui.Icon.V3 exposing
|
||||
( icon, decorativeIcon, link, linkExternal, linkSpa, button
|
||||
, IconType, IconSize(..), IconLinkSpaModel
|
||||
, activity
|
||||
, add
|
||||
, arrowDown
|
||||
, arrowLeft
|
||||
, arrowRight
|
||||
, assignmentStartButtonPrimary
|
||||
, assignmentStartButtonSecondary
|
||||
, assignmentTypeDiagnostic
|
||||
, assignmentTypePeerReview
|
||||
, assignmentTypeSelfReview
|
||||
, assignmentTypePractice
|
||||
, assignmentTypeQuickWrite
|
||||
, assignmentTypeQuiz
|
||||
, assignmentTypeWritingCycle
|
||||
, attention
|
||||
, bang
|
||||
, bulb
|
||||
, calendar
|
||||
, caret
|
||||
, checkMark
|
||||
, checkMarkSquiggily
|
||||
, checkMarkSvg
|
||||
, class
|
||||
, clever
|
||||
, clock
|
||||
, close
|
||||
, compassSvg
|
||||
, copy
|
||||
, custom
|
||||
, darkBlueCheckMark
|
||||
, document
|
||||
, download
|
||||
, edit
|
||||
, editWriting
|
||||
, equalitySign
|
||||
, exclamation
|
||||
, facebook
|
||||
, flag
|
||||
, flipper
|
||||
, footsteps
|
||||
, gardening
|
||||
, gear
|
||||
, greenCheckMark
|
||||
, guidedWrite
|
||||
, hat
|
||||
, help
|
||||
, helpSvg
|
||||
, highFive
|
||||
, key
|
||||
, keychain
|
||||
, late
|
||||
, leaderboard
|
||||
, lightBulb
|
||||
, lock
|
||||
, lockDeprecated
|
||||
, logo
|
||||
, masteryBadge
|
||||
, newspaper
|
||||
, notStarred
|
||||
, okay
|
||||
, openClose
|
||||
, peerReview
|
||||
, pen
|
||||
, performance
|
||||
, personBlue
|
||||
, preview
|
||||
, quickWrite
|
||||
, seeMore
|
||||
, share
|
||||
, skip
|
||||
, sort
|
||||
, sortArrow
|
||||
, speedometer
|
||||
, starred
|
||||
, submitting, rating, revising
|
||||
, thumbsUp
|
||||
, twitter
|
||||
, unarchive
|
||||
, writingAssignment
|
||||
, x
|
||||
, xSvg
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs icon, decorativeIcon, link, linkExternal, linkSpa, button
|
||||
@docs IconType, IconSize, IconLinkSpaModel
|
||||
@docs activity
|
||||
@docs add
|
||||
@docs arrowDown
|
||||
@docs arrowLeft
|
||||
@docs arrowRight
|
||||
@docs assignmentStartButtonPrimary
|
||||
@docs assignmentStartButtonSecondary
|
||||
@docs assignmentTypeDiagnostic
|
||||
@docs assignmentTypePeerReview
|
||||
@docs assignmentTypeSelfReview
|
||||
@docs assignmentTypePractice
|
||||
@docs assignmentTypeQuickWrite
|
||||
@docs assignmentTypeQuiz
|
||||
@docs assignmentTypeWritingCycle
|
||||
@docs attention
|
||||
@docs bang
|
||||
@docs bulb
|
||||
@docs calendar
|
||||
@docs caret
|
||||
@docs checkMark
|
||||
@docs checkMarkSquiggily
|
||||
@docs checkMarkSvg
|
||||
@docs class
|
||||
@docs clever
|
||||
@docs clock
|
||||
@docs close
|
||||
@docs compassSvg
|
||||
@docs copy
|
||||
@docs custom
|
||||
@docs darkBlueCheckMark
|
||||
@docs document
|
||||
@docs download
|
||||
@docs edit
|
||||
@docs editWriting
|
||||
@docs equalitySign
|
||||
@docs exclamation
|
||||
@docs facebook
|
||||
@docs flag
|
||||
@docs flipper
|
||||
@docs footsteps
|
||||
@docs gardening
|
||||
@docs gear
|
||||
@docs greenCheckMark
|
||||
@docs guidedWrite
|
||||
@docs hat
|
||||
@docs help
|
||||
@docs helpSvg
|
||||
@docs highFive
|
||||
@docs key
|
||||
@docs keychain
|
||||
@docs late
|
||||
@docs leaderboard
|
||||
@docs lightBulb
|
||||
@docs lock
|
||||
@docs lockDeprecated
|
||||
@docs logo
|
||||
@docs masteryBadge
|
||||
@docs newspaper
|
||||
@docs notStarred
|
||||
@docs okay
|
||||
@docs openClose
|
||||
@docs peerReview
|
||||
@docs pen
|
||||
@docs performance
|
||||
@docs personBlue
|
||||
@docs preview
|
||||
@docs quickWrite
|
||||
@docs seeMore
|
||||
@docs share
|
||||
@docs skip
|
||||
@docs sort
|
||||
@docs sortArrow
|
||||
@docs speedometer
|
||||
@docs starred
|
||||
@docs submitting, rating, revising
|
||||
@docs thumbsUp
|
||||
@docs twitter
|
||||
@docs unarchive
|
||||
@docs writingAssignment
|
||||
@docs x
|
||||
@docs xSvg
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Role as Role
|
||||
import Accessibility.Styled exposing (..)
|
||||
import Css exposing (..)
|
||||
import EventExtras
|
||||
import Html as RootHtml
|
||||
import Html.Attributes as RootAttr exposing (..)
|
||||
import Html.Styled
|
||||
import Html.Styled.Attributes as Attributes exposing (css)
|
||||
import Html.Styled.Events as Events
|
||||
import Nri.Ui.AssetPath exposing (Asset(..))
|
||||
import Nri.Ui.Colors.V1
|
||||
import Svg exposing (svg, use)
|
||||
import Svg.Attributes exposing (xlinkHref)
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias IconLinkModel =
|
||||
{ alt : String
|
||||
, url : String
|
||||
, icon : IconType
|
||||
, disabled : Bool
|
||||
, size : IconSize
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias IconLinkSpaModel route =
|
||||
{ alt : String
|
||||
, icon : IconType
|
||||
, disabled : Bool
|
||||
, size : IconSize
|
||||
, route : route
|
||||
}
|
||||
|
||||
|
||||
type alias IconButtonModel msg =
|
||||
{ alt : String
|
||||
, msg : msg
|
||||
, icon : IconType
|
||||
, disabled : Bool
|
||||
, size : IconSize
|
||||
}
|
||||
|
||||
|
||||
{-| An icon that can be rendered using the functions provided by this module.
|
||||
-}
|
||||
type IconType
|
||||
= ImgIcon Asset
|
||||
| SvgIcon String
|
||||
|
||||
|
||||
{-| Used for determining sizes on Icon.buttons and Icon.links
|
||||
-}
|
||||
type IconSize
|
||||
= Small
|
||||
| Medium
|
||||
|
||||
|
||||
{-| Create an icon that links to a part of NRI
|
||||
Uses our default icon styles (25 x 25 px, azure)
|
||||
-}
|
||||
link : IconLinkModel -> Html msg
|
||||
link =
|
||||
linkBase [ Attributes.target "_self" ]
|
||||
|
||||
|
||||
{-| Create an accessible icon button with an onClick handler
|
||||
Uses our default icon styles (25 x 25 px, azure)
|
||||
-}
|
||||
button : IconButtonModel msg -> Html msg
|
||||
button model =
|
||||
Accessibility.Styled.button
|
||||
[ css
|
||||
[ Css.batch
|
||||
[ backgroundColor transparent
|
||||
, border zero
|
||||
, color Nri.Ui.Colors.V1.azure
|
||||
, fontFamily inherit
|
||||
, Css.property "cursor" "pointer"
|
||||
, padding zero
|
||||
, focus
|
||||
[ backgroundColor transparent
|
||||
]
|
||||
]
|
||||
, sizeStyles model.size
|
||||
]
|
||||
, Events.onClick model.msg
|
||||
, Attributes.disabled model.disabled
|
||||
, Attributes.type_ "button"
|
||||
]
|
||||
[ icon
|
||||
{ alt = model.alt
|
||||
, icon = model.icon
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
icon : { alt : String, icon : IconType } -> Html msg
|
||||
icon config =
|
||||
case config.icon of
|
||||
SvgIcon iconId ->
|
||||
svg [ svgStyle ]
|
||||
[ Svg.title [] [ RootHtml.text config.alt ]
|
||||
, use [ xlinkHref ("#" ++ iconId) ] []
|
||||
]
|
||||
|> Html.Styled.fromUnstyled
|
||||
|
||||
ImgIcon assetPath ->
|
||||
img config.alt
|
||||
[ Attributes.src (Nri.Ui.AssetPath.url assetPath)
|
||||
]
|
||||
|
||||
|
||||
{-| Use this icon for purely decorative content that would be distracting
|
||||
rather than helpful on a screenreader.
|
||||
-}
|
||||
decorativeIcon : IconType -> Html msg
|
||||
decorativeIcon iconType =
|
||||
case iconType of
|
||||
SvgIcon iconId ->
|
||||
svg
|
||||
[ svgStyle
|
||||
, Role.img
|
||||
]
|
||||
[ use [ xlinkHref ("#" ++ iconId) ] []
|
||||
]
|
||||
|> Html.Styled.fromUnstyled
|
||||
|
||||
ImgIcon assetPath ->
|
||||
decorativeImg [ Attributes.src (Nri.Ui.AssetPath.url assetPath) ]
|
||||
|
||||
|
||||
{-| Use this link for routing within a single page app.
|
||||
|
||||
This will make a normal <a> tag, but change the onClick behavior to avoid reloading the page.
|
||||
|
||||
-}
|
||||
linkSpa : (route -> String) -> (route -> msg) -> IconLinkSpaModel route -> Html msg
|
||||
linkSpa toUrl toMsg config =
|
||||
linkBase
|
||||
[ EventExtras.onClickPreventDefaultForLinkWithHref (toMsg config.route)
|
||||
|> Attributes.fromUnstyled
|
||||
]
|
||||
{ alt = config.alt
|
||||
, url = toUrl config.route
|
||||
, icon = config.icon
|
||||
, disabled = config.disabled
|
||||
, size = config.size
|
||||
}
|
||||
|
||||
|
||||
{-| Create an icon that links to an external site
|
||||
Uses our default icon styles (25 x 25 px, azure)
|
||||
-}
|
||||
linkExternal : IconLinkModel -> Html msg
|
||||
linkExternal =
|
||||
linkBase [ Attributes.target "_blank" ]
|
||||
|
||||
|
||||
linkBase : List (Attribute msg) -> IconLinkModel -> Html msg
|
||||
linkBase linkAttributes model =
|
||||
span
|
||||
[]
|
||||
[ Html.Styled.a
|
||||
(linkAttributes ++ defaultLinkAttributes model)
|
||||
[ icon { alt = model.alt, icon = model.icon }
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
defaultLinkAttributes : IconLinkModel -> List (Attribute msg)
|
||||
defaultLinkAttributes model =
|
||||
if model.disabled then
|
||||
[ css
|
||||
[ Css.cursor Css.notAllowed
|
||||
, linkStyles
|
||||
, sizeStyles model.size
|
||||
]
|
||||
]
|
||||
|
||||
else
|
||||
[ css [ linkStyles, sizeStyles model.size ]
|
||||
, Attributes.href model.url
|
||||
]
|
||||
|
||||
|
||||
linkStyles : Style
|
||||
linkStyles =
|
||||
Css.batch
|
||||
[ color Nri.Ui.Colors.V1.azure
|
||||
, display inlineBlock
|
||||
, fontFamily inherit
|
||||
, Css.property "cursor" "pointer"
|
||||
, padding zero
|
||||
, visited [ color Nri.Ui.Colors.V1.azure ]
|
||||
]
|
||||
|
||||
|
||||
sizeStyles : IconSize -> Style
|
||||
sizeStyles size =
|
||||
Css.batch <|
|
||||
case size of
|
||||
Small ->
|
||||
[ Css.width (px 20)
|
||||
, Css.height (px 20)
|
||||
]
|
||||
|
||||
Medium ->
|
||||
[ Css.width (px 25)
|
||||
, Css.height (px 25)
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
activity : { r | activity : String } -> IconType
|
||||
activity assets =
|
||||
SvgIcon assets.activity
|
||||
|
||||
|
||||
{-| -}
|
||||
add : { r | icons_plusBlue_svg : Asset } -> IconType
|
||||
add assets =
|
||||
ImgIcon assets.icons_plusBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
arrowDown : { r | arrowDown : String } -> IconType
|
||||
arrowDown assets =
|
||||
SvgIcon assets.arrowDown
|
||||
|
||||
|
||||
{-| -}
|
||||
arrowLeft : { r | leftArrowBlue_png : Asset } -> IconType
|
||||
arrowLeft assets =
|
||||
ImgIcon assets.leftArrowBlue_png
|
||||
|
||||
|
||||
{-| -}
|
||||
arrowRight : { r | icons_arrowRightBlue_svg : Asset } -> IconType
|
||||
arrowRight assets =
|
||||
ImgIcon assets.icons_arrowRightBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentStartButtonPrimary : { r | assignmentStartButtonPrimary_svg : Asset } -> IconType
|
||||
assignmentStartButtonPrimary assets =
|
||||
ImgIcon assets.assignmentStartButtonPrimary_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentStartButtonSecondary : { r | assignmentStartButtonSecondary_svg : Asset } -> IconType
|
||||
assignmentStartButtonSecondary assets =
|
||||
ImgIcon assets.assignmentStartButtonSecondary_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeDiagnostic : { r | diagnostic : String } -> IconType
|
||||
assignmentTypeDiagnostic assets =
|
||||
SvgIcon assets.diagnostic
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypePeerReview : { r | icons_peerReviewWhite_svg : Asset } -> IconType
|
||||
assignmentTypePeerReview assets =
|
||||
ImgIcon assets.icons_peerReviewWhite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeSelfReview : { r | icons_selfReviewWhite_svg : Asset } -> IconType
|
||||
assignmentTypeSelfReview assets =
|
||||
ImgIcon assets.icons_selfReviewWhite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypePractice : { r | practice : String } -> IconType
|
||||
assignmentTypePractice assets =
|
||||
SvgIcon assets.practice
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeQuickWrite : { r | icons_quickWriteWhite_svg : Asset } -> IconType
|
||||
assignmentTypeQuickWrite assets =
|
||||
ImgIcon assets.icons_quickWriteWhite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeQuiz : { r | quiz : String } -> IconType
|
||||
assignmentTypeQuiz assets =
|
||||
SvgIcon assets.quiz
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeWritingCycle : { r | writingcycle : String } -> IconType
|
||||
assignmentTypeWritingCycle assets =
|
||||
SvgIcon assets.writingcycle
|
||||
|
||||
|
||||
{-| -}
|
||||
attention : { r | attention_svg : Asset } -> IconType
|
||||
attention assets =
|
||||
ImgIcon assets.attention_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
bang : { r | exclamationPoint_svg : Asset } -> IconType
|
||||
bang assets =
|
||||
ImgIcon assets.exclamationPoint_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
bulb : { r | bulb : String } -> IconType
|
||||
bulb assets =
|
||||
SvgIcon assets.bulb
|
||||
|
||||
|
||||
{-| -}
|
||||
calendar : { r | calendar : String } -> IconType
|
||||
calendar assets =
|
||||
SvgIcon assets.calendar
|
||||
|
||||
|
||||
{-| -}
|
||||
caret : { r | icons_arrowDownBlue_svg : Asset } -> IconType
|
||||
caret assets =
|
||||
ImgIcon assets.icons_arrowDownBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
checkMark : { r | iconCheck_png : Asset } -> IconType
|
||||
checkMark assets =
|
||||
ImgIcon assets.iconCheck_png
|
||||
|
||||
|
||||
{-| -}
|
||||
checkMarkSquiggily : { r | squiggly_png : Asset } -> IconType
|
||||
checkMarkSquiggily assets =
|
||||
ImgIcon assets.squiggly_png
|
||||
|
||||
|
||||
{-| -}
|
||||
checkMarkSvg : { r | checkmark : String } -> IconType
|
||||
checkMarkSvg assets =
|
||||
SvgIcon assets.checkmark
|
||||
|
||||
|
||||
{-| -}
|
||||
class : { r | class : String } -> IconType
|
||||
class assets =
|
||||
SvgIcon assets.class
|
||||
|
||||
|
||||
{-| -}
|
||||
clever : { r | clever : String } -> IconType
|
||||
clever assets =
|
||||
SvgIcon assets.clever
|
||||
|
||||
|
||||
{-| -}
|
||||
clock : { r | clock : String } -> IconType
|
||||
clock assets =
|
||||
SvgIcon assets.clock
|
||||
|
||||
|
||||
{-| -}
|
||||
close : { r | icons_xBlue_svg : Asset } -> IconType
|
||||
close assets =
|
||||
ImgIcon assets.icons_xBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
copy : { r | teach_assignments_copyWhite_svg : Asset } -> IconType
|
||||
copy assets =
|
||||
ImgIcon assets.teach_assignments_copyWhite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
compassSvg : { r | compass : String } -> IconType
|
||||
compassSvg assets =
|
||||
SvgIcon assets.compass
|
||||
|
||||
|
||||
{-| -}
|
||||
custom : Asset -> IconType
|
||||
custom asset =
|
||||
ImgIcon asset
|
||||
|
||||
|
||||
{-| -}
|
||||
darkBlueCheckMark : { r | darkBlueCheckmark_svg : Asset } -> IconType
|
||||
darkBlueCheckMark assets =
|
||||
ImgIcon assets.darkBlueCheckmark_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
document : { r | document : String } -> IconType
|
||||
document assets =
|
||||
SvgIcon assets.document
|
||||
|
||||
|
||||
{-| -}
|
||||
download : { r | download : String } -> IconType
|
||||
download assets =
|
||||
SvgIcon assets.download
|
||||
|
||||
|
||||
{-| -}
|
||||
edit : { r | edit : String } -> IconType
|
||||
edit assets =
|
||||
SvgIcon assets.edit
|
||||
|
||||
|
||||
{-| -}
|
||||
editWriting : { r | editWriting : String } -> IconType
|
||||
editWriting assets =
|
||||
SvgIcon assets.editWriting
|
||||
|
||||
|
||||
{-| -}
|
||||
equalitySign : { r | icons_equals_svg : Asset } -> IconType
|
||||
equalitySign assets =
|
||||
ImgIcon assets.icons_equals_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
exclamation : { r | exclamation : String } -> IconType
|
||||
exclamation assets =
|
||||
SvgIcon assets.exclamation
|
||||
|
||||
|
||||
{-| -}
|
||||
facebook : { r | facebookBlue_svg : Asset } -> IconType
|
||||
facebook assets =
|
||||
ImgIcon assets.facebookBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
flag : { r | iconFlag_png : Asset } -> IconType
|
||||
flag assets =
|
||||
ImgIcon assets.iconFlag_png
|
||||
|
||||
|
||||
{-| -}
|
||||
flipper : { r | flipper : String } -> IconType
|
||||
flipper assets =
|
||||
SvgIcon assets.flipper
|
||||
|
||||
|
||||
{-| -}
|
||||
footsteps : { r | footsteps : String } -> IconType
|
||||
footsteps assets =
|
||||
SvgIcon assets.footsteps
|
||||
|
||||
|
||||
{-| -}
|
||||
gardening : { r | startingOffBadge_png : Asset } -> IconType
|
||||
gardening assets =
|
||||
ImgIcon assets.startingOffBadge_png
|
||||
|
||||
|
||||
{-| -}
|
||||
gear : { r | gear : String } -> IconType
|
||||
gear assets =
|
||||
SvgIcon assets.gear
|
||||
|
||||
|
||||
{-| -}
|
||||
greenCheckMark : { r | smallCheckmark_png : Asset } -> IconType
|
||||
greenCheckMark assets =
|
||||
ImgIcon assets.smallCheckmark_png
|
||||
|
||||
|
||||
{-| -}
|
||||
guidedWrite : { r | icons_guidedWrite_svg : Asset } -> IconType
|
||||
guidedWrite assets =
|
||||
ImgIcon assets.icons_guidedWrite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
hat : { r | hat : String } -> IconType
|
||||
hat assets =
|
||||
SvgIcon assets.hat
|
||||
|
||||
|
||||
{-| -}
|
||||
help : { r | icons_helpBlue_svg : Asset } -> IconType
|
||||
help assets =
|
||||
ImgIcon assets.icons_helpBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
helpSvg : { r | help : String } -> IconType
|
||||
helpSvg assets =
|
||||
SvgIcon assets.help
|
||||
|
||||
|
||||
{-| -}
|
||||
highFive : { r | level3Badge_png : Asset } -> IconType
|
||||
highFive assets =
|
||||
ImgIcon assets.level3Badge_png
|
||||
|
||||
|
||||
{-| -}
|
||||
key : { r | key : String } -> IconType
|
||||
key assets =
|
||||
SvgIcon assets.key
|
||||
|
||||
|
||||
{-| -}
|
||||
keychain : { r | keychain : String } -> IconType
|
||||
keychain assets =
|
||||
SvgIcon assets.keychain
|
||||
|
||||
|
||||
{-| -}
|
||||
late : { r | icons_clockRed_svg : Asset } -> IconType
|
||||
late assets =
|
||||
ImgIcon assets.icons_clockRed_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
leaderboard : { r | leaderboard : String } -> IconType
|
||||
leaderboard assets =
|
||||
SvgIcon assets.leaderboard
|
||||
|
||||
|
||||
{-| -}
|
||||
lightBulb : { r | hint_png : Asset } -> IconType
|
||||
lightBulb assets =
|
||||
ImgIcon assets.hint_png
|
||||
|
||||
|
||||
{-| -}
|
||||
lock : { r | lock : String } -> IconType
|
||||
lock assets =
|
||||
SvgIcon assets.lock
|
||||
|
||||
|
||||
{-| -}
|
||||
lockDeprecated : { r | premiumLock_svg : Asset } -> IconType
|
||||
lockDeprecated assets =
|
||||
ImgIcon assets.premiumLock_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
logo : { r | logoRedBlack_svg : Asset } -> IconType
|
||||
logo assets =
|
||||
ImgIcon assets.logoRedBlack_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
masteryBadge : { r | masteryBadge : String } -> IconType
|
||||
masteryBadge assets =
|
||||
SvgIcon assets.masteryBadge
|
||||
|
||||
|
||||
{-| -}
|
||||
newspaper : { r | newspaper : String } -> IconType
|
||||
newspaper assets =
|
||||
SvgIcon assets.newspaper
|
||||
|
||||
|
||||
{-| -}
|
||||
notStarred : { r | commentNotStarred_png : Asset } -> IconType
|
||||
notStarred assets =
|
||||
ImgIcon assets.commentNotStarred_png
|
||||
|
||||
|
||||
{-| -}
|
||||
okay : { r | level2Badge_png : Asset } -> IconType
|
||||
okay assets =
|
||||
ImgIcon assets.level2Badge_png
|
||||
|
||||
|
||||
{-| -}
|
||||
openClose : { r | openClose : String } -> IconType
|
||||
openClose assets =
|
||||
SvgIcon assets.openClose
|
||||
|
||||
|
||||
{-| -}
|
||||
peerReview : { r | icons_peerReview_svg : Asset } -> IconType
|
||||
peerReview assets =
|
||||
ImgIcon assets.icons_peerReview_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
pen : { r | pen : Asset } -> IconType
|
||||
pen assets =
|
||||
ImgIcon assets.pen
|
||||
|
||||
|
||||
{-| -}
|
||||
performance : { r | performance : String } -> IconType
|
||||
performance assets =
|
||||
SvgIcon assets.performance
|
||||
|
||||
|
||||
{-| -}
|
||||
personBlue : { r | personBlue_svg : Asset } -> IconType
|
||||
personBlue assets =
|
||||
ImgIcon assets.personBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
preview : { r | preview : String } -> IconType
|
||||
preview assets =
|
||||
SvgIcon assets.preview
|
||||
|
||||
|
||||
{-| -}
|
||||
quickWrite : { r | icons_quickWrite_svg : Asset } -> IconType
|
||||
quickWrite assets =
|
||||
ImgIcon assets.icons_quickWrite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
seeMore : { r | seemore : String } -> IconType
|
||||
seeMore assets =
|
||||
SvgIcon assets.seemore
|
||||
|
||||
|
||||
{-| -}
|
||||
share : { r | share : String } -> IconType
|
||||
share assets =
|
||||
SvgIcon assets.share
|
||||
|
||||
|
||||
{-| -}
|
||||
skip : { r | skip : String } -> IconType
|
||||
skip assets =
|
||||
SvgIcon assets.skip
|
||||
|
||||
|
||||
{-| -}
|
||||
sort : { r | sort : String } -> IconType
|
||||
sort assets =
|
||||
SvgIcon assets.sort
|
||||
|
||||
|
||||
{-| -}
|
||||
sortArrow : { r | sortArrow : String } -> IconType
|
||||
sortArrow assets =
|
||||
SvgIcon assets.sortArrow
|
||||
|
||||
|
||||
{-| -}
|
||||
speedometer : { r | speedometer : String } -> IconType
|
||||
speedometer assets =
|
||||
SvgIcon assets.speedometer
|
||||
|
||||
|
||||
{-| -}
|
||||
starred : { r | commentStarred_png : Asset } -> IconType
|
||||
starred assets =
|
||||
ImgIcon assets.commentStarred_png
|
||||
|
||||
|
||||
{-| -}
|
||||
thumbsUp : { r | level1Badge_png : Asset } -> IconType
|
||||
thumbsUp assets =
|
||||
ImgIcon assets.level1Badge_png
|
||||
|
||||
|
||||
{-| -}
|
||||
twitter : { r | twitterBlue_svg : Asset } -> IconType
|
||||
twitter assets =
|
||||
ImgIcon assets.twitterBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
unarchive : { r | unarchiveBlue2x_png : Asset } -> IconType
|
||||
unarchive assets =
|
||||
ImgIcon assets.unarchiveBlue2x_png
|
||||
|
||||
|
||||
{-| -}
|
||||
writingAssignment : { r | writingAssignment : String } -> IconType
|
||||
writingAssignment assets =
|
||||
SvgIcon assets.writingAssignment
|
||||
|
||||
|
||||
{-| -}
|
||||
x : { r | xWhite_svg : Asset } -> IconType
|
||||
x assets =
|
||||
ImgIcon assets.xWhite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
xSvg : { r | x : String } -> IconType
|
||||
xSvg assets =
|
||||
SvgIcon assets.x
|
||||
|
||||
|
||||
{-| -}
|
||||
submitting : { r | submitting : String } -> IconType
|
||||
submitting assets =
|
||||
SvgIcon assets.submitting
|
||||
|
||||
|
||||
{-| -}
|
||||
rating : { r | rating : String } -> IconType
|
||||
rating assets =
|
||||
SvgIcon assets.rating
|
||||
|
||||
|
||||
{-| -}
|
||||
revising : { r | revising : String } -> IconType
|
||||
revising assets =
|
||||
SvgIcon assets.revising
|
||||
|
||||
|
||||
{-| Inlining SVG styles because styles.class doesn't work on SVG elements.
|
||||
The `className` property of an SVG element isn't a string, it's an object and so
|
||||
`styles.class` causes a runtime exception by attempting to overwrite it with
|
||||
a string. Another workaround is to use the `Svg.Attributes.class` attribute but
|
||||
since `withNamespace` hides a call to `Html.Attributes.class` we can't do it
|
||||
properly.
|
||||
-}
|
||||
svgStyle : RootHtml.Attribute msg
|
||||
svgStyle =
|
||||
RootAttr.style
|
||||
[ ( "fill", "currentColor" )
|
||||
, ( "width", "100%" )
|
||||
, ( "height", "100%" )
|
||||
]
|
@ -1,908 +0,0 @@
|
||||
module Nri.Ui.Icon.V4 exposing
|
||||
( icon, decorativeIcon, link, linkExternal, linkSpa, button
|
||||
, IconType, IconSize(..), IconLinkSpaModel
|
||||
, activity
|
||||
, add
|
||||
, arrowDown
|
||||
, arrowLeft
|
||||
, arrowRight
|
||||
, assignmentStartButtonPrimary
|
||||
, assignmentStartButtonSecondary
|
||||
, assignmentTypeDiagnostic
|
||||
, assignmentTypeGuidedDraft
|
||||
, assignmentTypePeerReview
|
||||
, assignmentTypeSelfReview
|
||||
, assignmentTypePractice
|
||||
, assignmentTypeQuickWrite
|
||||
, assignmentTypeQuiz
|
||||
, assignmentTypeWritingCycle
|
||||
, attention
|
||||
, bang
|
||||
, bulb
|
||||
, calendar
|
||||
, caret
|
||||
, checkMark
|
||||
, checkMarkSquiggily
|
||||
, checkMarkSvg
|
||||
, class
|
||||
, clever
|
||||
, clock
|
||||
, close
|
||||
, copy
|
||||
, compassSvg
|
||||
, custom
|
||||
, darkBlueCheckMark
|
||||
, document
|
||||
, download
|
||||
, edit
|
||||
, editWriting
|
||||
, equalitySign
|
||||
, exclamation
|
||||
, facebook
|
||||
, flag
|
||||
, flipper
|
||||
, footsteps
|
||||
, gardening
|
||||
, gear
|
||||
, greenCheckMark
|
||||
, guidedWrite
|
||||
, hat
|
||||
, help
|
||||
, helpSvg
|
||||
, highFive
|
||||
, key
|
||||
, keychain
|
||||
, late
|
||||
, leaderboard
|
||||
, lightBulb
|
||||
, lock
|
||||
, lockDeprecated
|
||||
, logo
|
||||
, masteryBadge
|
||||
, newspaper
|
||||
, notStarred
|
||||
, okay
|
||||
, openClose
|
||||
, peerReview
|
||||
, pen
|
||||
, performance
|
||||
, personBlue
|
||||
, preview
|
||||
, quickWrite
|
||||
, seeMore
|
||||
, share
|
||||
, skip
|
||||
, sort
|
||||
, sortArrow
|
||||
, speedometer
|
||||
, starred
|
||||
, thumbsUp
|
||||
, twitter
|
||||
, unarchive
|
||||
, writingAssignment
|
||||
, x
|
||||
, xSvg
|
||||
, submitting, rating, revising
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs icon, decorativeIcon, link, linkExternal, linkSpa, button
|
||||
@docs IconType, IconSize, IconLinkSpaModel
|
||||
@docs activity
|
||||
@docs add
|
||||
@docs arrowDown
|
||||
@docs arrowLeft
|
||||
@docs arrowRight
|
||||
@docs assignmentStartButtonPrimary
|
||||
@docs assignmentStartButtonSecondary
|
||||
@docs assignmentTypeDiagnostic
|
||||
@docs assignmentTypeGuidedDraft
|
||||
@docs assignmentTypePeerReview
|
||||
@docs assignmentTypeSelfReview
|
||||
@docs assignmentTypePractice
|
||||
@docs assignmentTypeQuickWrite
|
||||
@docs assignmentTypeQuiz
|
||||
@docs assignmentTypeWritingCycle
|
||||
@docs attention
|
||||
@docs bang
|
||||
@docs bulb
|
||||
@docs calendar
|
||||
@docs caret
|
||||
@docs checkMark
|
||||
@docs checkMarkSquiggily
|
||||
@docs checkMarkSvg
|
||||
@docs class
|
||||
@docs clever
|
||||
@docs clock
|
||||
@docs close
|
||||
@docs copy
|
||||
@docs compassSvg
|
||||
@docs custom
|
||||
@docs darkBlueCheckMark
|
||||
@docs document
|
||||
@docs download
|
||||
@docs edit
|
||||
@docs editWriting
|
||||
@docs equalitySign
|
||||
@docs exclamation
|
||||
@docs facebook
|
||||
@docs flag
|
||||
@docs flipper
|
||||
@docs footsteps
|
||||
@docs gardening
|
||||
@docs gear
|
||||
@docs greenCheckMark
|
||||
@docs guidedWrite
|
||||
@docs hat
|
||||
@docs help
|
||||
@docs helpSvg
|
||||
@docs highFive
|
||||
@docs key
|
||||
@docs keychain
|
||||
@docs late
|
||||
@docs leaderboard
|
||||
@docs lightBulb
|
||||
@docs lock
|
||||
@docs lockDeprecated
|
||||
@docs logo
|
||||
@docs masteryBadge
|
||||
@docs newspaper
|
||||
@docs notStarred
|
||||
@docs okay
|
||||
@docs openClose
|
||||
@docs peerReview
|
||||
@docs pen
|
||||
@docs performance
|
||||
@docs personBlue
|
||||
@docs preview
|
||||
@docs quickWrite
|
||||
@docs seeMore
|
||||
@docs share
|
||||
@docs skip
|
||||
@docs sort
|
||||
@docs sortArrow
|
||||
@docs speedometer
|
||||
@docs starred
|
||||
@docs thumbsUp
|
||||
@docs twitter
|
||||
@docs unarchive
|
||||
@docs writingAssignment
|
||||
@docs x
|
||||
@docs xSvg
|
||||
@docs submitting, rating, revising
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Role as Role
|
||||
import Accessibility.Styled exposing (..)
|
||||
import Css exposing (..)
|
||||
import EventExtras
|
||||
import Html as RootHtml
|
||||
import Html.Attributes as RootAttr exposing (..)
|
||||
import Html.Styled
|
||||
import Html.Styled.Attributes as Attributes exposing (css)
|
||||
import Html.Styled.Events as Events
|
||||
import Nri.Ui.AssetPath exposing (Asset(..))
|
||||
import Nri.Ui.Colors.V1
|
||||
import Svg exposing (svg, use)
|
||||
import Svg.Attributes exposing (xlinkHref)
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias IconLinkModel =
|
||||
{ alt : String
|
||||
, url : String
|
||||
, icon : IconType
|
||||
, disabled : Bool
|
||||
, size : IconSize
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias IconLinkSpaModel route =
|
||||
{ alt : String
|
||||
, icon : IconType
|
||||
, disabled : Bool
|
||||
, size : IconSize
|
||||
, route : route
|
||||
}
|
||||
|
||||
|
||||
type alias IconButtonModel msg =
|
||||
{ alt : String
|
||||
, msg : msg
|
||||
, icon : IconType
|
||||
, disabled : Bool
|
||||
, size : IconSize
|
||||
}
|
||||
|
||||
|
||||
{-| An icon that can be rendered using the functions provided by this module.
|
||||
-}
|
||||
type IconType
|
||||
= ImgIcon Asset
|
||||
| SvgIcon String
|
||||
|
||||
|
||||
{-| Used for determining sizes on Icon.buttons and Icon.links
|
||||
-}
|
||||
type IconSize
|
||||
= Small
|
||||
| Medium
|
||||
|
||||
|
||||
{-| Create an icon that links to a part of NRI
|
||||
Uses our default icon styles (25 x 25 px, azure)
|
||||
-}
|
||||
link : IconLinkModel -> Html msg
|
||||
link =
|
||||
linkBase [ Attributes.target "_self" ]
|
||||
|
||||
|
||||
{-| Create an accessible icon button with an onClick handler
|
||||
Uses our default icon styles (25 x 25 px, azure)
|
||||
-}
|
||||
button : IconButtonModel msg -> Html msg
|
||||
button model =
|
||||
Accessibility.Styled.button
|
||||
[ css
|
||||
[ Css.batch
|
||||
[ backgroundColor transparent
|
||||
, border zero
|
||||
, color Nri.Ui.Colors.V1.azure
|
||||
, fontFamily inherit
|
||||
, Css.property "cursor" "pointer"
|
||||
, padding zero
|
||||
, focus
|
||||
[ backgroundColor transparent
|
||||
]
|
||||
]
|
||||
, sizeStyles model.size
|
||||
]
|
||||
, Events.onClick model.msg
|
||||
, Attributes.disabled model.disabled
|
||||
, Attributes.type_ "button"
|
||||
]
|
||||
[ icon
|
||||
{ alt = model.alt
|
||||
, icon = model.icon
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
icon : { alt : String, icon : IconType } -> Html msg
|
||||
icon config =
|
||||
case config.icon of
|
||||
SvgIcon iconId ->
|
||||
svg [ svgStyle ]
|
||||
[ Svg.title [] [ RootHtml.text config.alt ]
|
||||
, use [ xlinkHref ("#" ++ iconId) ] []
|
||||
]
|
||||
|> Html.Styled.fromUnstyled
|
||||
|
||||
ImgIcon assetPath ->
|
||||
img config.alt
|
||||
[ Attributes.src (Nri.Ui.AssetPath.url assetPath)
|
||||
]
|
||||
|
||||
|
||||
{-| Use this icon for purely decorative content that would be distracting
|
||||
rather than helpful on a screenreader.
|
||||
-}
|
||||
decorativeIcon : IconType -> Html msg
|
||||
decorativeIcon iconType =
|
||||
case iconType of
|
||||
SvgIcon iconId ->
|
||||
svg
|
||||
[ svgStyle
|
||||
, Role.img
|
||||
]
|
||||
[ use [ xlinkHref ("#" ++ iconId) ] []
|
||||
]
|
||||
|> Html.Styled.fromUnstyled
|
||||
|
||||
ImgIcon assetPath ->
|
||||
decorativeImg [ Attributes.src (Nri.Ui.AssetPath.url assetPath) ]
|
||||
|
||||
|
||||
{-| Use this link for routing within a single page app.
|
||||
|
||||
This will make a normal <a> tag, but change the onClick behavior to avoid reloading the page.
|
||||
|
||||
-}
|
||||
linkSpa : (route -> String) -> (route -> msg) -> IconLinkSpaModel route -> Html msg
|
||||
linkSpa toUrl toMsg config =
|
||||
linkBase
|
||||
[ EventExtras.onClickPreventDefaultForLinkWithHref (toMsg config.route)
|
||||
|> Attributes.fromUnstyled
|
||||
]
|
||||
{ alt = config.alt
|
||||
, url = toUrl config.route
|
||||
, icon = config.icon
|
||||
, disabled = config.disabled
|
||||
, size = config.size
|
||||
}
|
||||
|
||||
|
||||
{-| Create an icon that links to an external site
|
||||
Uses our default icon styles (25 x 25 px, azure)
|
||||
-}
|
||||
linkExternal : IconLinkModel -> Html msg
|
||||
linkExternal =
|
||||
linkBase [ Attributes.target "_blank" ]
|
||||
|
||||
|
||||
linkBase : List (Attribute msg) -> IconLinkModel -> Html msg
|
||||
linkBase linkAttributes model =
|
||||
span
|
||||
[]
|
||||
[ Html.Styled.a
|
||||
(linkAttributes ++ defaultLinkAttributes model)
|
||||
[ icon { alt = model.alt, icon = model.icon }
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
defaultLinkAttributes : IconLinkModel -> List (Attribute msg)
|
||||
defaultLinkAttributes model =
|
||||
if model.disabled then
|
||||
[ css
|
||||
[ Css.cursor Css.notAllowed
|
||||
, linkStyles
|
||||
, sizeStyles model.size
|
||||
]
|
||||
]
|
||||
|
||||
else
|
||||
[ css [ linkStyles, sizeStyles model.size ]
|
||||
, Attributes.href model.url
|
||||
]
|
||||
|
||||
|
||||
linkStyles : Style
|
||||
linkStyles =
|
||||
Css.batch
|
||||
[ color Nri.Ui.Colors.V1.azure
|
||||
, display inlineBlock
|
||||
, fontFamily inherit
|
||||
, Css.property "cursor" "pointer"
|
||||
, padding zero
|
||||
, visited [ color Nri.Ui.Colors.V1.azure ]
|
||||
]
|
||||
|
||||
|
||||
sizeStyles : IconSize -> Style
|
||||
sizeStyles size =
|
||||
Css.batch <|
|
||||
case size of
|
||||
Small ->
|
||||
[ Css.width (px 20)
|
||||
, Css.height (px 20)
|
||||
]
|
||||
|
||||
Medium ->
|
||||
[ Css.width (px 25)
|
||||
, Css.height (px 25)
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
activity : { r | activity : String } -> IconType
|
||||
activity assets =
|
||||
SvgIcon assets.activity
|
||||
|
||||
|
||||
{-| -}
|
||||
add : { r | icons_plusBlue_svg : Asset } -> IconType
|
||||
add assets =
|
||||
ImgIcon assets.icons_plusBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
arrowDown : { r | arrowDown : String } -> IconType
|
||||
arrowDown assets =
|
||||
SvgIcon assets.arrowDown
|
||||
|
||||
|
||||
{-| -}
|
||||
arrowLeft : { r | leftArrowBlue_png : Asset } -> IconType
|
||||
arrowLeft assets =
|
||||
ImgIcon assets.leftArrowBlue_png
|
||||
|
||||
|
||||
{-| -}
|
||||
arrowRight : { r | icons_arrowRightBlue_svg : Asset } -> IconType
|
||||
arrowRight assets =
|
||||
ImgIcon assets.icons_arrowRightBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentStartButtonPrimary : { r | assignmentStartButtonPrimary_svg : Asset } -> IconType
|
||||
assignmentStartButtonPrimary assets =
|
||||
ImgIcon assets.assignmentStartButtonPrimary_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentStartButtonSecondary : { r | assignmentStartButtonSecondary_svg : Asset } -> IconType
|
||||
assignmentStartButtonSecondary assets =
|
||||
ImgIcon assets.assignmentStartButtonSecondary_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeDiagnostic : { r | diagnostic : String } -> IconType
|
||||
assignmentTypeDiagnostic assets =
|
||||
SvgIcon assets.diagnostic
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeGuidedDraft : { r | guidedDraft : String } -> IconType
|
||||
assignmentTypeGuidedDraft assets =
|
||||
SvgIcon assets.guidedDraft
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypePeerReview : { r | peerReview : String } -> IconType
|
||||
assignmentTypePeerReview assets =
|
||||
SvgIcon assets.peerReview
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeSelfReview : { r | selfReview : String } -> IconType
|
||||
assignmentTypeSelfReview assets =
|
||||
SvgIcon assets.selfReview
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypePractice : { r | practice : String } -> IconType
|
||||
assignmentTypePractice assets =
|
||||
SvgIcon assets.practice
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeQuickWrite : { r | quickWrite : String } -> IconType
|
||||
assignmentTypeQuickWrite assets =
|
||||
SvgIcon assets.quickWrite
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeQuiz : { r | quiz : String } -> IconType
|
||||
assignmentTypeQuiz assets =
|
||||
SvgIcon assets.quiz
|
||||
|
||||
|
||||
{-| -}
|
||||
assignmentTypeWritingCycle : { r | writingcycle : String } -> IconType
|
||||
assignmentTypeWritingCycle assets =
|
||||
SvgIcon assets.writingcycle
|
||||
|
||||
|
||||
{-| -}
|
||||
attention : { r | attention_svg : Asset } -> IconType
|
||||
attention assets =
|
||||
ImgIcon assets.attention_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
bang : { r | exclamationPoint_svg : Asset } -> IconType
|
||||
bang assets =
|
||||
ImgIcon assets.exclamationPoint_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
bulb : { r | bulb : String } -> IconType
|
||||
bulb assets =
|
||||
SvgIcon assets.bulb
|
||||
|
||||
|
||||
{-| -}
|
||||
calendar : { r | calendar : String } -> IconType
|
||||
calendar assets =
|
||||
SvgIcon assets.calendar
|
||||
|
||||
|
||||
{-| -}
|
||||
caret : { r | icons_arrowDownBlue_svg : Asset } -> IconType
|
||||
caret assets =
|
||||
ImgIcon assets.icons_arrowDownBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
checkMark : { r | iconCheck_png : Asset } -> IconType
|
||||
checkMark assets =
|
||||
ImgIcon assets.iconCheck_png
|
||||
|
||||
|
||||
{-| -}
|
||||
checkMarkSquiggily : { r | squiggly_png : Asset } -> IconType
|
||||
checkMarkSquiggily assets =
|
||||
ImgIcon assets.squiggly_png
|
||||
|
||||
|
||||
{-| -}
|
||||
checkMarkSvg : { r | checkmark : String } -> IconType
|
||||
checkMarkSvg assets =
|
||||
SvgIcon assets.checkmark
|
||||
|
||||
|
||||
{-| -}
|
||||
class : { r | class : String } -> IconType
|
||||
class assets =
|
||||
SvgIcon assets.class
|
||||
|
||||
|
||||
{-| -}
|
||||
clever : { r | clever : String } -> IconType
|
||||
clever assets =
|
||||
SvgIcon assets.clever
|
||||
|
||||
|
||||
{-| -}
|
||||
clock : { r | clock : String } -> IconType
|
||||
clock assets =
|
||||
SvgIcon assets.clock
|
||||
|
||||
|
||||
{-| -}
|
||||
close : { r | icons_xBlue_svg : Asset } -> IconType
|
||||
close assets =
|
||||
ImgIcon assets.icons_xBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
copy : { r | teach_assignments_copyWhite_svg : Asset } -> IconType
|
||||
copy assets =
|
||||
ImgIcon assets.teach_assignments_copyWhite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
compassSvg : { r | compass : String } -> IconType
|
||||
compassSvg assets =
|
||||
SvgIcon assets.compass
|
||||
|
||||
|
||||
{-| -}
|
||||
custom : Asset -> IconType
|
||||
custom asset =
|
||||
ImgIcon asset
|
||||
|
||||
|
||||
{-| -}
|
||||
darkBlueCheckMark : { r | darkBlueCheckmark_svg : Asset } -> IconType
|
||||
darkBlueCheckMark assets =
|
||||
ImgIcon assets.darkBlueCheckmark_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
document : { r | document : String } -> IconType
|
||||
document assets =
|
||||
SvgIcon assets.document
|
||||
|
||||
|
||||
{-| -}
|
||||
download : { r | download : String } -> IconType
|
||||
download assets =
|
||||
SvgIcon assets.download
|
||||
|
||||
|
||||
{-| -}
|
||||
edit : { r | edit : String } -> IconType
|
||||
edit assets =
|
||||
SvgIcon assets.edit
|
||||
|
||||
|
||||
{-| -}
|
||||
editWriting : { r | editWriting : String } -> IconType
|
||||
editWriting assets =
|
||||
SvgIcon assets.editWriting
|
||||
|
||||
|
||||
{-| -}
|
||||
equalitySign : { r | icons_equals_svg : Asset } -> IconType
|
||||
equalitySign assets =
|
||||
ImgIcon assets.icons_equals_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
exclamation : { r | exclamation : String } -> IconType
|
||||
exclamation assets =
|
||||
SvgIcon assets.exclamation
|
||||
|
||||
|
||||
{-| -}
|
||||
facebook : { r | facebookBlue_svg : Asset } -> IconType
|
||||
facebook assets =
|
||||
ImgIcon assets.facebookBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
flag : { r | iconFlag_png : Asset } -> IconType
|
||||
flag assets =
|
||||
ImgIcon assets.iconFlag_png
|
||||
|
||||
|
||||
{-| -}
|
||||
flipper : { r | flipper : String } -> IconType
|
||||
flipper assets =
|
||||
SvgIcon assets.flipper
|
||||
|
||||
|
||||
{-| -}
|
||||
footsteps : { r | footsteps : String } -> IconType
|
||||
footsteps assets =
|
||||
SvgIcon assets.footsteps
|
||||
|
||||
|
||||
{-| -}
|
||||
gardening : { r | startingOffBadge_png : Asset } -> IconType
|
||||
gardening assets =
|
||||
ImgIcon assets.startingOffBadge_png
|
||||
|
||||
|
||||
{-| -}
|
||||
gear : { r | gear : String } -> IconType
|
||||
gear assets =
|
||||
SvgIcon assets.gear
|
||||
|
||||
|
||||
{-| -}
|
||||
greenCheckMark : { r | smallCheckmark_png : Asset } -> IconType
|
||||
greenCheckMark assets =
|
||||
ImgIcon assets.smallCheckmark_png
|
||||
|
||||
|
||||
{-| -}
|
||||
guidedWrite : { r | icons_guidedWrite_svg : Asset } -> IconType
|
||||
guidedWrite assets =
|
||||
ImgIcon assets.icons_guidedWrite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
hat : { r | hat : String } -> IconType
|
||||
hat assets =
|
||||
SvgIcon assets.hat
|
||||
|
||||
|
||||
{-| -}
|
||||
help : { r | icons_helpBlue_svg : Asset } -> IconType
|
||||
help assets =
|
||||
ImgIcon assets.icons_helpBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
helpSvg : { r | help : String } -> IconType
|
||||
helpSvg assets =
|
||||
SvgIcon assets.help
|
||||
|
||||
|
||||
{-| -}
|
||||
highFive : { r | level3Badge_png : Asset } -> IconType
|
||||
highFive assets =
|
||||
ImgIcon assets.level3Badge_png
|
||||
|
||||
|
||||
{-| -}
|
||||
key : { r | key : String } -> IconType
|
||||
key assets =
|
||||
SvgIcon assets.key
|
||||
|
||||
|
||||
{-| -}
|
||||
keychain : { r | keychain : String } -> IconType
|
||||
keychain assets =
|
||||
SvgIcon assets.keychain
|
||||
|
||||
|
||||
{-| -}
|
||||
late : { r | icons_clockRed_svg : Asset } -> IconType
|
||||
late assets =
|
||||
ImgIcon assets.icons_clockRed_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
leaderboard : { r | leaderboard : String } -> IconType
|
||||
leaderboard assets =
|
||||
SvgIcon assets.leaderboard
|
||||
|
||||
|
||||
{-| -}
|
||||
lightBulb : { r | hint_png : Asset } -> IconType
|
||||
lightBulb assets =
|
||||
ImgIcon assets.hint_png
|
||||
|
||||
|
||||
{-| -}
|
||||
lock : { r | lock : String } -> IconType
|
||||
lock assets =
|
||||
SvgIcon assets.lock
|
||||
|
||||
|
||||
{-| -}
|
||||
lockDeprecated : { r | premiumLock_svg : Asset } -> IconType
|
||||
lockDeprecated assets =
|
||||
ImgIcon assets.premiumLock_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
logo : { r | logoRedBlack_svg : Asset } -> IconType
|
||||
logo assets =
|
||||
ImgIcon assets.logoRedBlack_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
masteryBadge : { r | masteryBadge : String } -> IconType
|
||||
masteryBadge assets =
|
||||
SvgIcon assets.masteryBadge
|
||||
|
||||
|
||||
{-| -}
|
||||
newspaper : { r | newspaper : String } -> IconType
|
||||
newspaper assets =
|
||||
SvgIcon assets.newspaper
|
||||
|
||||
|
||||
{-| -}
|
||||
notStarred : { r | commentNotStarred_png : Asset } -> IconType
|
||||
notStarred assets =
|
||||
ImgIcon assets.commentNotStarred_png
|
||||
|
||||
|
||||
{-| -}
|
||||
okay : { r | level2Badge_png : Asset } -> IconType
|
||||
okay assets =
|
||||
ImgIcon assets.level2Badge_png
|
||||
|
||||
|
||||
{-| -}
|
||||
openClose : { r | openClose : String } -> IconType
|
||||
openClose assets =
|
||||
SvgIcon assets.openClose
|
||||
|
||||
|
||||
{-| -}
|
||||
peerReview : { r | icons_peerReview_svg : Asset } -> IconType
|
||||
peerReview assets =
|
||||
ImgIcon assets.icons_peerReview_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
pen : { r | pen : Asset } -> IconType
|
||||
pen assets =
|
||||
ImgIcon assets.pen
|
||||
|
||||
|
||||
{-| -}
|
||||
performance : { r | performance : String } -> IconType
|
||||
performance assets =
|
||||
SvgIcon assets.performance
|
||||
|
||||
|
||||
{-| -}
|
||||
personBlue : { r | personBlue_svg : Asset } -> IconType
|
||||
personBlue assets =
|
||||
ImgIcon assets.personBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
preview : { r | preview : String } -> IconType
|
||||
preview assets =
|
||||
SvgIcon assets.preview
|
||||
|
||||
|
||||
{-| -}
|
||||
quickWrite : { r | icons_quickWrite_svg : Asset } -> IconType
|
||||
quickWrite assets =
|
||||
ImgIcon assets.icons_quickWrite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
seeMore : { r | seemore : String } -> IconType
|
||||
seeMore assets =
|
||||
SvgIcon assets.seemore
|
||||
|
||||
|
||||
{-| -}
|
||||
share : { r | share : String } -> IconType
|
||||
share assets =
|
||||
SvgIcon assets.share
|
||||
|
||||
|
||||
{-| -}
|
||||
skip : { r | skip : String } -> IconType
|
||||
skip assets =
|
||||
SvgIcon assets.skip
|
||||
|
||||
|
||||
{-| -}
|
||||
sort : { r | sort : String } -> IconType
|
||||
sort assets =
|
||||
SvgIcon assets.sort
|
||||
|
||||
|
||||
{-| -}
|
||||
sortArrow : { r | sortArrow : String } -> IconType
|
||||
sortArrow assets =
|
||||
SvgIcon assets.sortArrow
|
||||
|
||||
|
||||
{-| -}
|
||||
speedometer : { r | speedometer : String } -> IconType
|
||||
speedometer assets =
|
||||
SvgIcon assets.speedometer
|
||||
|
||||
|
||||
{-| -}
|
||||
starred : { r | commentStarred_png : Asset } -> IconType
|
||||
starred assets =
|
||||
ImgIcon assets.commentStarred_png
|
||||
|
||||
|
||||
{-| -}
|
||||
thumbsUp : { r | level1Badge_png : Asset } -> IconType
|
||||
thumbsUp assets =
|
||||
ImgIcon assets.level1Badge_png
|
||||
|
||||
|
||||
{-| -}
|
||||
twitter : { r | twitterBlue_svg : Asset } -> IconType
|
||||
twitter assets =
|
||||
ImgIcon assets.twitterBlue_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
unarchive : { r | unarchiveBlue2x_png : Asset } -> IconType
|
||||
unarchive assets =
|
||||
ImgIcon assets.unarchiveBlue2x_png
|
||||
|
||||
|
||||
{-| -}
|
||||
writingAssignment : { r | writingAssignment : String } -> IconType
|
||||
writingAssignment assets =
|
||||
SvgIcon assets.writingAssignment
|
||||
|
||||
|
||||
{-| -}
|
||||
x : { r | xWhite_svg : Asset } -> IconType
|
||||
x assets =
|
||||
ImgIcon assets.xWhite_svg
|
||||
|
||||
|
||||
{-| -}
|
||||
xSvg : { r | x : String } -> IconType
|
||||
xSvg assets =
|
||||
SvgIcon assets.x
|
||||
|
||||
|
||||
{-| -}
|
||||
submitting : { r | submitting : String } -> IconType
|
||||
submitting assets =
|
||||
SvgIcon assets.submitting
|
||||
|
||||
|
||||
{-| -}
|
||||
rating : { r | rating : String } -> IconType
|
||||
rating assets =
|
||||
SvgIcon assets.rating
|
||||
|
||||
|
||||
{-| -}
|
||||
revising : { r | revising : String } -> IconType
|
||||
revising assets =
|
||||
SvgIcon assets.revising
|
||||
|
||||
|
||||
{-| Inlining SVG styles because styles.class doesn't work on SVG elements.
|
||||
The `className` property of an SVG element isn't a string, it's an object and so
|
||||
`styles.class` causes a runtime exception by attempting to overwrite it with
|
||||
a string. Another workaround is to use the `Svg.Attributes.class` attribute but
|
||||
since `withNamespace` hides a call to `Html.Attributes.class` we can't do it
|
||||
properly.
|
||||
-}
|
||||
svgStyle : RootHtml.Attribute msg
|
||||
svgStyle =
|
||||
RootAttr.style
|
||||
[ ( "fill", "currentColor" )
|
||||
, ( "width", "100%" )
|
||||
, ( "height", "100%" )
|
||||
]
|
@ -1,223 +0,0 @@
|
||||
module Nri.Ui.InputStyles.V2 exposing
|
||||
( label, Theme(..), input
|
||||
, inputPaddingVertical, inputLineHeight, textAreaHeight, writingLineHeight, writingPadding, writingPaddingTop, writingMinHeight
|
||||
)
|
||||
|
||||
{-| InputStyles used by the TextInput and TextArea widgets.
|
||||
|
||||
@docs label, Theme, input
|
||||
|
||||
|
||||
## Shared hardcoded values
|
||||
|
||||
@docs inputPaddingVertical, inputLineHeight, textAreaHeight, writingLineHeight, writingPadding, writingPaddingTop, writingMinHeight
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
import Css.Global
|
||||
import Nri.Ui.Colors.V1 exposing (..)
|
||||
import Nri.Ui.Fonts.V1
|
||||
|
||||
|
||||
{-| -}
|
||||
type Theme
|
||||
= ContentCreation
|
||||
| Standard
|
||||
| Writing
|
||||
|
||||
|
||||
{-| -}
|
||||
label : Theme -> Bool -> Style
|
||||
label theme inError =
|
||||
let
|
||||
sharedStyles =
|
||||
batch
|
||||
[ backgroundColor white
|
||||
, left (px 10)
|
||||
, top (px 0)
|
||||
, fontSize (px 12)
|
||||
, Nri.Ui.Fonts.V1.baseFont
|
||||
, position absolute
|
||||
, fontWeight (int 600)
|
||||
, property "transition" "all 0.4s ease"
|
||||
]
|
||||
in
|
||||
case theme of
|
||||
Standard ->
|
||||
batch
|
||||
[ sharedStyles
|
||||
, padding2 zero (px 5)
|
||||
, fontSize (px 12)
|
||||
, color navy
|
||||
, if inError then
|
||||
batch [ color purple ]
|
||||
|
||||
else
|
||||
batch []
|
||||
]
|
||||
|
||||
ContentCreation ->
|
||||
batch
|
||||
[ sharedStyles
|
||||
, border3 (px 1) solid gray75
|
||||
, borderRadius (px 4)
|
||||
, padding2 zero (px 5)
|
||||
, fontSize (Css.px 11)
|
||||
, color gray45
|
||||
, padding2 (px 2) (px 5)
|
||||
]
|
||||
|
||||
Writing ->
|
||||
batch
|
||||
[ sharedStyles
|
||||
, padding2 zero (px 5)
|
||||
, border3 (px 1) solid gray75
|
||||
, borderRadius (px 4)
|
||||
, fontSize (px 15)
|
||||
, color navy
|
||||
, if inError then
|
||||
batch
|
||||
[ color purple
|
||||
, backgroundColor white
|
||||
, borderColor purple
|
||||
]
|
||||
|
||||
else
|
||||
batch []
|
||||
]
|
||||
|
||||
|
||||
{-| In order to use these styles in an input module, you will need to add the class "override-sass-styles". This is because sass styles in the monolith have higher precendence than the class styles here.
|
||||
-}
|
||||
input : Theme -> Bool -> Style
|
||||
input theme isInError =
|
||||
let
|
||||
sharedStyles =
|
||||
batch
|
||||
[ border3 (px 1) solid gray75
|
||||
, width (pct 100)
|
||||
, borderRadius (px 8)
|
||||
, property "transition" "all 0.1s ease"
|
||||
, pseudoClass "placeholder"
|
||||
[ color gray45
|
||||
]
|
||||
, color gray20
|
||||
|
||||
-- fix bootstrap
|
||||
, display inlineBlock
|
||||
, verticalAlign top
|
||||
, marginBottom zero
|
||||
, marginTop (px 9)
|
||||
, boxShadow6 inset zero (px 2) zero zero gray92
|
||||
, property "transition" "all 0.4s ease"
|
||||
, boxSizing borderBox
|
||||
, focus
|
||||
[ borderColor azure
|
||||
, outline none
|
||||
, boxShadow6 inset zero (px 2) zero zero glacier
|
||||
]
|
||||
, if isInError then
|
||||
batch
|
||||
[ borderColor purple
|
||||
, boxShadow6 inset zero (px 2) zero zero purpleLight
|
||||
, focus
|
||||
[ borderColor purple
|
||||
, boxShadow6 inset zero (px 2) zero zero purpleLight
|
||||
]
|
||||
]
|
||||
|
||||
else
|
||||
batch []
|
||||
]
|
||||
in
|
||||
batch
|
||||
[ Css.Global.withClass "override-sass-styles"
|
||||
[ case theme of
|
||||
Standard ->
|
||||
batch
|
||||
[ sharedStyles
|
||||
, padding2 inputPaddingVertical (px 14)
|
||||
, fontSize (px 15)
|
||||
, Nri.Ui.Fonts.V1.baseFont
|
||||
]
|
||||
|
||||
Writing ->
|
||||
batch
|
||||
[ sharedStyles
|
||||
, Nri.Ui.Fonts.V1.quizFont
|
||||
, fontSize (px 20)
|
||||
, lineHeight writingLineHeight
|
||||
, padding writingPadding
|
||||
, paddingTop writingPaddingTop
|
||||
, focus
|
||||
[ Css.Global.adjacentSiblings
|
||||
[ Css.Global.label
|
||||
[ backgroundColor azure
|
||||
, color white
|
||||
, borderColor azure
|
||||
, if isInError then
|
||||
batch
|
||||
[ backgroundColor purple
|
||||
, color white
|
||||
, borderColor purple
|
||||
]
|
||||
|
||||
else
|
||||
batch []
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
ContentCreation ->
|
||||
batch
|
||||
[ sharedStyles
|
||||
, padding2 inputPaddingVertical (px 14)
|
||||
, fontSize (px 15)
|
||||
, Nri.Ui.Fonts.V1.baseFont
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
inputPaddingVertical : Px
|
||||
inputPaddingVertical =
|
||||
px 8
|
||||
|
||||
|
||||
{-| -}
|
||||
inputLineHeight : Px
|
||||
inputLineHeight =
|
||||
px 20
|
||||
|
||||
|
||||
{-| -}
|
||||
textAreaHeight : Px
|
||||
textAreaHeight =
|
||||
px 100
|
||||
|
||||
|
||||
{-| -}
|
||||
writingLineHeight : Px
|
||||
writingLineHeight =
|
||||
px 25
|
||||
|
||||
|
||||
{-| -}
|
||||
writingPadding : Px
|
||||
writingPadding =
|
||||
px 15
|
||||
|
||||
|
||||
{-| -}
|
||||
writingPaddingTop : Px
|
||||
writingPaddingTop =
|
||||
px 20
|
||||
|
||||
|
||||
{-| -}
|
||||
writingMinHeight : Px
|
||||
writingMinHeight =
|
||||
px 150
|
@ -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
|
||||
)
|
@ -1,245 +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 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 ]
|
||||
]
|
||||
, 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 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
|
||||
)
|
@ -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)
|
||||
]
|
@ -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 = "\x1F914"
|
||||
, title = "We couldn’t 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 = "\x1F910"
|
||||
, 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)
|
||||
]
|
||||
[]
|
@ -1,159 +0,0 @@
|
||||
module Nri.Ui.Palette.V1 exposing
|
||||
( Palette, PaletteName(..)
|
||||
, white, gray, darkGray, blue, darkBlue, purple, turquoise, green, red, aqua, cornflower
|
||||
)
|
||||
|
||||
{-| Predefined color palettes for use in configurable components
|
||||
|
||||
@docs Palette, PaletteName
|
||||
@docs white, gray, darkGray, blue, darkBlue, purple, turquoise, green, red, aqua, cornflower
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Palette =
|
||||
{ border : Css.Color
|
||||
, background : Css.Color
|
||||
, primary : Css.Color
|
||||
, name : PaletteName
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type PaletteName
|
||||
= Gray
|
||||
| DarkGray
|
||||
| Blue
|
||||
| DarkBlue
|
||||
| Purple
|
||||
| Turquoise
|
||||
| Red
|
||||
| Green
|
||||
| White
|
||||
| Cornflower
|
||||
| Aqua
|
||||
|
||||
|
||||
{-| Gray palette
|
||||
-}
|
||||
gray : Palette
|
||||
gray =
|
||||
{ border = Colors.gray75
|
||||
, background = Colors.gray96
|
||||
, primary = Colors.gray75
|
||||
, name = Gray
|
||||
}
|
||||
|
||||
|
||||
{-| Aqua palette
|
||||
-}
|
||||
aqua : Palette
|
||||
aqua =
|
||||
{ border = Colors.aqua
|
||||
, background = Colors.aquaLight
|
||||
, primary = Colors.aqua
|
||||
, name = Aqua
|
||||
}
|
||||
|
||||
|
||||
{-| Dark Gray palette
|
||||
-}
|
||||
darkGray : Palette
|
||||
darkGray =
|
||||
{ border = Colors.gray45
|
||||
, background = Colors.gray96
|
||||
, primary = Colors.gray45
|
||||
, name = DarkGray
|
||||
}
|
||||
|
||||
|
||||
{-| Blue palette
|
||||
-}
|
||||
blue : Palette
|
||||
blue =
|
||||
{ border = Colors.azure
|
||||
, background = Colors.frost
|
||||
, primary = Colors.azure
|
||||
, name = Blue
|
||||
}
|
||||
|
||||
|
||||
{-| Dark blue palette
|
||||
-}
|
||||
darkBlue : Palette
|
||||
darkBlue =
|
||||
{ border = Colors.navy
|
||||
, background = Colors.frost
|
||||
, primary = Colors.navy
|
||||
, name = DarkBlue
|
||||
}
|
||||
|
||||
|
||||
{-| Purple palette
|
||||
-}
|
||||
purple : Palette
|
||||
purple =
|
||||
{ border = Colors.purple
|
||||
, background = Colors.purpleLight
|
||||
, primary = Colors.purple
|
||||
, name = Purple
|
||||
}
|
||||
|
||||
|
||||
{-| Turquoise palette
|
||||
-}
|
||||
turquoise : Palette
|
||||
turquoise =
|
||||
{ border = Colors.turquoise
|
||||
, background = Colors.turquoiseLight
|
||||
, primary = Colors.turquoise
|
||||
, name = Turquoise
|
||||
}
|
||||
|
||||
|
||||
{-| Green palette
|
||||
-}
|
||||
green : Palette
|
||||
green =
|
||||
{ border = Colors.green
|
||||
, background = Colors.greenLightest
|
||||
, primary = Colors.green
|
||||
, name = Green
|
||||
}
|
||||
|
||||
|
||||
{-| Red palette
|
||||
-}
|
||||
red : Palette
|
||||
red =
|
||||
{ border = Colors.red
|
||||
, background = Colors.redLight
|
||||
, primary = Colors.red
|
||||
, name = Red
|
||||
}
|
||||
|
||||
|
||||
{-| White palette (borders are blue)
|
||||
-}
|
||||
white : Palette
|
||||
white =
|
||||
{ border = Colors.navy
|
||||
, background = Colors.white
|
||||
, primary = Colors.navy
|
||||
, name = White
|
||||
}
|
||||
|
||||
|
||||
{-| Cornflower palette
|
||||
-}
|
||||
cornflower : Palette
|
||||
cornflower =
|
||||
{ border = Colors.cornflower
|
||||
, background = Colors.cornflowerLight
|
||||
, primary = Colors.cornflower
|
||||
, name = Cornflower
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
module Nri.Ui.PremiumCheckbox.V1 exposing (PremiumConfig, premium)
|
||||
|
||||
{-|
|
||||
|
||||
@docs PremiumConfig, premium
|
||||
|
||||
-}
|
||||
|
||||
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.V3 as Checkbox
|
||||
import Nri.Ui.Data.PremiumLevel as PremiumLevel exposing (PremiumLevel(..))
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
- `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
|
||||
, teacherPremiumLevel : PremiumLevel
|
||||
, contentPremiumLevel : PremiumLevel
|
||||
, showFlagWhenLocked : Bool
|
||||
, onChange : Bool -> msg
|
||||
, onLockedClick : msg
|
||||
, noOpMsg : msg
|
||||
}
|
||||
|
||||
|
||||
{-| A checkbox that should be used for premium content
|
||||
|
||||
This checkbox is locked when the premium level of the content is greater than the premium level of the teacher
|
||||
|
||||
-}
|
||||
premium : Assets a -> PremiumConfig msg -> Html.Html msg
|
||||
premium assets config =
|
||||
let
|
||||
isLocked =
|
||||
not <|
|
||||
PremiumLevel.allowedFor
|
||||
config.contentPremiumLevel
|
||||
config.teacherPremiumLevel
|
||||
in
|
||||
Html.div
|
||||
[ css
|
||||
[ displayFlex
|
||||
, alignItems center
|
||||
]
|
||||
]
|
||||
[ Checkbox.viewWithLabel assets
|
||||
{ identifier = config.id
|
||||
, label = config.label
|
||||
, setterMsg =
|
||||
if isLocked then
|
||||
\_ -> config.onLockedClick
|
||||
|
||||
else
|
||||
config.onChange
|
||||
, selected = config.selected
|
||||
, disabled = config.disabled
|
||||
, theme =
|
||||
if isLocked then
|
||||
Checkbox.Locked
|
||||
|
||||
else
|
||||
Checkbox.Square
|
||||
, noOpMsg = config.noOpMsg
|
||||
}
|
||||
, if
|
||||
(isLocked && config.showFlagWhenLocked)
|
||||
|| (not isLocked && config.contentPremiumLevel /= Free)
|
||||
then
|
||||
Html.div
|
||||
[ Attributes.class "premium-checkbox-V1__PremiumClass"
|
||||
, css
|
||||
[ property "content" "''"
|
||||
, display inlineBlock
|
||||
, width (px 26)
|
||||
, height (px 24)
|
||||
, marginLeft (px 8)
|
||||
, backgroundImage assets.iconPremiumFlag_svg
|
||||
, backgroundRepeat noRepeat
|
||||
, backgroundPosition center
|
||||
]
|
||||
]
|
||||
[]
|
||||
|
||||
else
|
||||
Html.text ""
|
||||
]
|
||||
|
||||
|
||||
{-| The assets used in this module.
|
||||
-}
|
||||
type alias Assets r =
|
||||
{ r
|
||||
| checkboxUnchecked_svg : Asset
|
||||
, checkboxChecked_svg : Asset
|
||||
, checkboxCheckedPartially_svg : Asset
|
||||
, checkboxLockOnInside_svg : Asset
|
||||
, iconPremiumFlag_svg : Asset
|
||||
}
|
||||
|
||||
|
||||
backgroundImage : Asset -> Style
|
||||
backgroundImage =
|
||||
Nri.Ui.AssetPath.Css.url
|
||||
>> property "background-image"
|
@ -1,119 +0,0 @@
|
||||
module Nri.Ui.PremiumCheckbox.V2 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.V3 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
|
||||
, noOpMsg : 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 assets
|
||||
{ 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
|
||||
, noOpMsg = config.noOpMsg
|
||||
}
|
||||
, 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
|
||||
| checkboxUnchecked_svg : Asset
|
||||
, checkboxChecked_svg : Asset
|
||||
, checkboxCheckedPartially_svg : Asset
|
||||
, checkboxLockOnInside_svg : Asset
|
||||
, iconPremiumFlag_svg : Asset
|
||||
, iconPremiumWithWritingFlag_svg : Asset
|
||||
}
|
||||
|
||||
|
||||
backgroundImage : Asset -> Style
|
||||
backgroundImage =
|
||||
Nri.Ui.AssetPath.Css.url
|
||||
>> property "background-image"
|
@ -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
|
||||
]
|
@ -1,82 +0,0 @@
|
||||
module Nri.Ui.Select.V5 exposing (Config, view)
|
||||
|
||||
{-| Build a select input.
|
||||
|
||||
@docs Config, view
|
||||
|
||||
-}
|
||||
|
||||
import Css
|
||||
import Dict
|
||||
import Html.Styled as Html exposing (..)
|
||||
import Html.Styled.Attributes as Attributes exposing (..)
|
||||
import Html.Styled.Events exposing (..)
|
||||
import Json.Decode exposing (Decoder, andThen, succeed)
|
||||
import Nri.Ui
|
||||
import Nri.Ui.Colors.V1
|
||||
import Nri.Ui.Util
|
||||
|
||||
|
||||
{-| Configure a Select
|
||||
-}
|
||||
type alias Config a =
|
||||
{ choices : List { label : String, value : a }
|
||||
, current : a
|
||||
, id : Maybe String
|
||||
, valueToString : a -> String
|
||||
}
|
||||
|
||||
|
||||
{-| TODO: Consider moving this to Nri.Ui.Util once the non-0.19-approved `toString` is removed
|
||||
-}
|
||||
niceId : String -> String -> String
|
||||
niceId prefix x =
|
||||
prefix ++ "-" ++ Nri.Ui.Util.dashify (Nri.Ui.Util.removePunctuation x)
|
||||
|
||||
|
||||
{-| A select dropdown
|
||||
-}
|
||||
view : Config a -> Html a
|
||||
view config =
|
||||
let
|
||||
valueLookup =
|
||||
-- TODO: probably worth using Lazy here, since choices won't change often
|
||||
config.choices
|
||||
|> List.map (\x -> ( niceId "nri-select" (config.valueToString x.value), x.value ))
|
||||
|> Dict.fromList
|
||||
|
||||
decodeValue string =
|
||||
Dict.get string valueLookup
|
||||
|> Maybe.map Json.Decode.succeed
|
||||
|> Maybe.withDefault (Json.Decode.fail ("Nri.Select: could not decode the value: " ++ toString string ++ "\nexpected one of: " ++ toString (Dict.keys valueLookup)))
|
||||
|
||||
onSelectHandler =
|
||||
on "change" (targetValue |> andThen decodeValue)
|
||||
|
||||
viewChoice choice =
|
||||
Html.option
|
||||
[ Attributes.id (niceId "nri-select" (config.valueToString choice.value))
|
||||
, Attributes.value (niceId "nri-select" (config.valueToString choice.value))
|
||||
, Attributes.selected (choice.value == config.current)
|
||||
]
|
||||
[ Html.text choice.label ]
|
||||
|
||||
extraAttrs =
|
||||
config.id
|
||||
|> Maybe.map (\id -> [ Attributes.id id ])
|
||||
|> Maybe.withDefault []
|
||||
in
|
||||
config.choices
|
||||
|> List.map viewChoice
|
||||
|> Nri.Ui.styled Html.select
|
||||
"nri-select-menu"
|
||||
[ Css.backgroundColor Nri.Ui.Colors.V1.white
|
||||
, Css.border3 (Css.px 1) Css.solid Nri.Ui.Colors.V1.gray75
|
||||
, Css.borderRadius (Css.px 8)
|
||||
, Css.color Nri.Ui.Colors.V1.gray20
|
||||
, Css.cursor Css.pointer
|
||||
, Css.fontSize (Css.px 15)
|
||||
, Css.height (Css.px 45)
|
||||
, Css.width (Css.pct 100)
|
||||
]
|
||||
([ onSelectHandler ] ++ extraAttrs)
|
@ -1,296 +0,0 @@
|
||||
module Nri.Ui.Table.V3 exposing
|
||||
( Column, custom, string
|
||||
, view, viewWithoutHeader
|
||||
, viewLoading, viewLoadingWithoutHeader
|
||||
, keyframes, keyframeStyles
|
||||
)
|
||||
|
||||
{-| Upgrading from V1:
|
||||
|
||||
- All the `width` fields in column configurations now take an elm-css length
|
||||
value rather than an Integer. Change `width = 100` to `width = px 100` to get
|
||||
the same widths as before.
|
||||
- Tables now by default take the full width of the container they are placed in.
|
||||
If this is not what you want, wrap the table in an element with a fixed width.
|
||||
- The table module now makes use of `Html.Styled` and no longer exposes a
|
||||
separate `styles` value.
|
||||
Check out the [elm-css](http://package.elm-lang.org/packages/rtfeldman/elm-css/14.0.0/Html-Styled)
|
||||
documentation on Html.Styled to see how to work with it.
|
||||
- The default cell padding has been removed and content is not vertically
|
||||
centered in its cell. If you need to overwrite this, wrap your cells in
|
||||
elements providing custom styling to the cell.
|
||||
|
||||
@docs Column, custom, string
|
||||
|
||||
@docs view, viewWithoutHeader
|
||||
|
||||
@docs viewLoading, viewLoadingWithoutHeader
|
||||
|
||||
@docs keyframes, keyframeStyles
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
import DEPRECATED.Nri.Ui.Styles.V2
|
||||
import Html.Styled as Html exposing (..)
|
||||
import Html.Styled.Attributes exposing (css)
|
||||
import Nri.Ui.Colors.V1 exposing (..)
|
||||
import Nri.Ui.Fonts.V1 exposing (baseFont)
|
||||
|
||||
|
||||
{-| Closed representation of how to render the header and cells of a column
|
||||
in the table
|
||||
-}
|
||||
type Column data msg
|
||||
= Column (Html msg) (data -> Html msg) Style
|
||||
|
||||
|
||||
{-| A column that renders some aspect of a value as text
|
||||
-}
|
||||
string :
|
||||
{ header : String
|
||||
, value : data -> String
|
||||
, width : LengthOrAuto compatible
|
||||
}
|
||||
-> Column data msg
|
||||
string { header, value, width } =
|
||||
Column (Html.text header) (value >> Html.text) (Css.width width)
|
||||
|
||||
|
||||
{-| A column that renders however you want it to
|
||||
-}
|
||||
custom :
|
||||
{ header : Html msg
|
||||
, view : data -> Html msg
|
||||
, width : LengthOrAuto compatible
|
||||
}
|
||||
-> Column data msg
|
||||
custom { header, view, width } =
|
||||
Column header view (Css.width width)
|
||||
|
||||
|
||||
|
||||
-- VIEW
|
||||
|
||||
|
||||
{-| Displays a table of data without a header row
|
||||
-}
|
||||
viewWithoutHeader : List (Column data msg) -> List data -> Html msg
|
||||
viewWithoutHeader columns =
|
||||
tableWithoutHeader [] columns (viewRow columns)
|
||||
|
||||
|
||||
{-| Displays a table of data based on the provided column definitions
|
||||
-}
|
||||
view : List (Column data msg) -> List data -> Html msg
|
||||
view columns =
|
||||
tableWithHeader [] columns (viewRow columns)
|
||||
|
||||
|
||||
viewRow : List (Column data msg) -> data -> Html msg
|
||||
viewRow columns data =
|
||||
tr
|
||||
[ css rowStyles ]
|
||||
(List.map (viewColumn data) columns)
|
||||
|
||||
|
||||
viewColumn : data -> Column data msg -> Html msg
|
||||
viewColumn data (Column _ renderer width) =
|
||||
td
|
||||
[ css (width :: cellStyles)
|
||||
]
|
||||
[ renderer data ]
|
||||
|
||||
|
||||
|
||||
-- VIEW LOADING
|
||||
|
||||
|
||||
{-| Display a table with the given columns but instead of data, show blocked
|
||||
out text with an interesting animation. This view lets the user know that
|
||||
data is on its way and what it will look like when it arrives.
|
||||
-}
|
||||
viewLoading : List (Column data msg) -> Html msg
|
||||
viewLoading columns =
|
||||
tableWithHeader loadingTableStyles columns (viewLoadingRow columns) (List.range 0 8)
|
||||
|
||||
|
||||
{-| Display the loading table without a header row
|
||||
-}
|
||||
viewLoadingWithoutHeader : List (Column data msg) -> Html msg
|
||||
viewLoadingWithoutHeader columns =
|
||||
tableWithoutHeader loadingTableStyles columns (viewLoadingRow columns) (List.range 0 8)
|
||||
|
||||
|
||||
viewLoadingRow : List (Column data msg) -> Int -> Html msg
|
||||
viewLoadingRow columns index =
|
||||
tr
|
||||
[ css rowStyles ]
|
||||
(List.indexedMap (viewLoadingColumn index) columns)
|
||||
|
||||
|
||||
viewLoadingColumn : Int -> Int -> Column data msg -> Html msg
|
||||
viewLoadingColumn rowIndex colIndex (Column _ _ width) =
|
||||
td
|
||||
[ css (stylesLoadingColumn rowIndex colIndex width ++ cellStyles ++ loadingCellStyles)
|
||||
]
|
||||
[ span [ css loadingContentStyles ] [] ]
|
||||
|
||||
|
||||
stylesLoadingColumn : Int -> Int -> Style -> List Style
|
||||
stylesLoadingColumn rowIndex colIndex width =
|
||||
[ width
|
||||
, property "animation-delay" (toString (toFloat (rowIndex + colIndex) * 0.1) ++ "s")
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- HELP
|
||||
|
||||
|
||||
tableWithoutHeader : List Style -> List (Column data msg) -> (a -> Html msg) -> List a -> Html msg
|
||||
tableWithoutHeader styles columns toRow data =
|
||||
table styles
|
||||
[ tableBody toRow data
|
||||
]
|
||||
|
||||
|
||||
tableWithHeader : List Style -> List (Column data msg) -> (a -> Html msg) -> List a -> Html msg
|
||||
tableWithHeader styles columns toRow data =
|
||||
table styles
|
||||
[ tableHeader columns
|
||||
, tableBody toRow data
|
||||
]
|
||||
|
||||
|
||||
table : List Style -> List (Html msg) -> Html msg
|
||||
table styles =
|
||||
Html.table [ css (styles ++ tableStyles) ]
|
||||
|
||||
|
||||
tableHeader : List (Column data msg) -> Html msg
|
||||
tableHeader columns =
|
||||
thead []
|
||||
[ tr [ css headersStyles ]
|
||||
(List.map tableRowHeader columns)
|
||||
]
|
||||
|
||||
|
||||
tableRowHeader : Column data msg -> Html msg
|
||||
tableRowHeader (Column header _ width) =
|
||||
th
|
||||
[ css (width :: headerStyles)
|
||||
]
|
||||
[ header ]
|
||||
|
||||
|
||||
tableBody : (a -> Html msg) -> List a -> Html msg
|
||||
tableBody toRow items =
|
||||
tbody [] (List.map toRow items)
|
||||
|
||||
|
||||
|
||||
-- STYLES
|
||||
|
||||
|
||||
headersStyles : List Style
|
||||
headersStyles =
|
||||
[ borderBottom3 (px 3) solid gray75
|
||||
, height (px 45)
|
||||
, fontSize (px 15)
|
||||
]
|
||||
|
||||
|
||||
headerStyles : List Style
|
||||
headerStyles =
|
||||
[ padding4 (px 15) (px 12) (px 11) (px 12)
|
||||
, textAlign left
|
||||
, fontWeight bold
|
||||
]
|
||||
|
||||
|
||||
rowStyles : List Style
|
||||
rowStyles =
|
||||
[ height (px 45)
|
||||
, fontSize (px 14)
|
||||
, color gray20
|
||||
, pseudoClass "nth-child(odd)"
|
||||
[ backgroundColor gray96 ]
|
||||
]
|
||||
|
||||
|
||||
cellStyles : List Style
|
||||
cellStyles =
|
||||
[ verticalAlign middle
|
||||
]
|
||||
|
||||
|
||||
loadingContentStyles : List Style
|
||||
loadingContentStyles =
|
||||
[ width (pct 100)
|
||||
, display inlineBlock
|
||||
, height (Css.em 1)
|
||||
, borderRadius (Css.em 1)
|
||||
, backgroundColor gray75
|
||||
]
|
||||
|
||||
|
||||
loadingCellStyles : List Style
|
||||
loadingCellStyles =
|
||||
[ batch flashAnimation
|
||||
, padding2 (px 14) (px 10)
|
||||
]
|
||||
|
||||
|
||||
loadingTableStyles : List Style
|
||||
loadingTableStyles =
|
||||
fadeInAnimation
|
||||
|
||||
|
||||
tableStyles : List Style
|
||||
tableStyles =
|
||||
[ borderCollapse collapse
|
||||
, baseFont
|
||||
, Css.width (Css.pct 100)
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
keyframes : List DEPRECATED.Nri.Ui.Styles.V2.Keyframe
|
||||
keyframes =
|
||||
[ DEPRECATED.Nri.Ui.Styles.V2.keyframes "Nri-Ui-Table-V2-flash"
|
||||
[ ( "0%", "opacity: 0.6" )
|
||||
, ( "50%", "opacity: 0.2" )
|
||||
, ( "100%", "opacity: 0.6" )
|
||||
]
|
||||
, DEPRECATED.Nri.Ui.Styles.V2.keyframes "Nri-Ui-Table-V2-fadein"
|
||||
[ ( "from", "opacity: 0" )
|
||||
, ( "to", "opacity: 1" )
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
keyframeStyles : Html msg
|
||||
keyframeStyles =
|
||||
Html.node "style"
|
||||
[]
|
||||
(List.map (Html.text << DEPRECATED.Nri.Ui.Styles.V2.toString) keyframes)
|
||||
|
||||
|
||||
flashAnimation : List Css.Style
|
||||
flashAnimation =
|
||||
[ property "-webkit-animation" "Nri-Ui-Table-V2-flash 2s infinite"
|
||||
, property "-moz-animation" "Nri-Ui-Table-V2-flash 2s infinite"
|
||||
, property "animation" "Nri-Ui-Table-V2-flash 2s infinite"
|
||||
, opacity (num 0.6)
|
||||
]
|
||||
|
||||
|
||||
fadeInAnimation : List Css.Style
|
||||
fadeInAnimation =
|
||||
[ property "-webkit-animation" "Nri-Ui-Table-V2-fadein 0.4s 0.2s forwards"
|
||||
, property "-moz-animation" "Nri-Ui-Table-V2-fadein 0.4s 0.2s forwards"
|
||||
, property "animation" "Nri-Ui-Table-V2-fadein 0.4s 0.2s forwards"
|
||||
, opacity (num 0)
|
||||
]
|
@ -1,289 +0,0 @@
|
||||
module Nri.Ui.Table.V4 exposing
|
||||
( Column, custom, string
|
||||
, view, viewWithoutHeader
|
||||
, viewLoading, viewLoadingWithoutHeader
|
||||
)
|
||||
|
||||
{-| Upgrading from V1:
|
||||
|
||||
- All the `width` fields in column configurations now take an elm-css length
|
||||
value rather than an Integer. Change `width = 100` to `width = px 100` to get
|
||||
the same widths as before.
|
||||
- Tables now by default take the full width of the container they are placed in.
|
||||
If this is not what you want, wrap the table in an element with a fixed width.
|
||||
- The table module now makes use of `Html.Styled` and no longer exposes a
|
||||
separate `styles` value.
|
||||
Check out the [elm-css](http://package.elm-lang.org/packages/rtfeldman/elm-css/14.0.0/Html-Styled)
|
||||
documentation on Html.Styled to see how to work with it.
|
||||
- The default cell padding has been removed and content is not vertically
|
||||
centered in its cell. If you need to overwrite this, wrap your cells in
|
||||
elements providing custom styling to the cell.
|
||||
|
||||
@docs Column, custom, string
|
||||
|
||||
@docs view, viewWithoutHeader
|
||||
|
||||
@docs viewLoading, viewLoadingWithoutHeader
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
import Css.Animations
|
||||
import Html.Styled as Html exposing (..)
|
||||
import Html.Styled.Attributes exposing (css)
|
||||
import Nri.Ui.Colors.V1 exposing (..)
|
||||
import Nri.Ui.Fonts.V1 exposing (baseFont)
|
||||
|
||||
|
||||
{-| Closed representation of how to render the header and cells of a column
|
||||
in the table
|
||||
-}
|
||||
type Column data msg
|
||||
= Column (Html msg) (data -> Html msg) Style
|
||||
|
||||
|
||||
{-| A column that renders some aspect of a value as text
|
||||
-}
|
||||
string :
|
||||
{ header : String
|
||||
, value : data -> String
|
||||
, width : LengthOrAuto compatible
|
||||
}
|
||||
-> Column data msg
|
||||
string { header, value, width } =
|
||||
Column (Html.text header) (value >> Html.text) (Css.width width)
|
||||
|
||||
|
||||
{-| A column that renders however you want it to
|
||||
-}
|
||||
custom :
|
||||
{ header : Html msg
|
||||
, view : data -> Html msg
|
||||
, width : LengthOrAuto compatible
|
||||
}
|
||||
-> Column data msg
|
||||
custom { header, view, width } =
|
||||
Column header view (Css.width width)
|
||||
|
||||
|
||||
|
||||
-- VIEW
|
||||
|
||||
|
||||
{-| Displays a table of data without a header row
|
||||
-}
|
||||
viewWithoutHeader : List (Column data msg) -> List data -> Html msg
|
||||
viewWithoutHeader columns =
|
||||
tableWithoutHeader [] columns (viewRow columns)
|
||||
|
||||
|
||||
{-| Displays a table of data based on the provided column definitions
|
||||
-}
|
||||
view : List (Column data msg) -> List data -> Html msg
|
||||
view columns =
|
||||
tableWithHeader [] columns (viewRow columns)
|
||||
|
||||
|
||||
viewRow : List (Column data msg) -> data -> Html msg
|
||||
viewRow columns data =
|
||||
tr
|
||||
[ css rowStyles ]
|
||||
(List.map (viewColumn data) columns)
|
||||
|
||||
|
||||
viewColumn : data -> Column data msg -> Html msg
|
||||
viewColumn data (Column _ renderer width) =
|
||||
td
|
||||
[ css (width :: cellStyles)
|
||||
]
|
||||
[ renderer data ]
|
||||
|
||||
|
||||
|
||||
-- VIEW LOADING
|
||||
|
||||
|
||||
{-| Display a table with the given columns but instead of data, show blocked
|
||||
out text with an interesting animation. This view lets the user know that
|
||||
data is on its way and what it will look like when it arrives.
|
||||
-}
|
||||
viewLoading : List (Column data msg) -> Html msg
|
||||
viewLoading columns =
|
||||
tableWithHeader loadingTableStyles columns (viewLoadingRow columns) (List.range 0 8)
|
||||
|
||||
|
||||
{-| Display the loading table without a header row
|
||||
-}
|
||||
viewLoadingWithoutHeader : List (Column data msg) -> Html msg
|
||||
viewLoadingWithoutHeader columns =
|
||||
tableWithoutHeader loadingTableStyles columns (viewLoadingRow columns) (List.range 0 8)
|
||||
|
||||
|
||||
viewLoadingRow : List (Column data msg) -> Int -> Html msg
|
||||
viewLoadingRow columns index =
|
||||
tr
|
||||
[ css rowStyles ]
|
||||
(List.indexedMap (viewLoadingColumn index) columns)
|
||||
|
||||
|
||||
viewLoadingColumn : Int -> Int -> Column data msg -> Html msg
|
||||
viewLoadingColumn rowIndex colIndex (Column _ _ width) =
|
||||
td
|
||||
[ css (stylesLoadingColumn rowIndex colIndex width ++ cellStyles ++ loadingCellStyles)
|
||||
]
|
||||
[ span [ css loadingContentStyles ] [] ]
|
||||
|
||||
|
||||
stylesLoadingColumn : Int -> Int -> Style -> List Style
|
||||
stylesLoadingColumn rowIndex colIndex width =
|
||||
[ width
|
||||
, property "animation-delay" (toString (toFloat (rowIndex + colIndex) * 0.1) ++ "s")
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- HELP
|
||||
|
||||
|
||||
tableWithoutHeader : List Style -> List (Column data msg) -> (a -> Html msg) -> List a -> Html msg
|
||||
tableWithoutHeader styles columns toRow data =
|
||||
table styles
|
||||
[ tableBody toRow data
|
||||
]
|
||||
|
||||
|
||||
tableWithHeader : List Style -> List (Column data msg) -> (a -> Html msg) -> List a -> Html msg
|
||||
tableWithHeader styles columns toRow data =
|
||||
table styles
|
||||
[ tableHeader columns
|
||||
, tableBody toRow data
|
||||
]
|
||||
|
||||
|
||||
table : List Style -> List (Html msg) -> Html msg
|
||||
table styles =
|
||||
Html.table [ css (styles ++ tableStyles) ]
|
||||
|
||||
|
||||
tableHeader : List (Column data msg) -> Html msg
|
||||
tableHeader columns =
|
||||
thead []
|
||||
[ tr [ css headersStyles ]
|
||||
(List.map tableRowHeader columns)
|
||||
]
|
||||
|
||||
|
||||
tableRowHeader : Column data msg -> Html msg
|
||||
tableRowHeader (Column header _ width) =
|
||||
th
|
||||
[ css (width :: headerStyles)
|
||||
]
|
||||
[ header ]
|
||||
|
||||
|
||||
tableBody : (a -> Html msg) -> List a -> Html msg
|
||||
tableBody toRow items =
|
||||
tbody [] (List.map toRow items)
|
||||
|
||||
|
||||
|
||||
-- STYLES
|
||||
|
||||
|
||||
headersStyles : List Style
|
||||
headersStyles =
|
||||
[ borderBottom3 (px 3) solid gray75
|
||||
, height (px 45)
|
||||
, fontSize (px 15)
|
||||
]
|
||||
|
||||
|
||||
headerStyles : List Style
|
||||
headerStyles =
|
||||
[ padding4 (px 15) (px 12) (px 11) (px 12)
|
||||
, textAlign left
|
||||
, fontWeight bold
|
||||
]
|
||||
|
||||
|
||||
rowStyles : List Style
|
||||
rowStyles =
|
||||
[ height (px 45)
|
||||
, fontSize (px 14)
|
||||
, color gray20
|
||||
, pseudoClass "nth-child(odd)"
|
||||
[ backgroundColor gray96 ]
|
||||
]
|
||||
|
||||
|
||||
cellStyles : List Style
|
||||
cellStyles =
|
||||
[ verticalAlign middle
|
||||
]
|
||||
|
||||
|
||||
loadingContentStyles : List Style
|
||||
loadingContentStyles =
|
||||
[ width (pct 100)
|
||||
, display inlineBlock
|
||||
, height (Css.em 1)
|
||||
, borderRadius (Css.em 1)
|
||||
, backgroundColor gray75
|
||||
]
|
||||
|
||||
|
||||
loadingCellStyles : List Style
|
||||
loadingCellStyles =
|
||||
[ batch flashAnimation
|
||||
, padding2 (px 14) (px 10)
|
||||
]
|
||||
|
||||
|
||||
loadingTableStyles : List Style
|
||||
loadingTableStyles =
|
||||
fadeInAnimation
|
||||
|
||||
|
||||
tableStyles : List Style
|
||||
tableStyles =
|
||||
[ borderCollapse collapse
|
||||
, baseFont
|
||||
, Css.width (Css.pct 100)
|
||||
]
|
||||
|
||||
|
||||
flash : Css.Animations.Keyframes {}
|
||||
flash =
|
||||
Css.Animations.keyframes
|
||||
[ ( 0, [ Css.Animations.opacity (Css.num 0.6) ] )
|
||||
, ( 50, [ Css.Animations.opacity (Css.num 0.2) ] )
|
||||
, ( 100, [ Css.Animations.opacity (Css.num 0.6) ] )
|
||||
]
|
||||
|
||||
|
||||
fadeIn : Css.Animations.Keyframes {}
|
||||
fadeIn =
|
||||
Css.Animations.keyframes
|
||||
[ ( 0, [ Css.Animations.opacity (Css.num 0) ] )
|
||||
, ( 100, [ Css.Animations.opacity (Css.num 1) ] )
|
||||
]
|
||||
|
||||
|
||||
flashAnimation : List Css.Style
|
||||
flashAnimation =
|
||||
[ animationName flash
|
||||
, property "animation-duration" "2s"
|
||||
, property "animation-iteration-count" "infinite"
|
||||
, opacity (num 0.6)
|
||||
]
|
||||
|
||||
|
||||
fadeInAnimation : List Css.Style
|
||||
fadeInAnimation =
|
||||
[ animationName fadeIn
|
||||
, property "animation-duration" "0.4s"
|
||||
, property "animation-delay" "0.2s"
|
||||
, property "animation-fill-mode" "forwards"
|
||||
, animationIterationCount (int 1)
|
||||
, opacity (num 0)
|
||||
]
|
@ -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 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.button
|
||||
[ 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.border Css.zero
|
||||
, 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 before current after) =
|
||||
Zipper
|
||||
(List.map (fn False) before)
|
||||
(fn True current)
|
||||
(List.map (fn False) after)
|
||||
|
||||
|
||||
|
||||
-- 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
|
||||
]
|
||||
]
|
@ -1,258 +0,0 @@
|
||||
module Nri.Ui.Text.V2 exposing
|
||||
( caption, heading, mediumBody, smallBody, smallBodyGray, subHeading, smallHeading, tagline
|
||||
, ugMediumBody, ugSmallBody
|
||||
, noWidow
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
## Semantic text types:
|
||||
|
||||
@docs caption, heading, mediumBody, smallBody, smallBodyGray, subHeading, smallHeading, tagline
|
||||
|
||||
|
||||
## User-generated text styles:
|
||||
|
||||
@docs ugMediumBody, ugSmallBody
|
||||
|
||||
|
||||
## Modifying strings to display nicely:
|
||||
|
||||
@docs noWidow
|
||||
|
||||
-}
|
||||
|
||||
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
|
||||
|
||||
|
||||
{-| This is a Page Heading.
|
||||
-}
|
||||
heading : List (Html msg) -> Html msg
|
||||
heading content =
|
||||
h1
|
||||
[ css
|
||||
(textStyles
|
||||
++ [ Fonts.baseFont
|
||||
, fontSize (px 30)
|
||||
, color navy
|
||||
, lineHeight (px 40.5)
|
||||
, fontWeight (int 700)
|
||||
, margin zero
|
||||
]
|
||||
)
|
||||
]
|
||||
content
|
||||
|
||||
|
||||
{-| This is a tagline for a page heading.
|
||||
-}
|
||||
tagline : List (Html msg) -> Html msg
|
||||
tagline content =
|
||||
h2
|
||||
[ css
|
||||
(textStyles
|
||||
++ [ Fonts.baseFont
|
||||
, fontSize (px 20)
|
||||
, color gray45
|
||||
, lineHeight (px 27)
|
||||
, fontWeight (int 400)
|
||||
, margin4 (px 5) (px 0) (px 0) (px 0)
|
||||
]
|
||||
)
|
||||
]
|
||||
content
|
||||
|
||||
|
||||
{-| This is a subhead.
|
||||
-}
|
||||
subHeading : List (Html msg) -> Html msg
|
||||
subHeading content =
|
||||
h3
|
||||
[ css
|
||||
(textStyles
|
||||
++ [ Fonts.baseFont
|
||||
, fontSize (px 20)
|
||||
, color navy
|
||||
, lineHeight (px 27)
|
||||
, fontWeight (int 700)
|
||||
, margin4 (px 20) (px 0) (px 10) (px 0)
|
||||
]
|
||||
)
|
||||
]
|
||||
content
|
||||
|
||||
|
||||
{-| This is a small Page Heading.
|
||||
-}
|
||||
smallHeading : List (Html msg) -> Html msg
|
||||
smallHeading content =
|
||||
h4
|
||||
[ css
|
||||
(textStyles
|
||||
++ [ Fonts.baseFont
|
||||
, fontSize (px 16)
|
||||
, color gray20
|
||||
, lineHeight (px 23)
|
||||
, fontWeight (int 700)
|
||||
, margin zero
|
||||
]
|
||||
)
|
||||
]
|
||||
content
|
||||
|
||||
|
||||
{-| This is some medium body copy.
|
||||
-}
|
||||
mediumBody : List (Html msg) -> Html msg
|
||||
mediumBody content =
|
||||
p
|
||||
[ css
|
||||
(textStyles
|
||||
++ [ Fonts.baseFont
|
||||
, fontSize (px 18)
|
||||
, color gray20
|
||||
, lineHeight (px 27)
|
||||
, fontWeight (int 400)
|
||||
, margin4 (px 10) (px 0) (px 0) (px 0)
|
||||
]
|
||||
)
|
||||
]
|
||||
content
|
||||
|
||||
|
||||
{-| This is some small body copy.
|
||||
-}
|
||||
smallBody : List (Html msg) -> Html msg
|
||||
smallBody content =
|
||||
p
|
||||
[ css
|
||||
(textStyles
|
||||
++ [ Fonts.baseFont
|
||||
, fontSize (px 15)
|
||||
, color gray20
|
||||
, lineHeight (px 23)
|
||||
, fontWeight (int 400)
|
||||
, margin4 (px 7) (px 0) (px 0) (px 0)
|
||||
]
|
||||
)
|
||||
]
|
||||
content
|
||||
|
||||
|
||||
{-| This is some small body copy but it's gray.
|
||||
-}
|
||||
smallBodyGray : List (Html msg) -> Html msg
|
||||
smallBodyGray content =
|
||||
p
|
||||
[ css
|
||||
(textStyles
|
||||
++ [ Fonts.baseFont
|
||||
, fontSize (px 15)
|
||||
, color gray45
|
||||
, lineHeight (px 23)
|
||||
, fontWeight (int 400)
|
||||
, margin4 (px 7) (px 0) (px 0) (px 0)
|
||||
]
|
||||
)
|
||||
]
|
||||
content
|
||||
|
||||
|
||||
textStyles =
|
||||
[ padding zero
|
||||
, textAlign left
|
||||
, firstChild
|
||||
[ margin zero
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
{-| This is a little note or caption.
|
||||
-}
|
||||
caption : List (Html msg) -> Html msg
|
||||
caption content =
|
||||
p
|
||||
[ css
|
||||
[ Fonts.baseFont
|
||||
, fontSize (px 13)
|
||||
, color gray45
|
||||
, lineHeight (px 18)
|
||||
, fontWeight (int 400)
|
||||
, margin4 (px 5) (px 0) (px 0) (px 0)
|
||||
]
|
||||
]
|
||||
content
|
||||
|
||||
|
||||
{-| User-generated text.
|
||||
-}
|
||||
ugMediumBody : List (Html msg) -> Html msg
|
||||
ugMediumBody =
|
||||
p
|
||||
[ css
|
||||
[ Fonts.quizFont
|
||||
, fontSize (px 18)
|
||||
, lineHeight (px 30)
|
||||
, whiteSpace preLine
|
||||
, color gray20
|
||||
, margin4 (px 10) (px 0) (px 0) (px 0)
|
||||
, firstChild [ margin zero ]
|
||||
, firstOfType [ margin zero ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
{-| User-generated text.
|
||||
-}
|
||||
ugSmallBody : List (Html msg) -> Html msg
|
||||
ugSmallBody =
|
||||
p
|
||||
[ css
|
||||
[ Fonts.quizFont
|
||||
, fontSize (px 16)
|
||||
, lineHeight (px 25)
|
||||
, whiteSpace preLine
|
||||
, color gray20
|
||||
, margin4 (px 7) (px 0) (px 0) (px 0)
|
||||
, firstChild [ margin zero ]
|
||||
, firstOfType [ margin zero ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
{-| 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 inputs =
|
||||
let
|
||||
-- this value is a unicode non-breaking space since Elm
|
||||
-- doesn't support named character entities
|
||||
nbsp =
|
||||
" "
|
||||
|
||||
words =
|
||||
String.split " " inputs
|
||||
|
||||
insertPoint =
|
||||
List.length words - 1
|
||||
in
|
||||
words
|
||||
|> List.indexedMap
|
||||
(\i word ->
|
||||
if i == 0 then
|
||||
word
|
||||
|
||||
else if i == insertPoint && insertPoint > 0 then
|
||||
nbsp ++ word
|
||||
|
||||
else
|
||||
" " ++ word
|
||||
)
|
||||
|> String.join ""
|
@ -1,34 +0,0 @@
|
||||
module Nri.Ui.Text.Writing.V1 exposing (footnote)
|
||||
|
||||
{-| Text types for writing:
|
||||
|
||||
@docs footnote
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
import Html.Styled exposing (..)
|
||||
import Nri.Ui.Colors.V1 exposing (..)
|
||||
import Nri.Ui.Fonts.V1 exposing (quizFont)
|
||||
|
||||
|
||||
{-| This is a little note or footnote.
|
||||
-}
|
||||
footnote : List (Html msg) -> Html msg
|
||||
footnote =
|
||||
styled p
|
||||
[ makeWritingFont (px 13) gray45
|
||||
, lineHeight (px 18)
|
||||
, fontWeight (int 400)
|
||||
, margin4 (px 5) (px 0) (px 0) (px 0)
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
makeWritingFont : Css.FontSize a -> Css.ColorValue b -> Style
|
||||
makeWritingFont size fontColor =
|
||||
Css.batch
|
||||
[ quizFont
|
||||
, fontSize size
|
||||
, color fontColor
|
||||
]
|
@ -1,201 +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.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"
|
||||
]
|
||||
[ Html.text model.value ]
|
||||
]
|
||||
, if not model.showLabel then
|
||||
Html.label
|
||||
[ Attributes.for (generateId model.label)
|
||||
, Attributes.css [ InputStyles.label theme model.isInError ]
|
||||
, 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)
|
@ -1,142 +0,0 @@
|
||||
module Nri.Ui.TextInput.V3 exposing
|
||||
( Model
|
||||
, view, writing
|
||||
, number
|
||||
, text
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Model
|
||||
@docs view, writing
|
||||
|
||||
|
||||
## Input types
|
||||
|
||||
@docs number
|
||||
@docs text
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled.Style as Accessibility
|
||||
import Css exposing (batch, center, position, px, relative, textAlign)
|
||||
import Css.Global
|
||||
import Html.Styled as Html exposing (..)
|
||||
import Html.Styled.Attributes as Attributes exposing (..)
|
||||
import Html.Styled.Events as Events exposing (onInput)
|
||||
import Nri.Ui.InputStyles.V2 as InputStyles exposing (Theme)
|
||||
import Nri.Ui.Util exposing (dashify)
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Model value msg =
|
||||
{ label : String
|
||||
, isInError : Bool
|
||||
, onInput : value -> msg
|
||||
, placeholder : String
|
||||
, value : value
|
||||
, autofocus : Bool
|
||||
, showLabel : Bool
|
||||
, type_ : InputType value
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
type InputType value
|
||||
= InputType
|
||||
{ toString : value -> String
|
||||
, fromString : String -> value
|
||||
, fieldType : String
|
||||
}
|
||||
|
||||
|
||||
{-| An input that allows text entry
|
||||
-}
|
||||
text : InputType String
|
||||
text =
|
||||
InputType
|
||||
{ toString = identity
|
||||
, fromString = identity
|
||||
, fieldType = "text"
|
||||
}
|
||||
|
||||
|
||||
{-| An input that allows number entry
|
||||
-}
|
||||
number : InputType (Maybe Int)
|
||||
number =
|
||||
InputType
|
||||
{ toString = Maybe.map toString >> Maybe.withDefault ""
|
||||
, fromString = String.toInt >> Result.toMaybe
|
||||
, fieldType = "number"
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
view : Model value msg -> Html msg
|
||||
view model =
|
||||
view_ InputStyles.Standard model
|
||||
|
||||
|
||||
{-| -}
|
||||
writing : Model value msg -> Html msg
|
||||
writing model =
|
||||
view_ InputStyles.Writing model
|
||||
|
||||
|
||||
view_ : Theme -> Model value msg -> Html msg
|
||||
view_ theme model =
|
||||
let
|
||||
idValue =
|
||||
"Nri-Ui-TextInput-" ++ dashify model.label
|
||||
|
||||
(InputType inputType) =
|
||||
model.type_
|
||||
in
|
||||
div
|
||||
[ Attributes.css [ position relative ]
|
||||
]
|
||||
[ input
|
||||
[ Attributes.id idValue
|
||||
, css
|
||||
[ InputStyles.input theme model.isInError
|
||||
, if theme == InputStyles.Writing then
|
||||
Css.Global.withClass "override-sass-styles"
|
||||
[ textAlign center
|
||||
, Css.height Css.auto
|
||||
]
|
||||
|
||||
else
|
||||
Css.Global.withClass "override-sass-styles"
|
||||
[ Css.height (px 45)
|
||||
]
|
||||
]
|
||||
, placeholder model.placeholder
|
||||
, defaultValue (inputType.toString model.value)
|
||||
, onInput (inputType.fromString >> model.onInput)
|
||||
, autofocus model.autofocus
|
||||
, type_ inputType.fieldType
|
||||
, class "override-sass-styles"
|
||||
, Attributes.attribute "aria-invalid" <|
|
||||
if model.isInError then
|
||||
"true"
|
||||
|
||||
else
|
||||
"false"
|
||||
]
|
||||
[]
|
||||
, if model.showLabel then
|
||||
Html.label
|
||||
[ for idValue
|
||||
, css [ InputStyles.label theme model.isInError ]
|
||||
]
|
||||
[ Html.text model.label ]
|
||||
|
||||
else
|
||||
Html.label
|
||||
[ for idValue
|
||||
, css [ InputStyles.label theme model.isInError ]
|
||||
, Accessibility.invisible
|
||||
]
|
||||
[ Html.text model.label ]
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
module Nri.Ui.Util exposing (dashify, removePunctuation)
|
||||
|
||||
import Regex
|
||||
|
||||
|
||||
{-| Convenience method for going from a string with spaces to a string with dashes.
|
||||
-}
|
||||
dashify : String -> String
|
||||
dashify =
|
||||
Regex.replace Regex.All (Regex.regex " ") (always "-")
|
||||
|
||||
|
||||
{-| Convenience method for removing punctuation
|
||||
(removes everything that isn't whitespace or alphanumeric).
|
||||
-}
|
||||
removePunctuation : String -> String
|
||||
removePunctuation =
|
||||
Regex.replace Regex.All (Regex.regex "[^A-z0-9\\w\\s]") (always "")
|
@ -1,25 +0,0 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"summary": "helpful summary of your project, less than 80 characters",
|
||||
"repository": "https://github.com/user/project.git",
|
||||
"license": "BSD3",
|
||||
"source-directories": [
|
||||
".",
|
||||
"../src-0.18"
|
||||
],
|
||||
"exposed-modules": [],
|
||||
"dependencies": {
|
||||
"elm-community/string-extra": "1.4.0 <= v < 5.0.0",
|
||||
"elm-lang/core": "5.1.1 <= v < 6.0.0",
|
||||
"elm-lang/html": "2.0.0 <= v < 3.0.0",
|
||||
"elm-lang/navigation": "2.1.0 <= v < 3.0.0",
|
||||
"elm-lang/svg": "2.0.0 <= v < 3.0.0",
|
||||
"evancz/url-parser": "2.0.1 <= v < 3.0.0",
|
||||
"lukewestby/accessible-html-with-css-temp": "1.0.0 <= v < 2.0.0",
|
||||
"pablohirafuji/elm-markdown": "2.0.4 <= v < 3.0.0",
|
||||
"rtfeldman/elm-css": "16.0.0 <= v < 17.0.0",
|
||||
"tesk9/accessible-html": "3.1.0 <= v < 4.0.0",
|
||||
"wernerdegroot/listzipper": "3.0.0 <= v < 4.0.0"
|
||||
},
|
||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||
}
|
Loading…
Reference in New Issue
Block a user