@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,140 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { getReactNodeText, replaceLocaleContent } from '~/modules/helpers';
|
|
4
|
+
|
|
5
|
+
import { TooltipProps } from '~/types';
|
|
6
|
+
|
|
7
|
+
import Container from './Container';
|
|
8
|
+
|
|
9
|
+
export default function JoyrideTooltip(props: TooltipProps) {
|
|
10
|
+
const { continuous, helpers, index, isLastStep, setTooltipRef, size, step } = props;
|
|
11
|
+
|
|
12
|
+
const handleClickBack = (event: React.MouseEvent<HTMLElement>) => {
|
|
13
|
+
event.preventDefault();
|
|
14
|
+
helpers.prev();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const handleClickClose = (event: React.MouseEvent<HTMLElement>) => {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
helpers.close('button_close');
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const handleClickPrimary = (event: React.MouseEvent<HTMLElement>) => {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
|
|
25
|
+
if (!continuous) {
|
|
26
|
+
helpers.close('button_primary');
|
|
27
|
+
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
helpers.next();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleClickSkip = (event: React.MouseEvent<HTMLElement>) => {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
helpers.skip();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getElementsProps = () => {
|
|
40
|
+
const { back, close, last, next, nextLabelWithProgress, skip } = step.locale;
|
|
41
|
+
|
|
42
|
+
const backText = getReactNodeText(back);
|
|
43
|
+
const closeText = getReactNodeText(close);
|
|
44
|
+
const lastText = getReactNodeText(last);
|
|
45
|
+
const nextText = getReactNodeText(next);
|
|
46
|
+
const skipText = getReactNodeText(skip);
|
|
47
|
+
|
|
48
|
+
let primary = close;
|
|
49
|
+
let primaryText = closeText;
|
|
50
|
+
|
|
51
|
+
if (continuous) {
|
|
52
|
+
primary = next;
|
|
53
|
+
primaryText = nextText;
|
|
54
|
+
|
|
55
|
+
if (step.showProgress && !isLastStep) {
|
|
56
|
+
const labelWithProgress = getReactNodeText(nextLabelWithProgress, {
|
|
57
|
+
step: index + 1,
|
|
58
|
+
steps: size,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
primary = replaceLocaleContent(nextLabelWithProgress, index + 1, size);
|
|
62
|
+
primaryText = labelWithProgress;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (isLastStep) {
|
|
66
|
+
primary = last;
|
|
67
|
+
primaryText = lastText;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
backProps: {
|
|
73
|
+
'aria-label': backText,
|
|
74
|
+
children: back,
|
|
75
|
+
'data-action': 'back',
|
|
76
|
+
onClick: handleClickBack,
|
|
77
|
+
role: 'button',
|
|
78
|
+
title: backText,
|
|
79
|
+
},
|
|
80
|
+
closeProps: {
|
|
81
|
+
'aria-label': closeText,
|
|
82
|
+
children: close,
|
|
83
|
+
'data-action': 'close',
|
|
84
|
+
onClick: handleClickClose,
|
|
85
|
+
role: 'button',
|
|
86
|
+
title: closeText,
|
|
87
|
+
},
|
|
88
|
+
primaryProps: {
|
|
89
|
+
'aria-label': primaryText,
|
|
90
|
+
children: primary,
|
|
91
|
+
'data-action': 'primary',
|
|
92
|
+
onClick: handleClickPrimary,
|
|
93
|
+
role: 'button',
|
|
94
|
+
title: primaryText,
|
|
95
|
+
},
|
|
96
|
+
skipProps: {
|
|
97
|
+
'aria-label': skipText,
|
|
98
|
+
children: skip,
|
|
99
|
+
'data-action': 'skip',
|
|
100
|
+
onClick: handleClickSkip,
|
|
101
|
+
role: 'button',
|
|
102
|
+
title: skipText,
|
|
103
|
+
},
|
|
104
|
+
tooltipProps: {
|
|
105
|
+
'aria-modal': true,
|
|
106
|
+
ref: setTooltipRef,
|
|
107
|
+
role: 'alertdialog',
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const { beaconComponent, tooltipComponent, ...cleanStep } = step;
|
|
113
|
+
|
|
114
|
+
if (tooltipComponent) {
|
|
115
|
+
const renderProps = {
|
|
116
|
+
...getElementsProps(),
|
|
117
|
+
continuous,
|
|
118
|
+
index,
|
|
119
|
+
isLastStep,
|
|
120
|
+
size,
|
|
121
|
+
step: cleanStep,
|
|
122
|
+
setTooltipRef,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const TooltipComponent = tooltipComponent;
|
|
126
|
+
|
|
127
|
+
return <TooltipComponent {...renderProps} />;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<Container
|
|
132
|
+
{...getElementsProps()}
|
|
133
|
+
continuous={continuous}
|
|
134
|
+
index={index}
|
|
135
|
+
isLastStep={isLastStep}
|
|
136
|
+
size={size}
|
|
137
|
+
step={step}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import isEqual from '@gilbarbara/deep-equal';
|
|
3
|
+
import is from 'is-lite';
|
|
4
|
+
import treeChanges from 'tree-changes';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
canUseDOM,
|
|
8
|
+
getElement,
|
|
9
|
+
getScrollParent,
|
|
10
|
+
getScrollTo,
|
|
11
|
+
hasCustomScrollParent,
|
|
12
|
+
scrollTo,
|
|
13
|
+
} from '~/modules/dom';
|
|
14
|
+
import { log, shouldScroll } from '~/modules/helpers';
|
|
15
|
+
import { getMergedStep, validateSteps } from '~/modules/step';
|
|
16
|
+
import createStore from '~/modules/store';
|
|
17
|
+
|
|
18
|
+
import { ACTIONS, EVENTS, LIFECYCLE, STATUS } from '~/literals';
|
|
19
|
+
|
|
20
|
+
import Overlay from '~/components/Overlay';
|
|
21
|
+
import Portal from '~/components/Portal';
|
|
22
|
+
|
|
23
|
+
import { Actions, CallBackProps, Props, State, Status, StoreHelpers } from '~/types';
|
|
24
|
+
|
|
25
|
+
import Step from './Step';
|
|
26
|
+
|
|
27
|
+
const defaultPropsValues = {
|
|
28
|
+
continuous: false,
|
|
29
|
+
debug: false,
|
|
30
|
+
disableCloseOnEsc: false,
|
|
31
|
+
disableOverlay: false,
|
|
32
|
+
disableOverlayClose: false,
|
|
33
|
+
disableScrolling: false,
|
|
34
|
+
disableScrollParentFix: false,
|
|
35
|
+
hideBackButton: false,
|
|
36
|
+
run: true,
|
|
37
|
+
scrollOffset: 20,
|
|
38
|
+
scrollDuration: 300,
|
|
39
|
+
scrollToFirstStep: false,
|
|
40
|
+
showSkipButton: false,
|
|
41
|
+
showProgress: false,
|
|
42
|
+
spotlightClicks: false,
|
|
43
|
+
spotlightPadding: 10,
|
|
44
|
+
steps: [],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function Joyride(inputProps: Props) {
|
|
48
|
+
const props = { ...defaultPropsValues, ...inputProps };
|
|
49
|
+
const {
|
|
50
|
+
callback: callbackProp,
|
|
51
|
+
continuous,
|
|
52
|
+
debug,
|
|
53
|
+
disableCloseOnEsc,
|
|
54
|
+
getHelpers,
|
|
55
|
+
nonce,
|
|
56
|
+
run,
|
|
57
|
+
scrollDuration,
|
|
58
|
+
scrollOffset,
|
|
59
|
+
scrollToFirstStep,
|
|
60
|
+
stepIndex,
|
|
61
|
+
steps,
|
|
62
|
+
} = props;
|
|
63
|
+
|
|
64
|
+
const storeRef = useRef(
|
|
65
|
+
createStore({
|
|
66
|
+
...props,
|
|
67
|
+
controlled: run && is.number(stepIndex),
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
const helpersRef = useRef<StoreHelpers>(storeRef.current.getHelpers());
|
|
71
|
+
const [state, setState] = useState<State>(storeRef.current.getState());
|
|
72
|
+
const previousStateRef = useRef<State>(state);
|
|
73
|
+
const previousPropsRef = useRef(props);
|
|
74
|
+
const mountedRef = useRef(false);
|
|
75
|
+
|
|
76
|
+
const store = storeRef.current;
|
|
77
|
+
const helpers = helpersRef.current;
|
|
78
|
+
|
|
79
|
+
const triggerCallback = useCallback(
|
|
80
|
+
(data: CallBackProps) => {
|
|
81
|
+
if (is.function(callbackProp)) {
|
|
82
|
+
callbackProp(data);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
[callbackProp],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const handleKeyboard = useCallback(
|
|
89
|
+
(event: KeyboardEvent) => {
|
|
90
|
+
const { index: currentIndex, lifecycle } = storeRef.current.getState();
|
|
91
|
+
const currentSteps = props.steps;
|
|
92
|
+
const currentStep = currentSteps[currentIndex];
|
|
93
|
+
|
|
94
|
+
if (lifecycle === LIFECYCLE.TOOLTIP) {
|
|
95
|
+
if (event.code === 'Escape' && currentStep && !currentStep.disableCloseOnEsc) {
|
|
96
|
+
storeRef.current.close('keyboard');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
[props.steps],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const handleClickOverlay = useCallback(() => {
|
|
104
|
+
const currentState = storeRef.current.getState();
|
|
105
|
+
const step = getMergedStep(props, steps[currentState.index]);
|
|
106
|
+
|
|
107
|
+
if (!step.disableOverlayClose) {
|
|
108
|
+
helpers.close('overlay');
|
|
109
|
+
}
|
|
110
|
+
}, [props, steps, helpers]);
|
|
111
|
+
|
|
112
|
+
// Set up store listener and keyboard handler
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
store.addListener(setState);
|
|
115
|
+
|
|
116
|
+
log({
|
|
117
|
+
title: 'init',
|
|
118
|
+
data: [
|
|
119
|
+
{ key: 'props', value: props },
|
|
120
|
+
{ key: 'state', value: state },
|
|
121
|
+
],
|
|
122
|
+
debug,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (getHelpers) {
|
|
126
|
+
getHelpers(helpers);
|
|
127
|
+
}
|
|
128
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
129
|
+
|
|
130
|
+
// Mount effect - start tour and add keyboard listener
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!canUseDOM()) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (validateSteps(steps, debug) && run) {
|
|
137
|
+
store.start();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!disableCloseOnEsc) {
|
|
141
|
+
document.body.addEventListener('keydown', handleKeyboard, { passive: true });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
mountedRef.current = true;
|
|
145
|
+
|
|
146
|
+
return () => {
|
|
147
|
+
if (!disableCloseOnEsc) {
|
|
148
|
+
document.body.removeEventListener('keydown', handleKeyboard);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
152
|
+
|
|
153
|
+
// componentDidUpdate for props changes
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!mountedRef.current || !canUseDOM()) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const previousProps = previousPropsRef.current;
|
|
160
|
+
const { changedProps } = treeChanges(previousProps, props) as any;
|
|
161
|
+
const changedPropsFn = (key: string) => {
|
|
162
|
+
const prev = (previousProps as any)[key];
|
|
163
|
+
const curr = (props as any)[key];
|
|
164
|
+
return prev !== curr;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const stepsChanged = !isEqual(previousProps.steps, steps);
|
|
168
|
+
|
|
169
|
+
if (stepsChanged) {
|
|
170
|
+
if (validateSteps(steps, debug)) {
|
|
171
|
+
store.setSteps(steps);
|
|
172
|
+
} else {
|
|
173
|
+
// eslint-disable-next-line no-console
|
|
174
|
+
console.warn('Steps are not valid', steps);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (changedPropsFn('run')) {
|
|
179
|
+
if (run) {
|
|
180
|
+
store.start(stepIndex);
|
|
181
|
+
} else {
|
|
182
|
+
store.stop();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const stepIndexChanged = is.number(stepIndex) && changedPropsFn('stepIndex');
|
|
187
|
+
|
|
188
|
+
if (stepIndexChanged) {
|
|
189
|
+
const currentState = store.getState();
|
|
190
|
+
let nextAction: Actions =
|
|
191
|
+
is.number(previousProps.stepIndex) && previousProps.stepIndex < stepIndex!
|
|
192
|
+
? ACTIONS.NEXT
|
|
193
|
+
: ACTIONS.PREV;
|
|
194
|
+
|
|
195
|
+
if (currentState.action === ACTIONS.STOP) {
|
|
196
|
+
nextAction = ACTIONS.START;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!([STATUS.FINISHED, STATUS.SKIPPED] as Array<Status>).includes(currentState.status)) {
|
|
200
|
+
store.update({
|
|
201
|
+
action: currentState.action === ACTIONS.CLOSE ? ACTIONS.CLOSE : nextAction,
|
|
202
|
+
index: stepIndex,
|
|
203
|
+
lifecycle: LIFECYCLE.INIT,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
previousPropsRef.current = props;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// componentDidUpdate for state changes
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (!mountedRef.current || !canUseDOM()) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const previousState = previousStateRef.current;
|
|
218
|
+
const { action, controlled, index, lifecycle, status } = state;
|
|
219
|
+
const { changed, changedFrom } = treeChanges(previousState, state);
|
|
220
|
+
const step = getMergedStep(props, steps[index]);
|
|
221
|
+
const target = getElement(step.target);
|
|
222
|
+
|
|
223
|
+
// Update the index if the first step is not found
|
|
224
|
+
if (!controlled && status === STATUS.RUNNING && index === 0 && !target) {
|
|
225
|
+
store.update({ index: index + 1 });
|
|
226
|
+
triggerCallback({
|
|
227
|
+
...state,
|
|
228
|
+
type: EVENTS.TARGET_NOT_FOUND,
|
|
229
|
+
step,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const callbackData = {
|
|
234
|
+
...state,
|
|
235
|
+
index,
|
|
236
|
+
step,
|
|
237
|
+
};
|
|
238
|
+
const isAfterAction = changed('action', [
|
|
239
|
+
ACTIONS.NEXT,
|
|
240
|
+
ACTIONS.PREV,
|
|
241
|
+
ACTIONS.SKIP,
|
|
242
|
+
ACTIONS.CLOSE,
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
if (isAfterAction && changed('status', STATUS.PAUSED)) {
|
|
246
|
+
const previousStep = getMergedStep(props, steps[previousState.index]);
|
|
247
|
+
|
|
248
|
+
triggerCallback({
|
|
249
|
+
...callbackData,
|
|
250
|
+
index: previousState.index,
|
|
251
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
252
|
+
step: previousStep,
|
|
253
|
+
type: EVENTS.STEP_AFTER,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (changed('status', [STATUS.FINISHED, STATUS.SKIPPED])) {
|
|
258
|
+
const previousStep = getMergedStep(props, steps[previousState.index]);
|
|
259
|
+
|
|
260
|
+
if (!controlled) {
|
|
261
|
+
triggerCallback({
|
|
262
|
+
...callbackData,
|
|
263
|
+
index: previousState.index,
|
|
264
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
265
|
+
step: previousStep,
|
|
266
|
+
type: EVENTS.STEP_AFTER,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
triggerCallback({
|
|
271
|
+
...callbackData,
|
|
272
|
+
type: EVENTS.TOUR_END,
|
|
273
|
+
// Return the last step when the tour is finished
|
|
274
|
+
step: previousStep,
|
|
275
|
+
index: previousState.index,
|
|
276
|
+
});
|
|
277
|
+
store.reset();
|
|
278
|
+
} else if (changedFrom('status', [STATUS.IDLE, STATUS.READY], STATUS.RUNNING)) {
|
|
279
|
+
triggerCallback({
|
|
280
|
+
...callbackData,
|
|
281
|
+
type: EVENTS.TOUR_START,
|
|
282
|
+
});
|
|
283
|
+
} else if (changed('status') || changed('action', ACTIONS.RESET)) {
|
|
284
|
+
triggerCallback({
|
|
285
|
+
...callbackData,
|
|
286
|
+
type: EVENTS.TOUR_STATUS,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Scroll to step
|
|
291
|
+
scrollToStep(previousState);
|
|
292
|
+
|
|
293
|
+
previousStateRef.current = state;
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
function scrollToStep(previousState: State) {
|
|
297
|
+
const { index, lifecycle, status } = state;
|
|
298
|
+
const {
|
|
299
|
+
disableScrollParentFix = false,
|
|
300
|
+
} = props;
|
|
301
|
+
const step = getMergedStep(props, steps[index]);
|
|
302
|
+
|
|
303
|
+
const target = getElement(step.target);
|
|
304
|
+
const shouldScrollToStep = shouldScroll({
|
|
305
|
+
isFirstStep: index === 0,
|
|
306
|
+
lifecycle,
|
|
307
|
+
previousLifecycle: previousState.lifecycle,
|
|
308
|
+
scrollToFirstStep,
|
|
309
|
+
step,
|
|
310
|
+
target,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
if (status === STATUS.RUNNING && shouldScrollToStep) {
|
|
314
|
+
const hasCustomScroll = hasCustomScrollParent(target, disableScrollParentFix);
|
|
315
|
+
const scrollParent = getScrollParent(target, disableScrollParentFix);
|
|
316
|
+
let scrollY = Math.floor(getScrollTo(target, scrollOffset, disableScrollParentFix)) || 0;
|
|
317
|
+
|
|
318
|
+
log({
|
|
319
|
+
title: 'scrollToStep',
|
|
320
|
+
data: [
|
|
321
|
+
{ key: 'index', value: index },
|
|
322
|
+
{ key: 'lifecycle', value: lifecycle },
|
|
323
|
+
{ key: 'status', value: status },
|
|
324
|
+
],
|
|
325
|
+
debug,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const beaconPopper = store.getPopper('beacon');
|
|
329
|
+
const tooltipPopper = store.getPopper('tooltip');
|
|
330
|
+
|
|
331
|
+
if (lifecycle === LIFECYCLE.BEACON && beaconPopper) {
|
|
332
|
+
const placement = beaconPopper.state?.placement ?? '';
|
|
333
|
+
const popperTop = beaconPopper.state?.rects?.popper?.y ?? 0;
|
|
334
|
+
|
|
335
|
+
if (!['bottom'].includes(placement) && !hasCustomScroll) {
|
|
336
|
+
scrollY = Math.floor(popperTop - scrollOffset);
|
|
337
|
+
}
|
|
338
|
+
} else if (lifecycle === LIFECYCLE.TOOLTIP && tooltipPopper) {
|
|
339
|
+
const placement = tooltipPopper.state?.placement ?? '';
|
|
340
|
+
const popperTop = tooltipPopper.state?.rects?.popper?.y ?? 0;
|
|
341
|
+
const flipped = tooltipPopper.state?.modifiersData?.flip?.overflows != null;
|
|
342
|
+
|
|
343
|
+
if (['top', 'right', 'left'].includes(placement) && !flipped && !hasCustomScroll) {
|
|
344
|
+
scrollY = Math.floor(popperTop - scrollOffset);
|
|
345
|
+
} else {
|
|
346
|
+
scrollY -= step.spotlightPadding;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
scrollY = scrollY >= 0 ? scrollY : 0;
|
|
351
|
+
|
|
352
|
+
if (status === STATUS.RUNNING) {
|
|
353
|
+
scrollTo(scrollY, { element: scrollParent as Element, duration: scrollDuration }).then(
|
|
354
|
+
() => {
|
|
355
|
+
setTimeout(() => {
|
|
356
|
+
store.getPopper('tooltip')?.update();
|
|
357
|
+
}, 10);
|
|
358
|
+
},
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (!canUseDOM()) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const { index, lifecycle, status } = state;
|
|
369
|
+
const isRunning = status === STATUS.RUNNING;
|
|
370
|
+
const content: Record<string, ReactNode> = {};
|
|
371
|
+
|
|
372
|
+
if (isRunning && steps[index]) {
|
|
373
|
+
const step = getMergedStep(props, steps[index]);
|
|
374
|
+
|
|
375
|
+
content.step = (
|
|
376
|
+
<Step
|
|
377
|
+
{...state}
|
|
378
|
+
callback={triggerCallback}
|
|
379
|
+
continuous={continuous}
|
|
380
|
+
debug={debug}
|
|
381
|
+
helpers={helpers}
|
|
382
|
+
nonce={nonce}
|
|
383
|
+
shouldScroll={!step.disableScrolling && (index !== 0 || scrollToFirstStep)}
|
|
384
|
+
step={step}
|
|
385
|
+
store={store}
|
|
386
|
+
/>
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
content.overlay = (
|
|
390
|
+
<Portal id="react-joyride-portal">
|
|
391
|
+
<Overlay
|
|
392
|
+
{...step}
|
|
393
|
+
continuous={continuous}
|
|
394
|
+
debug={debug}
|
|
395
|
+
lifecycle={lifecycle}
|
|
396
|
+
onClickOverlay={handleClickOverlay}
|
|
397
|
+
/>
|
|
398
|
+
</Portal>
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
<div className="react-joyride">
|
|
404
|
+
{content.step}
|
|
405
|
+
{content.overlay}
|
|
406
|
+
</div>
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export default Joyride;
|
package/src/defaults.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { noop } from '~/modules/helpers';
|
|
2
|
+
|
|
3
|
+
import { Locale, Props, Step } from '~/types';
|
|
4
|
+
|
|
5
|
+
export const defaultFloaterProps = {
|
|
6
|
+
wrapperOptions: {
|
|
7
|
+
offset: -18,
|
|
8
|
+
position: true,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const defaultLocale: Locale = {
|
|
13
|
+
back: 'Back',
|
|
14
|
+
close: 'Close',
|
|
15
|
+
last: 'Last',
|
|
16
|
+
next: 'Next',
|
|
17
|
+
nextLabelWithProgress: 'Next (Step {step} of {steps})',
|
|
18
|
+
open: 'Open the dialog',
|
|
19
|
+
skip: 'Skip',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const defaultStep = {
|
|
23
|
+
event: 'click',
|
|
24
|
+
placement: 'bottom',
|
|
25
|
+
offset: 10,
|
|
26
|
+
disableBeacon: false,
|
|
27
|
+
disableCloseOnEsc: false,
|
|
28
|
+
disableOverlay: false,
|
|
29
|
+
disableOverlayClose: false,
|
|
30
|
+
disableScrollParentFix: false,
|
|
31
|
+
disableScrolling: false,
|
|
32
|
+
hideBackButton: false,
|
|
33
|
+
hideCloseButton: false,
|
|
34
|
+
hideFooter: false,
|
|
35
|
+
isFixed: false,
|
|
36
|
+
locale: defaultLocale,
|
|
37
|
+
showProgress: false,
|
|
38
|
+
showSkipButton: false,
|
|
39
|
+
spotlightClicks: false,
|
|
40
|
+
spotlightPadding: 10,
|
|
41
|
+
} satisfies Omit<Step, 'content' | 'target'>;
|
|
42
|
+
|
|
43
|
+
export const defaultProps = {
|
|
44
|
+
continuous: false,
|
|
45
|
+
debug: false,
|
|
46
|
+
disableCloseOnEsc: false,
|
|
47
|
+
disableOverlay: false,
|
|
48
|
+
disableOverlayClose: false,
|
|
49
|
+
disableScrolling: false,
|
|
50
|
+
disableScrollParentFix: false,
|
|
51
|
+
getHelpers: noop(),
|
|
52
|
+
hideBackButton: false,
|
|
53
|
+
run: true,
|
|
54
|
+
scrollOffset: 20,
|
|
55
|
+
scrollDuration: 300,
|
|
56
|
+
scrollToFirstStep: false,
|
|
57
|
+
showSkipButton: false,
|
|
58
|
+
showProgress: false,
|
|
59
|
+
spotlightClicks: false,
|
|
60
|
+
spotlightPadding: 10,
|
|
61
|
+
steps: [],
|
|
62
|
+
} satisfies Props;
|
package/src/global.d.ts
ADDED
package/src/index.tsx
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const ACTIONS = {
|
|
2
|
+
INIT: 'init',
|
|
3
|
+
START: 'start',
|
|
4
|
+
STOP: 'stop',
|
|
5
|
+
RESET: 'reset',
|
|
6
|
+
PREV: 'prev',
|
|
7
|
+
NEXT: 'next',
|
|
8
|
+
GO: 'go',
|
|
9
|
+
CLOSE: 'close',
|
|
10
|
+
SKIP: 'skip',
|
|
11
|
+
UPDATE: 'update',
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const EVENTS = {
|
|
15
|
+
TOUR_START: 'tour:start',
|
|
16
|
+
STEP_BEFORE: 'step:before',
|
|
17
|
+
BEACON: 'beacon',
|
|
18
|
+
TOOLTIP: 'tooltip',
|
|
19
|
+
STEP_AFTER: 'step:after',
|
|
20
|
+
TOUR_END: 'tour:end',
|
|
21
|
+
TOUR_STATUS: 'tour:status',
|
|
22
|
+
TARGET_NOT_FOUND: 'error:target_not_found',
|
|
23
|
+
ERROR: 'error',
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export const LIFECYCLE = {
|
|
27
|
+
INIT: 'init',
|
|
28
|
+
READY: 'ready',
|
|
29
|
+
BEACON: 'beacon',
|
|
30
|
+
TOOLTIP: 'tooltip',
|
|
31
|
+
COMPLETE: 'complete',
|
|
32
|
+
ERROR: 'error',
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
export const ORIGIN = {
|
|
36
|
+
BUTTON_CLOSE: 'button_close',
|
|
37
|
+
BUTTON_PRIMARY: 'button_primary',
|
|
38
|
+
KEYBOARD: 'keyboard',
|
|
39
|
+
OVERLAY: 'overlay',
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
export const STATUS = {
|
|
43
|
+
IDLE: 'idle',
|
|
44
|
+
READY: 'ready',
|
|
45
|
+
WAITING: 'waiting',
|
|
46
|
+
RUNNING: 'running',
|
|
47
|
+
PAUSED: 'paused',
|
|
48
|
+
SKIPPED: 'skipped',
|
|
49
|
+
FINISHED: 'finished',
|
|
50
|
+
ERROR: 'error',
|
|
51
|
+
} as const;
|