import * as classNames from "classnames"; import * as _ from "lodash"; import * as React from "react"; import { CSSTransition } from "react-transition-group"; import { setTapRecognizers } from "../../utilities/interactions"; export interface IToastProps { mount: boolean; title: string; subtitle: string; /** * A selector to find the element in which the pointer * will point to */ pointTo?: string; arrowPosition?: ToastArrowPosition; } export enum ToastArrowPosition { Left = "Left", Center = "Center", Right = "Right", } interface IToastState { /** * We keep track of the mount in state * such that we can delay updating */ mount: boolean; } const defaultToast: IToastState = { mount: null, } const UPDATE_MOUNT_THRESHOLD = 300; export class Toast extends React.Component { public state: IToastState = defaultToast; public ref: React.RefObject; public pointerRef: React.RefObject; private timer: any; constructor(props) { super(props) this.ref = React.createRef(); this.pointerRef = React.createRef(); } public componentDidMount() { if (this.props.mount) { this.setState({ mount: true, }) } if (_.isString(this.props.pointTo)) { this.withPointerNode(pointerNode => { const pointToNode = document.querySelector(this.props.pointTo); if (pointToNode) { // @ts-ignore const middleX = pointToNode.offsetLeft + (pointToNode.offsetWidth / 2) const translateX = middleX - (pointerNode.offsetLeft + pointerNode.parentElement.offsetLeft + (pointerNode.offsetWidth / 2)) pointerNode.style.transform = `translateX(${translateX}px)` } }); } this.withNode(node => { setTapRecognizers(node, e => { this.setState({ mount: false, }); }); }); } public shouldComponentUpdate(nextProps: IToastProps, nextState: IToastState) { if (this.props.title !== nextProps.title) return true; if (this.props.subtitle !== nextProps.subtitle) return true; if (this.props.mount !== nextProps.mount) return true; if (this.props.arrowPosition !== nextProps.arrowPosition) return true; if (this.state.mount !== nextState.mount) return true; return false; } public componentDidUpdate(prevProps: IToastProps) { if (prevProps.mount !== this.props.mount) { this.handleChangeMountProps(this.props.mount); } } public render() { return (
{(_.isString(this.props.pointTo) || !_.isNil(this.props.arrowPosition)) && (
)}
{this.props.title}
{this.props.subtitle}
); } private handleChangeMountProps(mount: boolean) { if (!_.isNil(this.timer)) { clearTimeout(this.timer); this.timer = null; } if (mount) { this.setState({ mount, }); } else { this.timer = setTimeout(() => { this.setState({ mount, }, () => { this.timer = null }); }, UPDATE_MOUNT_THRESHOLD); } } // @ts-ignore private withNode(found: (node: HTMLDivElement) => T, notFound?: () => T): T { const node = this.ref.current if (node) { return found(node) } else if (notFound) { return notFound() } return null } private withPointerNode(found: (node: HTMLDivElement) => T, notFound?: () => T): T { const node = this.pointerRef.current if (node) { return found(node) } else if (notFound) { return notFound() } return null } }