New attributes-style API for TextInput.V6

This commit is contained in:
Aaron VonderHaar 2020-04-13 16:43:07 -07:00
parent fc61d0bfe6
commit a2d305b6e4
5 changed files with 369 additions and 163 deletions

3
elm-refactor/README.md Normal file
View File

@ -0,0 +1,3 @@
# elm-refactor scripts
See NoRedInk slack: `#eng-elm-refactor-alpha` for how to use the upgrade scripts here.

View File

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

View File

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

View File

@ -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)
]
]
}

View File

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