@gfazioli/mantine-onboarding-tour
Version:
A Mantine component enables you to create a onboarding-tour effect using overlays, popovers, and onboarding tours, which enhances element visibility and interactivity.
178 lines (175 loc) • 5.75 kB
JavaScript
'use client';
import React, { useId, useState, useRef, useEffect, useMemo, cloneElement } from 'react';
import { useProps, useStyles, Popover, Transition, Overlay } from '@mantine/core';
import { useUncontrolled, useInViewport, useMergedRef, useDidUpdate } from '@mantine/hooks';
import { useScrollIntoView } from '../hooks/use-scroll-into-view/use-scroll-into-view.mjs';
import { OnboardingTourFocusRevealGroup } from '../OnboardingTourFocusRevealGroup/OnboardingTourFocusRevealGroup.mjs';
import { useOnboardingTourFocusRevealGroupContext } from '../OnboardingTourFocusRevealGroup/OnboardingTourFocusRevealGroup.context.mjs';
import classes from './OnboardingTourFocusReveal.module.css.mjs';
const defaultProps = {
focusedMode: "none",
withReveal: true,
withOverlay: true,
overlayProps: {
blur: 2,
backgroundOpacity: 0.5,
color: "black"
},
transitionProps: { transition: "fade", duration: 150 },
popoverProps: {
position: "left",
withArrow: true,
arrowSize: 16,
arrowRadius: 4,
offset: -4,
radius: "md",
shadow: "xl"
}
};
function OnboardingTourFocusReveal(_props) {
const ctx = useOnboardingTourFocusRevealGroupContext();
const props = 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,
withOverlay,
overlayProps,
withReveal,
revealProps,
transitionProps,
focusedMode,
popoverContent,
popoverProps,
scrollableRef,
onChange,
onFocus,
onBlur,
onRevealFinish,
classNames,
style,
styles,
unstyled,
vars,
children,
className
} = props;
const getStyles = useStyles({
name: "OnboardingTourFocusReveal",
props,
classes,
className,
style,
classNames,
styles,
unstyled,
vars
});
const uuid = useId();
const [_focused, setFocused, controlled] = useUncontrolled({
value: focused,
defaultValue: defaultFocused,
finalValue: false,
onChange
});
const blur = () => {
setFocused(false);
_focused && onBlur?.();
};
const focus = () => {
setFocused(true);
!_focused && onFocus?.();
};
const [realFocused, setRealFocused] = useState(false);
const fallbackRef = useRef(null);
const { scrollIntoView, targetRef } = useScrollIntoView({
...revealProps,
onScrollFinish: onRevealFinish,
scrollableRef: scrollableRef || fallbackRef
});
const { ref: inViewportRef, inViewport } = useInViewport();
const mergedRef = useMergedRef(inViewportRef, targetRef);
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]);
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 = 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 ? classes.focused : "", className, child.props.className].filter(Boolean).join(" "),
style: {
position: "relative",
...child.props.style,
zIndex: realFocused ? 201 : 0
}
};
newProps["data-popover-dropdown"] = !!popoverContent;
return /* @__PURE__ */ React.createElement(Popover, { opened: realFocused && !!popoverContent, withinPortal: false, ...popoverProps }, /* @__PURE__ */ React.createElement(Popover.Target, null, cloneElement(child, newProps)), /* @__PURE__ */ React.createElement(Popover.Dropdown, null, popoverContent));
}, [children, realFocused, _focused, inViewport, defaultFocused]);
if (ctx) {
return clonedChildren;
}
return /* @__PURE__ */ React.createElement(React.Fragment, null, withOverlay && /* @__PURE__ */ React.createElement(
Transition,
{
transition: transitionProps?.transition,
mounted: _focused && inViewport,
duration: transitionProps?.duration,
exitDuration: transitionProps?.exitDuration
},
(transitionStyles) => /* @__PURE__ */ React.createElement(
Overlay,
{
"data-onboarding-tour-focus-reveal-overlay": true,
...overlayProps,
...getStyles("overlay", {
className: overlayProps?.className,
style: [transitionStyles, overlayProps?.style]
})
}
)
), clonedChildren);
}
OnboardingTourFocusReveal.classes = classes;
OnboardingTourFocusReveal.displayName = "OnboardingTourFocusReveal";
OnboardingTourFocusReveal.Group = OnboardingTourFocusRevealGroup;
export { OnboardingTourFocusReveal, defaultProps };
//# sourceMappingURL=OnboardingTourFocusReveal.mjs.map