RocketChat/Rocket.Chat

View on GitHub
packages/livechat/src/components/Tooltip/index.js

Summary

Maintainability
A
0 mins
Test Coverage
import { cloneElement, Component, createContext, toChildArray } from 'preact';

import { createClassName } from '../../helpers/createClassName';
import styles from './styles.scss';

const getPositioningStyle = (placement, { left, top, right, bottom }) => {
    switch (placement) {
        case 'left':
            return {
                left: `${left}px`,
                top: `${(top + bottom) / 2}px`,
            };

        case 'top':
        case 'top-left':
        case 'top-right':
            return {
                left: `${(left + right) / 2}px`,
                top: `${top}px`,
            };

        case 'right':
            return {
                left: `${right}px`,
                top: `${(top + bottom) / 2}px`,
            };

        case 'bottom':
        case 'bottom-left':
        case 'bottom-right':
        default:
            return {
                left: `${(left + right) / 2}px`,
                top: `${bottom}px`,
            };
    }
};

export const Tooltip = ({ children, hidden = false, placement, floating = false, triggerBounds, ...props }) => (
    <div
        className={createClassName(styles, 'tooltip', { hidden, placement, floating })}
        style={floating ? getPositioningStyle(placement, triggerBounds) : {}}
        {...props}
    >
        {children}
    </div>
);

const TooltipContext = createContext();

export class TooltipContainer extends Component {
    state = {
        tooltip: null,
        activeChild: null,
        event: null,
        placement: null,
    };

    showTooltip = (event, { content, placement = 'bottom', childIndex }) => {
        const triggerBounds = event.target.getBoundingClientRect();
        this.setState({
            tooltip: (
                <Tooltip floating placement={placement} triggerBounds={triggerBounds}>
                    {content}
                </Tooltip>
            ),
            activeChild: childIndex,
            event,
            placement,
            content,
        });
    };

    hideTooltip = () => {
        this.setState({ tooltip: null });
    };

    UNSAFE_componentWillReceiveProps(props) {
        if (this.state.tooltip) {
            const activeChildren = props?.children?.props?.children[this.state.activeChild];
            if (activeChildren && activeChildren.props.content !== this.state.content) {
                this.showTooltip(this.state.event, {
                    content: activeChildren.props.content,
                    placement: this.state.placement,
                    childIndex: this.state.activeChild,
                });
            }
        }
    }

    render({ children }) {
        return (
            <TooltipContext.Provider value={{ ...this.state, showTooltip: this.showTooltip, hideTooltip: this.hideTooltip }}>
                {children}
                <TooltipContext.Consumer>{({ tooltip }) => tooltip}</TooltipContext.Consumer>
            </TooltipContext.Provider>
        );
    }
}

export const TooltipTrigger = ({ children, content, placement = '' }) => (
    <TooltipContext.Consumer>
        {({ showTooltip, hideTooltip }) =>
            toChildArray(children).map((child, index) =>
                cloneElement(child, {
                    onMouseEnter: (event) => showTooltip(event, { content, placement, childIndex: index }),
                    onMouseLeave: (event) => hideTooltip(event),
                    onFocusCapture: (event) => showTooltip(event, { content, placement, childIndex: index }),
                    onBlurCapture: (event) => hideTooltip(event),
                    content,
                }),
            )
        }
    </TooltipContext.Consumer>
);

export const withTooltip = (component) => {
    const TooltipConnection = ({ tooltip, ...props }) => <Tooltip.Trigger content={tooltip}>{component(props)}</Tooltip.Trigger>;
    TooltipConnection.displayName = `withTooltip(${component.displayName})`;

    return TooltipConnection;
};

Tooltip.Container = TooltipContainer;
Tooltip.Trigger = TooltipTrigger;

export default Tooltip;