Merge pull request #277 from NoRedInk/tessa/kill-dead-code

💀 remove 0.18 modules
This commit is contained in:
Tessa 2019-05-22 14:05:13 -07:00 committed by GitHub
commit cc727ad70d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 4 additions and 11021 deletions

View File

@ -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:

View File

@ -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"
}

View File

@ -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(

View File

@ -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

View File

@ -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)
)

View File

@ -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)
)

View File

@ -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

View File

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

View File

@ -1,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

View File

@ -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 ++ ")"

View File

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

View File

@ -1,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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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
]

View File

@ -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

View File

@ -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"

View File

@ -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)
]

View File

@ -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
]

View File

@ -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)
]

View File

@ -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, ")" ]

View File

@ -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" ]

View File

@ -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

View File

@ -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

View File

@ -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 "&nbsp;"` not working in elm.
-}
nbsp : Html msg
nbsp =
Char.fromCode 160
|> String.fromChar
|> Html.text

View File

@ -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%" )
]

View File

@ -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%" )
]

View File

@ -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

View File

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

View File

@ -1,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
)

View File

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

View File

@ -1,201 +0,0 @@
module Nri.Ui.Page.V2 exposing (DefaultPage, broken, blocked, notFound, noPermission)
{-| A styled NRI issue page!
@docs DefaultPage, broken, blocked, notFound, noPermission
-}
import Css exposing (..)
import Html.Styled as Html exposing (Html)
import Html.Styled.Attributes as Attributes
import Nri.Ui.Button.V5 as Button
import Nri.Ui.Text.V2 as Text
{-| The default page information is for the button
which will direct the user back to the main page of
the SPA. Specify it's name and the message which will
navigate to the page.
-}
type alias DefaultPage msg =
{ link : msg
, name : String
}
{-| For the not found page.
-}
notFound : DefaultPage msg -> Html msg
notFound defaultPage =
view
{ emoji = "\x1F914"
, title = "We couldnt find that!"
, subtitle = "Feel free to browse around, or check out our help center."
, defaultPage = Just defaultPage
, details = Nothing
}
{-| For HTTP errors and other broken states.
-}
broken : DefaultPage msg -> Html msg
broken defaultPage =
view
{ emoji = "😵"
, title = "There was a problem!"
, subtitle = "You can try again, or check out our help center."
, defaultPage = Just defaultPage
, details = Nothing
}
{-| For HTTP errors and other broken states, where link goes to "/".
-}
blocked : String -> Html msg
blocked details =
view
{ emoji = "😵"
, title = "There was a problem!"
, subtitle = "You can try again, or check out our help center."
, defaultPage = Nothing
, details = Just details
}
{-| For pages the user does not have access to.
-}
noPermission : DefaultPage msg -> Html msg
noPermission defaultPage =
view
{ emoji = "\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)
]
[]

View File

@ -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
}

View File

@ -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"

View File

@ -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"

View File

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

View File

@ -1,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)

View File

@ -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)
]

View File

@ -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)
]

View File

@ -1,368 +0,0 @@
module Nri.Ui.Tabs.V3 exposing
( Alignment(..)
, Config
, LinkConfig
, Tab
, TabLink
, links
, view
, viewCustom
, viewTabDefault
)
{-|
@docs Alignment
@docs Config
@docs LinkConfig
@docs Tab
@docs TabLink
@docs links
@docs view
@docs viewCustom
## Defaults
@docs viewTabDefault
-}
import Accessibility.Aria
import Accessibility.Key
import Accessibility.Role
import Accessibility.Widget
import Css exposing (Style)
import Html.Styled as Html exposing (Attribute, Html)
import Html.Styled.Attributes as Attributes
import Html.Styled.Events as Events
import Json.Decode
import List.Zipper exposing (Zipper(..))
import 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
]
]

View File

@ -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 ""

View File

@ -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
]

View File

@ -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)

View File

@ -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 ]
]

View File

@ -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 "")

View File

@ -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"
}