Merge pull request #563 from NoRedInk/tessa/add-radio-button

Tessa/add radio button
This commit is contained in:
Tessa 2020-07-31 13:05:40 -07:00 committed by GitHub
commit 2aa97e4f5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 738 additions and 1 deletions

View File

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

View File

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

View 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

View File

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

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

View File

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