1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/dashboard/client/components/tooltip/tooltip.tsx
Jari Kolehmainen db4dca3005 lens app source code
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
2020-03-15 09:46:21 +02:00

152 lines
3.8 KiB
TypeScript

import './tooltip.scss'
import React from "react"
import { observer } from "mobx-react";
import { observable } from "mobx";
import { createPortal } from "react-dom"
import { autobind, cssNames } from "../../utils";
import { Animate } from "../animate";
export interface TooltipProps {
htmlFor: string;
className?: string;
position?: Position;
useAnimation?: boolean;
following?: boolean; // tooltip is following mouse position
style?: React.CSSProperties;
children?: React.ReactNode;
}
interface Position {
left?: boolean;
right?: boolean;
top?: boolean;
bottom?: boolean;
center?: boolean;
}
const defaultProps: Partial<TooltipProps> = {
useAnimation: true,
position: {
center: true,
bottom: true,
}
};
@observer
export class Tooltip extends React.Component<TooltipProps> {
static defaultProps = defaultProps as object;
public anchor: HTMLElement;
public elem: HTMLElement;
@observable isVisible = false;
componentDidMount() {
const { htmlFor } = this.props;
this.anchor = htmlFor ? document.getElementById(htmlFor) : this.elem.parentElement;
if (this.anchor) {
if (window.getComputedStyle(this.anchor).position === "static") {
this.anchor.style.position = "relative"
}
this.anchor.addEventListener("mouseenter", this.onMouseEnter);
this.anchor.addEventListener("mouseleave", this.onMouseLeave);
this.anchor.addEventListener("mousemove", this.onMouseMove);
}
}
componentWillUnmount() {
if (this.anchor) {
this.anchor.removeEventListener("mouseenter", this.onMouseEnter);
this.anchor.removeEventListener("mouseleave", this.onMouseLeave);
this.anchor.removeEventListener("mousemove", this.onMouseMove);
}
}
@autobind()
onMouseEnter(evt: MouseEvent) {
this.isVisible = true;
this.onMouseMove(evt);
}
@autobind()
onMouseLeave(evt: MouseEvent) {
this.isVisible = false;
}
@autobind()
onMouseMove(evt: MouseEvent) {
if (!this.props.following) {
return;
}
const offsetX = -10;
const offsetY = 10;
const { clientX, clientY } = evt;
const { innerWidth, innerHeight } = window;
const initialPos: Partial<CSSStyleDeclaration> = {
top: "auto",
left: "auto",
right: (innerWidth - clientX + offsetX) + "px",
bottom: (innerHeight - clientY + offsetY) + "px",
}
Object.assign(this.elem.style, initialPos);
// correct position if not fits to viewport
const { left, top } = this.elem.getBoundingClientRect();
if (left < 0) {
this.elem.style.left = clientX + offsetX + "px";
this.elem.style.right = "auto"
}
if (top < 0) {
this.elem.style.top = clientY + offsetY + "px";
this.elem.style.bottom = "auto"
}
}
@autobind()
bindRef(elem: HTMLElement) {
this.elem = elem;
}
render() {
const { isVisible } = this;
const { useAnimation, position, following, style, children } = this.props;
let { className } = this.props;
className = cssNames('Tooltip', position, { following }, className);
const tooltip = (
<Animate enter={isVisible} enabled={useAnimation}>
<div className={className} ref={this.bindRef} style={style}>
{children}
</div>
</Animate>
);
if (following) {
return createPortal(tooltip, document.body);
}
return tooltip;
}
}
interface TooltipContentProps {
className?: string;
narrow?: boolean; // max-width
warning?: boolean; // color
small?: boolean; // font-size
nowrap?: boolean; // white-space
tableView?: boolean;
}
export class TooltipContent extends React.Component<TooltipContentProps> {
render() {
const { className, children, ...modifiers } = this.props;
return (
<div className={cssNames("TooltipContent", className, modifiers)}>
{children}
</div>
)
}
}