dekk-app/react-mops

View on GitHub
packages/react-mops/src/elements.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import cx from "classnames";
import React from "react";
import {resizeClasses, rotationClasses} from "./cursors";
import styles from "./elements.css";
import {Mops} from "./types";

export const {Provider: PropProvider, Consumer: PropConsumer} = React.createContext<
    Mops.ProviderProps
>({
    getCursorSlice: n => n,
    handleRotationDown: () => undefined,
    isDraggable: false,
    isResizable: false,
    isRotatable: false,
    metaKey: false
});

const HandleLogic: React.RefForwardingComponent<
    HTMLAnchorElement,
    Partial<Mops.HandleProps> & {cursorSlice: string}
> = React.forwardRef(({children, cursorSlice, ...props}, ref: React.Ref<HTMLAnchorElement>) => {
    React.useEffect(() => {
        if (cursorSlice) {
            document.body.classList.remove(...resizeClasses, ...rotationClasses);
            document.body.classList.add(cursorSlice, styles.forceHandle);
        } else {
            document.body.classList.remove(
                ...resizeClasses,
                ...rotationClasses,
                styles.forceHandle
            );
        }
    }, [cursorSlice]);
    return (
        <a {...props} href="#" ref={ref}>
            {children}
        </a>
    );
});

const HandleBase: React.RefForwardingComponent<
    HTMLAnchorElement,
    Mops.HandleProps
> = React.forwardRef(
    (
        {children, className, onClick, isMouseDown, onMouseDown, variation, ...props},
        ref: React.Ref<HTMLAnchorElement>
    ) => {
        const [isDown, setDown] = React.useState(false);
        React.useEffect(() => {
            const handleMouseUp = () => {
                setDown(false);
            };
            window.addEventListener("mouseup", handleMouseUp);
            window.addEventListener("mouseleave", handleMouseUp);
            window.addEventListener("blur", handleMouseUp);
            return () => {
                window.removeEventListener("mouseup", handleMouseUp);
                window.removeEventListener("mouseleave", handleMouseUp);
                window.removeEventListener("blur", handleMouseUp);
            };
        }, [setDown]);
        const handleClick = React.useCallback(
            (e: React.MouseEvent<HTMLAnchorElement>) => {
                e.preventDefault();
                if (typeof onClick === "function") {
                    onClick(e);
                }
            },
            [onClick]
        );
        return (
            <PropConsumer>
                {({handleRotationDown, isResizable, isRotatable, getCursorSlice, metaKey}) => {
                    const cursorSlice = getCursorSlice(Mops.HandleVariations[variation]);
                    const rotationClassName = rotationClasses[cursorSlice];
                    const resizeClassName = resizeClasses[cursorSlice % resizeClasses.length];

                    return (
                        <HandleLogic
                            {...props}
                            className={cx(className, styles.handleBase, {
                                [resizeClassName]: !metaKey && isResizable,
                                [rotationClassName]: metaKey && isRotatable
                            })}
                            ref={ref}
                            onClick={handleClick}
                            cursorSlice={
                                isDown ? (metaKey ? rotationClassName : resizeClassName) : undefined
                            }
                            onMouseDown={e => {
                                setDown(true);
                                if (metaKey) {
                                    handleRotationDown(e);
                                } else {
                                    onMouseDown(e);
                                }
                            }}>
                            {children}
                        </HandleLogic>
                    );
                }}
            </PropConsumer>
        );
    }
);

const HandleMarker: React.FunctionComponent<React.HTMLAttributes<HTMLDivElement>> = ({
    children,
    className,
    ...props
}) => (
    <span {...props} className={cx(className, styles.handleMarker)}>
        {children}
    </span>
);

export const Handle: React.RefForwardingComponent<
    HTMLAnchorElement,
    Mops.HandleProps & {variation: Mops.HandleVariation; full?: boolean}
> = React.forwardRef(
    ({className, variation, isMouseDown, style, marker: Marker, full, ...props}, ref) => {
        return (
            <HandleBase
                {...props}
                variation={variation}
                isMouseDown={isMouseDown}
                className={cx(className, styles[variation], {
                    [styles.full]: full
                })}
                ref={ref}>
                <Marker />
            </HandleBase>
        );
    }
);

Handle.defaultProps = {
    marker: HandleMarker
};

export const Handles: React.FunctionComponent<
    React.HTMLAttributes<HTMLDivElement> & {draw?: boolean}
> = ({children, className, draw, ...props}) => (
    <div
        {...props}
        className={cx(className, styles.handles, {
            [styles.drawOutline]: draw
        })}>
        {children}
    </div>
);

export const Wrapper: React.RefForwardingComponent<
    HTMLElement,
    Mops.WrapperProps
> = React.forwardRef(
    ({children, className, isDown, as: As, ...props}, ref: React.Ref<HTMLElement>) => (
        <As
            {...props}
            ref={ref as React.Ref<HTMLElement>}
            className={cx(className, styles.wrapper)}>
            {children}
        </As>
    )
);

export const Content: React.RefForwardingComponent<
    HTMLDivElement,
    Mops.ContentProps
> = React.forwardRef(
    ({children, className, onMouseDown, ...props}, ref: React.Ref<HTMLDivElement>) => (
        <div
            {...props}
            onMouseDown={onMouseDown}
            ref={ref as React.Ref<HTMLDivElement>}
            className={cx(className, styles.content, {
                [styles.move]: typeof onMouseDown === "function"
            })}>
            {children}
        </div>
    )
);

export const BoundingBox: React.RefForwardingComponent<
    HTMLDivElement,
    React.HTMLAttributes<HTMLDivElement> & {draw?: boolean}
> = React.forwardRef(({children, className, draw, ...props}, ref: React.Ref<HTMLDivElement>) => (
    <div
        {...props}
        ref={ref as React.Ref<HTMLDivElement>}
        className={cx(className, styles.boundingBox, {
            [styles.drawOutline]: draw
        })}>
        {children}
    </div>
));