UNPKG

manuthecoder-react-native-spotlight-tour

Version:

React Native library to implement a highly customizable app tour feature with an awesome spotlight effect

143 lines 7.34 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { arrow, flip, offset, shift, useFloating, } from "@floating-ui/react-native"; import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, } from "react"; import { Animated, Modal, Platform, View, } from "react-native"; import { Defs, Mask, Rect, Svg } from "react-native-svg"; import { vh, vw } from "../../../helpers/responsive"; import { SpotlightTourContext, } from "../../SpotlightTour.context"; import { Css, DEFAULT_ARROW, arrowCss } from "./TourOverlay.styles"; import { CircleShape } from "./shapes/CircleShape.component"; import { RectShape } from "./shapes/RectShape.component"; export const TourOverlay = forwardRef((props, ref) => { const { backdropOpacity, color, current, motion, nativeDriver, onBackdropPress, shape, spot, tourStep, ...tooltipProps } = props; const { goTo, next, pause, previous, resume, start, steps, stop } = useContext(SpotlightTourContext); const arrowRef = useRef(null); const floating = useMemo(() => ({ arrow: tourStep.arrow ?? tooltipProps.arrow, flip: tourStep.flip ?? tooltipProps.flip, offset: tourStep.offset ?? tooltipProps.offset, placement: tourStep.placement ?? tooltipProps.placement, shift: tourStep.shift ?? tooltipProps.shift, }), [tooltipProps, tourStep.arrow, tourStep.flip, tourStep.offset, tourStep.placement, tourStep.shift]); const floatingOptions = useMemo(() => { return makeFloatingOptions(arrowRef, floating); }, [floating]); const { floatingStyles, middlewareData, placement, refs } = useFloating(floatingOptions); const tooltipOpacity = useRef(new Animated.Value(0)); const stepMotion = useMemo(() => { return tourStep.motion ?? motion; }, [tourStep, motion]); const shapeOptions = useMemo(() => { const options = tourStep.shape ?? shape; const padding = 16; return typeof options !== "string" ? { padding, type: "circle", ...options } : { padding, type: options }; }, [tourStep, shape]); const useNativeDriver = useMemo(() => { const driverConfig = typeof nativeDriver === "boolean" ? { android: nativeDriver, ios: nativeDriver, web: nativeDriver } : nativeDriver; return Platform.select({ android: driverConfig.android, default: false, ios: driverConfig.ios, web: false, }); }, [nativeDriver]); const ShapeMask = useMemo(() => { switch (shapeOptions.type) { case "circle": return CircleShape; case "rectangle": return RectShape; } }, [shapeOptions]); const handleBackdropPress = useCallback(() => { const handler = tourStep.onBackdropPress ?? onBackdropPress; if (handler !== undefined && current !== undefined) { switch (handler) { case "continue": return next(); case "stop": return stop(); default: return handler({ current, goTo, next, pause, previous, resume, start, status: "running", stop }); } } }, [tourStep, onBackdropPress, current, goTo, next, previous, start, stop, pause, resume]); useEffect(() => { const { height, width } = spot; if ([height, width].every(value => value > 0)) { Animated.timing(tooltipOpacity.current, { delay: 400, duration: 400, toValue: 1, useNativeDriver, }) .start(); } }, [spot, useNativeDriver]); useImperativeHandle(ref, () => ({ hideTooltip: () => { return new Promise(resolve => { if (current !== undefined) { Animated.timing(tooltipOpacity.current, { duration: 400, toValue: 0, useNativeDriver, }) .start(resolve); } else { resolve({ finished: true }); } }); }, }), [current, useNativeDriver]); return (_jsx(Modal, { animationType: "fade", presentationStyle: "overFullScreen", transparent: true, visible: current !== undefined, children: _jsxs(View, { testID: "Overlay View", style: Css.overlayView, children: [_jsxs(Svg, { testID: "Spot Svg", height: "100%", width: "100%", viewBox: `0 0 ${vw(100)} ${vh(100)}`, onPress: handleBackdropPress, shouldRasterizeIOS: true, renderToHardwareTextureAndroid: true, children: [_jsx(Defs, { children: _jsxs(Mask, { id: "mask", x: 0, y: 0, height: "100%", width: "100%", children: [_jsx(Rect, { height: "100%", width: "100%", fill: "#fff" }), _jsx(ShapeMask, { spot: spot, setReference: refs.setReference, motion: stepMotion, padding: shapeOptions.padding, useNativeDriver: useNativeDriver })] }) }), _jsx(Rect, { height: "100%", width: "100%", fill: color, mask: "url(#mask)", opacity: backdropOpacity })] }), current !== undefined && (_jsxs(Animated.View, { ref: refs.setFloating, testID: "Tooltip View", style: { ...floatingStyles, opacity: tooltipOpacity.current }, children: [_jsx(tourStep.render, { current: current, isFirst: current === 0, isLast: current === steps.length - 1, next: next, previous: previous, stop: stop, pause: pause, resume: resume, goTo: goTo }), floating.arrow !== false && (_jsx(View, { style: [ Css.tooltipArrow, arrowCss({ arrow: typeof floating.arrow !== "boolean" ? floating.arrow : undefined, data: middlewareData.arrow, placement, }), ], ref: arrowRef }))] }))] }) })); }); function makeFloatingOptions(arrowRef, props) { const arrowOption = typeof props?.arrow === "boolean" ? DEFAULT_ARROW : props?.arrow; const { size } = typeof arrowOption === "number" ? { ...DEFAULT_ARROW, size: arrowOption } : { ...DEFAULT_ARROW, ...arrowOption }; const baseOffset = props?.offset || 4; const offsetValue = props?.arrow !== false ? (Math.sqrt(2 * size ** 2) / 2) + baseOffset : baseOffset; const arrowMw = props?.arrow !== false ? arrow({ element: arrowRef }) : undefined; const flipMw = flipMiddleware(props?.flip); const offsetMw = props?.offset !== 0 ? offset(offsetValue) : undefined; const shiftMw = shiftMiddleware(props?.shift); return { middleware: [flipMw, offsetMw, shiftMw, arrowMw].filter(Boolean), placement: props?.placement, }; } function flipMiddleware(flipProps) { if (flipProps !== false) { return flip(flipProps === true ? undefined : flipProps); } return undefined; } function shiftMiddleware(shiftProps) { if (shiftProps !== false) { return shift(shiftProps === true ? undefined : shiftProps); } return undefined; } //# sourceMappingURL=TourOverlay.component.js.map