toggle-corp/react-store

View on GitHub
components/General/Modalize.js

Summary

Maintainability
A
1 hr
Test Coverage
import React, { Fragment, useState, useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
import { _cs, randomString } from '@togglecorp/fujs';

const controlledWrapperPropTypes = {
    disabled: PropTypes.bool,
    modal: PropTypes.element.isRequired,
    className: PropTypes.string,
    onClose: PropTypes.func,
    showModal: PropTypes.bool,
    onModalVisibilityChange: PropTypes.func,
};

const controlledWrapperDefaultProps = {
    showModal: false,
    disabled: false,
    className: '',
    onClose: undefined,
    onModalVisibilityChange: undefined,
};

export const controlledModalize = (WrappedButtonComponent) => {
    const ModalComponent = (props) => {
        const {
            disabled,
            modal,
            className: classNameFromProps,
            onClose,
            showModal,
            onModalVisibilityChange,
            ...otherProps
        } = props;

        const [wrappedButtonClassName] = useState(() => randomString(16));
        const [wrappedButtonBCR, setWrappedButtonBCR] = useState(undefined);

        const handleWrappedButtonBCRChange = useCallback(() => {
            const wrappedButton = document.getElementsByClassName(wrappedButtonClassName)[0];

            if (wrappedButton) {
                const newWrappedButtonBCR = wrappedButton.getBoundingClientRect();
                setWrappedButtonBCR(newWrappedButtonBCR);
            }
        }, [setWrappedButtonBCR, wrappedButtonClassName]);

        useEffect(() => {
            handleWrappedButtonBCRChange();
        }, [handleWrappedButtonBCRChange]);

        const handleResize = useCallback(() => {
            if (!showModal) {
                return;
            }
            handleWrappedButtonBCRChange();
        }, [handleWrappedButtonBCRChange, showModal]);

        const handleScroll = useCallback(() => {
            if (!showModal) {
                return;
            }
            handleWrappedButtonBCRChange();
        }, [handleWrappedButtonBCRChange, showModal]);

        useEffect(() => {
            window.addEventListener('scroll', handleScroll, true);
            window.addEventListener('resize', handleResize);

            return () => {
                window.removeEventListener('scroll', handleScroll, true);
                window.removeEventListener('resize', handleResize);
            };
        }, [handleScroll, handleResize]);

        const handleWrappedButtonClick = useCallback(() => {
            handleWrappedButtonBCRChange();

            if (onModalVisibilityChange) {
                onModalVisibilityChange(true);
            }
        }, [handleWrappedButtonBCRChange, onModalVisibilityChange]);

        const handleModalClose = useCallback((...args) => {
            if (onModalVisibilityChange) {
                onModalVisibilityChange(false);
            }
            if (onClose) {
                onClose(...args);
            }
        }, [onClose, onModalVisibilityChange]);

        const className = _cs(
            classNameFromProps,
            wrappedButtonClassName,
        );

        return (
            <Fragment>
                <WrappedButtonComponent
                    disabled={disabled || showModal}
                    onClick={handleWrappedButtonClick}
                    className={className}
                    {...otherProps}
                />
                { showModal && React.cloneElement(
                    modal,
                    {
                        closeModal: handleModalClose,
                        parentBCR: wrappedButtonBCR,
                    },
                )}
            </Fragment>
        );
    };

    ModalComponent.propTypes = controlledWrapperPropTypes;
    ModalComponent.defaultProps = controlledWrapperDefaultProps;

    return ModalComponent;
};

const propTypes = {
    initialShowModal: PropTypes.bool,
};

const defaultProps = {
    initialShowModal: false,
};

const modalize = (WrappedButtonComponent) => {
    const ControlledWrappedComponent = controlledModalize(WrappedButtonComponent);

    const ModalComponent = (props) => {
        const {
            initialShowModal,
            ...otherProps
        } = props;
        const [showModal, setShowModal] = useState(initialShowModal);

        return (
            <ControlledWrappedComponent
                showModal={showModal}
                onModalVisibilityChange={setShowModal}
                {...otherProps}
            />
        );
    };

    ModalComponent.propTypes = propTypes;
    ModalComponent.defaultProps = defaultProps;

    return ModalComponent;
};

export default modalize;