UNPKG

@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
'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