Skip to content

Commit 49fdb26

Browse files
feat: add float mode (follow mouse)
1 parent 7859ae3 commit 49fdb26

File tree

6 files changed

+96
-29
lines changed

6 files changed

+96
-29
lines changed

src/App.tsx

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ function App() {
4343
const [anchorId, setAnchorId] = useState('button')
4444
const [isDarkOpen, setIsDarkOpen] = useState(false)
4545
const [position, setPosition] = useState<IPosition>({ x: 0, y: 0 })
46+
const [toggle, setToggle] = useState(false)
4647

4748
const handlePositionClick: React.MouseEventHandler<HTMLDivElement> = (event) => {
4849
const x = event.clientX
@@ -122,21 +123,45 @@ function App() {
122123
<TooltipProvider>
123124
<WithProviderMultiple />
124125
</TooltipProvider>
125-
<div
126-
id="onClickAnchor"
127-
className={styles['on-click-anchor']}
128-
onClick={(event) => {
129-
handlePositionClick(event)
130-
}}
131-
>
132-
Click me!
126+
<div style={{ display: 'flex', gap: '12px', flexDirection: 'row' }}>
127+
<div>
128+
<div
129+
id="floatAnchor"
130+
className={styles['big-anchor']}
131+
onClick={() => {
132+
setToggle((t) => !t)
133+
}}
134+
>
135+
Hover me!
136+
</div>
137+
<Tooltip
138+
anchorId="floatAnchor"
139+
content={
140+
toggle
141+
? 'This is a float tooltip with a very very large content string'
142+
: 'This is a float tooltip'
143+
}
144+
float
145+
/>
146+
</div>
147+
<div>
148+
<div
149+
id="onClickAnchor"
150+
className={styles['big-anchor']}
151+
onClick={(event) => {
152+
handlePositionClick(event)
153+
}}
154+
>
155+
Click me!
156+
</div>
157+
<Tooltip
158+
anchorId="onClickAnchor"
159+
content={`This is an on click tooltip (x:${position.x},y:${position.y})`}
160+
events={['click']}
161+
position={position}
162+
/>
163+
</div>
133164
</div>
134-
<Tooltip
135-
anchorId="onClickAnchor"
136-
content={`This is an on click tooltip (x:${position.x},y:${position.y})`}
137-
events={['click']}
138-
position={position}
139-
/>
140165
</main>
141166
)
142167
}

src/components/Tooltip/Tooltip.tsx

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { TooltipContent } from 'components/TooltipContent'
55
import { useTooltip } from 'components/TooltipProvider'
66
import { computeTooltipPosition } from '../../utils/compute-positions'
77
import styles from './styles.module.css'
8-
import type { ITooltip } from './TooltipTypes'
8+
import type { IPosition, ITooltip } from './TooltipTypes'
99

1010
const Tooltip = ({
1111
// props
@@ -22,6 +22,7 @@ const Tooltip = ({
2222
children = null,
2323
delayShow = 0,
2424
delayHide = 0,
25+
float = false,
2526
noArrow,
2627
style: externalStyles,
2728
position,
@@ -39,6 +40,7 @@ const Tooltip = ({
3940
const [inlineArrowStyles, setInlineArrowStyles] = useState({})
4041
const [show, setShow] = useState<boolean>(false)
4142
const [calculatingPosition, setCalculatingPosition] = useState(false)
43+
const [lastFloatPosition, setLastFloatPosition] = useState<IPosition | null>(null)
4244
const { anchorRefs, setActiveAnchor: setProviderActiveAnchor } = useTooltip()(id)
4345
const [activeAnchor, setActiveAnchor] = useState<React.RefObject<HTMLElement>>({ current: null })
4446

@@ -101,22 +103,18 @@ const Tooltip = ({
101103
}
102104
}
103105

104-
const handleTooltipPosition = () => {
105-
if (!position) {
106-
return
107-
}
108-
106+
const handleTooltipPosition = ({ x, y }: IPosition) => {
109107
const virtualElement = {
110108
getBoundingClientRect() {
111109
return {
112-
x: position.x,
113-
y: position.y,
110+
x,
111+
y,
114112
width: 0,
115113
height: 0,
116-
top: position.y,
117-
left: position.x,
118-
right: position.x,
119-
bottom: position.y,
114+
top: y,
115+
left: x,
116+
right: x,
117+
bottom: y,
120118
}
121119
},
122120
} as Element
@@ -139,6 +137,19 @@ const Tooltip = ({
139137
})
140138
}
141139

140+
const handleMouseMove = (event?: Event) => {
141+
if (!event) {
142+
return
143+
}
144+
const e = event as MouseEvent
145+
const p = {
146+
x: e.clientX,
147+
y: e.clientY,
148+
}
149+
handleTooltipPosition(p)
150+
setLastFloatPosition(p)
151+
}
152+
142153
const handleClickTooltipAnchor = () => {
143154
if (setIsOpen) {
144155
setIsOpen(!isOpen)
@@ -191,6 +202,12 @@ const Tooltip = ({
191202
{ event: 'focus', listener: debouncedHandleShowTooltip },
192203
{ event: 'blur', listener: debouncedHandleHideTooltip },
193204
)
205+
if (float) {
206+
enabledEvents.push({
207+
event: 'mousemove',
208+
listener: handleMouseMove,
209+
})
210+
}
194211
}
195212

196213
enabledEvents.forEach(({ event, listener }) => {
@@ -211,8 +228,23 @@ const Tooltip = ({
211228

212229
useEffect(() => {
213230
if (position) {
214-
// if `position` is set, override regular positioning
215-
handleTooltipPosition()
231+
// if `position` is set, override regular and `float` positioning
232+
handleTooltipPosition(position)
233+
return () => null
234+
}
235+
236+
if (float) {
237+
if (lastFloatPosition) {
238+
/*
239+
Without this, changes to `content`, `place`, `offset`, ..., will only
240+
trigger a position calculation after a `mousemove` event.
241+
242+
To see why this matters, comment this line, run `yarn dev` and click the
243+
"Hover me!" anchor.
244+
*/
245+
handleTooltipPosition(lastFloatPosition)
246+
}
247+
// if `float` is set, override regular positioning
216248
return () => null
217249
}
218250

src/components/Tooltip/TooltipTypes.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type DataAttribute =
2323
| 'position-strategy'
2424
| 'delay-show'
2525
| 'delay-hide'
26+
| 'float'
2627

2728
export interface IPosition {
2829
x: number
@@ -46,6 +47,7 @@ export interface ITooltip {
4647
positionStrategy?: PositionStrategy
4748
delayShow?: number
4849
delayHide?: number
50+
float?: boolean
4951
noArrow?: boolean
5052
style?: CSSProperties
5153
position?: IPosition

src/components/TooltipController/TooltipController.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const TooltipController = ({
2828
positionStrategy = 'absolute',
2929
delayShow = 0,
3030
delayHide = 0,
31+
float = false,
3132
noArrow,
3233
style,
3334
position,
@@ -40,6 +41,7 @@ const TooltipController = ({
4041
const [tooltipOffset, setTooltipOffset] = useState(offset)
4142
const [tooltipDelayShow, setTooltipDelayShow] = useState(delayShow)
4243
const [tooltipDelayHide, setTooltipDelayHide] = useState(delayHide)
44+
const [tooltipFloat, setTooltipFloat] = useState(float)
4345
const [tooltipWrapper, setTooltipWrapper] = useState<WrapperType>(wrapper)
4446
const [tooltipEvents, setTooltipEvents] = useState(events)
4547
const [tooltipPositionStrategy, setTooltipPositionStrategy] = useState(positionStrategy)
@@ -95,6 +97,9 @@ const TooltipController = ({
9597
'delay-hide': (value) => {
9698
setTooltipDelayHide(value === null ? delayHide : Number(value))
9799
},
100+
float: (value) => {
101+
setTooltipFloat(value === null ? float : Boolean(value))
102+
},
98103
}
99104
// reset unset data attributes to default values
100105
// without this, data attributes from the last active anchor will still be used
@@ -177,6 +182,7 @@ const TooltipController = ({
177182
positionStrategy: tooltipPositionStrategy,
178183
delayShow: tooltipDelayShow,
179184
delayHide: tooltipDelayHide,
185+
float: tooltipFloat,
180186
noArrow,
181187
style,
182188
position,

src/components/TooltipController/TooltipControllerTypes.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface ITooltipController {
2626
positionStrategy?: PositionStrategy
2727
delayShow?: number
2828
delayHide?: number
29+
float?: boolean
2930
noArrow?: boolean
3031
style?: CSSProperties
3132
position?: IPosition
@@ -45,5 +46,6 @@ declare module 'react' {
4546
'data-tooltip-position-strategy'?: PositionStrategy
4647
'data-tooltip-delay-show'?: number
4748
'data-tooltip-delay-hide'?: number
49+
'data-tooltip-float'?: boolean
4850
}
4951
}

src/styles.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
height: 100vh;
55
}
66

7-
.on-click-anchor {
7+
.big-anchor {
88
width: 500px;
99
height: 300px;
1010
background-color: #d3d3d3;

0 commit comments

Comments
 (0)