2020-02-24 19:29:11 +00:00

108 lines
2.8 KiB
TypeScript

import React, { createElement, useCallback, useEffect, useRef, useState } from 'react';
import { render } from 'react-dom';
import styled from 'styled-components';
const Container = styled.div`
position: fixed;
z-index: 1000;
top: 0;
left: 0;
height: 100%;
width: 100%;
pointer-events: none;
`;
export const StyledTooltip = styled.div<{
visible: boolean;
x: string;
y: string;
}>`
position: fixed;
pointer-events: none;
z-index: 1001;
font-size: var(--font_sub_size);
color: var(--tooltip_foreground);
background-color: var(--tooltip_background);
border-radius: 6px;
padding: 5px 10px;
opacity: 0;
transition: opacity 0.2s ease 0.2s;
transform-origin: center;
${({ x, y }) => `transform: translate(calc(${x}), calc(${y}));`}
${({ visible }) => visible && `opacity: 1;`}
`;
const TOOLTIP_CONTAINER_ID = 'tooltip-container';
type Position = 'top' | 'bottom' | 'left' | 'right';
export const TooltipContainer = () => {
return <Container id={TOOLTIP_CONTAINER_ID}></Container>;
};
function calcX(triggerRect: ClientRect, position?: Position) {
if ('left' === position) {
return `${triggerRect.left - 10}px - 100%`;
} else if ('right' === position) {
return `${triggerRect.right + 10}px`;
}
return `${triggerRect.left + triggerRect.width / 2}px - 50%`;
}
function calcY(triggerRect: ClientRect, position?: string) {
if ('top' === position) {
return `${triggerRect.top - 10}px - 100%`;
} else if (!position || 'bottom' === position) {
return `${triggerRect.bottom + 10}px`;
}
return `${triggerRect.top + triggerRect.height / 2}px - 50%`;
}
export const Tooltip = (props: { position?: Position; children: any }) => {
const ref = useRef<HTMLDivElement>(null);
const [isTooltipVisible, setTooltipVisible] = useState(false);
const renderTooltip = useCallback(
(visible: boolean, triggerRect: ClientRect) => {
const tooltip = createElement(
StyledTooltip,
{
visible,
x: calcX(triggerRect, props.position),
y: calcY(triggerRect, props.position)
},
props.children
);
render(tooltip, document.getElementById(TOOLTIP_CONTAINER_ID));
},
[props.position, props.children]
);
useEffect(() => {
if (ref.current) {
const trigger = (ref.current as HTMLElement).parentElement;
if (trigger) {
if (isTooltipVisible) {
renderTooltip(true, trigger.getBoundingClientRect());
}
trigger.onmouseenter = _ => {
renderTooltip(true, trigger.getBoundingClientRect());
setTooltipVisible(true);
};
trigger.onmouseleave = _ => {
renderTooltip(false, trigger.getBoundingClientRect());
setTooltipVisible(false);
};
}
}
}, [isTooltipVisible, renderTooltip]);
return <div ref={ref}></div>;
};