react-native-smooth-pincode-input
Version:
A cross-platform, smooth, lightweight, customizable PIN code input component for React Native.
317 lines (283 loc) • 8.49 kB
JavaScript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
View,
Text,
TextInput,
StyleSheet,
I18nManager,
ViewPropTypes,
} from 'react-native';
import * as Animatable from 'react-native-animatable';
const styles = StyleSheet.create({
containerDefault: {},
cellDefault: {
borderColor: 'gray',
borderWidth: 1,
},
cellFocusedDefault: {
borderColor: 'black',
borderWidth: 2,
},
textStyleDefault: {
color: 'gray',
fontSize: 24,
},
textStyleFocusedDefault: {
color: 'black',
},
});
class SmoothPinCodeInput extends Component {
state = {
maskDelay: false,
focused: false,
};
ref = React.createRef();
inputRef = React.createRef();
animate = ({ animation = "shake", duration = 650 }) => {
if (!this.props.animated) {
return new Promise((resolve, reject) => reject(new Error("Animations are disabled")));
}
return this.ref.current[animation](duration);
};
shake = () => this.animate({animation: "shake"});
focus = () => {
return this.inputRef.current.focus();
};
blur = () => {
return this.inputRef.current.blur();
};
clear = () => {
return this.inputRef.current.clear();
};
_inputCode = (code) => {
const { password, codeLength = 4, onTextChange, onFulfill } = this.props;
if (this.props.restrictToNumbers) {
code = (code.match(/[0-9]/g) || []).join("");
}
if (onTextChange) {
onTextChange(code);
}
if (code.length === codeLength && onFulfill) {
onFulfill(code);
}
// handle password mask
const maskDelay = password &&
code.length > this.props.value.length; // only when input new char
this.setState({ maskDelay });
if (maskDelay) { // mask password after delay
clearTimeout(this.maskTimeout);
this.maskTimeout = setTimeout(() => {
this.setState({ maskDelay: false });
},
this.props.maskDelay
);
}
};
_keyPress = (event) => {
if (event.nativeEvent.key === 'Backspace') {
const { value, onBackspace } = this.props;
if (value === '' && onBackspace) {
onBackspace();
}
}
};
_onFocused = () => {
this.setState({ focused: true });
if (typeof this.props.onFocus === 'function') {
this.props.onFocus();
}
};
_onBlurred = () => {
this.setState({ focused: false });
if (typeof this.props.onBlur === 'function') {
this.props.onBlur();
}
};
componentWillUnmount() {
clearTimeout(this.maskTimeout);
}
render() {
const {
value,
codeLength, cellSize, cellSpacing,
placeholder,
password,
mask,
autoFocus,
containerStyle,
cellStyle,
cellStyleFocused,
cellStyleFilled,
textStyle,
textStyleFocused,
keyboardType,
animationFocused,
animated,
testID,
editable,
inputProps,
disableFullscreenUI,
} = this.props;
const { maskDelay, focused } = this.state;
return (
<Animatable.View
ref={this.ref}
style={[{
alignItems: 'stretch', flexDirection: 'row', justifyContent: 'center', position: 'relative',
width: cellSize * codeLength + cellSpacing * (codeLength - 1),
height: cellSize,
},
containerStyle,
]}>
<View style={{
position: 'absolute', margin: 0, height: '100%',
flexDirection: I18nManager.isRTL ? 'row-reverse': 'row',
alignItems: 'center',
}}>
{
Array.apply(null, Array(codeLength)).map((_, idx) => {
const cellFocused = focused && idx === value.length;
const filled = idx < value.length;
const last = (idx === value.length - 1);
const showMask = filled && (password && (!maskDelay || !last));
const isPlaceholderText = typeof placeholder === 'string';
const isMaskText = typeof mask === 'string';
const pinCodeChar = value.charAt(idx);
let cellText = null;
if (filled || placeholder !== null) {
if (showMask && isMaskText) {
cellText = mask;
} else if(!filled && isPlaceholderText) {
cellText = placeholder;
} else if (pinCodeChar) {
cellText = pinCodeChar;
}
}
const placeholderComponent = !isPlaceholderText ? placeholder : null;
const maskComponent = (showMask && !isMaskText) ? mask : null;
const isCellText = typeof cellText === 'string';
return (
<Animatable.View
key={idx}
style={[
{
width: cellSize,
height: cellSize,
marginLeft: cellSpacing / 2,
marginRight: cellSpacing / 2,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
cellStyle,
cellFocused ? cellStyleFocused : {},
filled ? cellStyleFilled : {},
]}
animation={idx === value.length && focused && animated ? animationFocused : null}
iterationCount="infinite"
duration={500}
>
{isCellText && !maskComponent && <Text style={[textStyle, cellFocused ? textStyleFocused : {}]}>
{cellText}
</Text>}
{(!isCellText && !maskComponent) && placeholderComponent}
{isCellText && maskComponent}
</Animatable.View>
);
})
}
</View>
<TextInput
disableFullscreenUI={disableFullscreenUI}
value={value}
ref={this.inputRef}
onChangeText={this._inputCode}
onKeyPress={this._keyPress}
onFocus={() => this._onFocused()}
onBlur={() => this._onBlurred()}
spellCheck={false}
autoFocus={autoFocus}
keyboardType={keyboardType}
numberOfLines={1}
caretHidden
maxLength={codeLength}
selection={{
start: value.length,
end: value.length,
}}
style={{
flex: 1,
opacity: 0,
textAlign: 'center',
}}
testID={testID || undefined}
editable={editable}
{...inputProps} />
</Animatable.View>
);
}
static defaultProps = {
value: '',
codeLength: 4,
cellSize: 48,
cellSpacing: 4,
placeholder: '',
password: false,
mask: '*',
maskDelay: 200,
keyboardType: 'numeric',
autoFocus: false,
restrictToNumbers: false,
containerStyle: styles.containerDefault,
cellStyle: styles.cellDefault,
cellStyleFocused: styles.cellFocusedDefault,
textStyle: styles.textStyleDefault,
textStyleFocused: styles.textStyleFocusedDefault,
animationFocused: 'pulse',
animated: true,
editable: true,
inputProps: {},
disableFullscreenUI: true,
};
}
SmoothPinCodeInput.propTypes = {
value: PropTypes.string,
codeLength: PropTypes.number,
cellSize: PropTypes.number,
cellSpacing: PropTypes.number,
placeholder: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
]),
mask: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
]),
maskDelay: PropTypes.number,
password: PropTypes.bool,
autoFocus: PropTypes.bool,
restrictToNumbers: PropTypes.bool,
containerStyle: ViewPropTypes.style,
cellStyle: ViewPropTypes.style,
cellStyleFocused: ViewPropTypes.style,
cellStyleFilled: ViewPropTypes.style,
textStyle: Text.propTypes.style,
textStyleFocused: Text.propTypes.style,
animated: PropTypes.bool,
animationFocused: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
]),
onFulfill: PropTypes.func,
onChangeText: PropTypes.func,
onBackspace: PropTypes.func,
onTextChange: PropTypes.func,
testID: PropTypes.any,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
keyboardType: PropTypes.string,
editable: PropTypes.bool,
inputProps: PropTypes.exact(TextInput.propTypes),
};
export default SmoothPinCodeInput;