Skip to content

Commit 1843709

Browse files
bestanderfacebook-github-bot-4
authored andcommitted
Open sourced spinner aka picker aka drop down for android
Reviewed By: mkonicek Differential Revision: D2830803 fb-gh-sync-id: e6b6fcdbe33d942180cf2c1041076ad71d0473ce
1 parent cd89016 commit 1843709

11 files changed

Lines changed: 779 additions & 0 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* The examples provided by Facebook are for non-commercial testing and
3+
* evaluation purposes only.
4+
*
5+
* Facebook reserves all rights not expressly granted.
6+
*
7+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
8+
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9+
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
10+
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
11+
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
12+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13+
*
14+
* @flow
15+
*/
16+
'use strict';
17+
18+
const React = require('react-native');
19+
const UIExplorerBlock = require('UIExplorerBlock');
20+
const UIExplorerPage = require('UIExplorerPage');
21+
22+
const {
23+
PickerAndroid,
24+
Text,
25+
TouchableWithoutFeedback,
26+
} = React;
27+
const Item = PickerAndroid.Item;
28+
29+
const PickerAndroidExample = React.createClass({
30+
getInitialState: function() {
31+
return {
32+
selected1: 'key1',
33+
selected2: 'key1',
34+
selected3: 'key1',
35+
selected4: 'key1',
36+
color: 'red',
37+
mode: PickerAndroid.MODE_DIALOG,
38+
};
39+
},
40+
41+
displayName: 'Android Picker',
42+
43+
render: function() {
44+
return (
45+
<UIExplorerPage title="<PickerAndroid>">
46+
<UIExplorerBlock title="Basic Picker">
47+
<PickerAndroid
48+
style={{width: 100, height: 56}}
49+
onSelect={this.onSelect.bind(this, 'selected1')}>
50+
<Item text="hello" value="key0" selected={this.state.selected1 === 'key0'} />
51+
<Item text="world" value="key1" selected={this.state.selected1 === 'key1'} />
52+
</PickerAndroid>
53+
</UIExplorerBlock>
54+
<UIExplorerBlock title="Disabled picker">
55+
<PickerAndroid style={{width: 100, height: 56}} enabled={false}>
56+
<Item text="hello" value="key0" selected={this.state.selected1 === 'key0'} />
57+
<Item text="world" value="key1" selected={this.state.selected1 === 'key1'} />
58+
</PickerAndroid>
59+
</UIExplorerBlock>
60+
<UIExplorerBlock title="Dropdown Picker">
61+
<PickerAndroid
62+
style={{width: 100, height: 56}}
63+
onSelect={this.onSelect.bind(this, 'selected2')}
64+
mode="dropdown">
65+
<Item text="hello" value="key0" selected={this.state.selected2 === 'key0'} />
66+
<Item text="world" value="key1" selected={this.state.selected2 === 'key1'} />
67+
</PickerAndroid>
68+
</UIExplorerBlock>
69+
<UIExplorerBlock title="Alternating Picker">
70+
<PickerAndroid
71+
style={{width: 100, height: 56}}
72+
onSelect={this.onSelect.bind(this, 'selected3')}
73+
mode={this.state.mode}>
74+
<Item text="hello" value="key0" selected={this.state.selected3 === 'key0'} />
75+
<Item text="world" value="key1" selected={this.state.selected3 === 'key1'} />
76+
</PickerAndroid>
77+
<TouchableWithoutFeedback onPress={this.changeMode}>
78+
<Text>Tap here to switch between dialog/dropdown.</Text>
79+
</TouchableWithoutFeedback>
80+
</UIExplorerBlock>
81+
<UIExplorerBlock title="Picker with prompt message">
82+
<PickerAndroid
83+
style={{width: 100, height: 56}}
84+
onSelect={this.onSelect.bind(this, 'selected4')}
85+
prompt="Pick one, just one">
86+
<Item text="hello" value="key0" selected={this.state.selected4 === 'key0'} />
87+
<Item text="world" value="key1" selected={this.state.selected4 === 'key1'} />
88+
</PickerAndroid>
89+
</UIExplorerBlock>
90+
<UIExplorerBlock title="Picker with no listener">
91+
<PickerAndroid style={{width: 100, height: 56}}>
92+
<Item text="hello" value="key0" />
93+
<Item text="world" value="key1" />
94+
</PickerAndroid>
95+
<Text>
96+
You can not change the value of this picker because it doesn't set a selected prop on
97+
its items.
98+
</Text>
99+
</UIExplorerBlock>
100+
<UIExplorerBlock title="Colorful pickers">
101+
<PickerAndroid style={{width: 100, height: 56, color: 'black'}}
102+
onSelect={this.onSelect.bind(this, 'color')}
103+
mode="dropdown">
104+
<Item text="red" color="red" value="red" selected={this.state.color === 'red'}/>
105+
<Item text="green" color="green" value="green" selected={this.state.color === 'green'}/>
106+
<Item text="blue" color="blue" value="blue" selected={this.state.color === 'blue'}/>
107+
</PickerAndroid>
108+
<PickerAndroid style={{width: 100, height: 56}}
109+
onSelect={this.onSelect.bind(this, 'color')}
110+
mode="dialog">
111+
<Item text="red" color="red" value="red" selected={this.state.color === 'red'}/>
112+
<Item text="green" color="green" value="green" selected={this.state.color === 'green'}/>
113+
<Item text="blue" color="blue" value="blue" selected={this.state.color === 'blue'} />
114+
</PickerAndroid>
115+
</UIExplorerBlock>
116+
</UIExplorerPage>
117+
);
118+
},
119+
120+
changeMode: function() {
121+
const newMode = this.state.mode === PickerAndroid.MODE_DIALOG
122+
? PickerAndroid.MODE_DROPDOWN
123+
: PickerAndroid.MODE_DIALOG;
124+
this.setState({mode: newMode});
125+
},
126+
127+
onSelect: function(key, value) {
128+
const newState = {};
129+
newState[key] = value;
130+
this.setState(newState);
131+
},
132+
});
133+
134+
exports.title = '<PickerAndroid>';
135+
exports.displayName = 'PickerAndroidExample';
136+
exports.description = 'The Android Picker component provides multiple options to choose from';
137+
exports.examples = [
138+
{
139+
title: 'PickerAndroidExample',
140+
render(): ReactElement { return <PickerAndroidExample />; }
141+
},
142+
];

