1- import { MouseEvent , ReactNode , RefObject , useCallback , useEffect , useRef , useState } from 'react'
1+ import { ReactNode , RefObject , useCallback , useEffect , useRef , useState } from 'react'
22
33import { useOnClickOutside , useOnScroll } from '@cowprotocol/common-hooks'
4- import { isMobile } from '@cowprotocol/common-utils'
54import { Command } from '@cowprotocol/types'
65
6+ import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'
77import styled from 'styled-components/macro'
88
9+ import { UI } from '../../enum'
910import Popover , { PopoverProps } from '../Popover'
1011
1112const TOOLTIP_CLOSE_DELAY = 300 // in milliseconds
@@ -45,6 +46,39 @@ export interface HoverTooltipProps extends Omit<PopoverProps, 'content' | 'show'
4546 tooltipCloseDelay ?: number
4647}
4748
49+ const BaseTooltipTrigger = styled . div `
50+ display: inline-flex;
51+ align-items: center;
52+ `
53+
54+ const BaseTooltipPopup = styled ( BaseTooltip . Popup ) < {
55+ bgColor ?: string
56+ color ?: string
57+ borderColor ?: string
58+ } > `
59+ background: ${ ( { bgColor } ) => bgColor || `var(${ UI . COLOR_PAPER_DARKER } )` } ;
60+ color: ${ ( { color } ) => color || `var(${ UI . COLOR_TEXT_PAPER } )` } ;
61+ box-shadow: var(${ UI . BOX_SHADOW } );
62+ border: 1px solid ${ ( { borderColor, bgColor } ) => borderColor || bgColor || `var(${ UI . COLOR_PAPER_DARKEST } )` } ;
63+ border-radius: 12px;
64+ padding: 6px 3px;
65+ font-size: 13px;
66+ backdrop-filter: blur(20px);
67+ transform-origin: var(--transform-origin);
68+
69+ > div div {
70+ font-size: inherit;
71+ }
72+ `
73+
74+ function getTooltipSide ( placement : HoverTooltipProps [ 'placement' ] ) : 'top' | 'bottom' | 'left' | 'right' {
75+ const side = placement ?. split ( '-' ) [ 0 ]
76+
77+ if ( side === 'bottom' || side === 'left' || side === 'right' ) return side
78+
79+ return 'top'
80+ }
81+
4882/**
4983 * Tooltip that appears when hovering over the children
5084 *
@@ -55,10 +89,7 @@ export interface HoverTooltipProps extends Omit<PopoverProps, 'content' | 'show'
5589 * @param props
5690 * @returns
5791 */
58- // TODO: Break down this large function into smaller functions
59- // TODO: Add proper return type annotation
60- // eslint-disable-next-line max-lines-per-function, @typescript-eslint/explicit-function-return-type
61- export function HoverTooltip ( props : HoverTooltipProps ) {
92+ export function HoverTooltip ( props : HoverTooltipProps ) : ReactNode {
6293 const {
6394 content,
6495 children,
@@ -67,78 +98,14 @@ export function HoverTooltip(props: HoverTooltipProps) {
6798 wrapInContainer = false ,
6899 tooltipCloseDelay = TOOLTIP_CLOSE_DELAY ,
69100 isClosed,
70- ...rest
101+ placement = 'top' ,
102+ bgColor,
103+ color,
104+ borderColor,
105+ className,
71106 } = props
72107
73- // { text, className, ...rest }: TooltipProps
74-
75108 const [ show , setShow ] = useState ( false )
76- const cancelCloseRef = useRef < Command | null > ( null )
77-
78- const divRef = useRef < HTMLDivElement > ( null )
79- const open = useCallback (
80- ( e : MouseEvent < HTMLDivElement > ) => {
81- e . preventDefault ( )
82- setShow ( true )
83- onOpen ?.( )
84- } ,
85- [ onOpen ] ,
86- )
87-
88- // Close the tooltip
89- const close = useCallback (
90- ( e : MouseEvent < HTMLDivElement > | null , eager = false ) => {
91- e && e . preventDefault ( )
92-
93- // Cancel any previous scheduled close
94- if ( cancelCloseRef . current ) {
95- cancelCloseRef . current ( )
96- }
97-
98- // TODO: Add proper return type annotation
99- // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
100- const closeNow = ( ) => {
101- cancelCloseRef . current = null
102- setShow ( false )
103- }
104-
105- if ( eager ) {
106- // Close eagerly
107- closeNow ( )
108- } else {
109- // Close after a delay
110- const closeTimeout = setTimeout ( closeNow , tooltipCloseDelay )
111-
112- cancelCloseRef . current = ( ) => {
113- cancelCloseRef . current = null
114- clearTimeout ( closeTimeout )
115- }
116- }
117-
118- return ( ) => cancelCloseRef . current && cancelCloseRef . current ( )
119- } ,
120- [ tooltipCloseDelay ] ,
121- )
122-
123- // Stop the delayed close when hovering the tooltip
124- const stopDelayedClose = useCallback ( ( ) => {
125- // Cancel any previous scheduled close
126- if ( cancelCloseRef . current ) {
127- cancelCloseRef . current ( )
128- }
129- } , [ ] )
130-
131- const toggleTooltip = useCallback (
132- ( e : MouseEvent < HTMLDivElement > ) => {
133- e . preventDefault ( )
134- if ( show ) {
135- close ( e , true )
136- } else {
137- open ( e )
138- }
139- } ,
140- [ close , open , show ] ,
141- )
142109
143110 useEffect ( ( ) => {
144111 if ( isClosed ) {
@@ -148,31 +115,51 @@ export function HoverTooltip(props: HoverTooltipProps) {
148115
149116 // Hide tooltip when scrolling
150117 useEffect ( ( ) => {
151- // TODO: Add proper return type annotation
152- // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
153- const handleScroll = ( ) => {
118+ const handleScroll = ( ) : void => {
154119 if ( show ) {
155- close ( null , true )
120+ setShow ( false )
156121 }
157122 }
158123
159124 document . addEventListener ( 'scroll' , handleScroll )
160125 return ( ) => {
161126 document . removeEventListener ( 'scroll' , handleScroll )
162127 }
163- } , [ show , close ] )
128+ } , [ show ] )
164129
165- const tooltipContent = disableHover ? null : (
166- < div ref = { divRef } onMouseEnter = { stopDelayedClose } onMouseLeave = { close } >
167- { wrapInContainer ? < TooltipContainer > { content } </ TooltipContainer > : content }
168- </ div >
130+ const onOpenChange = useCallback (
131+ ( open : boolean ) => {
132+ setShow ( open )
133+
134+ if ( open ) {
135+ onOpen ?.( )
136+ }
137+ } ,
138+ [ onOpen ] ,
169139 )
140+
141+ if ( disableHover ) return < > { children } </ >
142+
170143 return (
171- < Popover show = { show } content = { tooltipContent } { ...rest } >
172- < div onMouseEnter = { open } onMouseLeave = { close } onClick = { isMobile ? undefined : toggleTooltip } >
173- { children }
174- </ div >
175- </ Popover >
144+ < BaseTooltip . Provider delay = { 0 } closeDelay = { tooltipCloseDelay } >
145+ < BaseTooltip . Root open = { show } onOpenChange = { onOpenChange } >
146+ < BaseTooltip . Trigger closeOnClick = { false } render = { < BaseTooltipTrigger className = { className } /> } >
147+ { children }
148+ </ BaseTooltip . Trigger >
149+ < BaseTooltip . Portal >
150+ < BaseTooltip . Positioner
151+ side = { getTooltipSide ( placement ) }
152+ sideOffset = { 8 }
153+ positionMethod = "fixed"
154+ style = { { zIndex : 999999 } }
155+ >
156+ < BaseTooltipPopup bgColor = { bgColor } color = { color } borderColor = { borderColor } >
157+ { wrapInContainer ? < TooltipContainer > { content } </ TooltipContainer > : content }
158+ </ BaseTooltipPopup >
159+ </ BaseTooltip . Positioner >
160+ </ BaseTooltip . Portal >
161+ </ BaseTooltip . Root >
162+ </ BaseTooltip . Provider >
176163 )
177164}
178165
0 commit comments