Skip to content

Commit 3c30332

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents 94a1136 + 07795e7 commit 3c30332

File tree

8 files changed

+132
-14
lines changed

8 files changed

+132
-14
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,40 @@ Use `this.props.start(fromStep, onStop)` in the root component in order to trigg
180180

181181
The `onStop` method will be called after a user selects 'Skip' or 'Finish' buttion in the tooltip.
182182

183+
### Listening to the events
184+
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.
185+
186+
List of available events is:
187+
188+
- `start` — Copilot tutorial has started.
189+
- `stop` — Copilot tutorial has ended or skipped.
190+
- `stepChange` — Next step is triggered. Passes [`Step`](https://github.com/okgrow/react-native-copilot/blob/master/src/types.js#L2) instance as event handler argument.
191+
192+
193+
**Example:**
194+
```js
195+
import { copilot, CopilotStep } from '@okgrow/react-native-copilot';
196+
197+
const CustomComponent = ({ copilot }) => <View {...copilot}><Text>Hello world!</Text></View>;
198+
199+
class HomeScreen {
200+
componentDidMount() {
201+
this.props.copilotEvents.on('stop', () => {
202+
// Copilot tutorial finished!
203+
});
204+
}
205+
206+
componentWillUnmount() {
207+
// Don't forget to disable event handlers to prevent errors
208+
this.props.copilotEvents.off('stop');
209+
}
210+
211+
render() {
212+
// ...
213+
}
214+
}
215+
```
216+
183217
## Contributing
184218
Issues and Pull Requests are always welcome.
185219

example/App.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { Component } from 'react';
22
import PropTypes from 'prop-types';
3-
import { StyleSheet, Text, Image, View, TouchableOpacity } from 'react-native';
3+
import { StyleSheet, Text, Image, View, TouchableOpacity, Switch } from 'react-native';
44
import { Ionicons } from '@expo/vector-icons';
55

66
import { copilot, walkthroughable, CopilotStep } from '@okgrow/react-native-copilot';
@@ -46,6 +46,13 @@ const styles = StyleSheet.create({
4646
flex: 1,
4747
textAlign: 'center',
4848
},
49+
activeSwitchContainer: {
50+
flexDirection: 'row',
51+
justifyContent: 'space-between',
52+
marginBottom: 20,
53+
alignItems: 'center',
54+
paddingHorizontal: 40,
55+
},
4956
});
5057

5158
class App extends Component {
@@ -56,6 +63,10 @@ class App extends Component {
5663
}).isRequired,
5764
};
5865

66+
state = {
67+
secondStepActive: true,
68+
};
69+
5970
componentDidMount() {
6071
this.props.copilotEvents.on('stepChange', this.handleStepChange);
6172
this.props.start();
@@ -74,12 +85,21 @@ class App extends Component {
7485
</WalkthroughableText>
7586
</CopilotStep>
7687
<View style={styles.middleView}>
77-
<CopilotStep text="Here goes your profile picture!" order={2} name="secondText">
88+
<CopilotStep active={this.state.secondStepActive} text="Here goes your profile picture!" order={2} name="secondText">
7889
<WalkthroughableImage
7990
source={{ uri: 'https://pbs.twimg.com/profile_images/527584017189982208/l3wwN-l-_400x400.jpeg' }}
8091
style={styles.profilePhoto}
8192
/>
8293
</CopilotStep>
94+
<View style={styles.activeSwitchContainer}>
95+
<Text>Profile photo step activated?</Text>
96+
<View style={{ flexGrow: 1 }} />
97+
<Switch
98+
onValueChange={secondStepActive => this.setState({ secondStepActive })}
99+
value={this.state.secondStepActive}
100+
/>
101+
</View>
102+
83103
<TouchableOpacity style={styles.button} onPress={() => this.props.start()}>
84104
<Text style={styles.buttonText}>START THE TUTORIAL!</Text>
85105
</TouchableOpacity>

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@okgrow/react-native-copilot",
3-
"version": "2.3.0",
3+
"version": "2.4.0",
44
"description": "Make an interactive step by step tour guide for you react-native app",
55
"main": "src/index.js",
66
"private": false,
@@ -61,6 +61,7 @@
6161
}
6262
},
6363
"dependencies": {
64+
"hoist-non-react-statics": "^3.0.1",
6465
"mitt": "^1.1.3"
6566
}
6667
}

src/components/ConnectedCopilotStep.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,41 @@ type Props = {
77
name: string,
88
text: string,
99
order: number,
10+
active?: boolean,
1011
_copilot: CopilotContext,
1112
children: React$Element
1213
};
1314

