diff --git a/src/components/BigNumberPad.js b/src/components/BigNumberPad.js
index 3f12fdacaa62..8181cb67e9a9 100644
--- a/src/components/BigNumberPad.js
+++ b/src/components/BigNumberPad.js
@@ -1,7 +1,8 @@
-import React, {PureComponent} from 'react';
+import React from 'react';
import {
Text, TouchableOpacity, View,
} from 'react-native';
+import _ from 'underscore';
import PropTypes from 'prop-types';
import styles from '../styles/styles';
@@ -17,57 +18,30 @@ const padNumbers = [
['.', '0', '<'],
];
-class BigNumberPad extends PureComponent {
- /**
- * Creates set of buttons for given row
- *
- * @param {number} row
- * @returns {View}
- */
- createNumberPadRow(row) {
- const self = this;
- const numberPadRow = padNumbers[row].map((column, index) => self.createNumberPadButton(row, index));
- return (
-
- {numberPadRow}
+const BigNumberPad = ({numberPressed}) => (
+
+ {_.map(padNumbers, (row, rowIndex) => (
+
+ {_.map(row, (column, columnIndex) => {
+ // Adding margin between buttons except first column to
+ // avoid unccessary space before the first column.
+ const marginLeft = columnIndex > 0 ? styles.ml3 : {};
+ return (
+ numberPressed(column)}
+ >
+
+ {column}
+
+
+ );
+ })}
- );
- }
-
- /**
- * Creates a button for given row and column
- *
- * @param {number} row
- * @param {number} column
- * @returns {View}
- */
- createNumberPadButton(row, column) {
- // Adding margin between buttons except first column to
- // avoid unccessary space before the first column.
- const marginLeft = column > 0 ? styles.ml3 : {};
- return (
- this.props.numberPressed(padNumbers[row][column])}
- >
-
- {padNumbers[row][column]}
-
-
- );
- }
-
- render() {
- const self = this;
- const numberPad = padNumbers.map((row, index) => self.createNumberPadRow(index));
- return (
-
- {numberPad}
-
- );
- }
-}
+ ))}
+
+);
BigNumberPad.propTypes = propTypes;
BigNumberPad.displayName = 'BigNumberPad';
diff --git a/src/components/TextInputAutoWidth.js b/src/components/TextInputAutoWidth.js
new file mode 100644
index 000000000000..71fad594821b
--- /dev/null
+++ b/src/components/TextInputAutoWidth.js
@@ -0,0 +1,71 @@
+import React from 'react';
+import {View, Text} from 'react-native';
+import PropTypes from 'prop-types';
+import _ from 'underscore';
+import styles, {getAutoGrowTextInputStyle} from '../styles/styles';
+import TextInputFocusable from './TextInputFocusable';
+
+const propTypes = {
+
+ // The value of the comment box
+ value: PropTypes.string.isRequired,
+
+ // A ref to forward to the text input
+ forwardedRef: PropTypes.func.isRequired,
+
+ // General styles to apply to the text input
+ // eslint-disable-next-line react/forbid-prop-types
+ inputStyle: PropTypes.object,
+
+ // Styles to apply to the text input
+ // eslint-disable-next-line react/forbid-prop-types
+ textStyle: PropTypes.object.isRequired,
+};
+
+const defaultProps = {
+ inputStyle: {},
+};
+
+class TextInputAutoWidth extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ textInputWidth: 0,
+ };
+ }
+
+ render() {
+ const propsWithoutStyles = _.omit(this.props, ['inputStyle', 'textStyle']);
+ return (
+
+
+ {/*
+ Text input component doesn't support auto grow by default.
+ We're using a hidden text input to achieve that.
+ This text view is used to calculate width of the input value given textStyle in this component.
+ This text component is intentionally positioned out of the screen.
+ */}
+ this.setState({textInputWidth: e.nativeEvent.layout.width})}
+ >
+ {this.props.value}
+
+
+ );
+ }
+}
+
+TextInputAutoWidth.propTypes = propTypes;
+TextInputAutoWidth.defaultProps = defaultProps;
+
+export default React.forwardRef((props, ref) => (
+ /* eslint-disable-next-line react/jsx-props-no-spreading */
+
+));
diff --git a/src/components/TextInputFocusable/index.js b/src/components/TextInputFocusable/index.js
index 764ca1984ba1..ba75dc946c52 100644
--- a/src/components/TextInputFocusable/index.js
+++ b/src/components/TextInputFocusable/index.js
@@ -8,7 +8,10 @@ const propTypes = {
maxLines: PropTypes.number,
// The default value of the comment box
- defaultValue: PropTypes.string.isRequired,
+ defaultValue: PropTypes.string,
+
+ // The value of the comment box
+ value: PropTypes.string,
// Callback method to handle pasting a file
onPasteFile: PropTypes.func,
@@ -44,6 +47,8 @@ const propTypes = {
};
const defaultProps = {
+ defaultValue: '',
+ value: '',
maxLines: -1,
onPasteFile: () => {},
shouldClear: false,
@@ -76,8 +81,12 @@ class TextInputFocusable extends React.Component {
this.state = {
numberOfLines: 1,
selection: {
- start: this.props.defaultValue.length,
- end: this.props.defaultValue.length,
+ start: this.props.defaultValue
+ ? `${this.props.defaultValue}`.length
+ : `${this.props.value}`.length,
+ end: this.props.defaultValue
+ ? `${this.props.defaultValue}`.length
+ : `${this.props.value}`.length,
},
};
}
@@ -211,6 +220,9 @@ class TextInputFocusable extends React.Component {
const propStyles = StyleSheet.flatten(this.props.style);
propStyles.outline = 'none';
const propsWithoutStyles = _.omit(this.props, 'style');
+ const propsWithoutValueOrDefaultValue = this.props.defaultValue
+ ? _.omit(propsWithoutStyles, 'value')
+ : _.omit(propsWithoutStyles, 'defaultValue');
return (
this.textInput = el}
@@ -221,7 +233,7 @@ class TextInputFocusable extends React.Component {
numberOfLines={this.state.numberOfLines}
style={propStyles}
/* eslint-disable-next-line react/jsx-props-no-spreading */
- {...propsWithoutStyles}
+ {...propsWithoutValueOrDefaultValue}
disabled={this.props.isDisabled}
/>
);
diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js
index f3d67ed1a2e0..1e2375be27cd 100644
--- a/src/pages/iou/IOUModal.js
+++ b/src/pages/iou/IOUModal.js
@@ -39,16 +39,14 @@ class IOUModal extends Component {
this.navigateToPreviousStep = this.navigateToPreviousStep.bind(this);
this.navigateToNextStep = this.navigateToNextStep.bind(this);
- this.updateAmount = this.updateAmount.bind(this);
this.currencySelected = this.currencySelected.bind(this);
-
this.addParticipants = this.addParticipants.bind(this);
+
this.state = {
currentStepIndex: 0,
participants: [],
amount: '',
selectedCurrency: 'USD',
- isAmountPageNextButtonDisabled: true,
};
}
@@ -102,33 +100,6 @@ class IOUModal extends Component {
});
}
- /**
- * Update amount with number or Backspace pressed.
- * Validate new amount with decimal number regex up to 6 digits and 2 decimal digit
- *
- * @param {String} buttonPressed
- */
- updateAmount(buttonPressed) {
- // Backspace button is pressed
- if (buttonPressed === '<' || buttonPressed === 'Backspace') {
- if (this.state.amount.length > 0) {
- this.setState(prevState => ({
- amount: prevState.amount.substring(0, prevState.amount.length - 1),
- isAmountPageNextButtonDisabled: prevState.amount.length === 1,
- }));
- }
- } else {
- const decimalNumberRegex = new RegExp(/^\d{1,6}(\.\d{0,2})?$/, 'i');
- const amount = this.state.amount + buttonPressed;
- if (decimalNumberRegex.test(amount)) {
- this.setState({
- amount,
- isAmountPageNextButtonDisabled: false,
- });
- }
- }
- }
-
/**
* Update the currency state
*
@@ -174,12 +145,12 @@ class IOUModal extends Component {
{currentStep === Steps.IOUAmount && (
{
+ this.setState({amount});
+ this.navigateToNextStep();
+ }}
currencySelected={this.currencySelected}
- amount={this.state.amount}
selectedCurrency={this.state.selectedCurrency}
- isNextButtonDisabled={this.state.isAmountPageNextButtonDisabled}
/>
)}
{currentStep === Steps.IOUParticipants && (
diff --git a/src/pages/iou/steps/IOUAmountPage.js b/src/pages/iou/steps/IOUAmountPage.js
index 8d32220eced4..e266b6359451 100644
--- a/src/pages/iou/steps/IOUAmountPage.js
+++ b/src/pages/iou/steps/IOUAmountPage.js
@@ -12,15 +12,12 @@ import styles from '../../../styles/styles';
import themeColors from '../../../styles/themes/default';
import BigNumberPad from '../../../components/BigNumberPad';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
-import TextInputFocusable from '../../../components/TextInputFocusable';
+import TextInputAutoWidth from '../../../components/TextInputAutoWidth';
const propTypes = {
// Callback to inform parent modal of success
onStepComplete: PropTypes.func.isRequired,
- // Callback to inform parent modal with key pressed
- numberPressed: PropTypes.func.isRequired,
-
// Currency selection will be implemented later
// eslint-disable-next-line react/no-unused-prop-types
currencySelected: PropTypes.func.isRequired,
@@ -28,12 +25,6 @@ const propTypes = {
// User's currency preference
selectedCurrency: PropTypes.string.isRequired,
- // Amount value entered by user
- amount: PropTypes.string.isRequired,
-
- // To disable/enable Next button based on amount validity
- isNextButtonDisabled: PropTypes.bool.isRequired,
-
/* Window Dimensions Props */
...windowDimensionsPropTypes,
@@ -54,8 +45,9 @@ class IOUAmountPage extends React.Component {
constructor(props) {
super(props);
+ this.updateAmountIfValidInput = this.updateAmountIfValidInput.bind(this);
this.state = {
- textInputWidth: 0,
+ amount: '',
};
}
@@ -65,6 +57,37 @@ class IOUAmountPage extends React.Component {
}
}
+ /**
+ * Update amount with number or Backspace pressed.
+ * Validate new amount with decimal number regex up to 6 digits and 2 decimal digit to enable Next button
+ *
+ * @param {String} key
+ */
+ updateAmountIfValidInput(key) {
+ // Backspace button is pressed
+ if (key === '<' || key === 'Backspace') {
+ if (this.state.amount.length > 0) {
+ this.setState(prevState => ({
+ amount: prevState.amount.substring(0, prevState.amount.length - 1),
+ }));
+ }
+ return;
+ }
+
+ this.setState((prevState) => {
+ const newValue = `${prevState.amount}${key}`;
+
+ // Regex to validate decimal number with up to 3 decimal numbers
+ const decimalNumberRegex = new RegExp(/^\d+(\.\d{0,3})?$/, 'i');
+ if (!decimalNumberRegex.test(newValue)) {
+ return prevState;
+ }
+ return {
+ amount: newValue,
+ };
+ });
+ }
+
render() {
return (
@@ -81,38 +104,29 @@ class IOUAmountPage extends React.Component {
{this.props.selectedCurrency}
{this.props.isSmallScreenWidth
- ? {this.props.amount}
+ ? {this.state.amount}
: (
-
- {
- this.props.numberPressed(event.key);
- event.preventDefault();
- }}
- ref={el => this.textInput = el}
- defaultValue={this.props.amount}
- textAlign="left"
- />
- this.setState({textInputWidth: e.nativeEvent.layout.width})}
- >
- {this.props.amount}
-
-
+ {
+ this.updateAmountIfValidInput(event.key);
+ event.preventDefault();
+ }}
+ ref={el => this.textInput = el}
+ value={this.state.amount}
+ />
)}
{this.props.isSmallScreenWidth
- ?
+ ?
: }
this.props.onStepComplete(this.state.amount)}
+ disabled={this.state.amount.length === 0}
>
Next
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 0ea54b06c18b..d0de58cff84f 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -1245,12 +1245,14 @@ const styles = {
fontFamily: fontFamily.GTA_BOLD,
fontWeight: fontWeightBold,
fontSize: variables.iouAmountTextSize,
+ color: themeColors.heading,
},
iouAmountTextInput: addOutlineWidth({
fontFamily: fontFamily.GTA_BOLD,
fontWeight: fontWeightBold,
fontSize: variables.iouAmountTextSize,
+ color: themeColors.heading,
}, 0),
noScrollbars: {
@@ -1264,6 +1266,14 @@ const styles = {
alignItems: 'center',
zIndex: 10,
},
+
+ hiddenElementOutsideOfWindow: {
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ opacity: 0,
+ transform: 'translateX(-100%)',
+ },
};
const baseCodeTagStyles = {
@@ -1431,6 +1441,19 @@ function getZoomSizingStyle(isZoomed) {
};
}
+/**
+ * Returns auto grow text input style
+ *
+ * @param {Number} width
+ * @return {Object}
+ */
+function getAutoGrowTextInputStyle(width) {
+ return {
+ minWidth: 5,
+ width,
+ };
+}
+
/**
* Returns a style with backgroundColor and borderColor set to the same color
*
@@ -1558,6 +1581,7 @@ export {
getNavigationModalCardStyle,
getZoomCursorStyle,
getZoomSizingStyle,
+ getAutoGrowTextInputStyle,
getBackgroundAndBorderStyle,
getBackgroundColorStyle,
getButtonBackgroundColorStyle,