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.

228 lines (224 loc) 7.46 kB
'use client'; 'use strict'; var core = require('@mantine/core'); var React = require('react'); var hooks = require('@mantine/hooks'); var useScrollIntoView = require('../hooks/use-scroll-into-view/use-scroll-into-view.cjs'); var OnboardingTourFocusRevealGroup = require('../OnboardingTourFocusRevealGroup/OnboardingTourFocusRevealGroup.cjs'); var OnboardingTourFocusRevealGroup_context = require('../OnboardingTourFocusRevealGroup/OnboardingTourFocusRevealGroup.context.cjs'); var OnboardingTourFocusReveal_module = require('./OnboardingTourFocusReveal.module.css.cjs'); function isResponsiveObject(value) { if (typeof value !== "object" || value === null || Array.isArray(value)) { return false; } const keys = Object.keys(value); const breakpoints = ["base", "xs", "sm", "md", "lg", "xl"]; return keys.length > 0 && keys.every((k) => breakpoints.includes(k)); } function useResponsiveProp(value, defaultValue) { const matchesInput = isResponsiveObject(value) ? value : { base: value ?? defaultValue }; return core.useMatches(matchesInput); } const defaultProps = { focusedMode: "none", withReveal: true, withOverlay: true, focusedZIndex: 201, overlayProps: { blur: 2, backgroundOpacity: 0.5, color: "black" }, transitionProps: { transition: "fade", duration: 150 }, popoverProps: { position: { base: "bottom", sm: "left" }, withArrow: true, arrowSize: 16, arrowRadius: 4, offset: -4, radius: "md", shadow: "xl", middlewares: { shift: { padding: 20 }, flip: true } } }; function OnboardingTourFocusReveal(_props) { const ctx = OnboardingTourFocusRevealGroup_context.useOnboardingTourFocusRevealGroupContext(); const props = core.useProps( "OnboardingTourFocusReveal", { ...defaultProps, ...ctx ? ctx : {} }, _props ); props.overlayProps = { ...defaultProps.overlayProps, ..._props.overlayProps }; props.transitionProps = { ...defaultProps.transitionProps, ..._props.transitionProps }; props.popoverProps = { ...defaultProps.popoverProps, ..._props.popoverProps }; props.defaultFocused = ctx && _props.defaultFocused === void 0 ? true : _props.defaultFocused; const { focused, defaultFocused, disableTargetInteraction, withOverlay, overlayProps, withReveal, revealProps, transitionProps, focusedMode, popoverContent, popoverProps, focusedZIndex, scrollableRef, onChange, onFocus, onBlur, onRevealFinish, classNames, style, styles, unstyled, vars, children, className } = props; const getStyles = core.useStyles({ name: "OnboardingTourFocusReveal", props, classes: OnboardingTourFocusReveal_module, className, style, classNames, styles, unstyled, vars }); const uuid = React.useId(); const [_focused, setFocused, controlled] = hooks.useUncontrolled({ value: focused, defaultValue: defaultFocused, finalValue: false, onChange }); const blur = () => { setFocused(false); _focused && onBlur?.(); }; const focus = () => { setFocused(true); !_focused && onFocus?.(); }; const [realFocused, setRealFocused] = React.useState(false); const fallbackRef = React.useRef(null); const { scrollIntoView, targetRef } = useScrollIntoView.useScrollIntoView({ ...revealProps, onScrollFinish: onRevealFinish, scrollableRef: scrollableRef || fallbackRef }); const { ref: inViewportRef, inViewport } = hooks.useInViewport(); const mergedRef = hooks.useMergedRef(inViewportRef, targetRef); const resolvedPosition = useResponsiveProp(popoverProps?.position, "left"); const resolvedOffset = useResponsiveProp(popoverProps?.offset, -4); const resolvedWidth = useResponsiveProp(popoverProps?.width, "max-content"); const resolvedArrowSize = useResponsiveProp(popoverProps?.arrowSize, 16); React.useEffect(() => { ctx?.setMeInViewport?.(uuid, inViewport); if (targetRef.current) { if (_focused || defaultFocused) { if (withReveal) { scrollIntoView({ alignment: "center" }); } } if (_focused && inViewport && !realFocused) { setRealFocused(true); setFocused(_focused && inViewport); } else if (!_focused && realFocused) { setRealFocused(false); } } }, [_focused, withReveal, inViewport]); hooks.useDidUpdate(() => { ctx?.setMeInViewport?.(uuid, inViewport); if (_focused && !inViewport) { blur(); setRealFocused(false); ctx?.setRequestOverlay?.(false); } if (inViewport && !controlled && defaultFocused === true) { focus(); setRealFocused(true); ctx?.setRequestOverlay?.(true); } }, [inViewport, defaultFocused]); const clonedChildren = React.useMemo(() => { const child = children; const newProps = { ...realFocused && { "data-onboarding-tour-focus-reveal-focused": true }, "data-onboarding-tour-focus-reveal-mode": focusedMode, ref: mergedRef, className: [realFocused ? OnboardingTourFocusReveal_module.focused : "", className, child.props.className].filter(Boolean).join(" "), style: { position: "relative", ...child.props.style, zIndex: realFocused ? focusedZIndex ?? 201 : 0, pointerEvents: realFocused && disableTargetInteraction ? "none" : void 0 } }; newProps["data-popover-dropdown"] = !!popoverContent; const { position: _pos, offset: _off, width: _w, arrowSize: _as, ...restPopoverProps } = popoverProps || {}; const finalPopoverProps = { ...restPopoverProps, position: resolvedPosition, offset: resolvedOffset, width: resolvedWidth, arrowSize: resolvedArrowSize }; return /* @__PURE__ */ React.createElement(core.Popover, { opened: realFocused && !!popoverContent, ...finalPopoverProps }, /* @__PURE__ */ React.createElement(core.Popover.Target, null, React.cloneElement(child, newProps)), /* @__PURE__ */ React.createElement(core.Popover.Dropdown, null, popoverContent)); }, [ children, realFocused, _focused, inViewport, defaultFocused, resolvedPosition, resolvedOffset, resolvedWidth, resolvedArrowSize, focusedZIndex, popoverProps ]); if (ctx) { return clonedChildren; } return /* @__PURE__ */ React.createElement(React.Fragment, null, withOverlay && /* @__PURE__ */ React.createElement( core.Transition, { transition: transitionProps?.transition, mounted: _focused && inViewport, duration: transitionProps?.duration, exitDuration: transitionProps?.exitDuration }, (transitionStyles) => /* @__PURE__ */ React.createElement( core.Overlay, { "data-onboarding-tour-focus-reveal-overlay": true, ...overlayProps, ...getStyles("overlay", { className: overlayProps?.className, style: [transitionStyles, overlayProps?.style] }) } ) ), clonedChildren); } OnboardingTourFocusReveal.classes = OnboardingTourFocusReveal_module; OnboardingTourFocusReveal.displayName = "OnboardingTourFocusReveal"; OnboardingTourFocusReveal.Group = OnboardingTourFocusRevealGroup.OnboardingTourFocusRevealGroup; exports.OnboardingTourFocusReveal = OnboardingTourFocusReveal; exports.defaultProps = defaultProps; //# sourceMappingURL=OnboardingTourFocusReveal.cjs.map