diff --git a/elm-refactor/README.md b/elm-refactor/README.md new file mode 100644 index 00000000..ae4dd909 --- /dev/null +++ b/elm-refactor/README.md @@ -0,0 +1,3 @@ +# elm-refactor scripts + +See NoRedInk slack: `#eng-elm-refactor-alpha` for how to use the upgrade scripts here. diff --git a/elm-refactor/v8-to-v9/TextInputV6.elm b/elm-refactor/v8-to-v9/TextInputV6.elm new file mode 100644 index 00000000..c4045a12 --- /dev/null +++ b/elm-refactor/v8-to-v9/TextInputV6.elm @@ -0,0 +1,101 @@ +module Main exposing (upgrade_Nri_Ui_TextInput_V5_view) + +import ElmFix +import Nri.Ui.TextInput.V6 as TextInput + + +upgrade_Nri_Ui_TextInput_V5_text = + TextInput.text + + +upgrade_Nri_Ui_TextInput_V5_number = + TextInput.number + + +upgrade_Nri_Ui_TextInput_V5_float = + TextInput.float + + +upgrade_Nri_Ui_TextInput_V5_password = + TextInput.password + + +upgrade_Nri_Ui_TextInput_V5_email = + TextInput.email + + +upgrade_Nri_Ui_TextInput_V5_view model = + TextInput.view model.label + (model.type_ model.onInput) + [ case model.isInError of + False -> + ElmFix.remove + + _ -> + TextInput.errorIf model.isInError + , case model.showLabel of + True -> + ElmFix.remove + + False -> + TextInput.hiddenLabel + , if model.placeholder == model.label then + ElmFix.remove + + else + TextInput.placeholder model.placeholder + , case model.onBlur of + Nothing -> + ElmFix.remove + + Just msg -> + TextInput.onBlur msg + , case model.autofocus of + True -> + TextInput.autofocus + + False -> + ElmFix.remove + ] + model.value + + +upgrade_Nri_Ui_TextInput_V5_writing model = + TextInput.view model.label + (model.type_ model.onInput) + [ TextInput.writing + , case model.isInError of + False -> + ElmFix.remove + + _ -> + TextInput.errorIf model.isInError + , case model.showLabel of + True -> + ElmFix.remove + + False -> + TextInput.hiddenLabel + , if model.placeholder == model.label then + ElmFix.remove + + else + TextInput.placeholder model.placeholder + , case model.onBlur of + Nothing -> + ElmFix.remove + + Just msg -> + TextInput.onBlur msg + , case model.autofocus of + True -> + TextInput.autofocus + + False -> + ElmFix.remove + ] + model.value + + +upgrade_Nri_Ui_TextInput_V5_generateId labelText = + TextInput.generateId labelText diff --git a/src/Nri/Ui/TextInput/V6.elm b/src/Nri/Ui/TextInput/V6.elm index 55cf019a..08ee63cc 100644 --- a/src/Nri/Ui/TextInput/V6.elm +++ b/src/Nri/Ui/TextInput/V6.elm @@ -1,8 +1,8 @@ module Nri.Ui.TextInput.V6 exposing - ( Model - , view, writing - , generateId + ( view, generateId , InputType, number, float, text, password, email + , Attribute, placeholder, errorIf, hiddenLabel, onBlur, autofocus + , writing ) {-| @@ -10,17 +10,21 @@ module Nri.Ui.TextInput.V6 exposing # Changes from V5 - - nothing yet + - new Attributes-style API -@docs Model -@docs view, writing -@docs generateId +@docs view, generateId -## Input types +### Input types @docs InputType, number, float, text, password, email + +## Attributes + +@docs Attribute, placeholder, errorIf, hiddenLabel, onBlur, autofocus +@docs writing + -} import Accessibility.Styled.Style as Accessibility @@ -30,29 +34,15 @@ import Html.Styled as Html exposing (..) import Html.Styled.Attributes as Attributes exposing (..) import Html.Styled.Events as Events exposing (onInput) import Nri.Ui.Html.Attributes.V2 as Extra -import Nri.Ui.InputStyles.V2 as InputStyles exposing (Theme) +import Nri.Ui.InputStyles.V2 as InputStyles import Nri.Ui.Util exposing (dashify) {-| -} -type alias Model value msg = - { label : String - , isInError : Bool - , onInput : value -> msg - , onBlur : Maybe msg - , placeholder : String - , value : value - , autofocus : Bool - , showLabel : Bool - , type_ : InputType value - } - - -{-| -} -type InputType value +type InputType value msg = InputType { toString : value -> String - , fromString : String -> value + , fromString : String -> msg , fieldType : String , inputMode : Maybe String , autocomplete : Maybe String @@ -61,11 +51,11 @@ type InputType value {-| An input that allows text entry -} -text : InputType String -text = +text : (String -> msg) -> InputType String msg +text toMsg = InputType { toString = identity - , fromString = identity + , fromString = toMsg , fieldType = "text" , inputMode = Nothing , autocomplete = Nothing @@ -74,11 +64,11 @@ text = {-| An input that allows integer entry -} -number : InputType (Maybe Int) -number = +number : (Maybe Int -> msg) -> InputType (Maybe Int) msg +number toMsg = InputType { toString = Maybe.map String.fromInt >> Maybe.withDefault "" - , fromString = String.toInt + , fromString = String.toInt >> toMsg , fieldType = "number" , inputMode = Nothing , autocomplete = Nothing @@ -87,11 +77,11 @@ number = {-| An input that allows float entry -} -float : InputType (Maybe Float) -float = +float : (Maybe Float -> msg) -> InputType (Maybe Float) msg +float toMsg = InputType { toString = Maybe.map String.fromFloat >> Maybe.withDefault "" - , fromString = String.toFloat + , fromString = String.toFloat >> toMsg , fieldType = "number" , inputMode = Nothing , autocomplete = Nothing @@ -100,11 +90,11 @@ float = {-| An input that allows password entry -} -password : InputType String -password = +password : (String -> msg) -> InputType String msg +password toMsg = InputType { toString = identity - , fromString = identity + , fromString = toMsg , fieldType = "password" , inputMode = Nothing , autocomplete = Just "current-password" @@ -118,37 +108,137 @@ but not `type="email"` because that would enable browser-provided validation whi with our validation UI. -} -email : InputType String -email = +email : (String -> msg) -> InputType String msg +email toMsg = InputType { toString = identity - , fromString = identity + , fromString = toMsg , fieldType = "text" , inputMode = Just "email" , autocomplete = Just "email" } -{-| -} -view : Model value msg -> Html msg -view model = - view_ InputStyles.Standard model +{-| An optional customization of a TextInput. +-} +type Attribute value msg + = InputStyleAttribute InputStyles.Theme + | ErrorAttribute Bool + | HideLabelAttribute Bool + | PlaceholderAttribute String + | OnBlurAttribute msg + | AutofocusAttribute Bool -{-| -} -writing : Model value msg -> Html msg -writing model = - view_ InputStyles.Writing model +{-| If not explicit placeholder is given, the input label will be used as the placeholder. +-} +placeholder : String -> Attribute value msg +placeholder text_ = + PlaceholderAttribute text_ -view_ : Theme -> Model value msg -> Html msg -view_ theme model = +{-| Sets whether or not the field will be highlighted as having a validation error. +If you are always passing `True`, then you don't need to use this attribute. +-} +errorIf : Bool -> Attribute value msg +errorIf isInError = + ErrorAttribute isInError + + +{-| Hides the visible label. (There will still be an invisible label for screen readers. +-} +hiddenLabel : Attribute value msg +hiddenLabel = + HideLabelAttribute True + + +{-| Causes the TextInput to produce the given `msg` when the field is blurred. +-} +onBlur : msg -> Attribute value msg +onBlur msg = + OnBlurAttribute msg + + +{-| Sets the `autofocus` attribute of the resulting HTML input. +-} +autofocus : Attribute value msg +autofocus = + AutofocusAttribute True + + +{-| This is private. The public API only exposes `Attribute`. +-} +type alias Config msg = + { inputStyle : InputStyles.Theme + , isInError : Bool + , hideLabel : Bool + , placeholder : Maybe String + , onBlur : Maybe msg + , autofocus : Bool + } + + +emptyConfig : Config msg +emptyConfig = + { inputStyle = InputStyles.Standard + , isInError = False + , hideLabel = False + , placeholder = Nothing + , onBlur = Nothing + , autofocus = False + } + + +updateConfig : Attribute value msg -> Config msg -> Config msg +updateConfig attribute config = + case attribute of + InputStyleAttribute theme -> + { config | inputStyle = theme } + + ErrorAttribute isInError -> + { config | isInError = isInError } + + HideLabelAttribute hideLabel -> + { config | hideLabel = hideLabel } + + PlaceholderAttribute text_ -> + { config | placeholder = Just text_ } + + OnBlurAttribute msg -> + { config | onBlur = Just msg } + + AutofocusAttribute autofocus_ -> + { config | autofocus = autofocus_ } + + +{-| Render the TextInput as HTML. +The input's label, InputType, and current value are all required. Other attributes are all optional. +-} +view : String -> InputType value msg -> List (Attribute value msg) -> value -> Html msg +view label inputType attributes currentValue = + let + config = + List.foldl updateConfig emptyConfig attributes + in + view_ label inputType config currentValue + + +{-| Uses the "Writing" input style. See [`Nri.Ui.InputStyles.V2.Theme`](Nri-Ui-InputStyles-V2#Theme). +-} +writing : Attribute value msg +writing = + InputStyleAttribute InputStyles.Writing + + +view_ : String -> InputType value msg -> Config msg -> value -> Html msg +view_ label (InputType inputType) config currentValue = let idValue = - generateId model.label + generateId label - (InputType inputType) = - model.type_ + placeholder_ = + config.placeholder + |> Maybe.withDefault label maybeStep = if inputType.fieldType == "number" then @@ -169,8 +259,8 @@ view_ theme model = (maybeStep ++ [ Attributes.id idValue , css - [ InputStyles.input theme model.isInError - , if theme == InputStyles.Writing then + [ InputStyles.input config.inputStyle config.isInError + , if config.inputStyle == InputStyles.Writing then Css.Global.withClass "override-sass-styles" [ textAlign center , Css.height Css.auto @@ -181,17 +271,17 @@ view_ theme model = [ Css.height (px 45) ] ] - , placeholder model.placeholder - , value (inputType.toString model.value) - , onInput (inputType.fromString >> model.onInput) - , maybeAttr Events.onBlur model.onBlur - , autofocus model.autofocus + , Attributes.placeholder placeholder_ + , value (inputType.toString currentValue) + , onInput inputType.fromString + , maybeAttr Events.onBlur config.onBlur + , Attributes.autofocus config.autofocus , type_ inputType.fieldType , maybeAttr (attribute "inputmode") inputType.inputMode , maybeAttr (attribute "autocomplete") inputType.autocomplete , class "override-sass-styles" , Attributes.attribute "aria-invalid" <| - if model.isInError then + if config.isInError then "true" else @@ -199,21 +289,21 @@ view_ theme model = ] ) [] - , if model.showLabel then - Html.label - [ for idValue - , css [ InputStyles.label theme model.isInError ] - ] - [ Html.text model.label ] + , let + extraStyles = + if config.hideLabel then + Accessibility.invisible - else - Html.label - ([ for idValue - , css [ InputStyles.label theme model.isInError ] - ] - ++ Accessibility.invisible - ) - [ Html.text model.label ] + else + [] + in + Html.label + ([ for idValue + , css [ InputStyles.label config.inputStyle config.isInError ] + ] + ++ extraStyles + ) + [ Html.text label ] ] diff --git a/styleguide-app/Examples/TextInput.elm b/styleguide-app/Examples/TextInput.elm index d7775198..d35d4c26 100644 --- a/styleguide-app/Examples/TextInput.elm +++ b/styleguide-app/Examples/TextInput.elm @@ -60,89 +60,106 @@ example = [ Control.view UpdateControl state.control |> Html.fromUnstyled , Heading.h3 [] [ text "TextInput.view { type_ = TextInput.text }" ] - , TextInput.view - { label = exampleConfig.label ++ " (text)" - , isInError = exampleConfig.isInError - , placeholder = exampleConfig.placeholder - , showLabel = exampleConfig.showLabel - , value = Maybe.withDefault "" <| Dict.get 1 state.stringInputValues - , onInput = SetTextInput 1 - , onBlur = Nothing - , autofocus = False - , type_ = TextInput.text - } + , TextInput.view (exampleConfig.label ++ " (text)") + (TextInput.text (SetTextInput 1)) + ([ TextInput.errorIf exampleConfig.isInError + , TextInput.placeholder exampleConfig.placeholder + ] + ++ (if exampleConfig.showLabel then + [] + + else + [ TextInput.hiddenLabel ] + ) + ) + (Maybe.withDefault "" <| Dict.get 1 state.stringInputValues) , Heading.h3 [] [ text "... type_ = TextInput.number" ] - , TextInput.view - { label = exampleConfig.label ++ " (number)" - , isInError = exampleConfig.isInError - , placeholder = exampleConfig.placeholder - , showLabel = exampleConfig.showLabel - , value = state.numberInputValue - , onInput = SetNumberInput - , onBlur = Nothing - , autofocus = False - , type_ = TextInput.number - } + , TextInput.view (exampleConfig.label ++ " (number)") + (TextInput.number SetNumberInput) + ([ TextInput.errorIf exampleConfig.isInError + , TextInput.placeholder exampleConfig.placeholder + ] + ++ (if exampleConfig.showLabel then + [] + + else + [ TextInput.hiddenLabel ] + ) + ) + state.numberInputValue , Heading.h3 [] [ text "... type_ = TextInput.float" ] - , TextInput.view - { label = exampleConfig.label ++ " (float)" - , isInError = exampleConfig.isInError - , placeholder = exampleConfig.placeholder - , showLabel = exampleConfig.showLabel - , value = state.floatInputValue - , onInput = SetFloatInput - , onBlur = Nothing - , autofocus = False - , type_ = TextInput.float - } + , TextInput.view (exampleConfig.label ++ " (float)") + (TextInput.float SetFloatInput) + ([ TextInput.errorIf exampleConfig.isInError + , TextInput.placeholder exampleConfig.placeholder + ] + ++ (if exampleConfig.showLabel then + [] + + else + [ TextInput.hiddenLabel ] + ) + ) + state.floatInputValue , Heading.h3 [] [ text "... type_ = TextInput.password" ] - , TextInput.view - { label = exampleConfig.label ++ " (password)" - , isInError = exampleConfig.isInError - , placeholder = exampleConfig.placeholder - , showLabel = exampleConfig.showLabel - , value = state.passwordInputValue - , onInput = SetPassword - , onBlur = Nothing - , autofocus = False - , type_ = TextInput.password - } + , TextInput.view (exampleConfig.label ++ " (password)") + (TextInput.password SetPassword) + ([ TextInput.errorIf exampleConfig.isInError + , TextInput.placeholder exampleConfig.placeholder + ] + ++ (if exampleConfig.showLabel then + [] + + else + [ TextInput.hiddenLabel ] + ) + ) + state.passwordInputValue , Heading.h3 [] [ text "... type_ = TextInput.email" ] - , TextInput.view - { label = exampleConfig.label ++ " (email)" - , isInError = exampleConfig.isInError - , placeholder = exampleConfig.placeholder - , showLabel = exampleConfig.showLabel - , value = Maybe.withDefault "" <| Dict.get 2 state.stringInputValues - , onInput = SetTextInput 2 - , onBlur = Nothing - , autofocus = False - , type_ = TextInput.email - } + , TextInput.view (exampleConfig.label ++ " (email)") + (TextInput.email (SetTextInput 2)) + ([ TextInput.errorIf exampleConfig.isInError + , TextInput.placeholder exampleConfig.placeholder + ] + ++ (if exampleConfig.showLabel then + [] + + else + [ TextInput.hiddenLabel ] + ) + ) + (Maybe.withDefault "" <| Dict.get 2 state.stringInputValues) , Heading.h3 [] [ Html.text "TextInput.writing { type_ = TextInput.text }" ] - , TextInput.writing - { label = exampleConfig.label ++ " (writing)" - , isInError = exampleConfig.isInError - , placeholder = exampleConfig.placeholder - , value = Maybe.withDefault "" <| Dict.get 4 state.stringInputValues - , onInput = SetTextInput 4 - , onBlur = Nothing - , autofocus = False - , type_ = TextInput.text - , showLabel = exampleConfig.showLabel - } + , TextInput.view (exampleConfig.label ++ " (writing)") + (TextInput.text (SetTextInput 4)) + ([ TextInput.writing + , TextInput.errorIf exampleConfig.isInError + , TextInput.placeholder exampleConfig.placeholder + ] + ++ (if exampleConfig.showLabel then + [] + + else + [ TextInput.hiddenLabel ] + ) + ) + (Maybe.withDefault "" <| Dict.get 4 state.stringInputValues) , Heading.h3 [] [ text "onBlur demonstration" ] - , TextInput.writing - { label = exampleConfig.label ++ " (onBlur)" - , isInError = exampleConfig.isInError - , placeholder = exampleConfig.placeholder - , value = Maybe.withDefault "" <| Dict.get 7 state.stringInputValues - , onInput = SetTextInput 7 - , onBlur = Just (SetTextInput 7 "Blurred!") - , autofocus = False - , type_ = TextInput.text - , showLabel = exampleConfig.showLabel - } + , TextInput.view (exampleConfig.label ++ " (onBlur)") + (TextInput.text (SetTextInput 7)) + ([ TextInput.writing + , TextInput.errorIf exampleConfig.isInError + , TextInput.placeholder exampleConfig.placeholder + , TextInput.onBlur (SetTextInput 7 "Blurred!") + ] + ++ (if exampleConfig.showLabel then + [] + + else + [ TextInput.hiddenLabel ] + ) + ) + (Maybe.withDefault "" <| Dict.get 7 state.stringInputValues) ] ] } diff --git a/tests/Spec/Nri/Ui/TextInput/V6.elm b/tests/Spec/Nri/Ui/TextInput/V6.elm index 34665d59..28b12398 100644 --- a/tests/Spec/Nri/Ui/TextInput/V6.elm +++ b/tests/Spec/Nri/Ui/TextInput/V6.elm @@ -12,17 +12,12 @@ all = describe "Nri.Ui.TextInput.V6" [ test "it uses the same DOM id that generateId produces" <| \() -> - TextInput.view - { label = "myLabel" - , isInError = False - , onInput = identity - , onBlur = Nothing - , placeholder = "placeholder" - , value = "value" - , autofocus = False - , showLabel = False - , type_ = TextInput.text - } + TextInput.view "myLabel" + (TextInput.text identity) + [ TextInput.hiddenLabel + , TextInput.placeholder "placeholder" + ] + "value" |> Html.Styled.toUnstyled |> Query.fromHtml |> Query.has