Merge pull request #659 from NoRedInk/switch-v1

add Nri.Ui.Switch.V1
This commit is contained in:
Brian Hicks 2020-12-11 11:49:48 -06:00 committed by GitHub
commit f0050e5433
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 429 additions and 0 deletions

View File

@ -59,6 +59,7 @@
"Nri.Ui.SlideModal.V2",
"Nri.Ui.SortableTable.V2",
"Nri.Ui.Svg.V1",
"Nri.Ui.Switch.V1",
"Nri.Ui.Table.V4",
"Nri.Ui.Table.V5",
"Nri.Ui.Tabs.V6",

332
src/Nri/Ui/Switch/V1.elm Normal file
View File

@ -0,0 +1,332 @@
module Nri.Ui.Switch.V1 exposing (view, Attribute, onSwitch, disabled, id, label)
{-|
@docs view, Attribute, onSwitch, disabled, id, label
-}
import Accessibility.Styled as Html exposing (Html)
import Accessibility.Styled.Aria as Aria
import Accessibility.Styled.Widget as Widget
import Css
import Css.Global as Global
import Css.Media
import Html.Styled as WildWildHtml
import Html.Styled.Attributes as Attributes
import Html.Styled.Events as Events
import Nri.Ui.Colors.V1 as Colors
import Nri.Ui.Svg.V1 exposing (Svg)
import Svg.Styled as Svg
import Svg.Styled.Attributes as SvgAttributes
{-| -}
type Attribute msg
= OnSwitch (Bool -> msg)
| Id String
| Label (Html msg)
| Disabled
{-| Specify what happens when the switch is toggled.
-}
onSwitch : (Bool -> msg) -> Attribute msg
onSwitch =
OnSwitch
{-| Explicitly specify that you want this switch to be disabled. If you don't
specify `onSwitch`, this is the default, but it's provided so you don't have
to resort to `filterMap` or similar to build a clean list of attributes.
-}
disabled : Attribute msg
disabled =
Disabled
{-| Set the HTML ID of the switch toggle. If you have only one on the page,
you don't need to set this, but you should definitely set it if you have
more than one.
-}
id : String -> Attribute msg
id =
Id
{-| Add labeling text to the switch. This text should be descriptive and
able to be displayed inline. It should _not_ be interactive (if it were
ergonomic to make this argument `Html Never`, we would!)
-}
label : Html msg -> Attribute msg
label =
Label
type alias Config msg =
{ onSwitch : Maybe (Bool -> msg)
, id : String
, label : Maybe (Html msg)
}
defaultConfig : Config msg
defaultConfig =
{ onSwitch = Nothing
, id = "nri-ui-switch-with-default-id"
, label = Nothing
}
customize : Attribute msg -> Config msg -> Config msg
customize attr config =
case attr of
OnSwitch onSwitch_ ->
{ config | onSwitch = Just onSwitch_ }
Disabled ->
{ config | onSwitch = Nothing }
Id id_ ->
{ config | id = id_ }
Label label_ ->
{ config | label = Just label_ }
{-| Render a switch. The boolean here indicates whether the switch is on
or not.
-}
view : List (Attribute msg) -> Bool -> Html msg
view attrs isOn =
let
config =
List.foldl customize defaultConfig attrs
in
WildWildHtml.label
[ Attributes.id (config.id ++ "-container")
, Attributes.css
[ Css.display Css.inlineFlex
, Css.alignItems Css.center
, Css.position Css.relative
, Css.pseudoClass "focus-within"
[ Global.descendants
[ Global.class "switch-slider"
[ -- azure, but can't use the Color type here
Css.property "stroke" "#146AFF"
, Css.property "stroke-width" "3px"
]
]
]
, Css.cursor
(if config.onSwitch /= Nothing then
Css.pointer
else
Css.notAllowed
)
]
, Aria.controls config.id
, Widget.checked (Just isOn)
]
[ viewCheckbox
{ id = config.id
, onCheck = config.onSwitch
, checked = isOn
}
, Nri.Ui.Svg.V1.toHtml
(viewSwitch
{ id = config.id
, isOn = isOn
, enabled = config.onSwitch /= Nothing
}
)
, case config.label of
Just label_ ->
Html.span
[ Attributes.css
[ Css.fontWeight (Css.int 600)
, Css.color Colors.navy
, Css.paddingLeft (Css.px 5)
]
]
[ label_ ]
Nothing ->
Html.text ""
]
viewCheckbox :
{ id : String
, onCheck : Maybe (Bool -> msg)
, checked : Bool
}
-> Html msg
viewCheckbox config =
Html.checkbox config.id
(Just config.checked)
[ Attributes.id config.id
, Attributes.css
[ Css.position Css.absolute
, Css.top (Css.px 10)
, Css.left (Css.px 10)
, Css.zIndex (Css.int 0)
, Css.opacity (Css.num 0)
]
, case config.onCheck of
Just onCheck ->
Events.onCheck onCheck
Nothing ->
Widget.disabled True
]
viewSwitch :
{ id : String
, isOn : Bool
, enabled : Bool
}
-> Svg
viewSwitch config =
let
shadowFilterId =
config.id ++ "-shadow-filter"
shadowBoxId =
config.id ++ "-shadow-box"
in
Svg.svg
[ SvgAttributes.width "43"
, SvgAttributes.height "32"
, SvgAttributes.viewBox "0 0 43 32"
, SvgAttributes.css
[ Css.zIndex (Css.int 1)
, if config.enabled then
Css.opacity (Css.num 1)
else
Css.opacity (Css.num 0.4)
]
]
[ Svg.defs []
[ Svg.filter
[ SvgAttributes.id shadowFilterId
, SvgAttributes.width "105%"
, SvgAttributes.height "106.7%"
, SvgAttributes.x "-2.5%"
, SvgAttributes.y "-3.3%"
, SvgAttributes.filterUnits "objectBoundingBox"
]
[ Svg.feOffset
[ SvgAttributes.dy "2"
, SvgAttributes.in_ "SourceAlpha"
, SvgAttributes.result "shadowOffsetInner1"
]
[]
, Svg.feComposite
[ SvgAttributes.in_ "shadowOffsetInner1"
, SvgAttributes.in2 "SourceAlpha"
, SvgAttributes.k2 "-1"
, SvgAttributes.k3 "1"
, SvgAttributes.operator "arithmetic"
, SvgAttributes.result "shadowInnerInner1"
]
[]
, Svg.feColorMatrix
[ SvgAttributes.in_ "shadowInnerInner1"
, SvgAttributes.values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"
]
[]
]
, Svg.rect
[ SvgAttributes.id shadowBoxId
, SvgAttributes.width "40"
, SvgAttributes.height "30"
, SvgAttributes.x "0"
, SvgAttributes.y "0"
, SvgAttributes.rx "15"
]
[]
]
, Svg.g
[ SvgAttributes.fill "none"
, SvgAttributes.fillRule "even-odd"
, SvgAttributes.transform "translate(1, 1)"
]
[ Svg.g []
[ Svg.use
[ SvgAttributes.xlinkHref ("#" ++ shadowBoxId)
, SvgAttributes.css
[ if config.isOn then
Css.fill Colors.glacier
else
Css.fill Colors.gray92
, transition "fill 0.2s"
]
]
[]
, Svg.use
[ SvgAttributes.xlinkHref ("#" ++ shadowBoxId)
, SvgAttributes.fill "#000"
, SvgAttributes.filter ("url(#" ++ shadowFilterId ++ ")")
]
[]
]
, Svg.g
[ SvgAttributes.css
[ if config.isOn then
Css.transform (Css.translateX (Css.px 11))
else
Css.transform (Css.translateX (Css.px 0))
, transition "transform 0.2s ease-in-out"
]
]
[ Svg.circle
[ SvgAttributes.cx "15"
, SvgAttributes.cy "15"
, SvgAttributes.r "14.5"
, SvgAttributes.fill "#FFF"
, SvgAttributes.css
[ if config.isOn then
-- azure, but can't use the Color type here
Css.property "stroke" "#146AFF"
else
-- gray75, but can't use the Color type here
Css.property "stroke" "#BFBFBF"
, transition "stroke 0.1s"
]
, SvgAttributes.class "switch-slider"
]
[]
, Svg.path
[ SvgAttributes.strokeLinecap "round"
, SvgAttributes.strokeLinejoin "round"
, SvgAttributes.strokeWidth "3"
, SvgAttributes.d "M8 15.865L12.323 20 21.554 10"
, SvgAttributes.css
[ if config.isOn then
-- azure, but can't use the Color type here
Css.property "stroke" "#146AFF"
else
Css.property "stroke" "rgba(255,255,255,0)"
, transition "stroke 0.2s"
]
]
[]
]
]
]
|> Nri.Ui.Svg.V1.fromHtml
transition : String -> Css.Style
transition transitionRules =
Css.Media.withMediaQuery
[ "(prefers-reduced-motion: no-preference)" ]
[ Css.property "transition" transitionRules ]

