toggle-corp/react-store

View on GitHub
components/Input/MultiSelectInput/Options/index.js

Summary

Maintainability
A
0 mins
Test Coverage
import React from 'react';
import PropTypes from 'prop-types';
import { listToMap, isListEqual } from '@togglecorp/fujs';

import FloatingContainer from '../../../View/FloatingContainer';
import List from '../../../View/List';
import Option from '../Option';

import styles from './styles.scss';

const propTypes = {
    activeKeys: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
    className: PropTypes.string,
    data: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
    keySelector: PropTypes.func.isRequired,
    labelSelector: PropTypes.func.isRequired,
    optionLabelSelector: PropTypes.func,
    onBlur: PropTypes.func.isRequired,
    onInvalidate: PropTypes.func.isRequired,
    onOptionClick: PropTypes.func.isRequired,
    onOptionFocus: PropTypes.func.isRequired,
    parentContainer: PropTypes.object, // eslint-disable-line react/forbid-prop-types
    renderEmpty: PropTypes.func,
    show: PropTypes.bool.isRequired,
    focusedKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};

const defaultProps = {
    className: '',
    renderEmpty: () => 'No option available',
    parentContainer: undefined,
    focusedKey: undefined,
    optionLabelSelector: undefined,
};

export default class Options extends React.PureComponent {
    static propTypes = propTypes;

    static defaultProps = defaultProps;

    constructor(props) {
        super(props);
        this.generateActiveMap(props);
    }

    // eslint-disable-next-line camelcase
    UNSAFE_componentWillReceiveProps(nextProps) {
        const { activeKeys: oldActiveKeys } = this.props;
        const { activeKeys: newActiveKeys } = nextProps;

        if (!isListEqual(oldActiveKeys, newActiveKeys)) {
            this.generateActiveMap(nextProps);
        }
    }

    getClassName = () => {
        const { className } = this.props;

        const classNames = [
            className,
            styles.options,
        ];

        return classNames.join(' ');
    }

    generateActiveMap = (props) => {
        const { activeKeys } = props;

        this.activeKeysMap = listToMap(
            activeKeys,
            optionKey => optionKey,
            () => true,
        );
    }

    renderOption = (k, data) => {
        const {
            keySelector,
            labelSelector,
            onOptionClick,
            onOptionFocus,
            optionLabelSelector,
            focusedKey,
        } = this.props;

        const key = keySelector(data);
        const label = optionLabelSelector ? (
            optionLabelSelector(data)
        ) : (
            labelSelector(data)
        );
        const isActive = !!this.activeKeysMap[key];
        const isFocused = key === focusedKey;

        return (
            <Option
                key={key}
                optionKey={key}
                optionLabel={label}
                onClick={onOptionClick}
                onFocus={onOptionFocus}
                active={isActive}
                focused={isFocused}
            />
        );
    }

    renderEmpty = () => {
        const {
            renderEmpty: EmptyComponent,
            data,
        } = this.props;

        if (data.length > 0) {
            return null;
        }

        const className = `empty ${styles.empty}`;
        return (
            <div className={className}>
                <EmptyComponent />
            </div>
        );
    }

    render() {
        const {
            onBlur,
            onInvalidate,
            parentContainer,
            data,
            show,
        } = this.props;

        const className = this.getClassName();
        const Empty = this.renderEmpty;

        if (!show) {
            return null;
        }

        return (
            <FloatingContainer
                onBlur={onBlur}
                onInvalidate={onInvalidate}
                parent={parentContainer}
                className={className}
            >
                <List
                    data={data}
                    modifier={this.renderOption}
                />
                <Empty />
            </FloatingContainer>
        );
    }
}