All files / atom/pinInput/src PinInputField.js

89.28% Statements 25/28
64.28% Branches 9/14
80% Functions 8/10
88.46% Lines 23/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107                      1x   1x 30x             1x 18x         18x     1x 30x                             30x   30x   30x   18x         30x   18x         30x 18x 18x   18x     30x           30x   30x                                     1x 1x              
import {forwardRef, useCallback, useEffect, useMemo, useRef} from 'react'
 
import cx from 'classnames'
import PropTypes from 'prop-types'
 
import useMergeRefs from '@s-ui/react-hooks/lib/useMergeRefs'
 
import {actions as pinInputActions} from './reducer/index.js'
import {BASE_CLASSNAME, MASK} from './config.js'
import {usePinInputContext} from './PinInputContext.js'
 
const CLASSNAME = `${BASE_CLASSNAME}Field`
 
const getClassName = ({size, status, isFullWidth}) => {
  return cx(CLASSNAME, {
    [`${CLASSNAME}--size-${size}`]: size,
    [`${CLASSNAME}--status-${status}`]: status,
    [`${CLASSNAME}--fullWidth`]: isFullWidth
  })
}
 
const getInputMode = ({inputMode, mask}) => {
  const maskToInputMode = {
    [MASK.NUMBER]: 'numeric',
    [MASK.ALPHABETIC]: 'text',
    [MASK.ALPHANUMERIC]: 'text'
  }
  return inputMode || maskToInputMode[mask]
}
 
const PinInputField = forwardRef(({isFullWidth, ...props}, forwardedRef) => {
  const innerRef = useRef()
  const {
    disabled,
    dispatch,
    getIndex,
    inputMode,
    isOneTimeCode,
    isPassword,
    mask,
    onChange = () => null,
    placeholder,
    setFocus,
    size,
    status,
    value = []
  } = usePinInputContext()
 
  const index = getIndex(innerRef.current)
 
  const setElement = useCallback(
    node => {
      dispatch(pinInputActions.setElement({node}))
    },
    [dispatch]
  )
 
  const removeElement = useCallback(
    node => {
      dispatch(pinInputActions.removeElement({node}))
    },
    [dispatch]
  )
 
  useEffect(() => {
    Eif (innerRef.current) {
      setElement(innerRef.current)
    }
    return () => removeElement(innerRef.current) // eslint-disable-line react-hooks/exhaustive-deps
  }, [innerRef, setElement, removeElement])
 
  const onFocusHandler = () => {
    if (index >= 0) {
      setFocus(index)
    }
  }
 
  const inputModeMemoized = useMemo(() => getInputMode({inputMode, mask}), [mask, inputMode])
 
  return (
    <input
      className={getClassName({size, status, isFullWidth})}
      disabled={disabled}
      maxLength="1"
      onChange={onChange}
      onClick={onFocusHandler}
      onFocus={onFocusHandler}
      placeholder={placeholder}
      inputMode={inputModeMemoized}
      ref={useMergeRefs(innerRef, forwardedRef)}
      value={value[index] || ''}
      {...(isPassword && {type: 'password'})}
      {...(isOneTimeCode && {autoComplete: 'one-time-code'})}
      {...props}
    />
  )
})
 
PinInputField.displayName = 'PinInputField'
PinInputField.propTypes = {
  /** number value position */
  index: PropTypes.number,
  /** true input full width false default */
  isFullWidth: PropTypes.bool
}
export default PinInputField