From 5d4b58a85274a81decd43de4f980ca58dcca37ba Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Tue, 16 Nov 2021 14:05:59 -0500 Subject: [PATCH 01/67] copy v2 to v3 --- src/Nri/Ui/RadioButton/V3.elm | 426 ++++++++++++++++++++++++ styleguide-app/Examples/RadioButton.elm | 4 +- 2 files changed, 428 insertions(+), 2 deletions(-) create mode 100644 src/Nri/Ui/RadioButton/V3.elm diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm new file mode 100644 index 00000000..d38d921d --- /dev/null +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -0,0 +1,426 @@ +module Nri.Ui.RadioButton.V3 exposing (view, premium) + +{-| Changes from V1: + + - adds an outline when a radio button is focused + - remove NoOp/event swallowing (it broke default radio button behavior) + +@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 Css.Global +import Html.Styled as Html +import Html.Styled.Attributes exposing (..) +import Html.Styled.Events exposing (onClick, stopPropagationOn) +import Json.Decode +import Nri.Ui.ClickableSvg.V2 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 + , valueToString : a -> String + } + -> Html msg +view config = + internalView + { label = config.label + , value = config.value + , name = config.name + , selectedValue = config.selectedValue + , isLocked = False + , isDisabled = False + , onSelect = config.onSelect + , premiumMsg = Nothing + , 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 + , 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 + , valueToString = config.valueToString + , premiumMsg = Just config.premiumMsg + , 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 + , premiumMsg : Maybe 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)) + in + Html.span + [ id (id_ ++ "-container") + , classList [ ( "Nri-RadioButton-PremiumClass", config.showPennant ) ] + , css + [ position relative + , marginLeft (px -4) + , display inlineBlock + , Css.height (px 34) + , pseudoClass "focus-within" + [ Css.Global.descendants + [ Css.Global.class "Nri-RadioButton-RadioButtonIcon" + [ borderColor (rgb 0 95 204) + ] + ] + ] + ] + ] + [ radio config.name + (config.valueToString config.value) + isChecked + [ id id_ + , Widget.disabled (config.isLocked || config.isDisabled) + , if not config.isDisabled then + onClick (config.onSelect config.value) + + else + Attributes.none + , class "Nri-RadioButton-HiddenRadioInput" + , css + [ position absolute + , top (px 4) + , left (px 4) + , opacity zero + ] + ] + , Html.label + [ for id_ + , classList + [ ( "Nri-RadioButton-RadioButton", True ) + , ( "Nri-RadioButton-RadioButtonChecked", isChecked ) + ] + , css + [ padding4 (px 6) 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 + (if config.showPennant then + [ css + [ displayFlex + , alignItems center + , Css.height (px 20) + ] + ] + + else + [ css [ verticalAlign middle ] ] + ) + [ Html.text config.label + , viewIf + (\() -> + ClickableSvg.button "Premium" + Pennant.premiumFlag + [ Maybe.map ClickableSvg.onClick config.premiumMsg + |> Maybe.withDefault (ClickableSvg.custom []) + , ClickableSvg.exactWidth 26 + , ClickableSvg.exactHeight 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.property "transition" ".3s all" + , border3 (px 2) solid transparent + , borderRadius (px 50) + , padding (px 2) + , displayFlex + , justifyContent center + , alignItems center + ] + ] + [ image + |> Nri.Ui.Svg.V1.withHeight (Css.px 26) + |> Nri.Ui.Svg.V1.withWidth (Css.px 26) + |> Nri.Ui.Svg.V1.toHtml + ] + + +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 diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index 74d3dcef..e265ceee 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -23,7 +23,7 @@ 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.V11 as Modal -import Nri.Ui.RadioButton.V2 as RadioButton +import Nri.Ui.RadioButton.V3 as RadioButton import Nri.Ui.Text.V6 as Text import Task @@ -32,7 +32,7 @@ import Task example : Example State Msg example = { name = "RadioButton" - , version = 2 + , version = 3 , state = init , update = update , subscriptions = subscriptions From fc7c4b916f0c7d293e0cd941d2f3a1758a8c3848 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Wed, 17 Nov 2021 11:26:16 -0500 Subject: [PATCH 02/67] boilerplate to have list API for radio config/events --- src/Nri/Ui/RadioButton/V3.elm | 102 +++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 13 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index d38d921d..b27a730f 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -33,6 +33,83 @@ import Svg.Styled as Svg import Svg.Styled.Attributes as SvgAttributes +{-| Customizations for the RadioButton. +-} +type Attribute value msg + = Attribute (EventsAndValues value msg) (Config -> Config) + + +type alias EventsAndValues value msg = + { value : Maybe value + , selectedValue : Maybe value + , onSelect : Maybe (value -> msg) + , valueToString : Maybe (value -> String) + , premiumMsg : Maybe msg + } + + +emptyEventsAndValues : EventsAndValues value msg +emptyEventsAndValues = + { value = Nothing + , selectedValue = Nothing + , onSelect = Nothing + , valueToString = Nothing + , premiumMsg = Nothing + } + + +{-| This is private. The public API only exposes `Attribute`. +-} +type alias Config = + { label : String + , name : String + , isLocked : Bool + , isDisabled : Bool + , showPennant : Bool + } + + +emptyConfig : Config +emptyConfig = + { label = "" + , name = "" + , isLocked = False + , isDisabled = False + , showPennant = False + } + + +applyConfig : List (Attribute value msg) -> Config +applyConfig attributes = + List.foldl (\(Attribute _ update) config -> update config) + emptyConfig + attributes + + +orExisting : (acc -> Maybe a) -> acc -> acc -> Maybe a +orExisting f new previous = + case f previous of + Just just -> + Just just + + Nothing -> + f new + + +applyEvents : List (Attribute value msg) -> EventsAndValues value msg +applyEvents = + List.foldl + (\(Attribute eventsAndValues _) existing -> + { value = orExisting .value eventsAndValues existing + , selectedValue = orExisting .selectedValue eventsAndValues existing + , onSelect = orExisting .onSelect eventsAndValues existing + , valueToString = orExisting .valueToString eventsAndValues existing + , premiumMsg = orExisting .premiumMsg eventsAndValues existing + } + ) + emptyEventsAndValues + + {-| View a single radio button. If used in a group, all radio buttons in the group should have the same name attribute. -} @@ -137,6 +214,18 @@ internalView config = id_ = config.name ++ "-" ++ dasherize (toLower (config.valueToString config.value)) + + attributes : List (Attribute a msg) + attributes = + [] + + eventsAndValues : EventsAndValues a msg + eventsAndValues = + applyEvents attributes + + config_ : Config + config_ = + applyConfig attributes in Html.span [ id (id_ ++ "-container") @@ -234,19 +323,6 @@ internalView config = ] -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 From ac2ff7d59f2eaf44b0f2fadb0201daba10fd654a Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Wed, 17 Nov 2021 11:34:54 -0500 Subject: [PATCH 03/67] expose radiobutton v3 --- deprecated-modules.csv | 1 + elm.json | 1 + forbidden-imports.toml | 3 +++ tests/elm-verify-examples.json | 1 + 4 files changed, 6 insertions(+) diff --git a/deprecated-modules.csv b/deprecated-modules.csv index 56c8caf4..4d288ce1 100644 --- a/deprecated-modules.csv +++ b/deprecated-modules.csv @@ -1,6 +1,7 @@ Nri.Ui.Accordion.V1,upgrade to V3 Nri.Ui.Menu.V1,upgrade to V3 Nri.Ui.Menu.V2,upgrade to V3 +Nri.Ui.RadioButton.V2,upgrade to V3 Nri.Ui.Table.V4,upgrade to V5 Nri.Ui.Tabs.V6,upgrade to V7 Nri.Ui.Tooltip.V1,upgrade to V2 diff --git a/elm.json b/elm.json index 1c649ee9..0615c170 100644 --- a/elm.json +++ b/elm.json @@ -44,6 +44,7 @@ "Nri.Ui.Pennant.V2", "Nri.Ui.PremiumCheckbox.V6", "Nri.Ui.RadioButton.V2", + "Nri.Ui.RadioButton.V3", "Nri.Ui.SegmentedControl.V14", "Nri.Ui.Select.V8", "Nri.Ui.Slide.V1", diff --git a/forbidden-imports.toml b/forbidden-imports.toml index 0907ce19..a99c4982 100644 --- a/forbidden-imports.toml +++ b/forbidden-imports.toml @@ -81,6 +81,9 @@ hint = 'upgrade to V11' [forbidden."Nri.Ui.RadioButton.V1"] hint = 'upgrade to V2' +[forbidden."Nri.Ui.RadioButton.V2"] +hint = 'upgrade to V3' + [forbidden."Nri.Ui.SegmentedControl.V11"] hint = 'upgrade to V14' diff --git a/tests/elm-verify-examples.json b/tests/elm-verify-examples.json index 1af655ab..27b74fe3 100644 --- a/tests/elm-verify-examples.json +++ b/tests/elm-verify-examples.json @@ -40,6 +40,7 @@ "Nri.Ui.Pennant.V2", "Nri.Ui.PremiumCheckbox.V6", "Nri.Ui.RadioButton.V2", + "Nri.Ui.RadioButton.V3", "Nri.Ui.SegmentedControl.V14", "Nri.Ui.Select.V8", "Nri.Ui.Slide.V1", From 9ae32aaf0236d1873cd3d6053eef4d6221bdd338 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Wed, 17 Nov 2021 11:39:26 -0500 Subject: [PATCH 04/67] expose list based API from view --- src/Nri/Ui/RadioButton/V3.elm | 18 ++++++++---------- styleguide-app/Examples/RadioButton.elm | 2 ++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index b27a730f..48149813 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -115,12 +115,13 @@ If used in a group, all radio buttons in the group should have the same name att -} view : { label : String - , value : a + , value : value , name : String - , selectedValue : Maybe a - , onSelect : a -> msg - , valueToString : a -> String + , selectedValue : Maybe value + , onSelect : value -> msg + , valueToString : value -> String } + -> List (Attribute value msg) -> Html msg view config = internalView @@ -190,6 +191,7 @@ premium config = PremiumLevel.Free -> False } + [] type alias InternalConfig a msg = @@ -206,8 +208,8 @@ type alias InternalConfig a msg = } -internalView : InternalConfig a msg -> Html msg -internalView config = +internalView : InternalConfig a msg -> List (Attribute a msg) -> Html msg +internalView config attributes = let isChecked = config.selectedValue == Just config.value @@ -215,10 +217,6 @@ internalView config = id_ = config.name ++ "-" ++ dasherize (toLower (config.valueToString config.value)) - attributes : List (Attribute a msg) - attributes = - [] - eventsAndValues : EventsAndValues a msg eventsAndValues = applyEvents attributes diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index e265ceee..86c68991 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -94,6 +94,7 @@ viewVanilla state = , onSelect = Select , valueToString = identity } + [] , RadioButton.view { label = "Dogs" , value = "Dogs" @@ -102,6 +103,7 @@ viewVanilla state = , onSelect = Select , valueToString = identity } + [] ] From a750666b2e2cf2b41a5f22fd3637328b7f9b1573 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Wed, 17 Nov 2021 12:01:25 -0500 Subject: [PATCH 05/67] add disable/enabled attributes to the list based api --- src/Nri/Ui/RadioButton/V3.elm | 45 ++++++++++++++++++------- styleguide-app/Examples/RadioButton.elm | 5 +-- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 48149813..221ebb56 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -1,11 +1,14 @@ -module Nri.Ui.RadioButton.V3 exposing (view, premium) +module Nri.Ui.RadioButton.V3 exposing + ( view, premium + , disabled, enabled + ) -{-| Changes from V1: +{-| Changes from V2: - - adds an outline when a radio button is focused - - remove NoOp/event swallowing (it broke default radio button behavior) + - list based API instead of record based @docs view, premium +@docs disabled, enabled -} @@ -33,6 +36,22 @@ import Svg.Styled as Svg import Svg.Styled.Attributes as SvgAttributes +{-| This disables the input +-} +disabled : Attribute value msg +disabled = + Attribute emptyEventsAndValues <| + \config -> { config | isDisabled = True } + + +{-| This enables the input, this is the default behavior +-} +enabled : Attribute value msg +enabled = + Attribute emptyEventsAndValues <| + \config -> { config | isDisabled = False } + + {-| Customizations for the RadioButton. -} type Attribute value msg @@ -130,7 +149,6 @@ view config = , name = config.name , selectedValue = config.selectedValue , isLocked = False - , isDisabled = False , onSelect = config.onSelect , premiumMsg = Nothing , valueToString = config.valueToString @@ -176,7 +194,6 @@ premium config = , name = config.name , selectedValue = config.selectedValue , isLocked = isLocked - , isDisabled = config.isDisabled , onSelect = config.onSelect , valueToString = config.valueToString , premiumMsg = Just config.premiumMsg @@ -191,7 +208,12 @@ premium config = PremiumLevel.Free -> False } - [] + [ if config.isDisabled then + disabled + + else + enabled + ] type alias InternalConfig a msg = @@ -200,7 +222,6 @@ type alias InternalConfig a msg = , name : String , selectedValue : Maybe a , isLocked : Bool - , isDisabled : Bool , onSelect : a -> msg , premiumMsg : Maybe msg , valueToString : a -> String @@ -246,8 +267,8 @@ internalView config attributes = (config.valueToString config.value) isChecked [ id id_ - , Widget.disabled (config.isLocked || config.isDisabled) - , if not config.isDisabled then + , Widget.disabled (config.isLocked || config_.isDisabled) + , if not config_.isDisabled then onClick (config.onSelect config.value) else @@ -268,7 +289,7 @@ internalView config attributes = ] , css [ padding4 (px 6) zero (px 4) (px 40) - , if config.isDisabled then + , if config_.isDisabled then Css.batch [ color Colors.gray45 , cursor notAllowed @@ -288,7 +309,7 @@ internalView config attributes = ] [ radioInputIcon { isLocked = config.isLocked - , isDisabled = config.isDisabled + , isDisabled = config_.isDisabled , isChecked = isChecked } , span diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index 86c68991..5d223e12 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -94,7 +94,8 @@ viewVanilla state = , onSelect = Select , valueToString = identity } - [] + [ RadioButton.disabled + ] , RadioButton.view { label = "Dogs" , value = "Dogs" @@ -103,7 +104,7 @@ viewVanilla state = , onSelect = Select , valueToString = identity } - [] + [ RadioButton.enabled ] ] From 6a298e82b63444f1a4341b1b5be9a9b090eab2dd Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Thu, 18 Nov 2021 16:23:43 -0500 Subject: [PATCH 06/67] move value into list api --- src/Nri/Ui/RadioButton/V3.elm | 228 +++++++++++++----------- styleguide-app/Examples/RadioButton.elm | 14 +- 2 files changed, 128 insertions(+), 114 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 221ebb56..280f9d81 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -1,6 +1,6 @@ module Nri.Ui.RadioButton.V3 exposing ( view, premium - , disabled, enabled + , disabled, enabled, value ) {-| Changes from V2: @@ -8,7 +8,7 @@ module Nri.Ui.RadioButton.V3 exposing - list based API instead of record based @docs view, premium -@docs disabled, enabled +@docs disabled, enabled, value -} @@ -52,6 +52,12 @@ enabled = \config -> { config | isDisabled = False } +{-| -} +value : value -> Attribute value msg +value value_ = + Attribute { emptyEventsAndValues | value = Just value_ } identity + + {-| Customizations for the RadioButton. -} type Attribute value msg @@ -134,7 +140,6 @@ If used in a group, all radio buttons in the group should have the same name att -} view : { label : String - , value : value , name : String , selectedValue : Maybe value , onSelect : value -> msg @@ -145,7 +150,6 @@ view : view config = internalView { label = config.label - , value = config.value , name = config.name , selectedValue = config.selectedValue , isLocked = False @@ -190,7 +194,6 @@ premium config = in internalView { label = config.label - , value = config.value , name = config.name , selectedValue = config.selectedValue , isLocked = isLocked @@ -216,130 +219,139 @@ premium config = ] -type alias InternalConfig a msg = +type alias InternalConfig value msg = { label : String - , value : a , name : String - , selectedValue : Maybe a + , selectedValue : Maybe value , isLocked : Bool - , onSelect : a -> msg + , onSelect : value -> msg , premiumMsg : Maybe msg - , valueToString : a -> String + , valueToString : value -> String , showPennant : Bool } -internalView : InternalConfig a msg -> List (Attribute a msg) -> Html msg +internalView : InternalConfig value msg -> List (Attribute value msg) -> Html msg internalView config attributes = let isChecked = - config.selectedValue == Just config.value + config.selectedValue == eventsAndValues.value - id_ = - config.name ++ "-" ++ dasherize (toLower (config.valueToString config.value)) - - eventsAndValues : EventsAndValues a msg + eventsAndValues : EventsAndValues value msg eventsAndValues = applyEvents attributes config_ : Config config_ = applyConfig attributes + + unvalidatedRadioConfig : Maybe value + unvalidatedRadioConfig = + eventsAndValues.value in - Html.span - [ id (id_ ++ "-container") - , classList [ ( "Nri-RadioButton-PremiumClass", config.showPennant ) ] - , css - [ position relative - , marginLeft (px -4) - , display inlineBlock - , Css.height (px 34) - , pseudoClass "focus-within" - [ Css.Global.descendants - [ Css.Global.class "Nri-RadioButton-RadioButtonIcon" - [ borderColor (rgb 0 95 204) - ] - ] - ] - ] - ] - [ radio config.name - (config.valueToString config.value) - isChecked - [ id id_ - , Widget.disabled (config.isLocked || config_.isDisabled) - , if not config_.isDisabled then - onClick (config.onSelect config.value) - - else - Attributes.none - , class "Nri-RadioButton-HiddenRadioInput" - , css - [ position absolute - , top (px 4) - , left (px 4) - , opacity zero - ] - ] - , Html.label - [ for id_ - , classList - [ ( "Nri-RadioButton-RadioButton", True ) - , ( "Nri-RadioButton-RadioButtonChecked", isChecked ) - ] - , css - [ padding4 (px 6) 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 - (if config.showPennant then - [ css - [ displayFlex - , alignItems center - , Css.height (px 20) - ] - ] - - else - [ css [ verticalAlign middle ] ] - ) - [ Html.text config.label - , viewIf - (\() -> - ClickableSvg.button "Premium" - Pennant.premiumFlag - [ Maybe.map ClickableSvg.onClick config.premiumMsg - |> Maybe.withDefault (ClickableSvg.custom []) - , ClickableSvg.exactWidth 26 - , ClickableSvg.exactHeight 24 - , ClickableSvg.css [ marginLeft (px 8) ] + case unvalidatedRadioConfig of + Just realValue -> + let + id_ = + config.name ++ "-" ++ dasherize (toLower (config.valueToString realValue)) + in + Html.span + [ id (id_ ++ "-container") + , classList [ ( "Nri-RadioButton-PremiumClass", config.showPennant ) ] + , css + [ position relative + , marginLeft (px -4) + , display inlineBlock + , Css.height (px 34) + , pseudoClass "focus-within" + [ Css.Global.descendants + [ Css.Global.class "Nri-RadioButton-RadioButtonIcon" + [ borderColor (rgb 0 95 204) + ] ] - ) - config.showPennant + ] + ] ] - ] - ] + [ radio config.name + (config.valueToString realValue) + isChecked + [ id id_ + , Widget.disabled (config.isLocked || config_.isDisabled) + , if not config_.isDisabled then + onClick (config.onSelect realValue) + + else + Attributes.none + , class "Nri-RadioButton-HiddenRadioInput" + , css + [ position absolute + , top (px 4) + , left (px 4) + , opacity zero + ] + ] + , Html.label + [ for id_ + , classList + [ ( "Nri-RadioButton-RadioButton", True ) + , ( "Nri-RadioButton-RadioButtonChecked", isChecked ) + ] + , css + [ padding4 (px 6) 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 + (if config.showPennant then + [ css + [ displayFlex + , alignItems center + , Css.height (px 20) + ] + ] + + else + [ css [ verticalAlign middle ] ] + ) + [ Html.text config.label + , viewIf + (\() -> + ClickableSvg.button "Premium" + Pennant.premiumFlag + [ Maybe.map ClickableSvg.onClick config.premiumMsg + |> Maybe.withDefault (ClickableSvg.custom []) + , ClickableSvg.exactWidth 26 + , ClickableSvg.exactHeight 24 + , ClickableSvg.css [ marginLeft (px 8) ] + ] + ) + config.showPennant + ] + ] + ] + + Nothing -> + text "no radio button here" radioInputIcon : diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index 5d223e12..6ec9494e 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -88,23 +88,25 @@ viewVanilla state = div [] [ RadioButton.view { label = "Cats" - , value = "Cats" , name = "radio-button-examples" , selectedValue = state.selectedValue - , onSelect = Select + , onSelect = Debug.log "selected" >> Select , valueToString = identity } - [ RadioButton.disabled + [ RadioButton.enabled + , RadioButton.value "Felines" + , RadioButton.name "radio-button-examples" ] , RadioButton.view { label = "Dogs" - , value = "Dogs" , name = "radio-button-examples" , selectedValue = state.selectedValue - , onSelect = Select + , onSelect = Debug.log "selected" >> Select , valueToString = identity } - [ RadioButton.enabled ] + [ RadioButton.enabled + , RadioButton.value "Canines" + ] ] From a7efe81cb1e02473b406b00245c74b848284ba10 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Thu, 18 Nov 2021 16:43:48 -0500 Subject: [PATCH 07/67] move name into list api --- src/Nri/Ui/RadioButton/V3.elm | 22 ++++++++++++++-------- styleguide-app/Examples/RadioButton.elm | 3 +-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 280f9d81..b164ef36 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -1,6 +1,7 @@ module Nri.Ui.RadioButton.V3 exposing ( view, premium - , disabled, enabled, value + , disabled, enabled + , value, name ) {-| Changes from V2: @@ -8,7 +9,8 @@ module Nri.Ui.RadioButton.V3 exposing - list based API instead of record based @docs view, premium -@docs disabled, enabled, value +@docs disabled, enabled +@docs value, name -} @@ -52,6 +54,14 @@ enabled = \config -> { config | isDisabled = False } +{-| Every radio button in the same group should have the same name +-} +name : String -> Attribute value msg +name name_ = + Attribute emptyEventsAndValues <| + \config -> { config | name = name_ } + + {-| -} value : value -> Attribute value msg value value_ = @@ -140,7 +150,6 @@ If used in a group, all radio buttons in the group should have the same name att -} view : { label : String - , name : String , selectedValue : Maybe value , onSelect : value -> msg , valueToString : value -> String @@ -150,7 +159,6 @@ view : view config = internalView { label = config.label - , name = config.name , selectedValue = config.selectedValue , isLocked = False , onSelect = config.onSelect @@ -194,7 +202,6 @@ premium config = in internalView { label = config.label - , name = config.name , selectedValue = config.selectedValue , isLocked = isLocked , onSelect = config.onSelect @@ -221,7 +228,6 @@ premium config = type alias InternalConfig value msg = { label : String - , name : String , selectedValue : Maybe value , isLocked : Bool , onSelect : value -> msg @@ -253,7 +259,7 @@ internalView config attributes = Just realValue -> let id_ = - config.name ++ "-" ++ dasherize (toLower (config.valueToString realValue)) + config_.name ++ "-" ++ dasherize (toLower (config.valueToString realValue)) in Html.span [ id (id_ ++ "-container") @@ -272,7 +278,7 @@ internalView config attributes = ] ] ] - [ radio config.name + [ radio config_.name (config.valueToString realValue) isChecked [ id id_ diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index 6ec9494e..68b9fb12 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -88,7 +88,6 @@ viewVanilla state = div [] [ RadioButton.view { label = "Cats" - , name = "radio-button-examples" , selectedValue = state.selectedValue , onSelect = Debug.log "selected" >> Select , valueToString = identity @@ -99,13 +98,13 @@ viewVanilla state = ] , RadioButton.view { label = "Dogs" - , name = "radio-button-examples" , selectedValue = state.selectedValue , onSelect = Debug.log "selected" >> Select , valueToString = identity } [ RadioButton.enabled , RadioButton.value "Canines" + , RadioButton.name "radio-button-examples" ] ] From e30a9680883ea6355c1a7a3ff03ccd79f9d59572 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Thu, 18 Nov 2021 17:01:48 -0500 Subject: [PATCH 08/67] premium is becoming a hassle so I will remove it for now and maybe add it back later --- src/Nri/Ui/RadioButton/V3.elm | 62 +------------------ styleguide-app/Examples/RadioButton.elm | 80 +------------------------ 2 files changed, 4 insertions(+), 138 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index b164ef36..108ce099 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -1,5 +1,5 @@ module Nri.Ui.RadioButton.V3 exposing - ( view, premium + ( view , disabled, enabled , value, name ) @@ -8,7 +8,7 @@ module Nri.Ui.RadioButton.V3 exposing - list based API instead of record based -@docs view, premium +@docs view @docs disabled, enabled @docs value, name @@ -168,64 +168,6 @@ view config = } -{-| 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 - , 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 - , selectedValue = config.selectedValue - , isLocked = isLocked - , onSelect = config.onSelect - , valueToString = config.valueToString - , premiumMsg = Just config.premiumMsg - , showPennant = - case config.contentPremiumLevel of - PremiumLevel.Premium -> - config.showPennant - - PremiumLevel.PremiumWithWriting -> - config.showPennant - - PremiumLevel.Free -> - False - } - [ if config.isDisabled then - disabled - - else - enabled - ] - - type alias InternalConfig value msg = { label : String , selectedValue : Maybe value diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index 68b9fb12..f9752c0e 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -60,7 +60,6 @@ view model = , Heading.h4 [] [ Html.text "view" ] , viewVanilla model , Heading.h4 [] [ Html.text "premium" ] - , viewPremium model , Modal.view { title = "Go Premium!" , wrapMsg = ModalMsg @@ -88,102 +87,27 @@ viewVanilla state = div [] [ RadioButton.view { label = "Cats" - , selectedValue = state.selectedValue , onSelect = Debug.log "selected" >> Select , valueToString = identity } [ RadioButton.enabled , RadioButton.value "Felines" , RadioButton.name "radio-button-examples" + , RadioButton.selectedValue state.selectedValue ] , RadioButton.view { label = "Dogs" - , selectedValue = state.selectedValue , onSelect = Debug.log "selected" >> Select , valueToString = identity } [ RadioButton.enabled , RadioButton.value "Canines" , RadioButton.name "radio-button-examples" + , RadioButton.selectedValue state.selectedValue ] ] -viewPremium : State -> Html Msg -viewPremium state = - let - premiumConfig = - Control.currentValue state.premiumControl - in - div [] - [ 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 = OpenModal "hedgehogs-free" - , 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 = OpenModal "hedgehogs-premium" - , 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 = OpenModal "hedgehogs-premium" - , valueToString = identity - , showPennant = premiumConfig.showPennant - , isDisabled = True - } - ] - - {-| -} type alias State = { selectedValue : Maybe String From aaa1d0f2f788fe151c6f6f60a862951fda9ec4be Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Thu, 18 Nov 2021 17:10:29 -0500 Subject: [PATCH 09/67] move selectedValue into the list api --- src/Nri/Ui/RadioButton/V3.elm | 24 +++++++++++++++++------- styleguide-app/Examples/RadioButton.elm | 4 ++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 108ce099..1f6b5808 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -1,7 +1,8 @@ module Nri.Ui.RadioButton.V3 exposing ( view , disabled, enabled - , value, name + , value, selectedValue + , name ) {-| Changes from V2: @@ -10,7 +11,8 @@ module Nri.Ui.RadioButton.V3 exposing @docs view @docs disabled, enabled -@docs value, name +@docs value, selectedValue +@docs name -} @@ -62,12 +64,20 @@ name name_ = \config -> { config | name = name_ } -{-| -} +{-| Sets the value of one radio button +-} value : value -> Attribute value msg value value_ = Attribute { emptyEventsAndValues | value = Just value_ } identity +{-| Specifies what the current value of a group of radio buttons should be +-} +selectedValue : Maybe value -> Attribute value msg +selectedValue value_ = + Attribute { emptyEventsAndValues | selectedValue = value_ } identity + + {-| Customizations for the RadioButton. -} type Attribute value msg @@ -150,7 +160,6 @@ If used in a group, all radio buttons in the group should have the same name att -} view : { label : String - , selectedValue : Maybe value , onSelect : value -> msg , valueToString : value -> String } @@ -159,7 +168,6 @@ view : view config = internalView { label = config.label - , selectedValue = config.selectedValue , isLocked = False , onSelect = config.onSelect , premiumMsg = Nothing @@ -170,7 +178,6 @@ view config = type alias InternalConfig value msg = { label : String - , selectedValue : Maybe value , isLocked : Bool , onSelect : value -> msg , premiumMsg : Maybe msg @@ -183,7 +190,10 @@ internalView : InternalConfig value msg -> List (Attribute value msg) -> Html ms internalView config attributes = let isChecked = - config.selectedValue == eventsAndValues.value + -- why not guard and make sure neither is Nothing? + -- Because if value is Nothing we do not render a radio + eventsAndValues.selectedValue + == eventsAndValues.value eventsAndValues : EventsAndValues value msg eventsAndValues = diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index f9752c0e..a3e5ce19 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -87,7 +87,7 @@ viewVanilla state = div [] [ RadioButton.view { label = "Cats" - , onSelect = Debug.log "selected" >> Select + , onSelect = Select , valueToString = identity } [ RadioButton.enabled @@ -97,7 +97,7 @@ viewVanilla state = ] , RadioButton.view { label = "Dogs" - , onSelect = Debug.log "selected" >> Select + , onSelect = Select , valueToString = identity } [ RadioButton.enabled From 7460c4700bfa13b6298f9fdc72c8f8e3f65b1662 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Thu, 18 Nov 2021 17:48:49 -0500 Subject: [PATCH 10/67] move onSelect into list API --- src/Nri/Ui/RadioButton/V3.elm | 21 ++++++++++++++------- styleguide-app/Examples/RadioButton.elm | 14 ++++++++++++-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 1f6b5808..3ef74388 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -3,6 +3,7 @@ module Nri.Ui.RadioButton.V3 exposing , disabled, enabled , value, selectedValue , name + , onSelect ) {-| Changes from V2: @@ -12,6 +13,7 @@ module Nri.Ui.RadioButton.V3 exposing @docs view @docs disabled, enabled @docs value, selectedValue +@docs docs onSelect @docs name -} @@ -78,6 +80,13 @@ selectedValue value_ = Attribute { emptyEventsAndValues | selectedValue = value_ } identity +{-| What message +-} +onSelect : (value -> msg) -> Attribute value msg +onSelect onSelect_ = + Attribute { emptyEventsAndValues | onSelect = Just onSelect_ } identity + + {-| Customizations for the RadioButton. -} type Attribute value msg @@ -160,7 +169,6 @@ If used in a group, all radio buttons in the group should have the same name att -} view : { label : String - , onSelect : value -> msg , valueToString : value -> String } -> List (Attribute value msg) @@ -169,7 +177,6 @@ view config = internalView { label = config.label , isLocked = False - , onSelect = config.onSelect , premiumMsg = Nothing , valueToString = config.valueToString , showPennant = False @@ -179,7 +186,6 @@ view config = type alias InternalConfig value msg = { label : String , isLocked : Bool - , onSelect : value -> msg , premiumMsg : Maybe msg , valueToString : value -> String , showPennant : Bool @@ -235,11 +241,12 @@ internalView config attributes = isChecked [ id id_ , Widget.disabled (config.isLocked || config_.isDisabled) - , if not config_.isDisabled then - onClick (config.onSelect realValue) + , case ( eventsAndValues.onSelect, config_.isDisabled ) of + ( Just onSelect_, False ) -> + onClick (onSelect_ realValue) - else - Attributes.none + _ -> + Attributes.none , class "Nri-RadioButton-HiddenRadioInput" , css [ position absolute diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index a3e5ce19..b5a7f12c 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -87,23 +87,33 @@ viewVanilla state = div [] [ RadioButton.view { label = "Cats" - , onSelect = Select , valueToString = identity } [ RadioButton.enabled , RadioButton.value "Felines" , RadioButton.name "radio-button-examples" , RadioButton.selectedValue state.selectedValue + , RadioButton.onSelect Select ] , RadioButton.view { label = "Dogs" - , onSelect = Select , valueToString = identity } [ RadioButton.enabled , RadioButton.value "Canines" , RadioButton.name "radio-button-examples" , RadioButton.selectedValue state.selectedValue + , RadioButton.onSelect Select + ] + , RadioButton.view + { label = "Robots" + , valueToString = identity + } + [ RadioButton.disabled + , RadioButton.value "Robots" + , RadioButton.name "radio-button-examples" + , RadioButton.selectedValue state.selectedValue + , RadioButton.onSelect Select ] ] From b73a135939e83a7daf23c43959d5dc93739d06fb Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Thu, 18 Nov 2021 17:54:29 -0500 Subject: [PATCH 11/67] intermezzo to make actual use of my don't-render-nonsense-radios pattern --- src/Nri/Ui/RadioButton/V3.elm | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 3ef74388..61f0c387 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -63,7 +63,7 @@ enabled = name : String -> Attribute value msg name name_ = Attribute emptyEventsAndValues <| - \config -> { config | name = name_ } + \config -> { config | name = Just name_ } {-| Sets the value of one radio button @@ -115,8 +115,8 @@ emptyEventsAndValues = {-| This is private. The public API only exposes `Attribute`. -} type alias Config = - { label : String - , name : String + { label : Maybe String + , name : Maybe String , isLocked : Bool , isDisabled : Bool , showPennant : Bool @@ -125,8 +125,8 @@ type alias Config = emptyConfig : Config emptyConfig = - { label = "" - , name = "" + { label = Nothing + , name = Nothing , isLocked = False , isDisabled = False , showPennant = False @@ -209,15 +209,15 @@ internalView config attributes = config_ = applyConfig attributes - unvalidatedRadioConfig : Maybe value + unvalidatedRadioConfig : ( Maybe value, Maybe String ) unvalidatedRadioConfig = - eventsAndValues.value + ( eventsAndValues.value, config_.name ) in case unvalidatedRadioConfig of - Just realValue -> + ( Just value_, Just name_ ) -> let id_ = - config_.name ++ "-" ++ dasherize (toLower (config.valueToString realValue)) + name_ ++ "-" ++ dasherize (toLower (config.valueToString value_)) in Html.span [ id (id_ ++ "-container") @@ -236,14 +236,14 @@ internalView config attributes = ] ] ] - [ radio config_.name - (config.valueToString realValue) + [ radio name_ + (config.valueToString value_) isChecked [ id id_ , Widget.disabled (config.isLocked || config_.isDisabled) , case ( eventsAndValues.onSelect, config_.isDisabled ) of ( Just onSelect_, False ) -> - onClick (onSelect_ realValue) + onClick (onSelect_ value_) _ -> Attributes.none @@ -315,7 +315,7 @@ internalView config attributes = ] ] - Nothing -> + _ -> text "no radio button here" From 83486fbf2f97236c67de4d07266956c328283f4e Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Thu, 18 Nov 2021 18:14:44 -0500 Subject: [PATCH 12/67] move valueToString into the list API --- src/Nri/Ui/RadioButton/V3.elm | 30 ++++++++-------- styleguide-app/Examples/RadioButton.elm | 47 +++++++++++++++++-------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 61f0c387..7e4123c3 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -1,9 +1,9 @@ module Nri.Ui.RadioButton.V3 exposing ( view , disabled, enabled - , value, selectedValue - , name + , value, selectedValue, valueToString , onSelect + , name ) {-| Changes from V2: @@ -12,8 +12,8 @@ module Nri.Ui.RadioButton.V3 exposing @docs view @docs disabled, enabled -@docs value, selectedValue -@docs docs onSelect +@docs value, selectedValue, valueToString +@docs onSelect @docs name -} @@ -87,6 +87,11 @@ onSelect onSelect_ = Attribute { emptyEventsAndValues | onSelect = Just onSelect_ } identity +valueToString : (value -> String) -> Attribute value msg +valueToString valueToString_ = + Attribute { emptyEventsAndValues | valueToString = Just valueToString_ } identity + + {-| Customizations for the RadioButton. -} type Attribute value msg @@ -169,7 +174,6 @@ If used in a group, all radio buttons in the group should have the same name att -} view : { label : String - , valueToString : value -> String } -> List (Attribute value msg) -> Html msg @@ -178,21 +182,19 @@ view config = { label = config.label , isLocked = False , premiumMsg = Nothing - , valueToString = config.valueToString , showPennant = False } -type alias InternalConfig value msg = +type alias InternalConfig msg = { label : String , isLocked : Bool , premiumMsg : Maybe msg - , valueToString : value -> String , showPennant : Bool } -internalView : InternalConfig value msg -> List (Attribute value msg) -> Html msg +internalView : InternalConfig msg -> List (Attribute value msg) -> Html msg internalView config attributes = let isChecked = @@ -209,15 +211,15 @@ internalView config attributes = config_ = applyConfig attributes - unvalidatedRadioConfig : ( Maybe value, Maybe String ) + unvalidatedRadioConfig : ( Maybe value, Maybe String, Maybe (value -> String) ) unvalidatedRadioConfig = - ( eventsAndValues.value, config_.name ) + ( eventsAndValues.value, config_.name, eventsAndValues.valueToString ) in case unvalidatedRadioConfig of - ( Just value_, Just name_ ) -> + ( Just value_, Just name_, Just valueToString_ ) -> let id_ = - name_ ++ "-" ++ dasherize (toLower (config.valueToString value_)) + name_ ++ "-" ++ dasherize (toLower (valueToString_ value_)) in Html.span [ id (id_ ++ "-container") @@ -237,7 +239,7 @@ internalView config attributes = ] ] [ radio name_ - (config.valueToString value_) + (valueToString_ value_) isChecked [ id id_ , Widget.disabled (config.isLocked || config_.isDisabled) diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index b5a7f12c..9b33ca39 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -86,41 +86,60 @@ viewVanilla : State -> Html Msg viewVanilla state = div [] [ RadioButton.view - { label = "Cats" - , valueToString = identity + { label = selectionToString Dogs } [ RadioButton.enabled - , RadioButton.value "Felines" - , RadioButton.name "radio-button-examples" + , RadioButton.value Dogs + , RadioButton.name "pets" , RadioButton.selectedValue state.selectedValue , RadioButton.onSelect Select + , RadioButton.valueToString selectionToString ] , RadioButton.view - { label = "Dogs" - , valueToString = identity + { label = selectionToString Cats } [ RadioButton.enabled - , RadioButton.value "Canines" - , RadioButton.name "radio-button-examples" + , RadioButton.value Cats + , RadioButton.name "pets" , RadioButton.selectedValue state.selectedValue , RadioButton.onSelect Select + , RadioButton.valueToString selectionToString ] , RadioButton.view - { label = "Robots" - , valueToString = identity + { label = selectionToString Robots } [ RadioButton.disabled - , RadioButton.value "Robots" - , RadioButton.name "radio-button-examples" + , RadioButton.value Robots + , RadioButton.name "pets" , RadioButton.selectedValue state.selectedValue , RadioButton.onSelect Select + , RadioButton.valueToString selectionToString ] ] +type Selection + = Dogs + | Cats + | Robots + + +selectionToString : Selection -> String +selectionToString selection = + case selection of + Dogs -> + "Dogs" + + Cats -> + "Cats" + + Robots -> + "Robots" + + {-| -} type alias State = - { selectedValue : Maybe String + { selectedValue : Maybe Selection , modal : Modal.Model , premiumControl : Control PremiumConfig } @@ -157,7 +176,7 @@ type Msg = OpenModal String | ModalMsg Modal.Msg | CloseModal - | Select String + | Select Selection | SetPremiumControl (Control PremiumConfig) | Focus String | Focused (Result Dom.Error ()) From 710576115bdaeb20333c9a152cef509e33bead73 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Thu, 18 Nov 2021 18:18:50 -0500 Subject: [PATCH 13/67] required label attribute at the begininning, in keeping with other APIs --- src/Nri/Ui/RadioButton/V3.elm | 13 +++++-------- styleguide-app/Examples/RadioButton.elm | 9 +++------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 7e4123c3..826db4ca 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -120,8 +120,7 @@ emptyEventsAndValues = {-| This is private. The public API only exposes `Attribute`. -} type alias Config = - { label : Maybe String - , name : Maybe String + { name : Maybe String , isLocked : Bool , isDisabled : Bool , showPennant : Bool @@ -130,8 +129,7 @@ type alias Config = emptyConfig : Config emptyConfig = - { label = Nothing - , name = Nothing + { name = Nothing , isLocked = False , isDisabled = False , showPennant = False @@ -173,13 +171,12 @@ applyEvents = If used in a group, all radio buttons in the group should have the same name attribute. -} view : - { label : String - } + String -> List (Attribute value msg) -> Html msg -view config = +view label = internalView - { label = config.label + { label = label , isLocked = False , premiumMsg = Nothing , showPennant = False diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index 9b33ca39..4e05ed70 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -86,8 +86,7 @@ viewVanilla : State -> Html Msg viewVanilla state = div [] [ RadioButton.view - { label = selectionToString Dogs - } + (selectionToString Dogs) [ RadioButton.enabled , RadioButton.value Dogs , RadioButton.name "pets" @@ -96,8 +95,7 @@ viewVanilla state = , RadioButton.valueToString selectionToString ] , RadioButton.view - { label = selectionToString Cats - } + (selectionToString Cats) [ RadioButton.enabled , RadioButton.value Cats , RadioButton.name "pets" @@ -106,8 +104,7 @@ viewVanilla state = , RadioButton.valueToString selectionToString ] , RadioButton.view - { label = selectionToString Robots - } + (selectionToString Robots) [ RadioButton.disabled , RadioButton.value Robots , RadioButton.name "pets" From e18a25154ba8893a11fedd0da7b83389910fb675 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Thu, 18 Nov 2021 18:44:51 -0500 Subject: [PATCH 14/67] add back in premiumm as an attribute, requiring all the premium locking info at once --- src/Nri/Ui/RadioButton/V3.elm | 38 +++++++++++++++++++++---- styleguide-app/Examples/RadioButton.elm | 5 +++- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 826db4ca..e8282271 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -4,6 +4,7 @@ module Nri.Ui.RadioButton.V3 exposing , value, selectedValue, valueToString , onSelect , name + , premium ) {-| Changes from V2: @@ -15,6 +16,7 @@ module Nri.Ui.RadioButton.V3 exposing @docs value, selectedValue, valueToString @docs onSelect @docs name +@docs premium -} @@ -92,6 +94,18 @@ valueToString valueToString_ = Attribute { emptyEventsAndValues | valueToString = Just valueToString_ } identity +{-| Lock the content if the teacher does not have premium access +-} +premium : { teacherPremiumLevel : PremiumLevel, contentPremiumLevel : PremiumLevel } -> Attribute value msg +premium { teacherPremiumLevel, contentPremiumLevel } = + Attribute emptyEventsAndValues <| + \config -> + { config + | teacherPremiumLevel = Just teacherPremiumLevel + , contentPremiumLevel = Just contentPremiumLevel + } + + {-| Customizations for the RadioButton. -} type Attribute value msg @@ -121,7 +135,8 @@ emptyEventsAndValues = -} type alias Config = { name : Maybe String - , isLocked : Bool + , teacherPremiumLevel : Maybe PremiumLevel + , contentPremiumLevel : Maybe PremiumLevel , isDisabled : Bool , showPennant : Bool } @@ -130,7 +145,8 @@ type alias Config = emptyConfig : Config emptyConfig = { name = Nothing - , isLocked = False + , teacherPremiumLevel = Nothing + , contentPremiumLevel = Nothing , isDisabled = False , showPennant = False } @@ -177,7 +193,6 @@ view : view label = internalView { label = label - , isLocked = False , premiumMsg = Nothing , showPennant = False } @@ -185,7 +200,6 @@ view label = type alias InternalConfig msg = { label : String - , isLocked : Bool , premiumMsg : Maybe msg , showPennant : Bool } @@ -211,6 +225,18 @@ internalView config attributes = unvalidatedRadioConfig : ( Maybe value, Maybe String, Maybe (value -> String) ) unvalidatedRadioConfig = ( eventsAndValues.value, config_.name, eventsAndValues.valueToString ) + + isLocked : Bool + isLocked = + case ( config_.contentPremiumLevel, config_.teacherPremiumLevel ) of + ( Just contentPremiumLevel, Just teacherPremiumLevel ) -> + not <| + PremiumLevel.allowedFor + contentPremiumLevel + teacherPremiumLevel + + _ -> + False in case unvalidatedRadioConfig of ( Just value_, Just name_, Just valueToString_ ) -> @@ -239,7 +265,7 @@ internalView config attributes = (valueToString_ value_) isChecked [ id id_ - , Widget.disabled (config.isLocked || config_.isDisabled) + , Widget.disabled (isLocked || config_.isDisabled) , case ( eventsAndValues.onSelect, config_.isDisabled ) of ( Just onSelect_, False ) -> onClick (onSelect_ value_) @@ -281,7 +307,7 @@ internalView config attributes = ] ] [ radioInputIcon - { isLocked = config.isLocked + { isLocked = isLocked , isDisabled = config_.isDisabled , isChecked = isChecked } diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index 4e05ed70..ac833aba 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -105,7 +105,10 @@ viewVanilla state = ] , RadioButton.view (selectionToString Robots) - [ RadioButton.disabled + [ RadioButton.premium + { teacherPremiumLevel = PremiumLevel.Premium + , contentPremiumLevel = PremiumLevel.PremiumWithWriting + } , RadioButton.value Robots , RadioButton.name "pets" , RadioButton.selectedValue state.selectedValue From f4d74032d47b5e979984f1802e9f6dbff6badf8f Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Thu, 18 Nov 2021 19:07:21 -0500 Subject: [PATCH 15/67] add back in showPennant --- src/Nri/Ui/RadioButton/V3.elm | 48 ++++++++++++++----------- styleguide-app/Examples/RadioButton.elm | 1 + 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index e8282271..76110c4f 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -4,7 +4,7 @@ module Nri.Ui.RadioButton.V3 exposing , value, selectedValue, valueToString , onSelect , name - , premium + , premium, showPennant ) {-| Changes from V2: @@ -16,7 +16,7 @@ module Nri.Ui.RadioButton.V3 exposing @docs value, selectedValue, valueToString @docs onSelect @docs name -@docs premium +@docs premium, showPennant -} @@ -35,7 +35,7 @@ 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.Html.V3 exposing (viewJust) import Nri.Ui.Pennant.V2 as Pennant import Nri.Ui.Svg.V1 exposing (Svg, fromHtml) import String exposing (toLower) @@ -106,6 +106,13 @@ premium { teacherPremiumLevel, contentPremiumLevel } = } +{-| Show the pennant and attach this onClick handler +-} +showPennant : msg -> Attribute value msg +showPennant premiumMsg = + Attribute { emptyEventsAndValues | premiumMsg = Just premiumMsg } identity + + {-| Customizations for the RadioButton. -} type Attribute value msg @@ -192,20 +199,14 @@ view : -> Html msg view label = internalView - { label = label - , premiumMsg = Nothing - , showPennant = False - } + { label = label } -type alias InternalConfig msg = - { label : String - , premiumMsg : Maybe msg - , showPennant : Bool - } +type alias InternalConfig = + { label : String } -internalView : InternalConfig msg -> List (Attribute value msg) -> Html msg +internalView : InternalConfig -> List (Attribute value msg) -> Html msg internalView config attributes = let isChecked = @@ -237,6 +238,14 @@ internalView config attributes = _ -> False + + showPennant_ = + case eventsAndValues.premiumMsg of + Just _ -> + True + + _ -> + False in case unvalidatedRadioConfig of ( Just value_, Just name_, Just valueToString_ ) -> @@ -246,7 +255,7 @@ internalView config attributes = in Html.span [ id (id_ ++ "-container") - , classList [ ( "Nri-RadioButton-PremiumClass", config.showPennant ) ] + , classList [ ( "Nri-RadioButton-PremiumClass", showPennant_ ) ] , css [ position relative , marginLeft (px -4) @@ -312,7 +321,7 @@ internalView config attributes = , isChecked = isChecked } , span - (if config.showPennant then + (if showPennant_ then [ css [ displayFlex , alignItems center @@ -324,18 +333,17 @@ internalView config attributes = [ css [ verticalAlign middle ] ] ) [ Html.text config.label - , viewIf - (\() -> + , viewJust + (\premiumMsg -> ClickableSvg.button "Premium" Pennant.premiumFlag - [ Maybe.map ClickableSvg.onClick config.premiumMsg - |> Maybe.withDefault (ClickableSvg.custom []) + [ ClickableSvg.onClick premiumMsg , ClickableSvg.exactWidth 26 , ClickableSvg.exactHeight 24 , ClickableSvg.css [ marginLeft (px 8) ] ] ) - config.showPennant + eventsAndValues.premiumMsg ] ] ] diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index ac833aba..47bc29f9 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -114,6 +114,7 @@ viewVanilla state = , RadioButton.selectedValue state.selectedValue , RadioButton.onSelect Select , RadioButton.valueToString selectionToString + , RadioButton.showPennant <| OpenModal "" ] ] From 598daf604fd0ab3848c65e5bf6c65db365281dad Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 08:58:22 -0500 Subject: [PATCH 16/67] add doc messages --- src/Nri/Ui/RadioButton/V3.elm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 76110c4f..9ca57291 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -82,13 +82,16 @@ selectedValue value_ = Attribute { emptyEventsAndValues | selectedValue = value_ } identity -{-| What message +{-| Fire a message parameterized by the value type when selecting a radio option -} onSelect : (value -> msg) -> Attribute value msg onSelect onSelect_ = Attribute { emptyEventsAndValues | onSelect = Just onSelect_ } identity +{-| Since s transact in strings we need to be able to give every radio button in a group +a unique string value. This function should be the same for every RadioButton in a group. +-} valueToString : (value -> String) -> Attribute value msg valueToString valueToString_ = Attribute { emptyEventsAndValues | valueToString = Just valueToString_ } identity From 480e46c37c5db87fc0614f611865795b706b59c5 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 10:05:16 -0500 Subject: [PATCH 17/67] unstyled but functional disclosure content --- src/Nri/Ui/RadioButton/V3.elm | 41 ++++++++++++++++++++++++- styleguide-app/Examples/RadioButton.elm | 13 ++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 9ca57291..d04cf382 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -5,6 +5,7 @@ module Nri.Ui.RadioButton.V3 exposing , onSelect , name , premium, showPennant + , disclosure ) {-| Changes from V2: @@ -17,6 +18,7 @@ module Nri.Ui.RadioButton.V3 exposing @docs onSelect @docs name @docs premium, showPennant +@docs disclosure -} @@ -116,6 +118,13 @@ showPennant premiumMsg = Attribute { emptyEventsAndValues | premiumMsg = Just premiumMsg } identity +{-| Content that shows when this RadioButton is selected +-} +disclosure : List (Html msg) -> Attribute value msg +disclosure childNodes = + Attribute { emptyEventsAndValues | disclosedContent = childNodes } identity + + {-| Customizations for the RadioButton. -} type Attribute value msg @@ -128,6 +137,7 @@ type alias EventsAndValues value msg = , onSelect : Maybe (value -> msg) , valueToString : Maybe (value -> String) , premiumMsg : Maybe msg + , disclosedContent : List (Html msg) } @@ -138,6 +148,7 @@ emptyEventsAndValues = , onSelect = Nothing , valueToString = Nothing , premiumMsg = Nothing + , disclosedContent = [] } @@ -188,6 +199,7 @@ applyEvents = , onSelect = orExisting .onSelect eventsAndValues existing , valueToString = orExisting .valueToString eventsAndValues existing , premiumMsg = orExisting .premiumMsg eventsAndValues existing + , disclosedContent = eventsAndValues.disclosedContent ++ existing.disclosedContent } ) emptyEventsAndValues @@ -209,6 +221,13 @@ type alias InternalConfig = { label : String } +maybeAttr : (a -> Html.Attribute msg) -> Maybe a -> Html.Attribute msg +maybeAttr attr maybeValue = + maybeValue + |> Maybe.map attr + |> Maybe.withDefault Attributes.none + + internalView : InternalConfig -> List (Attribute value msg) -> Html msg internalView config attributes = let @@ -254,7 +273,23 @@ internalView config attributes = ( Just value_, Just name_, Just valueToString_ ) -> let id_ = - name_ ++ "-" ++ dasherize (toLower (valueToString_ value_)) + name_ ++ "-" ++ (dasherize <| toLower <| valueToString_ value_) + + disclosureIdAndElement : Maybe ( String, Html msg ) + disclosureIdAndElement = + case ( eventsAndValues.disclosedContent, isChecked ) of + ( [], _ ) -> + Nothing + + ( _, False ) -> + Nothing + + ( (_ :: _) as childNodes, True ) -> + let + disclosureId = + id_ ++ "-disclosure-content" + in + Just <| ( disclosureId, div [ id disclosureId ] childNodes ) in Html.span [ id (id_ ++ "-container") @@ -285,6 +320,7 @@ internalView config attributes = _ -> Attributes.none , class "Nri-RadioButton-HiddenRadioInput" + , maybeAttr (Tuple.first >> Aria.controls) disclosureIdAndElement , css [ position absolute , top (px 4) @@ -349,6 +385,9 @@ internalView config attributes = eventsAndValues.premiumMsg ] ] + , viewJust + Tuple.second + disclosureIdAndElement ] _ -> diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index 47bc29f9..ac5c27a5 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -102,6 +102,7 @@ viewVanilla state = , RadioButton.selectedValue state.selectedValue , RadioButton.onSelect Select , RadioButton.valueToString selectionToString + , RadioButton.disclosure [ text "more cat info" ] ] , RadioButton.view (selectionToString Robots) @@ -116,6 +117,18 @@ viewVanilla state = , RadioButton.valueToString selectionToString , RadioButton.showPennant <| OpenModal "" ] + , RadioButton.view + (selectionToString Robots) + [ RadioButton.premium + { teacherPremiumLevel = PremiumLevel.Premium + , contentPremiumLevel = PremiumLevel.PremiumWithWriting + } + , RadioButton.name "pets" + , RadioButton.selectedValue state.selectedValue + , RadioButton.onSelect Select + , RadioButton.valueToString selectionToString + , RadioButton.showPennant <| OpenModal "" + ] ] From 9abfa3bc68843b1873788c5c15e5c873743ef7af Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 10:12:22 -0500 Subject: [PATCH 18/67] add bullet to changelog about disclosure function --- src/Nri/Ui/RadioButton/V3.elm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index d04cf382..eedf7efe 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -11,6 +11,7 @@ module Nri.Ui.RadioButton.V3 exposing {-| Changes from V2: - list based API instead of record based + - add disclosure to show rich content when the radio is selected @docs view @docs disabled, enabled From 64409702acc6e70d2ad5cb0c7fc309fc8462571f Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 10:13:18 -0500 Subject: [PATCH 19/67] remove obviated exteranl/internal view distinction; clarify when view does not render in doc comment --- src/Nri/Ui/RadioButton/V3.elm | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index eedf7efe..bc3ff65c 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -206,22 +206,6 @@ applyEvents = emptyEventsAndValues -{-| View a single radio button. -If used in a group, all radio buttons in the group should have the same name attribute. --} -view : - String - -> List (Attribute value msg) - -> Html msg -view label = - internalView - { label = label } - - -type alias InternalConfig = - { label : String } - - maybeAttr : (a -> Html.Attribute msg) -> Maybe a -> Html.Attribute msg maybeAttr attr maybeValue = maybeValue @@ -229,8 +213,11 @@ maybeAttr attr maybeValue = |> Maybe.withDefault Attributes.none -internalView : InternalConfig -> List (Attribute value msg) -> Html msg -internalView config attributes = +{-| View a single radio button. +Renders nothing if the attributes list does not contain value, name, and valueToString. +-} +view : String -> List (Attribute value msg) -> Html msg +view label attributes = let isChecked = -- why not guard and make sure neither is Nothing? @@ -372,7 +359,7 @@ internalView config attributes = else [ css [ verticalAlign middle ] ] ) - [ Html.text config.label + [ Html.text label , viewJust (\premiumMsg -> ClickableSvg.button "Premium" From 7a5cfbc3006460a4183625646f8c85a198470dac Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 12:01:42 -0500 Subject: [PATCH 20/67] add describedBy and a no-op attribute --- src/Nri/Ui/RadioButton/V3.elm | 30 +++++++++++++++++++++++-- styleguide-app/Examples/RadioButton.elm | 18 +++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index bc3ff65c..7dce40fd 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -6,6 +6,8 @@ module Nri.Ui.RadioButton.V3 exposing , name , premium, showPennant , disclosure + , describedBy + , none ) {-| Changes from V2: @@ -20,6 +22,7 @@ module Nri.Ui.RadioButton.V3 exposing @docs name @docs premium, showPennant @docs disclosure +@docs describedBy -} @@ -27,7 +30,7 @@ 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 Css as Css exposing (..) import Css.Global import Html.Styled as Html import Html.Styled.Attributes exposing (..) @@ -126,6 +129,21 @@ disclosure childNodes = Attribute { emptyEventsAndValues | disclosedContent = childNodes } identity +{-| Set the Aria describedby attribute if given a non-empty list of IDs +-} +describedBy : List String -> Attribute value msg +describedBy ids = + Attribute emptyEventsAndValues <| + \config -> { config | describedByIds = ids } + + +{-| Has no effect; useful for conditionals and cases +-} +none : Attribute value msg +none = + Attribute emptyEventsAndValues identity + + {-| Customizations for the RadioButton. -} type Attribute value msg @@ -161,6 +179,7 @@ type alias Config = , contentPremiumLevel : Maybe PremiumLevel , isDisabled : Bool , showPennant : Bool + , describedByIds : List String } @@ -171,6 +190,7 @@ emptyConfig = , contentPremiumLevel = Nothing , isDisabled = False , showPennant = False + , describedByIds = [] } @@ -309,6 +329,12 @@ view label attributes = Attributes.none , class "Nri-RadioButton-HiddenRadioInput" , maybeAttr (Tuple.first >> Aria.controls) disclosureIdAndElement + , case config_.describedByIds of + (_ :: _) as describedByIds -> + Aria.describedBy describedByIds + + [] -> + Attributes.none , css [ position absolute , top (px 4) @@ -336,7 +362,7 @@ view label attributes = , Fonts.baseFont , Css.property "font-weight" "600" , position relative - , outline none + , outline Css.none , margin zero , display inlineBlock , color Colors.navy diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index ac5c27a5..89a968b5 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -17,7 +17,7 @@ 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 Html.Styled.Attributes as Attributes exposing (css) import KeyboardSupport exposing (Direction(..), Key(..)) import Nri.Ui.Button.V10 as Button import Nri.Ui.Data.PremiumLevel as PremiumLevel exposing (PremiumLevel) @@ -93,6 +93,8 @@ viewVanilla state = , RadioButton.selectedValue state.selectedValue , RadioButton.onSelect Select , RadioButton.valueToString selectionToString + , RadioButton.describedBy + [ "dogs-description" ] ] , RadioButton.view (selectionToString Cats) @@ -102,7 +104,16 @@ viewVanilla state = , RadioButton.selectedValue state.selectedValue , RadioButton.onSelect Select , RadioButton.valueToString selectionToString - , RadioButton.disclosure [ text "more cat info" ] + , if state.selectedValue == Just Cats then + RadioButton.describedBy [ "cats-description" ] + + else + RadioButton.none + , RadioButton.disclosure + [ span + [ Attributes.id "cats-description" ] + [ text "Cats kind of do their own thing" ] + ] ] , RadioButton.view (selectionToString Robots) @@ -129,6 +140,9 @@ viewVanilla state = , RadioButton.valueToString selectionToString , RadioButton.showPennant <| OpenModal "" ] + , p + [ Attributes.id "dogs-description" ] + [ text "Dogs are gregarious" ] ] From 1578dea07425ab844aa9ee438de3f8b00dd7d974 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 13:39:52 -0500 Subject: [PATCH 21/67] add doc for none --- src/Nri/Ui/RadioButton/V3.elm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 7dce40fd..a295c7f7 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -23,6 +23,7 @@ module Nri.Ui.RadioButton.V3 exposing @docs premium, showPennant @docs disclosure @docs describedBy +@docs none -} From 0d47f37ab9561eadc0dbe8c0edc2ea6344064999 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 16:22:32 -0500 Subject: [PATCH 22/67] make an explicit choice to display inline or in a block thing. use view helpers for those since branching inside of view was very unwieldy --- src/Nri/Ui/RadioButton/V3.elm | 488 +++++++++++++++++------- styleguide-app/Examples/RadioButton.elm | 8 +- 2 files changed, 362 insertions(+), 134 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index a295c7f7..fb4eef2b 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -5,7 +5,7 @@ module Nri.Ui.RadioButton.V3 exposing , onSelect , name , premium, showPennant - , disclosure + , disclosure, block, inline , describedBy , none ) @@ -21,7 +21,7 @@ module Nri.Ui.RadioButton.V3 exposing @docs onSelect @docs name @docs premium, showPennant -@docs disclosure +@docs disclosure, block, inline @docs describedBy @docs none @@ -145,6 +145,29 @@ none = Attribute emptyEventsAndValues identity +{-| Displays the radio button as a gray block with a small black label. +Designed to have the disclosure content be more prominent +-} +block : Attribute value msg +block = + Attribute emptyEventsAndValues <| + \config -> { config | display = GrayBlock } + + +{-| Displays the radio button as an inline span with a large blue label. +Designed to have the disclosure content be inline if present at all +-} +inline : Attribute value msg +inline = + Attribute emptyEventsAndValues <| + \config -> { config | display = Inline } + + +type Display + = GrayBlock + | Inline + + {-| Customizations for the RadioButton. -} type Attribute value msg @@ -181,6 +204,7 @@ type alias Config = , isDisabled : Bool , showPennant : Bool , describedByIds : List String + , display : Display } @@ -192,6 +216,7 @@ emptyConfig = , isDisabled = False , showPennant = False , describedByIds = [] + , display = Inline } @@ -240,12 +265,6 @@ Renders nothing if the attributes list does not contain value, name, and valueTo view : String -> List (Attribute value msg) -> Html msg view label attributes = let - isChecked = - -- why not guard and make sure neither is Nothing? - -- Because if value is Nothing we do not render a radio - eventsAndValues.selectedValue - == eventsAndValues.value - eventsAndValues : EventsAndValues value msg eventsAndValues = applyEvents attributes @@ -257,10 +276,52 @@ view label attributes = unvalidatedRadioConfig : ( Maybe value, Maybe String, Maybe (value -> String) ) unvalidatedRadioConfig = ( eventsAndValues.value, config_.name, eventsAndValues.valueToString ) + in + case unvalidatedRadioConfig of + ( Just value_, Just name_, Just valueToString_ ) -> + let + internalConfig = + { value = value_ + , name = name_ + , valueToString = valueToString_ + , eventsAndValues = eventsAndValues + , config = config_ + , label = label + } + in + case config_.display of + Inline -> + viewInline internalConfig + + GrayBlock -> + viewBlock internalConfig + + _ -> + text "no radio button here" + + +type alias InternalConfig value msg = + { value : value + , name : String + , valueToString : value -> String + , eventsAndValues : EventsAndValues value msg + , config : Config + , label : String + } + + +viewBlock : InternalConfig value msg -> Html msg +viewBlock internalConfig = + let + isChecked = + -- why not guard and make sure neither is Nothing? + -- Because if value is Nothing we do not render a radio + internalConfig.eventsAndValues.selectedValue + == internalConfig.eventsAndValues.value isLocked : Bool isLocked = - case ( config_.contentPremiumLevel, config_.teacherPremiumLevel ) of + case ( internalConfig.config.contentPremiumLevel, internalConfig.config.teacherPremiumLevel ) of ( Just contentPremiumLevel, Just teacherPremiumLevel ) -> not <| PremiumLevel.allowedFor @@ -271,142 +332,305 @@ view label attributes = False showPennant_ = - case eventsAndValues.premiumMsg of + case internalConfig.eventsAndValues.premiumMsg of Just _ -> True _ -> False + + id_ = + internalConfig.name ++ "-" ++ (dasherize <| toLower <| internalConfig.valueToString internalConfig.value) + + disclosureIdAndElement : Maybe ( String, Html msg ) + disclosureIdAndElement = + case ( internalConfig.eventsAndValues.disclosedContent, isChecked ) of + ( [], _ ) -> + Nothing + + ( _, False ) -> + Nothing + + ( (_ :: _) as childNodes, True ) -> + let + disclosureId = + id_ ++ "-disclosure-content" + in + Just <| ( disclosureId, div [ id disclosureId ] childNodes ) in - case unvalidatedRadioConfig of - ( Just value_, Just name_, Just valueToString_ ) -> - let - id_ = - name_ ++ "-" ++ (dasherize <| toLower <| valueToString_ value_) + Html.div + [ id (id_ ++ "-container") + , classList [ ( "Nri-RadioButton-PremiumClass", showPennant_ ) ] + , css + [ position relative + , display Css.block + , Css.height (px 34) + , Css.width <| pct 100 + , Css.backgroundColor Colors.gray96 + , padding <| Css.px 20 + , marginBottom <| Css.px 10 + , borderRadius <| Css.px 8 + ] + ] + [ radio internalConfig.name + (internalConfig.valueToString internalConfig.value) + isChecked + [ id id_ + , Widget.disabled (isLocked || internalConfig.config.isDisabled) + , case ( internalConfig.eventsAndValues.onSelect, internalConfig.config.isDisabled ) of + ( Just onSelect_, False ) -> + onClick (onSelect_ internalConfig.value) - disclosureIdAndElement : Maybe ( String, Html msg ) - disclosureIdAndElement = - case ( eventsAndValues.disclosedContent, isChecked ) of - ( [], _ ) -> - Nothing + _ -> + Attributes.none + , class "Nri-RadioButton-HiddenRadioInput" + , maybeAttr (Tuple.first >> Aria.controls) disclosureIdAndElement + , case internalConfig.config.describedByIds of + (_ :: _) as describedByIds -> + Aria.describedBy describedByIds - ( _, False ) -> - Nothing - - ( (_ :: _) as childNodes, True ) -> - let - disclosureId = - id_ ++ "-disclosure-content" - in - Just <| ( disclosureId, div [ id disclosureId ] childNodes ) - in - Html.span - [ id (id_ ++ "-container") - , classList [ ( "Nri-RadioButton-PremiumClass", showPennant_ ) ] - , css - [ position relative - , marginLeft (px -4) - , display inlineBlock - , Css.height (px 34) - , pseudoClass "focus-within" - [ Css.Global.descendants - [ Css.Global.class "Nri-RadioButton-RadioButtonIcon" - [ borderColor (rgb 0 95 204) - ] - ] - ] - ] - ] - [ radio name_ - (valueToString_ value_) - isChecked - [ id id_ - , Widget.disabled (isLocked || config_.isDisabled) - , case ( eventsAndValues.onSelect, config_.isDisabled ) of - ( Just onSelect_, False ) -> - onClick (onSelect_ value_) - - _ -> - Attributes.none - , class "Nri-RadioButton-HiddenRadioInput" - , maybeAttr (Tuple.first >> Aria.controls) disclosureIdAndElement - , case config_.describedByIds of - (_ :: _) as describedByIds -> - Aria.describedBy describedByIds - - [] -> - Attributes.none - , css - [ position absolute - , top (px 4) - , left (px 4) - , opacity zero - ] - ] - , Html.label - [ for id_ - , classList - [ ( "Nri-RadioButton-RadioButton", True ) - , ( "Nri-RadioButton-RadioButtonChecked", isChecked ) - ] - , css - [ padding4 (px 6) 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 Css.none - , margin zero - , display inlineBlock - , color Colors.navy - ] - ] - [ radioInputIcon - { isLocked = isLocked - , isDisabled = config_.isDisabled - , isChecked = isChecked - } - , span - (if showPennant_ then - [ css - [ displayFlex - , alignItems center - , Css.height (px 20) - ] - ] - - else - [ css [ verticalAlign middle ] ] - ) - [ Html.text label - , viewJust - (\premiumMsg -> - ClickableSvg.button "Premium" - Pennant.premiumFlag - [ ClickableSvg.onClick premiumMsg - , ClickableSvg.exactWidth 26 - , ClickableSvg.exactHeight 24 - , ClickableSvg.css [ marginLeft (px 8) ] + [] -> + Attributes.none + , css + [ position absolute + , top (px 4) + , left (px 4) + , opacity zero + , pseudoClass "focus" + [ Css.Global.adjacentSiblings + [ Css.Global.everything + [ Css.Global.descendants + [ Css.Global.class "Nri-RadioButton-RadioButtonIcon" + [ borderColor (rgb 0 95 204) ] - ) - eventsAndValues.premiumMsg + ] + ] ] ] - , viewJust - Tuple.second - disclosureIdAndElement ] + ] + , Html.label + [ for id_ + , classList + [ ( "Nri-RadioButton-RadioButton", True ) + , ( "Nri-RadioButton-RadioButtonChecked", isChecked ) + ] + , css <| + [ position relative + , outline Css.none + , margin zero + , Fonts.baseFont + , if internalConfig.config.isDisabled then + Css.batch + [ color Colors.gray45 + , cursor notAllowed + ] - _ -> - text "no radio button here" + else + cursor pointer + , padding4 zero zero zero (px 40) + ] + ] + [ radioInputIcon + { isLocked = isLocked + , isDisabled = internalConfig.config.isDisabled + , isChecked = isChecked + } + , span + (if showPennant_ then + [ css + [ display inlineFlex + , alignItems center + , Css.height (px 20) + ] + ] + + else + [ css [ verticalAlign middle ] ] + ) + [ Html.text internalConfig.label + , viewJust + (\premiumMsg -> + ClickableSvg.button "Premium" + Pennant.premiumFlag + [ ClickableSvg.onClick premiumMsg + , ClickableSvg.exactWidth 26 + , ClickableSvg.exactHeight 24 + , ClickableSvg.css [ marginLeft (px 8) ] + ] + ) + internalConfig.eventsAndValues.premiumMsg + ] + ] + , viewJust + Tuple.second + disclosureIdAndElement + ] + + +viewInline : InternalConfig value msg -> Html msg +viewInline internalConfig = + let + isChecked = + -- why not guard and make sure neither is Nothing? + -- Because if value is Nothing we do not render a radio + internalConfig.eventsAndValues.selectedValue + == internalConfig.eventsAndValues.value + + isLocked : Bool + isLocked = + case ( internalConfig.config.contentPremiumLevel, internalConfig.config.teacherPremiumLevel ) of + ( Just contentPremiumLevel, Just teacherPremiumLevel ) -> + not <| + PremiumLevel.allowedFor + contentPremiumLevel + teacherPremiumLevel + + _ -> + False + + showPennant_ = + case internalConfig.eventsAndValues.premiumMsg of + Just _ -> + True + + _ -> + False + + id_ = + internalConfig.name ++ "-" ++ (dasherize <| toLower <| internalConfig.valueToString internalConfig.value) + + disclosureIdAndElement : Maybe ( String, Html msg ) + disclosureIdAndElement = + case ( internalConfig.eventsAndValues.disclosedContent, isChecked ) of + ( [], _ ) -> + Nothing + + ( _, False ) -> + Nothing + + ( (_ :: _) as childNodes, True ) -> + let + disclosureId = + id_ ++ "-disclosure-content" + in + Just <| ( disclosureId, div [ id disclosureId ] childNodes ) + in + Html.span + [ id (id_ ++ "-container") + , classList [ ( "Nri-RadioButton-PremiumClass", showPennant_ ) ] + , css + [ position relative + , marginLeft (px -4) + , display inlineBlock + , Css.height (px 34) + , pseudoClass "focus-within" + [ Css.Global.descendants + [ Css.Global.class "Nri-RadioButton-RadioButtonIcon" + [ borderColor (rgb 0 95 204) + ] + ] + ] + ] + ] + [ radio internalConfig.name + (internalConfig.valueToString internalConfig.value) + isChecked + [ id id_ + , Widget.disabled (isLocked || internalConfig.config.isDisabled) + , case ( internalConfig.eventsAndValues.onSelect, internalConfig.config.isDisabled ) of + ( Just onSelect_, False ) -> + onClick (onSelect_ internalConfig.value) + + _ -> + Attributes.none + , class "Nri-RadioButton-HiddenRadioInput" + , maybeAttr (Tuple.first >> Aria.controls) disclosureIdAndElement + , case internalConfig.config.describedByIds of + (_ :: _) as describedByIds -> + Aria.describedBy describedByIds + + [] -> + Attributes.none + , css + [ position absolute + , top (px 4) + , left (px 4) + , opacity zero + , pseudoClass "focus" + [ Css.Global.adjacentSiblings + [ Css.Global.everything + [ Css.Global.descendants + [ Css.Global.class "Nri-RadioButton-RadioButtonIcon" + [ borderColor (rgb 0 95 204) + ] + ] + ] + ] + ] + ] + ] + , Html.label + [ for id_ + , classList + [ ( "Nri-RadioButton-RadioButton", True ) + , ( "Nri-RadioButton-RadioButtonChecked", isChecked ) + ] + , css <| + [ position relative + , outline Css.none + , margin zero + , Fonts.baseFont + , if internalConfig.config.isDisabled then + Css.batch + [ color Colors.gray45 + , cursor notAllowed + ] + + else + cursor pointer + , padding4 (px 6) zero (px 4) (px 40) + , fontSize (px 15) + , Css.property "font-weight" "600" + , display inlineBlock + , color Colors.navy + ] + ] + [ radioInputIcon + { isLocked = isLocked + , isDisabled = internalConfig.config.isDisabled + , isChecked = isChecked + } + , span + (if showPennant_ then + [ css + [ display inlineFlex + , alignItems center + , Css.height (px 20) + ] + ] + + else + [ css [ verticalAlign middle ] ] + ) + [ Html.text internalConfig.label + , viewJust + (\premiumMsg -> + ClickableSvg.button "Premium" + Pennant.premiumFlag + [ ClickableSvg.onClick premiumMsg + , ClickableSvg.exactWidth 26 + , ClickableSvg.exactHeight 24 + , ClickableSvg.css [ marginLeft (px 8) ] + ] + ) + internalConfig.eventsAndValues.premiumMsg + ] + ] + , viewJust + Tuple.second + disclosureIdAndElement + ] radioInputIcon : diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index 89a968b5..16851c7c 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -95,6 +95,7 @@ viewVanilla state = , RadioButton.valueToString selectionToString , RadioButton.describedBy [ "dogs-description" ] + , RadioButton.block ] , RadioButton.view (selectionToString Cats) @@ -112,8 +113,9 @@ viewVanilla state = , RadioButton.disclosure [ span [ Attributes.id "cats-description" ] - [ text "Cats kind of do their own thing" ] + [ Text.mediumBody [ Text.plaintext "Cats kind of do their own thing" ] ] ] + , RadioButton.block ] , RadioButton.view (selectionToString Robots) @@ -127,6 +129,7 @@ viewVanilla state = , RadioButton.onSelect Select , RadioButton.valueToString selectionToString , RadioButton.showPennant <| OpenModal "" + , RadioButton.block ] , RadioButton.view (selectionToString Robots) @@ -138,7 +141,8 @@ viewVanilla state = , RadioButton.selectedValue state.selectedValue , RadioButton.onSelect Select , RadioButton.valueToString selectionToString - , RadioButton.showPennant <| OpenModal "" + , RadioButton.showPennant <| OpenModal "pets-robots" + , RadioButton.block ] , p [ Attributes.id "dogs-description" ] From 6d2085a27df59bb723ee4ecf677fbc6325ec0890 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 16:32:24 -0500 Subject: [PATCH 23/67] internal config helper --- src/Nri/Ui/RadioButton/V3.elm | 46 ++++++++++++++----------- styleguide-app/Examples/RadioButton.elm | 17 ++++++++- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index fb4eef2b..950a4ad3 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -269,27 +269,13 @@ view label attributes = eventsAndValues = applyEvents attributes - config_ : Config - config_ = + config : Config + config = applyConfig attributes - - unvalidatedRadioConfig : ( Maybe value, Maybe String, Maybe (value -> String) ) - unvalidatedRadioConfig = - ( eventsAndValues.value, config_.name, eventsAndValues.valueToString ) in - case unvalidatedRadioConfig of - ( Just value_, Just name_, Just valueToString_ ) -> - let - internalConfig = - { value = value_ - , name = name_ - , valueToString = valueToString_ - , eventsAndValues = eventsAndValues - , config = config_ - , label = label - } - in - case config_.display of + case makeInternalConfig label config eventsAndValues of + Just internalConfig -> + case config.display of Inline -> viewInline internalConfig @@ -301,15 +287,35 @@ view label attributes = type alias InternalConfig value msg = - { value : value + { -- user specified values + value : value , name : String , valueToString : value -> String , eventsAndValues : EventsAndValues value msg , config : Config , label : String + + -- TODO: computed values that both view helpers need } +makeInternalConfig : String -> Config -> EventsAndValues value msg -> Maybe (InternalConfig value msg) +makeInternalConfig label config eventsAndValues = + case ( eventsAndValues.value, config.name, eventsAndValues.valueToString ) of + ( Just value_, Just name_, Just valueToString_ ) -> + Just + { value = value_ + , name = name_ + , valueToString = valueToString_ + , eventsAndValues = eventsAndValues + , config = config + , label = label + } + + _ -> + Nothing + + viewBlock : InternalConfig value msg -> Html msg viewBlock internalConfig = let diff --git a/styleguide-app/Examples/RadioButton.elm b/styleguide-app/Examples/RadioButton.elm index 16851c7c..305ad11b 100644 --- a/styleguide-app/Examples/RadioButton.elm +++ b/styleguide-app/Examples/RadioButton.elm @@ -137,12 +137,27 @@ viewVanilla state = { teacherPremiumLevel = PremiumLevel.Premium , contentPremiumLevel = PremiumLevel.PremiumWithWriting } + , RadioButton.value Robots , RadioButton.name "pets" , RadioButton.selectedValue state.selectedValue , RadioButton.onSelect Select , RadioButton.valueToString selectionToString , RadioButton.showPennant <| OpenModal "pets-robots" - , RadioButton.block + , RadioButton.inline + ] + , RadioButton.view + (selectionToString Robots) + [ RadioButton.premium + { teacherPremiumLevel = PremiumLevel.Premium + , contentPremiumLevel = PremiumLevel.PremiumWithWriting + } + , RadioButton.value Robots + , RadioButton.name "pets" + , RadioButton.selectedValue state.selectedValue + , RadioButton.onSelect Select + , RadioButton.valueToString selectionToString + , RadioButton.showPennant <| OpenModal "pets-robots" + , RadioButton.inline ] , p [ Attributes.id "dogs-description" ] From 6d095c0f6d975c496b7e6e080d95e1cba0bcf40a Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 16:46:42 -0500 Subject: [PATCH 24/67] move computed values from let blocks into internal config --- src/Nri/Ui/RadioButton/V3.elm | 187 +++++++++++++--------------------- 1 file changed, 71 insertions(+), 116 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 950a4ad3..24e7dcdd 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -296,6 +296,11 @@ type alias InternalConfig value msg = , label : String -- TODO: computed values that both view helpers need + , isChecked : Bool + , isLocked : Bool + , showPennant : Bool + , id : String + , disclosureIdAndElement : Maybe ( String, Html msg ) } @@ -303,6 +308,19 @@ makeInternalConfig : String -> Config -> EventsAndValues value msg -> Maybe (Int makeInternalConfig label config eventsAndValues = case ( eventsAndValues.value, config.name, eventsAndValues.valueToString ) of ( Just value_, Just name_, Just valueToString_ ) -> + let + isChecked = + -- why not guard and make sure neither is Nothing? + -- Because if value is Nothing we do not render a radio + eventsAndValues.selectedValue + == eventsAndValues.value + + id_ = + name_ ++ "-" ++ (dasherize <| toLower <| valueToString_ value_) + + disclosureId = + id_ ++ "-disclosure-content" + in Just { value = value_ , name = name_ @@ -310,6 +328,35 @@ makeInternalConfig label config eventsAndValues = , eventsAndValues = eventsAndValues , config = config , label = label + , isChecked = isChecked + , isLocked = + case ( config.contentPremiumLevel, config.teacherPremiumLevel ) of + ( Just contentPremiumLevel, Just teacherPremiumLevel ) -> + not <| + PremiumLevel.allowedFor + contentPremiumLevel + teacherPremiumLevel + + _ -> + False + , showPennant = + case eventsAndValues.premiumMsg of + Just _ -> + True + + _ -> + False + , id = id_ + , disclosureIdAndElement = + case ( eventsAndValues.disclosedContent, isChecked ) of + ( [], _ ) -> + Nothing + + ( _, False ) -> + Nothing + + ( (_ :: _) as childNodes, True ) -> + Just <| ( disclosureId, div [ id disclosureId ] childNodes ) } _ -> @@ -318,55 +365,9 @@ makeInternalConfig label config eventsAndValues = viewBlock : InternalConfig value msg -> Html msg viewBlock internalConfig = - let - isChecked = - -- why not guard and make sure neither is Nothing? - -- Because if value is Nothing we do not render a radio - internalConfig.eventsAndValues.selectedValue - == internalConfig.eventsAndValues.value - - isLocked : Bool - isLocked = - case ( internalConfig.config.contentPremiumLevel, internalConfig.config.teacherPremiumLevel ) of - ( Just contentPremiumLevel, Just teacherPremiumLevel ) -> - not <| - PremiumLevel.allowedFor - contentPremiumLevel - teacherPremiumLevel - - _ -> - False - - showPennant_ = - case internalConfig.eventsAndValues.premiumMsg of - Just _ -> - True - - _ -> - False - - id_ = - internalConfig.name ++ "-" ++ (dasherize <| toLower <| internalConfig.valueToString internalConfig.value) - - disclosureIdAndElement : Maybe ( String, Html msg ) - disclosureIdAndElement = - case ( internalConfig.eventsAndValues.disclosedContent, isChecked ) of - ( [], _ ) -> - Nothing - - ( _, False ) -> - Nothing - - ( (_ :: _) as childNodes, True ) -> - let - disclosureId = - id_ ++ "-disclosure-content" - in - Just <| ( disclosureId, div [ id disclosureId ] childNodes ) - in Html.div - [ id (id_ ++ "-container") - , classList [ ( "Nri-RadioButton-PremiumClass", showPennant_ ) ] + [ id (internalConfig.id ++ "-container") + , classList [ ( "Nri-RadioButton-PremiumClass", internalConfig.showPennant ) ] , css [ position relative , display Css.block @@ -380,9 +381,9 @@ viewBlock internalConfig = ] [ radio internalConfig.name (internalConfig.valueToString internalConfig.value) - isChecked - [ id id_ - , Widget.disabled (isLocked || internalConfig.config.isDisabled) + internalConfig.isChecked + [ id internalConfig.id + , Widget.disabled (internalConfig.isLocked || internalConfig.config.isDisabled) , case ( internalConfig.eventsAndValues.onSelect, internalConfig.config.isDisabled ) of ( Just onSelect_, False ) -> onClick (onSelect_ internalConfig.value) @@ -390,7 +391,7 @@ viewBlock internalConfig = _ -> Attributes.none , class "Nri-RadioButton-HiddenRadioInput" - , maybeAttr (Tuple.first >> Aria.controls) disclosureIdAndElement + , maybeAttr (Tuple.first >> Aria.controls) internalConfig.disclosureIdAndElement , case internalConfig.config.describedByIds of (_ :: _) as describedByIds -> Aria.describedBy describedByIds @@ -416,10 +417,10 @@ viewBlock internalConfig = ] ] , Html.label - [ for id_ + [ for internalConfig.id , classList [ ( "Nri-RadioButton-RadioButton", True ) - , ( "Nri-RadioButton-RadioButtonChecked", isChecked ) + , ( "Nri-RadioButton-RadioButtonChecked", internalConfig.isChecked ) ] , css <| [ position relative @@ -438,12 +439,12 @@ viewBlock internalConfig = ] ] [ radioInputIcon - { isLocked = isLocked + { isLocked = internalConfig.isLocked , isDisabled = internalConfig.config.isDisabled - , isChecked = isChecked + , isChecked = internalConfig.isChecked } , span - (if showPennant_ then + (if internalConfig.showPennant then [ css [ display inlineFlex , alignItems center @@ -470,61 +471,15 @@ viewBlock internalConfig = ] , viewJust Tuple.second - disclosureIdAndElement + internalConfig.disclosureIdAndElement ] viewInline : InternalConfig value msg -> Html msg viewInline internalConfig = - let - isChecked = - -- why not guard and make sure neither is Nothing? - -- Because if value is Nothing we do not render a radio - internalConfig.eventsAndValues.selectedValue - == internalConfig.eventsAndValues.value - - isLocked : Bool - isLocked = - case ( internalConfig.config.contentPremiumLevel, internalConfig.config.teacherPremiumLevel ) of - ( Just contentPremiumLevel, Just teacherPremiumLevel ) -> - not <| - PremiumLevel.allowedFor - contentPremiumLevel - teacherPremiumLevel - - _ -> - False - - showPennant_ = - case internalConfig.eventsAndValues.premiumMsg of - Just _ -> - True - - _ -> - False - - id_ = - internalConfig.name ++ "-" ++ (dasherize <| toLower <| internalConfig.valueToString internalConfig.value) - - disclosureIdAndElement : Maybe ( String, Html msg ) - disclosureIdAndElement = - case ( internalConfig.eventsAndValues.disclosedContent, isChecked ) of - ( [], _ ) -> - Nothing - - ( _, False ) -> - Nothing - - ( (_ :: _) as childNodes, True ) -> - let - disclosureId = - id_ ++ "-disclosure-content" - in - Just <| ( disclosureId, div [ id disclosureId ] childNodes ) - in Html.span - [ id (id_ ++ "-container") - , classList [ ( "Nri-RadioButton-PremiumClass", showPennant_ ) ] + [ id (internalConfig.id ++ "-container") + , classList [ ( "Nri-RadioButton-PremiumClass", internalConfig.showPennant ) ] , css [ position relative , marginLeft (px -4) @@ -541,9 +496,9 @@ viewInline internalConfig = ] [ radio internalConfig.name (internalConfig.valueToString internalConfig.value) - isChecked - [ id id_ - , Widget.disabled (isLocked || internalConfig.config.isDisabled) + internalConfig.isChecked + [ id internalConfig.id + , Widget.disabled (internalConfig.isLocked || internalConfig.config.isDisabled) , case ( internalConfig.eventsAndValues.onSelect, internalConfig.config.isDisabled ) of ( Just onSelect_, False ) -> onClick (onSelect_ internalConfig.value) @@ -551,7 +506,7 @@ viewInline internalConfig = _ -> Attributes.none , class "Nri-RadioButton-HiddenRadioInput" - , maybeAttr (Tuple.first >> Aria.controls) disclosureIdAndElement + , maybeAttr (Tuple.first >> Aria.controls) internalConfig.disclosureIdAndElement , case internalConfig.config.describedByIds of (_ :: _) as describedByIds -> Aria.describedBy describedByIds @@ -577,10 +532,10 @@ viewInline internalConfig = ] ] , Html.label - [ for id_ + [ for internalConfig.id , classList [ ( "Nri-RadioButton-RadioButton", True ) - , ( "Nri-RadioButton-RadioButtonChecked", isChecked ) + , ( "Nri-RadioButton-RadioButtonChecked", internalConfig.isChecked ) ] , css <| [ position relative @@ -603,12 +558,12 @@ viewInline internalConfig = ] ] [ radioInputIcon - { isLocked = isLocked + { isLocked = internalConfig.isLocked , isDisabled = internalConfig.config.isDisabled - , isChecked = isChecked + , isChecked = internalConfig.isChecked } , span - (if showPennant_ then + (if internalConfig.showPennant then [ css [ display inlineFlex , alignItems center @@ -635,7 +590,7 @@ viewInline internalConfig = ] , viewJust Tuple.second - disclosureIdAndElement + internalConfig.disclosureIdAndElement ] From 647967d1b2aba467f34f801fa70b306197e7e7db Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 17:03:27 -0500 Subject: [PATCH 25/67] lift config into internalConfig --- src/Nri/Ui/RadioButton/V3.elm | 48 ++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 24e7dcdd..117b6ce6 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -287,15 +287,20 @@ view label attributes = type alias InternalConfig value msg = - { -- user specified values - value : value - , name : String + { -- user specified Attributes + name : String , valueToString : value -> String - , eventsAndValues : EventsAndValues value msg - , config : Config , label : String + , teacherPremiumLevel : Maybe PremiumLevel + , contentPremiumLevel : Maybe PremiumLevel + , isDisabled : Bool + , describedByIds : List String - -- TODO: computed values that both view helpers need + -- user specified messages and values TODO unpack eventsAndValues + , eventsAndValues : EventsAndValues value msg + , value : value + + -- computed values that both view helpers need , isChecked : Bool , isLocked : Bool , showPennant : Bool @@ -322,12 +327,15 @@ makeInternalConfig label config eventsAndValues = id_ ++ "-disclosure-content" in Just - { value = value_ - , name = name_ + { name = name_ , valueToString = valueToString_ - , eventsAndValues = eventsAndValues - , config = config , label = label + , teacherPremiumLevel = config.teacherPremiumLevel + , contentPremiumLevel = config.contentPremiumLevel + , isDisabled = config.isDisabled + , describedByIds = config.describedByIds + , eventsAndValues = eventsAndValues + , value = value_ , isChecked = isChecked , isLocked = case ( config.contentPremiumLevel, config.teacherPremiumLevel ) of @@ -383,8 +391,8 @@ viewBlock internalConfig = (internalConfig.valueToString internalConfig.value) internalConfig.isChecked [ id internalConfig.id - , Widget.disabled (internalConfig.isLocked || internalConfig.config.isDisabled) - , case ( internalConfig.eventsAndValues.onSelect, internalConfig.config.isDisabled ) of + , Widget.disabled (internalConfig.isLocked || internalConfig.isDisabled) + , case ( internalConfig.eventsAndValues.onSelect, internalConfig.isDisabled ) of ( Just onSelect_, False ) -> onClick (onSelect_ internalConfig.value) @@ -392,7 +400,7 @@ viewBlock internalConfig = Attributes.none , class "Nri-RadioButton-HiddenRadioInput" , maybeAttr (Tuple.first >> Aria.controls) internalConfig.disclosureIdAndElement - , case internalConfig.config.describedByIds of + , case internalConfig.describedByIds of (_ :: _) as describedByIds -> Aria.describedBy describedByIds @@ -427,7 +435,7 @@ viewBlock internalConfig = , outline Css.none , margin zero , Fonts.baseFont - , if internalConfig.config.isDisabled then + , if internalConfig.isDisabled then Css.batch [ color Colors.gray45 , cursor notAllowed @@ -440,7 +448,7 @@ viewBlock internalConfig = ] [ radioInputIcon { isLocked = internalConfig.isLocked - , isDisabled = internalConfig.config.isDisabled + , isDisabled = internalConfig.isDisabled , isChecked = internalConfig.isChecked } , span @@ -498,8 +506,8 @@ viewInline internalConfig = (internalConfig.valueToString internalConfig.value) internalConfig.isChecked [ id internalConfig.id - , Widget.disabled (internalConfig.isLocked || internalConfig.config.isDisabled) - , case ( internalConfig.eventsAndValues.onSelect, internalConfig.config.isDisabled ) of + , Widget.disabled (internalConfig.isLocked || internalConfig.isDisabled) + , case ( internalConfig.eventsAndValues.onSelect, internalConfig.isDisabled ) of ( Just onSelect_, False ) -> onClick (onSelect_ internalConfig.value) @@ -507,7 +515,7 @@ viewInline internalConfig = Attributes.none , class "Nri-RadioButton-HiddenRadioInput" , maybeAttr (Tuple.first >> Aria.controls) internalConfig.disclosureIdAndElement - , case internalConfig.config.describedByIds of + , case internalConfig.describedByIds of (_ :: _) as describedByIds -> Aria.describedBy describedByIds @@ -542,7 +550,7 @@ viewInline internalConfig = , outline Css.none , margin zero , Fonts.baseFont - , if internalConfig.config.isDisabled then + , if internalConfig.isDisabled then Css.batch [ color Colors.gray45 , cursor notAllowed @@ -559,7 +567,7 @@ viewInline internalConfig = ] [ radioInputIcon { isLocked = internalConfig.isLocked - , isDisabled = internalConfig.config.isDisabled + , isDisabled = internalConfig.isDisabled , isChecked = internalConfig.isChecked } , span From 2db0f5a6af52ef3700104626467227bb16c23f9a Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 17:10:16 -0500 Subject: [PATCH 26/67] lift eventsAndValues into internalConfig --- src/Nri/Ui/RadioButton/V3.elm | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index 117b6ce6..f8c6d2da 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -289,7 +289,6 @@ view label attributes = type alias InternalConfig value msg = { -- user specified Attributes name : String - , valueToString : value -> String , label : String , teacherPremiumLevel : Maybe PremiumLevel , contentPremiumLevel : Maybe PremiumLevel @@ -297,8 +296,12 @@ type alias InternalConfig value msg = , describedByIds : List String -- user specified messages and values TODO unpack eventsAndValues - , eventsAndValues : EventsAndValues value msg , value : value + , selectedValue : Maybe value + , onSelect : Maybe (value -> msg) + , valueToString : value -> String + , premiumMsg : Maybe msg + , disclosedContent : List (Html msg) -- computed values that both view helpers need , isChecked : Bool @@ -328,14 +331,17 @@ makeInternalConfig label config eventsAndValues = in Just { name = name_ - , valueToString = valueToString_ , label = label , teacherPremiumLevel = config.teacherPremiumLevel , contentPremiumLevel = config.contentPremiumLevel , isDisabled = config.isDisabled , describedByIds = config.describedByIds - , eventsAndValues = eventsAndValues , value = value_ + , selectedValue = eventsAndValues.selectedValue + , onSelect = eventsAndValues.onSelect + , valueToString = valueToString_ + , premiumMsg = eventsAndValues.premiumMsg + , disclosedContent = eventsAndValues.disclosedContent , isChecked = isChecked , isLocked = case ( config.contentPremiumLevel, config.teacherPremiumLevel ) of @@ -392,7 +398,7 @@ viewBlock internalConfig = internalConfig.isChecked [ id internalConfig.id , Widget.disabled (internalConfig.isLocked || internalConfig.isDisabled) - , case ( internalConfig.eventsAndValues.onSelect, internalConfig.isDisabled ) of + , case ( internalConfig.onSelect, internalConfig.isDisabled ) of ( Just onSelect_, False ) -> onClick (onSelect_ internalConfig.value) @@ -474,7 +480,7 @@ viewBlock internalConfig = , ClickableSvg.css [ marginLeft (px 8) ] ] ) - internalConfig.eventsAndValues.premiumMsg + internalConfig.premiumMsg ] ] , viewJust @@ -507,7 +513,7 @@ viewInline internalConfig = internalConfig.isChecked [ id internalConfig.id , Widget.disabled (internalConfig.isLocked || internalConfig.isDisabled) - , case ( internalConfig.eventsAndValues.onSelect, internalConfig.isDisabled ) of + , case ( internalConfig.onSelect, internalConfig.isDisabled ) of ( Just onSelect_, False ) -> onClick (onSelect_ internalConfig.value) @@ -593,7 +599,7 @@ viewInline internalConfig = , ClickableSvg.css [ marginLeft (px 8) ] ] ) - internalConfig.eventsAndValues.premiumMsg + internalConfig.premiumMsg ] ] , viewJust From e0ef0ccc735638d40d44017790e7adb2cd0d41d7 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Fri, 19 Nov 2021 17:15:49 -0500 Subject: [PATCH 27/67] and finally clean up the names of internalConfig and config all over --- src/Nri/Ui/RadioButton/V3.elm | 109 ++++++++++++++++------------------ 1 file changed, 52 insertions(+), 57 deletions(-) diff --git a/src/Nri/Ui/RadioButton/V3.elm b/src/Nri/Ui/RadioButton/V3.elm index f8c6d2da..e1e08b29 100644 --- a/src/Nri/Ui/RadioButton/V3.elm +++ b/src/Nri/Ui/RadioButton/V3.elm @@ -265,22 +265,17 @@ Renders nothing if the attributes list does not contain value, name, and valueTo view : String -> List (Attribute value msg) -> Html msg view label attributes = let - eventsAndValues : EventsAndValues value msg - eventsAndValues = - applyEvents attributes - - config : Config - config = + config_ = applyConfig attributes in - case makeInternalConfig label config eventsAndValues of - Just internalConfig -> - case config.display of + case internalConfig label config_ (applyEvents attributes) of + Just config -> + case config_.display of Inline -> - viewInline internalConfig + viewInline config GrayBlock -> - viewBlock internalConfig + viewBlock config _ -> text "no radio button here" @@ -312,8 +307,8 @@ type alias InternalConfig value msg = } -makeInternalConfig : String -> Config -> EventsAndValues value msg -> Maybe (InternalConfig value msg) -makeInternalConfig label config eventsAndValues = +internalConfig : String -> Config -> EventsAndValues value msg -> Maybe (InternalConfig value msg) +internalConfig label config eventsAndValues = case ( eventsAndValues.value, config.name, eventsAndValues.valueToString ) of ( Just value_, Just name_, Just valueToString_ ) -> let @@ -378,10 +373,10 @@ makeInternalConfig label config eventsAndValues = viewBlock : InternalConfig value msg -> Html msg -viewBlock internalConfig = +viewBlock config = Html.div - [ id (internalConfig.id ++ "-container") - , classList [ ( "Nri-RadioButton-PremiumClass", internalConfig.showPennant ) ] + [ id (config.id ++ "-container") + , classList [ ( "Nri-RadioButton-PremiumClass", config.showPennant ) ] , css [ position relative , display Css.block @@ -393,20 +388,20 @@ viewBlock internalConfig = , borderRadius <| Css.px 8 ] ] - [ radio internalConfig.name - (internalConfig.valueToString internalConfig.value) - internalConfig.isChecked - [ id internalConfig.id - , Widget.disabled (internalConfig.isLocked || internalConfig.isDisabled) - , case ( internalConfig.onSelect, internalConfig.isDisabled ) of + [ radio config.name + (config.valueToString config.value) + config.isChecked + [ id config.id + , Widget.disabled (config.isLocked || config.isDisabled) + , case ( config.onSelect, config.isDisabled ) of ( Just onSelect_, False ) -> - onClick (onSelect_ internalConfig.value) + onClick (onSelect_ config.value) _ -> Attributes.none , class "Nri-RadioButton-HiddenRadioInput" - , maybeAttr (Tuple.first >> Aria.controls) internalConfig.disclosureIdAndElement - , case internalConfig.describedByIds of + , maybeAttr (Tuple.first >> Aria.controls) config.disclosureIdAndElement + , case config.describedByIds of (_ :: _) as describedByIds -> Aria.describedBy describedByIds @@ -431,17 +426,17 @@ viewBlock internalConfig = ] ] , Html.label - [ for internalConfig.id + [ for config.id , classList [ ( "Nri-RadioButton-RadioButton", True ) - , ( "Nri-RadioButton-RadioButtonChecked", internalConfig.isChecked ) + , ( "Nri-RadioButton-RadioButtonChecked", config.isChecked ) ] , css <| [ position relative , outline Css.none , margin zero , Fonts.baseFont - , if internalConfig.isDisabled then + , if config.isDisabled then Css.batch [ color Colors.gray45 , cursor notAllowed @@ -453,12 +448,12 @@ viewBlock internalConfig = ] ] [ radioInputIcon - { isLocked = internalConfig.isLocked - , isDisabled = internalConfig.isDisabled - , isChecked = internalConfig.isChecked + { isLocked = config.isLocked + , isDisabled = config.isDisabled + , isChecked = config.isChecked } , span - (if internalConfig.showPennant then + (if config.showPennant then [ css [ display inlineFlex , alignItems center @@ -469,7 +464,7 @@ viewBlock internalConfig = else [ css [ verticalAlign middle ] ] ) - [ Html.text internalConfig.label + [ Html.text config.label , viewJust (\premiumMsg -> ClickableSvg.button "Premium" @@ -480,20 +475,20 @@ viewBlock internalConfig = , ClickableSvg.css [ marginLeft (px 8) ] ] ) - internalConfig.premiumMsg + config.premiumMsg ] ] , viewJust Tuple.second - internalConfig.disclosureIdAndElement + config.disclosureIdAndElement ] viewInline : InternalConfig value msg -> Html msg -viewInline internalConfig = +viewInline config = Html.span - [ id (internalConfig.id ++ "-container") - , classList [ ( "Nri-RadioButton-PremiumClass", internalConfig.showPennant ) ] + [ id (config.id ++ "-container") + , classList [ ( "Nri-RadioButton-PremiumClass", config.showPennant ) ] , css [ position relative , marginLeft (px -4) @@ -508,20 +503,20 @@ viewInline internalConfig = ] ] ] - [ radio internalConfig.name - (internalConfig.valueToString internalConfig.value) - internalConfig.isChecked - [ id internalConfig.id - , Widget.disabled (internalConfig.isLocked || internalConfig.isDisabled) - , case ( internalConfig.onSelect, internalConfig.isDisabled ) of + [ radio config.name + (config.valueToString config.value) + config.isChecked + [ id config.id + , Widget.disabled (config.isLocked || config.isDisabled) + , case ( config.onSelect, config.isDisabled ) of ( Just onSelect_, False ) -> - onClick (onSelect_ internalConfig.value) + onClick (onSelect_ config.value) _ -> Attributes.none , class "Nri-RadioButton-HiddenRadioInput" - , maybeAttr (Tuple.first >> Aria.controls) internalConfig.disclosureIdAndElement - , case internalConfig.describedByIds of + , maybeAttr (Tuple.first >> Aria.controls) config.disclosureIdAndElement + , case config.describedByIds of (_ :: _) as describedByIds -> Aria.describedBy describedByIds @@ -546,17 +541,17 @@ viewInline internalConfig = ] ] , Html.label - [ for internalConfig.id + [ for config.id , classList [ ( "Nri-RadioButton-RadioButton", True ) - , ( "Nri-RadioButton-RadioButtonChecked", internalConfig.isChecked ) + , ( "Nri-RadioButton-RadioButtonChecked", config.isChecked ) ] , css <| [ position relative , outline Css.none , margin zero , Fonts.baseFont - , if internalConfig.isDisabled then + , if config.isDisabled then Css.batch [ color Colors.gray45 , cursor notAllowed @@ -572,12 +567,12 @@ viewInline internalConfig = ] ] [ radioInputIcon - { isLocked = internalConfig.isLocked - , isDisabled = internalConfig.isDisabled - , isChecked = internalConfig.isChecked + { isLocked = config.isLocked + , isDisabled = config.isDisabled + , isChecked = config.isChecked } , span - (if internalConfig.showPennant then + (if config.showPennant then [ css [ display inlineFlex , alignItems center @@ -588,7 +583,7 @@ viewInline internalConfig = else [ css [ verticalAlign middle ] ] ) - [ Html.text internalConfig.label + [ Html.text config.label , viewJust (\premiumMsg -> ClickableSvg.button "Premium" @@ -599,12 +594,12 @@ viewInline internalConfig = , ClickableSvg.css [ marginLeft (px 8) ] ] ) - internalConfig.premiumMsg + config.premiumMsg ] ] , viewJust Tuple.second - internalConfig.disclosureIdAndElement + config.disclosureIdAndElement ] From 5d86a5fa31cef3a77b859f3154d7364751168168 Mon Sep 17 00:00:00 2001 From: Alex Perkins Date: Sun, 21 Nov 2021 09:16:34 -0500 Subject: [PATCH 28/67] wrap grey block with a button, not a div This is so we can select the radio button when clicking anywhere in the grey. So why not a