mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-27 03:14:25 +03:00
Merge branch 'master' into tessa/kill-sortable-table-v1
This commit is contained in:
commit
327b49837d
4
elm.json
4
elm.json
@ -11,7 +11,6 @@
|
||||
"Nri.Ui.AssignmentIcon.V2",
|
||||
"Nri.Ui.Button.V10",
|
||||
"Nri.Ui.Button.V8",
|
||||
"Nri.Ui.Button.V5",
|
||||
"Nri.Ui.Callout.V1",
|
||||
"Nri.Ui.Checkbox.V5",
|
||||
"Nri.Ui.ClickableSvg.V1",
|
||||
@ -37,16 +36,13 @@
|
||||
"Nri.Ui.MasteryIcon.V1",
|
||||
"Nri.Ui.Menu.V1",
|
||||
"Nri.Ui.Message.V1",
|
||||
"Nri.Ui.Modal.V2",
|
||||
"Nri.Ui.Modal.V3",
|
||||
"Nri.Ui.Modal.V8",
|
||||
"Nri.Ui.Modal.V9",
|
||||
"Nri.Ui.Page.V3",
|
||||
"Nri.Ui.Palette.V1",
|
||||
"Nri.Ui.Pennant.V2",
|
||||
"Nri.Ui.PremiumCheckbox.V6",
|
||||
"Nri.Ui.RadioButton.V1",
|
||||
"Nri.Ui.SegmentedControl.V6",
|
||||
"Nri.Ui.SegmentedControl.V9",
|
||||
"Nri.Ui.Select.V5",
|
||||
"Nri.Ui.Select.V7",
|
||||
|
@ -3,8 +3,6 @@ src/Nri/Ui/Page/V3.elm,Nri.Ui.Text,2
|
||||
src/Nri/Ui/SlideModal/V2.elm,Nri.Ui.Button,8
|
||||
src/Nri/Ui/SlideModal/V2.elm,Nri.Ui.Text,2
|
||||
src/Nri/Ui/Button/V8.elm,Html,DEPRECATED
|
||||
src/Nri/Ui/Button/V5.elm,Nri.Ui.Icon,3
|
||||
src/Nri/Ui/SegmentedControl/V6.elm,Nri.Ui.Icon,3
|
||||
src/Nri/Ui/Icon/V3.elm,Accessibility.Role,DEPRECATED
|
||||
src/Nri/Ui/Icon/V3.elm,Html,DEPRECATED
|
||||
src/Nri/Ui/Icon/V5.elm,Accessibility.Role,DEPRECATED
|
||||
|
|
@ -1,813 +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
|
||||
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.preventDefaultOn "click"
|
||||
(Json.Decode.succeed ( onTrack, True ))
|
||||
]
|
||||
|
||||
|
||||
{-| Wrap some text so it looks like a button, but actually is wrapped in an anchor to some url and have it open to an external site
|
||||
|
||||
This should only take in messages that result in tracking events. For buttons that trigger other effects on the page, please use Nri.Ui.Button.V2.button instead
|
||||
|
||||
-}
|
||||
linkExternalWithTracking : msg -> LinkConfig -> Html msg
|
||||
linkExternalWithTracking onTrack =
|
||||
linkBase
|
||||
"linkExternalWithTracking"
|
||||
[ Attributes.target "_blank"
|
||||
, EventExtras.onClickForLinkWithHref onTrack
|
||||
]
|
||||
|
||||
|
||||
{-| Helper function for building links with an arbitrary number of Attributes
|
||||
-}
|
||||
linkBase : String -> List (Attribute msg) -> LinkConfig -> Html msg
|
||||
linkBase linkFunctionName extraAttrs config =
|
||||
Nri.Ui.styled Styled.a
|
||||
(styledName linkFunctionName)
|
||||
[ buttonStyles config.size config.width (styleToColorPalette config.style) ]
|
||||
(Attributes.href config.url
|
||||
:: extraAttrs
|
||||
)
|
||||
[ viewLabel config.icon config.label ]
|
||||
|
||||
|
||||
|
||||
-- HELPERS
|
||||
|
||||
|
||||
type ColorPalette
|
||||
= PrimaryColors
|
||||
| SecondaryColors
|
||||
| BorderlessColors
|
||||
| DangerColors
|
||||
| PremiumColors
|
||||
| InactiveColors
|
||||
| LoadingColors
|
||||
| SuccessColors
|
||||
| ErrorColors
|
||||
|
||||
|
||||
styleToColorPalette : ButtonStyle -> ColorPalette
|
||||
styleToColorPalette style =
|
||||
case style of
|
||||
Primary ->
|
||||
PrimaryColors
|
||||
|
||||
Secondary ->
|
||||
SecondaryColors
|
||||
|
||||
Borderless ->
|
||||
BorderlessColors
|
||||
|
||||
Danger ->
|
||||
DangerColors
|
||||
|
||||
Premium ->
|
||||
PremiumColors
|
||||
|
||||
|
||||
buttonStyles : ButtonSize -> ButtonWidth -> ColorPalette -> Style
|
||||
buttonStyles size width colorPalette =
|
||||
Css.batch
|
||||
[ buttonStyle
|
||||
, colorStyle colorPalette
|
||||
, sizeStyle size width
|
||||
]
|
||||
|
||||
|
||||
viewLabel : Maybe IconType -> String -> Html msg
|
||||
viewLabel icn label =
|
||||
Nri.Ui.styled Html.span
|
||||
"button-label-span"
|
||||
[ Css.overflow Css.hidden -- Keep scrollbars out of our button
|
||||
, Css.overflowWrap Css.breakWord -- Ensure that words that exceed the button width break instead of disappearing
|
||||
, Css.padding2 (Css.px 2) Css.zero -- Without a bit of bottom padding, text that extends below the baseline, like "g" gets cut off
|
||||
]
|
||||
[]
|
||||
(case icn of
|
||||
Nothing ->
|
||||
renderMarkdown label
|
||||
|
||||
Just iconType ->
|
||||
Icon.decorativeIcon iconType :: renderMarkdown label
|
||||
)
|
||||
|
||||
|
||||
renderMarkdown : String -> List (Html msg)
|
||||
renderMarkdown markdown =
|
||||
case Markdown.Block.parse Nothing markdown of
|
||||
-- It seems to be always first wrapped in a `Paragraph` and never directly a `PlainInline`
|
||||
[ Markdown.Block.Paragraph _ inlines ] ->
|
||||
List.map (Markdown.Inline.toHtml >> Styled.fromUnstyled) inlines
|
||||
|
||||
_ ->
|
||||
[ Html.text markdown ]
|
||||
|
||||
|
||||
|
||||
-- STYLES
|
||||
|
||||
|
||||
buttonStyle : Style
|
||||
buttonStyle =
|
||||
Css.batch
|
||||
[ Css.cursor Css.pointer
|
||||
, Css.display Css.inlineBlock
|
||||
, -- Specifying the font can and should go away after bootstrap is removed from application.css
|
||||
Nri.Ui.Fonts.V1.baseFont
|
||||
, Css.textOverflow Css.ellipsis
|
||||
, Css.overflow Css.hidden
|
||||
, Css.textDecoration Css.none
|
||||
, Css.backgroundImage Css.none
|
||||
, Css.textShadow Css.none
|
||||
, Css.property "transition" "background-color 0.2s, color 0.2s, box-shadow 0.2s, border 0.2s, border-width 0s"
|
||||
, Css.boxShadow Css.none
|
||||
, Css.border Css.zero
|
||||
, Css.marginBottom Css.zero
|
||||
, Css.hover [ Css.textDecoration Css.none ]
|
||||
, Css.disabled [ Css.cursor Css.notAllowed ]
|
||||
, Css.displayFlex
|
||||
, Css.alignItems Css.center
|
||||
, Css.justifyContent Css.center
|
||||
]
|
||||
|
||||
|
||||
colorStyle : ColorPalette -> Style
|
||||
colorStyle colorPalette =
|
||||
let
|
||||
( config, additionalStyles ) =
|
||||
case colorPalette of
|
||||
PrimaryColors ->
|
||||
( { background = Colors.azure
|
||||
, hover = Colors.azureDark
|
||||
, text = Colors.white
|
||||
, border = Nothing
|
||||
, shadow = Colors.azureDark
|
||||
}
|
||||
, []
|
||||
)
|
||||
|
||||
SecondaryColors ->
|
||||
( { background = Colors.white
|
||||
, hover = Colors.glacier
|
||||
, text = Colors.azure
|
||||
, border = Just <| Colors.azure
|
||||
, shadow = Colors.azure
|
||||
}
|
||||
, []
|
||||
)
|
||||
|
||||
BorderlessColors ->
|
||||
( { background = Css.rgba 0 0 0 0
|
||||
, hover = Css.rgba 0 0 0 0
|
||||
, text = Colors.azure
|
||||
, border = Nothing
|
||||
, shadow = Css.rgba 0 0 0 0
|
||||
}
|
||||
, [ Css.hover
|
||||
[ Css.textDecoration Css.underline
|
||||
, Css.disabled [ Css.textDecoration Css.none ]
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
DangerColors ->
|
||||
( { background = Colors.red
|
||||
, hover = Colors.redDark
|
||||
, text = Colors.white
|
||||
, border = Nothing
|
||||
, shadow = Colors.redDark
|
||||
}
|
||||
, []
|
||||
)
|
||||
|
||||
PremiumColors ->
|
||||
( { background = Colors.yellow
|
||||
, hover = Colors.ochre
|
||||
, text = Colors.navy
|
||||
, border = Nothing
|
||||
, shadow = Colors.ochre
|
||||
}
|
||||
, []
|
||||
)
|
||||
|
||||
InactiveColors ->
|
||||
( { background = Colors.gray92
|
||||
, hover = Colors.gray92
|
||||
, text = Colors.gray45
|
||||
, border = Nothing
|
||||
, shadow = Colors.gray92
|
||||
}
|
||||
, []
|
||||
)
|
||||
|
||||
LoadingColors ->
|
||||
( { background = Colors.glacier
|
||||
, hover = Colors.glacier
|
||||
, text = Colors.navy
|
||||
, border = Nothing
|
||||
, shadow = Colors.glacier
|
||||
}
|
||||
, []
|
||||
)
|
||||
|
||||
SuccessColors ->
|
||||
( { background = Colors.greenDark
|
||||
, hover = Colors.greenDark
|
||||
, text = Colors.white
|
||||
, border = Nothing
|
||||
, shadow = Colors.greenDark
|
||||
}
|
||||
, []
|
||||
)
|
||||
|
||||
ErrorColors ->
|
||||
( { background = Colors.purple
|
||||
, hover = Colors.purple
|
||||
, text = Colors.white
|
||||
, border = Nothing
|
||||
, shadow = Colors.purple
|
||||
}
|
||||
, []
|
||||
)
|
||||
in
|
||||
Css.batch
|
||||
[ Css.batch additionalStyles
|
||||
, Css.color config.text
|
||||
, Css.backgroundColor config.background
|
||||
, Css.fontWeight (Css.int 700)
|
||||
, Css.textAlign Css.center
|
||||
, case config.border of
|
||||
Nothing ->
|
||||
Css.borderStyle Css.none
|
||||
|
||||
Just color ->
|
||||
Css.batch
|
||||
[ Css.borderColor color
|
||||
, Css.borderStyle Css.solid
|
||||
]
|
||||
, Css.borderBottomStyle Css.solid
|
||||
, Css.borderBottomColor config.shadow
|
||||
, Css.fontStyle Css.normal
|
||||
, Css.hover
|
||||
[ Css.color config.text
|
||||
, Css.backgroundColor config.hover
|
||||
, Css.disabled [ Css.backgroundColor config.background ]
|
||||
]
|
||||
, Css.visited [ Css.color config.text ]
|
||||
]
|
||||
|
||||
|
||||
sizeStyle : ButtonSize -> ButtonWidth -> Style
|
||||
sizeStyle size width =
|
||||
let
|
||||
config =
|
||||
case size of
|
||||
Small ->
|
||||
{ fontSize = 15
|
||||
, height = 36
|
||||
, imageHeight = 15
|
||||
, shadowHeight = 2
|
||||
, minWidth = 75
|
||||
}
|
||||
|
||||
Medium ->
|
||||
{ fontSize = 17
|
||||
, height = 45
|
||||
, imageHeight = 15
|
||||
, shadowHeight = 3
|
||||
, minWidth = 100
|
||||
}
|
||||
|
||||
Large ->
|
||||
{ fontSize = 20
|
||||
, height = 56
|
||||
, imageHeight = 20
|
||||
, shadowHeight = 4
|
||||
, minWidth = 200
|
||||
}
|
||||
|
||||
sizingAttributes =
|
||||
let
|
||||
verticalPaddingPx =
|
||||
2
|
||||
in
|
||||
[ Css.minHeight (Css.px config.height)
|
||||
, Css.paddingTop (Css.px verticalPaddingPx)
|
||||
, Css.paddingBottom (Css.px verticalPaddingPx)
|
||||
]
|
||||
|
||||
widthAttributes =
|
||||
case width of
|
||||
WidthExact pxWidth ->
|
||||
[ Css.maxWidth (Css.pct 100)
|
||||
, Css.width (Css.px <| toFloat pxWidth)
|
||||
, Css.paddingRight (Css.px 4)
|
||||
, Css.paddingLeft (Css.px 4)
|
||||
]
|
||||
|
||||
WidthUnbounded ->
|
||||
[ Css.paddingLeft (Css.px 16)
|
||||
, Css.paddingRight (Css.px 16)
|
||||
, Css.minWidth (Css.px config.minWidth)
|
||||
]
|
||||
|
||||
lineHeightPx =
|
||||
case size of
|
||||
Small ->
|
||||
15
|
||||
|
||||
Medium ->
|
||||
19
|
||||
|
||||
Large ->
|
||||
22
|
||||
in
|
||||
Css.batch
|
||||
[ Css.fontSize (Css.px config.fontSize)
|
||||
, Css.borderRadius (Css.px 8)
|
||||
, Css.lineHeight (Css.px lineHeightPx)
|
||||
, Css.boxSizing Css.borderBox
|
||||
, Css.borderWidth (Css.px 1)
|
||||
, Css.borderBottomWidth (Css.px config.shadowHeight)
|
||||
, Css.batch sizingAttributes
|
||||
, Css.batch widthAttributes
|
||||
, Css.Global.descendants
|
||||
[ Css.Global.img
|
||||
[ Css.height (Css.px config.imageHeight)
|
||||
, Css.marginRight (Css.px <| config.imageHeight / 6)
|
||||
, Css.position Css.relative
|
||||
, Css.bottom (Css.px 2)
|
||||
, Css.verticalAlign Css.middle
|
||||
]
|
||||
, Css.Global.svg
|
||||
[ Css.height (Css.px config.imageHeight) |> Css.important
|
||||
, Css.width (Css.px config.imageHeight) |> Css.important
|
||||
, Css.marginRight (Css.px <| config.imageHeight / 6)
|
||||
, Css.position Css.relative
|
||||
, Css.bottom (Css.px 2)
|
||||
, Css.verticalAlign Css.middle
|
||||
]
|
||||
, Css.Global.svg
|
||||
[ Css.important <| Css.height (Css.px config.imageHeight)
|
||||
, Css.important <| Css.width Css.auto
|
||||
, Css.maxWidth (Css.px (config.imageHeight * 1.25))
|
||||
, Css.paddingRight (Css.px <| config.imageHeight / 6)
|
||||
, Css.position Css.relative
|
||||
, Css.bottom (Css.px 2)
|
||||
, Css.verticalAlign Css.middle
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
styledName : String -> String
|
||||
styledName suffix =
|
||||
"Nri-Ui-Button-V5-" ++ suffix
|
@ -85,7 +85,7 @@ spinningDots =
|
||||
]
|
||||
[ Svg.circle [ SvgAttributes.fill "#004e95", SvgAttributes.cx "6.13", SvgAttributes.cy "0.98", SvgAttributes.r "0.98" ] []
|
||||
, Svg.circle [ SvgAttributes.fill "#004cc9", SvgAttributes.cx "9.95", SvgAttributes.cy "2.47", SvgAttributes.r "0.98", SvgAttributes.transform "translate(1.12 7.67) rotate(-44.43)" ] []
|
||||
, Svg.circle [ SvgAttributes.fill "#146aff", SvgAttributes.x "11.56", SvgAttributes.cy "6.24", SvgAttributes.r "0.98", SvgAttributes.transform "translate(5.09 17.67) rotate(-88.86)" ] []
|
||||
, Svg.circle [ SvgAttributes.fill "#146aff", SvgAttributes.cx "11.56", SvgAttributes.cy "6.24", SvgAttributes.r "0.98", SvgAttributes.transform "translate(5.09 17.67) rotate(-88.86)" ] []
|
||||
, Svg.circle [ SvgAttributes.fill "#0af", SvgAttributes.cx "10", SvgAttributes.cy "10.02", SvgAttributes.r "0.98", SvgAttributes.transform "translate(-4.15 9.58) rotate(-43.29)" ] []
|
||||
, Svg.circle [ SvgAttributes.fill "#d4f0ff", SvgAttributes.cx "6.2", SvgAttributes.cy "11.56", SvgAttributes.r "0.98", SvgAttributes.transform "translate(-5.6 17.29) rotate(-87.71)" ] []
|
||||
, Svg.circle [ SvgAttributes.fill "#eef9ff", SvgAttributes.cx "2.44", SvgAttributes.cy "9.92", SvgAttributes.r "0.98", SvgAttributes.transform "translate(-6.03 4.21) rotate(-42.14)" ] []
|
||||
|
@ -1,213 +0,0 @@
|
||||
module Nri.Ui.Modal.V2 exposing
|
||||
( Model
|
||||
, info
|
||||
, warning
|
||||
)
|
||||
|
||||
{-| Changes from V1:
|
||||
|
||||
- Use Styled Html
|
||||
|
||||
@docs Model
|
||||
@docs info
|
||||
@docs warning
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled as Html exposing (..)
|
||||
import Accessibility.Styled.Role as Role
|
||||
import Accessibility.Styled.Widget as Widget
|
||||
import Css
|
||||
import Css.Global exposing (Snippet, body, children, descendants, everything, selector)
|
||||
import Html.Styled
|
||||
import Html.Styled.Events exposing (onClick)
|
||||
import Nri.Ui
|
||||
import Nri.Ui.Colors.Extra
|
||||
import Nri.Ui.Colors.V1
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
- `onDismiss`: If `Nothing`, the modal will not be dismissable
|
||||
- `visibleTitle`: If `False`, the title will still be used for screen readers
|
||||
- `content`: This will be placed in a `width:100%` div in the main area of the modal
|
||||
- `footerContent`: The optional items here will be stacked below the main content area and center-aligned.
|
||||
Commonly you will either give a list of Nri.Ui.Buttons,
|
||||
or an empty list.
|
||||
|
||||
-}
|
||||
type alias Model msg =
|
||||
{ title : String
|
||||
, visibleTitle : Bool
|
||||
, content : Html msg
|
||||
, footerContent : List (Html msg)
|
||||
, onDismiss : Maybe msg
|
||||
, width : Maybe Int
|
||||
}
|
||||
|
||||
|
||||
type ModalType
|
||||
= Info
|
||||
| Warning
|
||||
|
||||
|
||||
{-| -}
|
||||
info : Model msg -> Html msg
|
||||
info =
|
||||
view Info
|
||||
|
||||
|
||||
{-| -}
|
||||
warning : Model msg -> Html msg
|
||||
warning =
|
||||
view Warning
|
||||
|
||||
|
||||
view : ModalType -> Model msg -> Html msg
|
||||
view modalType { title, visibleTitle, content, onDismiss, footerContent, width } =
|
||||
Nri.Ui.styled div
|
||||
"modal-backdrop-container"
|
||||
((case modalType of
|
||||
Info ->
|
||||
Css.backgroundColor (Nri.Ui.Colors.Extra.withAlpha 0.9 Nri.Ui.Colors.V1.navy)
|
||||
|
||||
Warning ->
|
||||
Css.backgroundColor (Nri.Ui.Colors.Extra.withAlpha 0.9 Nri.Ui.Colors.V1.gray20)
|
||||
)
|
||||
:: [ Css.height (Css.vh 100)
|
||||
, Css.left Css.zero
|
||||
, Css.overflow Css.hidden
|
||||
, Css.position Css.fixed
|
||||
, Css.top Css.zero
|
||||
, Css.width (Css.pct 100)
|
||||
, Css.zIndex (Css.int 200)
|
||||
, Css.displayFlex
|
||||
, Css.alignItems Css.center
|
||||
, Css.justifyContent Css.center
|
||||
]
|
||||
)
|
||||
[ Role.dialog
|
||||
, Widget.label title
|
||||
, Widget.modal True
|
||||
]
|
||||
[ Nri.Ui.styled Html.Styled.div
|
||||
"modal-click-catcher"
|
||||
[ Css.bottom Css.zero
|
||||
, Css.left Css.zero
|
||||
, Css.position Css.absolute
|
||||
, Css.right Css.zero
|
||||
, Css.top Css.zero
|
||||
]
|
||||
(case onDismiss of
|
||||
Nothing ->
|
||||
[]
|
||||
|
||||
Just msg ->
|
||||
[ onClick msg ]
|
||||
)
|
||||
[]
|
||||
, Nri.Ui.styled div
|
||||
"modal-container"
|
||||
[ Css.width (Css.px 600)
|
||||
, Css.maxHeight <| Css.calc (Css.vh 100) Css.minus (Css.px 100)
|
||||
, Css.padding4 (Css.px 35) Css.zero (Css.px 25) Css.zero
|
||||
, Css.margin2 (Css.px 75) Css.auto
|
||||
, Css.backgroundColor Nri.Ui.Colors.V1.white
|
||||
, Css.borderRadius (Css.px 20)
|
||||
, Css.property "box-shadow" "0 1px 10px 0 rgba(0, 0, 0, 0.35)"
|
||||
, Css.position Css.relative -- required for closeButtonContainer
|
||||
, Css.displayFlex
|
||||
, Css.alignItems Css.center
|
||||
, Css.flexDirection Css.column
|
||||
, Css.flexWrap Css.noWrap
|
||||
, Fonts.baseFont
|
||||
]
|
||||
[]
|
||||
[ -- This global <style> node sets overflow to hidden on the body element,
|
||||
-- thereby preventing the page from scrolling behind the backdrop when the modal is
|
||||
-- open (and this node is present on the page).
|
||||
Css.Global.global
|
||||
[ Css.Global.body
|
||||
[ Css.overflow Css.hidden ]
|
||||
]
|
||||
, if visibleTitle then
|
||||
viewHeader modalType title
|
||||
|
||||
else
|
||||
text ""
|
||||
, viewContent modalType content
|
||||
, viewFooter footerContent
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewHeader : ModalType -> String -> Html msg
|
||||
viewHeader modalType title =
|
||||
Nri.Ui.styled Html.h3
|
||||
"modal-header"
|
||||
((case modalType of
|
||||
Info ->
|
||||
Css.color Nri.Ui.Colors.V1.navy
|
||||
|
||||
Warning ->
|
||||
Css.color Nri.Ui.Colors.V1.red
|
||||
)
|
||||
:: [ Css.fontWeight (Css.int 700)
|
||||
, Css.lineHeight (Css.px 27)
|
||||
, Css.margin2 Css.zero (Css.px 65)
|
||||
, Css.fontSize (Css.px 20)
|
||||
, Fonts.baseFont
|
||||
]
|
||||
)
|
||||
[]
|
||||
[ Html.text title
|
||||
]
|
||||
|
||||
|
||||
viewContent : ModalType -> Html msg -> Html msg
|
||||
viewContent modalType content =
|
||||
Nri.Ui.styled div
|
||||
"modal-content"
|
||||
[ Css.overflowY Css.scroll
|
||||
, Css.padding2 (Css.px 30) (Css.px 45)
|
||||
, Css.width (Css.pct 100)
|
||||
, Css.minHeight (Css.px 150)
|
||||
, Css.boxSizing Css.borderBox
|
||||
]
|
||||
[]
|
||||
[ content ]
|
||||
|
||||
|
||||
viewFooter : List (Html msg) -> Html msg
|
||||
viewFooter footerContent =
|
||||
case footerContent of
|
||||
[] ->
|
||||
Html.text ""
|
||||
|
||||
_ ->
|
||||
Nri.Ui.styled div
|
||||
"modal-footer"
|
||||
[ Css.alignItems Css.center
|
||||
, Css.displayFlex
|
||||
, Css.flexDirection Css.column
|
||||
, Css.flexGrow (Css.int 2)
|
||||
, Css.flexWrap Css.noWrap
|
||||
, Css.margin4 (Css.px 20) Css.zero Css.zero Css.zero
|
||||
, Css.width (Css.pct 100)
|
||||
]
|
||||
[]
|
||||
(List.map
|
||||
(\x ->
|
||||
Nri.Ui.styled div
|
||||
"modal-footer-item"
|
||||
[ Css.margin4 (Css.px 10) Css.zero Css.zero Css.zero
|
||||
, Css.firstChild
|
||||
[ Css.margin Css.zero
|
||||
]
|
||||
]
|
||||
[]
|
||||
[ x ]
|
||||
)
|
||||
footerContent
|
||||
)
|
@ -1,412 +0,0 @@
|
||||
module Nri.Ui.Modal.V8 exposing
|
||||
( Model, init
|
||||
, Msg, update, subscriptions
|
||||
, open, close
|
||||
, info, warning
|
||||
, ViewFuncs
|
||||
, Focusable
|
||||
, multipleFocusableElementView, onlyFocusableElementView
|
||||
)
|
||||
|
||||
{-| Changes from V7:
|
||||
|
||||
- More customizable attributes
|
||||
- Rather than accepting any number of attributes, Modal provides one callback that returns a focusable
|
||||
- viewFooter has been merged into viewContent
|
||||
- viewContent and closeButton are now callbacks that are pre-configured with settings
|
||||
(previously you passed config through)
|
||||
|
||||
|
||||
## State and updates
|
||||
|
||||
@docs Model, init
|
||||
@docs Msg, update, subscriptions
|
||||
|
||||
@docs open, close
|
||||
|
||||
|
||||
## Views
|
||||
|
||||
|
||||
### Modals
|
||||
|
||||
@docs info, warning
|
||||
@docs ViewFuncs
|
||||
|
||||
|
||||
### Focusable
|
||||
|
||||
@docs Focusable
|
||||
@docs multipleFocusableElementView, onlyFocusableElementView
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Modal.Copy as Modal
|
||||
import Accessibility.Styled as Html exposing (..)
|
||||
import Accessibility.Styled.Widget as Widget
|
||||
import Color.Transparent as Transparent
|
||||
import Css
|
||||
import Css.Transitions
|
||||
import Html.Styled.Attributes as Attributes exposing (css)
|
||||
import Html.Styled.Events exposing (onClick)
|
||||
import Nri.Ui.Colors.Extra
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.SpriteSheet
|
||||
import Nri.Ui.Svg.V1
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Model =
|
||||
Modal.Model
|
||||
|
||||
|
||||
type alias Config msg =
|
||||
{ visibleTitle : Bool
|
||||
, title : String
|
||||
, wrapMsg : Msg -> msg
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
init : Model
|
||||
init =
|
||||
Modal.init
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Msg =
|
||||
Modal.Msg
|
||||
|
||||
|
||||
{-| Include the subscription if you want the modal to dismiss on `Esc`.
|
||||
-}
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions =
|
||||
Modal.subscriptions
|
||||
|
||||
|
||||
{-| -}
|
||||
update : { dismissOnEscAndOverlayClick : Bool } -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update config msg model =
|
||||
Modal.update config msg model
|
||||
|
||||
|
||||
{-| -}
|
||||
close : Msg
|
||||
close =
|
||||
Modal.close
|
||||
|
||||
|
||||
{-| Pass the id of the element that focus should return to when the modal closes.
|
||||
-}
|
||||
open : String -> Msg
|
||||
open =
|
||||
Modal.open
|
||||
|
||||
|
||||
{-| -}
|
||||
info :
|
||||
Config msg
|
||||
-> (ViewFuncs msg -> Focusable msg)
|
||||
-> Model
|
||||
-> Html msg
|
||||
info config getFocusable model =
|
||||
view Info config getFocusable model
|
||||
|
||||
|
||||
{-| -}
|
||||
warning :
|
||||
Config msg
|
||||
-> (ViewFuncs msg -> Focusable msg)
|
||||
-> Model
|
||||
-> Html msg
|
||||
warning config getFocusable model =
|
||||
view Warning config getFocusable model
|
||||
|
||||
|
||||
type Theme
|
||||
= Info
|
||||
| Warning
|
||||
|
||||
|
||||
themeToOverlayColor : Theme -> Css.Color
|
||||
themeToOverlayColor theme =
|
||||
case theme of
|
||||
Info ->
|
||||
Colors.navy
|
||||
|
||||
Warning ->
|
||||
Colors.gray20
|
||||
|
||||
|
||||
themeToTitleColor : Theme -> Css.Color
|
||||
themeToTitleColor theme =
|
||||
case theme of
|
||||
Info ->
|
||||
Colors.navy
|
||||
|
||||
Warning ->
|
||||
Colors.red
|
||||
|
||||
|
||||
{-| -}
|
||||
type Focusable msg
|
||||
= Focusable (Modal.Attribute msg) (List (Modal.Attribute msg))
|
||||
|
||||
|
||||
{-| -}
|
||||
multipleFocusableElementView :
|
||||
({ firstFocusableElement : List (Html.Attribute msg)
|
||||
, lastFocusableElement : List (Html.Attribute msg)
|
||||
, autofocusElement : Html.Attribute msg
|
||||
}
|
||||
-> Html msg
|
||||
)
|
||||
-> Focusable msg
|
||||
multipleFocusableElementView f =
|
||||
Focusable (Modal.multipleFocusableElementView (\attributes -> f attributes)) []
|
||||
|
||||
|
||||
{-| -}
|
||||
onlyFocusableElementView : (List (Html.Attribute msg) -> Html msg) -> Focusable msg
|
||||
onlyFocusableElementView f =
|
||||
Focusable (Modal.onlyFocusableElementView (\attributes -> f attributes)) [ Modal.autofocusOnLastElement ]
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias ViewFuncs msg =
|
||||
{ viewContent : { content : List (Html msg), footer : List (Html msg) } -> Html msg
|
||||
, closeButton : List (Html.Attribute msg) -> Html msg
|
||||
}
|
||||
|
||||
|
||||
view :
|
||||
Theme
|
||||
-> Config msg
|
||||
-> (ViewFuncs msg -> Focusable msg)
|
||||
-> Model
|
||||
-> Html msg
|
||||
view theme config getFocusable model =
|
||||
let
|
||||
viewFuncs : ViewFuncs msg
|
||||
viewFuncs =
|
||||
{ viewContent = viewContent config.visibleTitle
|
||||
, closeButton = closeButton config.wrapMsg
|
||||
}
|
||||
|
||||
focusables =
|
||||
case getFocusable viewFuncs of
|
||||
Focusable fst rst ->
|
||||
fst :: rst
|
||||
in
|
||||
Modal.view
|
||||
config.wrapMsg
|
||||
config.title
|
||||
([ Modal.overlayColor (Nri.Ui.Colors.Extra.withAlpha 0.9 (themeToOverlayColor theme))
|
||||
, Modal.custom
|
||||
[ Css.width (Css.px 600)
|
||||
, Css.margin2 (Css.px 50) Css.auto
|
||||
, Css.borderRadius (Css.px 20)
|
||||
, Css.boxShadow5 Css.zero (Css.px 1) (Css.px 10) Css.zero (Css.rgba 0 0 0 0.35)
|
||||
, Css.backgroundColor Colors.white
|
||||
|
||||
-- the modal should grow up to the viewport minus a 50px margin
|
||||
, Css.maxHeight (Css.calc (Css.pct 100) Css.minus (Css.px 100))
|
||||
]
|
||||
, if config.visibleTitle then
|
||||
Modal.titleStyles
|
||||
[ Fonts.baseFont
|
||||
, Css.fontWeight (Css.int 700)
|
||||
, Css.paddingTop (Css.px 40)
|
||||
, Css.paddingBottom (Css.px 20)
|
||||
, Css.margin Css.zero
|
||||
, Css.fontSize (Css.px 20)
|
||||
, Css.textAlign Css.center
|
||||
, Css.color (themeToTitleColor theme)
|
||||
]
|
||||
|
||||
else
|
||||
Modal.titleStyles
|
||||
[ -- https://snook.ca/archives/html_and_css/hiding-content-for-accessibility
|
||||
Css.property "clip" "rect(1px, 1px, 1px, 1px)"
|
||||
, Css.position Css.absolute
|
||||
, Css.height (Css.px 1)
|
||||
, Css.width (Css.px 1)
|
||||
, Css.overflow Css.hidden
|
||||
, Css.margin (Css.px -1)
|
||||
, Css.padding Css.zero
|
||||
, Css.border Css.zero
|
||||
]
|
||||
]
|
||||
++ focusables
|
||||
)
|
||||
model
|
||||
|> List.singleton
|
||||
|> div [ css [ Css.position Css.relative, Css.zIndex (Css.int 1) ] ]
|
||||
|
||||
|
||||
{-| -}
|
||||
viewContent : Bool -> { content : List (Html msg), footer : List (Html msg) } -> Html msg
|
||||
viewContent visibleTitle { content, footer } =
|
||||
div []
|
||||
[ viewInnerContent content visibleTitle (not (List.isEmpty footer))
|
||||
, viewFooter footer
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
viewInnerContent : List (Html msg) -> Bool -> Bool -> Html msg
|
||||
viewInnerContent children visibleTitle visibleFooter =
|
||||
let
|
||||
titleHeight =
|
||||
if visibleTitle then
|
||||
45
|
||||
|
||||
else
|
||||
0
|
||||
|
||||
footerHeight =
|
||||
if visibleFooter then
|
||||
180
|
||||
|
||||
else
|
||||
0
|
||||
|
||||
modalTitleStyles =
|
||||
if visibleTitle then
|
||||
[]
|
||||
|
||||
else
|
||||
[ Css.borderTopLeftRadius (Css.px 20)
|
||||
, Css.borderTopRightRadius (Css.px 20)
|
||||
, Css.overflowY Css.hidden
|
||||
]
|
||||
|
||||
modalFooterStyles =
|
||||
if visibleFooter then
|
||||
[]
|
||||
|
||||
else
|
||||
[ Css.borderBottomLeftRadius (Css.px 20)
|
||||
, Css.borderBottomRightRadius (Css.px 20)
|
||||
, Css.overflowY Css.hidden
|
||||
]
|
||||
in
|
||||
div
|
||||
[ css (modalTitleStyles ++ modalFooterStyles)
|
||||
]
|
||||
[ div
|
||||
[ css
|
||||
[ Css.overflowY Css.auto
|
||||
, Css.overflowX Css.hidden
|
||||
, Css.minHeight (Css.px 150)
|
||||
, Css.maxHeight
|
||||
(Css.calc (Css.vh 100)
|
||||
Css.minus
|
||||
(Css.px (footerHeight + titleHeight + 145))
|
||||
)
|
||||
, Css.width (Css.pct 100)
|
||||
, Css.boxSizing Css.borderBox
|
||||
, Css.paddingLeft (Css.px 40)
|
||||
, Css.paddingRight (Css.px 40)
|
||||
, if visibleTitle then
|
||||
Css.paddingTop Css.zero
|
||||
|
||||
else
|
||||
Css.paddingTop (Css.px 40)
|
||||
, if visibleFooter then
|
||||
Css.paddingBottom Css.zero
|
||||
|
||||
else
|
||||
Css.paddingBottom (Css.px 40)
|
||||
, if visibleFooter then
|
||||
shadow (Transparent.customOpacity 0.15) (Css.px 16)
|
||||
|
||||
else
|
||||
shadow (Transparent.customOpacity 0.4) (Css.px 30)
|
||||
]
|
||||
]
|
||||
children
|
||||
]
|
||||
|
||||
|
||||
shadow : Transparent.Opacity -> Css.Px -> Css.Style
|
||||
shadow opacity bottomShadowHeight =
|
||||
let
|
||||
to =
|
||||
Transparent.fromRGBA { red = 0, green = 0, blue = 0, alpha = opacity }
|
||||
|> Transparent.toRGBAString
|
||||
in
|
||||
Css.batch
|
||||
[ -- Shadows for indicating that the content is scrollable
|
||||
[ "/* TOP shadow */"
|
||||
, "top linear-gradient(to top, rgb(255, 255, 255), rgb(255, 255, 255)) local,"
|
||||
, "top linear-gradient(to top, rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.15)) scroll,"
|
||||
, ""
|
||||
, "/* BOTTOM shadow */"
|
||||
, "bottom linear-gradient(to bottom, rgb(255, 255, 255), rgb(255, 255, 255)) local,"
|
||||
, "bottom linear-gradient(to bottom, rgba(255, 255, 255, 0), " ++ to ++ ") scroll"
|
||||
]
|
||||
|> String.join "\n"
|
||||
|> Css.property "background"
|
||||
, Css.backgroundSize2 (Css.pct 100) bottomShadowHeight
|
||||
, Css.backgroundRepeat Css.noRepeat
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
viewFooter : List (Html msg) -> Html msg
|
||||
viewFooter children =
|
||||
if List.isEmpty children then
|
||||
Html.text ""
|
||||
|
||||
else
|
||||
div
|
||||
[ css
|
||||
[ 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.paddingBottom (Css.px 40)
|
||||
, Css.width (Css.pct 100)
|
||||
]
|
||||
]
|
||||
children
|
||||
|
||||
|
||||
|
||||
--BUTTONS
|
||||
|
||||
|
||||
{-| -}
|
||||
closeButton : (Msg -> msg) -> List (Html.Attribute msg) -> Html msg
|
||||
closeButton wrapMsg focusableElementAttrs =
|
||||
button
|
||||
(Widget.label "Close modal"
|
||||
:: Attributes.map wrapMsg (onClick Modal.close)
|
||||
:: css
|
||||
[ -- in the upper-right corner of the modal
|
||||
Css.position Css.absolute
|
||||
, Css.top Css.zero
|
||||
, Css.right Css.zero
|
||||
|
||||
-- make the hitspace extend all the way to the corner
|
||||
, Css.width (Css.px 40)
|
||||
, Css.height (Css.px 40)
|
||||
, Css.padding4 (Css.px 20) (Css.px 20) Css.zero Css.zero
|
||||
|
||||
-- apply button styles
|
||||
, Css.borderWidth Css.zero
|
||||
, Css.backgroundColor Css.transparent
|
||||
, Css.cursor Css.pointer
|
||||
, Css.color Colors.azure
|
||||
, Css.hover [ Css.color Colors.azureDark ]
|
||||
, Css.Transitions.transition [ Css.Transitions.color 0.1 ]
|
||||
]
|
||||
:: focusableElementAttrs
|
||||
)
|
||||
[ Nri.Ui.Svg.V1.toHtml Nri.Ui.SpriteSheet.xSvg
|
||||
]
|
@ -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
|
||||
]
|
@ -7,7 +7,6 @@
|
||||
"Nri.Ui.AssignmentIcon.V2",
|
||||
"Nri.Ui.Button.V10",
|
||||
"Nri.Ui.Button.V8",
|
||||
"Nri.Ui.Button.V5",
|
||||
"Nri.Ui.Callout.V1",
|
||||
"Nri.Ui.Checkbox.V5",
|
||||
"Nri.Ui.ClickableSvg.V1",
|
||||
@ -33,16 +32,13 @@
|
||||
"Nri.Ui.MasteryIcon.V1",
|
||||
"Nri.Ui.Menu.V1",
|
||||
"Nri.Ui.Message.V1",
|
||||
"Nri.Ui.Modal.V2",
|
||||
"Nri.Ui.Modal.V3",
|
||||
"Nri.Ui.Modal.V8",
|
||||
"Nri.Ui.Modal.V9",
|
||||
"Nri.Ui.Page.V3",
|
||||
"Nri.Ui.Palette.V1",
|
||||
"Nri.Ui.Pennant.V2",
|
||||
"Nri.Ui.PremiumCheckbox.V6",
|
||||
"Nri.Ui.RadioButton.V1",
|
||||
"Nri.Ui.SegmentedControl.V6",
|
||||
"Nri.Ui.SegmentedControl.V9",
|
||||
"Nri.Ui.Select.V5",
|
||||
"Nri.Ui.Select.V7",
|
||||
|
Loading…
Reference in New Issue
Block a user