@sanity/ui 1.7.11 → 1.8.0
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/index.cjs.mjs +3 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.esm.js +133 -49
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +134 -47
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/hooks/useDelayed.test.ts +66 -0
- package/src/hooks/useDelayedState.ts +29 -0
- package/src/primitives/tooltip/__workshop__/props.tsx +86 -11
- package/src/primitives/tooltip/index.ts +1 -0
- package/src/primitives/tooltip/tooltip.test.tsx +278 -0
- package/src/primitives/tooltip/tooltip.tsx +81 -23
- package/src/primitives/tooltip/tooltipDelayGroup/index.ts +4 -0
- package/src/primitives/tooltip/tooltipDelayGroup/tooltipDelayGroupContext.tsx +13 -0
- package/src/primitives/tooltip/tooltipDelayGroup/tooltipDelayGroupProvider.tsx +61 -0
- package/src/primitives/tooltip/tooltipDelayGroup/types.ts +11 -0
- package/src/primitives/tooltip/tooltipDelayGroup/useTooltipDelayGroup.ts +12 -0
- package/src/primitives/types.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"source": "./src/index.ts",
|
|
@@ -66,13 +66,13 @@
|
|
|
66
66
|
"@juggle/resize-observer": "^3.4.0",
|
|
67
67
|
"@sanity/pkg-utils": "^2.4.8",
|
|
68
68
|
"@sanity/semantic-release-preset": "^4.1.3",
|
|
69
|
-
"@sanity/ui-workshop": "^1.2.
|
|
69
|
+
"@sanity/ui-workshop": "^1.2.11",
|
|
70
70
|
"@testing-library/dom": "^9.3.1",
|
|
71
71
|
"@testing-library/jest-dom": "^5.17.0",
|
|
72
72
|
"@testing-library/react": "^14.0.0",
|
|
73
73
|
"@types/jest": "^29.5.4",
|
|
74
74
|
"@types/jest-axe": "^3.5.5",
|
|
75
|
-
"@types/node": "^20.5.
|
|
75
|
+
"@types/node": "^20.5.4",
|
|
76
76
|
"@types/react": "^18.2.21",
|
|
77
77
|
"@types/react-dom": "^18.2.7",
|
|
78
78
|
"@types/react-is": "^18.2.1",
|
|
@@ -95,9 +95,9 @@
|
|
|
95
95
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
96
96
|
"http-server": "^14.1.1",
|
|
97
97
|
"husky": "^8.0.3",
|
|
98
|
-
"jest": "^29.6.
|
|
98
|
+
"jest": "^29.6.4",
|
|
99
99
|
"jest-axe": "^8.0.0",
|
|
100
|
-
"jest-environment-jsdom": "^29.6.
|
|
100
|
+
"jest-environment-jsdom": "^29.6.4",
|
|
101
101
|
"lint-staged": "^14.0.1",
|
|
102
102
|
"module-alias": "^2.2.3",
|
|
103
103
|
"npm-run-all": "^4.1.5",
|
|
@@ -105,8 +105,9 @@
|
|
|
105
105
|
"react": "^18.2.0",
|
|
106
106
|
"react-dom": "^18.2.0",
|
|
107
107
|
"react-is": "^18.2.0",
|
|
108
|
+
"refractor": "^4.8.1",
|
|
108
109
|
"rimraf": "^5.0.1",
|
|
109
|
-
"semantic-release": "^21.0
|
|
110
|
+
"semantic-release": "^21.1.0",
|
|
110
111
|
"start-server-and-test": "^2.0.0",
|
|
111
112
|
"styled-components": "^6.0.7",
|
|
112
113
|
"tsconfig-paths": "^4.2.0",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** @jest-environment jsdom */
|
|
2
|
+
|
|
3
|
+
import {renderHook, act} from '@testing-library/react'
|
|
4
|
+
import {useDelayedState} from './useDelayedState'
|
|
5
|
+
|
|
6
|
+
describe('useDelayedState', () => {
|
|
7
|
+
it('should update state immediately if delay is not provided', () => {
|
|
8
|
+
const {result} = renderHook(() => useDelayedState(false))
|
|
9
|
+
const [, setState] = result.current
|
|
10
|
+
act(() => {
|
|
11
|
+
setState(true)
|
|
12
|
+
})
|
|
13
|
+
expect(result.current[0]).toBe(true)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should update state after delay if delay is provided', async () => {
|
|
17
|
+
jest.useFakeTimers()
|
|
18
|
+
const {result} = renderHook(() => useDelayedState(false))
|
|
19
|
+
const [, setState] = result.current
|
|
20
|
+
|
|
21
|
+
act(() => {
|
|
22
|
+
setState(true, 1000)
|
|
23
|
+
})
|
|
24
|
+
expect(result.current[0]).toBe(false)
|
|
25
|
+
act(() => {
|
|
26
|
+
jest.advanceTimersByTime(500)
|
|
27
|
+
})
|
|
28
|
+
expect(result.current[0]).toBe(false)
|
|
29
|
+
|
|
30
|
+
act(() => {
|
|
31
|
+
jest.advanceTimersByTime(500)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
expect(result.current[0]).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should update state with callback function', () => {
|
|
38
|
+
const {result} = renderHook(() => useDelayedState(false))
|
|
39
|
+
const [, setState] = result.current
|
|
40
|
+
|
|
41
|
+
act(() => {
|
|
42
|
+
setState((prev: boolean) => !prev)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
expect(result.current[0]).toBe(true)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should cancel update if the set state was called with a new state', () => {
|
|
49
|
+
const {result} = renderHook(() => useDelayedState(false))
|
|
50
|
+
const [, setState] = result.current
|
|
51
|
+
|
|
52
|
+
act(() => {
|
|
53
|
+
setState(true, 1000)
|
|
54
|
+
})
|
|
55
|
+
expect(result.current[0]).toBe(false)
|
|
56
|
+
|
|
57
|
+
jest.advanceTimersByTime(500)
|
|
58
|
+
|
|
59
|
+
act(() => {
|
|
60
|
+
setState(false)
|
|
61
|
+
})
|
|
62
|
+
jest.advanceTimersByTime(600)
|
|
63
|
+
// Even after 1.1 seconds, the state should continue being false, because it was cancelled by a next setState call
|
|
64
|
+
expect(result.current[0]).toBe(false)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {useRef} from 'react'
|
|
2
|
+
import {useState, useCallback, SetStateAction} from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @beta
|
|
6
|
+
*/
|
|
7
|
+
export function useDelayedState<S>(
|
|
8
|
+
initialState: S | (() => S),
|
|
9
|
+
): [S, (nextState: SetStateAction<S>, delay?: number) => void] {
|
|
10
|
+
const [state, setState] = useState(initialState)
|
|
11
|
+
const delayedAction = useRef<NodeJS.Timeout | undefined>()
|
|
12
|
+
|
|
13
|
+
const onStateChange = useCallback((nextState: SetStateAction<S>, delay?: number) => {
|
|
14
|
+
const action = () => {
|
|
15
|
+
setState(nextState)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// A new state change has been initiated, cancel the previous one.
|
|
19
|
+
if (delayedAction.current) {
|
|
20
|
+
clearTimeout(delayedAction.current)
|
|
21
|
+
delayedAction.current = undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!delay) return action()
|
|
25
|
+
delayedAction.current = setTimeout(action, delay)
|
|
26
|
+
}, [])
|
|
27
|
+
|
|
28
|
+
return [state, onStateChange]
|
|
29
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {Button, Card, Flex, Text, Tooltip} from '@sanity/ui'
|
|
2
|
-
import {useBoolean, useSelect, useText} from '@sanity/ui-workshop'
|
|
1
|
+
import {Button, Card, Flex, Text, Tooltip, TooltipDelayGroupProvider, Stack} from '@sanity/ui'
|
|
2
|
+
import {useBoolean, useSelect, useText, useNumber} from '@sanity/ui-workshop'
|
|
3
3
|
import {
|
|
4
4
|
WORKSHOP_PLACEMENT_OPTIONS,
|
|
5
5
|
WORKSHOP_SHADOW_OPTIONS,
|
|
@@ -11,20 +11,95 @@ export default function PropsStory() {
|
|
|
11
11
|
const padding = useSelect('Padding', WORKSHOP_SPACE_OPTIONS, 2, 'Props')
|
|
12
12
|
const placement = useSelect('Placement', WORKSHOP_PLACEMENT_OPTIONS, 'top')
|
|
13
13
|
const portal = useBoolean('Portal', true)
|
|
14
|
+
const openDelay = useNumber('Open Delay', 200) || 0
|
|
15
|
+
const closeDelay = useNumber('Close Delay', 200) || 0
|
|
14
16
|
const shadow = useSelect('Shadow', WORKSHOP_SHADOW_OPTIONS, 2)
|
|
15
17
|
|
|
16
18
|
return (
|
|
17
19
|
<Card height="fill">
|
|
18
|
-
<Flex
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
<Flex
|
|
21
|
+
height="fill"
|
|
22
|
+
align="center"
|
|
23
|
+
justify="center"
|
|
24
|
+
padding={8}
|
|
25
|
+
sizing="border"
|
|
26
|
+
direction="column"
|
|
27
|
+
gap={4}
|
|
28
|
+
>
|
|
29
|
+
<Stack
|
|
30
|
+
padding={4}
|
|
31
|
+
style={{outline: '1px solid var(--card-border-color)', width: '100%', maxWidth: '640px'}}
|
|
32
|
+
space={4}
|
|
25
33
|
>
|
|
26
|
-
<
|
|
27
|
-
|
|
34
|
+
<Text align="center" size={3}>
|
|
35
|
+
Standalone tooltip
|
|
36
|
+
</Text>
|
|
37
|
+
<Flex align="center" justify="center" padding={4} sizing="border">
|
|
38
|
+
<Tooltip
|
|
39
|
+
content={<Text size={1}>{content}</Text>}
|
|
40
|
+
padding={padding}
|
|
41
|
+
placement={placement}
|
|
42
|
+
portal={portal}
|
|
43
|
+
shadow={shadow}
|
|
44
|
+
delay={{
|
|
45
|
+
open: openDelay,
|
|
46
|
+
close: closeDelay,
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<Button mode="bleed" text="Hover me" />
|
|
50
|
+
</Tooltip>
|
|
51
|
+
</Flex>
|
|
52
|
+
</Stack>
|
|
53
|
+
<Stack
|
|
54
|
+
padding={4}
|
|
55
|
+
style={{outline: '1px solid var(--card-border-color)', width: '100%', maxWidth: '640px'}}
|
|
56
|
+
space={4}
|
|
57
|
+
>
|
|
58
|
+
<Text align="center" size={3}>
|
|
59
|
+
Grouped tooltips
|
|
60
|
+
</Text>
|
|
61
|
+
<Text align="center" size={1}>
|
|
62
|
+
All tooltip delays are set to 1ms after the first tooltip within a DelayGroupProvider
|
|
63
|
+
opens.
|
|
64
|
+
</Text>
|
|
65
|
+
<Flex align="center" justify="center" padding={4} sizing="border" gap={4}>
|
|
66
|
+
<TooltipDelayGroupProvider
|
|
67
|
+
delay={{
|
|
68
|
+
open: openDelay,
|
|
69
|
+
close: closeDelay,
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<Tooltip
|
|
73
|
+
// This is overridden by the group delay, kept here intentionally for testing purposes.
|
|
74
|
+
delay={{
|
|
75
|
+
open: 100,
|
|
76
|
+
close: 100,
|
|
77
|
+
}}
|
|
78
|
+
content={<Text size={1}>{content}</Text>}
|
|
79
|
+
padding={padding}
|
|
80
|
+
placement={placement}
|
|
81
|
+
portal={portal}
|
|
82
|
+
shadow={shadow}
|
|
83
|
+
>
|
|
84
|
+
<Button mode="bleed" text="Hover me" />
|
|
85
|
+
</Tooltip>
|
|
86
|
+
<Tooltip
|
|
87
|
+
// This is overridden by the group delay, kept here intentionally for testing purposes.
|
|
88
|
+
delay={{
|
|
89
|
+
open: 100,
|
|
90
|
+
close: 100,
|
|
91
|
+
}}
|
|
92
|
+
content={<Text size={1}>{content}</Text>}
|
|
93
|
+
padding={padding}
|
|
94
|
+
placement={placement}
|
|
95
|
+
portal={portal}
|
|
96
|
+
shadow={shadow}
|
|
97
|
+
>
|
|
98
|
+
<Button mode="bleed" text="Or me" />
|
|
99
|
+
</Tooltip>
|
|
100
|
+
</TooltipDelayGroupProvider>
|
|
101
|
+
</Flex>
|
|
102
|
+
</Stack>
|
|
28
103
|
</Flex>
|
|
29
104
|
</Card>
|
|
30
105
|
)
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/** @jest-environment jsdom */
|
|
2
|
+
import {screen, act, fireEvent} from '@testing-library/react'
|
|
3
|
+
import '../../../test/mocks/resizeObserver.mock'
|
|
4
|
+
import '../../../test/mocks/matchMedia.mock'
|
|
5
|
+
import {render} from '../../../test'
|
|
6
|
+
import {Text, Button} from '../../primitives'
|
|
7
|
+
import {Tooltip, TooltipDelayGroupProvider} from '../tooltip'
|
|
8
|
+
|
|
9
|
+
describe('Tooltip', () => {
|
|
10
|
+
describe('Using same delay for open and close', () => {
|
|
11
|
+
it('should hide and show the tooltip content when hovered, with no delay', () => {
|
|
12
|
+
render(
|
|
13
|
+
<Tooltip content={<Text size={1}>{'Tooltip content'}</Text>} placement={'top'}>
|
|
14
|
+
<Button mode="bleed" text="Hover me" />
|
|
15
|
+
</Tooltip>,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const button = screen.getByText('Hover me')
|
|
19
|
+
|
|
20
|
+
// Validate tooltip content is not rendered
|
|
21
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
22
|
+
|
|
23
|
+
fireEvent.mouseEnter(button)
|
|
24
|
+
|
|
25
|
+
// Validate tooltip content is rendered
|
|
26
|
+
screen.getByText('Tooltip content')
|
|
27
|
+
|
|
28
|
+
fireEvent.mouseOut(button)
|
|
29
|
+
// Validate tooltip content is not rendered anymore
|
|
30
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
31
|
+
})
|
|
32
|
+
it('should support delays to show and hide the tooltip.', () => {
|
|
33
|
+
jest.useFakeTimers()
|
|
34
|
+
const delay = 200
|
|
35
|
+
|
|
36
|
+
render(
|
|
37
|
+
<Tooltip
|
|
38
|
+
content={<Text size={1}>{'Tooltip content'}</Text>}
|
|
39
|
+
placement={'top'}
|
|
40
|
+
delay={delay}
|
|
41
|
+
>
|
|
42
|
+
<Button mode="bleed" text="Hover me" />
|
|
43
|
+
</Tooltip>,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const button = screen.getByText('Hover me')
|
|
47
|
+
|
|
48
|
+
// Validate tooltip content is not rendered
|
|
49
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
50
|
+
|
|
51
|
+
fireEvent.mouseEnter(button)
|
|
52
|
+
|
|
53
|
+
act(() => jest.advanceTimersByTime(delay / 2))
|
|
54
|
+
// Content should not be rendered yet
|
|
55
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
56
|
+
act(() => jest.advanceTimersByTime(delay / 2))
|
|
57
|
+
|
|
58
|
+
// Validate tooltip content is rendered
|
|
59
|
+
screen.getByText('Tooltip content')
|
|
60
|
+
|
|
61
|
+
fireEvent.mouseOut(button)
|
|
62
|
+
// Validate tooltip content is still showing.
|
|
63
|
+
screen.getByText('Tooltip content')
|
|
64
|
+
act(() => jest.advanceTimersByTime(delay))
|
|
65
|
+
// Validate tooltip content is not rendered anymore
|
|
66
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
67
|
+
})
|
|
68
|
+
it('should support different open and close delays to show and hide the tooltip.', () => {
|
|
69
|
+
jest.useFakeTimers()
|
|
70
|
+
const openDelay = 200
|
|
71
|
+
const closeDelay = 150
|
|
72
|
+
|
|
73
|
+
render(
|
|
74
|
+
<Tooltip
|
|
75
|
+
content={<Text size={1}>{'Tooltip content'}</Text>}
|
|
76
|
+
placement={'top'}
|
|
77
|
+
delay={{
|
|
78
|
+
open: openDelay,
|
|
79
|
+
close: closeDelay,
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<Button mode="bleed" text="Hover me" />
|
|
83
|
+
</Tooltip>,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const button = screen.getByText('Hover me')
|
|
87
|
+
|
|
88
|
+
// Validate tooltip content is not rendered
|
|
89
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
90
|
+
|
|
91
|
+
fireEvent.mouseEnter(button)
|
|
92
|
+
|
|
93
|
+
act(() => jest.advanceTimersByTime(openDelay / 2))
|
|
94
|
+
// Content should not be rendered yet
|
|
95
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
96
|
+
act(() => jest.advanceTimersByTime(openDelay / 2))
|
|
97
|
+
|
|
98
|
+
// Validate tooltip content is rendered
|
|
99
|
+
screen.getByText('Tooltip content')
|
|
100
|
+
|
|
101
|
+
fireEvent.mouseOut(button)
|
|
102
|
+
// Validate tooltip content is still showing.
|
|
103
|
+
screen.getByText('Tooltip content')
|
|
104
|
+
act(() => jest.advanceTimersByTime(closeDelay))
|
|
105
|
+
// Validate tooltip content is not rendered anymore
|
|
106
|
+
expect(screen.queryByText('Tooltip content')).not.toBeInTheDocument()
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('Using the <TooltipDelayGroupProvider />', () => {
|
|
111
|
+
it('should support groups with the same delay to open and close.', () => {
|
|
112
|
+
const delay = 150
|
|
113
|
+
|
|
114
|
+
jest.useFakeTimers()
|
|
115
|
+
render(
|
|
116
|
+
<TooltipDelayGroupProvider delay={delay}>
|
|
117
|
+
<Tooltip content={<Text size={1}>{'Tooltip 1'}</Text>} placement={'top'} delay={400}>
|
|
118
|
+
<Button mode="bleed" text="Button 1" />
|
|
119
|
+
</Tooltip>
|
|
120
|
+
<Tooltip
|
|
121
|
+
content={<Text size={1}>{'Tooltip 2'}</Text>}
|
|
122
|
+
placement={'top'}
|
|
123
|
+
delay={400} // This should be overridden by the group delay
|
|
124
|
+
>
|
|
125
|
+
<Button mode="bleed" text="Button 2" />
|
|
126
|
+
</Tooltip>
|
|
127
|
+
</TooltipDelayGroupProvider>,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const button1 = screen.getByText('Button 1')
|
|
131
|
+
const button2 = screen.getByText('Button 2')
|
|
132
|
+
|
|
133
|
+
// Validate tooltip content is not rendered
|
|
134
|
+
expect(screen.queryByText('Tooltip 1')).not.toBeInTheDocument()
|
|
135
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
136
|
+
|
|
137
|
+
// Hovers on first button, it should show first tooltip only
|
|
138
|
+
fireEvent.mouseEnter(button1)
|
|
139
|
+
act(() => jest.advanceTimersByTime(delay / 2))
|
|
140
|
+
// Content should not be rendered yet, we have a delay of 150ms
|
|
141
|
+
expect(screen.queryByText('Tooltip 1')).not.toBeInTheDocument()
|
|
142
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
143
|
+
act(() => jest.advanceTimersByTime(delay / 2))
|
|
144
|
+
|
|
145
|
+
// Validate Tooltip 1 is rendered
|
|
146
|
+
screen.getByText('Tooltip 1')
|
|
147
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
148
|
+
|
|
149
|
+
// Hovers on second button.
|
|
150
|
+
fireEvent.mouseOut(button1)
|
|
151
|
+
fireEvent.mouseEnter(button2)
|
|
152
|
+
|
|
153
|
+
// Validate Tooltip 1 is not rendered, now tooltip 2 is open.
|
|
154
|
+
act(() => jest.advanceTimersByTime(1))
|
|
155
|
+
expect(screen.queryByText('Tooltip 1')).not.toBeInTheDocument()
|
|
156
|
+
screen.getByText('Tooltip 2')
|
|
157
|
+
|
|
158
|
+
// Validate tooltip content is not rendered anymore
|
|
159
|
+
fireEvent.mouseOut(button2)
|
|
160
|
+
act(() => jest.advanceTimersByTime(delay + 1))
|
|
161
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
162
|
+
|
|
163
|
+
// Hovering again, should trigger the tooltip to show immediately, as the group is not deactivated yet
|
|
164
|
+
fireEvent.mouseEnter(button2)
|
|
165
|
+
act(() => jest.advanceTimersByTime(1))
|
|
166
|
+
screen.getByText('Tooltip 2')
|
|
167
|
+
|
|
168
|
+
// Validate tooltip content is not rendered anymore
|
|
169
|
+
fireEvent.mouseOut(button2)
|
|
170
|
+
act(() => jest.advanceTimersByTime(delay + 1))
|
|
171
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
172
|
+
|
|
173
|
+
// Wait 200ms, the group is deactivated, hovering again should trigger the delay
|
|
174
|
+
act(() => jest.advanceTimersByTime(200))
|
|
175
|
+
fireEvent.mouseEnter(button2)
|
|
176
|
+
act(() => jest.advanceTimersByTime(delay / 2))
|
|
177
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
178
|
+
act(() => jest.advanceTimersByTime(delay / 2))
|
|
179
|
+
screen.getByText('Tooltip 2')
|
|
180
|
+
})
|
|
181
|
+
it('should support groups with different open and close delay.', () => {
|
|
182
|
+
const openDelay = 250
|
|
183
|
+
const closeDelay = 150
|
|
184
|
+
|
|
185
|
+
jest.useFakeTimers()
|
|
186
|
+
render(
|
|
187
|
+
<TooltipDelayGroupProvider
|
|
188
|
+
delay={{
|
|
189
|
+
open: openDelay,
|
|
190
|
+
close: closeDelay,
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
<Tooltip content={<Text size={1}>{'Tooltip 1'}</Text>} placement={'top'} delay={400}>
|
|
194
|
+
<Button mode="bleed" text="Button 1" />
|
|
195
|
+
</Tooltip>
|
|
196
|
+
<Tooltip
|
|
197
|
+
content={<Text size={1}>{'Tooltip 2'}</Text>}
|
|
198
|
+
placement={'top'}
|
|
199
|
+
delay={400} // This should be overridden by the group delay
|
|
200
|
+
>
|
|
201
|
+
<Button mode="bleed" text="Button 2" />
|
|
202
|
+
</Tooltip>
|
|
203
|
+
</TooltipDelayGroupProvider>,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
const button1 = screen.getByText('Button 1')
|
|
207
|
+
const button2 = screen.getByText('Button 2')
|
|
208
|
+
|
|
209
|
+
// Validate tooltip content is not rendered
|
|
210
|
+
expect(screen.queryByText('Tooltip 1')).not.toBeInTheDocument()
|
|
211
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
212
|
+
|
|
213
|
+
// Hovers on first button, it should show first tooltip only
|
|
214
|
+
fireEvent.mouseEnter(button1)
|
|
215
|
+
act(() => jest.advanceTimersByTime(openDelay / 2))
|
|
216
|
+
// Content should not be rendered yet, we have a delay of2150ms
|
|
217
|
+
expect(screen.queryByText('Tooltip 1')).not.toBeInTheDocument()
|
|
218
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
219
|
+
act(() => jest.advanceTimersByTime(openDelay / 2))
|
|
220
|
+
|
|
221
|
+
// Validate Tooltip 1 is rendered
|
|
222
|
+
screen.getByText('Tooltip 1')
|
|
223
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
224
|
+
|
|
225
|
+
// Hovers on second button.
|
|
226
|
+
fireEvent.mouseOut(button1)
|
|
227
|
+
fireEvent.mouseEnter(button2)
|
|
228
|
+
|
|
229
|
+
// Validate Tooltip 1 is not rendered, now tooltip 2 is open.
|
|
230
|
+
act(() => jest.advanceTimersByTime(1))
|
|
231
|
+
expect(screen.queryByText('Tooltip 1')).not.toBeInTheDocument()
|
|
232
|
+
screen.getByText('Tooltip 2')
|
|
233
|
+
|
|
234
|
+
// Validate tooltip content is not rendered anymore
|
|
235
|
+
fireEvent.mouseOut(button2)
|
|
236
|
+
act(() => jest.advanceTimersByTime(closeDelay + 1))
|
|
237
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
238
|
+
|
|
239
|
+
// Hovering again, should trigger the tooltip to show immediately, as the group is not deactivated yet
|
|
240
|
+
fireEvent.mouseEnter(button2)
|
|
241
|
+
act(() => jest.advanceTimersByTime(1))
|
|
242
|
+
screen.getByText('Tooltip 2')
|
|
243
|
+
|
|
244
|
+
// Validate tooltip content is not rendered anymore
|
|
245
|
+
fireEvent.mouseOut(button2)
|
|
246
|
+
act(() => jest.advanceTimersByTime(closeDelay + 1))
|
|
247
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
248
|
+
|
|
249
|
+
// Wait 200ms, the group is deactivated, hovering again should trigger the delay
|
|
250
|
+
act(() => jest.advanceTimersByTime(200))
|
|
251
|
+
fireEvent.mouseEnter(button2)
|
|
252
|
+
act(() => jest.advanceTimersByTime(openDelay / 2))
|
|
253
|
+
expect(screen.queryByText('Tooltip 2')).not.toBeInTheDocument()
|
|
254
|
+
act(() => jest.advanceTimersByTime(openDelay / 2))
|
|
255
|
+
screen.getByText('Tooltip 2')
|
|
256
|
+
})
|
|
257
|
+
it("should throw an error if it's nested inside another <TooltipDelayGroupProvider />", () => {
|
|
258
|
+
// eslint-disable-next-line no-console
|
|
259
|
+
console.error = jest.fn() // Silence console.error as we are actually expecting an error in this test.
|
|
260
|
+
expect(() => {
|
|
261
|
+
render(
|
|
262
|
+
<TooltipDelayGroupProvider delay={100}>
|
|
263
|
+
<TooltipDelayGroupProvider delay={100}>
|
|
264
|
+
<Tooltip content={<Text size={1}>{'Tooltip 1'}</Text>} placement={'top'} delay={400}>
|
|
265
|
+
<Button mode="bleed" text="Button 1" />
|
|
266
|
+
</Tooltip>
|
|
267
|
+
</TooltipDelayGroupProvider>
|
|
268
|
+
</TooltipDelayGroupProvider>,
|
|
269
|
+
)
|
|
270
|
+
}).toThrow(
|
|
271
|
+
'TooltipDelayGroupProvider cannot be nested inside another TooltipDelayGroupProvider',
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
// Clean up the console.error mock
|
|
275
|
+
jest.clearAllMocks()
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
})
|