mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-24 00:42:29 +03:00
Merge pull request #563 from NoRedInk/tessa/add-radio-button
Tessa/add radio button
This commit is contained in:
commit
2aa97e4f5d
1
elm.json
1
elm.json
@ -45,6 +45,7 @@
|
||||
"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",
|
||||
|
@ -12,7 +12,7 @@ jq -r -f script/axe-report.jq "$JSON_FILE"
|
||||
# Hey there! Did this script tell you to check out this file because the
|
||||
# expected error count went down? Well done! Just change this number to the new
|
||||
# value.
|
||||
TARGET_ERRORS=1
|
||||
TARGET_ERRORS=5
|
||||
|
||||
# ideally we'd fail on any failures, but we have had a bunch build up over time!
|
||||
# So right now, we need to fail if the error count is not exactly what we
|
||||
|
445
src/Nri/Ui/RadioButton/V1.elm
Normal file
445
src/Nri/Ui/RadioButton/V1.elm
Normal file
@ -0,0 +1,445 @@
|
||||
module Nri.Ui.RadioButton.V1 exposing (view, premium)
|
||||
|
||||
{-| Changes from monolith version:
|
||||
|
||||
- uses Nri.Ui.Data.PremiumLevel rather than monolith version
|
||||
- uses Nri.Ui.Html.* rather than deprecated monolith extras
|
||||
- removes Role.radio from the radio input's label
|
||||
|
||||
@docs view, premium
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled exposing (..)
|
||||
import Accessibility.Styled.Aria as Aria
|
||||
import Accessibility.Styled.Style as Style
|
||||
import Accessibility.Styled.Widget as Widget
|
||||
import Css exposing (..)
|
||||
import Html.Styled as Html
|
||||
import Html.Styled.Attributes exposing (..)
|
||||
import Html.Styled.Events exposing (onClick, stopPropagationOn)
|
||||
import Json.Decode
|
||||
import Nri.Ui.ClickableSvg.V1 as ClickableSvg
|
||||
import Nri.Ui.Colors.V1 as Colors
|
||||
import Nri.Ui.Data.PremiumLevel as PremiumLevel exposing (PremiumLevel)
|
||||
import Nri.Ui.Fonts.V1 as Fonts
|
||||
import Nri.Ui.Html.Attributes.V2 as Attributes
|
||||
import Nri.Ui.Html.V3 exposing (viewIf)
|
||||
import Nri.Ui.Pennant.V2 as Pennant
|
||||
import Nri.Ui.Svg.V1 exposing (Svg, fromHtml)
|
||||
import String exposing (toLower)
|
||||
import String.Extra exposing (dasherize)
|
||||
import Svg.Styled as Svg
|
||||
import Svg.Styled.Attributes as SvgAttributes
|
||||
|
||||
|
||||
{-| View a single radio button.
|
||||
If used in a group, all radio buttons in the group should have the same name attribute.
|
||||
-}
|
||||
view :
|
||||
{ label : String
|
||||
, value : a
|
||||
, name : String
|
||||
, selectedValue : Maybe a
|
||||
, onSelect : a -> msg
|
||||
, showLabel : Bool
|
||||
, noOpMsg : msg
|
||||
, valueToString : a -> String
|
||||
}
|
||||
-> Html msg
|
||||
view config =
|
||||
internalView
|
||||
{ label = config.label
|
||||
, value = config.value
|
||||
, name = config.name
|
||||
, selectedValue = config.selectedValue
|
||||
, showLabel = config.showLabel
|
||||
, isLocked = False
|
||||
, isDisabled = False
|
||||
, onSelect = config.onSelect
|
||||
, premiumMsg = config.noOpMsg
|
||||
, noOpMsg = config.noOpMsg
|
||||
, valueToString = config.valueToString
|
||||
, showPennant = False
|
||||
}
|
||||
|
||||
|
||||
{-| A radio button that should be used for premium content.
|
||||
|
||||
This radio button is locked when the premium level of the content
|
||||
is greater than the premium level of the teacher.
|
||||
|
||||
- `onChange`: A message for when the user selected the radio button
|
||||
- `onLockedClick`: A message for when the user clicks a radio button they don't have PremiumLevel for.
|
||||
If you get this message, you should show an `Nri.Premium.Model.view`
|
||||
|
||||
-}
|
||||
premium :
|
||||
{ label : String
|
||||
, value : a
|
||||
, name : String
|
||||
, selectedValue : Maybe a
|
||||
, teacherPremiumLevel : PremiumLevel
|
||||
, contentPremiumLevel : PremiumLevel
|
||||
, onSelect : a -> msg
|
||||
, premiumMsg : msg
|
||||
, noOpMsg : msg
|
||||
, valueToString : a -> String
|
||||
, showPennant : Bool
|
||||
, isDisabled : Bool
|
||||
}
|
||||
-> Html msg
|
||||
premium config =
|
||||
let
|
||||
isLocked =
|
||||
not <|
|
||||
PremiumLevel.allowedFor
|
||||
config.contentPremiumLevel
|
||||
config.teacherPremiumLevel
|
||||
in
|
||||
internalView
|
||||
{ label = config.label
|
||||
, value = config.value
|
||||
, name = config.name
|
||||
, selectedValue = config.selectedValue
|
||||
, isLocked = isLocked
|
||||
, isDisabled = config.isDisabled
|
||||
, onSelect = config.onSelect
|
||||
, showLabel = True
|
||||
, valueToString = config.valueToString
|
||||
, premiumMsg = config.premiumMsg
|
||||
, noOpMsg = config.noOpMsg
|
||||
, showPennant =
|
||||
case config.contentPremiumLevel of
|
||||
PremiumLevel.Premium ->
|
||||
config.showPennant
|
||||
|
||||
PremiumLevel.PremiumWithWriting ->
|
||||
config.showPennant
|
||||
|
||||
PremiumLevel.Free ->
|
||||
False
|
||||
}
|
||||
|
||||
|
||||
type alias InternalConfig a msg =
|
||||
{ label : String
|
||||
, value : a
|
||||
, name : String
|
||||
, selectedValue : Maybe a
|
||||
, isLocked : Bool
|
||||
, isDisabled : Bool
|
||||
, onSelect : a -> msg
|
||||
, showLabel : Bool
|
||||
, premiumMsg : msg
|
||||
, noOpMsg : msg
|
||||
, valueToString : a -> String
|
||||
, showPennant : Bool
|
||||
}
|
||||
|
||||
|
||||
internalView : InternalConfig a msg -> Html msg
|
||||
internalView config =
|
||||
let
|
||||
isChecked =
|
||||
config.selectedValue == Just config.value
|
||||
|
||||
id_ =
|
||||
config.name ++ "-" ++ dasherize (toLower (config.valueToString config.value))
|
||||
|
||||
onContainerClick =
|
||||
if config.isLocked then
|
||||
config.premiumMsg
|
||||
|
||||
else
|
||||
config.noOpMsg
|
||||
in
|
||||
Html.span
|
||||
[ -- This is necessary to prevent event propagation.
|
||||
-- See https://github.com/elm-lang/html/issues/96
|
||||
Html.Styled.Attributes.map (always onContainerClick) <|
|
||||
stopPropagationOn "click"
|
||||
(Json.Decode.succeed ( "stop click propagation", True ))
|
||||
, id (id_ ++ "-container")
|
||||
, classList [ ( "Nri-RadioButton-PremiumClass", config.showPennant ) ]
|
||||
]
|
||||
[ radio config.name
|
||||
(config.valueToString config.value)
|
||||
isChecked
|
||||
[ id id_
|
||||
, Html.Styled.Attributes.disabled config.isLocked
|
||||
, if not config.isDisabled then
|
||||
onClick (config.onSelect config.value)
|
||||
|
||||
else
|
||||
Attributes.none
|
||||
, class "Nri-RadioButton-HiddenRadioInput"
|
||||
, css [ display none ]
|
||||
]
|
||||
, Html.label
|
||||
[ for id_
|
||||
, Widget.disabled config.isLocked
|
||||
, Widget.checked (Just isChecked)
|
||||
, Aria.controls id_
|
||||
, if not config.isLocked then
|
||||
tabindex 0
|
||||
|
||||
else
|
||||
Attributes.none
|
||||
, if not config.isLocked && not config.isDisabled then
|
||||
onEnterAndSpacePreventDefault (config.onSelect config.value)
|
||||
|
||||
else
|
||||
Attributes.none
|
||||
, classList
|
||||
[ ( "Nri-RadioButton-RadioButton", True )
|
||||
, ( "Nri-RadioButton-RadioButtonChecked", isChecked )
|
||||
]
|
||||
, css
|
||||
[ if not config.showLabel then
|
||||
-- Invisible label styles
|
||||
Css.batch
|
||||
[ Css.height (px 28) -- Hardcode height for invisible labels so radio button image appears- normally, label height is set by the text
|
||||
, padding4 (px 4) zero (px 4) (px 28)
|
||||
]
|
||||
|
||||
else
|
||||
padding4 (px 4) zero (px 4) (px 40)
|
||||
, if config.isDisabled then
|
||||
Css.batch
|
||||
[ color Colors.gray45
|
||||
, cursor notAllowed
|
||||
]
|
||||
|
||||
else
|
||||
cursor pointer
|
||||
, fontSize (px 15)
|
||||
, Fonts.baseFont
|
||||
, Css.property "font-weight" "600"
|
||||
, position relative
|
||||
, outline none
|
||||
, margin zero
|
||||
, display inlineBlock
|
||||
, color Colors.navy
|
||||
]
|
||||
]
|
||||
[ radioInputIcon
|
||||
{ isLocked = config.isLocked
|
||||
, isDisabled = config.isDisabled
|
||||
, isChecked = isChecked
|
||||
}
|
||||
, span
|
||||
(case ( config.showLabel, config.showPennant ) of
|
||||
( False, _ ) ->
|
||||
Style.invisible
|
||||
|
||||
( True, False ) ->
|
||||
[]
|
||||
|
||||
( True, True ) ->
|
||||
[ css
|
||||
[ displayFlex
|
||||
, alignItems center
|
||||
, Css.height (px 20)
|
||||
]
|
||||
]
|
||||
)
|
||||
[ Html.text config.label
|
||||
, viewIf
|
||||
(\() ->
|
||||
ClickableSvg.button "Premium"
|
||||
Pennant.premiumFlag
|
||||
[ ClickableSvg.onClick config.premiumMsg
|
||||
, ClickableSvg.width (px 26)
|
||||
, ClickableSvg.height (px 24)
|
||||
, ClickableSvg.css [ marginLeft (px 8) ]
|
||||
]
|
||||
)
|
||||
config.showPennant
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
onEnterAndSpacePreventDefault : msg -> Attribute msg
|
||||
onEnterAndSpacePreventDefault msg =
|
||||
Nri.Ui.Html.V3.onKeyUp
|
||||
{ stopPropagation = False, preventDefault = True }
|
||||
(\code ->
|
||||
if code == 13 || code == 32 then
|
||||
Just msg
|
||||
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
|
||||
|
||||
radioInputIcon :
|
||||
{ isChecked : Bool
|
||||
, isLocked : Bool
|
||||
, isDisabled : Bool
|
||||
}
|
||||
-> Html msg
|
||||
radioInputIcon config =
|
||||
let
|
||||
image =
|
||||
case ( config.isDisabled, config.isLocked, config.isChecked ) of
|
||||
( _, True, _ ) ->
|
||||
lockedSvg
|
||||
|
||||
( True, _, _ ) ->
|
||||
unselectedSvg
|
||||
|
||||
( _, False, True ) ->
|
||||
selectedSvg
|
||||
|
||||
( _, False, False ) ->
|
||||
unselectedSvg
|
||||
in
|
||||
div
|
||||
[ classList
|
||||
[ ( "Nri-RadioButton-RadioButtonIcon", True )
|
||||
, ( "Nri-RadioButton-RadioButtonDisabled", config.isDisabled )
|
||||
]
|
||||
, css
|
||||
[ Css.batch <|
|
||||
if config.isDisabled then
|
||||
[ opacity (num 0.4) ]
|
||||
|
||||
else
|
||||
[]
|
||||
, position absolute
|
||||
, left zero
|
||||
, top zero
|
||||
, Css.width (px 30)
|
||||
, Css.height (px 30)
|
||||
, Css.property "transition" ".3s all"
|
||||
]
|
||||
]
|
||||
[ Nri.Ui.Svg.V1.toHtml image ]
|
||||
|
||||
|
||||
unselectedSvg : Svg
|
||||
unselectedSvg =
|
||||
Svg.svg [ SvgAttributes.viewBox "0 0 27 27" ]
|
||||
[ Svg.defs []
|
||||
[ Svg.rect [ SvgAttributes.id "unselected-path-1", SvgAttributes.x "0", SvgAttributes.y "0", SvgAttributes.width "27", SvgAttributes.height "27", SvgAttributes.rx "13.5" ] []
|
||||
, Svg.filter [ SvgAttributes.id "unselected-filter-2", SvgAttributes.x "-3.7%", SvgAttributes.y "-3.7%", SvgAttributes.width "107.4%", SvgAttributes.height "107.4%", SvgAttributes.filterUnits "objectBoundingBox" ] [ Svg.feOffset [ SvgAttributes.dx "0", SvgAttributes.dy "2", SvgAttributes.in_ "SourceAlpha", SvgAttributes.result "shadowOffsetInner1" ] [], Svg.feComposite [ SvgAttributes.in_ "shadowOffsetInner1", SvgAttributes.in2 "SourceAlpha", SvgAttributes.operator "arithmetic", SvgAttributes.k2 "-1", SvgAttributes.k3 "1", SvgAttributes.result "shadowInnerInner1" ] [], Svg.feColorMatrix [ SvgAttributes.values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0", SvgAttributes.in_ "shadowInnerInner1" ] [] ]
|
||||
]
|
||||
, Svg.g
|
||||
[ SvgAttributes.stroke "none"
|
||||
, SvgAttributes.strokeWidth "1"
|
||||
, SvgAttributes.fill "none"
|
||||
, SvgAttributes.fillRule "evenodd"
|
||||
]
|
||||
[ Svg.g []
|
||||
[ Svg.g []
|
||||
[ Svg.use
|
||||
[ SvgAttributes.fill "#EBEBEB"
|
||||
, SvgAttributes.fillRule "evenodd"
|
||||
, SvgAttributes.xlinkHref "#unselected-path-1"
|
||||
]
|
||||
[]
|
||||
, Svg.use
|
||||
[ SvgAttributes.fill "black"
|
||||
, SvgAttributes.fillOpacity "1"
|
||||
, SvgAttributes.filter "url(#unselected-filter-2)"
|
||||
, SvgAttributes.xlinkHref "#unselected-path-1"
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|> Nri.Ui.Svg.V1.fromHtml
|
||||
|
||||
|
||||
selectedSvg : Svg
|
||||
selectedSvg =
|
||||
Svg.svg [ SvgAttributes.viewBox "0 0 27 27" ]
|
||||
[ Svg.defs []
|
||||
[ Svg.rect [ SvgAttributes.id "selected-path-1", SvgAttributes.x "0", SvgAttributes.y "0", SvgAttributes.width "27", SvgAttributes.height "27", SvgAttributes.rx "13.5" ] []
|
||||
, Svg.filter
|
||||
[ SvgAttributes.id "selected-filter-2", SvgAttributes.x "-3.7%", SvgAttributes.y "-3.7%", SvgAttributes.width "107.4%", SvgAttributes.height "107.4%", SvgAttributes.filterUnits "objectBoundingBox" ]
|
||||
[ Svg.feOffset [ SvgAttributes.dx "0", SvgAttributes.dy "2", SvgAttributes.in_ "SourceAlpha", SvgAttributes.result "shadowOffsetInner1" ] [], Svg.feComposite [ SvgAttributes.in_ "shadowOffsetInner1", SvgAttributes.in2 "SourceAlpha", SvgAttributes.operator "arithmetic", SvgAttributes.k2 "-1", SvgAttributes.k3 "1", SvgAttributes.result "shadowInnerInner1" ] [], Svg.feColorMatrix [ SvgAttributes.values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0", SvgAttributes.in_ "shadowInnerInner1" ] [] ]
|
||||
]
|
||||
, Svg.g
|
||||
[ SvgAttributes.stroke "none"
|
||||
, SvgAttributes.strokeWidth "1"
|
||||
, SvgAttributes.fill "none"
|
||||
, SvgAttributes.fillRule "evenodd"
|
||||
]
|
||||
[ Svg.g []
|
||||
[ Svg.g []
|
||||
[ Svg.use
|
||||
[ SvgAttributes.fill "#D4F0FF"
|
||||
, SvgAttributes.fillRule "evenodd"
|
||||
, SvgAttributes.xlinkHref "#selected-path-1"
|
||||
]
|
||||
[]
|
||||
, Svg.use
|
||||
[ SvgAttributes.fill "black"
|
||||
, SvgAttributes.fillOpacity "1"
|
||||
, SvgAttributes.filter "url(#selected-filter-2)"
|
||||
, SvgAttributes.xlinkHref "#selected-path-1"
|
||||
]
|
||||
[]
|
||||
]
|
||||
, Svg.circle
|
||||
[ SvgAttributes.fill "#146AFF"
|
||||
, SvgAttributes.cx "13.5"
|
||||
, SvgAttributes.cy "13.5"
|
||||
, SvgAttributes.r "6.3"
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
|> Nri.Ui.Svg.V1.fromHtml
|
||||
|
||||
|
||||
lockedSvg : Svg
|
||||
lockedSvg =
|
||||
Svg.svg [ SvgAttributes.viewBox "0 0 30 30" ]
|
||||
[ Svg.defs []
|
||||
[ Svg.rect [ SvgAttributes.id "locked-path-1", SvgAttributes.x "0", SvgAttributes.y "0", SvgAttributes.width "30", SvgAttributes.height "30", SvgAttributes.rx "15" ] []
|
||||
, Svg.filter [ SvgAttributes.id "locked-filter-2", SvgAttributes.x "-3.3%", SvgAttributes.y "-3.3%", SvgAttributes.width "106.7%", SvgAttributes.height "106.7%", SvgAttributes.filterUnits "objectBoundingBox" ] [ Svg.feOffset [ SvgAttributes.dx "0", SvgAttributes.dy "2", SvgAttributes.in_ "SourceAlpha", SvgAttributes.result "shadowOffsetInner1" ] [], Svg.feComposite [ SvgAttributes.in_ "shadowOffsetInner1", SvgAttributes.in2 "SourceAlpha", SvgAttributes.operator "arithmetic", SvgAttributes.k2 "-1", SvgAttributes.k3 "1", SvgAttributes.result "shadowInnerInner1" ] [], Svg.feColorMatrix [ SvgAttributes.values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0", SvgAttributes.in_ "shadowInnerInner1" ] [] ]
|
||||
]
|
||||
, Svg.g
|
||||
[ SvgAttributes.stroke "none"
|
||||
, SvgAttributes.strokeWidth "1"
|
||||
, SvgAttributes.fill "none"
|
||||
, SvgAttributes.fillRule "evenodd"
|
||||
]
|
||||
[ Svg.g []
|
||||
[ Svg.use
|
||||
[ SvgAttributes.fill "#EBEBEB"
|
||||
, SvgAttributes.fillRule "evenodd"
|
||||
, SvgAttributes.xlinkHref "#locked-path-1"
|
||||
]
|
||||
[]
|
||||
, Svg.use
|
||||
[ SvgAttributes.fill "black"
|
||||
, SvgAttributes.fillOpacity "1"
|
||||
, SvgAttributes.filter "url(#locked-filter-2)"
|
||||
, SvgAttributes.xlinkHref "#locked-path-1"
|
||||
]
|
||||
[]
|
||||
]
|
||||
, Svg.g
|
||||
[ SvgAttributes.transform "translate(8.000000, 5.000000)"
|
||||
]
|
||||
[ Svg.path
|
||||
[ SvgAttributes.d "M11.7991616,9.36211885 L11.7991616,5.99470414 C11.7991616,3.24052783 9.64616757,1 7.00011784,1 C4.35359674,1 2.20083837,3.24052783 2.20083837,5.99470414 L2.20083837,9.36211885 L1.51499133,9.36211885 C0.678540765,9.36211885 -6.21724894e-14,10.0675883 -6.21724894e-14,10.9381415 L-6.21724894e-14,18.9239773 C-6.21724894e-14,19.7945305 0.678540765,20.5 1.51499133,20.5 L12.48548,20.5 C13.3219306,20.5 14,19.7945305 14,18.9239773 L14,10.9383868 C14,10.0678336 13.3219306,9.36211885 12.48548,9.36211885 L11.7991616,9.36211885 Z M7.46324136,15.4263108 L7.46324136,17.2991408 C7.46324136,17.5657769 7.25560176,17.7816368 7.00011784,17.7816368 C6.74416256,17.7816368 6.53652295,17.5657769 6.53652295,17.2991408 L6.53652295,15.4263108 C6.01259238,15.228848 5.63761553,14.7080859 5.63761553,14.0943569 C5.63761553,13.3116195 6.24757159,12.6763045 7.00011784,12.6763045 C7.75195704,12.6763045 8.36285584,13.3116195 8.36285584,14.0943569 C8.36285584,14.7088218 7.98717193,15.2295839 7.46324136,15.4263108 L7.46324136,15.4263108 Z M9.98178482,9.36211885 L4.01821518,9.36211885 L4.01821518,5.99470414 C4.01821518,4.2835237 5.35597044,2.89122723 7.00011784,2.89122723 C8.64402956,2.89122723 9.98178482,4.2835237 9.98178482,5.99470414 L9.98178482,9.36211885 L9.98178482,9.36211885 Z"
|
||||
, SvgAttributes.fill "#E68800"
|
||||
]
|
||||
[]
|
||||
, Svg.path
|
||||
[ SvgAttributes.d "M11.7991616,8.14770554 L11.7991616,4.8666348 C11.7991616,2.18307839 9.64616757,-7.10542736e-15 7.00011784,-7.10542736e-15 C4.35359674,-7.10542736e-15 2.20083837,2.18307839 2.20083837,4.8666348 L2.20083837,8.14770554 L1.51499133,8.14770554 C0.678540765,8.14770554 -6.21724894e-14,8.83508604 -6.21724894e-14,9.6833174 L-6.21724894e-14,17.4643881 C-6.21724894e-14,18.3126195 0.678540765,19 1.51499133,19 L12.48548,19 C13.3219306,19 14,18.3126195 14,17.4643881 L14,9.68355641 C14,8.83532505 13.3219306,8.14770554 12.48548,8.14770554 L11.7991616,8.14770554 Z M7.46324136,14.0564054 L7.46324136,15.8812141 C7.46324136,16.1410134 7.25560176,16.3513384 7.00011784,16.3513384 C6.74416256,16.3513384 6.53652295,16.1410134 6.53652295,15.8812141 L6.53652295,14.0564054 C6.01259238,13.8640057 5.63761553,13.3565966 5.63761553,12.7586042 C5.63761553,11.9959369 6.24757159,11.376912 7.00011784,11.376912 C7.75195704,11.376912 8.36285584,11.9959369 8.36285584,12.7586042 C8.36285584,13.3573136 7.98717193,13.8647228 7.46324136,14.0564054 L7.46324136,14.0564054 Z M9.98178482,8.14770554 L4.01821518,8.14770554 L4.01821518,4.8666348 C4.01821518,3.19933078 5.35597044,1.84273423 7.00011784,1.84273423 C8.64402956,1.84273423 9.98178482,3.19933078 9.98178482,4.8666348 L9.98178482,8.14770554 L9.98178482,8.14770554 Z"
|
||||
, SvgAttributes.fill "#FEC709"
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
|> Nri.Ui.Svg.V1.fromHtml
|
@ -23,6 +23,7 @@ import Examples.Message as Message
|
||||
import Examples.Modal as Modal
|
||||
import Examples.Page as Page
|
||||
import Examples.Pennant as Pennant
|
||||
import Examples.RadioButton as RadioButton
|
||||
import Examples.SegmentedControl as SegmentedControl
|
||||
import Examples.Select as Select
|
||||
import Examples.Slide as Slide
|
||||
@ -456,6 +457,25 @@ all =
|
||||
PennantState childState ->
|
||||
Just childState
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
, RadioButton.example
|
||||
|> Example.wrapMsg RadioButtonMsg
|
||||
(\msg ->
|
||||
case msg of
|
||||
RadioButtonMsg childMsg ->
|
||||
Just childMsg
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
|> Example.wrapState RadioButtonState
|
||||
(\msg ->
|
||||
case msg of
|
||||
RadioButtonState childState ->
|
||||
Just childState
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
@ -751,6 +771,7 @@ type State
|
||||
| ModalState Modal.State
|
||||
| PageState Page.State
|
||||
| PennantState Pennant.State
|
||||
| RadioButtonState RadioButton.State
|
||||
| SegmentedControlState SegmentedControl.State
|
||||
| SelectState Select.State
|
||||
| SlideState Slide.State
|
||||
@ -790,6 +811,7 @@ type Msg
|
||||
| ModalMsg Modal.Msg
|
||||
| PageMsg Page.Msg
|
||||
| PennantMsg Pennant.Msg
|
||||
| RadioButtonMsg RadioButton.Msg
|
||||
| SegmentedControlMsg SegmentedControl.Msg
|
||||
| SelectMsg Select.Msg
|
||||
| SlideMsg Slide.Msg
|
||||
|
268
styleguide-app/Examples/RadioButton.elm
Normal file
268
styleguide-app/Examples/RadioButton.elm
Normal file
@ -0,0 +1,268 @@
|
||||
module Examples.RadioButton exposing
|
||||
( example
|
||||
, State, Msg
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs example
|
||||
@docs State, Msg
|
||||
|
||||
-}
|
||||
|
||||
import AtomicDesignType exposing (AtomicDesignType(..))
|
||||
import Category exposing (Category(..))
|
||||
import Css exposing (..)
|
||||
import Debug.Control as Control exposing (Control)
|
||||
import Dict exposing (Dict)
|
||||
import Example exposing (Example)
|
||||
import Html.Styled as Html exposing (..)
|
||||
import Html.Styled.Attributes exposing (css)
|
||||
import KeyboardSupport exposing (Direction(..), Key(..))
|
||||
import Nri.Ui.Button.V10 as Button
|
||||
import Nri.Ui.Data.PremiumLevel as PremiumLevel exposing (PremiumLevel)
|
||||
import Nri.Ui.Heading.V2 as Heading
|
||||
import Nri.Ui.Modal.V9 as Modal
|
||||
import Nri.Ui.RadioButton.V1 as RadioButton
|
||||
import Nri.Ui.Text.V5 as Text
|
||||
|
||||
|
||||
{-| -}
|
||||
example : Example State Msg
|
||||
example =
|
||||
{ name = "Nri.Ui.RadioButton.V1"
|
||||
, state = init
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
, view = view
|
||||
, categories = [ Layout ]
|
||||
, atomicDesignType = Atom
|
||||
, keyboardSupport =
|
||||
-- TODO: fix keyboard support.
|
||||
[]
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
view : State -> List (Html Msg)
|
||||
view model =
|
||||
[ Heading.h3 [] [ Html.text "RadioButton" ]
|
||||
, Heading.h4 [] [ Html.text "view" ]
|
||||
, viewVanilla model
|
||||
, viewInvisibleLabel model
|
||||
, Heading.h4 [] [ Html.text "premium" ]
|
||||
, viewPremium model
|
||||
, Modal.info
|
||||
{ visibleTitle = True
|
||||
, title = "Go Premium!"
|
||||
, wrapMsg = ModalMsg
|
||||
, focusManager =
|
||||
Modal.MultipleFocusableElements
|
||||
(\{ firstFocusableElement, autofocusElement, lastFocusableElement, closeButton } ->
|
||||
{ content =
|
||||
[ Text.mediumBody [] [ text "Often, we'll launch a modal showing the benefits of premium when a locked radio button is clicked." ]
|
||||
, closeButton (autofocusElement :: firstFocusableElement)
|
||||
]
|
||||
, footer =
|
||||
[ Button.button "Okay"
|
||||
[ Button.large
|
||||
, Button.onClick (ModalMsg Modal.close)
|
||||
, Button.custom lastFocusableElement
|
||||
]
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
model.modal
|
||||
]
|
||||
|
||||
|
||||
viewVanilla : State -> Html Msg
|
||||
viewVanilla state =
|
||||
div [ css [ Css.margin (Css.px 8) ] ]
|
||||
[ RadioButton.view
|
||||
{ label = "Cats"
|
||||
, showLabel = True
|
||||
, value = "Cats"
|
||||
, name = "radio-button-examples"
|
||||
, selectedValue = state.selectedValue
|
||||
, onSelect = Select
|
||||
, noOpMsg = NoOp
|
||||
, valueToString = identity
|
||||
}
|
||||
, RadioButton.view
|
||||
{ label = "Dogs"
|
||||
, showLabel = True
|
||||
, value = "Dogs"
|
||||
, name = "radio-button-examples"
|
||||
, selectedValue = state.selectedValue
|
||||
, onSelect = Select
|
||||
, noOpMsg = NoOp
|
||||
, valueToString = identity
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
viewInvisibleLabel : State -> Html Msg
|
||||
viewInvisibleLabel state =
|
||||
div [ css [ Css.margin (Css.px 8) ] ]
|
||||
[ Heading.h4 [] [ Html.text "Invisible Label" ]
|
||||
, RadioButton.view
|
||||
{ label = "Shh"
|
||||
, showLabel = False
|
||||
, value = "I'm a secret... but not to screen readers"
|
||||
, name = "Secret"
|
||||
, selectedValue = state.selectedValue
|
||||
, onSelect = Select
|
||||
, noOpMsg = NoOp
|
||||
, valueToString = identity
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
viewPremium : State -> Html Msg
|
||||
viewPremium state =
|
||||
let
|
||||
premiumConfig =
|
||||
Control.currentValue state.premiumControl
|
||||
in
|
||||
div [ css [ Css.margin (Css.px 8) ] ]
|
||||
[ Heading.h4 [] [ Html.text "Premium Radio Buttons" ]
|
||||
, Html.div [ css [ Css.margin (Css.px 8) ] ]
|
||||
[ Control.view SetPremiumControl state.premiumControl
|
||||
|> Html.fromUnstyled
|
||||
]
|
||||
, RadioButton.premium
|
||||
{ label = "Hedgehog (Free)"
|
||||
, value = "Hedgehogs"
|
||||
, name = "radio-button-examples"
|
||||
, selectedValue = state.selectedValue
|
||||
, teacherPremiumLevel = premiumConfig.teacherPremiumLevel
|
||||
, contentPremiumLevel = PremiumLevel.Free
|
||||
, onSelect = Select
|
||||
|
||||
-- TODO:
|
||||
-- the next version of the RadioComponent will handle focus correctly,
|
||||
-- including re-capturing the focus when the modal closes.
|
||||
-- While we could change premiumMsg to be String -> msg now,
|
||||
-- and use the correct id, there's not much point in doing
|
||||
-- so yet since the radio doesn't handle focus correctly.
|
||||
, premiumMsg = ModalMsg (Modal.open "fake-id")
|
||||
, noOpMsg = NoOp
|
||||
, valueToString = identity
|
||||
, showPennant = premiumConfig.showPennant
|
||||
, isDisabled = False
|
||||
}
|
||||
, RadioButton.premium
|
||||
{ label = "Hedgehodge (Premium)"
|
||||
, value = "Hedgehodges"
|
||||
, name = "radio-button-examples"
|
||||
, selectedValue = state.selectedValue
|
||||
, teacherPremiumLevel = premiumConfig.teacherPremiumLevel
|
||||
, contentPremiumLevel = PremiumLevel.PremiumWithWriting
|
||||
, onSelect = Select
|
||||
|
||||
-- TODO:
|
||||
-- the next version of the RadioComponent will handle focus correctly,
|
||||
-- including re-capturing the focus when the modal closes.
|
||||
-- While we could change premiumMsg to be String -> msg now,
|
||||
-- and use the correct id, there's not much point in doing
|
||||
-- so yet since the radio doesn't handle focus correctly.
|
||||
, premiumMsg = ModalMsg (Modal.open "fake-id")
|
||||
, noOpMsg = NoOp
|
||||
, valueToString = identity
|
||||
, showPennant = premiumConfig.showPennant
|
||||
, isDisabled = False
|
||||
}
|
||||
, RadioButton.premium
|
||||
{ label = "Disabled"
|
||||
, value = "Disabled"
|
||||
, name = "radio-button-examples"
|
||||
, selectedValue = state.selectedValue
|
||||
, teacherPremiumLevel = premiumConfig.teacherPremiumLevel
|
||||
, contentPremiumLevel = PremiumLevel.PremiumWithWriting
|
||||
, onSelect = Select
|
||||
|
||||
-- TODO:
|
||||
-- the next version of the RadioComponent will handle focus correctly,
|
||||
-- including re-capturing the focus when the modal closes.
|
||||
-- While we could change premiumMsg to be String -> msg now,
|
||||
-- and use the correct id, there's not much point in doing
|
||||
-- so yet since the radio doesn't handle focus correctly.
|
||||
, premiumMsg = ModalMsg (Modal.open "fake-id")
|
||||
, noOpMsg = NoOp
|
||||
, valueToString = identity
|
||||
, showPennant = premiumConfig.showPennant
|
||||
, isDisabled = True
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias State =
|
||||
{ selectedValue : Maybe String
|
||||
, modal : Modal.Model
|
||||
, premiumControl : Control PremiumConfig
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
init : State
|
||||
init =
|
||||
{ selectedValue = Nothing
|
||||
, modal = Modal.init
|
||||
, premiumControl = initPremiumControls
|
||||
}
|
||||
|
||||
|
||||
type alias PremiumConfig =
|
||||
{ teacherPremiumLevel : PremiumLevel
|
||||
, showPennant : Bool
|
||||
}
|
||||
|
||||
|
||||
initPremiumControls : Control PremiumConfig
|
||||
initPremiumControls =
|
||||
Control.record PremiumConfig
|
||||
|> Control.field "teacherPremiumLevel"
|
||||
(Control.choice
|
||||
[ ( "Free", Control.value PremiumLevel.Free )
|
||||
, ( "Premium", Control.value PremiumLevel.PremiumWithWriting )
|
||||
]
|
||||
)
|
||||
|> Control.field "showPennant" (Control.bool False)
|
||||
|
||||
|
||||
type Msg
|
||||
= ModalMsg Modal.Msg
|
||||
| Select String
|
||||
| SetPremiumControl (Control PremiumConfig)
|
||||
| NoOp
|
||||
|
||||
|
||||
{-| -}
|
||||
update : Msg -> State -> ( State, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
ModalMsg modalMsg ->
|
||||
let
|
||||
( modal, cmd ) =
|
||||
Modal.update { dismissOnEscAndOverlayClick = True }
|
||||
modalMsg
|
||||
model.modal
|
||||
in
|
||||
( { model | modal = modal }, Cmd.map ModalMsg cmd )
|
||||
|
||||
Select value ->
|
||||
( { model | selectedValue = Just value }, Cmd.none )
|
||||
|
||||
SetPremiumControl premiumControl ->
|
||||
( { model | premiumControl = premiumControl }, Cmd.none )
|
||||
|
||||
NoOp ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
subscriptions : State -> Sub Msg
|
||||
subscriptions { modal } =
|
||||
Sub.map ModalMsg (Modal.subscriptions modal)
|
@ -41,6 +41,7 @@
|
||||
"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",
|
||||
|
Loading…
Reference in New Issue
Block a user