react-tooltip 5.1.0 → 5.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react-tooltip.cjs.js +1 -1
- package/dist/react-tooltip.cjs.min.js +1 -1
- package/dist/react-tooltip.d.ts +2 -3
- package/dist/react-tooltip.esm.js +1 -1
- package/dist/react-tooltip.esm.min.js +1 -1
- package/dist/react-tooltip.umd.js +1 -1
- package/dist/react-tooltip.umd.min.js +1 -1
- package/package.json +1 -1
- package/src/App.tsx +119 -0
- package/src/components/Tooltip/Tooltip.tsx +226 -0
- package/src/components/Tooltip/TooltipTypes.d.ts +47 -0
- package/src/components/Tooltip/index.ts +1 -0
- package/src/components/Tooltip/styles.module.css +62 -0
- package/src/components/TooltipContent/TooltipContent.tsx +8 -0
- package/src/components/TooltipContent/TooltipContentTypes.d.ts +3 -0
- package/src/components/TooltipContent/index.ts +1 -0
- package/src/components/TooltipController/TooltipController.tsx +187 -0
- package/src/components/TooltipController/TooltipControllerTypes.d.ts +46 -0
- package/src/components/TooltipController/index.ts +1 -0
- package/src/components/TooltipProvider/TooltipProvider.tsx +110 -0
- package/src/components/TooltipProvider/TooltipProviderTypes.d.ts +33 -0
- package/src/components/TooltipProvider/TooltipWrapper.tsx +48 -0
- package/src/components/TooltipProvider/index.ts +2 -0
- package/src/index-dev.tsx +16 -0
- package/src/index.tsx +4 -0
- package/src/styles.module.css +5 -0
- package/src/test/__snapshots__/index.spec.js.snap +102 -0
- package/src/test/index.spec.js +143 -0
- package/src/tokens.css +8 -0
- package/src/utils/compute-positions-types.d.ts +8 -0
- package/src/utils/compute-positions.ts +65 -0
- package/src/utils/debounce.ts +27 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode, RefObject } from 'react'
|
|
2
|
+
import type { ITooltipController } from 'components/TooltipController/TooltipControllerTypes'
|
|
3
|
+
|
|
4
|
+
export type AnchorRef = RefObject<HTMLElement>
|
|
5
|
+
|
|
6
|
+
export interface TooltipContextData {
|
|
7
|
+
anchorRefs: Set<AnchorRef>
|
|
8
|
+
activeAnchor: AnchorRef
|
|
9
|
+
attach: (...refs: AnchorRef[]) => void
|
|
10
|
+
detach: (...refs: AnchorRef[]) => void
|
|
11
|
+
setActiveAnchor: (ref: AnchorRef) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type TooltipContextDataWrapper = TooltipContextData & {
|
|
15
|
+
// This means the context is a callable object
|
|
16
|
+
(tooltipId?: string): TooltipContextData
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ITooltipWrapper {
|
|
20
|
+
tooltipId?: string
|
|
21
|
+
children: ReactNode
|
|
22
|
+
|
|
23
|
+
place?: ITooltipController['place']
|
|
24
|
+
content?: ITooltipController['content']
|
|
25
|
+
html?: ITooltipController['html']
|
|
26
|
+
variant?: ITooltipController['variant']
|
|
27
|
+
offset?: ITooltipController['offset']
|
|
28
|
+
wrapper?: ITooltipController['wrapper']
|
|
29
|
+
events?: ITooltipController['events']
|
|
30
|
+
positionStrategy?: ITooltipController['positionStrategy']
|
|
31
|
+
delayShow?: ITooltipController['delayShow']
|
|
32
|
+
delayHide?: ITooltipController['delayHide']
|
|
33
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import { useTooltip } from './TooltipProvider'
|
|
3
|
+
import type { ITooltipWrapper } from './TooltipProviderTypes'
|
|
4
|
+
|
|
5
|
+
const TooltipWrapper = ({
|
|
6
|
+
tooltipId,
|
|
7
|
+
children,
|
|
8
|
+
place,
|
|
9
|
+
content,
|
|
10
|
+
html,
|
|
11
|
+
variant,
|
|
12
|
+
offset,
|
|
13
|
+
wrapper,
|
|
14
|
+
events,
|
|
15
|
+
positionStrategy,
|
|
16
|
+
delayShow,
|
|
17
|
+
delayHide,
|
|
18
|
+
}: ITooltipWrapper) => {
|
|
19
|
+
const { attach, detach } = useTooltip()(tooltipId)
|
|
20
|
+
const anchorRef = useRef<HTMLElement | null>(null)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
attach(anchorRef)
|
|
24
|
+
return () => {
|
|
25
|
+
detach(anchorRef)
|
|
26
|
+
}
|
|
27
|
+
}, [])
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<span
|
|
31
|
+
ref={anchorRef}
|
|
32
|
+
data-tooltip-place={place}
|
|
33
|
+
data-tooltip-content={content}
|
|
34
|
+
data-tooltip-html={html}
|
|
35
|
+
data-tooltip-variant={variant}
|
|
36
|
+
data-tooltip-offset={offset}
|
|
37
|
+
data-tooltip-wrapper={wrapper}
|
|
38
|
+
data-tooltip-events={events}
|
|
39
|
+
data-tooltip-position-strategy={positionStrategy}
|
|
40
|
+
data-tooltip-delay-show={delayShow}
|
|
41
|
+
data-tooltip-delay-hide={delayHide}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</span>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default TooltipWrapper
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StrictMode, version } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import './tokens.css'
|
|
4
|
+
import App from './App'
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line no-console
|
|
7
|
+
console.log('Parent folder loaded react version: ', version)
|
|
8
|
+
|
|
9
|
+
const container = document.getElementById('app')
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
11
|
+
const root = createRoot(container!)
|
|
12
|
+
root.render(
|
|
13
|
+
<StrictMode>
|
|
14
|
+
<App />
|
|
15
|
+
</StrictMode>,
|
|
16
|
+
)
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`tooltip props basic tooltip component 1`] = `
|
|
4
|
+
[
|
|
5
|
+
<span
|
|
6
|
+
id="basic-example"
|
|
7
|
+
>
|
|
8
|
+
Lorem Ipsum
|
|
9
|
+
</span>,
|
|
10
|
+
<div
|
|
11
|
+
className=""
|
|
12
|
+
role="tooltip"
|
|
13
|
+
style={{}}
|
|
14
|
+
>
|
|
15
|
+
Hello World!
|
|
16
|
+
<div
|
|
17
|
+
className=""
|
|
18
|
+
style={{}}
|
|
19
|
+
/>
|
|
20
|
+
</div>,
|
|
21
|
+
]
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
exports[`tooltip props tooltip component - getContent 1`] = `
|
|
25
|
+
[
|
|
26
|
+
<span
|
|
27
|
+
id="basic-example-get-content"
|
|
28
|
+
>
|
|
29
|
+
Lorem Ipsum
|
|
30
|
+
</span>,
|
|
31
|
+
<div
|
|
32
|
+
className=""
|
|
33
|
+
role="tooltip"
|
|
34
|
+
style={{}}
|
|
35
|
+
>
|
|
36
|
+
Hello World!
|
|
37
|
+
<div
|
|
38
|
+
className=""
|
|
39
|
+
style={{}}
|
|
40
|
+
/>
|
|
41
|
+
</div>,
|
|
42
|
+
]
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
exports[`tooltip props tooltip component - html 1`] = `
|
|
46
|
+
[
|
|
47
|
+
<span
|
|
48
|
+
id="basic-example-html"
|
|
49
|
+
>
|
|
50
|
+
Lorem Ipsum
|
|
51
|
+
</span>,
|
|
52
|
+
<div
|
|
53
|
+
className=""
|
|
54
|
+
role="tooltip"
|
|
55
|
+
style={{}}
|
|
56
|
+
>
|
|
57
|
+
<span
|
|
58
|
+
dangerouslySetInnerHTML={
|
|
59
|
+
{
|
|
60
|
+
"__html": "Hello World!",
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/>
|
|
64
|
+
<div
|
|
65
|
+
className=""
|
|
66
|
+
style={{}}
|
|
67
|
+
/>
|
|
68
|
+
</div>,
|
|
69
|
+
]
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
exports[`tooltip props tooltip component - without anchorId 1`] = `
|
|
73
|
+
[
|
|
74
|
+
<span>
|
|
75
|
+
Lorem Ipsum
|
|
76
|
+
</span>,
|
|
77
|
+
<div
|
|
78
|
+
className=""
|
|
79
|
+
role="tooltip"
|
|
80
|
+
style={{}}
|
|
81
|
+
>
|
|
82
|
+
Hello World!
|
|
83
|
+
<div
|
|
84
|
+
className=""
|
|
85
|
+
style={{}}
|
|
86
|
+
/>
|
|
87
|
+
</div>,
|
|
88
|
+
]
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
exports[`tooltip props tooltip component - without element reference 1`] = `
|
|
92
|
+
<div
|
|
93
|
+
className=""
|
|
94
|
+
role="tooltip"
|
|
95
|
+
style={{}}
|
|
96
|
+
>
|
|
97
|
+
<div
|
|
98
|
+
className=""
|
|
99
|
+
style={{}}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
`;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import renderer from 'react-test-renderer'
|
|
2
|
+
import debounce from 'utils/debounce'
|
|
3
|
+
import { computeTooltipPosition } from 'utils/compute-positions'
|
|
4
|
+
import { TooltipController as Tooltip } from '../components/TooltipController'
|
|
5
|
+
|
|
6
|
+
// Tell Jest to mock all timeout functions
|
|
7
|
+
jest.useFakeTimers()
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line react/prop-types
|
|
10
|
+
const TooltipProps = ({ id, ...tooltipParams }) => (
|
|
11
|
+
<>
|
|
12
|
+
<span id={id}>Lorem Ipsum</span>
|
|
13
|
+
<Tooltip anchorId={id} {...tooltipParams} />
|
|
14
|
+
</>
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
describe('tooltip props', () => {
|
|
18
|
+
test('tooltip component - without anchorId', () => {
|
|
19
|
+
const component = renderer.create(<TooltipProps content="Hello World!" />)
|
|
20
|
+
const tree = component.toJSON()
|
|
21
|
+
expect(tree).toMatchSnapshot()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('tooltip component - without element reference', () => {
|
|
25
|
+
const component = renderer.create(<Tooltip />)
|
|
26
|
+
const tree = component.toJSON()
|
|
27
|
+
expect(tree).toMatchSnapshot()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('basic tooltip component', () => {
|
|
31
|
+
const component = renderer.create(<TooltipProps id="basic-example" content="Hello World!" />)
|
|
32
|
+
const tree = component.toJSON()
|
|
33
|
+
expect(tree).toMatchSnapshot()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('tooltip component - html', () => {
|
|
37
|
+
const component = renderer.create(
|
|
38
|
+
<TooltipProps id="basic-example-html" html="Hello World!" variant="info" place="top" />,
|
|
39
|
+
)
|
|
40
|
+
const tree = component.toJSON()
|
|
41
|
+
expect(tree).toMatchSnapshot()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('tooltip component - getContent', () => {
|
|
45
|
+
const component = renderer.create(
|
|
46
|
+
<TooltipProps
|
|
47
|
+
id="basic-example-get-content"
|
|
48
|
+
content="Hello World!"
|
|
49
|
+
getContent={(value) => `${value} Manipuled!`}
|
|
50
|
+
variant="info"
|
|
51
|
+
place="top"
|
|
52
|
+
/>,
|
|
53
|
+
)
|
|
54
|
+
const tree = component.toJSON()
|
|
55
|
+
expect(tree).toMatchSnapshot()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('compute positions', () => {
|
|
60
|
+
test('empty reference elements', async () => {
|
|
61
|
+
const value = await computeTooltipPosition({
|
|
62
|
+
elementReference: null,
|
|
63
|
+
tooltipReference: null,
|
|
64
|
+
tooltipArrowReference: null,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
expect(value).toEqual({ tooltipStyles: {}, tooltipArrowStyles: {} })
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('empty tooltip reference element', async () => {
|
|
71
|
+
const element = document.createElement('div')
|
|
72
|
+
const value = await computeTooltipPosition({
|
|
73
|
+
elementReference: element,
|
|
74
|
+
tooltipReference: null,
|
|
75
|
+
tooltipArrowReference: null,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
expect(value).toEqual({ tooltipStyles: {}, tooltipArrowStyles: {} })
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('empty tooltip arrow reference element', async () => {
|
|
82
|
+
const element = document.createElement('div')
|
|
83
|
+
const elementTooltip = document.createElement('div')
|
|
84
|
+
const value = await computeTooltipPosition({
|
|
85
|
+
elementReference: element,
|
|
86
|
+
tooltipReference: elementTooltip,
|
|
87
|
+
tooltipArrowReference: null,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
expect(value).toEqual({
|
|
91
|
+
tooltipArrowStyles: {},
|
|
92
|
+
tooltipStyles: {
|
|
93
|
+
left: '5px',
|
|
94
|
+
top: '10px',
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('all reference elements', async () => {
|
|
100
|
+
const element = document.createElement('div')
|
|
101
|
+
const elementTooltip = document.createElement('div')
|
|
102
|
+
const elementTooltipArrow = document.createElement('div')
|
|
103
|
+
const value = await computeTooltipPosition({
|
|
104
|
+
elementReference: element,
|
|
105
|
+
tooltipReference: elementTooltip,
|
|
106
|
+
tooltipArrowReference: elementTooltipArrow,
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
expect(value).toEqual({
|
|
110
|
+
tooltipArrowStyles: {
|
|
111
|
+
bottom: '-4px',
|
|
112
|
+
left: '0px',
|
|
113
|
+
right: '',
|
|
114
|
+
top: '',
|
|
115
|
+
},
|
|
116
|
+
tooltipStyles: {
|
|
117
|
+
left: '5px',
|
|
118
|
+
top: '-10px',
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
describe('debounce', () => {
|
|
125
|
+
let func
|
|
126
|
+
let debouncedFunc
|
|
127
|
+
|
|
128
|
+
beforeEach((timeout = 1000) => {
|
|
129
|
+
func = jest.fn()
|
|
130
|
+
debouncedFunc = debounce(func, timeout)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('execute just once', () => {
|
|
134
|
+
for (let i = 0; i < 100; i += 1) {
|
|
135
|
+
debouncedFunc()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Fast-forward time
|
|
139
|
+
jest.runAllTimers()
|
|
140
|
+
|
|
141
|
+
expect(func).toBeCalledTimes(1)
|
|
142
|
+
})
|
|
143
|
+
})
|
package/src/tokens.css
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface IComputePositions {
|
|
2
|
+
elementReference?: Element | HTMLElement | null
|
|
3
|
+
tooltipReference?: Element | HTMLElement | null
|
|
4
|
+
tooltipArrowReference?: Element | HTMLElement | null
|
|
5
|
+
place?: 'top' | 'right' | 'bottom' | 'left'
|
|
6
|
+
offset?: number
|
|
7
|
+
strategy?: 'absolute' | 'fixed'
|
|
8
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { computePosition, offset, flip, shift, arrow } from '@floating-ui/dom'
|
|
2
|
+
import type { IComputePositions } from './compute-positions-types'
|
|
3
|
+
|
|
4
|
+
export const computeTooltipPosition = async ({
|
|
5
|
+
elementReference = null,
|
|
6
|
+
tooltipReference = null,
|
|
7
|
+
tooltipArrowReference = null,
|
|
8
|
+
place = 'top',
|
|
9
|
+
offset: offsetValue = 10,
|
|
10
|
+
strategy = 'absolute',
|
|
11
|
+
}: IComputePositions) => {
|
|
12
|
+
if (!elementReference) {
|
|
13
|
+
// elementReference can be null or undefined and we will not compute the position
|
|
14
|
+
// eslint-disable-next-line no-console
|
|
15
|
+
// console.error('The reference element for tooltip was not defined: ', elementReference)
|
|
16
|
+
return { tooltipStyles: {}, tooltipArrowStyles: {} }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (tooltipReference === null) {
|
|
20
|
+
return { tooltipStyles: {}, tooltipArrowStyles: {} }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const middleware = [offset(Number(offsetValue)), flip(), shift({ padding: 5 })]
|
|
24
|
+
|
|
25
|
+
if (tooltipArrowReference) {
|
|
26
|
+
middleware.push(arrow({ element: tooltipArrowReference as HTMLElement }))
|
|
27
|
+
return computePosition(elementReference as HTMLElement, tooltipReference as HTMLElement, {
|
|
28
|
+
placement: place,
|
|
29
|
+
strategy,
|
|
30
|
+
middleware,
|
|
31
|
+
}).then(({ x, y, placement, middlewareData }) => {
|
|
32
|
+
const styles = { left: `${x}px`, top: `${y}px` }
|
|
33
|
+
|
|
34
|
+
const { x: arrowX, y: arrowY } = middlewareData.arrow ?? { x: 0, y: 0 }
|
|
35
|
+
|
|
36
|
+
const staticSide =
|
|
37
|
+
{
|
|
38
|
+
top: 'bottom',
|
|
39
|
+
right: 'left',
|
|
40
|
+
bottom: 'top',
|
|
41
|
+
left: 'right',
|
|
42
|
+
}[placement.split('-')[0]] ?? 'bottom'
|
|
43
|
+
|
|
44
|
+
const arrowStyle = {
|
|
45
|
+
left: arrowX != null ? `${arrowX}px` : '',
|
|
46
|
+
top: arrowY != null ? `${arrowY}px` : '',
|
|
47
|
+
right: '',
|
|
48
|
+
bottom: '',
|
|
49
|
+
[staticSide]: '-4px',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { tooltipStyles: styles, tooltipArrowStyles: arrowStyle }
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return computePosition(elementReference as HTMLElement, tooltipReference as HTMLElement, {
|
|
57
|
+
placement: 'bottom',
|
|
58
|
+
strategy,
|
|
59
|
+
middleware,
|
|
60
|
+
}).then(({ x, y }) => {
|
|
61
|
+
const styles = { left: `${x}px`, top: `${y}px` }
|
|
62
|
+
|
|
63
|
+
return { tooltipStyles: styles, tooltipArrowStyles: {} }
|
|
64
|
+
})
|
|
65
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/**
|
|
3
|
+
* This function debounce the received function
|
|
4
|
+
* @param { function } func Function to be debounced
|
|
5
|
+
* @param { number } wait Time to wait before execut the function
|
|
6
|
+
* @param { boolean } immediate Param to define if the function will be executed immediately
|
|
7
|
+
*/
|
|
8
|
+
const debounce = (func: (...args: any[]) => void, wait?: number, immediate?: true) => {
|
|
9
|
+
let timeout: NodeJS.Timeout | null = null
|
|
10
|
+
|
|
11
|
+
return function debounced(this: typeof func, ...args: any[]) {
|
|
12
|
+
const later = () => {
|
|
13
|
+
timeout = null
|
|
14
|
+
if (!immediate) {
|
|
15
|
+
func.apply(this, args)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (timeout) {
|
|
20
|
+
clearTimeout(timeout)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
timeout = setTimeout(later, wait)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default debounce
|