jsx import { CSSObject , jsx } from '@emotion/core' ; import React , { useEffect , useState } from 'react' ; import type { Theme } from '../Layout' ; import { noop } from '../util' ; import { InputProps , inputStyle } from './Input' ; const CodeInputWrapper = ( props ) => ( < div css = { { display : 'flex' , justifyContent : 'center' , } } { ... props } />); type DigitInputProps < T = HTMLInputElement > = InputProps < T > ; const digitInputStyle = ( theme , props ) => ( { ... inputStyle ( theme , props ) , '& + &' : { marginLeft : '19px' , } , fontSize : '32px' , lineHeight : '56px' , padding : 0 , textAlign : 'center' , width : '48px' , } ) ; const DigitInput = React . forwardRef < HTMLInputElement , DigitInputProps < HTMLInputElement > > ( ( props , ref ) => < input ref = { ref } css = { theme => digitInputStyle ( theme , props ) } { ... props } />); export interface CodeInputProps < T = HTMLInputElement > extends InputProps < T > { autoFocus ? : boolean ; digits ? : number ; markInvalid ? : boolean ; onCodeComplete ? : ( completeCode ? : string ) => void ; } export const CodeInput = ( { style , digits = 6 , autoFocus = false , markInvalid , onCodeComplete = noop , disabled , } ) => { const [ values , setValues ] = useState ( Array ( digits ) . fill ( '' ) ) ; const inputs = Array ( digits ) ; const forceSelection = ( event ) => { const target = event . target as HTMLInputElement ; target . select ( ) ; } ; const forceSelectionPreventDefault = ( event ) => { forceSelection ( event ) ; event . preventDefault ( ) ; } ; const nextField = ( currentFieldIndex ) => { const nextFieldIndex = currentFieldIndex + 1 ; if ( nextFieldIndex < digits ) { inputs [ nextFieldIndex ] . focus ( ) ; } } ; const previousField = ( currentFieldIndex ) => { if ( currentFieldIndex > 0 ) { inputs [ currentFieldIndex - 1 ] . focus ( ) ; } } ; const setValue = ( fieldIndex , value ) => { if ( /^[0-9]?$/ . test ( value ) ) { const valuesCopy = values . slice ( ) ; valuesCopy [ fieldIndex ] = value ; setValues ( valuesCopy ) ; if ( value . length ) { nextField ( fieldIndex ) ; } } } ; const handleKeyDown = ( fieldIndex , { key } ) => { switch ( key ) { case 'Backspace' : setValue ( fieldIndex , '' ) ; previousField ( fieldIndex ) ; break ; case 'ArrowLeft' : previousField ( fieldIndex ) ; break ; case 'ArrowRight' : nextField ( fieldIndex ) ; break ; } if ( /^[0-9]$/ . test ( key ) ) { setValue ( fieldIndex , key ) ; } } ; const handlePaste = ( fieldIndex , event ) => { const pastedValue = event . clipboardData . getData ( 'Text' ) ; const cleanedPaste = pastedValue . replace ( /[^0-9]/g , '' ) ; if ( /^[0-9]+$/ . test ( cleanedPaste ) ) { setValues ( values . slice ( 0 , fieldIndex ) . concat ( cleanedPaste . split ( '' ) ) . slice ( 0 , digits ) ) ; } } ; useEffect ( ( ) => { const completeCode = values . join ( '' ) ; if ( completeCode . length === digits ) { onCodeComplete ( completeCode ) ; } } , [ values ] ) ; return ( < CodeInputWrapper style = { style } > { Array . from ( { length : digits } , ( _ , index ) => ( < DigitInput autoFocus = { index === 0 && autoFocus } key = { index } onPaste = { event => handlePaste ( index , event ) } onFocus = { forceSelection } onMouseDown = { forceSelectionPreventDefault } onTouchStart = { forceSelectionPreventDefault } onKeyDown = { event => handleKeyDown ( index , event ) } onKeyUp = { forceSelection } markInvalid = { markInvalid } ref = { node => ( inputs [ index ] = node ) } type = "text" value = { values [ index ] } onChange = { ( ) => { } } disabled = { disabled } / > ) ) } < /CodeInputWrapper>); } ;