@andreferi/react-native-confirmation-code-input
Version:
A react-native component to input confirmation code for both Android and IOS
329 lines (297 loc) • 8.74 kB
JavaScript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View, TextInput, StyleSheet, ViewPropTypes, Clipboard } from 'react-native';
import _ from 'lodash';
// if ViewPropTypes is not defined fall back to View.propType (to support RN < 0.44)
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,
isPasted: false
};
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");
}
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);
}
for (const i in newCodeArr) {
if (i >= index) {
newCodeArr[i] = '';
}
}
this.setState({
codeArr: newCodeArr,
currentIndex: index
})
}
_isMatchingCode(code, compareWithCode, ignoreCase = false) {
if (ignoreCase) {
return code.toLowerCase() == compareWithCode.toLowerCase();
}
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
}
}
}
_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
};
}
}
_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;
}
}
_onKeyPress(e) {
if (e.nativeEvent.key === 'Backspace') {
const { currentIndex } = this.state;
const nextIndex = currentIndex > 0 ? currentIndex - 1 : 0;
this._setFocus(nextIndex);
}
}
async _onInputCode(character, index) {
var copiedContent = await Clipboard.getString();
let pastedArr = copiedContent.split('')
let newCodeArr = _.clone(this.state.codeArr);
let isPasted = newCodeArr.includes(pastedArr[0])
const { codeLength, onFulfill, compareWithCode, ignoreCase } = this.props;
if (!isPasted && index == 0 && character.length > 1) {
for (let i in pastedArr) {
newCodeArr[i] = pastedArr[i]
}
if (copiedContent.length == codeLength) {
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(pastedArr.length)
}
this.setState({
codeArr: newCodeArr,
currentIndex: pastedArr.length,
isPasted: true
})
} else {
const i = this.state.currentIndex
newCodeArr[i] = character
if (i == 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(i);
} else {
this._setFocus(i + 1)
}
this.setState(prevState => ({
codeArr: newCodeArr,
currentIndex: prevState.currentIndex + 1,
isPasted: true
}))
}
}
render() {
const {
codeLength,
codeInputStyle,
containerStyle,
inputPosition,
autoFocus,
className,
size } = this.props;
const initialCodeInputStyle = {
width: size,
height: size
};
let codeInputs = [];
for (let i = 0; i < codeLength; i++) {
const id = i;
codeInputs.push(
<TextInput
key={id}
ref={ref => (this.codeInputRefs[id] = ref)}
style={[
styles.codeInput,
initialCodeInputStyle,
this._getClassStyle(className, this.state.currentIndex == id),
codeInputStyle,
(className == 'border-circle' && this.state.codeArr[id]) && {
backgroundColor: '#e5e5e5'
}
]}
underlineColorAndroid="transparent"
selectionColor={'white'}
keyboardType={'name-phone-pad'}
returnKeyType={'done'}
{...this.props}
autoFocus={autoFocus && id == 0}
onFocus={() => this._onFocus(id)}
value={this.state.codeArr[id] && className !== 'border-circle' ? this.state.codeArr[id].toString() : ''}
onChangeText={text => this._onInputCode(text, id)}
onKeyPress={(e) => this._onKeyPress(e)}
/>
)
}
return (
<View style={[styles.container, this._getContainerStyle(size, inputPosition), containerStyle]}>
{codeInputs}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
marginTop: 20
},
codeInput: {
backgroundColor: 'transparent',
textAlign: 'center',
flex: 1,
padding: 0
}
});