Permalink
Browse files

Open sourced spinner aka picker aka drop down for android

Reviewed By: mkonicek

Differential Revision: D2830803

fb-gh-sync-id: e6b6fcdbe33d942180cf2c1041076ad71d0473ce
  • Loading branch information...
bestander authored and facebook-github-bot-4 committed Jan 15, 2016
1 parent cd89016 commit 18437093f2664beedd3239bb525d7daa3f2bcdbd
@@ -0,0 +1,142 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+const React = require('react-native');
+const UIExplorerBlock = require('UIExplorerBlock');
+const UIExplorerPage = require('UIExplorerPage');
+
+const {
+ PickerAndroid,
+ Text,
+ TouchableWithoutFeedback,
+} = React;
+const Item = PickerAndroid.Item;
+
+const PickerAndroidExample = React.createClass({
+ getInitialState: function() {
+ return {
+ selected1: 'key1',
+ selected2: 'key1',
+ selected3: 'key1',
+ selected4: 'key1',
+ color: 'red',
+ mode: PickerAndroid.MODE_DIALOG,
+ };
+ },
+
+ displayName: 'Android Picker',
+
+ render: function() {
+ return (
+ <UIExplorerPage title="<PickerAndroid>">
+ <UIExplorerBlock title="Basic Picker">
+ <PickerAndroid
+ style={{width: 100, height: 56}}
+ onSelect={this.onSelect.bind(this, 'selected1')}>
+ <Item text="hello" value="key0" selected={this.state.selected1 === 'key0'} />
+ <Item text="world" value="key1" selected={this.state.selected1 === 'key1'} />
+ </PickerAndroid>
+ </UIExplorerBlock>
+ <UIExplorerBlock title="Disabled picker">
+ <PickerAndroid style={{width: 100, height: 56}} enabled={false}>
+ <Item text="hello" value="key0" selected={this.state.selected1 === 'key0'} />
+ <Item text="world" value="key1" selected={this.state.selected1 === 'key1'} />
+ </PickerAndroid>
+ </UIExplorerBlock>
+ <UIExplorerBlock title="Dropdown Picker">
+ <PickerAndroid
+ style={{width: 100, height: 56}}
+ onSelect={this.onSelect.bind(this, 'selected2')}
+ mode="dropdown">
+ <Item text="hello" value="key0" selected={this.state.selected2 === 'key0'} />
+ <Item text="world" value="key1" selected={this.state.selected2 === 'key1'} />
+ </PickerAndroid>
+ </UIExplorerBlock>
+ <UIExplorerBlock title="Alternating Picker">
+ <PickerAndroid
+ style={{width: 100, height: 56}}
+ onSelect={this.onSelect.bind(this, 'selected3')}
+ mode={this.state.mode}>
+ <Item text="hello" value="key0" selected={this.state.selected3 === 'key0'} />
+ <Item text="world" value="key1" selected={this.state.selected3 === 'key1'} />
+ </PickerAndroid>
+ <TouchableWithoutFeedback onPress={this.changeMode}>
+ <Text>Tap here to switch between dialog/dropdown.</Text>
+ </TouchableWithoutFeedback>
+ </UIExplorerBlock>
+ <UIExplorerBlock title="Picker with prompt message">
+ <PickerAndroid
+ style={{width: 100, height: 56}}
+ onSelect={this.onSelect.bind(this, 'selected4')}
+ prompt="Pick one, just one">
+ <Item text="hello" value="key0" selected={this.state.selected4 === 'key0'} />
+ <Item text="world" value="key1" selected={this.state.selected4 === 'key1'} />
+ </PickerAndroid>
+ </UIExplorerBlock>
+ <UIExplorerBlock title="Picker with no listener">
+ <PickerAndroid style={{width: 100, height: 56}}>
+ <Item text="hello" value="key0" />
+ <Item text="world" value="key1" />
+ </PickerAndroid>
+ <Text>
+ You can not change the value of this picker because it doesn't set a selected prop on
+ its items.
+ </Text>
+ </UIExplorerBlock>
+ <UIExplorerBlock title="Colorful pickers">
+ <PickerAndroid style={{width: 100, height: 56, color: 'black'}}
+ onSelect={this.onSelect.bind(this, 'color')}
+ mode="dropdown">
+ <Item text="red" color="red" value="red" selected={this.state.color === 'red'}/>
+ <Item text="green" color="green" value="green" selected={this.state.color === 'green'}/>
+ <Item text="blue" color="blue" value="blue" selected={this.state.color === 'blue'}/>
+ </PickerAndroid>
+ <PickerAndroid style={{width: 100, height: 56}}
+ onSelect={this.onSelect.bind(this, 'color')}
+ mode="dialog">
+ <Item text="red" color="red" value="red" selected={this.state.color === 'red'}/>
+ <Item text="green" color="green" value="green" selected={this.state.color === 'green'}/>
+ <Item text="blue" color="blue" value="blue" selected={this.state.color === 'blue'} />
+ </PickerAndroid>
+ </UIExplorerBlock>
+ </UIExplorerPage>
+ );
+ },
+
+ changeMode: function() {
+ const newMode = this.state.mode === PickerAndroid.MODE_DIALOG
+ ? PickerAndroid.MODE_DROPDOWN
+ : PickerAndroid.MODE_DIALOG;
+ this.setState({mode: newMode});
+ },
+
+ onSelect: function(key, value) {
+ const newState = {};
+ newState[key] = value;
+ this.setState(newState);
+ },
+});
+
+exports.title = '<PickerAndroid>';
+exports.displayName = 'PickerAndroidExample';
+exports.description = 'The Android Picker component provides multiple options to choose from';
+exports.examples = [
+ {
+ title: 'PickerAndroidExample',
+ render(): ReactElement { return <PickerAndroidExample />; }
+ },
+];
@@ -29,6 +29,7 @@ var COMPONENTS = [
require('./ScrollViewSimpleExample'),
require('./SwitchExample'),
require('./RefreshControlExample'),
+ require('./PickerAndroidExample'),
require('./PullToRefreshViewAndroidExample.android'),
require('./TextExample.android'),
require('./TextInputExample.android'),
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule PickerAndroid
+ * @flow
+ */
+
+'use strict';
+
+var ColorPropType = require('ColorPropType');
+var React = require('React');
+var ReactChildren = require('ReactChildren');
+var ReactPropTypes = require('ReactPropTypes');
+var StyleSheetPropType = require('StyleSheetPropType');
+var View = require('View');
+var ViewStylePropTypes = require('ViewStylePropTypes');
+
+var processColor = require('processColor');
+var requireNativeComponent = require('requireNativeComponent');
+
+var MODE_DIALOG = 'dialog';
+var MODE_DROPDOWN = 'dropdown';
+var REF_PICKER = 'picker';
+
+var pickerStyleType = StyleSheetPropType({
+ ...ViewStylePropTypes,
+ color: ColorPropType,
+});
+
+type Items = {
+ selected: number;
+ items: any[];
+};
+
+type Event = Object;
+
+/**
+ * Individual selectable item in a Picker.
+ */
+var Item = React.createClass({
+
+ propTypes: {
+ /**
+ * Color of this item's text.
+ */
+ color: ColorPropType,
+ /**
+ * Text to display for this item.
+ */
+ text: ReactPropTypes.string.isRequired,
+ /**
+ * The value to be passed to picker's `onSelect` callback when this item is selected.
+ */
+ value: ReactPropTypes.string,
+ /**
+ * If `true`, this item is selected and shown in the picker.
+ * Usually this is set based on state.
+ */
+ selected: ReactPropTypes.bool,
+ /**
+ * Used to locate this view in end-to-end tests.
+ */
+ testID: ReactPropTypes.string,
+ },
+
+ render: function() {
+ throw new Error('Picker items should never be rendered');
+ },
+
+});
+
+/**
+ * <PickerAndroid> - A React component that renders the native Picker widget on Android. The items
+ * that can be selected are specified as children views of type Item. Example usage:
+ *
+ * <PickerAndroid>
+ * <PickerAndroid.Item text="Java" value="js" />
+ * <PickerAndroid.Item text="JavaScript" value="java" selected={true} />
+ * </PickerAndroid>
+ */
+var PickerAndroid = React.createClass({
+
+ propTypes: {
+ ...View.propTypes,
+ style: pickerStyleType,
+ /**
+ * If set to false, the picker will be disabled, i.e. the user will not be able to make a
+ * selection.
+ */
+ enabled: ReactPropTypes.bool,
+ /**
+ * Specifies how to display the selection items when the user taps on the picker:
+ *
+ * - dialog: Show a modal dialog
+ * - dropdown: Shows a dropdown anchored to the picker view
+ */
+ mode: ReactPropTypes.oneOf([MODE_DIALOG, MODE_DROPDOWN]),
+ /**
+ * Callback for when an item is selected. This is called with the following parameters:
+ *
+ * - `itemValue`: the `value` prop of the item that was selected
+ * - `itemPosition`: the index of the selected item in this picker
+ */
+ onSelect: ReactPropTypes.func,
+ /**
+ * Prompt string for this picker, currently only used in `dialog` mode as the title of the
+ * dialog.
+ */
+ prompt: ReactPropTypes.string,
+ /**
+ * Used to locate this view in end-to-end tests.
+ */
+ testID: ReactPropTypes.string,
+ },
+
+ statics: {
+ Item: Item,
+ MODE_DIALOG: MODE_DIALOG,
+ MODE_DROPDOWN: MODE_DROPDOWN,
+ },
+
+ getDefaultProps: function() {
+ return {
+ mode: MODE_DIALOG,
+ };
+ },
+
+ render: function() {
+ var Picker = this.props.mode === MODE_DROPDOWN ? DropdownPicker : DialogPicker;
+
+ var { selected, items } = this._getItems();
+
+ var nativeProps = {
+ enabled: this.props.enabled,
+ items: items,
+ mode: this.props.mode,
+ onSelect: this._onSelect,
+ prompt: this.props.prompt,
+ selected: selected,
+ style: this.props.style,
+ testID: this.props.testID,
+ };
+
+ return <Picker ref={REF_PICKER} {...nativeProps} />;
+ },
+
+ /**
+ * Transform this view's children into an array of items to be passed to the native component.
+ * Since we're traversing the children, also determine the selected position.
+ *
+ * @returns an object with two keys:
+ *
+ * - `selected` (number) - the index of the selected item
+ * - `items` (array) - the items of this picker, as an array of strings
+ */
+ _getItems: function(): Items {
+ var items = [];
+ var selected = 0;
+ ReactChildren.forEach(this.props.children, function(child, index) {
+ var childProps = Object.assign({}, child.props);
+ if (childProps.color) {
+ childProps.color = processColor(childProps.color);
+ }
+ items.push(childProps);
+ if (childProps.selected) {
+ selected = index;
+ }
+ });
+ return {
+ selected: selected,
+ items: items,
+ };
+ },
+
+ _onSelect: function(event: Event) {
+ if (this.props.onSelect) {
+ var position = event.nativeEvent.position;
+ if (position >= 0) {
+ var value = this.props.children[position].props.value;
+ this.props.onSelect(value, position);
+ } else {
+ this.props.onSelect(null, position);
+ }
+ }
+
+ // The native Picker has changed, but the props haven't (yet). If
+ // the handler decides to not accept the new value or do something
+ // else with it we might end up in a bad state, so we reset the
+ // selection on the native component.
+ // tl;dr: PickerAndroid is a controlled component.
+ var { selected } = this._getItems();
+ if (this.refs[REF_PICKER]) {
+ this.refs[REF_PICKER].setNativeProps({selected: selected});
+ }
+ },
+
+});
+
+var cfg = {
+ nativeOnly: {
+ items: true,
+ selected: true,
+ }
+}
+var DropdownPicker = requireNativeComponent('AndroidDropdownPicker', PickerAndroid, cfg);
+var DialogPicker = requireNativeComponent('AndroidDialogPicker', PickerAndroid, cfg);
+
+module.exports = PickerAndroid;
@@ -25,6 +25,7 @@ var ReactNative = {
get Modal() { return require('Modal'); },
get Navigator() { return require('Navigator'); },
get NavigatorIOS() { return require('NavigatorIOS'); },
+ get PickerAndroid() { return require('PickerAndroid'); },
get PickerIOS() { return require('PickerIOS'); },
get ProgressBarAndroid() { return require('ProgressBarAndroid'); },
get ProgressViewIOS() { return require('ProgressViewIOS'); },
@@ -37,6 +37,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
Modal: require('Modal'),
Navigator: require('Navigator'),
NavigatorIOS: require('NavigatorIOS'),
+ PickerAndroid: require('PickerAndroid'),
PickerIOS: require('PickerIOS'),
ProgressBarAndroid: require('ProgressBarAndroid'),
ProgressViewIOS: require('ProgressViewIOS'),
@@ -32,6 +32,8 @@
import com.facebook.react.views.art.ARTSurfaceViewManager;
import com.facebook.react.views.drawer.ReactDrawerLayoutManager;
import com.facebook.react.views.image.ReactImageManager;
+import com.facebook.react.views.picker.ReactDialogPickerManager;
+import com.facebook.react.views.picker.ReactDropdownPickerManager;
import com.facebook.react.views.progressbar.ReactProgressBarViewManager;
import com.facebook.react.views.recyclerview.RecyclerViewBackedScrollViewManager;
import com.facebook.react.views.scroll.ReactHorizontalScrollViewManager;
@@ -82,7 +84,9 @@
ARTRenderableViewManager.createARTShapeViewManager(),
ARTRenderableViewManager.createARTTextViewManager(),
new ARTSurfaceViewManager(),
+ new ReactDialogPickerManager(),
new ReactDrawerLayoutManager(),
+ new ReactDropdownPickerManager(),
new ReactHorizontalScrollViewManager(),
new ReactImageManager(),
new ReactProgressBarViewManager(),
Oops, something went wrong.

3 comments on commit 1843709

@mkonicek

This comment has been minimized.

Show comment
Hide comment
@mkonicek

mkonicek Jan 15, 2016

Contributor

🚀

Contributor

mkonicek replied Jan 15, 2016

🚀

@kanerogers

This comment has been minimized.

Show comment
Hide comment

Oh my GOD.

@danleveille

This comment has been minimized.

Show comment
Hide comment
@danleveille

danleveille Jan 27, 2016

So excited about this. Trying it now! It's not possible to style the actual dropdown or dialog, is it? (I'm trying to set the item color to white, but it shows up white on white in the dropdown.)

So excited about this. Trying it now! It's not possible to style the actual dropdown or dialog, is it? (I'm trying to set the item color to white, but it shows up white on white in the dropdown.)

Please sign in to comment.