diff --git a/README.md b/README.md index 604189a9..0e5c58e1 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,28 @@ class HomeScreen { } } ``` +### Add onPress handler for masked component +If you want the user to be able to interact with app while stepping through the tutorial, You can add onPress event to to one of Step components. + +```js +import { walkthroughable, CopilotStep } from '@okgrow/react-native-copilot'; + +const CopilotTouchableOpacity = walkthroughable(TouchableOpacity); + +class HomeScreen { + render() { + return ( + + + Hello world! + + + ); + } +} +``` ### Triggering the tutorial Use `this.props.start()` in the root component in order to trigger the tutorial. You can either invoke it with a touch event or in `componentDidMount`. Note that the component and all its descendants must be mounted before starting the tutorial since the `CopilotStep`s need to be registered first. diff --git a/src/components/CopilotModal.js b/src/components/CopilotModal.js index a47e66b8..f7e50ab0 100644 --- a/src/components/CopilotModal.js +++ b/src/components/CopilotModal.js @@ -1,6 +1,15 @@ // @flow import React, { Component } from 'react'; -import { Animated, Easing, View, NativeModules, Modal, StatusBar, Platform } from 'react-native'; +import { + Animated, + Easing, + View, + NativeModules, + Modal, + StatusBar, + Platform, + I18nManager, +} from 'react-native'; import Tooltip from './Tooltip'; import StepNumber from './StepNumber'; import styles, { MARGIN, ARROW_SIZE, STEP_NUMBER_DIAMETER, STEP_NUMBER_RADIUS } from './style'; @@ -36,6 +45,10 @@ type State = { const noop = () => {}; +const rtl = I18nManager.isRTL; +const start = rtl ? 'right' : 'left'; +const end = rtl ? 'left' : 'right'; + class CopilotModal extends Component { static defaultProps = { easing: Easing.elastic(0.7), @@ -101,15 +114,31 @@ class CopilotModal extends Component { obj.top -= StatusBar.currentHeight; // eslint-disable-line no-param-reassign } - let stepNumberLeft = obj.left - STEP_NUMBER_RADIUS; + let stepNumberLeft; + + const edgeCase = (stepLeft) => { + if (stepLeft > layout.width - STEP_NUMBER_DIAMETER) { + return layout.width - STEP_NUMBER_DIAMETER; + } + return stepLeft; + }; + + if (!rtl) { + stepNumberLeft = obj.left - STEP_NUMBER_RADIUS; - if (stepNumberLeft < 0) { + if (stepNumberLeft < 0) { + stepNumberLeft = (obj.left + obj.width) - STEP_NUMBER_RADIUS; + stepNumberLeft = edgeCase(stepNumberLeft); + } + } else { stepNumberLeft = (obj.left + obj.width) - STEP_NUMBER_RADIUS; - if (stepNumberLeft > layout.width - STEP_NUMBER_DIAMETER) { - stepNumberLeft = layout.width - STEP_NUMBER_DIAMETER; + if (stepNumberLeft > layout.width) { + stepNumberLeft = obj.left - STEP_NUMBER_RADIUS; + stepNumberLeft = edgeCase(stepNumberLeft); } } + const center = { x: obj.left + (obj.width / 2), y: obj.top + (obj.height / 2), @@ -137,15 +166,15 @@ class CopilotModal extends Component { } if (horizontalPosition === 'left') { - tooltip.right = Math.max(layout.width - (obj.left + obj.width), 0); + tooltip[end] = Math.max(layout.width - (obj.left + obj.width), 0); tooltip.right = tooltip.right === 0 ? tooltip.right + MARGIN : tooltip.right; tooltip.maxWidth = layout.width - tooltip.right - MARGIN; - arrow.right = tooltip.right + MARGIN; + arrow[end] = tooltip[end] + MARGIN; } else { - tooltip.left = Math.max(obj.left, 0); + tooltip[start] = Math.max(obj.left, 0); tooltip.left = tooltip.left === 0 ? tooltip.left + MARGIN : tooltip.left; tooltip.maxWidth = layout.width - tooltip.left - MARGIN; - arrow.left = tooltip.left + MARGIN; + arrow[start] = tooltip[start] + MARGIN; } const animate = { @@ -249,7 +278,7 @@ class CopilotModal extends Component { style={[ styles.stepNumberContainer, { - left: this.state.animatedValues.stepNumberLeft, + [start]: this.state.animatedValues.stepNumberLeft, top: Animated.add(this.state.animatedValues.top, -STEP_NUMBER_RADIUS), }, ]} diff --git a/src/components/ViewMask.js b/src/components/ViewMask.js index de4884b9..315386bf 100644 --- a/src/components/ViewMask.js +++ b/src/components/ViewMask.js @@ -1,10 +1,15 @@ // @flow import React, { Component } from 'react'; - -import { View, Animated } from 'react-native'; +import PropTypes from 'prop-types'; +import { View, Animated, I18nManager, TouchableOpacity } from 'react-native'; import styles from './style'; -import type { valueXY } from '../types'; +import type { CopilotContext, valueXY } from '../types'; + + +const rtl = I18nManager.isRTL; +const start = rtl ? 'right' : 'left'; +const end = rtl ? 'left' : 'right'; type Props = { size: valueXY, @@ -26,6 +31,10 @@ type State = { }; class ViewMask extends Component { + static contextTypes = { + _copilot: PropTypes.object, + } + state = { size: new Animated.ValueXY({ x: 0, y: 0 }), position: new Animated.ValueXY({ x: 0, y: 0 }), @@ -37,6 +46,10 @@ class ViewMask extends Component { } } + context: { + _copilot: CopilotContext, + } + animate = (size: valueXY = this.props.size, position: valueXY = this.props.position): void => { if (this.state.animated) { Animated.parallel([ @@ -78,14 +91,14 @@ class ViewMask extends Component { style={[ styles.overlayRectangle, { - right: leftOverlayRight, + [end]: leftOverlayRight, }]} /> { styles.overlayRectangle, { top: bottomOverlayTopBoundary, - left: verticalOverlayLeftBoundary, - right: verticalOverlayRightBoundary, + [start]: verticalOverlayLeftBoundary, + [end]: verticalOverlayRightBoundary, }, ]} /> @@ -103,11 +116,22 @@ class ViewMask extends Component { styles.overlayRectangle, { bottom: topOverlayBottomBoundary, - left: verticalOverlayLeftBoundary, - right: verticalOverlayRightBoundary, + [start]: verticalOverlayLeftBoundary, + [end]: verticalOverlayRightBoundary, }, ]} /> + ); }