toggle-corp/react-store

View on GitHub
components/Input/ChecklistInput/index.js

Summary

Maintainability
A
0 mins
Test Coverage
import PropTypes from 'prop-types';
import React from 'react';
import memoize from 'memoize-one';
import {
    _cs,
    isFalsyString,
    caseInsensitiveSubmatch,
    compareStringSearch,
} from '@togglecorp/fujs';
import { FaramInputElement } from '@togglecorp/faram';

import ListView from '../../View/List/ListView';
import Checkbox from '../Checkbox';

import Label from '../Label';
import HintAndError from '../HintAndError';

import styles from './styles.scss';

const propTypes = {
    className: PropTypes.string,
    listClassName: PropTypes.string,
    value: PropTypes.arrayOf(PropTypes.oneOfType([
        PropTypes.number,
        PropTypes.string,
    ])),
    options: PropTypes.arrayOf(PropTypes.object),
    onChange: PropTypes.func.isRequired,
    keySelector: PropTypes.func,
    labelSelector: PropTypes.func,
    tooltipSelector: PropTypes.func,
    searchText: PropTypes.string,

    label: PropTypes.string,
    disabled: PropTypes.bool,
    readOnly: PropTypes.bool,
    error: PropTypes.string,
    hint: PropTypes.string,
    showLabel: PropTypes.bool,
    showHintAndError: PropTypes.bool,
    segment: PropTypes.bool,
};

const defaultProps = {
    className: '',
    listClassName: '',
    value: [],
    options: [],
    keySelector: item => item.key,
    tooltipSelector: item => item.label,
    labelSelector: item => item.label,
    searchText: undefined,

    label: '',
    disabled: false,
    readOnly: false,
    error: '',
    hint: '',
    showLabel: true,
    showHintAndError: true,
    segment: false,
};


export class NormalChecklistInput extends React.PureComponent {
    static propTypes = propTypes;

    static defaultProps = defaultProps;

    getClassName = () => {
        const {
            className: classNameFromProps,
            disabled,
            error,
            segment,
        } = this.props;

        const className = _cs(
            classNameFromProps,
            styles.listSelection,
            'list-selection',
            segment ? 'multi-segment' : 'single-segment',
            disabled && 'disabled',
            error && 'error',
        );

        return className;
    }

    getFilteredOptions = memoize((searchText, options) => {
        const {
            labelSelector,
        } = this.props;

        if (isFalsyString(searchText)) {
            return options;
        }

        const newOptions = options
            .filter(option => caseInsensitiveSubmatch(labelSelector(option), searchText))
            .sort((a, b) => compareStringSearch(
                labelSelector(a),
                labelSelector(b),
                searchText,
            ));
        return newOptions;
    });

    handleItemChange = (key, isSelected) => {
        const { value, onChange } = this.props;

        const newValue = [...value];
        if (!isSelected) {
            const index = newValue.indexOf(key);
            newValue.splice(index, 1);
        } else {
            newValue.push(key);
        }

        onChange(newValue);
    }

    renderParams = (key, itemData) => {
        const {
            labelSelector,
            tooltipSelector,
            value,
            disabled,
            readOnly,
            segment,
        } = this.props;

        const selected = value.indexOf(key) >= 0;

        return {
            className: styles.item,
            label: labelSelector(itemData),
            tooltip: tooltipSelector(itemData),
            value: selected,
            onChange: val => this.handleItemChange(key, val),
            checkboxType: segment ? 'check' : 'checkbox',
            disabled,
            readOnly,
        };
    }

    renderInput = () => {
        const {
            options,
            keySelector,
            listClassName,
            searchText,
        } = this.props;

        const filteredOptions = this.getFilteredOptions(searchText, options);

        return (
            <ListView
                className={_cs(
                    styles.options,
                    'list-selection-options',
                    listClassName,
                )}
                data={filteredOptions}
                renderer={Checkbox}
                keySelector={keySelector}
                rendererParams={this.renderParams}
            />
        );
    }

    render() {
        const {
            label,
            hint,
            error,
            showHintAndError,
            showLabel,
            disabled,
        } = this.props;

        const className = this.getClassName();
        const Input = this.renderInput;

        return (
            <div className={className}>
                <Label
                    show={showLabel}
                    text={label}
                    disabled={disabled}
                    error={!!error}
                />
                <Input />
                <HintAndError
                    show={showHintAndError}
                    hint={hint}
                    error={error}
                />
            </div>
        );
    }
}

export default FaramInputElement(NormalChecklistInput);