Skip to content

Commit

Permalink
Refactor Overlay
Browse files Browse the repository at this point in the history
- render it outside the step to avoid flickering
- update getDocumentHeight to prevent cumulative height
  • Loading branch information
gilbarbara committed Mar 14, 2024
1 parent 9656c56 commit 6b3869c
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 55 deletions.
61 changes: 38 additions & 23 deletions src/components/Overlay.tsx
Expand Up @@ -14,7 +14,7 @@ import { getBrowser, isLegacy, log } from '~/modules/helpers';

import { LIFECYCLE } from '~/literals';

import { OverlayProps } from '~/types';
import { Lifecycle, OverlayProps } from '~/types';

import Spotlight from './Spotlight';

Expand Down Expand Up @@ -101,6 +101,35 @@ export default class JoyrideOverlay extends React.Component<OverlayProps, State>
this.scrollParent?.removeEventListener('scroll', this.handleScroll);
}

hideSpotlight = () => {
const { continuous, disableOverlay, lifecycle } = this.props;
const hiddenLifecycles = [LIFECYCLE.BEACON, LIFECYCLE.COMPLETE, LIFECYCLE.ERROR] as Lifecycle[];

return (
disableOverlay ||
(continuous ? hiddenLifecycles.includes(lifecycle) : lifecycle !== LIFECYCLE.TOOLTIP)
);
};

get overlayStyles() {
const { mouseOverSpotlight } = this.state;
const { disableOverlayClose, placement, styles } = this.props;

let baseStyles = styles.overlay;

/* istanbul ignore else */
if (isLegacy()) {
baseStyles = placement === 'center' ? styles.overlayLegacyCenter : styles.overlayLegacy;
}

return {
cursor: disableOverlayClose ? 'default' : 'pointer',
height: getDocumentHeight(),
pointerEvents: mouseOverSpotlight ? 'none' : 'auto',
...baseStyles,
} as React.CSSProperties;
}

