Skip to content

Commit 57bb786

Browse files
fix(tooltip): use BaseUI tooltip for HoverTooltip
1 parent 93aea26 commit 57bb786

3 files changed

Lines changed: 303 additions & 199 deletions

File tree

libs/ui/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,22 @@
2424
}
2525
},
2626
"dependencies": {
27-
"@cowprotocol/cow-sdk": "9.0.2",
27+
"@base-ui/react": "^1.4.1",
2828
"@cowprotocol/analytics": "workspace:*",
2929
"@cowprotocol/assets": "workspace:*",
3030
"@cowprotocol/common-const": "workspace:*",
3131
"@cowprotocol/common-hooks": "workspace:*",
3232
"@cowprotocol/common-utils": "workspace:*",
3333
"@cowprotocol/core": "workspace:*",
34+
"@cowprotocol/cow-sdk": "9.0.2",
35+
"@cowprotocol/currency": "workspace:*",
3436
"@cowprotocol/types": "workspace:*",
3537
"@cowprotocol/ui-utils": "workspace:*",
3638
"@lingui/core": "^5.4.1",
3739
"@lingui/react": "^5.4.1",
3840
"@popperjs/core": "^2.4.4",
3941
"@reach/menu-button": "^0.18.0",
4042
"@reach/portal": "^0.18.0",
41-
"@cowprotocol/currency": "workspace:*",
4243
"bignumber.js": "^9.1.2",
4344
"color2k": "^2.0.2",
4445
"jotai": "2.16.2",

libs/ui/src/pure/Tooltip/index.tsx

Lines changed: 76 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { MouseEvent, ReactNode, RefObject, useCallback, useEffect, useRef, useState } from 'react'
1+
import { ReactNode, RefObject, useCallback, useEffect, useRef, useState } from 'react'
22

33
import { useOnClickOutside, useOnScroll } from '@cowprotocol/common-hooks'
4-
import { isMobile } from '@cowprotocol/common-utils'
54
import { Command } from '@cowprotocol/types'
65

6+
import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'
77
import styled from 'styled-components/macro'
88

9+
import { UI } from '../../enum'
910
import Popover, { PopoverProps } from '../Popover'
1011

1112
const 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

Comments
 (0)