Skip to content

Commit f3a5f04

Browse files
committed
configurable touchable components, resolved #16
1 parent 7bdee73 commit f3a5f04

File tree

12 files changed

+169
-28
lines changed

12 files changed

+169
-28
lines changed

__mocks__/.eslintrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"globals": {
3+
"jest": true,
4+
}
5+
}

__mocks__/react-native.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,27 @@ const Animated = {
3434
const Text = () => "Text";
3535
const TouchableHighlight = () => false;
3636
const TouchableWithoutFeedback = () => false;
37+
const TouchableNativeFeedback = () => false;
3738
const TouchableOpacity = () => false;
3839
const ToolbarAndroid = () => false;
3940
const Image = () => false;
4041
const ScrollView = () => false;
42+
const Platform = {
43+
select: jest.fn(o => o.ios),
44+
};
4145

4246
ReactNative.View = View;
4347
ReactNative.ScrollView = ScrollView;
4448
ReactNative.ListView = ListView;
4549
ReactNative.Text = Text;
4650
ReactNative.TouchableOpacity = TouchableOpacity;
4751
ReactNative.TouchableHighlight = TouchableHighlight;
52+
ReactNative.TouchableNativeFeedback = TouchableNativeFeedback;
4853
ReactNative.TouchableWithoutFeedback = TouchableWithoutFeedback;
4954
ReactNative.ToolbarAndroid = ToolbarAndroid;
5055
ReactNative.Image = Image;
5156
ReactNative.AppRegistry = AppRegistry;
5257
ReactNative.Animated = Animated;
58+
ReactNative.Platform = Platform;
5359

5460
module.exports = ReactNative;

__tests__/MenuOption-test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { TouchableHighlight, View, Text } from 'react-native';
33
import { render, normalizeStyle, nthChild } from './helpers';
44

55
jest.dontMock('../src/MenuOption');
6+
jest.dontMock('../src/helpers');
67
const MenuOption = require('../src/MenuOption').default;
78
const { createSpy, objectContaining } = jasmine;
89

@@ -98,10 +99,11 @@ describe('MenuOption', () => {
9899
<MenuOption text='some text' customStyles={customStyles} />
99100
);
100101
const touchable = output;
102+
const view = nthChild(output, 1);
101103
const text = nthChild(output, 2);
102104
expect(normalizeStyle(touchable.props))
103105
.toEqual(objectContaining({ underlayColor: 'green' }));
104-
expect(normalizeStyle(touchable.props.style))
106+
expect(normalizeStyle(view.props.style))
105107
.toEqual(objectContaining(customStyles.optionWrapper));
106108
expect(normalizeStyle(text.props.style))
107109
.toEqual(objectContaining(customStyles.optionText));

__tests__/MenuTrigger-test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { TouchableHighlight, View, Text } from 'react-native';
33
import { render, normalizeStyle, nthChild } from './helpers';
44

55
jest.dontMock('../src/MenuTrigger');
6+
jest.dontMock('../src/helpers');
67
const MenuTrigger = require('../src/MenuTrigger').default;
78
const { createSpy, objectContaining } = jasmine;
89

@@ -82,10 +83,11 @@ describe('MenuTrigger', () => {
8283
<MenuTrigger menuName='menu1' text='some text' customStyles={customStyles} />
8384
);
8485
const touchable = nthChild(output, 1);
86+
const view = nthChild(output, 2);
8587
const text = nthChild(output, 3);
8688
expect(normalizeStyle(touchable.props))
8789
.toEqual(objectContaining({ underlayColor: 'green' }));
88-
expect(normalizeStyle(touchable.props.style))
90+
expect(normalizeStyle(view.props.style))
8991
.toEqual(objectContaining(customStyles.triggerWrapper));
9092
expect(normalizeStyle(text.props.style))
9193
.toEqual(objectContaining(customStyles.triggerText));

__tests__/helpers-test.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { expect } from 'chai';
2+
import { TouchableHighlight, TouchableNativeFeedback, Platform } from 'react-native';
23

34
jest.dontMock('../src/helpers');
4-
const { measure, makeName } = require('../src/helpers');
5+
const { measure, makeName, makeTouchable } = require('../src/helpers');
56

67
describe('helpers test', () => {
78

@@ -40,4 +41,33 @@ describe('helpers test', () => {
4041
});
4142
});
4243

44+
describe('makeTouchable', () => {
45+
46+
it('should create TouchableNativeFeedback for android', () => {
47+
Platform.select.mockImplementationOnce(o => {
48+
return o.android;
49+
});
50+
const { Touchable, defaultTouchableProps } = makeTouchable();
51+
expect(Touchable).to.be.equal(TouchableNativeFeedback);
52+
expect(defaultTouchableProps).to.be.an('object');
53+
});
54+
55+
it('should create TouchableHighlight for ios', () => {
56+
Platform.select.mockImplementationOnce(o => {
57+
return o.ios;
58+
});
59+
const { Touchable, defaultTouchableProps } = makeTouchable();
60+
expect(Touchable).to.be.equal(TouchableHighlight);
61+
expect(defaultTouchableProps).to.be.an('object');
62+
});
63+
64+
it('should return passed component', () => {
65+
const MyTouchable = () => null;
66+
const { Touchable, defaultTouchableProps } = makeTouchable(MyTouchable);
67+
expect(Touchable).to.be.equal(MyTouchable);
68+
expect(defaultTouchableProps).to.be.an('object');
69+
});
70+
71+
});
72+
4373
});

doc/api.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ To style `<MenuContext />` and backdrop component you can pass `customStyles` ob
3535
**Note:** `Style` type is any valid RN style parameter.
3636
**Note:** In addition to these styles we add also `{flex:1}`. You can disable it by e.g. `style={{flex:0}}`.
3737

38-
See more in custom styling [example](../examples/StylingExample.js).
38+
See more in custom [styling example](../examples/StylingExample.js).
3939

4040
## Menu
4141

@@ -92,12 +92,13 @@ To style `<MenuTrigger />` component you can pass `customStyles` object prop wit
9292
| Object key | Type | Notes |
9393
|---|---|---|
9494
|`triggerWrapper`|`Style`|Style of wrapping `View` component|
95-
|`triggerTouchable`|`Style`|Style props of `TouchableHighlight`. Supported keys: `activeOpacity`, `underlayColor`|
9695
|`triggerText`|`Style`|Style of `Text` component (used when `text` shorthand option is defined)|
96+
|`TriggerTouchableComponent`|`Component`|Touchable component of trigger. Default value is `TouchableHighlight` for iOS and `TouchableNativeFeedvack` for Android|
97+
|`triggerTouchable`|`Object`|Properties passed to the touchable component (e.g. `activeOpacity`, `underlayColor` for `TouchableHighlight`)|
9798

9899
**Note:** `Style` type is any valid RN style parameter.
99100

100-
See more in custom styling [example](../examples/StylingExample.js).
101+
See more in custom [styling example](../examples/StylingExample.js) and [touchable example](../examples/TouchableExample.js).
101102

102103
## MenuOptions
103104

@@ -121,14 +122,15 @@ To style `<MenuOptions />` and it's `<MenuOption />` components you can pass `cu
121122
|`optionsWrapper`|`Style`|Style of `View` component wrapping all options|
122123
|`optionsContainer`|`Style`|Style of wrapping `AnimatedView` component|
123124
|`optionWrapper`|`Style`|Style of `View` component wrapping single option|
124-
|`optionTouchable`|`Style`|Style props of `TouchableHighlight`. Supported keys: `activeOpacity`, `underlayColor`|
125125
|`optionText`|`Style`|Style of `Text` component (when `text` shorthand option is defined)|
126+
|`OptionTouchableComponent`|`Component`|Touchable component of option. Default value is `TouchableHighlight` for iOS and `TouchableNativeFeedvack` for Android|
127+
|`optionTouchable`|`Object`|Properties passed to the touchable component (e.g. `activeOpacity`, `underlayColor` for `TouchableHighlight`)|
126128

127129
**Note:** `optionWrapper`, `optionTouchable` and `optionText` styles of particular menu option can be overriden by `customStyles` prop of `<MenuOption />` component.
128130

129131
**Note:** `Style` type is any valid RN style parameter.
130132

131-
See more in custom styling [example](../examples/StylingExample.js).
133+
See more in custom [styling example](../examples/StylingExample.js) and [touchable example](../examples/TouchableExample.js).
132134

133135
## MenuOption
134136

@@ -154,12 +156,13 @@ To style `<MenuOption />` component you can pass `customStyles` object prop with
154156
| Object key | Type | Notes |
155157
|---|---|---|
156158
|`optionWrapper`|`Style`|Style of wrapping `View` component.|
157-
|`optionTouchable`|`Style`|Style props of `TouchableHighlight`. Supported keys: `activeOpacity`, `underlayColor`|
158159
|`optionText`|`Style`|Style of `Text` component (when `text` shorthand option is defined)|
160+
|`OptionTouchableComponent`|`Component`|Touchable component of option. Default value is `TouchableHighlight` for iOS and `TouchableNativeFeedvack` for Android|
161+
|`optionTouchable`|`Object`|Properties passed to the touchable component (e.g. `activeOpacity`, `underlayColor` for `TouchableHighlight`)|
159162

160163
**Note:** `Style` type is any valid RN style parameter.
161164

162-
See more in custom styling [example](../examples/StylingExample.js).
165+
See more in custom [styling example](../examples/StylingExample.js) and [touchable example](../examples/TouchableExample.js).
163166

164167
## renderers
165168

doc/examples.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const triggerStyles = {
104104
underlayColor: 'darkblue',
105105
activeOpacity: 70,
106106
},
107+
TriggerTouchableComponent: TouchableHighlight,
107108
};
108109
```
109110
For exact definitions of `customStyles` please refer to [API](api.md).

examples/Demo.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import ModalExample from './ModalExample';
1111
import StylingExample from './StylingExample';
1212
import NonRootExample from './NonRootExample';
1313
import NavigatorExample from './NavigatorExample';
14+
import TouchableExample from './TouchableExample';
1415

1516
const demos = [
1617
{ Component: BasicExample, name: 'Basic example' },
@@ -20,6 +21,7 @@ const demos = [
2021
{ Component: ExtensionExample, name: 'Extensions example' },
2122
{ Component: ModalExample, name: 'Modal example' },
2223
{ Component: StylingExample, name: 'Styling example' },
24+
{ Component: TouchableExample, name: 'Touchable config example' },
2325
{ Component: NonRootExample, name: 'Non root example' },
2426
{ Component: NavigatorExample, name: 'Example with react-native-router-flux' },
2527
];

examples/TouchableExample.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from 'react';
2+
import {
3+
TouchableOpacity,
4+
TouchableHighlight,
5+
TouchableWithoutFeedback,
6+
Button,
7+
} from 'react-native';
8+
import Menu, {
9+
MenuContext,
10+
MenuOptions,
11+
MenuOption,
12+
MenuTrigger,
13+
} from 'react-native-popup-menu';
14+
15+
const TouchableExample = () => (
16+
<MenuContext style={{flexDirection: 'column', padding: 30}}>
17+
18+
<Menu>
19+
<MenuTrigger
20+
customStyles={{
21+
TriggerTouchableComponent: Button,
22+
triggerTouchable: { title: 'Select (Custom Touchables)' }
23+
}}
24+
/>
25+
<MenuOptions>
26+
<MenuOption text='Default' />
27+
<MenuOption text='TouchableOpacity' customStyles={{
28+
OptionTouchableComponent: TouchableOpacity,
29+
optionTouchable: touchableOpacityProps,
30+
}} />
31+
<MenuOption text='TouchableHighlight' customStyles={{
32+
OptionTouchableComponent: TouchableHighlight,
33+
optionTouchable: touchableHighlightProps,
34+
}} />
35+
<MenuOption text='TouchableWithoutFeedback' customStyles={{
36+
OptionTouchableComponent: TouchableWithoutFeedback,
37+
}} />
38+
</MenuOptions>
39+
</Menu>
40+
41+
<Menu style={{paddingTop: 30}}>
42+
<MenuTrigger
43+
customStyles={{
44+
TriggerTouchableComponent: Button,
45+
triggerTouchable: { title: 'Select (all TouchableOpacity)' }
46+
}}
47+
/>
48+
<MenuOptions customStyles={{
49+
OptionTouchableComponent: TouchableOpacity,
50+
optionTouchable: touchableOpacityProps,
51+
}}>
52+
<MenuOption text='Option 1' />
53+
<MenuOption text='Option 2' />
54+
<MenuOption text='Option 3' />
55+
<MenuOption text='Option 4' />
56+
</MenuOptions>
57+
</Menu>
58+
59+
</MenuContext>
60+
);
61+
62+
const touchableOpacityProps = {
63+
activeOpacity: 0.6,
64+
};
65+
66+
const touchableHighlightProps = {
67+
activeOpacity: 0.5,
68+
underlayColor: 'green',
69+
};
70+
71+
export default TouchableExample;

src/MenuOption.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { Component } from 'react';
2-
import { View, StyleSheet, TouchableHighlight, Text } from 'react-native';
2+
import { View, StyleSheet, Text } from 'react-native';
33
import { debug } from './logger';
4+
import { makeTouchable } from './helpers';
45

56
export default class MenuOption extends Component {
67

@@ -23,14 +24,17 @@ export default class MenuOption extends Component {
2324
</View>
2425
);
2526
}
27+
const { Touchable, defaultTouchableProps } = makeTouchable(customStyles.OptionTouchableComponent);
2628
return (
27-
<TouchableHighlight onPress={() => this._onSelect()}
28-
style={[defaultStyles.option, customStyles.optionWrapper, style]}
29-
{...defaultTouchableStyles} {...customStyles.optionTouchable}>
30-
<View>
29+
<Touchable
30+
onPress={() => this._onSelect()}
31+
{...defaultTouchableProps}
32+
{...customStyles.optionTouchable}
33+
>
34+
<View style={[defaultStyles.option, customStyles.optionWrapper, style]}>
3135
{text ? <Text style={customStyles.optionText}>{text}</Text> : children}
3236
</View>
33-
</TouchableHighlight>
37+
</Touchable>
3438
);
3539
}
3640
}
@@ -62,7 +66,3 @@ const defaultStyles = StyleSheet.create({
6266
color: '#ccc',
6367
},
6468
});
65-
66-
const defaultTouchableStyles = {
67-
underlayColor: 'rgba(0, 0, 0, 0.1)',
68-
};

0 commit comments

Comments
 (0)