@@ -6,14 +6,15 @@ import {
66 flip ,
77 FloatingPortal ,
88 offset ,
9+ safePolygon ,
910 shift ,
1011 useFloating ,
1112 useHover ,
1213 useInteractions ,
1314} from '@floating-ui/react'
1415import { AnimatePresence , motion } from 'framer-motion'
1516import { transparentize } from 'polished'
16- import React , { ReactNode , useRef , useState } from 'react'
17+ import React , { CSSProperties , ReactNode , useRef , useState } from 'react'
1718import { SolvedTheme , solvedThemes } from '../styles'
1819import { Card , CardProps } from './Card'
1920
@@ -27,7 +28,6 @@ const TooltipContainer = styled(motion(Card))`
2728 border: ${ ( { theme } ) => theme . styles . border ( ) } ;
2829 box-shadow: ${ ( { theme } ) => theme . styles . shadow ( undefined , 16 ) } ;
2930 z-index: 30000;
30- pointer-events: none;
3131 backdrop-filter: blur(4px);
3232 font-size: initial;
3333 font-weight: initial;
@@ -53,10 +53,21 @@ const renderSide = {
5353 left : 'right' ,
5454} as const
5555
56+ type TooltipPlacementBasic = 'top' | 'right' | 'bottom' | 'left'
57+ type TooltipPlacementRelative = 'start' | 'end'
58+
59+ export type TooltipPlacement =
60+ | `${TooltipPlacementBasic } -${TooltipPlacementRelative } `
61+ | TooltipPlacementBasic
62+
5663export type TooltipProps = {
5764 title ?: ReactNode
5865 theme ?: SolvedTheme
5966 children ?: ReactNode
67+ arrow ?: boolean
68+ keepOpen ?: boolean
69+ place ?: TooltipPlacement
70+ interactive ?: boolean
6071} & (
6172 | {
6273 noDefaultStyles : false
@@ -66,15 +77,57 @@ export type TooltipProps = {
6677 } )
6778)
6879
80+ const resolveArrowStyles = (
81+ arrowX : number | undefined | null ,
82+ arrowY : number | undefined | null ,
83+ arrowPosition : 'top' | 'bottom' | 'left' | 'right' ,
84+ padding = 16
85+ ) : CSSProperties => {
86+ if ( arrowPosition === 'bottom' ) {
87+ return {
88+ left : arrowX ?? undefined ,
89+ bottom : - padding ,
90+ transform : `scaleY(-1)` ,
91+ }
92+ }
93+ if ( arrowPosition === 'top' ) {
94+ return {
95+ left : arrowX ?? undefined ,
96+ top : - padding ,
97+ }
98+ }
99+ if ( arrowPosition === 'left' ) {
100+ return {
101+ top : arrowY ?? undefined ,
102+ left : - 16 ,
103+ transform : `rotate(-90deg)` ,
104+ }
105+ }
106+ if ( arrowPosition === 'right' ) {
107+ return {
108+ top : arrowY ?? undefined ,
109+ right : - 16 ,
110+ transform : `rotate(90deg)` ,
111+ }
112+ }
113+ return { }
114+ }
115+
69116export const Tooltip : React . FC < TooltipProps > = ( props ) => {
70117 const {
71118 title,
72119 theme,
73120 noDefaultStyles : noBackground ,
74121 children,
122+ arrow : drawArrow = true ,
123+ keepOpen = false ,
124+ place,
125+ interactive = false ,
75126 ...cardProps
76127 } = props
77128 const [ isOpen , setIsOpen ] = useState ( false )
129+ const renderTooltip = keepOpen || isOpen
130+
78131 const arrowRef = useRef ( null )
79132
80133 const {
@@ -86,13 +139,14 @@ export const Tooltip: React.FC<TooltipProps> = (props) => {
86139 placement,
87140 middlewareData : { arrow : { x : arrowX , y : arrowY } = { } } ,
88141 } = useFloating ( {
142+ placement : place ,
89143 strategy : 'fixed' ,
90144 open : isOpen ,
91145 onOpenChange : setIsOpen ,
92146 middleware : [
93- offset ( 8 ) ,
147+ offset ( 16 ) ,
148+ shift ( { padding : 16 } ) ,
94149 flip ( ) ,
95- shift ( { padding : 8 } ) ,
96150 arrow ( { element : arrowRef } ) ,
97151 ] ,
98152 whileElementsMounted : ( reference , floating , update ) =>
@@ -104,6 +158,10 @@ export const Tooltip: React.FC<TooltipProps> = (props) => {
104158 const { getReferenceProps, getFloatingProps } = useInteractions ( [
105159 useHover ( context , {
106160 delay : 200 ,
161+ move : true ,
162+ handleClose : safePolygon ( {
163+ buffer : 1 ,
164+ } ) ,
107165 } ) ,
108166 ] )
109167
@@ -120,7 +178,7 @@ export const Tooltip: React.FC<TooltipProps> = (props) => {
120178 < FloatingPortal >
121179 < ThemeProvider theme = { theme || solvedThemes . dark } >
122180 < AnimatePresence >
123- { isOpen && (
181+ { renderTooltip && (
124182 < React . Fragment >
125183 < RenderComponent
126184 ref = { refs . setFloating }
@@ -129,31 +187,22 @@ export const Tooltip: React.FC<TooltipProps> = (props) => {
129187 position : strategy ,
130188 top : y || 0 ,
131189 left : x || 0 ,
190+ pointerEvents : interactive ? 'auto' : 'none' ,
132191 } ,
133192 } ) }
134193 { ...cardProps }
135194 transition = { { duration : 0.2 , ease : 'easeInOut' } }
136- initial = { { opacity : 0 , y : 0 , scale : 0.9 } }
137- animate = { { opacity : 1 , y : 8 , scale : 1 } }
138- exit = { { opacity : 0 , y : 0 , scale : 0.9 } }
195+ initial = { { opacity : 0 , scale : 0.9 } }
196+ animate = { { opacity : 1 , scale : 1 } }
197+ exit = { { opacity : 0 , scale : 0.9 } }
139198 >
140199 { title }
141- < Arrow
142- ref = { arrowRef }
143- style = {
144- arrowPosition === 'bottom'
145- ? {
146- left : arrowX ?? undefined ,
147- [ arrowPosition ] : - 16 ,
148- transform : `scaleY(-1)` ,
149- }
150- : {
151- top :
152- arrowY !== null ? ( arrowY || 0 ) - 16 : undefined ,
153- left : arrowX ?? undefined ,
154- }
155- }
156- />
200+ { drawArrow && (
201+ < Arrow
202+ ref = { arrowRef }
203+ style = { resolveArrowStyles ( arrowX , arrowY , arrowPosition ) }
204+ />
205+ ) }
157206 </ RenderComponent >
158207 </ React . Fragment >
159208 ) }
0 commit comments