@gfazioli/mantine-onboarding-tour
Version:
A Mantine 9 onboarding tour component with focus-reveal overlays, cutout highlights, step-by-step popover navigation, and compound components for guided user experiences.
182 lines (179 loc) • 6.95 kB
JavaScript
'use client';
import { factory, useProps, useStyles, useResolvedStylesApi, Box, rgba } from '@mantine/core';
import React, { useEffect } from 'react';
import { useCutoutRect, buildCutoutPath } from './hooks/use-cutout-rect/use-cutout-rect.mjs';
import { useOnboardingTour } from './hooks/use-onboarding-tour/use-onboarding-tour.mjs';
import { _OnboardingTourProvider } from './OnboardingTour.context.mjs';
import { OnboardingTourFocusReveal } from './OnboardingTourFocusReveal/OnboardingTourFocusReveal.mjs';
import { OnboardingTourPopoverContent } from './OnboardingTourPopoverContent/OnboardingTourPopoverContent.mjs';
import { OnboardingTourTarget } from './OnboardingTourTarget/OnboardingTourTarget.mjs';
import classes from './OnboardingTour.module.css.mjs';
const DEFAULT_CUTOUT_PADDING = 8;
const DEFAULT_CUTOUT_RADIUS = 8;
const defaultProps = {};
const OnboardingTour = factory((_props) => {
const props = useProps("OnboardingTour", defaultProps, _props);
const {
tour,
started,
loop,
focusRevealProps: _focusRevealProps,
cutoutPadding: _cutoutPadding,
cutoutRadius: _cutoutRadius,
onOnboardingTourStart,
onOnboardingTourEnd,
onOnboardingTourComplete,
onOnboardingTourSkip,
onOnboardingTourChange,
classNames,
styles,
unstyled,
children,
...others
} = props;
const getStyles = useStyles({
name: "OnboardingTour",
classes,
props,
classNames,
styles,
unstyled
});
const onboardingTour = useOnboardingTour(tour, {
loop,
onOnboardingTourStart,
onOnboardingTourEnd,
onOnboardingTourComplete,
onOnboardingTourSkip,
onOnboardingTourChange
});
const focusRevealProps = _focusRevealProps ? typeof _focusRevealProps === "function" ? _focusRevealProps(onboardingTour) : _focusRevealProps : {};
const value = {
...onboardingTour,
...others,
focusRevealProps,
getStyles,
unstyled
};
const { resolvedClassNames, resolvedStyles } = useResolvedStylesApi({
classNames,
styles,
props
});
const { selectedStepId: selectedTourId, startTour } = onboardingTour;
useEffect(() => {
if (started) {
startTour();
}
}, [started]);
const currentStepFocusRevealProps = (() => {
const stepProps = onboardingTour.currentStep?.focusRevealProps;
if (!stepProps) {
return void 0;
}
return typeof stepProps === "function" ? stepProps(onboardingTour) : stepProps;
})();
const overlayColor = currentStepFocusRevealProps?.overlayProps?.color ?? focusRevealProps?.overlayProps?.color ?? "#000";
const overlayOpacity = currentStepFocusRevealProps?.overlayProps?.backgroundOpacity ?? focusRevealProps?.overlayProps?.backgroundOpacity ?? 0.5;
const overlayBlur = currentStepFocusRevealProps?.overlayProps?.blur ?? focusRevealProps?.overlayProps?.blur ?? 2;
const overlayZIndex = currentStepFocusRevealProps?.overlayProps?.zIndex ?? focusRevealProps?.overlayProps?.zIndex ?? 200;
const isTourActive = started && onboardingTour.currentStepIndex !== void 0;
const cutoutState = useCutoutRect(isTourActive, selectedTourId);
useEffect(() => {
if (isTourActive) {
const prev = document.documentElement.style.overflowX;
document.documentElement.style.overflowX = "hidden";
return () => {
document.documentElement.style.overflowX = prev;
};
}
return void 0;
}, [isTourActive]);
const wrapChildren = (children2) => {
if (!started || !selectedTourId) {
return children2;
}
return React.Children.map(children2, (child) => {
if (React.isValidElement(child)) {
const childProps = child.props;
const tourId = childProps["data-onboarding-tour-id"];
if (tourId) {
const mergedFocusRevealProps = {
...focusRevealProps,
...typeof onboardingTour.currentStep?.focusRevealProps === "function" ? onboardingTour.currentStep.focusRevealProps(onboardingTour) : onboardingTour.currentStep?.focusRevealProps
};
return /* @__PURE__ */ React.createElement(
OnboardingTourFocusReveal,
{
...mergedFocusRevealProps,
withOverlay: false,
popoverProps: {
...mergedFocusRevealProps.popoverProps,
withinPortal: true
},
classNames: resolvedClassNames,
key: `onboarding-tour-${tourId}`,
popoverContent: /* @__PURE__ */ React.createElement(
OnboardingTour.PopoverContent,
{
classNames: resolvedClassNames,
styles: resolvedStyles,
unstyled,
...others,
tourController: onboardingTour,
key: `onboarding-tour-content-${tourId}`
}
),
focused: tourId === selectedTourId,
transitionProps: { duration: 0, exitDuration: 0 }
},
React.cloneElement(child)
);
}
if (childProps.children) {
return React.cloneElement(child, {
children: wrapChildren(childProps.children)
});
}
}
return child;
});
};
const rawCutoutPadding = onboardingTour.currentStep?.cutoutPadding ?? _cutoutPadding ?? DEFAULT_CUTOUT_PADDING;
const rawCutoutRadius = onboardingTour.currentStep?.cutoutRadius ?? _cutoutRadius ?? DEFAULT_CUTOUT_RADIUS;
const resolvedCutoutPadding = Number.isFinite(rawCutoutPadding) ? Math.max(0, rawCutoutPadding) : DEFAULT_CUTOUT_PADDING;
const resolvedCutoutRadius = Number.isFinite(rawCutoutRadius) ? Math.max(0, rawCutoutRadius) : DEFAULT_CUTOUT_RADIUS;
const cssClipPath = cutoutState ? `path(evenodd, "${buildCutoutPath(
cutoutState.vw,
cutoutState.vh,
cutoutState.rect,
resolvedCutoutPadding,
resolvedCutoutRadius
)}")` : void 0;
return /* @__PURE__ */ React.createElement(Box, null, isTourActive && /* @__PURE__ */ React.createElement(
Box,
{
"data-onboarding-tour-overlay": true,
className: classes.tourOverlay,
style: {
backgroundColor: rgba(overlayColor, overlayOpacity),
...Number(overlayBlur) > 0 && {
backdropFilter: `blur(${overlayBlur}px)`,
WebkitBackdropFilter: `blur(${overlayBlur}px)`
},
...cssClipPath && {
clipPath: cssClipPath,
WebkitClipPath: cssClipPath
},
zIndex: overlayZIndex
}
}
), /* @__PURE__ */ React.createElement(_OnboardingTourProvider, { value }, wrapChildren(children)));
});
OnboardingTour.displayName = "OnboardingTour";
OnboardingTour.classes = classes;
OnboardingTour.FocusReveal = OnboardingTourFocusReveal;
OnboardingTour.PopoverContent = OnboardingTourPopoverContent;
OnboardingTour.Target = OnboardingTourTarget;
export { OnboardingTour, defaultProps };
//# sourceMappingURL=OnboardingTour.mjs.map