From aa3bee7933a774a2cde7008b23a812a0484b8908 Mon Sep 17 00:00:00 2001 From: Krzysztof Sroka Date: Fri, 16 Feb 2018 09:57:29 +0100 Subject: [PATCH] feat: adding basic helperText support to TextInput --- .../textinput-helpertext-error.png | Bin 0 -> 7604 bytes .../screenshots/textinput-helpertext.png | Bin 0 -> 6580 bytes example/src/TextInputExample.js | 21 ++- src/components/HelperText.js | 172 ++++++++++++++++++ src/components/TextInput.js | 125 ++++++++++--- .../BottomNavigation.test.js.snap | 11 ++ .../__snapshots__/ListItem.test.js.snap | 6 + .../__snapshots__/ListSection.test.js.snap | 5 + src/index.js | 1 + src/styles/DefaultTheme.js | 3 +- src/types.js | 1 + 11 files changed, 313 insertions(+), 32 deletions(-) create mode 100644 docs/assets/screenshots/textinput-helpertext-error.png create mode 100644 docs/assets/screenshots/textinput-helpertext.png create mode 100644 src/components/HelperText.js diff --git a/docs/assets/screenshots/textinput-helpertext-error.png b/docs/assets/screenshots/textinput-helpertext-error.png new file mode 100644 index 0000000000000000000000000000000000000000..2518cf4bb579ac373fe45314a4cacdada88ad377 GIT binary patch literal 7604 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 Q-t)|=DrqWK-Lj7Q4=wMRdH?_b literal 0 HcmV?d00001 diff --git a/example/src/TextInputExample.js b/example/src/TextInputExample.js index 259372c937..05e290ac7b 100644 --- a/example/src/TextInputExample.js +++ b/example/src/TextInputExample.js @@ -1,8 +1,8 @@ /* @flow */ import * as React from 'react'; -import { ScrollView, StyleSheet } from 'react-native'; -import { TextInput, withTheme } from 'react-native-paper'; +import { ScrollView, StyleSheet, View } from 'react-native'; +import { TextInput, HelperText, withTheme } from 'react-native-paper'; import type { Theme } from 'react-native-paper/types'; type Props = { @@ -11,6 +11,7 @@ type Props = { type State = { text: string, + errorInputText: string, }; class TextInputExample extends React.Component { @@ -18,6 +19,7 @@ class TextInputExample extends React.Component { state = { text: '', + errorInputText: '', }; render() { @@ -35,6 +37,21 @@ class TextInputExample extends React.Component { value={this.state.text} onChangeText={text => this.setState({ text })} /> + + 3} + onChangeText={errorInputText => this.setState({ errorInputText })} + /> + 3} + > + Error: text too long + + + *
+ * + *
Without error + *
+ *
+ * + *
With error
+ *
+ * + * + * ## Usage + * ```js + * import * as React from 'react'; + * import { HelperText, TextInput } from 'react-native-paper'; + * + * class MyComponent extends React.Component { + * state = { + * text: '' + * }; + * + * render(){ + * return ( + * + * this.setState({ text })} + * /> + * + * Info text + * + * + * ); + * } + * } + * ``` + */ +class HelperText extends React.PureComponent { + static defaultProps = { + type: 'info', + visible: true, + }; + + state = { + shown: new Animated.Value(this.props.visible ? 1 : 0), + textHeight: 0, + }; + + componentDidUpdate(prevProps) { + if (prevProps.visible !== this.props.visible) { + if (this.props.visible) { + this._animateFocus(); + } else { + this._animateBlur(); + } + } + } + + _animateFocus = () => { + Animated.timing(this.state.shown, { + toValue: 1, + duration: 150, + }).start(); + }; + + _animateBlur = () => { + Animated.timing(this.state.shown, { + toValue: 0, + duration: 180, + }).start(); + }; + + _getTextColor = () => { + const { + colors: { text, error }, + dark, + } = this.props.theme; + switch (this.props.type) { + case 'error': + return error; + case 'info': + default: + return color(text) + .alpha(dark ? 0.7 : 0.54) + .rgb() + .string(); + } + }; + + _onTextLayout = ({ nativeEvent }) => { + this.setState({ textHeight: nativeEvent.layout.height }); + }; + + render() { + const { style, type, visible } = this.props; + const textContainerStyle = { + paddingVertical: 4, + opacity: this.state.shown, + transform: + visible && type === 'error' + ? [ + { + translateY: this.state.shown.interpolate({ + inputRange: [0, 1], + outputRange: [-this.state.textHeight, 0], + }), + }, + ] + : [], + }; + + return ( + + + {this.props.children} + + + ); + } +} + +const styles = StyleSheet.create({ + text: { + fontSize: 12, + }, +}); + +export default withTheme(HelperText); diff --git a/src/components/TextInput.js b/src/components/TextInput.js index ae36b5dba2..b6a4f33b1a 100644 --- a/src/components/TextInput.js +++ b/src/components/TextInput.js @@ -13,6 +13,13 @@ import type { Theme } from '../types'; const AnimatedText = Animated.createAnimatedComponent(Text); +const MINIMIZED_LABEL_Y_OFFSET = -22; +const MAXIMIZED_LABEL_FONT_SIZE = 16; +const MINIMIZED_LABEL_FONT_SIZE = 12; +const LABEL_WIGGLE_X_OFFSET = 4; +const FOCUS_ANIMATION_MILLIS = 150; +const BLUR_ANIMATION_MILLIS = 180; + type Props = { /** * If true, user won't be able to interact with the component. @@ -26,6 +33,10 @@ type Props = { * Placeholder for the input. */ placeholder?: string, + /** + * Whether to style the TextInput with error style. + */ + error?: boolean, /** * Callback that is called when the text input's text changes. Changed text is passed as an argument to the callback handler. */ @@ -63,6 +74,7 @@ type Props = { type State = { focused: Animated.Value, + errorShown: Animated.Value, placeholder: ?string, value: ?string, }; @@ -110,15 +122,27 @@ type State = { class TextInput extends React.Component { static defaultProps = { disabled: false, + error: false, multiline: false, }; state = { focused: new Animated.Value(0), placeholder: '', + errorShown: new Animated.Value(this.props.error ? 1 : 0), value: this.props.value, }; + componentWillReceiveProps(nextProps) { + if (nextProps.error !== this.props.error) { + if (nextProps.error) { + this._animateErrorShown(); + } else { + this._animateErrorHidden(); + } + } + } + componentDidUpdate(prevProps, prevState) { if ( prevState.value !== this.state.value || @@ -157,25 +181,37 @@ class TextInput extends React.Component { _root: NativeTextInput; + _animateErrorShown = () => { + Animated.timing(this.state.errorShown, { + toValue: 1, + duration: FOCUS_ANIMATION_MILLIS, + }).start(this._setPlaceholder); + }; + + _animateErrorHidden = () => { + Animated.timing(this.state.errorShown, { + toValue: 0, + duration: BLUR_ANIMATION_MILLIS, + }).start(); + }; + _animateFocus = () => { Animated.timing(this.state.focused, { toValue: 1, - duration: 150, + duration: FOCUS_ANIMATION_MILLIS, }).start(this._setPlaceholder); }; _animateBlur = () => { this._removePlaceholder(); - Animated.timing(this.state.focused, { toValue: 0, - duration: 180, + duration: BLUR_ANIMATION_MILLIS, }).start(); }; _handleFocus = (...args) => { this._animateFocus(); - if (this.props.onFocus) { this.props.onFocus(...args); } @@ -183,7 +219,6 @@ class TextInput extends React.Component { _handleBlur = (...args) => { this._animateBlur(); - if (this.props.onBlur) { this.props.onBlur(...args); } @@ -194,6 +229,15 @@ class TextInput extends React.Component { this.props.onChangeText && this.props.onChangeText(value); }; + _getBottomLineStyle = (color: string, animatedValue: Animated.Value) => ({ + backgroundColor: color, + transform: [{ scaleX: animatedValue }], + opacity: animatedValue.interpolate({ + inputRange: [0, 0.1, 1], + outputRange: [0, 1, 1], + }), + }); + /** * @internal */ @@ -233,6 +277,7 @@ class TextInput extends React.Component { const { disabled, label, + error, underlineColor, style, theme, @@ -241,14 +286,17 @@ class TextInput extends React.Component { const { colors, fonts } = theme; const fontFamily = fonts.regular; - const primaryColor = colors.primary; - const inactiveColor = colors.disabled; + const { + primary: primaryColor, + disabled: inactiveColor, + error: errorColor, + } = colors; let inputTextColor, labelColor, bottomLineColor; if (!disabled) { inputTextColor = colors.text; - labelColor = primaryColor; + labelColor = (error && errorColor) || primaryColor; bottomLineColor = underlineColor || primaryColor; } else { inputTextColor = labelColor = bottomLineColor = inactiveColor; @@ -259,39 +307,40 @@ class TextInput extends React.Component { outputRange: [inactiveColor, labelColor], }); - const translateY = this.state.value - ? -22 + /* Wiggle when error appears and label is minimized */ + const labelTranslateX = + this.state.value && error + ? this.state.errorShown.interpolate({ + inputRange: [0, 0.5, 1], + outputRange: [0, LABEL_WIGGLE_X_OFFSET, 0], + }) + : 0; + + /* Move label to top if value is set */ + const labelTranslateY = this.state.value + ? MINIMIZED_LABEL_Y_OFFSET : this.state.focused.interpolate({ inputRange: [0, 1], - outputRange: [0, -22], + outputRange: [0, MINIMIZED_LABEL_Y_OFFSET], }); - const fontSize = this.state.value - ? 12 + + const labelFontSize = this.state.value + ? MINIMIZED_LABEL_FONT_SIZE : this.state.focused.interpolate({ inputRange: [0, 1], - outputRange: [16, 12], + outputRange: [MAXIMIZED_LABEL_FONT_SIZE, MINIMIZED_LABEL_FONT_SIZE], }); const labelStyle = { color: labelColorAnimation, fontFamily, - fontSize, + fontSize: labelFontSize, transform: [ - { - translateY, - }, + { translateX: labelTranslateX }, + { translateY: labelTranslateY }, ], }; - const bottomLineStyle = { - backgroundColor: bottomLineColor, - transform: [{ scaleX: this.state.focused }], - opacity: this.state.focused.interpolate({ - inputRange: [0, 0.1, 1], - outputRange: [0, 1, 1], - }), - }; - return ( { /> + diff --git a/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap b/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap index 3a590147ee..b33d87fe99 100644 --- a/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap +++ b/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap @@ -1507,6 +1507,7 @@ exports[`renders non-shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -1564,6 +1565,7 @@ exports[`renders non-shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -1762,6 +1764,7 @@ exports[`renders non-shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -1819,6 +1822,7 @@ exports[`renders non-shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -2017,6 +2021,7 @@ exports[`renders non-shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -2074,6 +2079,7 @@ exports[`renders non-shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -2386,6 +2392,7 @@ exports[`renders shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -2546,6 +2553,7 @@ exports[`renders shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -2706,6 +2714,7 @@ exports[`renders shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -2866,6 +2875,7 @@ exports[`renders shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -3026,6 +3036,7 @@ exports[`renders shifting bottom navigation 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", diff --git a/src/components/__tests__/__snapshots__/ListItem.test.js.snap b/src/components/__tests__/__snapshots__/ListItem.test.js.snap index 76881e3bcf..3aff22843d 100644 --- a/src/components/__tests__/__snapshots__/ListItem.test.js.snap +++ b/src/components/__tests__/__snapshots__/ListItem.test.js.snap @@ -96,6 +96,7 @@ exports[`renders list item with avatar 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -219,6 +220,7 @@ exports[`renders list item with avatar and icon 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -264,6 +266,7 @@ exports[`renders list item with avatar and icon 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -433,6 +436,7 @@ exports[`renders list item with icon 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -531,6 +535,7 @@ exports[`renders list item with title and description 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -576,6 +581,7 @@ exports[`renders list item with title and description 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", diff --git a/src/components/__tests__/__snapshots__/ListSection.test.js.snap b/src/components/__tests__/__snapshots__/ListSection.test.js.snap index 364f586c2d..2f536c6db1 100644 --- a/src/components/__tests__/__snapshots__/ListSection.test.js.snap +++ b/src/components/__tests__/__snapshots__/ListSection.test.js.snap @@ -40,6 +40,7 @@ exports[`renders list section with title 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -167,6 +168,7 @@ exports[`renders list section with title 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -297,6 +299,7 @@ exports[`renders list section with title 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -441,6 +444,7 @@ exports[`renders list section without title 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", @@ -571,6 +575,7 @@ exports[`renders list section without title 1`] = ` "accent": "#ff4081", "background": "#fafafa", "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", "paper": "#ffffff", "placeholder": "rgba(0, 0, 0, 0.38)", "primary": "#3f51b5", diff --git a/src/index.js b/src/index.js index 2d3d55e846..4fa85bd78c 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,7 @@ export { default as Divider } from './components/Divider'; export { default as DrawerItem } from './components/DrawerItem'; export { default as DrawerSection } from './components/DrawerSection'; export { default as FAB } from './components/FAB'; +export { default as HelperText } from './components/HelperText'; export { default as ListSection } from './components/ListSection'; export { default as ListItem } from './components/ListItem'; export { default as Modal } from './components/Modal'; diff --git a/src/styles/DefaultTheme.js b/src/styles/DefaultTheme.js index e107c3277c..233f0fb27a 100644 --- a/src/styles/DefaultTheme.js +++ b/src/styles/DefaultTheme.js @@ -1,7 +1,7 @@ /* @flow */ import color from 'color'; -import { indigo500, pinkA200, black, white, grey50 } from './colors'; +import { indigo500, pinkA200, black, white, grey50, redA400 } from './colors'; import fonts from './fonts'; export default { @@ -12,6 +12,7 @@ export default { accent: pinkA200, background: grey50, paper: white, + error: redA400, text: black, disabled: color(black) .alpha(0.26) diff --git a/src/types.js b/src/types.js index a4b90a46d7..7bf17f07f6 100644 --- a/src/types.js +++ b/src/types.js @@ -8,6 +8,7 @@ export type Theme = { background: string, paper: string, accent: string, + error: string, text: string, disabled: string, placeholder: string,