import React, { useRef, useImperativeHandle, useState, useEffect, useContext } from 'react'; import { Arrow, PopupContainer } from './style'; import { useSpring } from 'react-spring'; import { getSiblingLocation } from './helper'; import { IPlacement } from './types'; import { useClickOutSide } from '../../utils/hooks'; function mapTransformOrigin(placement: IPlacement) { switch (placement) { case 'topLeft': return '16px 100%'; case 'top': return 'calc(50% - 4px) 100%'; case 'topRight': return 'calc(100% - 16px) 100%'; case 'rightTop': return '-4px 12px'; case 'right': return '-4px calc(50% - 4px)'; case 'rightBottom': return '-4px calc(100% - 16px)'; case 'bottomLeft': return '16px 0'; case 'bottom': return 'calc(50% - 4px) 0'; case 'bottomRight': return 'calc(100% - 16px) 0'; case 'leftTop': return '100% 16px'; case 'left': return '100% calc(50% - 4px)'; case 'leftBottom': return '100% calc(100% - 16px)'; } } export function setPopupLocation(placement: IPlacement, target: DOMRect, popup: HTMLElement): any { switch (placement) { case 'topLeft': return { left: target.left, top: target.top - popup.offsetHeight - 10, }; case 'top': return { left: target.left + target.width / 2 - popup.offsetWidth / 2, top: target.top - popup.offsetHeight - 10, }; case 'topRight': return { right: target.right, top: target.top - popup.offsetHeight - 10, }; case 'rightTop': return { left: target.left + target.width + 10, top: target.top, }; case 'right': return { left: target.left + target.width + 10, top: target.top + target.height / 2 - popup.offsetHeight / 2, }; case 'rightBottom': return { left: target.left + target.width + 10, bottom: target.bottom, }; case 'bottomLeft': return { left: target.left, bottom: target.bottom - popup.offsetHeight - 10, }; case 'bottom': return { left: target.left + target.width / 2 - popup.offsetWidth / 2, bottom: target.bottom - popup.offsetHeight - 10, }; case 'bottomRight': return { right: target.right, bottom: target.bottom - popup.offsetHeight - 10, }; case 'leftTop': return { left: target.left - popup.offsetWidth - 10, top: target.top, }; case 'left': return { left: target.left - popup.offsetWidth - 10, top: target.top + target.height / 2 - popup.offsetHeight / 2, }; case 'leftBottom': return { left: target.left - popup.offsetWidth - 10, bottom: target.bottom, }; } } export type IProps = { trigger: 'click' | 'hover'; content: React.ReactNode; themeColor: 'white' | 'black'; visible?: boolean; onVisibleChange?: (visible: boolean) => void; siblingRef: any; placement: IPlacement; className?: string; style?: React.CSSProperties; }; const Popup = function(props: IProps, ref: any) { const { themeColor, content, trigger, siblingRef, onVisibleChange, placement, visible, ...rest } = props; const controlledVisible = 'visible' in props; const wrapRef: any = useRef(); const [show, setShow] = useState(controlledVisible ? props.visible : false); const style = useSpring({ opacity: show ? 1 : 0, transform: `scale(${show ? 1 : 0})`, transformOrigin: mapTransformOrigin(placement), config: { mass: 5, friction: 180, tension: 3000, clamp: true }, }); useEffect(() => { if (controlledVisible) { var id = setTimeout(() => { const loc = getSiblingLocation(siblingRef); //setPos({ // left: loc.left, // top: loc.top - wrapRef.current.offsetHeight - 10, //}); setPos(setPopupLocation(placement, loc as DOMRect, wrapRef.current as HTMLElement)); setShow(props.visible); }, 100); } return () => clearTimeout(id); }, [controlledVisible, props.visible, siblingRef.current]); const [cursorIn, setCursor] = useState(false); useClickOutSide( siblingRef, e => { if (trigger === 'click' && !wrapRef.current.contains(e)) { if (!controlledVisible) { setShow(false); } else { onVisibleChange && show && onVisibleChange(false); } } }, true ); const [pos, setPos] = useState({ left: 0, top: 0 }); useImperativeHandle(ref, () => ({ dom: wrapRef.current, setVisible: setShow, visible: show, cursorIn, setPos, })); return ( { if (trigger === 'hover') { setCursor(true); } }} onMouseLeave={() => { if (trigger === 'hover') { setCursor(false); if (!controlledVisible) { setShow(false); } else { onVisibleChange && onVisibleChange(false); } } }} data-contrast={themeColor === 'black'} ref={wrapRef} style={{ ...pos, ...style }} {...rest} > {content} ); }; export default React.forwardRef(Popup);