├── .eslintrc ├── extras ├── logo.png ├── datetimepicker-ios.gif └── datetimepicker-android.gif ├── src ├── index.js ├── CustomModal │ ├── index.style.js │ └── index.js ├── CustomDatePickerIOS │ ├── index.style.js │ └── index.js └── CustomDatePickerAndroid │ └── index.js ├── .gitignore ├── .npmignore ├── package.json └── README.md /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-mostaza-react", 3 | "parser": "babel-eslint" 4 | } 5 | -------------------------------------------------------------------------------- /extras/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corymsmith/react-native-modal-datetime-picker/master/extras/logo.png -------------------------------------------------------------------------------- /extras/datetimepicker-ios.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corymsmith/react-native-modal-datetime-picker/master/extras/datetimepicker-ios.gif -------------------------------------------------------------------------------- /extras/datetimepicker-android.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corymsmith/react-native-modal-datetime-picker/master/extras/datetimepicker-android.gif -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native' 2 | import CustomDatePickerAndroid from './CustomDatePickerAndroid' 3 | import CustomDatePickerIOS from './CustomDatePickerIOS' 4 | 5 | const IS_ANDROID = Platform.OS === 'android' 6 | 7 | export default IS_ANDROID ? CustomDatePickerAndroid : CustomDatePickerIOS 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | 37 | # BUCK 38 | buck-out/ 39 | \.buckd/ 40 | android/app/libs 41 | android/keystores/debug.keystore 42 | -------------------------------------------------------------------------------- /src/CustomModal/index.style.js: -------------------------------------------------------------------------------- 1 | import { Dimensions, StyleSheet } from 'react-native' 2 | 3 | const DEVICE_WIDTH = Dimensions.get('window').width 4 | const DEVICE_HEIGHT = Dimensions.get('window').height 5 | 6 | export default StyleSheet.create({ 7 | backdrop: { 8 | position: 'absolute', 9 | top: 0, 10 | bottom: 0, 11 | left: 0, 12 | right: 0, 13 | height: DEVICE_HEIGHT, 14 | width: DEVICE_WIDTH, 15 | opacity: 0, 16 | backgroundColor: 'black' 17 | }, 18 | contentContainer: { 19 | flex: 1, 20 | justifyContent: 'center', 21 | margin: DEVICE_WIDTH * 0.05 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | 37 | # BUCK 38 | buck-out/ 39 | \.buckd/ 40 | android/app/libs 41 | android/keystores/debug.keystore 42 | 43 | # Images and extras 44 | extras/ 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-modal-datetime-picker", 3 | "version": "1.0.2", 4 | "description": "A react-native datetime-picker for Android and iOS", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "lint": "eslint src", 8 | "test": "npm run lint" 9 | }, 10 | "keywords": [ 11 | "react-native", 12 | "react", 13 | "native", 14 | "date", 15 | "time", 16 | "picker", 17 | "android", 18 | "ios" 19 | ], 20 | "author": "Mazzarolo Matteo", 21 | "license": "ISC", 22 | "dependencies": { 23 | "moment": "^2.15.0", 24 | "react-native-animatable": "^0.6.1" 25 | }, 26 | "devDependencies": { 27 | "babel-eslint": "^6.1.2", 28 | "eslint": "^3.5.0", 29 | "eslint-config-mostaza-react": "^1.11.2", 30 | "eslint-plugin-import": "^1.15.0", 31 | "eslint-plugin-jsx-a11y": "^2.2.2", 32 | "eslint-plugin-react": "^6.2.1", 33 | "react": ">=15.0.0", 34 | "react-native": ">=0.24.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/CustomDatePickerIOS/index.style.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native' 2 | 3 | const BORDER_RADIUS = 14 4 | const BACKGROUND_COLOR = 'white' 5 | 6 | export default StyleSheet.create({ 7 | contentContainer: { 8 | justifyContent: 'flex-end' 9 | }, 10 | datepickerContainer: { 11 | backgroundColor: BACKGROUND_COLOR, 12 | borderRadius: BORDER_RADIUS 13 | }, 14 | titleContainer: { 15 | borderBottomColor: 'rgba(0, 0, 0, 0.1)', 16 | borderBottomWidth: 1, 17 | padding: 12, 18 | backgroundColor: 'transparent' 19 | }, 20 | title: { 21 | textAlign: 'center', 22 | color: 'grey', 23 | fontSize: 20 24 | }, 25 | confirmButton: { 26 | borderTopColor: 'rgba(0, 0, 0, 0.1)', 27 | borderTopWidth: 1, 28 | padding: 10, 29 | backgroundColor: 'transparent' 30 | }, 31 | confirmText: { 32 | textAlign: 'center', 33 | color: '#2E93FC', 34 | fontSize: 24, 35 | fontWeight: '500', 36 | backgroundColor: 'transparent' 37 | }, 38 | cancelButton: { 39 | marginTop: 20, 40 | backgroundColor: BACKGROUND_COLOR, 41 | padding: 10, 42 | borderRadius: BORDER_RADIUS 43 | }, 44 | cancelText: { 45 | textAlign: 'center', 46 | color: '#2E93FC', 47 | fontSize: 24, 48 | fontWeight: '700', 49 | backgroundColor: 'transparent' 50 | } 51 | }) 52 | -------------------------------------------------------------------------------- /src/CustomModal/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-return-assign */ 2 | import React, { Component, PropTypes } from 'react' 3 | import { Modal } from 'react-native' 4 | import { View } from 'react-native-animatable' 5 | 6 | import styles from './index.style.js' 7 | 8 | export default class CustomModal extends Component { 9 | static propTypes = { 10 | visible: PropTypes.bool, 11 | children: PropTypes.node, 12 | contentContainerStyle: PropTypes.any 13 | } 14 | 15 | static defaultProps = { 16 | contentContainerStyle: {}, 17 | visible: false 18 | } 19 | 20 | state = { 21 | visible: false 22 | } 23 | 24 | componentWillReceiveProps (nextProps) { 25 | if (!this.state.visible && nextProps.visible) { 26 | this.setState({ visible: true }) 27 | } 28 | } 29 | 30 | componentDidUpdate (prevProps, prevState) { 31 | // On modal open request slide the view up and fade in the backdrop 32 | if (this.state.visible && !prevState.visible) { 33 | this._open() 34 | // On modal close request slide the view down and fade out the backdrop 35 | } else if (!this.props.visible && prevProps.visible) { 36 | this._close() 37 | } 38 | } 39 | 40 | _open = () => { 41 | this.backdropRef.transitionTo({ opacity: 0.70 }) 42 | this.contentRef.slideInUp(300) 43 | } 44 | 45 | _close = () => { 46 | this.backdropRef.transitionTo({ opacity: 0 }) 47 | this.contentRef.slideOutDown(300) 48 | .then(() => this.setState({ visible: false })) 49 | } 50 | 51 | render () { 52 | const { children, contentContainerStyle, ...otherProps } = this.props 53 | const { visible } = this.state 54 | return ( 55 | 61 | this.backdropRef = ref} style={styles.backdrop} /> 62 | this.contentRef = ref} style={[styles.contentContainer, contentContainerStyle]}> 63 | {children} 64 | 65 | 66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/CustomDatePickerAndroid/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { DatePickerAndroid, TimePickerAndroid } from 'react-native' 3 | import moment from 'moment' 4 | 5 | export default class CustomDatePickerAndroid extends Component { 6 | static propTypes = { 7 | date: PropTypes.instanceOf(Date), 8 | mode: PropTypes.oneOf(['date', 'time']), 9 | onCancel: PropTypes.func.isRequired, 10 | onConfirm: PropTypes.func.isRequired, 11 | visible: PropTypes.bool 12 | } 13 | 14 | static defaultProps = { 15 | date: new Date(), 16 | mode: 'date', 17 | visible: false 18 | } 19 | 20 | componentDidUpdate = (prevProps) => { 21 | if (!prevProps.visible && this.props.visible) { 22 | if (this.props.mode === 'date') { 23 | this._showDatePickerAndroid() 24 | } else { 25 | this._showTimePickerAndroid() 26 | } 27 | } 28 | } 29 | 30 | _showDatePickerAndroid = async () => { 31 | try { 32 | const { action, year, month, day } = await DatePickerAndroid.open({ 33 | date: this.props.date 34 | }) 35 | if (action !== DatePickerAndroid.dismissedAction) { 36 | const date = moment({ year, month, day }).toDate() 37 | this.props.onConfirm(date) 38 | } else { 39 | this.props.onCancel() 40 | } 41 | } catch ({ code, message }) { 42 | console.warn('Cannot open date picker', message) 43 | } 44 | } 45 | 46 | _showTimePickerAndroid = async () => { 47 | try { 48 | const { action, hour, minute } = await TimePickerAndroid.open({ 49 | hour: moment(this.props.date).hour(), 50 | minute: moment(this.props.date).minute(), 51 | is24Hour: true 52 | }) 53 | if (action !== TimePickerAndroid.dismissedAction) { 54 | const date = moment({ hour, minute }).toDate() 55 | this.props.onConfirm(date) 56 | } else { 57 | this.props.onCancel() 58 | } 59 | } catch ({ code, message }) { 60 | console.warn('Cannot open time picker', message) 61 | } 62 | } 63 | 64 | render () { 65 | return null 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/CustomDatePickerIOS/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { DatePickerIOS, Text, TouchableOpacity, View } from 'react-native' 3 | import CustomModal from '../CustomModal' 4 | 5 | import styles from './index.style' 6 | 7 | export default class CustomDatePickerIOS extends Component { 8 | static propTypes = { 9 | cancelTextIOS: PropTypes.string, 10 | confirmTextIOS: PropTypes.string, 11 | date: PropTypes.instanceOf(Date), 12 | mode: PropTypes.oneOf(['date', 'time']), 13 | onConfirm: PropTypes.func.isRequired, 14 | onCancel: PropTypes.func.isRequired, 15 | titleIOS: PropTypes.string, 16 | visible: PropTypes.bool 17 | } 18 | 19 | static defaultProps = { 20 | cancelTextIOS: 'Cancel', 21 | confirmTextIOS: 'Confirm', 22 | date: new Date(), 23 | mode: 'date', 24 | titleIOS: 'Pick a date', 25 | visible: false 26 | } 27 | 28 | state = { 29 | date: this.props.date 30 | } 31 | 32 | _handleConfirm = () => this.props.onConfirm(this.state.date) 33 | 34 | _handleDateChange = (date) => this.setState({ date }) 35 | 36 | render () { 37 | const { onCancel, visible, mode, titleIOS, confirmTextIOS, cancelTextIOS, date, ...otherProps } = this.props 38 | return ( 39 | 40 | 41 | 42 | {titleIOS} 43 | 44 | 50 | 51 | {confirmTextIOS} 52 | 53 | 54 | 55 | {cancelTextIOS} 56 | 57 | 58 | ) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # react-native-modal-datetime-picker 3 | A react-native datetime-picker that works on both Android and iOS. 4 |
5 |
6 | 7 | ## Description 8 | This library exposes a cross-platform interface for showing the native date and time pickers inside a modal. 9 | You will have an unified user experience, you won't have to worry anymore about testing the device platform and you won't have to programmatically call the Android TimePicker/DatePicker APIs. 10 |
11 | 12 | ## Setup 13 | This library is available on npm, install it with: `npm install --save react-native-modal-datetime-picker`. 14 | 15 | ## GIFs! 16 | 17 | 18 | 19 | ## Usage 20 | ```javascript 21 | import React, { Component, PropTypes } from 'react' 22 | import { Text, TouchableOpacity, View } from 'react-native' 23 | import DateTimePicker from 'react-native-modal-datetime-picker' 24 | 25 | export default class DateTimePickerTester extends Component { 26 | state = { 27 | isDateTimePickerVisible: false 28 | } 29 | 30 | _showDateTimePicker = () => this.setState({ isDateTimePickerVisible: true }) 31 | 32 | _hideDateTimePicker = () => this.setState({ isDateTimePickerVisible: false }) 33 | 34 | _handleDatePicked = (date) => { 35 | console.log('A date has been picked: ', date) 36 | this._hideDateTimePicker() 37 | } 38 | 39 | render () { 40 | return ( 41 | 42 | 43 | Show TimePicker 44 | 45 | 50 | 51 | ) 52 | } 53 | 54 | } 55 | ``` 56 |
57 | 58 | ## Available props 59 | | Name | Type| Default | Description | 60 | | --- | --- | --- | --- | 61 | | visible | bool | false | Show the datetime picker? | 62 | | onConfirm | func | **REQUIRED** | Funcion called on date picked. | 63 | | onCancel | func | **REQUIRED** | Funcion called on dismiss. | 64 | | mode | string | 'date' | Datepicker? 'date' Timepicker? 'time' | 65 | | date | obj | new Date() | Initial selected date/time | 66 | | titleIOS | string | 'Pick a date' | The title text on iOS | 67 | | confirmTextIOS | string | 'Confirm' | The text on the confirm button on iOS | 68 | | cancelTextIOS | string | 'Cancel' | The text on the cancel button on iOS | 69 | 70 | All the [DatePickerIOS props](https://facebook.github.io/react-native/docs/datepickerios.html) are also supported! 71 | 72 | ## Notes 73 | Just remember to always set `visible` props to `false` in `onConfirm` and `onCancel` (like in the example above). 74 | Pull request and suggestions are welcome! 75 | --------------------------------------------------------------------------------