1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/renderer/components/tooltip/tooltip.tsx
Roman 3a115ae485 refactoring & fixes
Signed-off-by: Roman <ixrock@gmail.com>
2020-07-29 14:57:27 +03:00

177 lines
5.0 KiB
TypeScript

import './tooltip.scss'
import React from "react"
import { createPortal } from "react-dom"
import { observer } from "mobx-react";
import { autobind, cssNames, IClassName } from "../../utils";
import { observable } from "mobx";
export enum TooltipPosition {
TOP = "top",
BOTTOM = "bottom",
LEFT = "left",
RIGHT = "right",
}
export interface TooltipProps {
targetId: string; // html-id of target element to bind for
visible?: boolean; // initial visibility
offset?: number; // offset from target element in pixels (all sides)
usePortal?: boolean; // renders element outside of parent (in body), disable for "easy-styling", default: true
position?: TooltipPosition;
className?: IClassName;
formatters?: TooltipContentFormatters;
style?: React.CSSProperties;
children?: React.ReactNode;
}
export interface TooltipContentFormatters {
narrow?: boolean; // max-width
warning?: boolean; // color
small?: boolean; // font-size
nowrap?: boolean; // white-space
tableView?: boolean;
}
const defaultProps: Partial<TooltipProps> = {
usePortal: true,
offset: 10,
}
@observer
export class Tooltip extends React.Component<TooltipProps> {
static defaultProps = defaultProps as object;
@observable.ref elem: HTMLElement;
@observable activePosition: TooltipPosition;
@observable isVisible = !!this.props.visible;
get targetElem(): HTMLElement {
return document.getElementById(this.props.targetId)
}
componentDidMount() {
this.targetElem.addEventListener("mouseenter", this.onEnterTarget)
this.targetElem.addEventListener("mouseleave", this.onLeaveTarget)
}
componentWillUnmount() {
this.targetElem.removeEventListener("mouseenter", this.onEnterTarget)
this.targetElem.removeEventListener("mouseleave", this.onLeaveTarget)
}
@autobind()
protected onEnterTarget(evt: MouseEvent) {
this.isVisible = true;
this.refreshPosition();
}
@autobind()
protected onLeaveTarget(evt: MouseEvent) {
this.isVisible = false;
}
@autobind()
refreshPosition() {
const { position } = this.props;
const { elem, targetElem } = this;
let allPositions: TooltipPosition[] = [
TooltipPosition.RIGHT,
TooltipPosition.BOTTOM,
TooltipPosition.LEFT,
TooltipPosition.RIGHT,
];
if (allPositions.includes(position)) {
allPositions = [
position, // put first as priority side for positioning
...allPositions.filter(pos => pos !== position),
];
}
// reset position first and get all possible client-rect area for tooltip element
this.setPosition({ left: 0, top: 0 });
const selfBounds = elem.getBoundingClientRect();
const targetBounds = targetElem.getBoundingClientRect();
const { innerWidth: viewportWidth, innerHeight: viewportHeight } = window;
// find proper position
this.activePosition = null;
for (const pos of allPositions) {
const { left, top, right, bottom } = this.getPosition(pos, selfBounds, targetBounds)
const fitsToWindow = left >= 0 && top >= 0 && right <= viewportWidth && bottom <= viewportHeight;
if (fitsToWindow) {
this.activePosition = pos;
this.setPosition({ top, left });
break;
}
}
if (!this.activePosition) {
const { left, top } = this.getPosition(allPositions[0], selfBounds, targetBounds)
this.activePosition = allPositions[0];
this.setPosition({ left, top });
}
}
protected setPosition(pos: { left: number, top: number }) {
const elemStyle = this.elem.style;
elemStyle.left = pos.left + "px"
elemStyle.top = pos.top + "px"
}
protected getPosition(position: TooltipPosition, selfBounds: DOMRect, targetBounds: DOMRect) {
let left: number
let top: number
const offset = this.props.offset;
const horizontalCenter = targetBounds.left + (targetBounds.width - selfBounds.width) / 2;
const verticalCenter = targetBounds.top + (targetBounds.height - selfBounds.height) / 2;
switch (position) {
case "top":
left = horizontalCenter;
top = targetBounds.top - selfBounds.height - offset;
break;
case "bottom":
left = horizontalCenter;
top = targetBounds.bottom + offset;
break;
case "left":
top = verticalCenter;
left = targetBounds.left - selfBounds.width - offset;
break;
case "right":
top = verticalCenter;
left = targetBounds.right + offset;
break;
}
return {
left: left,
top: top,
right: left + selfBounds.width,
bottom: top + selfBounds.height,
};
}
@autobind()
bindRef(elem: HTMLElement) {
this.elem = elem;
}
render() {
const { style, formatters, usePortal, children } = this.props;
const className = cssNames("Tooltip", this.props.className, formatters, this.activePosition, {
hidden: !this.isVisible,
formatter: !!formatters,
});
const tooltip = (
<div className={className} style={style} ref={this.bindRef}>
{children}
</div>
)
if (usePortal) {
return createPortal(tooltip, document.body,);
}
return tooltip;
}
}