import * as React from "react"; import * as ReactDOM from "react-dom"; import {ReactElement} from "react"; import {guid} from "tc-shared/crypto/uid"; const cssStyle = require("./Tooltip.scss"); interface GlobalTooltipState { pageX: number; pageY: number; tooltipId: string; } class GlobalTooltip extends React.Component<{}, GlobalTooltipState> { private currentTooltip_: Tooltip; private isUnmount: boolean; constructor(props) { super(props); this.state = { pageX: 0, pageY: 0, tooltipId: "unset" }; } currentTooltip() { return this.currentTooltip_; } updateTooltip(provider?: Tooltip) { this.currentTooltip_ = provider; this.forceUpdate(); this.isUnmount = false; } unmountTooltip(element: Tooltip) { if(element !== this.currentTooltip_) return; this.isUnmount = true; this.forceUpdate(() => { if(this.currentTooltip_ === element) this.currentTooltip_ = undefined; }); } render() { if(!this.currentTooltip_ || this.isUnmount) { return (
{this.currentTooltip_?.props.tooltip()}
); } return (
{this.currentTooltip_.props.tooltip()}
) } } export interface TooltipState { forceShow: boolean; hovered: boolean; pageX: number; pageY: number; } export interface TooltipProperties { tooltip: () => ReactElement | ReactElement[] | string; className?: string; } export class Tooltip extends React.Component { readonly tooltipId = guid(); private refContainer = React.createRef(); private currentContainer: HTMLElement; constructor(props) { super(props); this.state = { forceShow: false, hovered: false, pageX: 0, pageY: 0 }; }; componentWillUnmount(): void { globalTooltipRef.current?.unmountTooltip(this); } render() { return ( this.onMouseEnter(event)} onMouseLeave={() => this.setState({ hovered: false })} onClick={() => this.setState({ hovered: !this.state.hovered })} style={{ cursor: "pointer" }} className={this.props.className} > {this.props.children} ); } componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { if(this.state.forceShow || this.state.hovered) { globalTooltipRef.current?.updateTooltip(this); globalTooltipRef.current?.setState({ pageY: this.state.pageY, pageX: this.state.pageX, tooltipId: this.tooltipId }); } else if(prevState.forceShow || prevState.hovered) { globalTooltipRef.current?.unmountTooltip(this); } } private onMouseEnter(event: React.MouseEvent) { /* check if may only the span has been hovered, should not be the case! */ if(event.target === this.refContainer.current) { return; } this.setState({ hovered: true }); let container = event.target as HTMLElement; while(container.parentElement !== this.refContainer.current) container = container.parentElement; this.currentContainer = container; this.updatePosition(); } updatePosition() { const container = this.currentContainer || this.refContainer.current?.children.item(0) || this.refContainer.current; if(!container) return; const rect = container.getBoundingClientRect(); this.setState({ pageY: rect.top, pageX: rect.left + rect.width / 2 }); } } export const IconTooltip = (props: { children?: React.ReactElement | React.ReactElement[], className?: string, outerClassName?: string }) => ( props.children} className={props.outerClassName}>
{""}
); const globalTooltipRef = React.createRef(); const tooltipContainer = document.createElement("div"); document.body.appendChild(tooltipContainer); ReactDOM.render(, tooltipContainer);