diff --git a/.babelrc b/.babelrc
new file mode 100644
index 00000000..a9ce1369
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["react-native"]
+}
diff --git a/.codeclimate.yml b/.codeclimate.yml
index b12fee86..05e05cef 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -12,7 +12,7 @@ checks:
plugins:
eslint:
enabled: true
- channel: "eslint-5"
+ channel: "eslint-4"
exclude_patterns:
- "example/"
- "**/test.js"
diff --git a/.eslintrc b/.eslintrc
index 0c229732..c5360064 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,14 +1,8 @@
{
'parser': 'babel-eslint',
- 'env': {
- 'es6': true,
- 'react-native/react-native': true,
- },
-
'plugins': [
'react',
- 'react-native'
],
'extends': [
@@ -16,12 +10,6 @@
'plugin:react/recommended',
],
- 'settings': {
- 'react': {
- 'version': '16.9',
- },
- },
-
'rules': {
'no-unused-vars': ['error', {
'vars': 'all',
@@ -29,183 +17,7 @@
'ignoreRestSiblings': true,
}],
- 'no-console': ['error'],
-
- /*
- * let foo = [1];
- * let bar = [
- * 1,
- * ];
- */
- 'array-bracket-newline': ['error', 'consistent'],
-
- /*
- * let foo = [1, 2, 3];
- */
- 'array-bracket-spacing': ['error', 'never'],
-
- /*
- * if (foo) { bar() }
- */
- 'block-spacing': ['error', 'always'],
-
- /*
- * if (foo) {
- * bar();
- * } else {
- * baz();
- * }
- */
- 'brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
-
- /*
- * let foo = [
- * 1,
- * ];
- */
'comma-dangle': ['error', 'always-multiline'],
-
- /*
- * let foo = [1, 2];
- */
- 'comma-spacing': ['error', { 'before': false, 'after': true }],
-
- /*
- * let foo,
- * bar,
- * baz;
- */
- 'comma-style': ['error', 'last'],
-
- /*
- * let foo = bar[bar];
- */
- 'computed-property-spacing': ['error', 'never'],
-
- /*
- * let that = this;
- */
- 'consistent-this': ['error', 'that'],
-
- /*
- * call();
- */
- 'func-call-spacing': ['error', 'never'],
-
- /*
- * function bar() { 1; }
- * let bar = () => 1;
- */
- 'func-style': ['error', 'declaration', { 'allowArrowFunctions': true }],
-
- /*
- * foo(1);
- * bar(
- * 2, 3
- * );
- */
- 'function-paren-newline': ['error', 'consistent'],
-
- /*
- * () => 1;
- */
- 'implicit-arrow-linebreak': ['error', 'beside'],
-
- /*
- * [
- * 1
- * ]
- */
- 'indent': ['warn', 2, { 'SwitchCase': 1 }],
-
- /*
- *
- */
- 'jsx-quotes': ['error', 'prefer-single'],
-
- /*
- * let foo = { bar: true };
- */
- 'key-spacing': ['error', { 'beforeColon': false, 'afterColon': true }],
-
- /*
- * if (foo) { return; }
- */
- 'keyword-spacing': ['error', { 'before': true, 'after': true }],
-
- 'linebreak-style': ['error', 'unix'],
-
- 'lines-between-class-members': ['error', 'always'],
-
- 'max-len': ['warn', { 'code': 100, 'ignoreTrailingComments': true }],
-
- /*
- * foo(1, 2, 3, 4, 5);
- */
- 'max-params': ['error', { 'max': 6 }],
-
- 'max-statements-per-line': ['error', { 'max': 1 }],
-
- 'new-parens': ['error'],
-
- 'no-trailing-spaces': ['error'],
-
- 'no-whitespace-before-property': ['error'],
-
- /*
- * let foo = { bar, baz };
- */
- 'object-curly-newline': ['error', { 'consistent': true }],
- 'object-curly-spacing': ['error', 'always'],
- 'object-property-newline': ['error', { 'allowAllPropertiesOnSameLine': true }],
-
- /*
- * let foo = 'bar'
- * + 'baz';
- */
- 'operator-linebreak': ['error', 'before', { 'overrides': { '?': 'after', ':': 'after' } }],
-
- /*
- * 'bar'
- */
- 'quotes': ['warn', 'single'],
-
- /*
- * 1;
- */
- 'semi': ['warn', 'always'],
- 'semi-spacing': ['error', { 'before': false, 'after': true }],
- 'semi-style': ['error', 'last'],
-
- /*
- * () => {
- * };
- */
- 'space-before-blocks': ['error', 'always'],
-
- /*
- * foo(1, 2, 3);
- */
- 'space-before-function-paren': ['error', {
- 'anonymous': 'always',
- 'named': 'never',
- 'asyncArrow': 'always',
- }],
-
- /*
- * foo('bar');
- */
- 'space-in-parens': ['error', 'never'],
-
- /*
- * switch (a) {
- * case 0: break;
- * }
- */
- 'switch-colon-spacing': ['error', { 'after': true, 'before': false }],
-
- 'react-native/no-unused-styles': ['warn'],
- 'react-native/no-inline-styles': ['warn'],
},
}
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 00000000..44ce6534
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,2 @@
+example
+coverage
diff --git a/.travis.yml b/.travis.yml
index 6953d40c..7a56d2a5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,3 @@
-os: osx
language: node_js
-
node_js:
- "stable"
diff --git a/index.js b/index.js
index 60c77f35..bb8fd6ee 100644
--- a/index.js
+++ b/index.js
@@ -1,5 +1,3 @@
import TextField from './src/components/field';
-import FilledTextField from './src/components/field-filled';
-import OutlinedTextField from './src/components/field-outlined';
-export { TextField, FilledTextField, OutlinedTextField };
+export { TextField };
diff --git a/package.json b/package.json
index 1e63b0fb..cc741a88 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-material-textfield",
- "version": "0.16.1",
+ "version": "0.12.0",
"license": "BSD-3-Clause",
"author": "Alexander Nazarov ",
@@ -21,7 +21,6 @@
],
"main": "index.js",
- "files": ["index.js", "src/*", "license.txt", "readme.md", "changelog.md"],
"repository": {
"type": "git",
@@ -33,28 +32,25 @@
},
"peerDependencies": {
- "react": ">=16.3.0",
- "react-native": ">=0.55.0"
+ "react": "*",
+ "react-native": "*"
},
"devDependencies": {
- "@babel/core": "^7.5.5",
- "@babel/runtime": "^7.5.5",
- "babel-eslint": "^10.0.0",
- "babel-jest": "^24.9.0",
- "eslint": "^6.5.0",
- "eslint-plugin-react": "^7.16.0",
- "eslint-plugin-react-native": "^3.7.0",
- "jest": "^24.9.0",
- "metro-react-native-babel-preset": "^0.56.0",
- "react": "16.10.2",
- "react-native": "0.61.2",
- "react-test-renderer": "16.10.2"
+ "babel-eslint": "^8.0.0",
+ "babel-jest": "^22.0.0",
+ "babel-preset-react-native": "4.0.0",
+ "eslint": "^4.6.0",
+ "eslint-plugin-react": "^7.0.0",
+ "jest": "^22.0.0",
+ "react": "16.2.0",
+ "react-native": "0.52.0",
+ "react-test-renderer": "16.2.0"
},
"scripts": {
- "test": "eslint index.js src && jest --silent",
- "lint": "eslint index.js src example/app.js",
+ "test": "npm run lint && npm run jest -- --silent",
+ "lint": "eslint src example/app.js",
"jest": "jest"
},
diff --git a/readme.md b/readme.md
index 4083fb2f..55d215e1 100644
--- a/readme.md
+++ b/readme.md
@@ -28,12 +28,10 @@ Material texfield with consistent behaviour on iOS and Android
* Animated state transitions (normal, focused and errored)
* Customizable font size, colors and animation duration
* Disabled state (with dotted underline)
-* Outlined and filled fields
-* Masked input support
* Multiline text input
* Character counter
* Prefix and suffix
-* Accessory views
+* Accessory view
* Helper text
* RTL support
* Pure javascript implementation
@@ -48,33 +46,21 @@ npm install --save react-native-material-textfield
```javascript
import React, { Component } from 'react';
-import {
- TextField,
- FilledTextField,
- OutlinedTextField,
-} from 'react-native-material-textfield';
+import { TextField } from 'react-native-material-textfield';
class Example extends Component {
- fieldRef = React.createRef();
-
- onSubmit = () => {
- let { current: field } = this.fieldRef;
-
- console.log(field.value());
- };
-
- formatText = (text) => {
- return text.replace(/[^+\d]/g, '');
+ state = {
+ phone: '',
};
render() {
+ let { phone } = this.state;
+
return (
- this.setState({ phone }) }
/>
);
}
@@ -87,7 +73,11 @@ class Example extends Component {
:--------------------- |:------------------------------------------- | --------:|:------------------
textColor | Text input color | String | rgba(0, 0, 0, .87)
fontSize | Text input font size | Number | 16
+ titleFontSize | Text field title and error fontSize | Number | 12
labelFontSize | Text field label font size | Number | 12
+ labelHeight | Text field label base height | Number | 32
+ labelPadding | Text field label base padding | Number | 4
+ inputContainerPadding | Text field input container base padding | Number | 8
lineWidth | Text field underline width | Number | 0.5
activeLineWidth | Text field active underline width | Number | 2
disabledLineWidth | Text field disabled underline width | Number | 1
@@ -99,62 +89,34 @@ class Example extends Component {
suffix | Text field suffix text | String | -
error | Text field error text | String | -
errorColor | Text field color for errored state | String | rgb(213, 0, 0)
- lineType | Text field line type | String | solid
disabledLineType | Text field line type in disabled state | String | dotted
animationDuration | Text field animation duration in ms | Number | 225
characterRestriction | Text field soft limit for character counter | Number | -
disabled | Text field availability | Boolean | false
editable | Text field text can be edited | Boolean | true
multiline | Text filed multiline input | Boolean | false
- contentInset | Layout configuration object | Object | [{...}](#content-inset)
- labelOffset | Label position adjustment | Object | [{...}](#label-offset)
inputContainerStyle | Style for input container view | Object | -
containerStyle | Style for container view | Object | -
labelTextStyle | Style for label inner Text component | Object | -
titleTextStyle | Style for title inner Text component | Object | -
affixTextStyle | Style for affix inner Text component | Object | -
- formatText | Input mask callback | Function | -
- renderLeftAccessory | Render left input accessory view | Function | -
- renderRightAccessory | Render right input accessory view | Function | -
+ renderAccessory | Render input accessory view | Function | -
onChangeText | Change text callback | Function | -
onFocus | Focus callback | Function | -
onBlur | Blur callback | Function | -
-Other [TextInput][rn-textinput] properties will also work.
-
-### Content Inset
-
- name | description | Normal | Filled | Outlined
-:----- |:--------------------------------- | ------:| ------:| --------:
- top | Inset on the top side | 16 | 8 | 0
- left | Inset on the left side | 0 | 12 | 12
- right | Inset on the right side | 0 | 12 | 12
- label | Space between label and TextInput | 4 | 4 | 4
- input | Space between line and TextInput | 8 | 8 | 16
-
-### Label Offset
-
- name | description | Normal | Filled | Outlined
-:---- |:------------------------------------ | ------:| ------:| --------:
- x0 | Horizontal offset for inactive state | 0 | 0 | 0
- y0 | Vertical offset for inactive state | 0 | -10 | 0
- x1 | Horizontal offset for active state | 0 | 0 | 0
- y1 | Vertical offset for active state | 0 | -2 | -10
+Other [TextInput][rn-textinput] properties will also work
## Methods
- name | description | returns
-:---------------------- |:----------------------------- | -------:
- focus() | Acquire focus | -
- blur() | Release focus | -
- clear() | Clear text field | -
- value() | Get current value | String
- isFocused() | Get current focus state | Boolean
- isErrored() | Get current error state | Boolean
- isRestricted() | Get current restriction state | Boolean
- isDefaultVisible() | Get default value visibility | Boolean
- isPlaceholderVisible() | Get placeholder visibility | Boolean
- setValue() | Set current value | -
+ name | description | returns
+:-------------- |:----------------------------- | -------:
+ focus() | Acquire focus | -
+ blur() | Release focus | -
+ clear() | Clear text field | -
+ value() | Get current value | String
+ isFocused() | Get current focus state | Boolean
+ isRestricted() | Get current restriction state | Boolean
## Example
@@ -169,4 +131,4 @@ npm run ios # or npm run android
BSD License
-Copyright 2017-2019 Alexander Nazarov. All rights reserved.
+Copyright 2017 Alexander Nazarov. All rights reserved.
diff --git a/src/components/affix/__snapshots__/test.js.snap b/src/components/affix/__snapshots__/test.js.snap
index 4e065728..8ae91131 100644
--- a/src/components/affix/__snapshots__/test.js.snap
+++ b/src/components/affix/__snapshots__/test.js.snap
@@ -1,81 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`renders inactive prefix 1`] = `
-
-
- a
-
-
-`;
-
-exports[`renders inactive suffix 1`] = `
-
-
- z
-
-
-`;
-
exports[`renders prefix 1`] = `
@@ -86,24 +34,28 @@ exports[`renders prefix 1`] = `
exports[`renders suffix 1`] = `
diff --git a/src/components/affix/index.js b/src/components/affix/index.js
index 0f85022e..d7cfd41f 100644
--- a/src/components/affix/index.js
+++ b/src/components/affix/index.js
@@ -7,22 +7,24 @@ import styles from './styles';
export default class Affix extends PureComponent {
static defaultProps = {
numberOfLines: 1,
+
+ active: false,
+ focused: false,
};
static propTypes = {
numberOfLines: PropTypes.number,
- style: Animated.Text.propTypes.style,
- color: PropTypes.string.isRequired,
- fontSize: PropTypes.number.isRequired,
+ active: PropTypes.bool,
+ focused: PropTypes.bool,
- type: PropTypes
- .oneOf(['prefix', 'suffix'])
- .isRequired,
+ type: PropTypes.oneOf(['prefix', 'suffix']).isRequired,
+
+ fontSize: PropTypes.number.isRequired,
+ baseColor: PropTypes.string.isRequired,
+ animationDuration: PropTypes.number.isRequired,
- labelAnimation: PropTypes
- .instanceOf(Animated.Value)
- .isRequired,
+ // style: Animated.Text.propTypes.style,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
@@ -30,20 +32,42 @@ export default class Affix extends PureComponent {
]),
};
+ constructor(props) {
+ super(props);
+
+ let { active, focused } = this.props;
+
+ this.state = {
+ opacity: new Animated.Value((active || focused)? 1 : 0),
+ };
+ }
+
+ componentWillReceiveProps(props) {
+ let { opacity } = this.state;
+ let { active, focused, animationDuration } = this.props;
+
+ if ((focused ^ props.focused) || (active ^ props.active)) {
+ Animated
+ .timing(opacity, {
+ toValue: (props.active || props.focused)? 1 : 0,
+ duration: animationDuration,
+ })
+ .start();
+ }
+ }
+
render() {
- let { labelAnimation, style, children, type, fontSize, color } = this.props;
+ let { opacity } = this.state;
+ let { style, children, type, fontSize, baseColor: color } = this.props;
let containerStyle = {
height: fontSize * 1.5,
- opacity: labelAnimation,
+ opacity,
};
let textStyle = {
- includeFontPadding: false,
- textAlignVertical: 'top',
-
- fontSize,
color,
+ fontSize,
};
switch (type) {
diff --git a/src/components/affix/styles.js b/src/components/affix/styles.js
index 2ac28a6d..ca4e1fcd 100644
--- a/src/components/affix/styles.js
+++ b/src/components/affix/styles.js
@@ -3,6 +3,7 @@ import { StyleSheet } from 'react-native';
export default StyleSheet.create({
container: {
top: 2,
+ alignSelf: 'flex-start',
justifyContent: 'center',
},
});
diff --git a/src/components/affix/test.js b/src/components/affix/test.js
index 86d1aa0f..6bfa4430 100644
--- a/src/components/affix/test.js
+++ b/src/components/affix/test.js
@@ -1,6 +1,5 @@
import 'react-native';
import React from 'react';
-import { Animated } from 'react-native';
import renderer from 'react-test-renderer';
import Affix from '.';
@@ -8,10 +7,9 @@ import Affix from '.';
/* eslint-env jest */
const props = {
- color: 'black',
fontSize: 16,
-
- labelAnimation: new Animated.Value(1),
+ baseColor: 'blue',
+ animationDuration: 225,
};
const prefix = 'a';
@@ -26,19 +24,6 @@ it('renders prefix', () => {
.toMatchSnapshot();
});
-it('renders inactive prefix', () => {
- let affix = renderer
- .create(
-
- {prefix}
-
- )
- .toJSON();
-
- expect(affix)
- .toMatchSnapshot();
-});
-
it('renders suffix', () => {
let affix = renderer
.create({suffix})
@@ -47,16 +32,3 @@ it('renders suffix', () => {
expect(affix)
.toMatchSnapshot();
});
-
-it('renders inactive suffix', () => {
- let affix = renderer
- .create(
-
- {suffix}
-
- )
- .toJSON();
-
- expect(affix)
- .toMatchSnapshot();
-});
diff --git a/src/components/counter/__snapshots__/test.js.snap b/src/components/counter/__snapshots__/test.js.snap
index 71ab773f..815c25cb 100644
--- a/src/components/counter/__snapshots__/test.js.snap
+++ b/src/components/counter/__snapshots__/test.js.snap
@@ -1,51 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders when limit is exceeded 1`] = `
-
- 2
- /
- 1
-
+
+ 2
+ /
+ 1
+
+
`;
exports[`renders when limit is set 1`] = `
-
- 1
- /
- 1
-
+
+ 1
+ /
+ 1
+
+
`;
diff --git a/src/components/counter/index.js b/src/components/counter/index.js
index 35d3264f..ad6e3133 100644
--- a/src/components/counter/index.js
+++ b/src/components/counter/index.js
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
-import { Text } from 'react-native';
+import { View, Text } from 'react-native';
import styles from './styles';
@@ -9,6 +9,8 @@ export default class Counter extends PureComponent {
count: PropTypes.number.isRequired,
limit: PropTypes.number,
+ fontSize: PropTypes.number,
+
baseColor: PropTypes.string.isRequired,
errorColor: PropTypes.string.isRequired,
@@ -16,22 +18,23 @@ export default class Counter extends PureComponent {
};
render() {
- let { count, limit, baseColor, errorColor, style } = this.props;
+ let { count, limit, baseColor, errorColor, fontSize, style } = this.props;
+
+ let textStyle = {
+ color: count > limit? errorColor : baseColor,
+ fontSize,
+ };
if (!limit) {
return null;
}
- let textStyle = {
- color: count > limit?
- errorColor:
- baseColor,
- };
-
return (
-
- {count} / {limit}
-
+
+
+ {count} / {limit}
+
+
);
}
}
diff --git a/src/components/counter/styles.js b/src/components/counter/styles.js
index 8eb9cdf4..5a3a718d 100644
--- a/src/components/counter/styles.js
+++ b/src/components/counter/styles.js
@@ -1,12 +1,13 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
+ container: {
+ paddingVertical: 4,
+ paddingLeft: 4,
+ },
+
text: {
- fontSize: 12,
- lineHeight: 16,
textAlign: 'right',
backgroundColor: 'transparent',
- paddingVertical: 2,
- marginLeft: 8,
},
});
diff --git a/src/components/field/__snapshots__/test.js.snap b/src/components/field/__snapshots__/test.js.snap
index 705e7bf3..caf36655 100644
--- a/src/components/field/__snapshots__/test.js.snap
+++ b/src/components/field/__snapshots__/test.js.snap
@@ -5,52 +5,98 @@ exports[`renders 1`] = `
onResponderRelease={[Function]}
onStartShouldSetResponder={[Function]}
pointerEvents="auto"
+ style={undefined}
>
-
+ test
+
+
+
+
+
+
- test
-
+ />
-
-
`;
-exports[`renders counter 1`] = `
+exports[`renders accessory 1`] = `
-
+ test
+
+
+
+
+
+
+
+
+
- test
-
+ />
-
+
+`;
+
+exports[`renders counter 1`] = `
+
+
+
+ test
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
- 4
- /
- 10
-
+
+ 4
+ /
+ 10
+
+
`;
@@ -346,52 +540,98 @@ exports[`renders default value 1`] = `
onResponderRelease={[Function]}
onStartShouldSetResponder={[Function]}
pointerEvents="auto"
+ style={undefined}
>
-
+ test
+
+
+
+
+
+
- test
-
+ />
-
-
`;
@@ -505,52 +704,116 @@ exports[`renders disabled value 1`] = `
onResponderRelease={[Function]}
onStartShouldSetResponder={[Function]}
pointerEvents="none"
+ style={undefined}
>
+
-
+ test
+
+
+
+
+
+
- test
-
+ />
-
-
`;
@@ -664,52 +886,99 @@ exports[`renders error 1`] = `
onResponderRelease={[Function]}
onStartShouldSetResponder={[Function]}
pointerEvents="auto"
+ style={undefined}
>
-
+ test
+
+
+
+
+
+
- test
+ message
-
-
-
- message
-
-
`;
-exports[`renders left accessory 1`] = `
+exports[`renders multiline value 1`] = `
-
+ test
+
+
+
+
-
+
+
- test
-
+ />
-
-
`;
-exports[`renders multiline value 1`] = `
+exports[`renders prefix 1`] = `
-
+ >
+ test
+
- test
+ $
-
-
-
+ underlineColorAndroid="transparent"
+ value="text"
+ />
-
-`;
-
-exports[`renders prefix 1`] = `
-
-
-
-
-
- test
-
+ />
-
-
- $
-
-
-
-
`;
@@ -1349,52 +1413,98 @@ exports[`renders restriction 1`] = `
onResponderRelease={[Function]}
onStartShouldSetResponder={[Function]}
pointerEvents="auto"
+ style={undefined}
>
-
+ test
+
+
+
+
+
+
- test
-
+ />
-
-
-
-
- 4
- /
- 2
-
+
+ 4
+ /
+ 2
+
+
`;
-exports[`renders right accessory 1`] = `
+exports[`renders suffix 1`] = `
-
+ >
+ test
+
+
- test
+ .com
-
-
-
-
-
-`;
-
-exports[`renders suffix 1`] = `
-
-
-
-
-
- test
-
+ />
-
-
-
- .com
-
-
+ />
-
`;
@@ -1875,52 +1802,98 @@ exports[`renders title 1`] = `
onResponderRelease={[Function]}
onStartShouldSetResponder={[Function]}
pointerEvents="auto"
+ style={undefined}
>
-
+ test
+
+
+
+
+
+
- test
-
+ />
-
+ Object {
+ "backgroundColor": "transparent",
+ "color": "rgba(0, 0, 0, .38)",
+ "fontSize": 12,
+ "opacity": 1,
+ }
+ }
+ >
+ field
+
-
-
- field
-
-
`;
@@ -2051,52 +1968,98 @@ exports[`renders value 1`] = `
onResponderRelease={[Function]}
onStartShouldSetResponder={[Function]}
pointerEvents="auto"
+ style={undefined}
>
-
+ test
+
+
+
+
+
+
- test
-
+ />
-
-
`;
diff --git a/src/components/field/index.js b/src/components/field/index.js
index 281b7451..52c1868a 100644
--- a/src/components/field/index.js
+++ b/src/components/field/index.js
@@ -8,34 +8,18 @@ import {
StyleSheet,
Platform,
ViewPropTypes,
+ I18nManager,
} from 'react-native';
+import RN from 'react-native/package.json';
+
import Line from '../line';
import Label from '../label';
import Affix from '../affix';
import Helper from '../helper';
import Counter from '../counter';
-import styles from './styles';
-
-function startAnimation(animation, options, callback) {
- Animated
- .timing(animation, options)
- .start(callback);
-}
-
-function labelStateFromProps(props, state) {
- let { placeholder, defaultValue } = props;
- let { text, receivedFocus } = state;
-
- return !!(placeholder || text || (!receivedFocus && defaultValue));
-}
-
-function errorStateFromProps(props, state) {
- let { error } = props;
-
- return !!error;
-}
+import styles from './styles.js';
export default class TextField extends PureComponent {
static defaultProps = {
@@ -47,7 +31,11 @@ export default class TextField extends PureComponent {
animationDuration: 225,
fontSize: 16,
+ titleFontSize: 12,
labelFontSize: 12,
+ labelHeight: 32,
+ labelPadding: 4,
+ inputContainerPadding: 8,
tintColor: 'rgb(0, 145, 234)',
textColor: 'rgba(0, 0, 0, .87)',
@@ -57,12 +45,10 @@ export default class TextField extends PureComponent {
lineWidth: StyleSheet.hairlineWidth,
activeLineWidth: 2,
- disabledLineWidth: 1,
-
- lineType: 'solid',
- disabledLineType: 'dotted',
disabled: false,
+ disabledLineType: 'dotted',
+ disabledLineWidth: 1,
};
static propTypes = {
@@ -71,18 +57,11 @@ export default class TextField extends PureComponent {
animationDuration: PropTypes.number,
fontSize: PropTypes.number,
+ titleFontSize: PropTypes.number,
labelFontSize: PropTypes.number,
-
- contentInset: PropTypes.shape({
- top: PropTypes.number,
- label: PropTypes.number,
- input: PropTypes.number,
- left: PropTypes.number,
- right: PropTypes.number,
- bottom: PropTypes.number,
- }),
-
- labelOffset: Label.propTypes.offset,
+ labelHeight: PropTypes.number,
+ labelPadding: PropTypes.number,
+ inputContainerPadding: PropTypes.number,
labelTextStyle: Text.propTypes.style,
titleTextStyle: Text.propTypes.style,
@@ -92,7 +71,7 @@ export default class TextField extends PureComponent {
textColor: PropTypes.string,
baseColor: PropTypes.string,
- label: PropTypes.string,
+ label: PropTypes.string.isRequired,
title: PropTypes.string,
characterRestriction: PropTypes.number,
@@ -102,17 +81,12 @@ export default class TextField extends PureComponent {
lineWidth: PropTypes.number,
activeLineWidth: PropTypes.number,
- disabledLineWidth: PropTypes.number,
-
- lineType: Line.propTypes.lineType,
- disabledLineType: Line.propTypes.lineType,
disabled: PropTypes.bool,
+ disabledLineType: Line.propTypes.type,
+ disabledLineWidth: PropTypes.number,
- formatText: PropTypes.func,
-
- renderLeftAccessory: PropTypes.func,
- renderRightAccessory: PropTypes.func,
+ renderAccessory: PropTypes.func,
prefix: PropTypes.string,
suffix: PropTypes.string,
@@ -121,33 +95,6 @@ export default class TextField extends PureComponent {
inputContainerStyle: (ViewPropTypes || View.propTypes).style,
};
- static inputContainerStyle = styles.inputContainer;
-
- static contentInset = {
- top: 16,
- label: 4,
- input: 8,
- left: 0,
- right: 0,
- bottom: 8,
- };
-
- static labelOffset = {
- x0: 0,
- y0: 0,
- x1: 0,
- y1: 0,
- };
-
- static getDerivedStateFromProps({ error }, state) {
- /* Keep last received error in state */
- if (error && error !== state.error) {
- return { error };
- }
-
- return null;
- }
-
constructor(props) {
super(props);
@@ -159,38 +106,39 @@ export default class TextField extends PureComponent {
this.onContentSizeChange = this.onContentSizeChange.bind(this);
this.onFocusAnimationEnd = this.onFocusAnimationEnd.bind(this);
- this.createGetter('contentInset');
- this.createGetter('labelOffset');
+ this.updateRef = this.updateRef.bind(this, 'input');
- this.inputRef = React.createRef();
- this.mounted = false;
- this.focused = false;
-
- let { value: text, error, fontSize } = this.props;
-
- let labelState = labelStateFromProps(this.props, { text })? 1 : 0;
- let focusState = errorStateFromProps(this.props)? -1 : 0;
+ let { value, error, fontSize } = this.props;
+ this.mounted = false;
this.state = {
- text,
- error,
-
- focusAnimation: new Animated.Value(focusState),
- labelAnimation: new Animated.Value(labelState),
+ text: value,
+ focus: new Animated.Value(this.focusState(error, false)),
+ focused: false,
receivedFocus: false,
+ error: error,
+ errored: !!error,
+
height: fontSize * 1.5,
};
}
- createGetter(name) {
- this[name] = () => {
- let { [name]: value } = this.props;
- let { [name]: defaultValue } = this.constructor;
+ componentWillReceiveProps(props) {
+ let { error } = this.state;
- return { ...defaultValue, ...value };
- };
+ if (null != props.value) {
+ this.setState({ text: props.value });
+ }
+
+ if (props.error && props.error !== error) {
+ this.setState({ error: props.error });
+ }
+
+ if (props.error !== this.props.error) {
+ this.setState({ errored: !!props.error });
+ }
}
componentDidMount() {
@@ -201,151 +149,68 @@ export default class TextField extends PureComponent {
this.mounted = false;
}
- componentDidUpdate(prevProps, prevState) {
- let errorState = errorStateFromProps(this.props);
- let prevErrorState = errorStateFromProps(prevProps);
+ componentWillUpdate(props, state) {
+ let { error, animationDuration: duration } = this.props;
+ let { focus, focused } = this.state;
- if (errorState ^ prevErrorState) {
- this.startFocusAnimation();
- }
+ if (props.error !== error || focused ^ state.focused) {
+ let toValue = this.focusState(props.error, state.focused);
- let labelState = labelStateFromProps(this.props, this.state);
- let prevLabelState = labelStateFromProps(prevProps, prevState);
-
- if (labelState ^ prevLabelState) {
- this.startLabelAnimation();
+ Animated
+ .timing(focus, { toValue, duration })
+ .start(this.onFocusAnimationEnd);
}
}
- startFocusAnimation() {
- let { focusAnimation } = this.state;
- let { animationDuration: duration } = this.props;
-
- let options = {
- toValue: this.focusState(),
- duration,
- };
-
- startAnimation(focusAnimation, options, this.onFocusAnimationEnd);
- }
-
- startLabelAnimation() {
- let { labelAnimation } = this.state;
- let { animationDuration: duration } = this.props;
-
- let options = {
- toValue: this.labelState(),
- useNativeDriver: true,
- duration,
- };
-
- startAnimation(labelAnimation, options);
- }
-
- setNativeProps(props) {
- let { current: input } = this.inputRef;
-
- input.setNativeProps(props);
+ updateRef(name, ref) {
+ this[name] = ref;
}
- focusState() {
- if (errorStateFromProps(this.props)) {
- return -1;
- }
-
- return this.focused? 1 : 0;
- }
-
- labelState() {
- if (labelStateFromProps(this.props, this.state)) {
- return 1;
- }
-
- return this.focused? 1 : 0;
+ focusState(error, focused) {
+ return error? -1 : (focused? 1 : 0);
}
focus() {
let { disabled, editable } = this.props;
- let { current: input } = this.inputRef;
if (!disabled && editable) {
- input.focus();
+ this.input.focus();
}
}
blur() {
- let { current: input } = this.inputRef;
-
- input.blur();
+ this.input.blur();
}
clear() {
- let { current: input } = this.inputRef;
-
- input.clear();
+ this.input.clear();
/* onChangeText is not triggered by .clear() */
this.onChangeText('');
}
value() {
- let { text } = this.state;
- let { defaultValue } = this.props;
-
- let value = this.isDefaultVisible()?
- defaultValue:
- text;
-
- if (null == value) {
- return '';
- }
-
- return 'string' === typeof value?
- value:
- String(value);
- }
+ let { text, receivedFocus } = this.state;
+ let { value, defaultValue } = this.props;
- setValue(text) {
- this.setState({ text });
+ return (receivedFocus || null != value || null == defaultValue)?
+ text:
+ defaultValue;
}
isFocused() {
- let { current: input } = this.inputRef;
-
- return input.isFocused();
+ return this.input.isFocused();
}
isRestricted() {
- let { characterRestriction: limit } = this.props;
- let { length: count } = this.value();
-
- return limit < count;
- }
+ let { characterRestriction } = this.props;
+ let { text = '' } = this.state;
- isErrored() {
- return errorStateFromProps(this.props);
- }
-
- isDefaultVisible() {
- let { text, receivedFocus } = this.state;
- let { defaultValue } = this.props;
-
- return !receivedFocus && null == text && null != defaultValue;
- }
-
- isPlaceholderVisible() {
- let { placeholder } = this.props;
-
- return placeholder && !this.focused && !this.value();
- }
-
- isLabelActive() {
- return 1 === this.labelState();
+ return characterRestriction < text.length;
}
onFocus(event) {
let { onFocus, clearTextOnFocus } = this.props;
- let { receivedFocus } = this.state;
if ('function' === typeof onFocus) {
onFocus(event);
@@ -355,14 +220,7 @@ export default class TextField extends PureComponent {
this.clear();
}
- this.focused = true;
-
- this.startFocusAnimation();
- this.startLabelAnimation();
-
- if (!receivedFocus) {
- this.setState({ receivedFocus: true, text: this.value() });
- }
+ this.setState({ focused: true, receivedFocus: true });
}
onBlur(event) {
@@ -372,26 +230,26 @@ export default class TextField extends PureComponent {
onBlur(event);
}
- this.focused = false;
-
- this.startFocusAnimation();
- this.startLabelAnimation();
+ this.setState({ focused: false });
}
onChange(event) {
- let { onChange } = this.props;
+ let { onChange, multiline } = this.props;
if ('function' === typeof onChange) {
onChange(event);
}
+
+ /* XXX: onContentSizeChange is not called on RN 0.44 and 0.45 */
+ if (multiline && 'android' === Platform.OS) {
+ if (/^0\.4[45]\./.test(RN.version)) {
+ this.onContentSizeChange(event);
+ }
+ }
}
onChangeText(text) {
- let { onChangeText, formatText } = this.props;
-
- if ('function' === typeof formatText) {
- text = formatText(text);
- }
+ let { onChangeText } = this.props;
this.setState({ text });
@@ -411,130 +269,38 @@ export default class TextField extends PureComponent {
this.setState({
height: Math.max(
fontSize * 1.5,
- Math.ceil(height) + Platform.select({ ios: 4, android: 1 })
+ Math.ceil(height) + Platform.select({ ios: 5, android: 1 })
),
});
}
onFocusAnimationEnd() {
- let { error } = this.props;
- let { error: retainedError } = this.state;
-
- if (this.mounted && !error && retainedError) {
- this.setState({ error: null });
+ if (this.mounted) {
+ this.setState((state, { error }) => ({ error }));
}
}
- inputHeight() {
- let { height: computedHeight } = this.state;
- let { multiline, fontSize, height = computedHeight } = this.props;
-
- return multiline?
- height:
- fontSize * 1.5;
- }
-
- inputContainerHeight() {
- let { labelFontSize, multiline } = this.props;
- let contentInset = this.contentInset();
-
- if ('web' === Platform.OS && multiline) {
- return 'auto';
- }
-
- return contentInset.top
- + labelFontSize
- + contentInset.label
- + this.inputHeight()
- + contentInset.input;
- }
-
- inputProps() {
- let store = {};
-
- for (let key in TextInput.propTypes) {
- if ('defaultValue' === key) {
- continue;
- }
+ renderAccessory() {
+ let { renderAccessory } = this.props;
- if (key in this.props) {
- store[key] = this.props[key];
- }
- }
-
- return store;
- }
-
- inputStyle() {
- let { fontSize, baseColor, textColor, disabled, multiline } = this.props;
-
- let color = disabled || this.isDefaultVisible()?
- baseColor:
- textColor;
-
- let style = {
- fontSize,
- color,
-
- height: this.inputHeight(),
- };
-
- if (multiline) {
- let lineHeight = fontSize * 1.5;
- let offset = 'ios' === Platform.OS? 2 : 0;
-
- style.height += lineHeight;
- style.transform = [{
- translateY: lineHeight + offset,
- }];
+ if ('function' !== typeof renderAccessory) {
+ return null;
}
- return style;
- }
-
- renderLabel(props) {
- let offset = this.labelOffset();
-
- let {
- label,
- fontSize,
- labelFontSize,
- labelTextStyle,
- } = this.props;
-
return (
-
- );
- }
-
- renderLine(props) {
- return (
-
+
+ {renderAccessory()}
+
);
}
- renderAccessory(prop) {
- let { [prop]: renderAccessory } = this.props;
-
- return 'function' === typeof renderAccessory?
- renderAccessory():
- null;
- }
-
- renderAffix(type) {
- let { labelAnimation } = this.state;
+ renderAffix(type, active, focused) {
let {
[type]: affix,
fontSize,
- baseColor: color,
- affixTextStyle: style,
+ baseColor,
+ animationDuration,
+ affixTextStyle,
} = this.props;
if (null == affix) {
@@ -543,122 +309,153 @@ export default class TextField extends PureComponent {
let props = {
type,
- style,
- color,
+ active,
+ focused,
fontSize,
- labelAnimation,
+ baseColor,
+ animationDuration,
};
return (
- {affix}
+ {affix}
);
}
- renderHelper() {
- let { focusAnimation, error } = this.state;
-
+ render() {
+ let { receivedFocus, focus, focused, error, errored, height, text = '' } = this.state;
let {
+ style: inputStyleOverrides,
+ label,
title,
+ value,
+ defaultValue,
+ characterRestriction: limit,
+ editable,
disabled,
+ disabledLineType,
+ disabledLineWidth,
+ animationDuration,
+ fontSize,
+ titleFontSize,
+ labelFontSize,
+ labelHeight,
+ labelPadding,
+ inputContainerPadding,
+ labelTextStyle,
+ titleTextStyle,
+ tintColor,
baseColor,
+ textColor,
errorColor,
- titleTextStyle: style,
- characterRestriction: limit,
+ lineWidth,
+ activeLineWidth,
+ containerStyle,
+ inputContainerStyle: inputContainerStyleOverrides,
+ clearTextOnFocus,
+ ...props
} = this.props;
- let { length: count } = this.value();
- let contentInset = this.contentInset();
+ if (props.multiline && props.height) {
+ /* Disable autogrow if height is passed as prop */
+ height = props.height;
+ }
- let containerStyle = {
- paddingLeft: contentInset.left,
- paddingRight: contentInset.right,
- minHeight: contentInset.bottom,
- };
+ let defaultVisible = !(receivedFocus || null != value || null == defaultValue);
- let styleProps = {
- style,
- baseColor,
- errorColor,
- };
+ value = defaultVisible?
+ defaultValue:
+ text;
- let counterProps = {
- ...styleProps,
- limit,
- count,
+ let active = !!(value || props.placeholder);
+ let count = value.length;
+ let restricted = limit < count;
+
+ let textAlign = I18nManager.isRTL?
+ 'right':
+ 'left';
+
+ let borderBottomColor = restricted?
+ errorColor:
+ focus.interpolate({
+ inputRange: [-1, 0, 1],
+ outputRange: [errorColor, baseColor, tintColor],
+ });
+
+ let borderBottomWidth = restricted?
+ activeLineWidth:
+ focus.interpolate({
+ inputRange: [-1, 0, 1],
+ outputRange: [activeLineWidth, lineWidth, activeLineWidth],
+ });
+
+ let inputContainerStyle = {
+ paddingTop: labelHeight,
+ paddingBottom: inputContainerPadding,
+
+ ...(disabled?
+ { overflow: 'hidden' }:
+ { borderBottomColor, borderBottomWidth }),
+
+ ...(props.multiline?
+ { height: 'web' === Platform.OS ? 'auto' : labelHeight + inputContainerPadding + height }:
+ { height: labelHeight + inputContainerPadding + fontSize * 1.5 }),
};
- let helperProps = {
- ...styleProps,
- title,
- error,
- disabled,
- focusAnimation,
+ let inputStyle = {
+ fontSize,
+ textAlign,
+
+ color: (disabled || defaultVisible)?
+ baseColor:
+ textColor,
+
+ ...(props.multiline?
+ {
+ height: fontSize * 1.5 + height,
+
+ ...Platform.select({
+ ios: { top: -1 },
+ android: { textAlignVertical: 'top' },
+ }),
+ }:
+ { height: fontSize * 1.5 }),
};
- return (
-
-
-
-
- );
- }
+ let errorStyle = {
+ color: errorColor,
- renderInput() {
- let {
- disabled,
- editable,
- tintColor,
- style: inputStyleOverrides,
- } = this.props;
+ opacity: focus.interpolate({
+ inputRange: [-1, 0, 1],
+ outputRange: [1, 0, 0],
+ }),
- let props = this.inputProps();
- let inputStyle = this.inputStyle();
+ fontSize: title?
+ titleFontSize:
+ focus.interpolate({
+ inputRange: [-1, 0, 1],
+ outputRange: [titleFontSize, 0, 0],
+ }),
+ };
- return (
-
- );
- }
+ let titleStyle = {
+ color: baseColor,
- render() {
- let { labelAnimation, focusAnimation } = this.state;
- let {
- editable,
- disabled,
- lineType,
- disabledLineType,
- lineWidth,
- activeLineWidth,
- disabledLineWidth,
- tintColor,
- baseColor,
- errorColor,
- containerStyle,
- inputContainerStyle: inputContainerStyleOverrides,
- } = this.props;
+ opacity: focus.interpolate({
+ inputRange: [-1, 0, 1],
+ outputRange: [0, 1, 1],
+ }),
- let restricted = this.isRestricted();
- let contentInset = this.contentInset();
+ fontSize: titleFontSize,
+ };
- let inputContainerStyle = {
- paddingTop: contentInset.top,
- paddingRight: contentInset.right,
- paddingBottom: contentInset.input,
- paddingLeft: contentInset.left,
- height: this.inputContainerHeight(),
+ let helperContainerStyle = {
+ flexDirection: 'row',
+ height: (title || limit)?
+ titleFontSize * 2:
+ focus.interpolate({
+ inputRange: [-1, 0, 1],
+ outputRange: [titleFontSize * 2, 8, 8],
+ }),
};
let containerProps = {
@@ -672,56 +469,82 @@ export default class TextField extends PureComponent {
let inputContainerProps = {
style: [
- this.constructor.inputContainerStyle,
+ styles.inputContainer,
inputContainerStyle,
inputContainerStyleOverrides,
],
};
- let styleProps = {
- disabled,
- restricted,
- baseColor,
+ let lineProps = {
+ type: disabledLineType,
+ width: disabledLineWidth,
+ color: baseColor,
+ };
+
+ let labelProps = {
+ baseSize: labelHeight,
+ basePadding: labelPadding,
+ fontSize,
+ activeFontSize: labelFontSize,
tintColor,
+ baseColor,
errorColor,
-
- contentInset,
-
- focusAnimation,
- labelAnimation,
+ animationDuration,
+ active,
+ focused,
+ errored,
+ restricted,
+ style: labelTextStyle,
};
- let lineProps = {
- ...styleProps,
-
- lineWidth,
- activeLineWidth,
- disabledLineWidth,
-
- lineType,
- disabledLineType,
+ let counterProps = {
+ baseColor,
+ errorColor,
+ count,
+ limit,
+ fontSize: titleFontSize,
+ style: titleTextStyle,
};
return (
- {this.renderLine(lineProps)}
- {this.renderAccessory('renderLeftAccessory')}
+ {disabled && }
-
- {this.renderLabel(styleProps)}
+
-
- {this.renderAffix('prefix')}
- {this.renderInput()}
- {this.renderAffix('suffix')}
-
-
+
+ {this.renderAffix('prefix', active, focused)}
- {this.renderAccessory('renderRightAccessory')}
+
+
+ {this.renderAffix('suffix', active, focused)}
+ {this.renderAccessory()}
+
- {this.renderHelper()}
+
+
+ {error}
+ {title}
+
+
+
+
);
}
diff --git a/src/components/field/styles.js b/src/components/field/styles.js
index 012a6e14..6864adf6 100644
--- a/src/components/field/styles.js
+++ b/src/components/field/styles.js
@@ -1,44 +1,28 @@
-import { StyleSheet, I18nManager } from 'react-native';
+import { StyleSheet } from 'react-native';
export default StyleSheet.create({
inputContainer: {
- flexDirection: 'row',
- alignItems: 'flex-end',
+ backgroundColor: 'transparent',
},
input: {
top: 2,
padding: 0,
- paddingTop: 0, /* XXX: iOS has paddingTop set for multiline input */
margin: 0,
flex: 1,
-
- textAlign: I18nManager.isRTL?
- 'right':
- 'left',
-
- includeFontPadding: false,
- textAlignVertical: 'top',
- },
-
- helperContainer: {
- flexDirection: 'row',
- justifyContent: 'flex-end',
- alignItems: 'flex-start',
},
row: {
- flex: 1,
flexDirection: 'row',
- alignItems: 'flex-end',
},
- stack: {
+ flex: {
flex: 1,
- alignSelf: 'stretch',
},
- flex: {
- flex: 1,
+ accessory: {
+ top: 2,
+ justifyContent: 'center',
+ alignSelf: 'flex-start',
},
});
diff --git a/src/components/field/test.js b/src/components/field/test.js
index c54234c7..27a3629a 100644
--- a/src/components/field/test.js
+++ b/src/components/field/test.js
@@ -109,26 +109,11 @@ it('renders suffix', () => {
.toMatchSnapshot();
});
-it('renders left accessory', () => {
- let render = () => (
-
- );
+it('renders accessory', () => {
+ let render = () =>
let field = renderer
- .create()
- .toJSON();
-
- expect(field)
- .toMatchSnapshot();
-});
-
-it('renders right accessory', () => {
- let render = () => (
-
- );
-
- let field = renderer
- .create()
+ .create()
.toJSON();
expect(field)
diff --git a/src/components/helper/__snapshots__/test.js.snap b/src/components/helper/__snapshots__/test.js.snap
index 95567022..99f86f8b 100644
--- a/src/components/helper/__snapshots__/test.js.snap
+++ b/src/components/helper/__snapshots__/test.js.snap
@@ -1,58 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`renders disabled helper 1`] = `
-
- helper
-
-`;
-
exports[`renders helper 1`] = `
-
- helper
-
-`;
-
-exports[`renders helper with error 1`] = `
-
- helper
-
+ >
+ helper
+
+
`;
diff --git a/src/components/helper/index.js b/src/components/helper/index.js
index 6060f9f5..0d20d8fc 100644
--- a/src/components/helper/index.js
+++ b/src/components/helper/index.js
@@ -1,97 +1,31 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
-import { Animated } from 'react-native';
+import { View, Animated } from 'react-native';
import styles from './styles';
export default class Helper extends PureComponent {
- static propTypes = {
- title: PropTypes.string,
- error: PropTypes.string,
-
- disabled: PropTypes.bool,
-
- style: Animated.Text.propTypes.style,
-
- baseColor: PropTypes.string,
- errorColor: PropTypes.string,
-
- focusAnimation: PropTypes.instanceOf(Animated.Value),
+ static defaultProps = {
+ numberOfLines: 1,
};
- constructor(props) {
- super(props);
-
- let { error, focusAnimation } = this.props;
-
- let opacity = focusAnimation.interpolate({
- inputRange: [-1, -0.5, 0],
- outputRange: [1, 0, 1],
- extrapolate: 'clamp',
- });
-
- this.state = {
- errored: !!error,
- opacity,
- };
- }
-
- componentDidMount() {
- let { focusAnimation } = this.props;
-
- this.listener = focusAnimation
- .addListener(this.onAnimation.bind(this));
- }
-
- componentWillUnmount() {
- let { focusAnimation } = this.props;
-
- focusAnimation.removeListener(this.listener);
- }
-
- onAnimation({ value }) {
- if (this.animationValue > -0.5 && value <= -0.5) {
- this.setState({ errored: true });
- }
-
- if (this.animationValue < -0.5 && value >= -0.5) {
- this.setState({ errored: false });
- }
-
- this.animationValue = value;
- }
+ static propTypes = {
+ // style: Animated.Text.propTypes.style,
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]),
+ };
render() {
- let { errored, opacity } = this.state;
- let {
- style,
- title,
- error,
- disabled,
- baseColor,
- errorColor,
- } = this.props;
-
- let text = errored?
- error:
- title;
-
- if (null == text) {
- return null;
- }
-
- let textStyle = {
- opacity,
-
- color: !disabled && errored?
- errorColor:
- baseColor,
- };
+ let { children, style, ...props } = this.props;
return (
-
- {text}
-
+
+
+ {children}
+
+
);
}
}
diff --git a/src/components/helper/styles.js b/src/components/helper/styles.js
index b9dec92f..483a0997 100644
--- a/src/components/helper/styles.js
+++ b/src/components/helper/styles.js
@@ -1,12 +1,13 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
+ container: {
+ ...StyleSheet.absoluteFillObject,
+ paddingVertical: 4,
+ alignItems: 'flex-start',
+ },
+
text: {
- flex: 1,
- fontSize: 12,
- lineHeight: 16,
backgroundColor: 'transparent',
- paddingVertical: 2,
- textAlign: 'left',
},
});
diff --git a/src/components/helper/test.js b/src/components/helper/test.js
index ecf6c240..bac3fdbd 100644
--- a/src/components/helper/test.js
+++ b/src/components/helper/test.js
@@ -1,6 +1,5 @@
import 'react-native';
import React from 'react';
-import { Animated } from 'react-native';
import renderer from 'react-test-renderer';
import Helper from '.';
@@ -8,41 +7,10 @@ import Helper from '.';
/* eslint-env jest */
const text = 'helper';
-const props = {
- title: text,
- fontSize: 16,
-
- baseColor: 'black',
- errorColor: 'red',
-
- focusAnimation: new Animated.Value(0),
-};
it('renders helper', () => {
let helper = renderer
- .create()
- .toJSON();
-
- expect(helper)
- .toMatchSnapshot();
-});
-
-it('renders disabled helper', () => {
- let helper = renderer
- .create(
-
- )
- .toJSON();
-
- expect(helper)
- .toMatchSnapshot();
-});
-
-it('renders helper with error', () => {
- let helper = renderer
- .create(
-
- )
+ .create({text})
.toJSON();
expect(helper)
diff --git a/src/components/label/__snapshots__/test.js.snap b/src/components/label/__snapshots__/test.js.snap
index e7c6a190..729e0f72 100644
--- a/src/components/label/__snapshots__/test.js.snap
+++ b/src/components/label/__snapshots__/test.js.snap
@@ -1,120 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`renders active errored label 1`] = `
-
-
- test
-
-
-`;
-
-exports[`renders active focused label 1`] = `
-
-
- test
-
-
-`;
-
exports[`renders active label 1`] = `
@@ -123,41 +28,26 @@ exports[`renders active label 1`] = `
`;
-exports[`renders empty label 1`] = `null`;
-
exports[`renders errored label 1`] = `
@@ -166,39 +56,26 @@ exports[`renders errored label 1`] = `
`;
-exports[`renders label 1`] = `
+exports[`renders focused label 1`] = `
@@ -207,39 +84,26 @@ exports[`renders label 1`] = `
`;
-exports[`renders restricted label 1`] = `
+exports[`renders label 1`] = `
@@ -248,40 +112,26 @@ exports[`renders restricted label 1`] = `
`;
-exports[`renders styled label 1`] = `
+exports[`renders restricted label 1`] = `
diff --git a/src/components/label/index.js b/src/components/label/index.js
index 82eaf033..3d4a9a9f 100644
--- a/src/components/label/index.js
+++ b/src/components/label/index.js
@@ -2,117 +2,132 @@ import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import { Animated } from 'react-native';
-import styles from './styles';
-
export default class Label extends PureComponent {
static defaultProps = {
numberOfLines: 1,
- disabled: false,
+
+ active: false,
+ focused: false,
+ errored: false,
restricted: false,
};
static propTypes = {
- numberOfLines: PropTypes.number,
-
- disabled: PropTypes.bool,
+ active: PropTypes.bool,
+ focused: PropTypes.bool,
+ errored: PropTypes.bool,
restricted: PropTypes.bool,
+ baseSize: PropTypes.number.isRequired,
fontSize: PropTypes.number.isRequired,
activeFontSize: PropTypes.number.isRequired,
+ basePadding: PropTypes.number.isRequired,
- baseColor: PropTypes.string.isRequired,
tintColor: PropTypes.string.isRequired,
+ baseColor: PropTypes.string.isRequired,
errorColor: PropTypes.string.isRequired,
- focusAnimation: PropTypes
- .instanceOf(Animated.Value)
- .isRequired,
+ animationDuration: PropTypes.number.isRequired,
- labelAnimation: PropTypes
- .instanceOf(Animated.Value)
- .isRequired,
+ // style: Animated.Text.propTypes.style,
- contentInset: PropTypes.shape({
- label: PropTypes.number,
- }),
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]),
+ };
- offset: PropTypes.shape({
- x0: PropTypes.number,
- y0: PropTypes.number,
- x1: PropTypes.number,
- y1: PropTypes.number,
- }),
+ constructor(props) {
+ super(props);
- style: Animated.Text.propTypes.style,
- label: PropTypes.string,
- };
+ this.state = {
+ input: new Animated.Value(this.inputState()),
+ focus: new Animated.Value(this.focusState()),
+ };
+ }
+
+ componentWillReceiveProps(props) {
+ let { focus, input } = this.state;
+ let { active, focused, errored, animationDuration: duration } = this.props;
+
+ if (focused ^ props.focused || active ^ props.active) {
+ let toValue = this.inputState(props);
+
+ Animated
+ .timing(input, { toValue, duration })
+ .start();
+ }
+
+ if (focused ^ props.focused || errored ^ props.errored) {
+ let toValue = this.focusState(props);
+
+ Animated
+ .timing(focus, { toValue, duration })
+ .start();
+ }
+ }
+
+ inputState({ focused, active } = this.props) {
+ return active || focused? 1 : 0;
+ }
+
+ focusState({ focused, errored } = this.props) {
+ return errored? -1 : (focused? 1 : 0);
+ }
render() {
+ let { focus, input } = this.state;
let {
- label,
- offset,
- disabled,
+ children,
restricted,
fontSize,
activeFontSize,
- contentInset,
errorColor,
baseColor,
tintColor,
+ baseSize,
+ basePadding,
style,
- focusAnimation,
- labelAnimation,
+ errored,
+ active,
+ focused,
+ animationDuration,
...props
} = this.props;
- if (null == label) {
- return null;
- }
-
- let color = disabled?
- baseColor:
- restricted?
- errorColor:
- focusAnimation.interpolate({
- inputRange: [-1, 0, 1],
- outputRange: [errorColor, baseColor, tintColor],
- });
+ let color = restricted?
+ errorColor:
+ focus.interpolate({
+ inputRange: [-1, 0, 1],
+ outputRange: [errorColor, baseColor, tintColor],
+ });
+
+ let top = input.interpolate({
+ inputRange: [0, 1],
+ outputRange: [
+ baseSize + fontSize * 0.25,
+ baseSize - basePadding - activeFontSize,
+ ],
+ });
let textStyle = {
- lineHeight: fontSize,
- fontSize,
+ fontSize: input.interpolate({
+ inputRange: [0, 1],
+ outputRange: [fontSize, activeFontSize],
+ }),
+
color,
};
- let { x0, y0, x1, y1 } = offset;
-
- y0 += activeFontSize;
- y0 += contentInset.label;
- y0 += fontSize * 0.25;
-
let containerStyle = {
- transform: [{
- scale: labelAnimation.interpolate({
- inputRange: [0, 1],
- outputRange: [1, activeFontSize / fontSize],
- }),
- }, {
- translateY: labelAnimation.interpolate({
- inputRange: [0, 1],
- outputRange: [y0, y1],
- }),
- }, {
- translateX: labelAnimation.interpolate({
- inputRange: [0, 1],
- outputRange: [x0, x1],
- }),
- }],
+ position: 'absolute',
+ top,
};
return (
-
-
- {label}
+
+
+ {children}
);
diff --git a/src/components/label/test.js b/src/components/label/test.js
index cdd7cb85..4441c575 100644
--- a/src/components/label/test.js
+++ b/src/components/label/test.js
@@ -1,40 +1,26 @@
import 'react-native';
import React from 'react';
-import { Animated } from 'react-native';
import renderer from 'react-test-renderer';
import Label from '.';
/* eslint-env jest */
+const text = 'test';
const props = {
+ baseSize: 32,
+ basePadding: 4,
fontSize: 16,
activeFontSize: 12,
-
- contentInset: { label: 4 },
-
- baseColor: 'black',
tintColor: 'blue',
+ baseColor: 'black',
errorColor: 'red',
-
- offset: { x0: 0, y0: 0, x1: 0, y1: 0 },
-
- focusAnimation: new Animated.Value(0),
- labelAnimation: new Animated.Value(0),
- label: 'test',
+ animationDuration: 225,
};
it('renders label', () => {
let label = renderer
- .create();
-
- expect(label.toJSON())
- .toMatchSnapshot();
-});
-
-it('renders empty label', () => {
- let label = renderer
- .create();
+ .create();
expect(label.toJSON())
.toMatchSnapshot();
@@ -42,23 +28,15 @@ it('renders empty label', () => {
it('renders active label', () => {
let label = renderer
- .create(
-
- );
+ .create();
expect(label.toJSON())
.toMatchSnapshot();
});
-it('renders active focused label', () => {
+it('renders focused label', () => {
let label = renderer
- .create(
-
- );
+ .create();
expect(label.toJSON())
.toMatchSnapshot();
@@ -66,27 +44,7 @@ it('renders active focused label', () => {
it('renders errored label', () => {
let label = renderer
- .create(
-
- );
-
- expect(label.toJSON())
- .toMatchSnapshot();
-});
-
-it('renders active errored label', () => {
- let label = renderer
- .create(
-
- );
+ .create();
expect(label.toJSON())
.toMatchSnapshot();
@@ -94,20 +52,7 @@ it('renders active errored label', () => {
it('renders restricted label', () => {
let label = renderer
- .create(
-
- );
-
- expect(label.toJSON())
- .toMatchSnapshot();
-});
-
-it('renders styled label', () => {
- let style = { textTransform: 'uppercase' };
- let label = renderer
- .create(
-
- );
+ .create();
expect(label.toJSON())
.toMatchSnapshot();
diff --git a/src/components/line/__snapshots__/test.js.snap b/src/components/line/__snapshots__/test.js.snap
index 9c32afb2..58c82d2c 100644
--- a/src/components/line/__snapshots__/test.js.snap
+++ b/src/components/line/__snapshots__/test.js.snap
@@ -1,125 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`renders active line 1`] = `
+exports[`renders dotted grey line 1`] = `
-
-
-`;
-
-exports[`renders disabled line 1`] = `
-
-
-
+ "left": -1.5,
+ "right": -1.5,
+ "top": -2,
+ },
+ ]
+ }
+/>
`;
-exports[`renders line 1`] = `
+exports[`renders solid black line 1`] = `
-
-
-`;
-
-exports[`renders restricted line 1`] = `
-
-
-
+ "left": -1.5,
+ "right": -1.5,
+ "top": -2,
+ },
+ ]
+ }
+/>
`;
diff --git a/src/components/line/index.js b/src/components/line/index.js
index 44995e98..4f1074a8 100644
--- a/src/components/line/index.js
+++ b/src/components/line/index.js
@@ -1,126 +1,42 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
-import { View, Animated } from 'react-native';
+import { View } from 'react-native';
import styles from './styles';
-const lineTypes = PropTypes
- .oneOf(['solid', 'dotted', 'dashed', 'none']);
-
export default class Line extends PureComponent {
- static defaultProps = {
- lineType: 'solid',
- disabledLineType: 'dotted',
-
- disabled: false,
- restricted: false,
- };
-
static propTypes = {
- lineType: lineTypes,
- disabledLineType: lineTypes,
-
- disabled: PropTypes.bool,
- restricted: PropTypes.bool,
-
- tintColor: PropTypes.string,
- baseColor: PropTypes.string,
- errorColor: PropTypes.string,
-
- lineWidth: PropTypes.number,
- activeLineWidth: PropTypes.number,
- disabledLineWidth: PropTypes.number,
-
- focusAnimation: PropTypes.instanceOf(Animated.Value),
+ type: PropTypes.oneOf(['solid', 'dotted', 'dashed', 'none']).isRequired,
+ width: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
};
- static getDerivedStateFromProps(props, state) {
- let { lineWidth, activeLineWidth, disabledLineWidth } = props;
-
- let maxLineWidth = Math.max(
- lineWidth,
- activeLineWidth,
- disabledLineWidth,
- 1,
- );
-
- if (maxLineWidth !== state.maxLineWidth) {
- return { maxLineWidth };
- }
-
- return null;
- }
-
- state = { maxLineWidth: 1 };
-
- borderProps() {
+ render() {
let {
- disabled,
- restricted,
- lineWidth,
- activeLineWidth,
- disabledLineWidth,
- baseColor,
- tintColor,
- errorColor,
- focusAnimation,
+ width: borderWidth,
+ color: borderColor,
+ type: borderStyle,
} = this.props;
- if (disabled) {
- return {
- borderColor: baseColor,
- borderWidth: disabledLineWidth,
- };
- }
-
- if (restricted) {
- return {
- borderColor: errorColor,
- borderWidth: activeLineWidth,
- };
- }
-
- return {
- borderColor: focusAnimation.interpolate({
- inputRange: [-1, 0, 1],
- outputRange: [errorColor, baseColor, tintColor],
- }),
-
- borderWidth: focusAnimation.interpolate({
- inputRange: [-1, 0, 1],
- outputRange: [activeLineWidth, lineWidth, activeLineWidth],
- }),
- };
- }
-
- render() {
- let { maxLineWidth } = this.state;
- let { disabled, lineType, disabledLineType } = this.props;
-
- let borderStyle = disabled?
- disabledLineType:
- lineType;
-
if ('none' === borderStyle) {
return null;
}
- let [top, right, left] = Array
- .from(new Array(3), () => -1.5 * maxLineWidth);
+ let [top, right, bottom, left] = [-2, -1.5, 0, -1.5]
+ .map((value) => value * borderWidth);
let lineStyle = {
- ...this.borderProps(),
-
+ borderColor,
borderStyle,
top,
right,
+ bottom,
left,
+ borderWidth,
};
return (
-
-
-
+
);
}
}
diff --git a/src/components/line/styles.js b/src/components/line/styles.js
index d0701be9..32c5fc7a 100644
--- a/src/components/line/styles.js
+++ b/src/components/line/styles.js
@@ -3,16 +3,9 @@ import { StyleSheet, Platform } from 'react-native';
export default StyleSheet.create({
line: {
position: 'absolute',
- bottom: 0,
...Platform.select({
android: { borderRadius: Number.EPSILON },
}),
},
-
- container: {
- ...StyleSheet.absoluteFillObject,
-
- overflow: 'hidden',
- },
});
diff --git a/src/components/line/test.js b/src/components/line/test.js
index c418ce44..81d71e35 100644
--- a/src/components/line/test.js
+++ b/src/components/line/test.js
@@ -1,59 +1,19 @@
import 'react-native';
import React from 'react';
-import { Animated } from 'react-native';
import renderer from 'react-test-renderer';
import Line from '.';
/* eslint-env jest */
-const props = {
- disabled: false,
- restricted: false,
-
- baseColor: 'black',
- tintColor: 'blue',
- errorColor: 'red',
-
- lineWidth: 0.5,
- activeLineWidth: 2,
- disabledLineWidth: 1,
-
- focusAnimation: new Animated.Value(0),
-};
-
-it('renders line', () => {
- let line = renderer
- .create()
- .toJSON();
-
- expect(line)
- .toMatchSnapshot();
-});
-
-it('renders disabled line', () => {
- let line = renderer
- .create()
- .toJSON();
-
- expect(line)
- .toMatchSnapshot();
-});
-
-it('renders restricted line', () => {
- let line = renderer
- .create()
- .toJSON();
-
- expect(line)
- .toMatchSnapshot();
-});
-
-it('renders active line', () => {
- let line = renderer
- .create()
- .toJSON();
-
- expect(line)
- .toMatchSnapshot();
-});
+[{ type: 'solid', color: 'black', width: 1 }, { type: 'dotted', color: 'grey', width: 1 }]
+ .forEach(({ type, color, width }) => {
+ it(`renders ${type} ${color} line`, () => {
+ let line = renderer
+ .create()
+ .toJSON();
+
+ expect(line)
+ .toMatchSnapshot();
+ });
+ });