1415
class ConnectedCopilotStep extends Component<Props> {
16+
static defaultProps = {
17+
active: true,
18+
};
19+
1520
componentDidMount() {
21+
if (this.props.active) {
22+
this.register();
23+
}
24+
}
25+
26+
componentWillReceiveProps(nextProps) {
27+
if (nextProps.active !== this.props.active) {
28+
if (nextProps.active) {
29+
this.register();
30+
} else {
31+
this.unregister();
32+
}
33+
}
34+
}
35+
36+
componentWillUnmount() {
37+
this.unregister();
38+
}
39+
40+
setNativeProps(obj) {
41+
this.wrapper.setNativeProps(obj);
42+
}
43+
44+
register() {
1645
this.props._copilot.registerStep({
1746
name: this.props.name,
1847
text: this.props.text,
@@ -22,14 +51,10 @@ class ConnectedCopilotStep extends Component<Props> {
2251
});
2352
}
2453

25-
componentWillUnmount() {
54+
unregister() {
2655
this.props._copilot.unregisterStep(this.props.name);
2756
}
2857

29-
setNativeProps(obj) {
30-
this.wrapper.setNativeProps(obj);
31-
}
32-
3358
measure() {
3459
if (typeof __TEST__ !== 'undefined' && __TEST__) { // eslint-disable-line no-undef
3560
return new Promise(resolve => resolve({

src/components/CopilotModal.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class CopilotModal extends Component<Props, State> {
4646
overlay: typeof NativeModules.RNSVGSvgViewManager !== 'undefined' ? 'svg' : 'view',
4747
// If animated was not specified, rely on the default overlay type
4848
animated: typeof NativeModules.RNSVGSvgViewManager !== 'undefined',
49-
androidStatusBarVisible: true,
49+
androidStatusBarVisible: false,
5050
};
5151

5252
state = {
@@ -97,7 +97,7 @@ class CopilotModal extends Component<Props, State> {
9797

9898
async _animateMove(obj = {}): void {
9999
const layout = await this.measure();
100-
if (this.props.androidStatusBarVisible && Platform.OS === 'android') {
100+
if (!this.props.androidStatusBarVisible && Platform.OS === 'android') {
101101
obj.top -= StatusBar.currentHeight; // eslint-disable-line no-param-reassign
102102
}
103103

src/hocs/copilot.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
55
import { View } from 'react-native';
66

77
import mitt from 'mitt';
8+
import hoistStatics from 'hoist-non-react-statics'
89

910
import CopilotModal from '../components/CopilotModal';
1011
import { OFFSET_WIDTH } from '../components/style';
@@ -141,7 +142,7 @@ const copilot = ({
141142
requestAnimationFrame(() => this.start(fromStep));
142143
} else {
143144
this.eventEmitter.emit('start');
144-
await this.setCurrentStep(currentStep, false);
145+
await this.setCurrentStep(currentStep);
145146
await this.moveToCurrentStep();
146147
await this.setVisibility(true);
147148
this.startTries = 0;
@@ -205,7 +206,7 @@ const copilot = ({
205206
_copilot: PropTypes.object.isRequired,
206207
};
207208

208-
return Copilot;
209+
return hoistStatics(Copilot, WrappedComponent);
209210
};
210211

211212
export default copilot;

src/hocs/copilot.test.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @flow
12
import React from 'react';
23
import { View, Modal } from 'react-native';
34
import renderer from 'react-test-renderer';
@@ -8,12 +9,16 @@ import SvgMask from '../components/SvgMask';
89

910
const WalkthroughableView = walkthroughable(View);
1011

11-
const SampleComponent = () => (
12+
type SampleComponentProps = {
13+
secondStepActive?: boolean
14+
};
15+
16+
const SampleComponent = ({ secondStepActive }: SampleComponentProps) => (
1217
<View>
1318
<CopilotStep order={0} name="step-1" text="This is the description for the first step">
1419
<WalkthroughableView />
1520
</CopilotStep>
16-
<CopilotStep order={1} name="step-2" text="This is the description for the second step">
21+
<CopilotStep order={1} name="step-2" active={secondStepActive} text="This is the description for the second step">
1722
<WalkthroughableView />
1823
</CopilotStep>
1924
<CopilotStep order={3} name="step-3" text="This is the description for the third step">
@@ -22,6 +27,10 @@ const SampleComponent = () => (
2227
</View>
2328
);
2429

30+
SampleComponent.defaultProps = {
31+
secondStepActive: true,
32+
};
33+
2534
it('only renders the component within a wrapper as long as tutorial has not been started', () => {
2635
const CopilotComponent = copilot()(SampleComponent);
2736

@@ -134,3 +143,25 @@ it('shows the custom tooltip component if specified', async () => {
134143
expect(tooltip.props.currentStep).toHaveProperty('order');
135144
expect(tooltip.props.currentStep).toHaveProperty('text');
136145
});
146+
147+
it('skips a step if disabled', async () => {
148+
const CopilotComponent = copilot()(SampleComponent);
149+
150+
const tree = renderer.create(<CopilotComponent secondStepActive={false} />);
151+
await tree.root.findByType(SampleComponent).props.start();
152+
153+
const textComponent = tree.root.findByProps({
154+
testID: 'stepDescription',
155+
});
156+
157+
expect(textComponent.props.children).toBe('This is the description for the first step');
158+
159+
await tree.root.instance.next();
160+
161+
expect(textComponent.props.children).not.toBe('This is the description for the second step');
162+
expect(textComponent.props.children).toBe('This is the description for the third step');
163+
164+
await tree.root.instance.prev();
165+
166+
expect(textComponent.props.children).toBe('This is the description for the first step');
167+
});

yarn.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2859,6 +2859,12 @@ [email protected]:
28592859
version "4.2.1"
28602860
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
28612861

2862+
hoist-non-react-statics@^3.0.1:
2863+
version "3.0.1"
2864+
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz#fba3e7df0210eb9447757ca1a7cb607162f0a364"
2865+
dependencies:
2866+
react-is "^16.3.2"
2867+
28622868
home-or-tmp@^2.0.0:
28632869
version "2.0.0"
28642870
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"

0 commit comments

Comments
 (0)