@matthesketh/react-guidetour 1.0.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/LICENSE +15 -0
- package/README.md +41 -0
- package/dist/index.d.mts +496 -0
- package/dist/index.d.ts +496 -0
- package/dist/index.js +2168 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2127 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +76 -0
- package/src/components/Beacon.tsx +135 -0
- package/src/components/Overlay.tsx +238 -0
- package/src/components/Portal.tsx +37 -0
- package/src/components/Spotlight.tsx +18 -0
- package/src/components/Step.tsx +255 -0
- package/src/components/Tooltip/CloseButton.tsx +31 -0
- package/src/components/Tooltip/Container.tsx +75 -0
- package/src/components/Tooltip/index.tsx +140 -0
- package/src/components/index.tsx +410 -0
- package/src/defaults.ts +62 -0
- package/src/global.d.ts +8 -0
- package/src/index.tsx +5 -0
- package/src/literals/index.ts +51 -0
- package/src/modules/dom.ts +277 -0
- package/src/modules/helpers.tsx +268 -0
- package/src/modules/scope.ts +136 -0
- package/src/modules/step.ts +120 -0
- package/src/modules/store.ts +325 -0
- package/src/styles.ts +191 -0
- package/src/types/common.ts +110 -0
- package/src/types/components.ts +382 -0
- package/src/types/index.ts +2 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import treeChanges from 'tree-changes';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getClientRect,
|
|
7
|
+
getDocumentHeight,
|
|
8
|
+
getElement,
|
|
9
|
+
getElementPosition,
|
|
10
|
+
getScrollParent,
|
|
11
|
+
hasCustomScrollParent,
|
|
12
|
+
hasPosition,
|
|
13
|
+
} from '~/modules/dom';
|
|
14
|
+
import { isSafari, log } from '~/modules/helpers';
|
|
15
|
+
|
|
16
|
+
import { LIFECYCLE } from '~/literals';
|
|
17
|
+
|
|
18
|
+
import { Lifecycle, OverlayProps } from '~/types';
|
|
19
|
+
|
|
20
|
+
import Spotlight from './Spotlight';
|
|
21
|
+
|
|
22
|
+
interface SpotlightStyles extends React.CSSProperties {
|
|
23
|
+
height: number;
|
|
24
|
+
left: number;
|
|
25
|
+
top: number;
|
|
26
|
+
width: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function JoyrideOverlay(props: OverlayProps) {
|
|
30
|
+
const {
|
|
31
|
+
continuous,
|
|
32
|
+
debug,
|
|
33
|
+
disableOverlay,
|
|
34
|
+
disableOverlayClose,
|
|
35
|
+
disableScrolling,
|
|
36
|
+
disableScrollParentFix = false,
|
|
37
|
+
lifecycle,
|
|
38
|
+
onClickOverlay,
|
|
39
|
+
placement,
|
|
40
|
+
spotlightClicks,
|
|
41
|
+
spotlightPadding = 0,
|
|
42
|
+
styles,
|
|
43
|
+
target,
|
|
44
|
+
} = props;
|
|
45
|
+
|
|
46
|
+
const [isScrolling, setIsScrolling] = useState(false);
|
|
47
|
+
const [mouseOverSpotlight, setMouseOverSpotlight] = useState(false);
|
|
48
|
+
const [showSpotlight, setShowSpotlight] = useState(true);
|
|
49
|
+
const [, setForceRender] = useState(0);
|
|
50
|
+
|
|
51
|
+
const isActiveRef = useRef(false);
|
|
52
|
+
const resizeTimeoutRef = useRef<number | undefined>(undefined);
|
|
53
|
+
const scrollTimeoutRef = useRef<number | undefined>(undefined);
|
|
54
|
+
const scrollParentRef = useRef<Document | Element | undefined>(undefined);
|
|
55
|
+
const previousPropsRef = useRef(props);
|
|
56
|
+
|
|
57
|
+
const getSpotlightStyles = useCallback((): SpotlightStyles => {
|
|
58
|
+
const element = getElement(target);
|
|
59
|
+
const elementRect = getClientRect(element);
|
|
60
|
+
const isFixedTarget = hasPosition(element);
|
|
61
|
+
const top = getElementPosition(element, spotlightPadding, disableScrollParentFix);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
...styles.spotlight,
|
|
65
|
+
height: Math.round((elementRect?.height ?? 0) + spotlightPadding * 2),
|
|
66
|
+
left: Math.round((elementRect?.left ?? 0) - spotlightPadding),
|
|
67
|
+
opacity: showSpotlight ? 1 : 0,
|
|
68
|
+
pointerEvents: spotlightClicks ? 'none' : 'auto',
|
|
69
|
+
position: isFixedTarget ? 'fixed' : 'absolute',
|
|
70
|
+
top,
|
|
71
|
+
transition: 'opacity 0.2s',
|
|
72
|
+
width: Math.round((elementRect?.width ?? 0) + spotlightPadding * 2),
|
|
73
|
+
} satisfies React.CSSProperties;
|
|
74
|
+
}, [target, spotlightPadding, disableScrollParentFix, styles.spotlight, showSpotlight, spotlightClicks]);
|
|
75
|
+
|
|
76
|
+
const handleMouseMove = useCallback((event: MouseEvent) => {
|
|
77
|
+
const spotStyles = getSpotlightStyles();
|
|
78
|
+
const { height, left, position, top, width } = spotStyles;
|
|
79
|
+
|
|
80
|
+
const offsetY = position === 'fixed' ? event.clientY : event.pageY;
|
|
81
|
+
const offsetX = position === 'fixed' ? event.clientX : event.pageX;
|
|
82
|
+
const inSpotlightHeight = offsetY >= top && offsetY <= top + height;
|
|
83
|
+
const inSpotlightWidth = offsetX >= left && offsetX <= left + width;
|
|
84
|
+
const inSpotlight = inSpotlightWidth && inSpotlightHeight;
|
|
85
|
+
|
|
86
|
+
setMouseOverSpotlight(prev => {
|
|
87
|
+
if (inSpotlight !== prev) {
|
|
88
|
+
return inSpotlight;
|
|
89
|
+
}
|
|
90
|
+
return prev;
|
|
91
|
+
});
|
|
92
|
+
}, [getSpotlightStyles]);
|
|
93
|
+
|
|
94
|
+
const handleScroll = useCallback(() => {
|
|
95
|
+
const element = getElement(target);
|
|
96
|
+
|
|
97
|
+
if (scrollParentRef.current !== document) {
|
|
98
|
+
setIsScrolling(prev => {
|
|
99
|
+
if (!prev) {
|
|
100
|
+
setShowSpotlight(false);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return prev;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
107
|
+
|
|
108
|
+
scrollTimeoutRef.current = window.setTimeout(() => {
|
|
109
|
+
if (isActiveRef.current) {
|
|
110
|
+
setIsScrolling(false);
|
|
111
|
+
setShowSpotlight(true);
|
|
112
|
+
}
|
|
113
|
+
}, 50);
|
|
114
|
+
} else if (hasPosition(element, 'sticky')) {
|
|
115
|
+
setForceRender(c => c + 1);
|
|
116
|
+
}
|
|
117
|
+
}, [target]);
|
|
118
|
+
|
|
119
|
+
const handleResize = useCallback(() => {
|
|
120
|
+
clearTimeout(resizeTimeoutRef.current);
|
|
121
|
+
|
|
122
|
+
resizeTimeoutRef.current = window.setTimeout(() => {
|
|
123
|
+
if (isActiveRef.current) {
|
|
124
|
+
setForceRender(c => c + 1);
|
|
125
|
+
}
|
|
126
|
+
}, 100);
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
// Mount/unmount
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const element = getElement(target);
|
|
132
|
+
scrollParentRef.current = getScrollParent(element ?? document.body, disableScrollParentFix, true);
|
|
133
|
+
isActiveRef.current = true;
|
|
134
|
+
|
|
135
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
136
|
+
if (!disableScrolling && hasCustomScrollParent(element, true)) {
|
|
137
|
+
log({
|
|
138
|
+
title: 'step has a custom scroll parent and can cause trouble with scrolling',
|
|
139
|
+
data: [{ key: 'parent', value: scrollParentRef.current }],
|
|
140
|
+
debug,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
window.addEventListener('resize', handleResize);
|
|
146
|
+
|
|
147
|
+
return () => {
|
|
148
|
+
isActiveRef.current = false;
|
|
149
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
150
|
+
window.removeEventListener('resize', handleResize);
|
|
151
|
+
clearTimeout(resizeTimeoutRef.current);
|
|
152
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
153
|
+
scrollParentRef.current?.removeEventListener('scroll', handleScroll);
|
|
154
|
+
};
|
|
155
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
156
|
+
|
|
157
|
+
// componentDidUpdate equivalent
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
const previousProps = previousPropsRef.current;
|
|
160
|
+
const { changed } = treeChanges(previousProps, props);
|
|
161
|
+
|
|
162
|
+
if (changed('target') || changed('disableScrollParentFix')) {
|
|
163
|
+
const element = getElement(target);
|
|
164
|
+
scrollParentRef.current = getScrollParent(element ?? document.body, disableScrollParentFix, true);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (changed('lifecycle', LIFECYCLE.TOOLTIP)) {
|
|
168
|
+
scrollParentRef.current?.addEventListener('scroll', handleScroll, { passive: true });
|
|
169
|
+
|
|
170
|
+
setTimeout(() => {
|
|
171
|
+
if (!isScrolling) {
|
|
172
|
+
setShowSpotlight(true);
|
|
173
|
+
}
|
|
174
|
+
}, 100);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (changed('spotlightClicks') || changed('disableOverlay') || changed('lifecycle')) {
|
|
178
|
+
if (spotlightClicks && lifecycle === LIFECYCLE.TOOLTIP) {
|
|
179
|
+
window.addEventListener('mousemove', handleMouseMove, false);
|
|
180
|
+
} else if (lifecycle !== LIFECYCLE.TOOLTIP) {
|
|
181
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
previousPropsRef.current = props;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const hideSpotlight = () => {
|
|
189
|
+
const hiddenLifecycles = [
|
|
190
|
+
LIFECYCLE.INIT,
|
|
191
|
+
LIFECYCLE.BEACON,
|
|
192
|
+
LIFECYCLE.COMPLETE,
|
|
193
|
+
LIFECYCLE.ERROR,
|
|
194
|
+
] as Lifecycle[];
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
disableOverlay ||
|
|
198
|
+
(continuous ? hiddenLifecycles.includes(lifecycle) : lifecycle !== LIFECYCLE.TOOLTIP)
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
if (hideSpotlight()) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const spotlightStyles = getSpotlightStyles();
|
|
207
|
+
|
|
208
|
+
const overlayStyles: React.CSSProperties = {
|
|
209
|
+
cursor: disableOverlayClose ? 'default' : 'pointer',
|
|
210
|
+
height: getDocumentHeight(),
|
|
211
|
+
pointerEvents: mouseOverSpotlight ? 'none' : 'auto',
|
|
212
|
+
...styles.overlay,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
let spotlight = placement !== 'center' && showSpotlight && (
|
|
216
|
+
<Spotlight styles={spotlightStyles} />
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Hack for Safari bug with mix-blend-mode with z-index
|
|
220
|
+
if (isSafari()) {
|
|
221
|
+
const { mixBlendMode, zIndex, ...safariOverlay } = overlayStyles;
|
|
222
|
+
|
|
223
|
+
spotlight = <div style={{ ...safariOverlay }}>{spotlight}</div>;
|
|
224
|
+
delete overlayStyles.backgroundColor;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div
|
|
229
|
+
className="react-joyride__overlay"
|
|
230
|
+
data-test-id="overlay"
|
|
231
|
+
onClick={onClickOverlay}
|
|
232
|
+
role="presentation"
|
|
233
|
+
style={overlayStyles}
|
|
234
|
+
>
|
|
235
|
+
{spotlight}
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ReactElement, useEffect, useRef } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
|
|
4
|
+
import { canUseDOM } from '~/modules/dom';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
children: ReactElement;
|
|
8
|
+
id: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function JoyridePortal({ children, id }: Props) {
|
|
12
|
+
const nodeRef = useRef<HTMLElement | null>(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!canUseDOM()) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const node = document.createElement('div');
|
|
20
|
+
node.id = id;
|
|
21
|
+
document.body.appendChild(node);
|
|
22
|
+
nodeRef.current = node;
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
if (node.parentNode === document.body) {
|
|
26
|
+
document.body.removeChild(node);
|
|
27
|
+
}
|
|
28
|
+
nodeRef.current = null;
|
|
29
|
+
};
|
|
30
|
+
}, [id]);
|
|
31
|
+
|
|
32
|
+
if (!canUseDOM() || !nodeRef.current) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return createPortal(children, nodeRef.current);
|
|
37
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
styles: React.CSSProperties;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function JoyrideSpotlight({ styles }: Props) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
key="JoyrideSpotlight"
|
|
11
|
+
className="react-joyride__spotlight"
|
|
12
|
+
data-test-id="spotlight"
|
|
13
|
+
style={styles}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default JoyrideSpotlight;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import Floater, { Props as FloaterProps } from 'react-floater';
|
|
3
|
+
import is from 'is-lite';
|
|
4
|
+
import treeChanges from 'tree-changes';
|
|
5
|
+
|
|
6
|
+
import { getElement, isElementVisible } from '~/modules/dom';
|
|
7
|
+
import { hideBeacon, log } from '~/modules/helpers';
|
|
8
|
+
import Scope from '~/modules/scope';
|
|
9
|
+
import { validateStep } from '~/modules/step';
|
|
10
|
+
|
|
11
|
+
import { ACTIONS, EVENTS, LIFECYCLE, STATUS } from '~/literals';
|
|
12
|
+
|
|
13
|
+
import { StepProps } from '~/types';
|
|
14
|
+
|
|
15
|
+
import Beacon from './Beacon';
|
|
16
|
+
import Tooltip from './Tooltip/index';
|
|
17
|
+
|
|
18
|
+
export default function JoyrideStep(props: StepProps) {
|
|
19
|
+
const {
|
|
20
|
+
action,
|
|
21
|
+
callback,
|
|
22
|
+
continuous,
|
|
23
|
+
controlled,
|
|
24
|
+
debug,
|
|
25
|
+
helpers,
|
|
26
|
+
index,
|
|
27
|
+
lifecycle,
|
|
28
|
+
nonce,
|
|
29
|
+
shouldScroll,
|
|
30
|
+
size,
|
|
31
|
+
status,
|
|
32
|
+
step,
|
|
33
|
+
store,
|
|
34
|
+
} = props;
|
|
35
|
+
|
|
36
|
+
const scopeRef = useRef<Scope | null>(null);
|
|
37
|
+
const tooltipRef = useRef<HTMLElement | null>(null);
|
|
38
|
+
const previousPropsRef = useRef(props);
|
|
39
|
+
|
|
40
|
+
const setTooltipRef = useCallback((element: HTMLElement) => {
|
|
41
|
+
tooltipRef.current = element;
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const handleClickHoverBeacon = useCallback((event: React.MouseEvent<HTMLElement>) => {
|
|
45
|
+
if (event.type === 'mouseenter' && step.event !== 'hover') {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
store.update({ lifecycle: LIFECYCLE.TOOLTIP });
|
|
50
|
+
}, [step.event, store]);
|
|
51
|
+
|
|
52
|
+
const setPopper: FloaterProps['getPopper'] = useCallback((popper: any, type: any) => {
|
|
53
|
+
if (type === 'wrapper') {
|
|
54
|
+
store.setPopper('beacon', popper);
|
|
55
|
+
} else {
|
|
56
|
+
store.setPopper('tooltip', popper);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
store.getPopper('beacon') &&
|
|
61
|
+
(store.getPopper('tooltip') || step.placement === 'center') &&
|
|
62
|
+
lifecycle === LIFECYCLE.INIT
|
|
63
|
+
) {
|
|
64
|
+
store.update({
|
|
65
|
+
action,
|
|
66
|
+
lifecycle: LIFECYCLE.READY,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (step.floaterProps?.getPopper) {
|
|
71
|
+
step.floaterProps.getPopper(popper, type);
|
|
72
|
+
}
|
|
73
|
+
}, [action, lifecycle, step.placement, step.floaterProps, store]);
|
|
74
|
+
|
|
75
|
+
// Log on mount
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
log({
|
|
78
|
+
title: `step:${index}`,
|
|
79
|
+
data: [{ key: 'props', value: props }],
|
|
80
|
+
debug,
|
|
81
|
+
});
|
|
82
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
83
|
+
|
|
84
|
+
// Cleanup scope on unmount
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
return () => {
|
|
87
|
+
scopeRef.current?.removeScope();
|
|
88
|
+
};
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
// componentDidUpdate equivalent
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const previousProps = previousPropsRef.current;
|
|
94
|
+
const { changed, changedFrom } = treeChanges(previousProps, props);
|
|
95
|
+
const state = helpers.info();
|
|
96
|
+
|
|
97
|
+
const skipBeacon =
|
|
98
|
+
continuous && action !== ACTIONS.CLOSE && (index > 0 || action === ACTIONS.PREV);
|
|
99
|
+
const hasStoreChanged =
|
|
100
|
+
changed('action') || changed('index') || changed('lifecycle') || changed('status');
|
|
101
|
+
const isInitial = changedFrom('lifecycle', [LIFECYCLE.TOOLTIP, LIFECYCLE.INIT], LIFECYCLE.INIT);
|
|
102
|
+
const isAfterAction = changed('action', [
|
|
103
|
+
ACTIONS.NEXT,
|
|
104
|
+
ACTIONS.PREV,
|
|
105
|
+
ACTIONS.SKIP,
|
|
106
|
+
ACTIONS.CLOSE,
|
|
107
|
+
]);
|
|
108
|
+
const isControlled = controlled && index === previousProps.index;
|
|
109
|
+
|
|
110
|
+
if (isAfterAction && (isInitial || isControlled)) {
|
|
111
|
+
callback({
|
|
112
|
+
...state,
|
|
113
|
+
index: previousProps.index,
|
|
114
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
115
|
+
step: previousProps.step,
|
|
116
|
+
type: EVENTS.STEP_AFTER,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (
|
|
121
|
+
step.placement === 'center' &&
|
|
122
|
+
status === STATUS.RUNNING &&
|
|
123
|
+
changed('index') &&
|
|
124
|
+
action !== ACTIONS.START &&
|
|
125
|
+
lifecycle === LIFECYCLE.INIT
|
|
126
|
+
) {
|
|
127
|
+
store.update({ lifecycle: LIFECYCLE.READY });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (hasStoreChanged) {
|
|
131
|
+
const element = getElement(step.target);
|
|
132
|
+
const elementExists = !!element;
|
|
133
|
+
const hasRenderedTarget = elementExists && isElementVisible(element);
|
|
134
|
+
|
|
135
|
+
if (hasRenderedTarget) {
|
|
136
|
+
if (
|
|
137
|
+
changedFrom('status', STATUS.READY, STATUS.RUNNING) ||
|
|
138
|
+
changedFrom('lifecycle', LIFECYCLE.INIT, LIFECYCLE.READY)
|
|
139
|
+
) {
|
|
140
|
+
callback({
|
|
141
|
+
...state,
|
|
142
|
+
step,
|
|
143
|
+
type: EVENTS.STEP_BEFORE,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
// eslint-disable-next-line no-console
|
|
148
|
+
console.warn(elementExists ? 'Target not visible' : 'Target not mounted', step);
|
|
149
|
+
callback({
|
|
150
|
+
...state,
|
|
151
|
+
type: EVENTS.TARGET_NOT_FOUND,
|
|
152
|
+
step,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!controlled) {
|
|
156
|
+
store.update({ index: index + (action === ACTIONS.PREV ? -1 : 1) });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (changedFrom('lifecycle', LIFECYCLE.INIT, LIFECYCLE.READY)) {
|
|
162
|
+
store.update({
|
|
163
|
+
lifecycle: hideBeacon(step) || skipBeacon ? LIFECYCLE.TOOLTIP : LIFECYCLE.BEACON,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (changed('index')) {
|
|
168
|
+
log({
|
|
169
|
+
title: `step:${lifecycle}`,
|
|
170
|
+
data: [{ key: 'props', value: props }],
|
|
171
|
+
debug,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (changed('lifecycle', LIFECYCLE.BEACON)) {
|
|
176
|
+
callback({
|
|
177
|
+
...state,
|
|
178
|
+
step,
|
|
179
|
+
type: EVENTS.BEACON,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (changed('lifecycle', LIFECYCLE.TOOLTIP)) {
|
|
184
|
+
callback({
|
|
185
|
+
...state,
|
|
186
|
+
step,
|
|
187
|
+
type: EVENTS.TOOLTIP,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (shouldScroll && tooltipRef.current) {
|
|
191
|
+
scopeRef.current = new Scope(tooltipRef.current, { selector: '[data-action=primary]' });
|
|
192
|
+
scopeRef.current.setFocus();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (changedFrom('lifecycle', [LIFECYCLE.TOOLTIP, LIFECYCLE.INIT], LIFECYCLE.INIT)) {
|
|
197
|
+
scopeRef.current?.removeScope();
|
|
198
|
+
store.cleanupPoppers();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
previousPropsRef.current = props;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const target = getElement(step.target);
|
|
205
|
+
|
|
206
|
+
if (!validateStep(step) || !is.domElement(target)) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const isOpen = hideBeacon(step) || lifecycle === LIFECYCLE.TOOLTIP;
|
|
211
|
+
|
|
212
|
+
const TooltipComponent = () => (
|
|
213
|
+
<Tooltip
|
|
214
|
+
continuous={continuous}
|
|
215
|
+
helpers={helpers}
|
|
216
|
+
index={index}
|
|
217
|
+
isLastStep={index + 1 === size}
|
|
218
|
+
setTooltipRef={setTooltipRef}
|
|
219
|
+
size={size}
|
|
220
|
+
step={step}
|
|
221
|
+
/>
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Omit content/component from step.floaterProps to avoid conflict with RequireExactlyOne
|
|
225
|
+
const { content: _c, component: _comp, ...safeFloaterProps } = (step.floaterProps ?? {}) as any;
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div key={`JoyrideStep-${index}`} className="react-joyride__step">
|
|
229
|
+
<Floater
|
|
230
|
+
{...safeFloaterProps}
|
|
231
|
+
component={TooltipComponent}
|
|
232
|
+
debug={debug}
|
|
233
|
+
getPopper={setPopper}
|
|
234
|
+
id={`react-joyride-step-${index}`}
|
|
235
|
+
open={isOpen}
|
|
236
|
+
placement={step.placement}
|
|
237
|
+
target={step.target}
|
|
238
|
+
>
|
|
239
|
+
<Beacon
|
|
240
|
+
beaconComponent={step.beaconComponent}
|
|
241
|
+
continuous={continuous}
|
|
242
|
+
index={index}
|
|
243
|
+
isLastStep={index + 1 === size}
|
|
244
|
+
locale={step.locale}
|
|
245
|
+
nonce={nonce}
|
|
246
|
+
onClickOrHover={handleClickHoverBeacon}
|
|
247
|
+
shouldFocus={shouldScroll}
|
|
248
|
+
size={size}
|
|
249
|
+
step={step}
|
|
250
|
+
styles={step.styles}
|
|
251
|
+
/>
|
|
252
|
+
</Floater>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { CSSProperties } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
styles: CSSProperties;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function JoyrideTooltipCloseButton({ styles, ...props }: Props) {
|
|
8
|
+
const { color, height, width, ...style } = styles;
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<button style={style} type="button" {...props}>
|
|
12
|
+
<svg
|
|
13
|
+
height={typeof height === 'number' ? `${height}px` : height}
|
|
14
|
+
preserveAspectRatio="xMidYMid"
|
|
15
|
+
version="1.1"
|
|
16
|
+
viewBox="0 0 18 18"
|
|
17
|
+
width={typeof width === 'number' ? `${width}px` : width}
|
|
18
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
19
|
+
>
|
|
20
|
+
<g>
|
|
21
|
+
<path
|
|
22
|
+
d="M8.13911129,9.00268191 L0.171521827,17.0258467 C-0.0498027049,17.248715 -0.0498027049,17.6098394 0.171521827,17.8327545 C0.28204354,17.9443526 0.427188206,17.9998706 0.572051765,17.9998706 C0.71714958,17.9998706 0.862013139,17.9443526 0.972581703,17.8327545 L9.0000937,9.74924618 L17.0276057,17.8327545 C17.1384085,17.9443526 17.2832721,17.9998706 17.4281356,17.9998706 C17.5729992,17.9998706 17.718097,17.9443526 17.8286656,17.8327545 C18.0499901,17.6098862 18.0499901,17.2487618 17.8286656,17.0258467 L9.86135722,9.00268191 L17.8340066,0.973848225 C18.0553311,0.750979934 18.0553311,0.389855532 17.8340066,0.16694039 C17.6126821,-0.0556467968 17.254037,-0.0556467968 17.0329467,0.16694039 L9.00042166,8.25611765 L0.967006424,0.167268345 C0.745681892,-0.0553188426 0.387317931,-0.0553188426 0.165993399,0.167268345 C-0.0553311331,0.390136635 -0.0553311331,0.751261038 0.165993399,0.974176179 L8.13920499,9.00268191 L8.13911129,9.00268191 Z"
|
|
23
|
+
fill={color}
|
|
24
|
+
/>
|
|
25
|
+
</g>
|
|
26
|
+
</svg>
|
|
27
|
+
</button>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default JoyrideTooltipCloseButton;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { getReactNodeText } from '~/modules/helpers';
|
|
4
|
+
|
|
5
|
+
import { TooltipRenderProps } from '~/types';
|
|
6
|
+
|
|
7
|
+
import CloseButton from './CloseButton';
|
|
8
|
+
|
|
9
|
+
function JoyrideTooltipContainer(props: TooltipRenderProps) {
|
|
10
|
+
const { backProps, closeProps, index, isLastStep, primaryProps, skipProps, step, tooltipProps } =
|
|
11
|
+
props;
|
|
12
|
+
const { content, hideBackButton, hideCloseButton, hideFooter, showSkipButton, styles, title } =
|
|
13
|
+
step;
|
|
14
|
+
const output: Record<string, React.ReactNode> = {};
|
|
15
|
+
|
|
16
|
+
output.primary = (
|
|
17
|
+
<button
|
|
18
|
+
data-test-id="button-primary"
|
|
19
|
+
style={styles.buttonNext}
|
|
20
|
+
type="button"
|
|
21
|
+
{...primaryProps}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (showSkipButton && !isLastStep) {
|
|
26
|
+
output.skip = (
|
|
27
|
+
<button
|
|
28
|
+
aria-live="off"
|
|
29
|
+
data-test-id="button-skip"
|
|
30
|
+
style={styles.buttonSkip}
|
|
31
|
+
type="button"
|
|
32
|
+
{...skipProps}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!hideBackButton && index > 0) {
|
|
38
|
+
output.back = (
|
|
39
|
+
<button data-test-id="button-back" style={styles.buttonBack} type="button" {...backProps} />
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
output.close = !hideCloseButton && (
|
|
44
|
+
<CloseButton data-test-id="button-close" styles={styles.buttonClose} {...closeProps} />
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
key="JoyrideTooltip"
|
|
50
|
+
aria-label={getReactNodeText(title ?? content)}
|
|
51
|
+
className="react-joyride__tooltip"
|
|
52
|
+
style={styles.tooltip}
|
|
53
|
+
{...tooltipProps}
|
|
54
|
+
>
|
|
55
|
+
<div style={styles.tooltipContainer}>
|
|
56
|
+
{title && (
|
|
57
|
+
<h1 aria-label={getReactNodeText(title)} style={styles.tooltipTitle}>
|
|
58
|
+
{title}
|
|
59
|
+
</h1>
|
|
60
|
+
)}
|
|
61
|
+
<div style={styles.tooltipContent}>{content}</div>
|
|
62
|
+
</div>
|
|
63
|
+
{!hideFooter && (
|
|
64
|
+
<div style={styles.tooltipFooter}>
|
|
65
|
+
<div style={styles.tooltipFooterSpacer}>{output.skip}</div>
|
|
66
|
+
{output.back}
|
|
67
|
+
{output.primary}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
{output.close}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default JoyrideTooltipContainer;
|