From 4029105dd6f30f0468f1d082365fbc3b3f75d2f8 Mon Sep 17 00:00:00 2001
From: Daniel Huang
Date: Tue, 16 Oct 2018 10:36:47 -0700
Subject: [PATCH 01/11] add initial support for scroll view
---
src/hocs/copilot.js | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git a/src/hocs/copilot.js b/src/hocs/copilot.js
index 5627b7d3..32d454c1 100644
--- a/src/hocs/copilot.js
+++ b/src/hocs/copilot.js
@@ -1,7 +1,9 @@
// @flow
import React, { Component } from 'react';
+import ReactNative from "react-native";
import PropTypes from 'prop-types';
+
import { View } from 'react-native';
import mitt from 'mitt';
@@ -25,7 +27,7 @@ type State = {
currentStep: ?Step,
visible: boolean,
androidStatusBarVisible: boolean,
- backdropColor: string
+ scrollView?: React.RefObject
};
const copilot = ({
@@ -34,7 +36,6 @@ const copilot = ({
stepNumberComponent,
animated,
androidStatusBarVisible,
- backdropColor,
} = {}) =>
(WrappedComponent) => {
class Copilot extends Component {
@@ -42,6 +43,7 @@ const copilot = ({
steps: {},
currentStep: null,
visible: false,
+ scrollView: null
};
getChildContext(): { _copilot: CopilotContext } {
@@ -78,6 +80,14 @@ const copilot = ({
setCurrentStep = async (step: Step, move?: boolean = true): void => {
await this.setState({ currentStep: step });
this.eventEmitter.emit('stepChange', step);
+ const scrollView = this.state.scrollView.current;
+
+ if (this.state.scrollView) {
+ const relativeSize = await this.state.currentStep.wrapper.measureLayout(ReactNative.findNodeHandle(scrollView), (x, y, w, h) => {
+ const yOffsett = y > 0 ? y - (h / 2) : 0;
+ scrollView.scrollTo({ y: yOffsett, animated: false });
+ });
+ }
if (move) {
this.moveToCurrentStep();
@@ -126,9 +136,12 @@ const copilot = ({
await this.setCurrentStep(this.getPrevStep());
}
- start = async (fromStep?: string): void => {
+ start = async (fromStep?: string, scrollView?: React.RefObject): void => {
+
const { steps } = this.state;
+ this.state.scrollView ? null : this.setState({ scrollView })
+
const currentStep = fromStep
? steps[fromStep]
: this.getFirstStep();
@@ -143,7 +156,7 @@ const copilot = ({
requestAnimationFrame(() => this.start(fromStep));
} else {
this.eventEmitter.emit('start');
- await this.setCurrentStep(currentStep);
+ await this.setCurrentStep(currentStep, true);
await this.moveToCurrentStep();
await this.setVisibility(true);
this.startTries = 0;
@@ -155,7 +168,7 @@ const copilot = ({
this.eventEmitter.emit('stop');
}
- async moveToCurrentStep(): void {
+ async moveToCurrentStep(): void {
const size = await this.state.currentStep.target.measure();
await this.modal.animateMove({
@@ -190,7 +203,6 @@ const copilot = ({
overlay={overlay}
animated={animated}
androidStatusBarVisible={androidStatusBarVisible}
- backdropColor={backdropColor}
ref={(modal) => { this.modal = modal; }}
/>
From 21f532163e577f3dc6ee3bd0833176c0bafe059c Mon Sep 17 00:00:00 2001
From: Daniel Huang
Date: Tue, 16 Oct 2018 10:48:04 -0700
Subject: [PATCH 02/11] move variable after check and set timeout if scroll
view to allow scrolling to finish before measuring
---
src/hocs/copilot.js | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/hocs/copilot.js b/src/hocs/copilot.js
index 32d454c1..2057f443 100644
--- a/src/hocs/copilot.js
+++ b/src/hocs/copilot.js
@@ -80,18 +80,20 @@ const copilot = ({
setCurrentStep = async (step: Step, move?: boolean = true): void => {
await this.setState({ currentStep: step });
this.eventEmitter.emit('stepChange', step);
- const scrollView = this.state.scrollView.current;
if (this.state.scrollView) {
+ const scrollView = this.state.scrollView.current;
const relativeSize = await this.state.currentStep.wrapper.measureLayout(ReactNative.findNodeHandle(scrollView), (x, y, w, h) => {
const yOffsett = y > 0 ? y - (h / 2) : 0;
scrollView.scrollTo({ y: yOffsett, animated: false });
});
}
-
- if (move) {
- this.moveToCurrentStep();
- }
+
+ setTimeout(() => {
+ if (move) {
+ this.moveToCurrentStep();
+ }
+ }, this.state.scrollView ? 100 : 0);
}
setVisibility = (visible: boolean): void => new Promise((resolve) => {
From abfd25d8203d3ef2830c1a6736f02bbd614b0fb9 Mon Sep 17 00:00:00 2001
From: Daniel Huang
Date: Tue, 16 Oct 2018 10:57:16 -0700
Subject: [PATCH 03/11] cleanup
---
src/hocs/copilot.js | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/hocs/copilot.js b/src/hocs/copilot.js
index 2057f443..3ffc5af8 100644
--- a/src/hocs/copilot.js
+++ b/src/hocs/copilot.js
@@ -27,6 +27,7 @@ type State = {
currentStep: ?Step,
visible: boolean,
androidStatusBarVisible: boolean,
+ backdropColor: string,
scrollView?: React.RefObject
};
@@ -36,6 +37,7 @@ const copilot = ({
stepNumberComponent,
animated,
androidStatusBarVisible,
+ backdropColor,
} = {}) =>
(WrappedComponent) => {
class Copilot extends Component {
@@ -88,12 +90,12 @@ const copilot = ({
scrollView.scrollTo({ y: yOffsett, animated: false });
});
}
-
setTimeout(() => {
if (move) {
this.moveToCurrentStep();
}
- }, this.state.scrollView ? 100 : 0);
+ }, this.state.scrollView ? 100 : 0)
+
}
setVisibility = (visible: boolean): void => new Promise((resolve) => {
@@ -158,7 +160,7 @@ const copilot = ({
requestAnimationFrame(() => this.start(fromStep));
} else {
this.eventEmitter.emit('start');
- await this.setCurrentStep(currentStep, true);
+ await this.setCurrentStep(currentStep);
await this.moveToCurrentStep();
await this.setVisibility(true);
this.startTries = 0;
@@ -170,7 +172,7 @@ const copilot = ({
this.eventEmitter.emit('stop');
}
- async moveToCurrentStep(): void {
+ async moveToCurrentStep(): void {
const size = await this.state.currentStep.target.measure();
await this.modal.animateMove({
@@ -205,6 +207,7 @@ const copilot = ({
overlay={overlay}
animated={animated}
androidStatusBarVisible={androidStatusBarVisible}
+ backdropColor={backdropColor}
ref={(modal) => { this.modal = modal; }}
/>
From 8b4383b5acaa61ef4dd7d1c85d0a479f56f67a42 Mon Sep 17 00:00:00 2001
From: Daniel Huang
Date: Tue, 16 Oct 2018 10:59:25 -0700
Subject: [PATCH 04/11] remove added empty line
---
src/hocs/copilot.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/hocs/copilot.js b/src/hocs/copilot.js
index 3ffc5af8..bf9d4fb7 100644
--- a/src/hocs/copilot.js
+++ b/src/hocs/copilot.js
@@ -3,7 +3,6 @@ import React, { Component } from 'react';
import ReactNative from "react-native";
import PropTypes from 'prop-types';
-
import { View } from 'react-native';
import mitt from 'mitt';
From 559bf57bc489857cd93ba417d63c9bf19f69407d Mon Sep 17 00:00:00 2001
From: Sibtain Ali
Date: Sat, 30 Mar 2019 21:24:47 +0500
Subject: [PATCH 05/11] Fixed the double conversion error.
---
src/hocs/copilot.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/hocs/copilot.js b/src/hocs/copilot.js
index bf9d4fb7..6242ab31 100644
--- a/src/hocs/copilot.js
+++ b/src/hocs/copilot.js
@@ -83,7 +83,7 @@ const copilot = ({
this.eventEmitter.emit('stepChange', step);
if (this.state.scrollView) {
- const scrollView = this.state.scrollView.current;
+ const scrollView = this.state.scrollView;
const relativeSize = await this.state.currentStep.wrapper.measureLayout(ReactNative.findNodeHandle(scrollView), (x, y, w, h) => {
const yOffsett = y > 0 ? y - (h / 2) : 0;
scrollView.scrollTo({ y: yOffsett, animated: false });
From 40b941cc7cce6b33baa9810a13ecae9d9e724805 Mon Sep 17 00:00:00 2001
From: Sibtain Ali
Date: Sat, 30 Mar 2019 21:32:38 +0500
Subject: [PATCH 06/11] Added documentation for the scrollview feature.
---
README.md | 35 ++++++++++++++++++++++++++++++++++-
1 file changed, 34 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index fa81c242..ff60eab6 100644
--- a/README.md
+++ b/README.md
@@ -188,6 +188,34 @@ class HomeScreen {
### 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.
+### Usage inside a ScrollView
+Pass the ScrollView reference as the second argument to the `this.props.start()` function.
+eg `this.props.start(false, ScrollViewRef)`
+
+```js
+import { ScrollView } from 'react-native';
+import { copilot } from '@okgrow/react-native-copilot';
+
+class HomeScreen {
+ componentDidMount() {
+ // Starting the tutorial and passing the scrollview reference.
+ this.props.start(false, this.scrollView);
+ }
+
+ componentWillUnmount() {
+ // Don't forget to disable event handlers to prevent errors
+ this.props.copilotEvents.off('stop');
+ }
+
+ render() {
+ this.scrollView = ref}>
+ // ...
+
+ }
+}
+export default copilot()(HomeScreen);
+```
+
### Listening to the events
Along with `this.props.start()`, `copilot` HOC passes `copilotEvents` function to the component to help you with tracking of tutorial progress. It utilizes [mitt](https://github.com/developit/mitt) under the hood, you can see how full API there.
@@ -200,12 +228,15 @@ List of available events is:
**Example:**
```js
+import { ScrollView } from 'react-native';
import { copilot, CopilotStep } from '@okgrow/react-native-copilot';
const CustomComponent = ({ copilot }) => Hello world! ;
class HomeScreen {
componentDidMount() {
+ // Starting the tutorial.
+ this.props.start(false, this.scrollView);
this.props.copilotEvents.on('stop', () => {
// Copilot tutorial finished!
});
@@ -217,7 +248,9 @@ class HomeScreen {
}
render() {
- // ...
+ this.scrollView = ref}>
+ // ...
+
}
}
```
From aecc25b7843f772d77e57140d1e0ad1dc10b9a73 Mon Sep 17 00:00:00 2001
From: Sibtain Ali
Date: Mon, 1 Apr 2019 01:45:34 +0500
Subject: [PATCH 07/11] Added feature to add custom component over the
walkthrough overlay
---
src/components/ConnectedCopilotStep.js | 42 +++++--
src/components/CopilotModal.js | 160 ++++++++++++++-----------
src/components/CopilotStep.js | 30 +++--
src/components/SvgMask.js | 120 ++++++++++++-------
src/components/Tooltip.js | 60 +++++-----
src/components/style.js | 62 +++++-----
src/hocs/walkthroughable.js | 11 +-
src/types.js | 16 ++-
8 files changed, 289 insertions(+), 212 deletions(-)
diff --git a/src/components/ConnectedCopilotStep.js b/src/components/ConnectedCopilotStep.js
index 4408a9b4..aa631b2d 100644
--- a/src/components/ConnectedCopilotStep.js
+++ b/src/components/ConnectedCopilotStep.js
@@ -1,12 +1,13 @@
// @flow
-import React, { Component } from 'react';
+import React, { Component } from "react";
-import type { CopilotContext } from '../types';
+import type { CopilotContext, OverLayElement } from "../types";
type Props = {
name: string,
text: string,
order: number,
+ overlayElement?: OverLayElement,
active?: boolean,
_copilot: CopilotContext,
children: React$Element
@@ -14,7 +15,7 @@ type Props = {
class ConnectedCopilotStep extends Component {
static defaultProps = {
- active: true,
+ active: true
};
componentDidMount() {
@@ -48,6 +49,9 @@ class ConnectedCopilotStep extends Component {
order: this.props.order,
target: this,
wrapper: this.wrapper,
+ ...(this.props.overlayElement && {
+ overlayElement: this.props.overlayElement
+ })
});
}
@@ -56,10 +60,16 @@ class ConnectedCopilotStep extends Component {
}
measure() {
- if (typeof __TEST__ !== 'undefined' && __TEST__) { // eslint-disable-line no-undef
- return new Promise(resolve => resolve({
- x: 0, y: 0, width: 0, height: 0,
- }));
+ if (typeof __TEST__ !== "undefined" && __TEST__) {
+ // eslint-disable-line no-undef
+ return new Promise(resolve =>
+ resolve({
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ })
+ );
}
return new Promise((resolve, reject) => {
@@ -67,10 +77,14 @@ class ConnectedCopilotStep extends Component {
// Wait until the wrapper element appears
if (this.wrapper.measure) {
this.wrapper.measure(
- (ox, oy, width, height, x, y) => resolve({
- x, y, width, height,
- }),
- reject,
+ (ox, oy, width, height, x, y) =>
+ resolve({
+ x,
+ y,
+ width,
+ height
+ }),
+ reject
);
} else {
requestAnimationFrame(measure);
@@ -83,8 +97,10 @@ class ConnectedCopilotStep extends Component {
render() {
const copilot = {
- ref: (wrapper) => { this.wrapper = wrapper; },
- onLayout: () => { }, // Android hack
+ ref: wrapper => {
+ this.wrapper = wrapper;
+ },
+ onLayout: () => {} // Android hack
};
return React.cloneElement(this.props.children, { copilot });
diff --git a/src/components/CopilotModal.js b/src/components/CopilotModal.js
index abff9c0c..b53e6c81 100644
--- a/src/components/CopilotModal.js
+++ b/src/components/CopilotModal.js
@@ -1,9 +1,22 @@
// @flow
-import React, { Component } from 'react';
-import { Animated, Easing, View, NativeModules, Modal, StatusBar, Platform } from 'react-native';
-import Tooltip from './Tooltip';
-import StepNumber from './StepNumber';
-import styles, { MARGIN, ARROW_SIZE, STEP_NUMBER_DIAMETER, STEP_NUMBER_RADIUS } from './style';
+import React, { Component } from "react";
+import {
+ Animated,
+ Easing,
+ View,
+ NativeModules,
+ Modal,
+ StatusBar,
+ Platform
+} from "react-native";
+import Tooltip from "./Tooltip";
+import StepNumber from "./StepNumber";
+import styles, {
+ MARGIN,
+ ARROW_SIZE,
+ STEP_NUMBER_DIAMETER,
+ STEP_NUMBER_RADIUS
+} from "./style";
type Props = {
stop: () => void,
@@ -18,7 +31,7 @@ type Props = {
animationDuration: ?number,
tooltipComponent: ?React$Component,
stepNumberComponent: ?React$Component,
- overlay: 'svg' | 'view',
+ overlay: "svg" | "view",
animated: boolean,
androidStatusBarVisible: boolean,
backdropColor: string
@@ -31,8 +44,8 @@ type State = {
notAnimated: boolean,
layout: ?{
width: number,
- height: number,
- },
+ height: number
+ }
};
const noop = () => {};
@@ -44,11 +57,12 @@ class CopilotModal extends Component {
tooltipComponent: Tooltip,
stepNumberComponent: StepNumber,
// If react-native-svg native module was avaialble, use svg as the default overlay component
- overlay: typeof NativeModules.RNSVGSvgViewManager !== 'undefined' ? 'svg' : 'view',
+ overlay:
+ typeof NativeModules.RNSVGSvgViewManager !== "undefined" ? "svg" : "view",
// If animated was not specified, rely on the default overlay type
- animated: typeof NativeModules.RNSVGSvgViewManager !== 'undefined',
+ animated: typeof NativeModules.RNSVGSvgViewManager !== "undefined",
androidStatusBarVisible: false,
- backdropColor: 'rgba(0, 0, 0, 0.4)',
+ backdropColor: "rgba(0, 0, 0, 0.4)"
};
state = {
@@ -56,10 +70,10 @@ class CopilotModal extends Component {
arrow: {},
animatedValues: {
top: new Animated.Value(0),
- stepNumberLeft: new Animated.Value(0),
+ stepNumberLeft: new Animated.Value(0)
},
animated: false,
- containerVisible: false,
+ containerVisible: false
};
componentWillReceiveProps(nextProps: Props) {
@@ -70,22 +84,27 @@ class CopilotModal extends Component {
layout = {
width: 0,
- height: 0,
- }
+ height: 0
+ };
handleLayoutChange = ({ nativeEvent: { layout } }) => {
this.layout = layout;
- }
+ };
measure(): Promise {
- if (typeof __TEST__ !== 'undefined' && __TEST__) { // eslint-disable-line no-undef
- return new Promise(resolve => resolve({
- x: 0, y: 0, width: 0, height: 0,
- }));
+ if (typeof __TEST__ !== "undefined" && __TEST__) {
+ // eslint-disable-line no-undef
+ return new Promise(resolve =>
+ resolve({
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ })
+ );
}
-
- return new Promise((resolve) => {
+ return new Promise(resolve => {
const setLayout = () => {
if (this.layout.width !== 0) {
resolve(this.layout);
@@ -99,22 +118,22 @@ class CopilotModal extends Component {
async _animateMove(obj = {}): void {
const layout = await this.measure();
- if (!this.props.androidStatusBarVisible && Platform.OS === 'android') {
+ if (!this.props.androidStatusBarVisible && Platform.OS === "android") {
obj.top -= StatusBar.currentHeight; // eslint-disable-line no-param-reassign
}
let stepNumberLeft = obj.left - STEP_NUMBER_RADIUS;
if (stepNumberLeft < 0) {
- stepNumberLeft = (obj.left + obj.width) - STEP_NUMBER_RADIUS;
+ stepNumberLeft = obj.left + obj.width - STEP_NUMBER_RADIUS;
if (stepNumberLeft > layout.width - STEP_NUMBER_DIAMETER) {
stepNumberLeft = layout.width - STEP_NUMBER_DIAMETER;
}
}
const center = {
- x: obj.left + (obj.width / 2),
- y: obj.top + (obj.height / 2),
+ x: obj.left + obj.width / 2,
+ y: obj.top + obj.height / 2
};
const relativeToLeft = center.x;
@@ -122,25 +141,28 @@ class CopilotModal extends Component {
const relativeToBottom = Math.abs(center.y - layout.height);
const relativeToRight = Math.abs(center.x - layout.width);
- const verticalPosition = relativeToBottom > relativeToTop ? 'bottom' : 'top';
- const horizontalPosition = relativeToLeft > relativeToRight ? 'left' : 'right';
+ const verticalPosition =
+ relativeToBottom > relativeToTop ? "bottom" : "top";
+ const horizontalPosition =
+ relativeToLeft > relativeToRight ? "left" : "right";
const tooltip = {};
const arrow = {};
- if (verticalPosition === 'bottom') {
+ if (verticalPosition === "bottom") {
tooltip.top = obj.top + obj.height + MARGIN;
- arrow.borderBottomColor = '#fff';
- arrow.top = tooltip.top - (ARROW_SIZE * 2);
+ arrow.borderBottomColor = "#fff";
+ arrow.top = tooltip.top - ARROW_SIZE * 2;
} else {
tooltip.bottom = layout.height - (obj.top - MARGIN);
- arrow.borderTopColor = '#fff';
- arrow.bottom = tooltip.bottom - (ARROW_SIZE * 2);
+ arrow.borderTopColor = "#fff";
+ arrow.bottom = tooltip.bottom - ARROW_SIZE * 2;
}
- if (horizontalPosition === 'left') {
+ if (horizontalPosition === "left") {
tooltip.right = Math.max(layout.width - (obj.left + obj.width), 0);
- tooltip.right = tooltip.right === 0 ? tooltip.right + MARGIN : tooltip.right;
+ tooltip.right =
+ tooltip.right === 0 ? tooltip.right + MARGIN : tooltip.right;
tooltip.maxWidth = layout.width - tooltip.right - MARGIN;
arrow.right = tooltip.right + MARGIN;
} else {
@@ -152,20 +174,21 @@ class CopilotModal extends Component {
const animate = {
top: obj.top,
- stepNumberLeft,
+ stepNumberLeft
};
if (this.state.animated) {
- Animated
- .parallel(Object.keys(animate)
- .map(key => Animated.timing(this.state.animatedValues[key], {
+ Animated.parallel(
+ Object.keys(animate).map(key =>
+ Animated.timing(this.state.animatedValues[key], {
toValue: animate[key],
duration: this.props.animationDuration,
- easing: this.props.easing,
- })))
- .start();
+ easing: this.props.easing
+ })
+ )
+ ).start();
} else {
- Object.keys(animate).forEach((key) => {
+ Object.keys(animate).forEach(key => {
this.state.animatedValues[key].setValue(animate[key]);
});
}
@@ -177,23 +200,22 @@ class CopilotModal extends Component {
animated: this.props.animated,
size: {
x: obj.width,
- y: obj.height,
+ y: obj.height
},
position: {
x: Math.floor(Math.max(obj.left, 0)),
- y: Math.floor(Math.max(obj.top, 0)),
- },
+ y: Math.floor(Math.max(obj.top, 0))
+ }
});
}
animateMove(obj = {}): void {
- return new Promise((resolve) => {
- this.setState(
- { containerVisible: true },
- () => requestAnimationFrame(async () => {
+ return new Promise(resolve => {
+ this.setState({ containerVisible: true }, () =>
+ requestAnimationFrame(async () => {
await this._animateMove(obj);
resolve();
- }),
+ })
);
});
}
@@ -202,28 +224,29 @@ class CopilotModal extends Component {
this.setState({
animated: false,
containerVisible: false,
- layout: undefined,
+ layout: undefined
});
}
handleNext = () => {
this.props.next();
- }
+ };
handlePrev = () => {
this.props.prev();
- }
+ };
handleStop = () => {
this.reset();
this.props.stop();
- }
+ };
renderMask() {
/* eslint-disable global-require */
- const MaskComponent = this.props.overlay === 'svg'
- ? require('./SvgMask').default
- : require('./ViewMask').default;
+ const MaskComponent =
+ this.props.overlay === "svg"
+ ? require("./SvgMask").default
+ : require("./ViewMask").default;
/* eslint-enable */
return (
{
easing={this.props.easing}
animationDuration={this.props.animationDuration}
backdropColor={this.props.backdropColor}
+ step={this.props.currentStep}
+ handleNext={this.handleNext}
+ handlePrevious={this.handlePrev}
/>
);
}
@@ -242,7 +268,7 @@ class CopilotModal extends Component {
renderTooltip() {
const {
tooltipComponent: TooltipComponent,
- stepNumberComponent: StepNumberComponent,
+ stepNumberComponent: StepNumberComponent
} = this.props;
return [
@@ -252,8 +278,11 @@ class CopilotModal extends Component {
styles.stepNumberContainer,
{
left: this.state.animatedValues.stepNumberLeft,
- top: Animated.add(this.state.animatedValues.top, -STEP_NUMBER_RADIUS),
- },
+ top: Animated.add(
+ this.state.animatedValues.top,
+ -STEP_NUMBER_RADIUS
+ )
+ }
]}
>
{
handlePrev={this.handlePrev}
handleStop={this.handleStop}
/>
- ,
+
];
}
@@ -287,12 +316,9 @@ class CopilotModal extends Component {
visible={containerVisible}
onRequestClose={noop}
transparent
- supportedOrientations={['portrait', 'landscape']}
+ supportedOrientations={["portrait", "landscape"]}
>
-
+
{contentVisible && this.renderMask()}
{contentVisible && this.renderTooltip()}
diff --git a/src/components/CopilotStep.js b/src/components/CopilotStep.js
index 17cd9168..be5db307 100644
--- a/src/components/CopilotStep.js
+++ b/src/components/CopilotStep.js
@@ -1,37 +1,35 @@
// @flow
-import { Component, createElement } from 'react';
-import PropTypes from 'prop-types';
+import { Component, createElement } from "react";
+import PropTypes from "prop-types";
-import ConnectedCopilotStep from './ConnectedCopilotStep';
+import ConnectedCopilotStep from "./ConnectedCopilotStep";
-import type { CopilotContext } from '../types';
+import type { CopilotContext, OverLayElement } from "../types";
type Props = {
name: string,
order: number, // eslint-disable-line react/no-unused-prop-types
text: string, // eslint-disable-line react/no-unused-prop-types
+ overlayElement?: OverLayElement
};
class CopilotStep extends Component {
static contextTypes = {
- _copilot: PropTypes.object,
- }
+ _copilot: PropTypes.object
+ };
context: {
- _copilot: CopilotContext,
- }
+ _copilot: CopilotContext
+ };
render() {
const currentStep = this.context._copilot.getCurrentStep();
- return createElement(
- ConnectedCopilotStep,
- {
- ...this.props,
- _copilot: this.context._copilot,
- visible: currentStep && currentStep.name === this.props.name,
- },
- );
+ return createElement(ConnectedCopilotStep, {
+ ...this.props,
+ _copilot: this.context._copilot,
+ visible: currentStep && currentStep.name === this.props.name
+ });
}
}
diff --git a/src/components/SvgMask.js b/src/components/SvgMask.js
index f97713c9..0a7219e8 100644
--- a/src/components/SvgMask.js
+++ b/src/components/SvgMask.js
@@ -1,19 +1,19 @@
// @flow
-import React, { Component } from 'react';
-import {
- View,
- Animated,
- Easing,
- Dimensions,
-} from 'react-native';
+import React, { Component } from "react";
+import { View, Animated, Easing, Dimensions, Text } from "react-native";
// import { Svg } from 'expo';
-import Svg from 'react-native-svg';
-import AnimatedSvgPath from './AnimatedPath';
+import Svg from "react-native-svg";
+import AnimatedSvgPath from "./AnimatedPath";
-import type { valueXY } from '../types';
+import type { valueXY, Step } from "../types";
-const windowDimensions = Dimensions.get('window');
-const path = (size, position, canvasSize): string => `M0,0H${canvasSize.x}V${canvasSize.y}H0V0ZM${position.x._value},${position.y._value}H${position.x._value + size.x._value}V${position.y._value + size.y._value}H${position.x._value}V${position.y._value}Z`;
+const windowDimensions = Dimensions.get("window");
+const path = (size, position, canvasSize): string =>
+ `M0,0H${canvasSize.x}V${canvasSize.y}H0V0ZM${position.x._value},${
+ position.y._value
+ }H${position.x._value + size.x._value}V${position.y._value + size.y._value}H${
+ position.x._value
+ }V${position.y._value}Z`;
type Props = {
size: valueXY,
@@ -23,18 +23,21 @@ type Props = {
animationDuration: number,
animated: boolean,
backdropColor: string,
+ step: Step,
+ handleNext: Function,
+ handlePrevious: Function
};
type State = {
size: Animated.ValueXY,
position: Animated.ValueXY,
- canvasSize: ?valueXY,
+ canvasSize: ?valueXY
};
class SvgMask extends Component {
static defaultProps = {
animationDuration: 300,
- easing: Easing.linear,
+ easing: Easing.linear
};
constructor(props) {
@@ -43,75 +46,106 @@ class SvgMask extends Component {
this.state = {
canvasSize: {
x: windowDimensions.width,
- y: windowDimensions.height,
+ y: windowDimensions.height
},
size: new Animated.ValueXY(props.size),
- position: new Animated.ValueXY(props.position),
+ position: new Animated.ValueXY(props.position)
};
this.state.position.addListener(this.animationListener);
}
componentWillReceiveProps(nextProps) {
- if (this.props.position !== nextProps.position || this.props.size !== nextProps.size) {
+ if (
+ this.props.position !== nextProps.position ||
+ this.props.size !== nextProps.size
+ ) {
this.animate(nextProps.size, nextProps.position);
}
}
animationListener = (): void => {
- const d: string = path(this.state.size, this.state.position, this.state.canvasSize);
+ const d: string = path(
+ this.state.size,
+ this.state.position,
+ this.state.canvasSize
+ );
if (this.mask) {
this.mask.setNativeProps({ d });
}
};
- animate = (size: valueXY = this.props.size, position: valueXY = this.props.position): void => {
+ animate = (
+ size: valueXY = this.props.size,
+ position: valueXY = this.props.position
+ ): void => {
if (this.props.animated) {
Animated.parallel([
Animated.timing(this.state.size, {
toValue: size,
duration: this.props.animationDuration,
- easing: this.props.easing,
+ easing: this.props.easing
}),
Animated.timing(this.state.position, {
toValue: position,
duration: this.props.animationDuration,
- easing: this.props.easing,
- }),
+ easing: this.props.easing
+ })
]).start();
} else {
this.state.size.setValue(size);
this.state.position.setValue(position);
}
- }
+ };
- handleLayout = ({ nativeEvent: { layout: { width, height } } }) => {
+ handleLayout = ({
+ nativeEvent: {
+ layout: { width, height }
+ }
+ }) => {
this.setState({
canvasSize: {
x: width,
- y: height,
- },
+ y: height
+ }
});
- }
+ };
render() {
return (
-
- {
- this.state.canvasSize
- ? (
-
- { this.mask = ref; }}
- fill={this.props.backdropColor}
- fillRule="evenodd"
- strokeWidth={1}
- d={path(this.state.size, this.state.position, this.state.canvasSize)}
- />
-
- )
- : null
- }
+
+ {this.state.canvasSize ? (
+
+ {
+ this.mask = ref;
+ }}
+ fill={this.props.backdropColor}
+ fillRule="evenodd"
+ strokeWidth={1}
+ d={path(
+ this.state.size,
+ this.state.position,
+ this.state.canvasSize
+ )}
+ />
+ {this.props.step.overlayElement &&
+ this.props.step.overlayElement(
+ this.props.position,
+ this.props.size,
+ this.props.handleNext,
+ this.props.handlePrevious
+ )}
+
+ ) : null}
);
}
diff --git a/src/components/Tooltip.js b/src/components/Tooltip.js
index 94c2a7fa..b8e4a63f 100644
--- a/src/components/Tooltip.js
+++ b/src/components/Tooltip.js
@@ -1,12 +1,12 @@
// @flow
-import React from 'react';
-import { View, Text, TouchableOpacity } from 'react-native';
+import React from "react";
+import { View, Text, TouchableOpacity } from "react-native";
-import Button from './Button';
+import Button from "./Button";
-import styles from './style';
+import styles from "./style";
-import type { Step } from '../types';
+import type { Step } from "../types";
type Props = {
isFirstStep: boolean,
@@ -14,7 +14,7 @@ type Props = {
handleNext: func,
handlePrev: func,
handleStop: func,
- currentStep: Step,
+ currentStep: Step
};
const Tooltip = ({
@@ -23,36 +23,34 @@ const Tooltip = ({
handleNext,
handlePrev,
handleStop,
- currentStep,
+ currentStep
}: Props) => (
- {currentStep.text}
+
+ {currentStep.text}
+
- {
- !isLastStep ?
-
- Skip
-
- : null
- }
- {
- !isFirstStep ?
-
- Previous
-
- : null
- }
- {
- !isLastStep ?
-
- Next
- :
-
- Finish
-
- }
+ {!isLastStep ? (
+
+ Skip
+
+ ) : null}
+ {!isFirstStep ? (
+
+ Previous
+
+ ) : null}
+ {!isLastStep ? (
+
+ Next
+
+ ) : (
+
+ Finish
+
+ )}
);
diff --git a/src/components/style.js b/src/components/style.js
index ce9fdfcd..7ee18176 100644
--- a/src/components/style.js
+++ b/src/components/style.js
@@ -1,5 +1,5 @@
// @flow
-import { StyleSheet } from 'react-native';
+import { StyleSheet } from "react-native";
export const STEP_NUMBER_RADIUS: number = 14;
export const STEP_NUMBER_DIAMETER: number = STEP_NUMBER_RADIUS * 2;
@@ -10,77 +10,75 @@ export const ARROW_SIZE: number = 6;
export default StyleSheet.create({
container: {
- position: 'absolute',
+ position: "absolute",
left: 0,
top: 0,
right: 0,
bottom: 0,
- zIndex: ZINDEX,
+ zIndex: ZINDEX
},
arrow: {
- position: 'absolute',
- borderColor: 'transparent',
- borderWidth: ARROW_SIZE,
+ position: "absolute",
+ borderColor: "transparent",
+ borderWidth: ARROW_SIZE
},
tooltip: {
- position: 'absolute',
+ position: "absolute",
paddingTop: 15,
paddingHorizontal: 15,
- backgroundColor: '#fff',
+ backgroundColor: "#fff",
borderRadius: 3,
- overflow: 'hidden',
- },
- tooltipText: {
-
+ overflow: "hidden"
},
+ tooltipText: {},
tooltipContainer: {
- flex: 1,
+ flex: 1
},
stepNumberContainer: {
- position: 'absolute',
+ position: "absolute",
width: STEP_NUMBER_DIAMETER,
height: STEP_NUMBER_DIAMETER,
- overflow: 'hidden',
- zIndex: ZINDEX + 1,
+ overflow: "hidden",
+ zIndex: ZINDEX + 1
},
stepNumber: {
flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
+ alignItems: "center",
+ justifyContent: "center",
borderWidth: 2,
borderRadius: STEP_NUMBER_RADIUS,
- borderColor: '#FFFFFF',
- backgroundColor: '#27ae60',
+ borderColor: "#FFFFFF",
+ backgroundColor: "#4285F4"
},
stepNumberText: {
fontSize: 10,
- backgroundColor: 'transparent',
- color: '#FFFFFF',
+ backgroundColor: "transparent",
+ color: "#FFFFFF"
},
button: {
- padding: 10,
+ padding: 10
},
buttonText: {
- color: '#27ae60',
+ color: "#4285F4"
},
bottomBar: {
marginTop: 10,
- flexDirection: 'row',
- justifyContent: 'flex-end',
+ flexDirection: "row",
+ justifyContent: "flex-end"
},
overlayRectangle: {
- position: 'absolute',
- backgroundColor: 'rgba(0,0,0,0.2)',
+ position: "absolute",
+ backgroundColor: "rgba(0,0,0,0.2)",
left: 0,
top: 0,
bottom: 0,
- right: 0,
+ right: 0
},
overlayContainer: {
- position: 'absolute',
+ position: "absolute",
left: 0,
top: 0,
bottom: 0,
- right: 0,
- },
+ right: 0
+ }
});
diff --git a/src/hocs/walkthroughable.js b/src/hocs/walkthroughable.js
index bd7a9f74..ffbf1ff6 100644
--- a/src/hocs/walkthroughable.js
+++ b/src/hocs/walkthroughable.js
@@ -1,13 +1,12 @@
// @flow
-import React from 'react';
+import React from "react";
type Props = {
- copilot: Object,
+ copilot: Object
};
-const walkthroughable =
- WrappedComponent =>
- ({ copilot, ...props }: Props) =>
- ;
+const walkthroughable = WrappedComponent => ({ copilot, ...props }: Props) => (
+
+);
export default walkthroughable;
diff --git a/src/types.js b/src/types.js
index 48cd72e1..e83e1be8 100644
--- a/src/types.js
+++ b/src/types.js
@@ -5,15 +5,23 @@ export type Step = {
visible: boolean,
target: React$Element,
wrapper: React$Element,
+ overlayElement?: OverLayElement
};
+export type OverLayElement = (
+ position: valueXY,
+ size: valueXY,
+ handleNext: Function,
+ handlePrevious: Function
+) => React$Element;
+
export type CopilotContext = {
- registerStep: (Step) => void,
+ registerStep: Step => void,
unregisterStep: (name: string) => void,
- getCurrentStep: () => Step,
-}
+ getCurrentStep: () => Step
+};
export type valueXY = {
x: number,
- y: number,
+ y: number
};
From c0678c77a02f591f838b6fa9112e289fd8221be0 Mon Sep 17 00:00:00 2001
From: Sibtain Ali
Date: Mon, 1 Apr 2019 02:10:59 +0500
Subject: [PATCH 08/11] Updated readme.md
---
README.md | 35 ++++++++++++++++++++++++++++++++++-
1 file changed, 34 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index ff60eab6..bc766387 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
-
+
@@ -115,6 +115,39 @@ copilot({
animated: true, // or false
})(RootComponent);
```
+### Custom component over the overlay
+This comes in handy when you want to show some kind of icon (like swipe gesture) on top of the overlay. The CopilotStep component accepts a prop called "overlayElement" that must return a react component wrapped inside a function. The position, size, nextHandle and previousHandle are automatically injected into this function.
+
+```js
+import { React, { Component } } from "react";
+import { View } from "react-native";
+
+class Example extends Component {
+ renderOverlayElement = (position, size, handleNext, handlePrevious) => {
+ //Displays this in the center of the overlay
+ return (
+
+ //...
+
+ );
+ }
+ render(){
+
+ //...
+
+ }
+}
### Custom tooltip component
You can customize the tooltip by passing a component to the `copilot` HOC maker. If you are looking for an example tooltip component, take a look at [the default tooltip implementation](https://github.com/okgrow/react-native-copilot/blob/master/src/components/Tooltip.js).
From 76c9211bb4ac4e3181eb28581c714f0792bed1f1 Mon Sep 17 00:00:00 2001
From: Sibtain Ali
Date: Mon, 1 Apr 2019 02:12:21 +0500
Subject: [PATCH 09/11] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index bc766387..f85a463d 100644
--- a/README.md
+++ b/README.md
@@ -148,7 +148,7 @@ class Example extends Component {
}
}
-
+```
### Custom tooltip component
You can customize the tooltip by passing a component to the `copilot` HOC maker. If you are looking for an example tooltip component, take a look at [the default tooltip implementation](https://github.com/okgrow/react-native-copilot/blob/master/src/components/Tooltip.js).
From 80d8f069ca1945657fed2cd54d83bc726fee9c98 Mon Sep 17 00:00:00 2001
From: Sibtain Ali
Date: Mon, 1 Apr 2019 02:26:33 +0500
Subject: [PATCH 10/11] Update README.md
---
README.md | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/README.md b/README.md
index f85a463d..bc9ba0b7 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,5 @@
React Native Copilot
-
-
Step-by-step walkthrough for your react native app!
From 04cfb9bc2a6659e0d0e69f2242c8c0c03dd55ed6 Mon Sep 17 00:00:00 2001
From: Sibtain Ali
Date: Wed, 3 Apr 2019 16:00:54 +0500
Subject: [PATCH 11/11] Changed tooltip button text
---
src/components/Tooltip.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/Tooltip.js b/src/components/Tooltip.js
index b8e4a63f..50fd7b5b 100644
--- a/src/components/Tooltip.js
+++ b/src/components/Tooltip.js
@@ -34,7 +34,7 @@ const Tooltip = ({
{!isLastStep ? (
- Skip
+ Skip tutorial
) : null}
{!isFirstStep ? (