diff --git a/README.md b/README.md index 799fe11..9fded2b 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ This component uses the same props as [](https://facebook.github.io/ Prop | Type | Default | Description ---------- | ------- | ---------- | ----------------------- `codeLength` | number | 5 | length of confirmation code -> number of cells +`keyboardType` | string | `default` | determines which keyboard to open `compareWithCode` | string | | code to compare. if null, onFulfill callback return inputted code to check later `inputPosition` | string | `center` | position of code input in its container: `left`, `right`, `center`, `full-width` `size` | number | 40 | size of input cells diff --git a/components/ConfirmationCodeInput.js b/components/ConfirmationCodeInput.js index 239c85a..fa21ff0 100644 --- a/components/ConfirmationCodeInput.js +++ b/components/ConfirmationCodeInput.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { View, TextInput, StyleSheet, Dimensions, ViewPropTypes } from 'react-native'; import _ from 'lodash'; @@ -7,290 +7,292 @@ import _ from 'lodash'; const viewPropTypes = ViewPropTypes || View.propTypes; export default class ConfirmationCodeInput extends Component { - static propTypes = { - codeLength: PropTypes.number, - compareWithCode: PropTypes.string, - inputPosition: PropTypes.string, - size: PropTypes.number, - space: PropTypes.number, - className: PropTypes.string, - cellBorderWidth: PropTypes.number, - activeColor: PropTypes.string, - inactiveColor: PropTypes.string, - ignoreCase: PropTypes.bool, - autoFocus: PropTypes.bool, - codeInputStyle: TextInput.propTypes.style, - containerStyle: viewPropTypes.style, - onFulfill: PropTypes.func, - }; - - static defaultProps = { - codeLength: 5, - inputPosition: 'center', - autoFocus: true, - size: 40, - className: 'border-box', - cellBorderWidth: 1, - activeColor: 'rgba(255, 255, 255, 1)', - inactiveColor: 'rgba(255, 255, 255, 0.2)', - space: 8, - compareWithCode: '', - ignoreCase: false - }; - - constructor(props) { - super(props); - - this.state = { - codeArr: new Array(this.props.codeLength).fill(''), - currentIndex: 0 + static propTypes = { + codeLength: PropTypes.number, + compareWithCode: PropTypes.string, + inputPosition: PropTypes.string, + size: PropTypes.number, + space: PropTypes.number, + className: PropTypes.string, + cellBorderWidth: PropTypes.number, + activeColor: PropTypes.string, + inactiveColor: PropTypes.string, + ignoreCase: PropTypes.bool, + autoFocus: PropTypes.bool, + codeInputStyle: TextInput.propTypes.style, + containerStyle: viewPropTypes.style, + onFulfill: PropTypes.func, + keyboardType: PropTypes.string }; - - this.codeInputRefs = []; - } - - componentDidMount() { - const { compareWithCode, codeLength, inputPosition } = this.props; - if (compareWithCode && compareWithCode.length !== codeLength) { - console.error("Invalid props: compareWith length is not equal to codeLength"); + + static defaultProps = { + codeLength: 5, + inputPosition: 'center', + autoFocus: true, + size: 40, + className: 'border-box', + cellBorderWidth: 1, + activeColor: 'rgba(255, 255, 255, 1)', + inactiveColor: 'rgba(255, 255, 255, 0.2)', + space: 8, + compareWithCode: '', + ignoreCase: false + }; + + constructor(props) { + super(props); + + this.state = { + codeArr: new Array(this.props.codeLength).fill(''), + currentIndex: 0 + }; + + this.codeInputRefs = []; } - - if (_.indexOf(['center', 'left', 'right', 'full-width'], inputPosition) === -1) { - console.error('Invalid input position. Must be in: center, left, right, full'); + + componentDidMount() { + const { compareWithCode, codeLength, inputPosition } = this.props; + if (compareWithCode && compareWithCode.length !== codeLength) { + console.error("Invalid props: compareWith length is not equal to codeLength"); + } + + if (_.indexOf(['center', 'left', 'right', 'full-width'], inputPosition) === -1) { + console.error('Invalid input position. Must be in: center, left, right, full'); + } } - } - - clear() { - this.setState({ - codeArr: new Array(this.props.codeLength).fill(''), - currentIndex: 0 - }); - this._setFocus(0); - } - - _setFocus(index) { - this.codeInputRefs[index].focus(); - } - - _blur(index) { - this.codeInputRefs[index].blur(); - } - - _onFocus(index) { - let newCodeArr = _.clone(this.state.codeArr); - const currentEmptyIndex = _.findIndex(newCodeArr, c => !c); - if (currentEmptyIndex !== -1 && currentEmptyIndex < index) { - return this._setFocus(currentEmptyIndex); + + clear() { + this.setState({ + codeArr: new Array(this.props.codeLength).fill(''), + currentIndex: 0 + }); + this._setFocus(0); } - for (const i in newCodeArr) { - if (i >= index) { - newCodeArr[i] = ''; - } + + _setFocus(index) { + this.codeInputRefs[index].focus(); } - - this.setState({ - codeArr: newCodeArr, - currentIndex: index - }) - } - - _isMatchingCode(code, compareWithCode, ignoreCase = false) { - if (ignoreCase) { - return code.toLowerCase() == compareWithCode.toLowerCase(); + + _blur(index) { + this.codeInputRefs[index].blur(); } - return code == compareWithCode; - } - - _getContainerStyle(size, position) { - switch (position) { - case 'left': - return { - justifyContent: 'flex-start', - height: size - }; - case 'center': - return { - justifyContent: 'center', - height: size - }; - case 'right': - return { - justifyContent: 'flex-end', - height: size - }; - default: - return { - justifyContent: 'space-between', - height: size + + _onFocus(index) { + let newCodeArr = _.clone(this.state.codeArr); + const currentEmptyIndex = _.findIndex(newCodeArr, c => !c); + if (currentEmptyIndex !== -1 && currentEmptyIndex < index) { + return this._setFocus(currentEmptyIndex); + } + for (const i in newCodeArr) { + if (i >= index) { + newCodeArr[i] = ''; + } } + + this.setState({ + codeArr: newCodeArr, + currentIndex: index + }) } - } - - _getInputSpaceStyle(space) { - const { inputPosition } = this.props; - switch (inputPosition) { - case 'left': - return { - marginRight: space - }; - case 'center': - return { - marginRight: space/2, - marginLeft: space/2 - }; - case 'right': - return { - marginLeft: space - }; - default: - return { - marginRight: 0, - marginLeft: 0 - }; + + _isMatchingCode(code, compareWithCode, ignoreCase = false) { + if (ignoreCase) { + return code.toLowerCase() == compareWithCode.toLowerCase(); + } + return code == compareWithCode; } - } - - _getClassStyle(className, active) { - const { cellBorderWidth, activeColor, inactiveColor, space } = this.props; - let classStyle = { - ...this._getInputSpaceStyle(space), - color: activeColor - }; - - switch (className) { - case 'clear': - return _.merge(classStyle, { borderWidth: 0 }); - case 'border-box': - return _.merge(classStyle, { - borderWidth: cellBorderWidth, - borderColor: (active ? activeColor : inactiveColor) - }); - case 'border-circle': - return _.merge(classStyle, { - borderWidth: cellBorderWidth, - borderRadius: 50, - borderColor: (active ? activeColor : inactiveColor) - }); - case 'border-b': - return _.merge(classStyle, { - borderBottomWidth: cellBorderWidth, - borderColor: (active ? activeColor : inactiveColor), - }); - case 'border-b-t': - return _.merge(classStyle, { - borderTopWidth: cellBorderWidth, - borderBottomWidth: cellBorderWidth, - borderColor: (active ? activeColor : inactiveColor) - }); - case 'border-l-r': - return _.merge(classStyle, { - borderLeftWidth: cellBorderWidth, - borderRightWidth: cellBorderWidth, - borderColor: (active ? activeColor : inactiveColor) - }); - default: - return className; + + _getContainerStyle(size, position) { + switch (position) { + case 'left': + return { + justifyContent: 'flex-start', + height: size + }; + case 'center': + return { + justifyContent: 'center', + height: size + }; + case 'right': + return { + justifyContent: 'flex-end', + height: size + }; + default: + return { + justifyContent: 'space-between', + height: size + } + } } - } - - _onKeyPress(e) { - if (e.nativeEvent.key === 'Backspace') { - const { currentIndex } = this.state; - const nextIndex = currentIndex > 0 ? currentIndex - 1 : 0; - this._setFocus(nextIndex); + + _getInputSpaceStyle(space) { + const { inputPosition } = this.props; + switch (inputPosition) { + case 'left': + return { + marginRight: space + }; + case 'center': + return { + marginRight: space / 2, + marginLeft: space / 2 + }; + case 'right': + return { + marginLeft: space + }; + default: + return { + marginRight: 0, + marginLeft: 0 + }; + } } - } - - _onInputCode(character, index) { - const { codeLength, onFulfill, compareWithCode, ignoreCase } = this.props; - let newCodeArr = _.clone(this.state.codeArr); - newCodeArr[index] = character; - - if (index == codeLength - 1) { - const code = newCodeArr.join(''); - - if (compareWithCode) { - const isMatching = this._isMatchingCode(code, compareWithCode, ignoreCase); - onFulfill(isMatching, code); - !isMatching && this.clear(); - } else { - onFulfill(code); - } - this._blur(this.state.currentIndex); - } else { - this._setFocus(this.state.currentIndex + 1); + + _getClassStyle(className, active) { + const { cellBorderWidth, activeColor, inactiveColor, space } = this.props; + let classStyle = { + ...this._getInputSpaceStyle(space), + color: activeColor + }; + + switch (className) { + case 'clear': + return _.merge(classStyle, { borderWidth: 0 }); + case 'border-box': + return _.merge(classStyle, { + borderWidth: cellBorderWidth, + borderColor: (active ? activeColor : inactiveColor) + }); + case 'border-circle': + return _.merge(classStyle, { + borderWidth: cellBorderWidth, + borderRadius: 50, + borderColor: (active ? activeColor : inactiveColor) + }); + case 'border-b': + return _.merge(classStyle, { + borderBottomWidth: cellBorderWidth, + borderColor: (active ? activeColor : inactiveColor), + }); + case 'border-b-t': + return _.merge(classStyle, { + borderTopWidth: cellBorderWidth, + borderBottomWidth: cellBorderWidth, + borderColor: (active ? activeColor : inactiveColor) + }); + case 'border-l-r': + return _.merge(classStyle, { + borderLeftWidth: cellBorderWidth, + borderRightWidth: cellBorderWidth, + borderColor: (active ? activeColor : inactiveColor) + }); + default: + return className; + } } - - this.setState(prevState => { - return { - codeArr: newCodeArr, - currentIndex: prevState.currentIndex + 1 - }; - }); - } - - render() { - const { - codeLength, - codeInputStyle, - containerStyle, - inputPosition, - autoFocus, - className, - size, - activeColor - } = this.props; - - const initialCodeInputStyle = { - width: size, - height: size - }; - - let codeInputs = []; - for (let i = 0; i < codeLength; i++) { - const id = i; - codeInputs.push( - (this.codeInputRefs[id] = ref)} - style={[ - styles.codeInput, - initialCodeInputStyle, - this._getClassStyle(className, this.state.currentIndex == id), - codeInputStyle - ]} - underlineColorAndroid="transparent" - selectionColor={activeColor} - keyboardType={'name-phone-pad'} - returnKeyType={'done'} - {...this.props} - autoFocus={autoFocus && id == 0} - onFocus={() => this._onFocus(id)} - value={this.state.codeArr[id] ? this.state.codeArr[id].toString() : ''} - onChangeText={text => this._onInputCode(text, id)} - onKeyPress={(e) => this._onKeyPress(e)} - maxLength={1} - /> - ) + + _onKeyPress(e) { + if (e.nativeEvent.key === 'Backspace') { + const { currentIndex } = this.state; + const nextIndex = currentIndex > 0 ? currentIndex - 1 : 0; + this._setFocus(nextIndex); + } + } + + _onInputCode(character, index) { + const { codeLength, onFulfill, compareWithCode, ignoreCase } = this.props; + let newCodeArr = _.clone(this.state.codeArr); + newCodeArr[index] = character; + + if (index == codeLength - 1) { + const code = newCodeArr.join(''); + + if (compareWithCode) { + const isMatching = this._isMatchingCode(code, compareWithCode, ignoreCase); + onFulfill(isMatching, code); + !isMatching && this.clear(); + } else { + onFulfill(code); + } + this._blur(this.state.currentIndex); + } else { + this._setFocus(this.state.currentIndex + 1); + } + + this.setState(prevState => { + return { + codeArr: newCodeArr, + currentIndex: prevState.currentIndex + 1 + }; + }); + } + + render() { + const { + codeLength, + codeInputStyle, + containerStyle, + inputPosition, + autoFocus, + className, + size, + activeColor, + keyboardType + } = this.props; + + const initialCodeInputStyle = { + width: size, + height: size + }; + + let codeInputs = []; + for (let i = 0; i < codeLength; i++) { + const id = i; + codeInputs.push( + (this.codeInputRefs[id] = ref)} + style={[ + styles.codeInput, + initialCodeInputStyle, + this._getClassStyle(className, this.state.currentIndex == id), + codeInputStyle + ]} + underlineColorAndroid="transparent" + selectionColor={activeColor} + keyboardType={keyboardType} + returnKeyType={'done'} + {...this.props} + autoFocus={autoFocus && id == 0} + onFocus={() => this._onFocus(id)} + value={this.state.codeArr[id] ? this.state.codeArr[id].toString() : ''} + onChangeText={text => this._onInputCode(text, id)} + onKeyPress={(e) => this._onKeyPress(e)} + maxLength={1} + /> + ) + } + + return ( + + {codeInputs} + + ); } - - return ( - - {codeInputs} - - ); - } } const styles = StyleSheet.create({ - container: { - flex: 1, - flexDirection: 'row', - marginTop: 20 - }, - codeInput: { - backgroundColor: 'transparent', - textAlign: 'center', - padding: 0 - } + container: { + flex: 1, + flexDirection: 'row', + marginTop: 20 + }, + codeInput: { + backgroundColor: 'transparent', + textAlign: 'center', + padding: 0 + } }); diff --git a/index.d.ts b/index.d.ts index 3473a5d..12f8e89 100644 --- a/index.d.ts +++ b/index.d.ts @@ -8,6 +8,7 @@ declare module "react-native-confirmation-code-input" { interface CodeInputProps extends ReactNative.TextInputProperties { codeLength?: number; + keyboardType?: ReactNative.KeyboardType; compareWithCode?: string; inputPosition?: InputPositions; size?: number;