From 11a8434074f2fa76efb873c515e2709643862186 Mon Sep 17 00:00:00 2001 From: Juan Edi Date: Tue, 10 Dec 2024 13:24:04 -0300 Subject: [PATCH 1/6] rename Switch.V3 -> Switch.V4 --- src/Nri/Ui/Switch/{V3.elm => V4.elm} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Nri/Ui/Switch/{V3.elm => V4.elm} (100%) diff --git a/src/Nri/Ui/Switch/V3.elm b/src/Nri/Ui/Switch/V4.elm similarity index 100% rename from src/Nri/Ui/Switch/V3.elm rename to src/Nri/Ui/Switch/V4.elm From 6ffcec062f317ac28f2eaf4654dcb85858bc4ff8 Mon Sep 17 00:00:00 2001 From: Juan Edi Date: Tue, 10 Dec 2024 13:27:20 -0300 Subject: [PATCH 2/6] restore V3 --- src/Nri/Ui/Switch/V3.elm | 398 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 src/Nri/Ui/Switch/V3.elm diff --git a/src/Nri/Ui/Switch/V3.elm b/src/Nri/Ui/Switch/V3.elm new file mode 100644 index 000000000..df4469f90 --- /dev/null +++ b/src/Nri/Ui/Switch/V3.elm @@ -0,0 +1,398 @@ +module Nri.Ui.Switch.V3 exposing + ( view + , Attribute + , selected + , containerCss, labelCss, custom, nriDescription, testId + , onSwitch, disabled + ) + +{-| + + +### Changes from V2: + + - Replace underlying checkbox input with a custom implementation + - Allow attributes that produce msgs to be passed through + +@docs view + + +### Attributes + +@docs Attribute +@docs selected +@docs containerCss, labelCss, custom, nriDescription, testId +@docs onSwitch, disabled + +-} + +import Accessibility.Styled.Aria as Aria +import Accessibility.Styled.Key as Key +import Accessibility.Styled.Role as Role +import Css exposing (Color, Style) +import Css.Global as Global +import Html.Styled as Html exposing (Html) +import Html.Styled.Attributes as Attributes +import Html.Styled.Events as Events +import Nri.Ui.Colors.Extra exposing (toCssString) +import Nri.Ui.Colors.V1 as Colors +import Nri.Ui.FocusRing.V1 as FocusRing +import Nri.Ui.Fonts.V1 as Fonts +import Nri.Ui.Html.Attributes.V2 as Extra +import Nri.Ui.MediaQuery.V1 as MediaQuery +import Nri.Ui.Svg.V1 exposing (Svg) +import Svg.Styled as Svg +import Svg.Styled.Attributes as SvgAttributes + + +{-| -} +type Attribute msg + = Attribute (Config msg -> Config msg) + + +{-| What is the status of the Switch, selected or not? +-} +selected : Bool -> Attribute msg +selected isSelected = + Attribute <| \config -> { config | isSelected = isSelected } + + +{-| Specify what happens when the switch is toggled. +-} +onSwitch : (Bool -> msg) -> Attribute msg +onSwitch onSwitch_ = + Attribute <| \config -> { config | onSwitch = Just onSwitch_ } + + +{-| Explicitly specify that you want this switch to be disabled. If you don't +specify `onSwitch`, this is the default, but it's provided so you don't have +to resort to `filterMap` or similar to build a clean list of attributes. +-} +disabled : Bool -> Attribute msg +disabled isDisabled = + Attribute <| \config -> { config | isDisabled = isDisabled } + + +{-| Pass custom attributes through to be attached to the underlying input. + +Do NOT use this helper to add css styles, as they may not be applied the way +you want/expect if underlying styles change. +Instead, please use `containerCss` or `labelCss`. + +-} +custom : List (Html.Attribute msg) -> Attribute msg +custom custom_ = + Attribute <| \config -> { config | custom = config.custom ++ custom_ } + + +{-| -} +nriDescription : String -> Attribute msg +nriDescription description = + custom [ Extra.nriDescription description ] + + +{-| -} +testId : String -> Attribute msg +testId id_ = + custom [ Extra.testId id_ ] + + +{-| Adds CSS to the Switch container. +-} +containerCss : List Css.Style -> Attribute msg +containerCss styles = + Attribute <| \config -> { config | containerCss = config.containerCss ++ styles } + + +{-| Adds CSS to the element containing the label text. + +Note that these styles don't apply to the literal HTML label element, since it contains the icon SVG as well. + +-} +labelCss : List Css.Style -> Attribute msg +labelCss styles = + Attribute <| \config -> { config | labelCss = config.labelCss ++ styles } + + +type alias Config msg = + { onSwitch : Maybe (Bool -> msg) + , containerCss : List Style + , labelCss : List Style + , isDisabled : Bool + , isSelected : Bool + , custom : List (Html.Attribute msg) + } + + +defaultConfig : Config msg +defaultConfig = + { onSwitch = Nothing + , containerCss = [] + , labelCss = [] + , isDisabled = False + , isSelected = False + , custom = [] + } + + +{-| Render a switch. The boolean here indicates whether the switch is on +or not. +-} +view : { label : String, id : String } -> List (Attribute msg) -> Html msg +view { label, id } attrs = + let + config = + List.foldl (\(Attribute update) -> update) defaultConfig attrs + + isDisabled_ = + notOperable config + in + Html.div + ([ Attributes.css + [ Css.display Css.inlineFlex + , Css.alignItems Css.center + , Css.fontSize (Css.px 15) + , Css.outline Css.none + , Css.pseudoClass "focus-within" + [ Global.descendants + [ Global.class "switch-track" + [ FocusRing.boxShadows [] + , Css.borderRadius (Css.px 16) + ] + ] + ] + , Css.cursor + (if isDisabled_ then + Css.notAllowed + + else + Css.pointer + ) + , Css.batch config.containerCss + ] + , Attributes.class FocusRing.customClass + ] + ++ switchAttributes id config + ++ config.custom + ) + [ Nri.Ui.Svg.V1.toHtml + (viewSwitch + { id = id + , isSelected = config.isSelected + , isDisabled = isDisabled_ + } + ) + , Html.span + [ Attributes.css + [ Css.fontWeight (Css.int 600) + , Css.color + (if isDisabled_ then + Colors.gray45 + + else + Colors.navy + ) + , Css.paddingLeft (Css.px 5) + , Fonts.baseFont + , Css.batch config.labelCss + ] + ] + [ Html.text label ] + ] + + +switchAttributes : String -> Config msg -> List (Html.Attribute msg) +switchAttributes id config = + let + eventsOrDisabled = + case ( config.onSwitch, config.isDisabled ) of + ( Just onSwitch_, False ) -> + [ Events.onClick (onSwitch_ (not config.isSelected)) + , Key.onKeyDownPreventDefault + [ Key.space (onSwitch_ (not config.isSelected)) + ] + ] + + _ -> + [ Aria.disabled True ] + in + [ Attributes.id id + , Role.switch + , Aria.checked (Just config.isSelected) + , Key.tabbable True + ] + ++ eventsOrDisabled + + +notOperable : Config msg -> Bool +notOperable config = + config.onSwitch == Nothing || config.isDisabled + + +viewSwitch : + { id : String + , isSelected : Bool + , isDisabled : Bool + } + -> Svg +viewSwitch config = + let + shadowFilterId = + config.id ++ "-shadow-filter" + + shadowBoxId = + config.id ++ "-shadow-box" + + disabledPrimaryCircleColor = + if config.isSelected then + Colors.gray45 + + else + Colors.gray75 + in + Nri.Ui.Svg.V1.init "0 0 43 32" + [ Svg.defs [] + [ Svg.filter + [ SvgAttributes.id shadowFilterId + , SvgAttributes.width "105%" + , SvgAttributes.height "106.7%" + , SvgAttributes.x "-2.5%" + , SvgAttributes.y "-3.3%" + , SvgAttributes.filterUnits "objectBoundingBox" + ] + [ Svg.feOffset + [ SvgAttributes.dy "2" + , SvgAttributes.in_ "SourceAlpha" + , SvgAttributes.result "shadowOffsetInner1" + ] + [] + , Svg.feComposite + [ SvgAttributes.in_ "shadowOffsetInner1" + , SvgAttributes.in2 "SourceAlpha" + , SvgAttributes.k2 "-1" + , SvgAttributes.k3 "1" + , SvgAttributes.operator "arithmetic" + , SvgAttributes.result "shadowInnerInner1" + ] + [] + , Svg.feColorMatrix + [ SvgAttributes.in_ "shadowInnerInner1" + , SvgAttributes.values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" + ] + [] + ] + , Svg.rect + [ SvgAttributes.id shadowBoxId + , SvgAttributes.width "40" + , SvgAttributes.height "30" + , SvgAttributes.x "0" + , SvgAttributes.y "0" + , SvgAttributes.rx "15" + ] + [] + ] + , Svg.g + [ SvgAttributes.fill "none" + , SvgAttributes.fillRule "even-odd" + , SvgAttributes.transform "translate(1, 1)" + ] + [ Svg.g [] + [ Svg.use + [ SvgAttributes.xlinkHref ("#" ++ shadowBoxId) + , SvgAttributes.css + [ if config.isDisabled then + if config.isSelected then + Css.fill Colors.gray75 + + else + Css.fill Colors.gray85 + + else if config.isSelected then + Css.fill Colors.glacier + + else + Css.fill Colors.gray92 + , transition "fill 0.2s" + ] + ] + [] + , if not config.isDisabled then + Svg.use + [ SvgAttributes.xlinkHref ("#" ++ shadowBoxId) + , SvgAttributes.fill "#000" + , SvgAttributes.filter ("url(#" ++ shadowFilterId ++ ")") + ] + [] + + else + Svg.g [] [] + ] + , Svg.g + [ SvgAttributes.css + [ if config.isSelected then + Css.transform (Css.translateX (Css.px 11)) + + else + Css.transform (Css.translateX (Css.px 0)) + , transition "transform 0.2s ease-in-out" + ] + ] + [ Svg.circle + [ SvgAttributes.cx "15" + , SvgAttributes.cy "15" + , SvgAttributes.r "14.5" + , SvgAttributes.fill + (if config.isDisabled then + disabledPrimaryCircleColor + + else + Colors.white + ).value + , SvgAttributes.css + [ if config.isDisabled then + stroke disabledPrimaryCircleColor + + else if config.isSelected then + stroke Colors.azure + + else + stroke Colors.gray75 + , transition "stroke 0.1s" + ] + , SvgAttributes.class "switch-slider" + ] + [] + , Svg.path + [ SvgAttributes.strokeLinecap "round" + , SvgAttributes.strokeLinejoin "round" + , SvgAttributes.strokeWidth "3" + , SvgAttributes.d "M8 15.865L12.323 20 21.554 10" + , SvgAttributes.css + [ if config.isDisabled && config.isSelected then + stroke Colors.white + + else if config.isSelected then + stroke Colors.azure + + else + Css.display Css.none + , transition "stroke 0.2s" + ] + ] + [] + ] + ] + ] + |> Nri.Ui.Svg.V1.withWidth (Css.px 43) + |> Nri.Ui.Svg.V1.withHeight (Css.px 32) + |> Nri.Ui.Svg.V1.withCustom [ SvgAttributes.class "switch-track" ] + + +stroke : Color -> Style +stroke color = + Css.property "stroke" (toCssString color) + + +transition : String -> Css.Style +transition transitionRules = + MediaQuery.anyMotion [ Css.property "transition" transitionRules ] From 598ba627969a48b8df9887915d11a9f3bc4f15b6 Mon Sep 17 00:00:00 2001 From: Juan Edi Date: Tue, 10 Dec 2024 13:27:33 -0300 Subject: [PATCH 3/6] Switch.V4 supports arbitrary HTML for the label --- src/Nri/Ui/Switch/V4.elm | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Nri/Ui/Switch/V4.elm b/src/Nri/Ui/Switch/V4.elm index df4469f90..0c85f4429 100644 --- a/src/Nri/Ui/Switch/V4.elm +++ b/src/Nri/Ui/Switch/V4.elm @@ -1,4 +1,4 @@ -module Nri.Ui.Switch.V3 exposing +module Nri.Ui.Switch.V4 exposing ( view , Attribute , selected @@ -9,10 +9,9 @@ module Nri.Ui.Switch.V3 exposing {-| -### Changes from V2: +### Changes from V3: - - Replace underlying checkbox input with a custom implementation - - Allow attributes that produce msgs to be passed through + - `view` accepts arbitrary html for the label @docs view @@ -138,7 +137,7 @@ defaultConfig = {-| Render a switch. The boolean here indicates whether the switch is on or not. -} -view : { label : String, id : String } -> List (Attribute msg) -> Html msg +view : { label : Html msg, id : String } -> List (Attribute msg) -> Html msg view { label, id } attrs = let config = @@ -197,7 +196,7 @@ view { label, id } attrs = , Css.batch config.labelCss ] ] - [ Html.text label ] + [ label ] ] From 781c8007a6cc10a69af63ff64318891ecf15e9cd Mon Sep 17 00:00:00 2001 From: Juan Edi Date: Tue, 10 Dec 2024 14:03:20 -0300 Subject: [PATCH 4/6] upgrade references to new version of Switch --- component-catalog/src/Examples/FocusRing.elm | 4 ++-- component-catalog/src/Examples/Menu.elm | 4 ++-- component-catalog/src/Examples/Switch.elm | 18 +++++++++--------- .../src/UsageExamples/FocusLoop.elm | 6 +++--- tests/Spec/Nri/Ui/Switch.elm | 6 +++--- tests/elm-verify-examples.json | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/component-catalog/src/Examples/FocusRing.elm b/component-catalog/src/Examples/FocusRing.elm index f7b6e3d67..61b02fcd6 100644 --- a/component-catalog/src/Examples/FocusRing.elm +++ b/component-catalog/src/Examples/FocusRing.elm @@ -26,7 +26,7 @@ import Nri.Ui.RadioButton.V4 as RadioButton import Nri.Ui.SegmentedControl.V14 as SegmentedControl import Nri.Ui.Spacing.V1 as Spacing import Nri.Ui.Svg.V1 as Svg -import Nri.Ui.Switch.V3 as Switch +import Nri.Ui.Switch.V4 as Switch import Nri.Ui.Table.V8 as Table import Nri.Ui.Text.V6 as Text import Nri.Ui.UiIcon.V1 as UiIcon @@ -262,7 +262,7 @@ NOTE: use `boxShadows` instead if your focusable element: , view = exampleWithBorderAndBG [ FocusRing.boxShadows [] ] , twoToned = Just True , examples = - [ Switch.view { label = "Switch", id = "switch" } [] + [ Switch.view { label = text "Switch", id = "switch" } [] , ClickableSvg.button "ClickableSvg button" UiIcon.playInCircle [] , SegmentedControl.viewRadioGroup { legend = "SegmentedControls.viewRadioGroup" diff --git a/component-catalog/src/Examples/Menu.elm b/component-catalog/src/Examples/Menu.elm index fd369bcfa..30e46fdab 100644 --- a/component-catalog/src/Examples/Menu.elm +++ b/component-catalog/src/Examples/Menu.elm @@ -37,7 +37,7 @@ import Nri.Ui.Menu.V5 as Menu import Nri.Ui.RadioButton.V4 as RadioButton import Nri.Ui.Spacing.V1 as Spacing import Nri.Ui.Svg.V1 as Svg -import Nri.Ui.Switch.V3 as Switch +import Nri.Ui.Switch.V4 as Switch import Nri.Ui.Table.V8 as Table import Nri.Ui.Text.V6 as Text import Nri.Ui.TextInput.V8 as TextInput @@ -923,7 +923,7 @@ viewScoreDisplay value selected attributes = viewDroppedStudentsSwitch : Bool -> List (Attribute Msg) -> Html Msg viewDroppedStudentsSwitch showDroppedStudents attributes = Switch.view - { label = "Show dropped students" + { label = text "Show dropped students" , id = droppedStudentsId } [ Switch.onSwitch ShowDroppedStudents diff --git a/component-catalog/src/Examples/Switch.elm b/component-catalog/src/Examples/Switch.elm index bb964b8de..24dd25b5f 100644 --- a/component-catalog/src/Examples/Switch.elm +++ b/component-catalog/src/Examples/Switch.elm @@ -19,7 +19,7 @@ import Html.Styled exposing (..) import KeyboardSupport exposing (Key(..)) import Nri.Ui.Heading.V3 as Heading import Nri.Ui.Spacing.V1 as Spacing -import Nri.Ui.Switch.V3 as Switch +import Nri.Ui.Switch.V4 as Switch import Nri.Ui.Table.V8 as Table import Nri.Ui.Tooltip.V3 as Tooltip @@ -46,11 +46,11 @@ example = , update = update , subscriptions = \_ -> Sub.none , preview = - [ Switch.view { label = "Toggle Off", id = "preview-switch-a" } + [ Switch.view { label = text "Toggle Off", id = "preview-switch-a" } [ Switch.selected False , Switch.custom [ Key.tabbable False ] ] - , Switch.view { label = "Toggle On", id = "preview-switch-b" } + , Switch.view { label = text "Toggle On", id = "preview-switch-b" } [ Switch.selected True , Switch.custom [ Key.tabbable False ] ] @@ -99,7 +99,7 @@ example = [ Heading.plaintext "Customizable example" , Heading.css [ Css.marginTop Spacing.verticalSpacerPx ] ] - , Switch.view { label = currentValue.label, id = "view-switch-example" } + , Switch.view { label = text currentValue.label, id = "view-switch-example" } (Switch.selected state.selected :: Switch.onSwitch Switch :: List.map Tuple.second currentValue.attributes @@ -134,7 +134,7 @@ example = [ { state = "Off" , enabled = Switch.view - { label = "Show dropped students" + { label = text "Show dropped students" , id = "show-dropped-students-off-enabled" } [ Switch.selected False @@ -142,7 +142,7 @@ example = ] , disabled = Switch.view - { label = "Show dropped students" + { label = text "Show dropped students" , id = "show-dropped-students-off-disabled" } [ Switch.selected False @@ -152,7 +152,7 @@ example = , { state = "On" , enabled = Switch.view - { label = "Show dropped students" + { label = text "Show dropped students" , id = "show-dropped-students-on-enabled" } [ Switch.selected True @@ -160,7 +160,7 @@ example = ] , disabled = Switch.view - { label = "Show dropped students" + { label = text "Show dropped students" , id = "show-dropped-students-on-disabled" } [ Switch.selected True @@ -178,7 +178,7 @@ example = , Tooltip.view { trigger = \attrs -> - Switch.view { id = "tooltip-example", label = "Show pandas in results" } + Switch.view { id = "tooltip-example", label = text "Show pandas in results" } [ Switch.disabled True , Switch.custom attrs ] diff --git a/component-catalog/src/UsageExamples/FocusLoop.elm b/component-catalog/src/UsageExamples/FocusLoop.elm index a79701152..e12327e1c 100644 --- a/component-catalog/src/UsageExamples/FocusLoop.elm +++ b/component-catalog/src/UsageExamples/FocusLoop.elm @@ -20,7 +20,7 @@ import Nri.Ui.Button.V10 as Button import Nri.Ui.FocusLoop.Lazy.V1 as FocusLoop import Nri.Ui.FocusLoop.V1 as FocusLoop import Nri.Ui.Html.V3 exposing (viewIf) -import Nri.Ui.Switch.V3 as Switch +import Nri.Ui.Switch.V4 as Switch import Nri.Ui.TextInput.V8 as TextInput import Nri.Ui.Tooltip.V3 as Tooltip import Task @@ -259,7 +259,7 @@ viewLazyToggle = \useLazy tooltipOpen -> Html.div [ Attrs.css [ Css.displayFlex, Css.alignItems Css.center, Css.property "gap" "10px" ] ] [ Switch.view - { label = "Use Lazy" + { label = Html.text "Use Lazy" , id = "lazy-switch" } [ Switch.selected useLazy @@ -279,7 +279,7 @@ viewSimulateExpensiveComputationToggle = \settings tooltipOpen -> Html.div [ Attrs.css [ Css.displayFlex, Css.alignItems Css.center, Css.property "gap" "10px" ] ] [ Switch.view - { label = "Simulate Expensive Computation" + { label = Html.text "Simulate Expensive Computation" , id = "simulate-expensive-computation-switch" } [ Switch.selected settings.simulateExpensiveComputation diff --git a/tests/Spec/Nri/Ui/Switch.elm b/tests/Spec/Nri/Ui/Switch.elm index fd6fe9867..110588f0d 100644 --- a/tests/Spec/Nri/Ui/Switch.elm +++ b/tests/Spec/Nri/Ui/Switch.elm @@ -5,7 +5,7 @@ import Accessibility.Role as Role import Html.Styled exposing (..) import Nri.Test.KeyboardHelpers.V1 as KeyboardHelpers import Nri.Test.MouseHelpers.V1 as MouseHelpers -import Nri.Ui.Switch.V3 as Switch +import Nri.Ui.Switch.V4 as Switch import ProgramTest exposing (..) import Spec.Helpers exposing (expectFailure) import Test exposing (..) @@ -14,7 +14,7 @@ import Test.Html.Selector exposing (..) spec : Test spec = - describe "Nri.Ui.Switch.V2" + describe "Nri.Ui.Switch.V4" [ describe "'switch' role" hasCorrectRole , describe "helpfully disabled switch" helpfullyDisabledSwitch ] @@ -114,7 +114,7 @@ view attributes state = div [] [ Switch.view { id = "switch" - , label = "Switch" + , label = Html.Styled.text "Switch" } (Switch.selected state.selected :: Switch.onSwitch Toggle :: attributes) ] diff --git a/tests/elm-verify-examples.json b/tests/elm-verify-examples.json index 5a22887ea..8a63d7837 100644 --- a/tests/elm-verify-examples.json +++ b/tests/elm-verify-examples.json @@ -74,7 +74,7 @@ "Nri.Ui.Sprite.V1", "Nri.Ui.StickerIcon.V1", "Nri.Ui.Svg.V1", - "Nri.Ui.Switch.V3", + "Nri.Ui.Switch.V4", "Nri.Ui.Table.V8", "Nri.Ui.Tabs.V6", "Nri.Ui.Tabs.V9", From 1b2844ed84e7a69eeae3f655f6065747dc2d2b87 Mon Sep 17 00:00:00 2001 From: Juan Edi Date: Tue, 10 Dec 2024 14:05:41 -0300 Subject: [PATCH 5/6] expose V4 and delete V3 the migration is so straightforward that we don't need to keep both around --- elm.json | 2 +- src/Nri/Ui/Switch/V3.elm | 398 --------------------------------------- 2 files changed, 1 insertion(+), 399 deletions(-) delete mode 100644 src/Nri/Ui/Switch/V3.elm diff --git a/elm.json b/elm.json index 036bd58d2..5b9344b2c 100644 --- a/elm.json +++ b/elm.json @@ -78,7 +78,7 @@ "Nri.Ui.Sprite.V1", "Nri.Ui.StickerIcon.V1", "Nri.Ui.Svg.V1", - "Nri.Ui.Switch.V3", + "Nri.Ui.Switch.V4", "Nri.Ui.Table.V8", "Nri.Ui.Tabs.V6", "Nri.Ui.Tabs.V9", diff --git a/src/Nri/Ui/Switch/V3.elm b/src/Nri/Ui/Switch/V3.elm deleted file mode 100644 index df4469f90..000000000 --- a/src/Nri/Ui/Switch/V3.elm +++ /dev/null @@ -1,398 +0,0 @@ -module Nri.Ui.Switch.V3 exposing - ( view - , Attribute - , selected - , containerCss, labelCss, custom, nriDescription, testId - , onSwitch, disabled - ) - -{-| - - -### Changes from V2: - - - Replace underlying checkbox input with a custom implementation - - Allow attributes that produce msgs to be passed through - -@docs view - - -### Attributes - -@docs Attribute -@docs selected -@docs containerCss, labelCss, custom, nriDescription, testId -@docs onSwitch, disabled - --} - -import Accessibility.Styled.Aria as Aria -import Accessibility.Styled.Key as Key -import Accessibility.Styled.Role as Role -import Css exposing (Color, Style) -import Css.Global as Global -import Html.Styled as Html exposing (Html) -import Html.Styled.Attributes as Attributes -import Html.Styled.Events as Events -import Nri.Ui.Colors.Extra exposing (toCssString) -import Nri.Ui.Colors.V1 as Colors -import Nri.Ui.FocusRing.V1 as FocusRing -import Nri.Ui.Fonts.V1 as Fonts -import Nri.Ui.Html.Attributes.V2 as Extra -import Nri.Ui.MediaQuery.V1 as MediaQuery -import Nri.Ui.Svg.V1 exposing (Svg) -import Svg.Styled as Svg -import Svg.Styled.Attributes as SvgAttributes - - -{-| -} -type Attribute msg - = Attribute (Config msg -> Config msg) - - -{-| What is the status of the Switch, selected or not? --} -selected : Bool -> Attribute msg -selected isSelected = - Attribute <| \config -> { config | isSelected = isSelected } - - -{-| Specify what happens when the switch is toggled. --} -onSwitch : (Bool -> msg) -> Attribute msg -onSwitch onSwitch_ = - Attribute <| \config -> { config | onSwitch = Just onSwitch_ } - - -{-| Explicitly specify that you want this switch to be disabled. If you don't -specify `onSwitch`, this is the default, but it's provided so you don't have -to resort to `filterMap` or similar to build a clean list of attributes. --} -disabled : Bool -> Attribute msg -disabled isDisabled = - Attribute <| \config -> { config | isDisabled = isDisabled } - - -{-| Pass custom attributes through to be attached to the underlying input. - -Do NOT use this helper to add css styles, as they may not be applied the way -you want/expect if underlying styles change. -Instead, please use `containerCss` or `labelCss`. - --} -custom : List (Html.Attribute msg) -> Attribute msg -custom custom_ = - Attribute <| \config -> { config | custom = config.custom ++ custom_ } - - -{-| -} -nriDescription : String -> Attribute msg -nriDescription description = - custom [ Extra.nriDescription description ] - - -{-| -} -testId : String -> Attribute msg -testId id_ = - custom [ Extra.testId id_ ] - - -{-| Adds CSS to the Switch container. --} -containerCss : List Css.Style -> Attribute msg -containerCss styles = - Attribute <| \config -> { config | containerCss = config.containerCss ++ styles } - - -{-| Adds CSS to the element containing the label text. - -Note that these styles don't apply to the literal HTML label element, since it contains the icon SVG as well. - --} -labelCss : List Css.Style -> Attribute msg -labelCss styles = - Attribute <| \config -> { config | labelCss = config.labelCss ++ styles } - - -type alias Config msg = - { onSwitch : Maybe (Bool -> msg) - , containerCss : List Style - , labelCss : List Style - , isDisabled : Bool - , isSelected : Bool - , custom : List (Html.Attribute msg) - } - - -defaultConfig : Config msg -defaultConfig = - { onSwitch = Nothing - , containerCss = [] - , labelCss = [] - , isDisabled = False - , isSelected = False - , custom = [] - } - - -{-| Render a switch. The boolean here indicates whether the switch is on -or not. --} -view : { label : String, id : String } -> List (Attribute msg) -> Html msg -view { label, id } attrs = - let - config = - List.foldl (\(Attribute update) -> update) defaultConfig attrs - - isDisabled_ = - notOperable config - in - Html.div - ([ Attributes.css - [ Css.display Css.inlineFlex - , Css.alignItems Css.center - , Css.fontSize (Css.px 15) - , Css.outline Css.none - , Css.pseudoClass "focus-within" - [ Global.descendants - [ Global.class "switch-track" - [ FocusRing.boxShadows [] - , Css.borderRadius (Css.px 16) - ] - ] - ] - , Css.cursor - (if isDisabled_ then - Css.notAllowed - - else - Css.pointer - ) - , Css.batch config.containerCss - ] - , Attributes.class FocusRing.customClass - ] - ++ switchAttributes id config - ++ config.custom - ) - [ Nri.Ui.Svg.V1.toHtml - (viewSwitch - { id = id - , isSelected = config.isSelected - , isDisabled = isDisabled_ - } - ) - , Html.span - [ Attributes.css - [ Css.fontWeight (Css.int 600) - , Css.color - (if isDisabled_ then - Colors.gray45 - - else - Colors.navy - ) - , Css.paddingLeft (Css.px 5) - , Fonts.baseFont - , Css.batch config.labelCss - ] - ] - [ Html.text label ] - ] - - -switchAttributes : String -> Config msg -> List (Html.Attribute msg) -switchAttributes id config = - let - eventsOrDisabled = - case ( config.onSwitch, config.isDisabled ) of - ( Just onSwitch_, False ) -> - [ Events.onClick (onSwitch_ (not config.isSelected)) - , Key.onKeyDownPreventDefault - [ Key.space (onSwitch_ (not config.isSelected)) - ] - ] - - _ -> - [ Aria.disabled True ] - in - [ Attributes.id id - , Role.switch - , Aria.checked (Just config.isSelected) - , Key.tabbable True - ] - ++ eventsOrDisabled - - -notOperable : Config msg -> Bool -notOperable config = - config.onSwitch == Nothing || config.isDisabled - - -viewSwitch : - { id : String - , isSelected : Bool - , isDisabled : Bool - } - -> Svg -viewSwitch config = - let - shadowFilterId = - config.id ++ "-shadow-filter" - - shadowBoxId = - config.id ++ "-shadow-box" - - disabledPrimaryCircleColor = - if config.isSelected then - Colors.gray45 - - else - Colors.gray75 - in - Nri.Ui.Svg.V1.init "0 0 43 32" - [ Svg.defs [] - [ Svg.filter - [ SvgAttributes.id shadowFilterId - , SvgAttributes.width "105%" - , SvgAttributes.height "106.7%" - , SvgAttributes.x "-2.5%" - , SvgAttributes.y "-3.3%" - , SvgAttributes.filterUnits "objectBoundingBox" - ] - [ Svg.feOffset - [ SvgAttributes.dy "2" - , SvgAttributes.in_ "SourceAlpha" - , SvgAttributes.result "shadowOffsetInner1" - ] - [] - , Svg.feComposite - [ SvgAttributes.in_ "shadowOffsetInner1" - , SvgAttributes.in2 "SourceAlpha" - , SvgAttributes.k2 "-1" - , SvgAttributes.k3 "1" - , SvgAttributes.operator "arithmetic" - , SvgAttributes.result "shadowInnerInner1" - ] - [] - , Svg.feColorMatrix - [ SvgAttributes.in_ "shadowInnerInner1" - , SvgAttributes.values "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" - ] - [] - ] - , Svg.rect - [ SvgAttributes.id shadowBoxId - , SvgAttributes.width "40" - , SvgAttributes.height "30" - , SvgAttributes.x "0" - , SvgAttributes.y "0" - , SvgAttributes.rx "15" - ] - [] - ] - , Svg.g - [ SvgAttributes.fill "none" - , SvgAttributes.fillRule "even-odd" - , SvgAttributes.transform "translate(1, 1)" - ] - [ Svg.g [] - [ Svg.use - [ SvgAttributes.xlinkHref ("#" ++ shadowBoxId) - , SvgAttributes.css - [ if config.isDisabled then - if config.isSelected then - Css.fill Colors.gray75 - - else - Css.fill Colors.gray85 - - else if config.isSelected then - Css.fill Colors.glacier - - else - Css.fill Colors.gray92 - , transition "fill 0.2s" - ] - ] - [] - , if not config.isDisabled then - Svg.use - [ SvgAttributes.xlinkHref ("#" ++ shadowBoxId) - , SvgAttributes.fill "#000" - , SvgAttributes.filter ("url(#" ++ shadowFilterId ++ ")") - ] - [] - - else - Svg.g [] [] - ] - , Svg.g - [ SvgAttributes.css - [ if config.isSelected then - Css.transform (Css.translateX (Css.px 11)) - - else - Css.transform (Css.translateX (Css.px 0)) - , transition "transform 0.2s ease-in-out" - ] - ] - [ Svg.circle - [ SvgAttributes.cx "15" - , SvgAttributes.cy "15" - , SvgAttributes.r "14.5" - , SvgAttributes.fill - (if config.isDisabled then - disabledPrimaryCircleColor - - else - Colors.white - ).value - , SvgAttributes.css - [ if config.isDisabled then - stroke disabledPrimaryCircleColor - - else if config.isSelected then - stroke Colors.azure - - else - stroke Colors.gray75 - , transition "stroke 0.1s" - ] - , SvgAttributes.class "switch-slider" - ] - [] - , Svg.path - [ SvgAttributes.strokeLinecap "round" - , SvgAttributes.strokeLinejoin "round" - , SvgAttributes.strokeWidth "3" - , SvgAttributes.d "M8 15.865L12.323 20 21.554 10" - , SvgAttributes.css - [ if config.isDisabled && config.isSelected then - stroke Colors.white - - else if config.isSelected then - stroke Colors.azure - - else - Css.display Css.none - , transition "stroke 0.2s" - ] - ] - [] - ] - ] - ] - |> Nri.Ui.Svg.V1.withWidth (Css.px 43) - |> Nri.Ui.Svg.V1.withHeight (Css.px 32) - |> Nri.Ui.Svg.V1.withCustom [ SvgAttributes.class "switch-track" ] - - -stroke : Color -> Style -stroke color = - Css.property "stroke" (toCssString color) - - -transition : String -> Css.Style -transition transitionRules = - MediaQuery.anyMotion [ Css.property "transition" transitionRules ] From df9d385b6876ba383e6bf382e80a41ca28e17822 Mon Sep 17 00:00:00 2001 From: Juan Edi Date: Tue, 10 Dec 2024 14:28:29 -0300 Subject: [PATCH 6/6] update generated snippet in example page --- component-catalog/src/Examples/Switch.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-catalog/src/Examples/Switch.elm b/component-catalog/src/Examples/Switch.elm index 24dd25b5f..bc79ecfaf 100644 --- a/component-catalog/src/Examples/Switch.elm +++ b/component-catalog/src/Examples/Switch.elm @@ -80,7 +80,7 @@ example = , code = Code.fromModule moduleName "view" ++ Code.recordMultiline - [ ( "label", Code.string label ) + [ ( "label", Code.apply [ Code.fromModule "Html" "text", Code.string label ] ) , ( "id", Code.string "view-switch-example" ) ] 1