Created
October 23, 2018 07:37
-
-
Save olecksamdr/b2e15fdef8b02642f6a3e5349e15251f to your computer and use it in GitHub Desktop.
Pin Input Component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { PureComponent } from 'react'; | |
import { number, func, bool } from 'prop-types'; | |
import { PinItem } from './style'; | |
const BACKSPACE_KEY = 8; | |
const LEFT_ARROW_KEY = 37; | |
const UP_ARROW_KEY = 38; | |
const RIGHT_ARROW_KEY = 39; | |
const DOWN_ARROW_KEY = 40; | |
class PinInput extends PureComponent { | |
constructor(props) { | |
super(props); | |
this.state = ({ | |
values: new Array(props.length).fill(''), | |
}); | |
this.elements = new Array(props.length); | |
this.maxIndex = props.length - 1; | |
} | |
onChange = ({ target: { value } }, currentIndex) => { | |
const { length, onComplete, onChange } = this.props; | |
const val = value.replace(/[^\d]/g, ''); | |
const values = this.update(currentIndex, val); | |
this.setState({ values }); | |
if (val.length === 1) { | |
this.focusNext(currentIndex); | |
} | |
// Notify the parent | |
const fullValue = values.join(''); | |
onChange(fullValue); | |
if (fullValue.length === length) { | |
onComplete(fullValue); | |
} | |
}; | |
onKeyDown = (event, currentIndex) => { | |
switch (event.keyCode) { | |
case BACKSPACE_KEY: | |
event.preventDefault(); | |
const values = this.update(currentIndex, ''); | |
this.props.onChange(values.join('')); | |
this.setState({ values }); | |
this.focusPrev(currentIndex); | |
break; | |
case LEFT_ARROW_KEY: | |
event.preventDefault(); | |
this.focusPrev(currentIndex); | |
break; | |
case RIGHT_ARROW_KEY: | |
event.preventDefault(); | |
this.focusNext(currentIndex); | |
break; | |
case UP_ARROW_KEY: | |
case DOWN_ARROW_KEY: | |
event.preventDefault(); | |
} | |
}; | |
update(currentIndex, value) { | |
const values = this.state.values.slice(); | |
if (value.length > 1) { | |
value.split('').forEach((char, charIndex) => { | |
const valueIndex = currentIndex + charIndex; | |
if (valueIndex <= this.maxIndex) { | |
values[valueIndex] = char; | |
this.elements[valueIndex].focus(); | |
} | |
}); | |
} else { | |
values[currentIndex] = value; | |
} | |
return values; | |
} | |
focusPrev(currentIndex) { | |
if (currentIndex > 0) { | |
this.elements[currentIndex - 1].focus(); | |
} | |
} | |
focusNext(currentIndex) { | |
if (currentIndex < this.maxIndex) { | |
this.elements[currentIndex + 1].focus(); | |
} | |
} | |
render() { | |
const { onChange, onKeyDown } = this; | |
const { disable, invalid } = this.props; | |
return ( | |
<div> | |
{this.state.values.map((value, index) => ( | |
<PinItem | |
// eslint-disable-next-line react/no-array-index-key | |
key={index} | |
autoFocus={index === 0} | |
innerRef={(ref) => { | |
this.elements[index] = ref; | |
}} | |
onFocus={event => event.target.select()} | |
onChange={event => onChange(event, index)} | |
onKeyDown={event => onKeyDown(event, index)} | |
{...{ value, disable, invalid }} | |
/> | |
))} | |
</div> | |
); | |
} | |
} | |
PinInput.propTypes = { | |
disable: bool, | |
invalid: bool, | |
length: number, | |
onComplete: func.isRequired, | |
onChange: func, | |
}; | |
PinInput.defaultProps = { | |
length: 4, | |
disable: false, | |
invalid: false, | |
onChange: () => {}, | |
}; | |
export default PinInput; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import styled from 'styled-components'; | |
import { getThemeColor, getThemeFont } from 'utils/theme'; | |
import { invalidStyles } from 'components/global/Input'; | |
export const PinItem = styled.input.attrs({ | |
autoComplete: 'off', | |
inputMode: 'numeric', | |
placeholder: '-', | |
})` | |
width: 78px; | |
padding: 20px 26px; | |
display: inline-block; | |
position: relative; | |
border-radius: 4px; | |
font-size: 2.5rem; | |
line-height: 1.2; | |
border: 1px solid ${getThemeColor(['cloudDark'])}; | |
font-weight: ${getThemeFont(['semibold'])}; | |
&:not(:last-child) { | |
margin-right: 8px; | |
} | |
::placeholder { | |
text-align: center; | |
color: ${getThemeColor(['cloudDark'])}; | |
} | |
${invalidStyles}; | |
`; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment