nexxtway/react-rainbow

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

Summary

Maintainability
B
4 hrs
Test Coverage
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import autosize from 'autosize';
import withReduxForm from '../../libs/hocs/withReduxForm';
import { uniqueId } from '../../libs/utils';
import RenderIf from '../RenderIf';
import Label from '../Input/label';
import StyledContainer from './styled/container';
import StyledTextareaContainer from './styled/textareaContainer';
import StyledTextarea from './styled/textarea';
import StyledBottomHelp from '../Input/styled/helpText';
import StyledError from '../Input/styled/errorText';

/**
 * Textarea inputs are used for freeform data entry.
 * @category Form
 */
class Textarea extends Component {
    constructor(props) {
        super(props);
        this.textareaRef = React.createRef();
        this.textareaId = uniqueId('textarea');
        this.inlineTextLabelId = uniqueId('inline-text-label');
        this.errorMessageId = uniqueId('error-message');
        this.updateFocus = this.updateFocus.bind(this);
        this.state = {
            isFocused: false,
        };
    }

    componentDidMount() {
        const { grow } = this.props;
        if (grow) {
            return autosize(this.textareaRef.current);
        }
        return null;
    }

    getInlineTextLabelId() {
        const { bottomHelpText } = this.props;
        if (bottomHelpText) {
            return this.inlineTextLabelId;
        }
        return undefined;
    }

    getErrorMessageId() {
        const { error } = this.props;
        if (error) {
            return this.errorMessageId;
        }
        return undefined;
    }

    /**
     * Sets focus on the element.
     * @public
     */
    focus() {
        this.textareaRef.current.focus();
    }

    /**
     * Sets click on the element.
     * @public
     */
    click() {
        this.textareaRef.current.click();
    }

    /**
     * Sets blur on the element.
     * @public
     */
    blur() {
        this.textareaRef.current.blur();
    }

    updateFocus(isFocused, handler) {
        return (...args) => {
            this.setState({ isFocused });
            handler(...args);
        };
    }

    render() {
        const {
            style,
            className,
            onChange,
            onClick,
            onFocus,
            onBlur,
            onKeyDown,
            onPaste,
            value,
            readOnly,
            label,
            error,
            placeholder,
            disabled,
            maxLength,
            minLength,
            bottomHelpText,
            required,
            rows,
            id,
            labelAlignment,
            hideLabel,
            name,
            header,
            footer,
            variant,
            size,
            borderRadius,
        } = this.props;
        const { isFocused } = this.state;

        return (
            <StyledContainer className={className} style={style} id={id}>
                <Label
                    label={label}
                    labelAlignment={labelAlignment}
                    hideLabel={hideLabel}
                    required={required}
                    inputId={this.textareaId}
                    readOnly={readOnly}
                    size={size}
                    id={this.getInlineTextLabelId()}
                />
                <StyledTextareaContainer
                    error={error}
                    readOnly={readOnly}
                    disabled={disabled}
                    variant={variant}
                    isFocused={isFocused}
                    borderRadius={borderRadius}
                >
                    <RenderIf isTrue={header}>{header}</RenderIf>
                    <StyledTextarea
                        error={error}
                        id={this.textareaId}
                        name={name}
                        placeholder={placeholder}
                        disabled={disabled}
                        required={required}
                        maxLength={maxLength}
                        minLength={minLength}
                        onChange={onChange}
                        onClick={onClick}
                        onFocus={this.updateFocus(true, onFocus)}
                        onBlur={this.updateFocus(false, onBlur)}
                        onKeyDown={onKeyDown}
                        onPaste={onPaste}
                        readOnly={readOnly}
                        rows={rows}
                        value={value}
                        aria-labelledby={this.getInlineTextLabelId()}
                        aria-describedby={this.getErrorMessageId()}
                        ref={this.textareaRef}
                        size={size}
                    />
                    <RenderIf isTrue={footer}>{footer}</RenderIf>
                </StyledTextareaContainer>
                <RenderIf isTrue={bottomHelpText}>
                    <StyledBottomHelp>{bottomHelpText}</StyledBottomHelp>
                </RenderIf>
                <RenderIf isTrue={error}>
                    <StyledError id={this.getErrorMessageId()}>{error}</StyledError>
                </RenderIf>
            </StyledContainer>
        );
    }
}

Textarea.propTypes = {
    /** Text label for the Textarea. */
    label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
    /** Describes the position of the Textarea label. Options include left, center and right.
     * This value defaults to center. */
    labelAlignment: PropTypes.oneOf(['left', 'center', 'right']),
    /** A boolean to hide the Textarea label */
    hideLabel: PropTypes.bool,
    /** The name of the Textarea */
    name: PropTypes.string,
    /** The value of the Textarea, also used as the default value during init. */
    value: PropTypes.string,
    /** Text that is displayed when the field is empty, to prompt the user for a valid entry. */
    placeholder: PropTypes.string,
    /** The maximum number of characters allowed in the Textarea. */
    maxLength: PropTypes.number,
    /** The minimum number of characters allowed in the Textarea. */
    minLength: PropTypes.number,
    /** This make to Textarea grow. This value defaults to false. */
    grow: PropTypes.bool,
    /** Shows the help message below the Textarea. */
    bottomHelpText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
    /** Specifies that the textarea field must be filled out before submitting the form.
     * This value defaults to false. */
    required: PropTypes.bool,
    /** Specifies that the textarea field must be filled out before submitting the form. */
    error: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
    /** Specifies that the textarea element should be disabled. This value defaults to false. */
    disabled: PropTypes.bool,
    /** The number of visible text lines for the control. The value by default is 3. */
    rows: PropTypes.number,
    /** Specifies that the textarea field is read-only. This value defaults to false. */
    readOnly: PropTypes.bool,
    /** The action triggered when a value attribute changes. */
    onChange: PropTypes.func,
    /** The action triggered when the element is clicked. */
    onClick: PropTypes.func,
    /** The action triggered when the element receives 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,
    /** Event fired when the user paste on the Textarea */
    onPaste: 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,
    /** The variant changes the appearance of the Textarea. Accepted variants include default,
     * and shaded. This value defaults to default. */
    variant: PropTypes.oneOf(['default', 'shaded']),
    /** The id of the outer element. */
    id: PropTypes.string,
    /** It is what will be displayed at the top of the component. */
    header: PropTypes.node,
    /** It is what will be displayed at the bottom of the component. */
    footer: PropTypes.node,
    /** The size of the textarea. Valid values are small, medium, and large. */
    size: PropTypes.oneOf(['small', 'medium', 'large']),
    /** The border radius of the textarea. Valid values are square, semi-square, semi-rounded and rounded. This value defaults to rounded. */
    borderRadius: PropTypes.oneOf(['square', 'semi-square', 'semi-rounded', 'rounded']),
};

Textarea.defaultProps = {
    label: undefined,
    value: undefined,
    name: undefined,
    placeholder: null,
    maxLength: undefined,
    minLength: undefined,
    grow: false,
    bottomHelpText: null,
    required: false,
    error: null,
    disabled: false,
    rows: 3,
    readOnly: false,
    onChange: () => {},
    onClick: () => {},
    onFocus: () => {},
    onBlur: () => {},
    onKeyDown: () => {},
    onPaste: () => {},
    className: undefined,
    style: undefined,
    variant: 'default',
    id: undefined,
    labelAlignment: 'center',
    hideLabel: false,
    header: undefined,
    footer: undefined,
    size: 'medium',
    borderRadius: 'rounded',
};

export default withReduxForm(Textarea);