View File

@ -30,6 +30,7 @@ import Examples.Slide as Slide
import Examples.SlideModal as SlideModal
import Examples.SortableTable as SortableTable
import Examples.Svg as Svg
import Examples.Switch as Switch
import Examples.Table as Table
import Examples.Tabs as Tabs
import Examples.Text as Text
@ -590,6 +591,25 @@ all =
SvgState childState ->
Just childState
_ ->
Nothing
)
, Switch.example
|> Example.wrapMsg SwitchMsg
(\msg ->
case msg of
SwitchMsg childMsg ->
Just childMsg
_ ->
Nothing
)
|> Example.wrapState SwitchState
(\msg ->
case msg of
SwitchState childState ->
Just childState
_ ->
Nothing
)
@ -778,6 +798,7 @@ type State
| SlideModalState SlideModal.State
| SortableTableState SortableTable.State
| SvgState Svg.State
| SwitchState Switch.State
| TableState Table.State
| TabsState Tabs.State
| TextState Text.State
@ -818,6 +839,7 @@ type Msg
| SlideModalMsg SlideModal.Msg
| SortableTableMsg SortableTable.Msg
| SvgMsg Svg.Msg
| SwitchMsg Switch.Msg
| TableMsg Table.Msg
| TabsMsg Tabs.Msg
| TextMsg Text.Msg

