nexxtway/react-rainbow

View on GitHub
src/components/CodeInput/index.js

Summary

Maintainability
C
7 hrs
Test Coverage
/* eslint-disable react/no-unused-prop-types */
import React, { useEffect, useImperativeHandle, useRef } from 'react';
import PropTypes from 'prop-types';
import InputItems from './inputItems';
import RenderIf from '../RenderIf';
import Label from '../Input/label';
import { useReduxForm } from '../../libs/hooks';
import { useFocusedIndexState, usePreviousIndex, useValueState } from './hooks';
import { getNormalizedValue, getNumbersFromClipboard, setFocus } from './helpers';
import { StyledErrorMessage, StyledFieldset, StyledHelpText } from './styled';

/**
 * The CodeInput is an element that allows to fill a list of numbers, suitable for code validations.
 * @category Form
 */
const CodeInput = React.forwardRef((props, ref) => {
    const {
        id,
        value: valueProp,
        label,
        labelAlignment,
        hideLabel,
        bottomHelpText,
        length,
        disabled,
        required,
        readOnly,
        error,
        tabIndex,
        onClick,
        onChange,
        onFocus,
        onBlur,
        onKeyDown,
        className,
        style,
    } = useReduxForm(props);
    const inputRef = useRef();
    const value = useValueState(valueProp, length);
    const focusedIndex = useFocusedIndexState(value, length, disabled, readOnly);
    const previousFocusedIndex = usePreviousIndex(focusedIndex);

    useImperativeHandle(ref, () => ({
        focus: () => {
            inputRef.current.focus();
        },
        click: () => {
            inputRef.current.click();
        },
        blur: () => {
            inputRef.current.blur();
        },
    }));

    useEffect(() => {
        if (previousFocusedIndex !== undefined) {
            setFocus(inputRef);
        }
    }, [inputRef, focusedIndex, previousFocusedIndex]);

    const handleOnChange = (inputValue, index) => {
        const newValue = getNormalizedValue(inputValue, index, value);
        const hasValueChanged = newValue !== valueProp;
        if (hasValueChanged) {
            onChange(newValue);
        }
    };

    const handleOnFocus = (event, index) => {
        if (focusedIndex !== index) {
            setFocus(inputRef);
        }
        onFocus(event);
    };

    const handleOnPaste = event => {
        onChange(getNumbersFromClipboard(event.clipboardData.getData('Text')));
    };

    return (
        <StyledFieldset className={className} style={style} id={id}>
            <RenderIf isTrue={label}>
                <Label
                    label={label}
                    labelAlignment={labelAlignment}
                    hideLabel={hideLabel}
                    required={required}
                    as="legend"
                />
            </RenderIf>

            <InputItems
                value={value}
                disabled={disabled}
                readOnly={readOnly}
                error={error}
                tabIndex={tabIndex}
                onClick={onClick}
                onChange={handleOnChange}
                onFocus={handleOnFocus}
                onBlur={onBlur}
                onKeyDown={onKeyDown}
                onPaste={handleOnPaste}
                focusedIndex={focusedIndex}
                ref={inputRef}
            />
            <RenderIf isTrue={bottomHelpText}>
                <StyledHelpText>{bottomHelpText}</StyledHelpText>
            </RenderIf>
            <RenderIf isTrue={error}>
                <StyledErrorMessage>{error}</StyledErrorMessage>
            </RenderIf>
        </StyledFieldset>
    );
});

CodeInput.propTypes = {
    /** The id of the outer element. */
    id: PropTypes.string,
    /** Specifies the value of CodeInput. */
    value: PropTypes.string,
    /** Specifies the label CodeInput. */
    label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
    /** Describes the position of the CodeInput label. Options include left, center and right.
     * This value defaults to center. */
    labelAlignment: PropTypes.oneOf(['left', 'center', 'right']),
    /** A boolean to hide the CodeInput label. */
    hideLabel: PropTypes.bool,
    /** Shows the help message below the CodeInput */
    bottomHelpText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
    /** Specifies the numeric length to be filled. */
    length: PropTypes.number,
    /** Specifies that the CodeInput element should be disabled. This value defaults to false. */
    disabled: PropTypes.bool,
    /** Specifies that the CodeInput field must be filled before submitting the form. */
    required: PropTypes.bool,
    /** Specifies that the CodeInput is read-only. */
    readOnly: PropTypes.bool,
    /** Specifies that the CodeInput must be filled out before submitting the form. */
    error: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
    /** Specifies the tab order of an element (when the tab button is used for navigating). */
    tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    /** The action triggered when the element is clicked. */
    onClick: PropTypes.func,
    /** The action triggered when the value changes. */
    onChange: PropTypes.func,
    /** The action triggered when the element receives the focus. */
    onFocus: PropTypes.func,
    /** The action triggered when the element releases focus. */
    onBlur: PropTypes.func,
    /** The action triggered when a key is pressed on the element. */
    onKeyDown: PropTypes.func,
    /** A CSS class for the outer element, in addition to the component's base classes. */
    className: PropTypes.string,
    /** An object with custom style applied to the outer element. */
    style: PropTypes.object,
};

CodeInput.defaultProps = {
    id: undefined,
    value: '',
    label: undefined,
    labelAlignment: 'center',
    hideLabel: false,
    bottomHelpText: undefined,
    length: 4,
    disabled: false,
    required: false,
    readOnly: false,
    error: null,
    tabIndex: undefined,
    onClick: () => {},
    onChange: () => {},
    onFocus: () => {},
    onBlur: () => {},
    onKeyDown: () => {},
    className: undefined,
    style: undefined,
};

export default CodeInput;