import React, { ReactNode, useEffect, useRef, useState } from 'react'; import './Tooltip.scss'; import { createPortal } from 'react-dom'; interface Styles { [key: string]: { top?: number; left?: number; right?: number; }; } export interface TooltipProps { title: string; children: React.ReactNode; placement?: | 'bottom' | 'left' | 'right' | 'top' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end'; disabled?: boolean; } const Tooltip: React.FC = ({ title, children, placement = 'bottom', disabled = false, }) => { const [isVisible, setIsVisible] = useState(false); const titleRef = useRef(null); const [tooltipContainerPosition, settooltipContainerPosition] = useState({ posX: 0, fromRight: 0, posY: 0, width: 0, fromBottom: 0, }); const [titleDimensions, setTitleDimensions] = useState({ height: 0, width: 0, }); const [childrenContainerHeight, setChildrenContainerHeight] = useState(0); const styles: Styles = { left: { top: tooltipContainerPosition.posY, left: tooltipContainerPosition.posX - titleDimensions.width - 5, }, right: { top: tooltipContainerPosition.posY, left: tooltipContainerPosition.fromRight + 5, }, top: { top: tooltipContainerPosition.posY - titleDimensions.height - 5, left: tooltipContainerPosition.posX - titleDimensions.width / 2 + tooltipContainerPosition.width / 2, }, bottom: { top: tooltipContainerPosition.posY + childrenContainerHeight + 5, left: tooltipContainerPosition.posX - titleDimensions.width / 2 + tooltipContainerPosition.width / 2, }, 'top-start': { top: tooltipContainerPosition.posY - titleDimensions.height - 5, left: tooltipContainerPosition.posX, }, 'top-end': { top: tooltipContainerPosition.posY - titleDimensions.height - 5, left: tooltipContainerPosition.fromRight - titleDimensions.width, }, 'bottom-start': { top: tooltipContainerPosition.posY + childrenContainerHeight + 5, left: tooltipContainerPosition.posX, }, 'bottom-end': { top: tooltipContainerPosition.posY + childrenContainerHeight + 5, left: tooltipContainerPosition.fromRight - titleDimensions.width, }, }; const tooltipContainerRef = useRef(null); const handleScroll = () => { if (tooltipContainerRef.current) { setIsVisible(false); } }; const nullCheckForTitle = (val: string) => { return val === undefined || val == null || val.length <= 0 ? true : false; }; const handleClickAnywhere = () => { setIsVisible(false); }; useEffect(() => { window.addEventListener('scroll', handleScroll); window.addEventListener('click', handleClickAnywhere); return () => { window.removeEventListener('scroll', handleScroll); window.removeEventListener('click', handleClickAnywhere); }; }, []); const calculateDim = () => { if (tooltipContainerRef?.current) { const computedStyle = window.getComputedStyle( tooltipContainerRef.current ); if ( computedStyle.display === 'none' || computedStyle.visibility === 'hidden' || computedStyle.opacity === '0' ) { setIsVisible(false); return; } } if (titleRef?.current) { setTitleDimensions({ height: titleRef.current.offsetHeight, width: titleRef.current.offsetWidth, }); } if (tooltipContainerRef?.current && titleRef?.current) { const rect = tooltipContainerRef.current.getBoundingClientRect(); setChildrenContainerHeight(rect.height); let leftSpaceAvailable = rect.left + window.scrollX + tooltipContainerRef.current.offsetWidth / 2 - titleRef.current.offsetWidth / 2; let rightSpaceAvailable = window.innerWidth - rect.right + tooltipContainerRef.current.offsetWidth / 2 - titleRef.current.offsetWidth / 2; let posX = rect.left + window.scrollX; if (leftSpaceAvailable < 0) { posX = posX - leftSpaceAvailable; } if (rightSpaceAvailable < 0) { posX = posX + rightSpaceAvailable; } settooltipContainerPosition({ posX: posX, fromRight: rect.right, posY: rect.top, width: tooltipContainerRef.current.offsetWidth, fromBottom: window.innerHeight - tooltipContainerRef.current.offsetTop, }); } }; useEffect(() => { calculateDim(); }, [isVisible]); const onMouseEnter = () => { setIsVisible(true); }; const onMouseLeave = () => { setIsVisible(false); }; return (
{children} {isVisible && !disabled && !nullCheckForTitle(title) && createPortal(
{title}
, document.body )}
); }; export default Tooltip;