View File

@ -0,0 +1,73 @@
module Examples.Switch exposing (Msg, State, example)
{-|
@docs Msg, State, example
-}
import AtomicDesignType
import Category
import Example exposing (Example)
import Html.Styled as Html
import Nri.Ui.Heading.V2 as Heading
import Nri.Ui.Switch.V1 as Switch
import Nri.Ui.Text.V5 as Text
{-| -}
type alias State =
Bool
{-| -}
type Msg
= Switch Bool
example : Example State Msg
example =
{ name = "Switch"
, version = 1
, state = True
, update = \(Switch new) _ -> ( new, Cmd.none )
, subscriptions = \_ -> Sub.none
, view =
\interactiveIsOn ->
[ Heading.h3 [] [ Html.text "Interactive" ]
, Text.mediumBody []
[ Switch.view
[ Switch.onSwitch Switch
, Switch.id "switch-interactive"
, Switch.label
(if interactiveIsOn then
Html.text "On"
else
Html.text "Off"
)
]
interactiveIsOn
]
, Heading.h3 [] [ Html.text "Disabled" ]
, Text.mediumBody []
[ Switch.view
[ Switch.disabled
, Switch.id "switch-disabled-on"
, Switch.label (Html.text "Permanently on")
]
True
]
, Text.mediumBody []
[ Switch.view
[ Switch.disabled
, Switch.id "switch-disabled-off"
, Switch.label (Html.text "Permanently off")
]
False
]
]
, categories = [ Category.Inputs ]
, atomicDesignType = AtomicDesignType.Atom
, keyboardSupport = [{- TODO -}]
}

View File

@ -55,6 +55,7 @@
"Nri.Ui.SlideModal.V2",
"Nri.Ui.SortableTable.V2",
"Nri.Ui.Svg.V1",
"Nri.Ui.Switch.V1",
"Nri.Ui.Table.V4",
"Nri.Ui.Table.V5",
"Nri.Ui.Tabs.V6",