diff --git a/packages/react-native-dom/Libraries/Components/Picker/Picker.dom.js b/packages/react-native-dom/Libraries/Components/Picker/Picker.dom.js
new file mode 100644
index 000000000..d35322778
--- /dev/null
+++ b/packages/react-native-dom/Libraries/Components/Picker/Picker.dom.js
@@ -0,0 +1,156 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @providesModule Picker
+ * @flow
+ */
+
+"use strict";
+
+const ColorPropType = require("ColorPropType");
+const PickerDOM = require("PickerDOM");
+const Platform = require("Platform");
+const React = require("React");
+const PropTypes = require("prop-types");
+const StyleSheetPropType = require("StyleSheetPropType");
+const TextStylePropTypes = require("TextStylePropTypes");
+const UnimplementedView = require("UnimplementedView");
+const ViewPropTypes = require("ViewPropTypes");
+const ViewStylePropTypes = require("ViewStylePropTypes");
+
+const itemStylePropType = StyleSheetPropType(TextStylePropTypes);
+
+const pickerStyleType = StyleSheetPropType({
+ ...ViewStylePropTypes,
+ color: ColorPropType
+});
+
+const MODE_DIALOG = "dialog";
+const MODE_DROPDOWN = "dropdown";
+
+/**
+ * Individual selectable item in a Picker.
+ */
+class PickerItem extends React.Component<{
+ label: string,
+ value?: any,
+ color?: ColorPropType,
+ testID?: string
+}> {
+ static propTypes = {
+ /**
+ * Text to display for this item.
+ */
+ label: PropTypes.string.isRequired,
+ /**
+ * The value to be passed to picker's `onValueChange` callback when
+ * this item is selected. Can be a string or an integer.
+ */
+ value: PropTypes.any,
+ /**
+ * Color of this item's text.
+ * @platform android
+ */
+ color: ColorPropType,
+ /**
+ * Used to locate the item in end-to-end tests.
+ */
+ testID: PropTypes.string
+ };
+
+ render() {
+ // The items are not rendered directly
+ throw null;
+ }
+}
+
+/**
+ * Renders the native picker component on iOS and Android. Example:
+ *
+ * this.setState({language: itemValue})}>
+ *
+ *
+ *
+ */
+class Picker extends React.Component<{
+ style?: $FlowFixMe,
+ selectedValue?: any,
+ onValueChange?: Function,
+ enabled?: boolean,
+ mode?: "dialog" | "dropdown",
+ itemStyle?: $FlowFixMe,
+ prompt?: string,
+ testID?: string
+}> {
+ /**
+ * On Android, display the options in a dialog.
+ */
+ static MODE_DIALOG = MODE_DIALOG;
+
+ /**
+ * On Android, display the options in a dropdown (this is the default).
+ */
+ static MODE_DROPDOWN = MODE_DROPDOWN;
+
+ static Item = PickerItem;
+
+ static defaultProps = {
+ mode: MODE_DIALOG
+ };
+
+ // $FlowFixMe(>=0.41.0)
+ static propTypes = {
+ ...ViewPropTypes,
+ style: pickerStyleType,
+ /**
+ * Value matching value of one of the items. Can be a string or an integer.
+ */
+ selectedValue: PropTypes.any,
+ /**
+ * 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
+ */
+ onValueChange: PropTypes.func,
+ /**
+ * If set to false, the picker will be disabled, i.e. the user will not be able to make a
+ * selection.
+ * @platform android
+ */
+ enabled: PropTypes.bool,
+ /**
+ * On Android, specifies how to display the selection items when the user taps on the picker:
+ *
+ * - 'dialog': Show a modal dialog. This is the default.
+ * - 'dropdown': Shows a dropdown anchored to the picker view
+ *
+ * @platform android
+ */
+ mode: PropTypes.oneOf(["dialog", "dropdown"]),
+ /**
+ * Style to apply to each of the item labels.
+ * @platform ios
+ */
+ itemStyle: itemStylePropType,
+ /**
+ * Prompt string for this picker, used on Android in dialog mode as the title of the dialog.
+ * @platform android
+ */
+ prompt: PropTypes.string,
+ /**
+ * Used to locate this view in end-to-end tests.
+ */
+ testID: PropTypes.string
+ };
+
+ render() {
+ return {this.props.children};
+ }
+}
+
+module.exports = Picker;
diff --git a/packages/react-native-dom/Libraries/Components/Picker/PickerDOM.dom.js b/packages/react-native-dom/Libraries/Components/Picker/PickerDOM.dom.js
new file mode 100644
index 000000000..fce3c820f
--- /dev/null
+++ b/packages/react-native-dom/Libraries/Components/Picker/PickerDOM.dom.js
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @providesModule PickerDOM
+ *
+ * This is a controlled component version of RCTPickerDOM
+ */
+"use strict";
+
+const ColorPropType = require("ColorPropType");
+const NativeMethodsMixin = require("NativeMethodsMixin");
+const React = require("React");
+const PropTypes = require("prop-types");
+const StyleSheet = require("StyleSheet");
+const StyleSheetPropType = require("StyleSheetPropType");
+const TextStylePropTypes = require("TextStylePropTypes");
+const View = require("View");
+const ViewPropTypes = require("ViewPropTypes");
+const ViewStylePropTypes = require("ViewStylePropTypes");
+const processColor = require("processColor");
+
+const createReactClass = require("create-react-class");
+const requireNativeComponent = require("requireNativeComponent");
+
+const pickerStyleType = StyleSheetPropType({
+ ...ViewStylePropTypes,
+ color: ColorPropType
+});
+
+const PickerDOM = createReactClass({
+ displayName: "PickerDOM",
+ mixins: [NativeMethodsMixin],
+
+ propTypes: {
+ ...ViewPropTypes,
+ style: pickerStyleType,
+ selectedValue: PropTypes.any,
+ enabled: PropTypes.bool,
+ onValueChange: PropTypes.func,
+ selectedValue: PropTypes.any // string or integer basically
+ },
+
+ getInitialState: function() {
+ return this._stateFromProps(this.props);
+ },
+
+ UNSAFE_componentWillReceiveProps: function(nextProps) {
+ this.setState(this._stateFromProps(nextProps));
+ },
+
+ // Translate PickerDOM prop and children into stuff that RCTPickerDOM understands.
+ _stateFromProps: function(props) {
+ let selectedIndex = 0;
+ const items = [];
+ React.Children.toArray(props.children).forEach(function(child, index) {
+ if (child.props.value === props.selectedValue) {
+ selectedIndex = index;
+ }
+ items.push({
+ value: child.props.value,
+ label: child.props.label,
+ textColor: processColor(child.props.color)
+ });
+ });
+ return { selectedIndex, items };
+ },
+
+ render: function() {
+ return (
+ (this._picker = picker)}
+ style={[styles.pickerDOM, this.props.style]}
+ items={this.state.items}
+ selectedIndex={this.state.selectedIndex}
+ onChange={this._onChange}
+ enabled={this.props.enabled}
+ />
+ );
+ },
+
+ _onChange: function(event) {
+ if (this.props.onChange) {
+ this.props.onChange(event);
+ }
+ if (this.props.onValueChange) {
+ this.props.onValueChange(
+ event.nativeEvent.newValue,
+ event.nativeEvent.newIndex
+ );
+ }
+
+ // The picker is a controlled component. This means we expect the
+ // on*Change handlers to be in charge of updating our
+ // `selectedValue` prop. That way they can also
+ // disallow/undo/mutate the selection of certain values. In other
+ // words, the embedder of this component should be the source of
+ // truth, not the native component.
+ if (
+ this._picker &&
+ this.state.selectedIndex !== event.nativeEvent.newIndex
+ ) {
+ this._picker.setNativeProps({
+ selectedIndex: this.state.selectedIndex
+ });
+ }
+ }
+});
+
+PickerDOM.Item = class extends React.Component {
+ static propTypes = {
+ value: PropTypes.any, // string or integer basically
+ label: PropTypes.string,
+ color: PropTypes.string
+ };
+
+ render() {
+ // These items don't get rendered directly.
+ return null;
+ }
+};
+
+const styles = StyleSheet.create({
+ pickerDOM: {
+ // The picker will conform to whatever width is given, but we do
+ // have to set the component's height explicitly on the
+ // surrounding view to ensure it gets rendered.
+ height: 38
+ }
+});
+
+const RCTPickerDOM = requireNativeComponent(
+ "RCTPicker",
+ {
+ propTypes: {
+ ...ViewPropTypes,
+ style: pickerStyleType
+ }
+ },
+ {
+ nativeOnly: {
+ items: true,
+ onChange: true,
+ selectedIndex: true,
+ enabled: true
+ }
+ }
+);
+
+module.exports = PickerDOM;
diff --git a/packages/react-native-dom/ReactDom/index.js b/packages/react-native-dom/ReactDom/index.js
index 9dc239866..11c36f6eb 100644
--- a/packages/react-native-dom/ReactDom/index.js
+++ b/packages/react-native-dom/ReactDom/index.js
@@ -83,7 +83,8 @@ const builtInNativeModules: any[] = [
import("RCTRedBox"),
import("RCTWebViewManager"),
import("RCTNetworkingNative"),
- import("RCTBlobManager")
+ import("RCTBlobManager"),
+ import("RCTPickerManager")
];
// Development Specific Native Modules
diff --git a/packages/react-native-dom/ReactDom/views/RCTPicker.js b/packages/react-native-dom/ReactDom/views/RCTPicker.js
new file mode 100644
index 000000000..20dd64fb6
--- /dev/null
+++ b/packages/react-native-dom/ReactDom/views/RCTPicker.js
@@ -0,0 +1,131 @@
+/**
+ * @providesModule RCTPicker
+ * @flow
+ */
+import RCTView from "RCTView";
+import type RCTBridge from "RCTBridge";
+import CustomElement from "CustomElement";
+import ColorArrayFromHexARGB from "ColorArrayFromHexARGB";
+
+export type PickerItem = { label: string, value: string, textColor: ?number };
+export type SelectOnChange = ({ newIndex: number, newValue: ?string }) => void;
+
+@CustomElement("rct-picker")
+class RCTPicker extends RCTView {
+ _selectElement: HTMLSelectElement;
+
+ _selectedIndex: number;
+ _color: string;
+ _onChange: ?SelectOnChange;
+
+ constructor(bridge: RCTBridge) {
+ super(bridge);
+
+ this._selectElement = document.createElement("select");
+ this._selectedIndex = -1;
+
+ // update select element styles
+ this._selectElement.style.fontSize = "16px";
+ this._selectElement.style.boxSizing = "border-box";
+ this._selectElement.style.width = "100%";
+ this._selectElement.style.height = "100%";
+ this._selectElement.style.background = "none";
+ this._selectElement.style.borderStyle = "solid";
+ this._selectElement.style.borderWidth = "1px";
+ this._selectElement.style.borderColor = "#CCC";
+ this._selectElement.style.borderRadius = "0";
+ this._selectElement.style.padding = "5px";
+ // $FlowFixMe
+ this._selectElement.style.webkitAppearance = "none";
+ // $FlowFixMe
+ this._selectElement.style.mozAppearance = "none";
+ // $FlowFixMe
+ this._selectElement.style.appearance = "none";
+
+ // Styles for Custom Arrow
+ this._selectElement.style.background = `url("data:image/svg+xml;utf8,") no-repeat`;
+ this._selectElement.style.backgroundSize = "10px";
+ this._selectElement.style.backgroundPosition =
+ "calc(100% - 10px) calc(50%)";
+ this._selectElement.style.backgroundRepeat = "no-repeat";
+
+ this.disabled = false;
+
+ // bind select element events
+ this._selectElement.addEventListener(
+ "change",
+ this.handleValueChange,
+ false
+ );
+
+ // add to dom tree
+ this.childContainer.appendChild(this._selectElement);
+ }
+
+ set disabled(value: boolean) {
+ this._selectElement.disabled = value;
+ if (value) {
+ this._selectElement.style.opacity = "0.6";
+ this._selectElement.style.pointerEvents = "none";
+ this._selectElement.style.cursor = "default";
+ } else {
+ this._selectElement.style.opacity = "1.0";
+ this._selectElement.style.pointerEvents = "auto";
+ this._selectElement.style.cursor = "pointer";
+ }
+ }
+
+ set selectedIndex(value: number) {
+ this._selectedIndex = value;
+ this._selectElement.selectedIndex = value;
+ }
+
+ set color(value: string) {
+ this._color = value;
+ }
+
+ resolveColor(color: ?number) {
+ if (color != null) {
+ const [a, r, g, b] = ColorArrayFromHexARGB(color);
+ return `rgba(${r},${g},${b},${a})`;
+ }
+ return this._color ? this._color : "#000";
+ }
+
+ set items(items: PickerItem[]) {
+ // clean up previous item list
+ while (this._selectElement.firstChild) {
+ this._selectElement.removeChild(this._selectElement.firstChild);
+ }
+
+ // construct new option list
+ for (let item of items) {
+ const optionElem = document.createElement("option");
+ optionElem.innerText = item.label;
+ optionElem.value = item.value;
+ optionElem.style.color =
+ this.resolveColor(item.textColor) + " !important";
+
+ this._selectElement.appendChild(optionElem);
+ }
+ }
+
+ set onChange(value: ?SelectOnChange) {
+ this._onChange = value;
+ }
+
+ handleValueChange = (event: Event) => {
+ event.preventDefault();
+
+ const onChange = this._onChange;
+ if (onChange) {
+ const payload = {
+ newIndex: this._selectElement.selectedIndex,
+ newValue: this._selectElement.value
+ };
+ onChange(payload);
+ }
+ };
+}
+
+export default RCTPicker;
diff --git a/packages/react-native-dom/ReactDom/views/RCTPickerManager.js b/packages/react-native-dom/ReactDom/views/RCTPickerManager.js
new file mode 100644
index 000000000..7c0519c17
--- /dev/null
+++ b/packages/react-native-dom/ReactDom/views/RCTPickerManager.js
@@ -0,0 +1,60 @@
+/**
+ * @providesModule RCTPickerManager
+ * @flow
+ */
+
+import type UIView from "UIView";
+import RCTBridge, {
+ RCTFunctionTypeNormal,
+ RCT_EXPORT_METHOD,
+ RCT_EXPORT_MODULE
+} from "RCTBridge";
+import _RCTViewManager from "RCTViewManager";
+import RCTPicker from "RCTPicker";
+import ColorArrayFromHexARGB from "ColorArrayFromHexARGB";
+import type { PickerItem, SelectOnChange } from "RCTPicker";
+
+module.exports = (async () => {
+ const RCTViewManager = await _RCTViewManager;
+ const { RCT_EXPORT_VIEW_PROP } = RCTViewManager;
+
+ @RCT_EXPORT_MODULE("RCTPickerManager")
+ class RCTPickerManager extends RCTViewManager {
+ view(): UIView {
+ return new RCTPicker(this.bridge);
+ }
+
+ @RCT_EXPORT_VIEW_PROP("color", "color")
+ setColor(view: RCTPicker, value: ?number) {
+ if (value) {
+ const [a, r, g, b] = ColorArrayFromHexARGB(value);
+ const stringValue = `rgba(${r},${g},${b},${a})`;
+ view.color = stringValue;
+ } else {
+ view.color = "#000";
+ }
+ }
+
+ @RCT_EXPORT_VIEW_PROP("selectedIndex", "number")
+ setSelectedIndex(view: RCTPicker, value: ?number) {
+ view.selectedIndex = value != null ? value : -1;
+ }
+
+ @RCT_EXPORT_VIEW_PROP("items", "array")
+ setItems(view: RCTPicker, value: ?(PickerItem[])) {
+ view.items = value ? value : [];
+ }
+
+ @RCT_EXPORT_VIEW_PROP("enabled", "bool")
+ setDisabled(view: RCTPicker, value: boolean = true) {
+ view.disabled = !value;
+ }
+
+ @RCT_EXPORT_VIEW_PROP("onChange", "RCTBubblingEventBlock")
+ setOnChange(view: RCTPicker, value: ?SelectOnChange) {
+ view.onChange = value;
+ }
+ }
+
+ return RCTPickerManager;
+})();