1- import React , { useState , useRef , useLayoutEffect , ReactNode } from 'react' ;
1+ import React , { ReactNode } from 'react' ;
22import { createPortal } from 'react-dom' ;
3+ import { useTooltip } from './hooks/useTooltip' ;
4+ import { Position } from './utils/tooltipPosition' ;
35import cn from 'clsx' ;
46import s from './Tooltip.module.scss' ;
57
6- type Coords = {
7- top : number ;
8- left : number ;
9- } ;
10-
11- type CoordProps = {
12- targetRect : DOMRect ;
13- tooltipRect : DOMRect ;
14- offset : number ;
15- } ;
16-
17- export type Position = 'top' | 'bottom' | 'left' | 'right' ;
18-
19- type PositionMap = Record < Position , ( props : CoordProps ) => Coords > ;
20-
21- const getCenterCoord = ( primary : number , secondary : number ) => ( primary - secondary ) / 2 ;
22-
23- const YLeft = ( primary : DOMRect , secondary : DOMRect ) =>
24- primary . left + window . scrollX + getCenterCoord ( primary . width , secondary . width ) ;
25- const XTop = ( primary : DOMRect , secondary : DOMRect ) =>
26- primary . top + window . scrollY + getCenterCoord ( primary . height , secondary . height ) ;
27-
28- const positionMap : PositionMap = {
29- top : ( { targetRect, tooltipRect, offset } ) => ( {
30- top : targetRect . top + window . scrollY - tooltipRect . height - offset ,
31- left : targetRect . left + window . scrollX + getCenterCoord ( targetRect . width , tooltipRect . width ) ,
32- } ) ,
33-
34- bottom : ( { targetRect, tooltipRect, offset } ) => ( {
35- top : targetRect . bottom + window . scrollY + offset ,
36- left : YLeft ( targetRect , tooltipRect ) ,
37- } ) ,
38- left : ( { targetRect, tooltipRect, offset } ) => ( {
39- top : XTop ( targetRect , tooltipRect ) ,
40- left : targetRect . left + window . scrollX - tooltipRect . width - offset ,
41- } ) ,
42-
43- right : ( { targetRect, tooltipRect, offset } ) => ( {
44- top : XTop ( targetRect , tooltipRect ) ,
45- left : targetRect . left + window . scrollX + targetRect . width + offset ,
46- } ) ,
47- } ;
48-
498type TooltipProps = {
509 children : ReactNode ;
5110 content : ReactNode ;
@@ -54,50 +13,10 @@ type TooltipProps = {
5413} ;
5514
5615export const Tooltip = ( { children, content, duration = 1000 , position = 'bottom' } : TooltipProps ) => {
57- const [ visible , setVisible ] = useState ( false ) ;
58- const [ mounted , setMounted ] = useState ( false ) ;
59- const [ coords , setCoords ] = useState ( { top : 0 , left : 0 } ) ;
60- const tooltipRef = useRef < HTMLDivElement > ( null ) ;
61- const targetRef = useRef < HTMLDivElement > ( null ) ;
62- const timerRef = useRef ( null ) ;
63- const mountTimerRef = useRef ( null ) ;
64-
65- const mountTimer = 50 ;
66-
67- const clearTimeouts = ( ) => {
68- timerRef . current && clearTimeout ( timerRef . current ) ;
69- mountTimerRef . current && clearTimeout ( mountTimerRef . current ) ;
70- } ;
71-
72- const handleMouseEnter = ( ) => {
73- clearTimeouts ( ) ;
74- setMounted ( true ) ;
75- mountTimerRef . current = setTimeout ( ( ) => setVisible ( true ) , mountTimer ) ;
76- } ;
77-
78- const handleMouseLeave = ( ) => {
79- setVisible ( false ) ;
80- timerRef . current = setTimeout ( ( ) => setMounted ( false ) , duration + mountTimer ) ;
81- } ;
82-
83- useLayoutEffect ( ( ) => {
84- const target = targetRef . current ;
85- const tooltip = tooltipRef . current ;
86-
87- if ( ! target || ! tooltip ) return ;
88-
89- if ( mounted ) {
90- tooltipRef . current ?. style . setProperty ( '--tooltip-animation-ms' , `${ duration + mountTimer } ms` ) ;
91- const targetRect = target . getBoundingClientRect ( ) ;
92- const tooltipRect = tooltip . getBoundingClientRect ( ) ;
93- const calcPosition = positionMap [ position ] ?? positionMap [ 'bottom' ] ;
94- setCoords ( calcPosition ( { targetRect, tooltipRect, offset : 5 } ) ) ;
95- }
96-
97- return ( ) => {
98- clearTimeouts ( ) ;
99- } ;
100- } , [ mounted ] ) ;
16+ const { visible, mounted, coords, tooltipRef, targetRef, handleMouseEnter, handleMouseLeave } = useTooltip (
17+ position ,
18+ duration
19+ ) ;
10120
10221 return (
10322 < >
0 commit comments