import React, { Component, RefObject } from 'react'; import ReactDOM from 'react-dom'; import classnames from 'classnames'; import Icons from '../icons'; import styles from './styles.module.scss'; export type InfoPopupProps = { infoIconColor?: string, singleLine?: boolean, horizontalIconOffset?: number, verticalIconOffset?: number, verticalPopupOffset?: number, popupAnchor?: 'left' | 'right' | 'center', popupBackground?: string, popupBorder?: string, popupPadding?: string | number, popupWidth?: string | number, target?: React.ReactNode, children: React.ReactNode, }; export type InfoPopupState = { top: number, left: number, visible: boolean, }; // DEPRECATED: Use the "Tooltip" component instead export default class InfoPopup extends Component { private icon: RefObject; private popup: RefObject; constructor(props) { super(props); this.state = { top: 0, left: 0, visible: false, }; this.icon = React.createRef(); this.popup = React.createRef(); this.onShow = this.onShow.bind(this); this.onHide = this.onHide.bind(this); this.onResize = this.onResize.bind(this); this.onBodyTouch = this.onBodyTouch.bind(this); } componentDidMount() { document.body.addEventListener('touchstart', this.onBodyTouch); window.addEventListener('resize', this.onResize); this.onResize(); /* initially place the popup */ } componentWillUnmount() { document.body.removeEventListener('touchstart', this.onBodyTouch); window.removeEventListener('resize', this.onResize); } onResize() { if (!this.popup.current || !this.icon.current) { return; } const { popupAnchor = 'center', verticalPopupOffset } = this.props; const popupBBox = this.popup.current.getBoundingClientRect(); const iconBBox = this.icon.current.getBoundingClientRect(); const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; let top = iconBBox.bottom + 3 + (verticalPopupOffset || 0); // Craft a "left" value that will ensure that the popup is correctly positioned. let left = iconBBox.left; if (popupAnchor === 'center') { left = left + (iconBBox.width / 2) - (popupBBox.width / 2); } else if (popupAnchor === 'right') { left = left - popupBBox.width + iconBBox.width; } // Attempt to handle the case of the popup going off the left edge of the screen if (left < 20) { left = 20; } // Attempt to handle the case of the popup going off the right edge of the screen if (left + popupBBox.width > windowWidth-20) { left = windowWidth - popupBBox.width - 20; } // Place the popup above the target if it is too low if (top + popupBBox.height > windowHeight) { top = iconBBox.top - popupBBox.height - 3; } // Use raw `left` / `top` values, because this popup will render in a portal this.setState({ top: top, left: left, }); } // When the user touches the body outside of the popup, dismiss any open popups. onBodyTouch(e) { let element = e.target; // Ensure the touch occured outside of the popup. while (element) { if (element.className === styles.infoPopupContainer) { return; } element = element.parentNode; } this.onHide(); } onShow() { this.setState({visible: true}); } onHide() { this.setState({visible: false}); } render() { console.warn('The InfoPopup component is deprecated. Use the Tooltip component instead.'); const { infoIconColor, singleLine, horizontalIconOffset, verticalIconOffset, popupBackground, popupBorder, popupPadding, popupWidth, target, children, } = this.props; const { top, left, visible } = this.state; return this.onShow()} onMouseLeave={e => this.onHide()} onTouchStart={e => this.onShow()} tabIndex={0} onFocus={e => this.onShow()} onBlur={e => this.onHide()} style={{transform: `translate(${horizontalIconOffset || 0}px, ${verticalIconOffset || 0}px)`}} > {target || } {ReactDOM.createPortal(
{children}
, document.querySelector('body') as HTMLBodyElement )}
; } } export const InfoPopupCardWellHighlight: React.FC<{ target: React.ReactNode, children: React.ReactNode }> = p => { const props = Object.assign({}, p); const {target, children} = props; delete props.target; delete props.children; return ( {target}} {...props}> {children} ); }