get spotlightStyles(): SpotlightStyles {
const { showSpotlight } = this.state;
const {
Expand Down Expand Up @@ -185,38 +214,24 @@ export default class JoyrideOverlay extends React.Component<OverlayProps, State>
}

render() {
const { mouseOverSpotlight, showSpotlight } = this.state;
const { disableOverlay, disableOverlayClose, lifecycle, onClickOverlay, placement, styles } =
this.props;
const { showSpotlight } = this.state;
const { onClickOverlay, placement } = this.props;
const { hideSpotlight, overlayStyles, spotlightStyles } = this;

if (disableOverlay || lifecycle !== LIFECYCLE.TOOLTIP) {
if (hideSpotlight()) {
return null;
}

let baseStyles = styles.overlay;

/* istanbul ignore else */
if (isLegacy()) {
baseStyles = placement === 'center' ? styles.overlayLegacyCenter : styles.overlayLegacy;
}

const stylesOverlay: React.CSSProperties = {
cursor: disableOverlayClose ? 'default' : 'pointer',
height: getDocumentHeight(),
pointerEvents: mouseOverSpotlight ? 'none' : 'auto',
...baseStyles,
};

let spotlight = placement !== 'center' && showSpotlight && (
<Spotlight styles={this.spotlightStyles} />
<Spotlight styles={spotlightStyles} />
);

// Hack for Safari bug with mix-blend-mode with z-index
if (getBrowser() === 'safari') {
const { mixBlendMode, zIndex, ...safarOverlay } = stylesOverlay;
const { mixBlendMode, zIndex, ...safarOverlay } = overlayStyles;

spotlight = <div style={{ ...safarOverlay }}>{spotlight}</div>;
delete stylesOverlay.backgroundColor;
delete overlayStyles.backgroundColor;
}

return (
Expand All @@ -225,7 +240,7 @@ export default class JoyrideOverlay extends React.Component<OverlayProps, State>
data-test-id="overlay"
onClick={onClickOverlay}
role="presentation"
style={stylesOverlay}
style={overlayStyles}
>
{spotlight}
</div>
Expand Down
20 changes: 1 addition & 19 deletions src/components/Step.tsx
Expand Up @@ -13,8 +13,6 @@ import { ACTIONS, EVENTS, LIFECYCLE, STATUS } from '~/literals';
import { StepProps } from '~/types';

import Beacon from './Beacon';
import Overlay from './Overlay';
import Portal from './Portal';
import Tooltip from './Tooltip/index';

export default class JoyrideStep extends React.Component<StepProps> {
Expand Down Expand Up @@ -171,14 +169,6 @@ export default class JoyrideStep extends React.Component<StepProps> {
store.update({ lifecycle: LIFECYCLE.TOOLTIP });
};

handleClickOverlay = () => {
const { helpers, step } = this.props;

if (!step.disableOverlayClose) {
helpers.close('overlay');
}
};

setTooltipRef = (element: HTMLElement) => {
this.tooltip = element;
};
Expand Down Expand Up @@ -228,7 +218,7 @@ export default class JoyrideStep extends React.Component<StepProps> {
};

render() {
const { continuous, debug, index, lifecycle, nonce, shouldScroll, size, step } = this.props;
const { continuous, debug, index, nonce, shouldScroll, size, step } = this.props;
const target = getElement(step.target);

if (!validateStep(step) || !is.domElement(target)) {
Expand All @@ -237,14 +227,6 @@ export default class JoyrideStep extends React.Component<StepProps> {

return (
<div key={`JoyrideStep-${index}`} className="react-joyride__step">
<Portal id="react-joyride-portal">
<Overlay
{...step}
debug={debug}
lifecycle={lifecycle}
onClickOverlay={this.handleClickOverlay}
/>
</Portal>
<Floater
{...step.floaterProps}
component={this.renderTooltip}
Expand Down
43 changes: 38 additions & 5 deletions src/components/index.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
import { ReactNode } from 'react';
import isEqual from '@gilbarbara/deep-equal';
import is from 'is-lite';
import treeChanges from 'tree-changes';
Expand All @@ -17,6 +18,9 @@ import createStore from '~/modules/store';

import { ACTIONS, EVENTS, LIFECYCLE, STATUS } from '~/literals';

import Overlay from '~/components/Overlay';
import Portal from '~/components/Portal';

import { defaultProps } from '~/defaults';
import { Actions, CallBackProps, Props, State, Status, StoreHelpers } from '~/types';

Expand Down Expand Up @@ -241,6 +245,17 @@ class Joyride extends React.Component<Props, State> {
}
};

handleClickOverlay = () => {
const { index } = this.state;
const { steps } = this.props;

const step = getMergedStep(this.props, steps[index]);

if (!step.disableOverlayClose) {
this.helpers.close('overlay');
}
};

/**
* Sync the store with the component's state
*/
Expand Down Expand Up @@ -326,20 +341,21 @@ class Joyride extends React.Component<Props, State> {
return null;
}

const { index, status } = this.state;
const { index, lifecycle, status } = this.state;
const {
continuous = false,
debug = false,
nonce,
scrollToFirstStep = false,
steps,
} = this.props;
let output;
const isRunning = status === STATUS.RUNNING;
const content: Record<string, ReactNode> = {};

if (status === STATUS.RUNNING && steps[index]) {
if (isRunning && steps[index]) {
const step = getMergedStep(this.props, steps[index]);

output = (
content.step = (
<Step
{...this.state}
callback={this.callback}
Expand All @@ -352,9 +368,26 @@ class Joyride extends React.Component<Props, State> {
store={this.store}
/>
);

content.overlay = (
<Portal id="react-joyride-portal">
<Overlay
{...step}
continuous={continuous}
debug={debug}
lifecycle={lifecycle}
onClickOverlay={this.handleClickOverlay}
/>
</Portal>
);
}

return <div className="react-joyride">{output}</div>;
return (
<div className="react-joyride">
{content.step}
{content.overlay}
</div>
);
}
}

Expand Down
19 changes: 18 additions & 1 deletion src/modules/dom.ts
Expand Up @@ -19,13 +19,30 @@ export function getClientRect(element: HTMLElement | null) {
/**
* Helper function to get the browser-normalized "document height"
*/
export function getDocumentHeight(): number {
export function getDocumentHeight(median = true): number {
const { body, documentElement } = document;

if (!body || !documentElement) {
return 0;
}

if (median) {
const heights = [
body.scrollHeight,
body.offsetHeight,
documentElement.clientHeight,
documentElement.scrollHeight,
documentElement.offsetHeight,
].sort((a, b) => a - b);
const middle = Math.floor(heights.length / 2);

if (heights.length % 2 === 0) {
return (heights[middle - 1] + heights[middle]) / 2;
}

return heights[middle];
}

return Math.max(
body.scrollHeight,
body.offsetHeight,
Expand Down
5 changes: 3 additions & 2 deletions src/types/components.ts
@@ -1,6 +1,6 @@
import { ElementType, MouseEventHandler, ReactNode, RefCallback } from 'react';
import { Props as FloaterProps } from 'react-floater';
import { PartialDeep, SetRequired, Simplify, ValueOf } from 'type-fest';
import { PartialDeep, SetRequired, Simplify } from 'type-fest';

import type { StoreInstance } from '~/modules/store';

Expand Down Expand Up @@ -142,8 +142,9 @@ export type CallBackProps = {

export type OverlayProps = Simplify<
StepMerged & {
continuous: boolean;
debug: boolean;
lifecycle: ValueOf<Lifecycle>;
lifecycle: Lifecycle;
onClickOverlay: () => void;
}
>;
Expand Down
5 changes: 5 additions & 0 deletions test/__setup__/setupFiles.ts
Expand Up @@ -21,6 +21,11 @@ if (typeof window !== 'undefined') {
value: 50,
});

Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
configurable: true,
value: 50,
});

Object.defineProperty(window, 'matchMedia', {
writable: true,
value: (query: any) => ({
Expand Down

0 comments on commit 6b3869c

Please sign in to comment.