mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2025-01-02 11:28:01 +03:00
Merge pull request #63 from NoRedInk/textarea-v2-minimum-height
Textarea v2 adding a minimum height
This commit is contained in:
commit
1acc5996e8
@ -41,6 +41,7 @@
|
||||
"Nri.Ui.Text.V1",
|
||||
"Nri.Ui.Text.V2",
|
||||
"Nri.Ui.TextArea.V1",
|
||||
"Nri.Ui.TextArea.V2",
|
||||
"Nri.Ui.TextInput.V1",
|
||||
"Nri.Ui.TextInput.V2",
|
||||
"Nri.Ui"
|
||||
@ -55,6 +56,7 @@
|
||||
"rtfeldman/elm-css-helpers": "2.1.0 <= v < 3.0.0",
|
||||
"rtfeldman/elm-css-util": "1.0.2 <= v < 2.0.0",
|
||||
"tesk9/accessible-html": "3.0.0 <= v < 4.0.0",
|
||||
"tesk9/accessible-html-with-css": "1.0.1 <= v < 2.0.0",
|
||||
"wernerdegroot/listzipper": "3.0.0 <= v < 4.0.0"
|
||||
},
|
||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||
|
@ -1,9 +1,14 @@
|
||||
module Nri.Ui.InputStyles exposing (Assets, CssClasses(..), styles)
|
||||
module Nri.Ui.InputStyles exposing (Assets, CssClasses(..), inputLineHeight, inputPaddingVertical, styles, textAreaHeight, writingLineHeight, writingMinHeight, writingPadding, writingPaddingTop)
|
||||
|
||||
{-|
|
||||
|
||||
@docs styles, CssClasses
|
||||
|
||||
|
||||
## Shared hardcoded values
|
||||
|
||||
@docs inputPaddingVertical, inputLineHeight, textAreaHeight, writingLineHeight, writingPadding, writingPaddingTop, writingMinHeight
|
||||
|
||||
-}
|
||||
|
||||
import Css exposing (..)
|
||||
@ -60,7 +65,7 @@ styles =
|
||||
inputStyle =
|
||||
[ border3 (px 1) solid gray75
|
||||
, borderRadius (px 8)
|
||||
, padding2 (px 8) (px 14)
|
||||
, padding2 inputPaddingVertical (px 14)
|
||||
, property "transition" "all 0.1s ease"
|
||||
, pseudoClass "placeholder"
|
||||
[ color gray45
|
||||
@ -73,7 +78,7 @@ styles =
|
||||
, display inlineBlock
|
||||
, verticalAlign top
|
||||
, marginBottom zero
|
||||
, lineHeight (px 20)
|
||||
, lineHeight inputLineHeight
|
||||
, marginTop (px 9)
|
||||
, boxShadow6 inset zero (px 2) zero zero gray92
|
||||
, property "transition" "all 0.4s ease"
|
||||
@ -98,7 +103,7 @@ styles =
|
||||
, selector "textarea"
|
||||
[ withClass Input
|
||||
(inputStyle
|
||||
++ [ height (px 100)
|
||||
++ [ height textAreaHeight
|
||||
, width (pct 100)
|
||||
]
|
||||
)
|
||||
@ -123,9 +128,9 @@ styles =
|
||||
[ class Input
|
||||
[ Nri.Ui.Fonts.V1.quizFont
|
||||
, fontSize (px 20)
|
||||
, lineHeight (px 25)
|
||||
, padding (px 15)
|
||||
, paddingTop (px 20)
|
||||
, lineHeight writingLineHeight
|
||||
, padding writingPadding
|
||||
, paddingTop writingPaddingTop
|
||||
, height auto
|
||||
]
|
||||
, class Label
|
||||
@ -141,7 +146,7 @@ styles =
|
||||
, borderColor azure
|
||||
]
|
||||
, selector "textarea"
|
||||
[ minHeight (px 150)
|
||||
[ minHeight writingMinHeight
|
||||
]
|
||||
, selector "input"
|
||||
[ textAlign center
|
||||
@ -253,3 +258,38 @@ type alias Assets r =
|
||||
, icons_searchGray_svg : Asset
|
||||
, icons_xBlue_svg : Asset
|
||||
}
|
||||
|
||||
|
||||
inputPaddingVertical : Px
|
||||
inputPaddingVertical =
|
||||
px 8
|
||||
|
||||
|
||||
inputLineHeight : Px
|
||||
inputLineHeight =
|
||||
px 20
|
||||
|
||||
|
||||
textAreaHeight : Px
|
||||
textAreaHeight =
|
||||
px 100
|
||||
|
||||
|
||||
writingLineHeight : Px
|
||||
writingLineHeight =
|
||||
px 25
|
||||
|
||||
|
||||
writingPadding : Px
|
||||
writingPadding =
|
||||
px 15
|
||||
|
||||
|
||||
writingPaddingTop : Px
|
||||
writingPaddingTop =
|
||||
px 20
|
||||
|
||||
|
||||
writingMinHeight : Px
|
||||
writingMinHeight =
|
||||
px 150
|
||||
|
244
src/Nri/Ui/TextArea/V2.elm
Normal file
244
src/Nri/Ui/TextArea/V2.elm
Normal file
@ -0,0 +1,244 @@
|
||||
module Nri.Ui.TextArea.V2
|
||||
exposing
|
||||
( Height(..)
|
||||
, HeightBehavior(..)
|
||||
, Model
|
||||
, contentCreation
|
||||
, generateId
|
||||
, styles
|
||||
, view
|
||||
, writing
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
## Upgrading from V2
|
||||
|
||||
- The Model's autoResize field is now `height : HeightBehavior`, which can be
|
||||
either `Fixed` or `AutoResize Height`. `Height` is either
|
||||
`DefaultHeight` or `SingleLine` and controls the minimum height of the textarea.
|
||||
|
||||
- The view now returns `Html.Styled` rather than plain `Html`.
|
||||
|
||||
|
||||
## The Nri styleguide-specified textarea with overlapping label
|
||||
|
||||
@docs view, writing, contentCreation, Height, HeightBehavior, Model, generateId, styles
|
||||
|
||||
-}
|
||||
|
||||
import Accessibility.Styled.Style
|
||||
import Css exposing ((|+|))
|
||||
import Html.Styled as Html exposing (Html)
|
||||
import Html.Styled.Attributes as Attributes
|
||||
import Html.Styled.Events as Events
|
||||
import Nri.Ui.InputStyles as InputStyles exposing (CssClasses(..))
|
||||
import Nri.Ui.Styles.V1
|
||||
import Nri.Ui.Util exposing (dashify, removePunctuation)
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Model msg =
|
||||
{ value : String
|
||||
, autofocus : Bool
|
||||
, onInput : String -> msg
|
||||
, isInError : Bool
|
||||
, height : HeightBehavior
|
||||
, placeholder : String
|
||||
, label : String
|
||||
, showLabel : Bool
|
||||
}
|
||||
|
||||
|
||||
{-| Control whether to auto-expand the height.
|
||||
-}
|
||||
type HeightBehavior
|
||||
= Fixed
|
||||
| AutoResize Height
|
||||
|
||||
|
||||
{-| For specifying the actual height.
|
||||
-}
|
||||
type Height
|
||||
= DefaultHeight
|
||||
| SingleLine
|
||||
|
||||
|
||||
{-| -}
|
||||
view : Model msg -> Html msg
|
||||
view model =
|
||||
view_ DefaultStyle model
|
||||
|
||||
|
||||
{-| Used for Writing Cycles
|
||||
-}
|
||||
writing : Model msg -> Html msg
|
||||
writing model =
|
||||
view_ WritingStyle model
|
||||
|
||||
|
||||
{-| Used for Content Creation
|
||||
-}
|
||||
contentCreation : Model msg -> Html msg
|
||||
contentCreation model =
|
||||
view_ ContentCreationStyle model
|
||||
|
||||
|
||||
type TextAreaStyle
|
||||
= DefaultStyle
|
||||
| WritingStyle
|
||||
| ContentCreationStyle
|
||||
|
||||
|
||||
{-| -}
|
||||
view_ : TextAreaStyle -> Model msg -> Html msg
|
||||
view_ textAreaStyle model =
|
||||
let
|
||||
showWritingClass =
|
||||
textAreaStyle == WritingStyle
|
||||
|
||||
showContentCreationClass =
|
||||
textAreaStyle == ContentCreationStyle
|
||||
|
||||
minHeight =
|
||||
case model.height of
|
||||
Fixed ->
|
||||
[]
|
||||
|
||||
AutoResize minimumHeight ->
|
||||
-- FIXME: Css.important is needed here because InputStyles's
|
||||
-- min-height rule has more specificity. It can go away once
|
||||
-- we've fully migrated to Html.Styled.
|
||||
[ calculateMinHeight textAreaStyle minimumHeight
|
||||
|> Css.minHeight
|
||||
|> Css.important
|
||||
]
|
||||
|
||||
sharedAttributes =
|
||||
[ Events.onInput model.onInput
|
||||
, Attributes.id (generateId model.label)
|
||||
, styles.class [ Input ]
|
||||
|> Attributes.fromUnstyled
|
||||
, Attributes.autofocus model.autofocus
|
||||
, Attributes.placeholder model.placeholder
|
||||
, Attributes.attribute "data-gramm" "false" -- disables grammarly to prevent https://github.com/NoRedInk/NoRedInk/issues/14859
|
||||
, Attributes.css
|
||||
(minHeight
|
||||
++ [ Css.boxSizing Css.borderBox ]
|
||||
)
|
||||
]
|
||||
in
|
||||
Html.div
|
||||
[ styles.classList
|
||||
[ ( Container, True )
|
||||
, ( IsInError, model.isInError )
|
||||
, ( Writing, showWritingClass )
|
||||
, ( ContentCreation, showContentCreationClass )
|
||||
]
|
||||
|> Attributes.fromUnstyled
|
||||
]
|
||||
[ case model.height of
|
||||
AutoResize _ ->
|
||||
{- NOTES:
|
||||
The autoresize-textarea element is implemented to pass information applied to itself to an internal
|
||||
textarea element that it inserts into the DOM automatically. Maintaing this behavior may require some
|
||||
changes on your part, as listed below.
|
||||
|
||||
- When adding an Html.Attribute that is a _property_, you must edit Nri/TextArea.js to ensure that a getter and setter
|
||||
are set up to properly reflect the property to the actual textarea element that autoresize-textarea creates
|
||||
- When adding a new listener from Html.Events, you must edit Nri/TextArea.js to ensure that a listener is set up on
|
||||
the textarea that will trigger this event on the autoresize-textarea element itself. See AutoresizeTextArea.prototype._onInput
|
||||
and AutoresizeTextArea.prototype.connectedCallback for an example pertaining to the `input` event
|
||||
- When adding a new Html.Attribute that is an _attribute_, you don't have to do anything. All attributes are
|
||||
automatically reflected onto the textarea element via AutoresizeTextArea.prototype.attributeChangedCallback
|
||||
-}
|
||||
Html.node "autoresize-textarea"
|
||||
(sharedAttributes
|
||||
++ [ -- setting the default value via a text node doesn't play well with the custom element,
|
||||
-- but we'll be able to switch to the regular value property in 0.19 anyway
|
||||
Attributes.defaultValue model.value
|
||||
]
|
||||
)
|
||||
[]
|
||||
|
||||
Fixed ->
|
||||
Html.textarea sharedAttributes
|
||||
[ Html.text model.value ]
|
||||
, if not model.showLabel then
|
||||
Html.label
|
||||
[ Attributes.for (generateId model.label)
|
||||
, styles.class [ Label ]
|
||||
|> Attributes.fromUnstyled
|
||||
, Accessibility.Styled.Style.invisible
|
||||
]
|
||||
[ Html.text model.label ]
|
||||
else
|
||||
Html.label
|
||||
[ Attributes.for (generateId model.label)
|
||||
, styles.class [ Label ]
|
||||
|> Attributes.fromUnstyled
|
||||
]
|
||||
[ Html.text model.label ]
|
||||
]
|
||||
|
||||
|
||||
calculateMinHeight : TextAreaStyle -> Height -> Css.Px
|
||||
calculateMinHeight textAreaStyle specifiedHeight =
|
||||
{- On including padding in this calculation:
|
||||
|
||||
When the textarea is autoresized, TextArea.js updates the textarea's
|
||||
height by taking its scrollHeight. Because scrollHeight's calculation
|
||||
includes the element's padding no matter what [1], we need to set the
|
||||
textarea's box-sizing to border-box in order to use the same measurement
|
||||
for its height as scrollHeight.
|
||||
|
||||
So, min-height also needs to be specified in terms of padding + content
|
||||
height.
|
||||
|
||||
[1] https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
|
||||
-}
|
||||
case specifiedHeight of
|
||||
SingleLine ->
|
||||
case textAreaStyle of
|
||||
DefaultStyle ->
|
||||
singleLineHeight
|
||||
|
||||
WritingStyle ->
|
||||
writingSingleLineHeight
|
||||
|
||||
ContentCreationStyle ->
|
||||
singleLineHeight
|
||||
|
||||
DefaultHeight ->
|
||||
case textAreaStyle of
|
||||
DefaultStyle ->
|
||||
InputStyles.textAreaHeight
|
||||
|
||||
WritingStyle ->
|
||||
InputStyles.writingMinHeight
|
||||
|
||||
ContentCreationStyle ->
|
||||
InputStyles.textAreaHeight
|
||||
|
||||
|
||||
singleLineHeight : Css.Px
|
||||
singleLineHeight =
|
||||
InputStyles.inputPaddingVertical |+| InputStyles.inputLineHeight |+| InputStyles.inputPaddingVertical
|
||||
|
||||
|
||||
writingSingleLineHeight : Css.Px
|
||||
writingSingleLineHeight =
|
||||
InputStyles.writingPaddingTop |+| InputStyles.writingLineHeight |+| InputStyles.writingPadding
|
||||
|
||||
|
||||
{-| -}
|
||||
generateId : String -> String
|
||||
generateId labelText =
|
||||
"nri-ui-text-area-" ++ (dashify <| removePunctuation labelText)
|
||||
|
||||
|
||||
{-| -}
|
||||
styles : Nri.Ui.Styles.V1.StylesWithAssets Never CssClasses msg (InputStyles.Assets r)
|
||||
styles =
|
||||
InputStyles.styles
|
@ -6,13 +6,14 @@ module Examples.TextArea exposing (Msg, State, example, init, update)
|
||||
|
||||
-}
|
||||
|
||||
import Css
|
||||
import Dict exposing (Dict)
|
||||
import Html
|
||||
import Html.Styled
|
||||
import ModuleExample as ModuleExample exposing (Category(..), ModuleExample)
|
||||
import Nri.Ui.Checkbox.V2 as Checkbox
|
||||
import Nri.Ui.Text.V2 as Text
|
||||
import Nri.Ui.TextArea.V1 as TextArea
|
||||
import Nri.Ui.TextArea.V2 as TextArea
|
||||
|
||||
|
||||
{-| -}
|
||||
@ -76,30 +77,45 @@ example parentMessage state =
|
||||
, onInput = InputGiven 1
|
||||
, isInError = state.isInError
|
||||
, label = "TextArea.view"
|
||||
, autoResize = state.autoResize
|
||||
, height =
|
||||
if state.autoResize then
|
||||
TextArea.AutoResize TextArea.SingleLine
|
||||
else
|
||||
TextArea.Fixed
|
||||
, placeholder = "Placeholder"
|
||||
, showLabel = state.showLabel
|
||||
}
|
||||
|> Html.Styled.toUnstyled
|
||||
, TextArea.writing
|
||||
{ value = Maybe.withDefault "" <| Dict.get 2 state.textValues
|
||||
, autofocus = False
|
||||
, onInput = InputGiven 2
|
||||
, isInError = state.isInError
|
||||
, label = "TextArea.writing"
|
||||
, autoResize = state.autoResize
|
||||
, height =
|
||||
if state.autoResize then
|
||||
TextArea.AutoResize TextArea.DefaultHeight
|
||||
else
|
||||
TextArea.Fixed
|
||||
, placeholder = "Placeholder"
|
||||
, showLabel = state.showLabel
|
||||
}
|
||||
|> Html.Styled.toUnstyled
|
||||
, TextArea.contentCreation
|
||||
{ value = Maybe.withDefault "" <| Dict.get 3 state.textValues
|
||||
, autofocus = False
|
||||
, onInput = InputGiven 3
|
||||
, isInError = state.isInError
|
||||
, label = "TextArea.contentCreation"
|
||||
, autoResize = state.autoResize
|
||||
, height =
|
||||
if state.autoResize then
|
||||
TextArea.AutoResize TextArea.DefaultHeight
|
||||
else
|
||||
TextArea.Fixed
|
||||
, placeholder = "Placeholder"
|
||||
, showLabel = state.showLabel
|
||||
}
|
||||
|> Html.Styled.toUnstyled
|
||||
]
|
||||
|> List.map (Html.map parentMessage)
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ import Nri.Ui.Dropdown.V1
|
||||
import Nri.Ui.Icon.V2
|
||||
import Nri.Ui.SegmentedControl.V5
|
||||
import Nri.Ui.Select.V2
|
||||
import Nri.Ui.TextArea.V1 as TextArea
|
||||
import Nri.Ui.Text.V2 as Text
|
||||
import Nri.Ui.TextArea.V2 as TextArea
|
||||
import String.Extra
|
||||
|
||||
|
||||
|
@ -43,23 +43,30 @@ Object.defineProperties(AutoresizeTextArea.prototype, {
|
||||
}
|
||||
});
|
||||
|
||||
AutoresizeTextArea.prototype._onInput = function() {
|
||||
AutoresizeTextArea.prototype._resize = function() {
|
||||
var minHeight = null;
|
||||
if (this._textarea.style.minHeight) {
|
||||
minHeight = parseInt(this._textarea.style.minHeight, 10);
|
||||
} else {
|
||||
minHeight = parseInt(window.getComputedStyle(this._textarea).minHeight, 10);
|
||||
}
|
||||
if (minHeight === 0) {
|
||||
minHeight = parseInt(window.getComputedStyle(this._textarea).height, 10);
|
||||
}
|
||||
|
||||
this._textarea.style.overflowY = "hidden";
|
||||
this._textarea.style.minHeight = minHeight + "px";
|
||||
this._textarea.style.transition = "none";
|
||||
if (this._textarea.scrollHeight > minHeight) {
|
||||
this._textarea.style.height = "auto";
|
||||
this._textarea.style.height = minHeight + "px";
|
||||
this._textarea.style.height = this._textarea.scrollHeight + "px";
|
||||
} else {
|
||||
this._textarea.style.height = minHeight + "px";
|
||||
}
|
||||
};
|
||||
|
||||
AutoresizeTextArea.prototype._onInput = function() {
|
||||
this._resize();
|
||||
this.dispatchEvent(new Event("input"));
|
||||
};
|
||||
|
||||
@ -91,6 +98,7 @@ AutoresizeTextArea.prototype.connectedCallback = function() {
|
||||
this._textarea.addEventListener("input", this._onInput);
|
||||
this._reflectAttributes();
|
||||
this.appendChild(this._textarea);
|
||||
this._resize();
|
||||
};
|
||||
|
||||
AutoresizeTextArea.prototype.disconnectedCallback = function() {
|
||||
|
@ -21,6 +21,7 @@
|
||||
"rtfeldman/elm-css-helpers": "2.1.0 <= v < 3.0.0",
|
||||
"rtfeldman/elm-css-util": "1.0.2 <= v < 2.0.0",
|
||||
"tesk9/accessible-html": "3.0.0 <= v < 4.0.0",
|
||||
"tesk9/accessible-html-with-css": "1.0.1 <= v < 2.0.0",
|
||||
"wernerdegroot/listzipper": "3.0.0 <= v < 4.0.0"
|
||||
},
|
||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||
|
@ -19,6 +19,7 @@
|
||||
"rtfeldman/elm-css-helpers": "2.1.0 <= v < 3.0.0",
|
||||
"rtfeldman/elm-css-util": "1.0.2 <= v < 2.0.0",
|
||||
"tesk9/accessible-html": "3.0.0 <= v < 4.0.0",
|
||||
"tesk9/accessible-html-with-css": "1.0.1 <= v < 2.0.0",
|
||||
"wernerdegroot/listzipper": "3.0.0 <= v < 4.0.0"
|
||||
},
|
||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||
|
Loading…
Reference in New Issue
Block a user