Examples/UIExplorer/UIExplorerList.android.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ var COMPONENTS = [
2929
require('./ScrollViewSimpleExample'),
3030
require('./SwitchExample'),
3131
require('./RefreshControlExample'),
32+
require('./PickerAndroidExample'),
3233
require('./PullToRefreshViewAndroidExample.android'),
3334
require('./TextExample.android'),
3435
require('./TextInputExample.android'),
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule PickerAndroid
10+
* @flow
11+
*/
12+
13+
'use strict';
14+
15+
var ColorPropType = require('ColorPropType');
16+
var React = require('React');
17+
var ReactChildren = require('ReactChildren');
18+
var ReactPropTypes = require('ReactPropTypes');
19+
var StyleSheetPropType = require('StyleSheetPropType');
20+
var View = require('View');
21+
var ViewStylePropTypes = require('ViewStylePropTypes');
22+
23+
var processColor = require('processColor');
24+
var requireNativeComponent = require('requireNativeComponent');
25+
26+
var MODE_DIALOG = 'dialog';
27+
var MODE_DROPDOWN = 'dropdown';
28+
var REF_PICKER = 'picker';
29+
30+
var pickerStyleType = StyleSheetPropType({
31+
...ViewStylePropTypes,
32+
color: ColorPropType,
33+
});
34+
35+
type Items = {
36+
selected: number;
37+
items: any[];
38+
};
39+
40+
type Event = Object;
41+
42+
/**
43+
* Individual selectable item in a Picker.
44+
*/
45+
var Item = React.createClass({
46+
47+
propTypes: {
48+
/**
49+
* Color of this item's text.
50+
*/
51+
color: ColorPropType,
52+
/**
53+
* Text to display for this item.
54+
*/
55+
text: ReactPropTypes.string.isRequired,
56+
/**
57+
* The value to be passed to picker's `onSelect` callback when this item is selected.
58+
*/
59+
value: ReactPropTypes.string,
60+
/**
61+
* If `true`, this item is selected and shown in the picker.
62+
* Usually this is set based on state.
63+
*/
64+
selected: ReactPropTypes.bool,
65+
/**
66+
* Used to locate this view in end-to-end tests.
67+
*/
68+
testID: ReactPropTypes.string,
69+
},
70+
71+
render: function() {
72+
throw new Error('Picker items should never be rendered');
73+
},
74+
75+
});
76+
77+
/**
78+
* <PickerAndroid> - A React component that renders the native Picker widget on Android. The items
79+
* that can be selected are specified as children views of type Item. Example usage:
80+
*
81+
* <PickerAndroid>
82+
* <PickerAndroid.Item text="Java" value="js" />
83+
* <PickerAndroid.Item text="JavaScript" value="java" selected={true} />
84+
* </PickerAndroid>
85+
*/
86+
var PickerAndroid = React.createClass({
87+
88+
propTypes: {
89+
...View.propTypes,
90+
style: pickerStyleType,
91+
/**
92+
* If set to false, the picker will be disabled, i.e. the user will not be able to make a
93+
* selection.
94+
*/
95+
enabled: ReactPropTypes.bool,
96+
/**
97+
* Specifies how to display the selection items when the user taps on the picker:
98+
*
99+
* - dialog: Show a modal dialog
100+
* - dropdown: Shows a dropdown anchored to the picker view
101+
*/
102+
mode: ReactPropTypes.oneOf([MODE_DIALOG, MODE_DROPDOWN]),
103+
/**
104+
* Callback for when an item is selected. This is called with the following parameters:
105+
*
106+
* - `itemValue`: the `value` prop of the item that was selected
107+
* - `itemPosition`: the index of the selected item in this picker
108+
*/
109+
onSelect: ReactPropTypes.func,
110+
/**
111+
* Prompt string for this picker, currently only used in `dialog` mode as the title of the
112+
* dialog.
113+
*/
114+
prompt: ReactPropTypes.string,
115+
/**
116+
* Used to locate this view in end-to-end tests.
117+
*/
118+
testID: ReactPropTypes.string,
119+
},
120+
121+
statics: {
122+
Item: Item,
123+
MODE_DIALOG: MODE_DIALOG,
124+
MODE_DROPDOWN: MODE_DROPDOWN,
125+
},
126+
127+
getDefaultProps: function() {
128+
return {
129+
mode: MODE_DIALOG,
130+
};
131+
},
132+
133+
render: function() {
134+
var Picker = this.props.mode === MODE_DROPDOWN ? DropdownPicker : DialogPicker;
135+
136+
var { selected, items } = this._getItems();
137+
138+
var nativeProps = {
139+
enabled: this.props.enabled,
140+
items: items,
141+
mode: this.props.mode,
142+
onSelect: this._onSelect,
143+
prompt: this.props.prompt,
144+
selected: selected,
145+
style: this.props.style,
146+
testID: this.props.testID,
147+
};
148+
149+
return <Picker ref={REF_PICKER} {...nativeProps} />;
150+
},
151+
152+
/**
153+
* Transform this view's children into an array of items to be passed to the native component.
154+
* Since we're traversing the children, also determine the selected position.
155+
*
156+
* @returns an object with two keys:
157+
*
158+
* - `selected` (number) - the index of the selected item
159+
* - `items` (array) - the items of this picker, as an array of strings
160+
*/
161+
_getItems: function(): Items {
162+
var items = [];
163+
var selected = 0;
164+
ReactChildren.forEach(this.props.children, function(child, index) {
165+
var childProps = Object.assign({}, child.props);
166+
if (childProps.color) {
167+
childProps.color = processColor(childProps.color);
168+
}
169+
items.push(childProps);
170+
if (childProps.selected) {
171+
selected = index;
172+
}
173+
});
174+
return {
175+
selected: selected,
176+
items: items,
177+
};
178+
},
179+
180+
_onSelect: function(event: Event) {
181+
if (this.props.onSelect) {
182+
var position = event.nativeEvent.position;
183+
if (position >= 0) {
184+
var value = this.props.children[position].props.value;
185+
this.props.onSelect(value, position);
186+
} else {
187+
this.props.onSelect(null, position);
188+
}
189+
}
190+
191+
// The native Picker has changed, but the props haven't (yet). If
192+
// the handler decides to not accept the new value or do something
193+
// else with it we might end up in a bad state, so we reset the
194+
// selection on the native component.
195+
// tl;dr: PickerAndroid is a controlled component.
196+
var { selected } = this._getItems();
197+
if (this.refs[REF_PICKER]) {
198+
this.refs[REF_PICKER].setNativeProps({selected: selected});
199+
}
200+
},
201+
202+
});
203+
204+
var cfg = {
205+
nativeOnly: {
206+
items: true,
207+
selected: true,
208+
}
209+
}
210+
var DropdownPicker = requireNativeComponent('AndroidDropdownPicker', PickerAndroid, cfg);
211+
var DialogPicker = requireNativeComponent('AndroidDialogPicker', PickerAndroid, cfg);
212+
213+
module.exports = PickerAndroid;

Libraries/react-native/react-native.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var ReactNative = {
2525
get Modal() { return require('Modal'); },
2626
get Navigator() { return require('Navigator'); },
2727
get NavigatorIOS() { return require('NavigatorIOS'); },
28+
get PickerAndroid() { return require('PickerAndroid'); },
2829
get PickerIOS() { return require('PickerIOS'); },
2930
get ProgressBarAndroid() { return require('ProgressBarAndroid'); },
3031
get ProgressViewIOS() { return require('ProgressViewIOS'); },

Libraries/react-native/react-native.js.flow

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
3737
Modal: require('Modal'),
3838
Navigator: require('Navigator'),
3939
NavigatorIOS: require('NavigatorIOS'),
40+
PickerAndroid: require('PickerAndroid'),
4041
PickerIOS: require('PickerIOS'),
4142
ProgressBarAndroid: require('ProgressBarAndroid'),
4243
ProgressViewIOS: require('ProgressViewIOS'),

ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import com.facebook.react.views.art.ARTSurfaceViewManager;
3333
import com.facebook.react.views.drawer.ReactDrawerLayoutManager;
3434
import com.facebook.react.views.image.ReactImageManager;
35+
import com.facebook.react.views.picker.ReactDialogPickerManager;
36+
import com.facebook.react.views.picker.ReactDropdownPickerManager;
3537
import com.facebook.react.views.progressbar.ReactProgressBarViewManager;
3638
import com.facebook.react.views.recyclerview.RecyclerViewBackedScrollViewManager;
3739
import com.facebook.react.views.scroll.ReactHorizontalScrollViewManager;
@@ -82,7 +84,9 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
8284
ARTRenderableViewManager.createARTShapeViewManager(),
8385
ARTRenderableViewManager.createARTTextViewManager(),
8486
new ARTSurfaceViewManager(),
87+
new ReactDialogPickerManager(),
8588
new ReactDrawerLayoutManager(),
89+
new ReactDropdownPickerManager(),
8690
new ReactHorizontalScrollViewManager(),
8791
new ReactImageManager(),
8892
new ReactProgressBarViewManager(),

0 commit comments

Comments
 (0)