diff --git a/README.md b/README.md
index 9a6542cc..5c5bf6c1 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,9 @@ React Native date & time picker component for iOS, Android and Windows (please n
+
+
+
Windows
@@ -78,6 +81,7 @@ React Native date & time picker component for iOS, Android and Windows (please n
- [Localization note](#localization-note)
- [Android imperative API](#android-imperative-api)
- [Android styling](#android-styling)
+ - [Date range picker (Android only)](#date-range-picker)
- [Props / params](#component-props--params-of-the-android-imperative-api)
- [`mode` (`optional`)](#mode-optional)
- [`display` (`optional`)](#display-optional)
@@ -301,6 +305,57 @@ Styling of the dialogs on Android can be easily customized by using the provided
Refer to this documentation for more information: [android-styling.md](/docs/android-styling.md).
+### Date range picker (Android only)
+
+Android has an additional component that allows users to select a range of dates (start and end dates). This is only available as a Material picker, meaning your application theme must inherit from `Theme.Material3.DayNight.NoActionBar` in `styles.xml`.
+
+The component is accessible through an imperative API, similar to the Android date and time pickers.
+
+```js
+MaterialRangePicker.open({
+ value: {
+ start: LAST_SUNDAY,
+ end: NEXT_SUNDAY,
+ },
+ onChange: handleChange,
+ fullscreen: true,
+});
+```
+
+The range picker supports many of the same props as the Material date picker with a few modifications:
+
+### `value` (`optional`)
+
+The value is an optional object with two properties: `start` and `end`. Both properties can be `null` or a `Date` object.
+
+```js
+MaterialRangePicker.open({
+ value: {
+ start: new Date(),
+ end: new Date(),
+ },
+});
+```
+
+This will pre-select the range picker with the provided dates. If no value is provided, the user will be able to select any range.
+
+### `onChange` (`required`)
+
+Range change handler.
+
+This is called when the user changes the range. It receives the event and the new range as parameters. The range will be in the same format as the `value` prop.
+
+```js
+const setRange = (event: RangePickerEvent, range: Range) => {
+ const {
+ type,
+ nativeEvent: {startTimestamp, endTimestamp, utcOffset},
+ } = event;
+};
+```
+
+The utcOffset field is only available on Android and iOS. It is the offset in minutes between the selected date and UTC time.
+
## Component props / params of the Android imperative api
> Please note that this library currently exposes functionality from [`UIDatePicker`](https://developer.apple.com/documentation/uikit/uidatepicker?language=objc) on iOS and [DatePickerDialog](https://developer.android.com/reference/android/app/DatePickerDialog) + [TimePickerDialog](https://developer.android.com/reference/android/app/TimePickerDialog) on Android, and [`CalendarDatePicker`](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/calendar-date-picker) + [TimePicker](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.timepicker?view=winrt-19041) on Windows.
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java
index ca6b2e06..9874ec1b 100644
--- a/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/Common.java
@@ -248,6 +248,20 @@ public static Bundle createDatePickerArguments(ReadableMap options) {
return args;
}
+ public static Bundle createRangePickerArguments(ReadableMap options) {
+ final Bundle args = createDatePickerArguments(options);
+
+ if (options.hasKey(RNConstants.ARG_START_TIMESTAMP) && !options.isNull(RNConstants.ARG_START_TIMESTAMP)) {
+ args.putLong(RNConstants.ARG_START_TIMESTAMP, (long) options.getDouble(RNConstants.ARG_START_TIMESTAMP));
+ }
+
+ if (options.hasKey(RNConstants.ARG_END_TIMESTAMP) && !options.isNull(RNConstants.ARG_END_TIMESTAMP)) {
+ args.putLong(RNConstants.ARG_END_TIMESTAMP, (long) options.getDouble(RNConstants.ARG_END_TIMESTAMP));
+ }
+
+ return args;
+ }
+
public static Bundle createTimePickerArguments(ReadableMap options) {
final Bundle args = Common.createFragmentArguments(options);
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialRangePickerModule.kt b/android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialRangePickerModule.kt
new file mode 100644
index 00000000..79d2f17c
--- /dev/null
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/MaterialRangePickerModule.kt
@@ -0,0 +1,45 @@
+package com.reactcommunity.rndatetimepicker
+
+import androidx.fragment.app.FragmentActivity
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.ReadableMap
+import com.facebook.react.bridge.UiThreadUtil
+import com.reactcommunity.rndatetimepicker.Common.createDatePickerArguments
+import com.reactcommunity.rndatetimepicker.Common.createRangePickerArguments
+import com.reactcommunity.rndatetimepicker.Common.dismissDialog
+
+class MaterialRangePickerModule(reactContext: ReactApplicationContext): NativeModuleMaterialRangePickerSpec(reactContext) {
+ companion object {
+ const val NAME = "RNCMaterialRangePicker"
+ }
+
+ override fun getName(): String {
+ return NAME
+ }
+
+ override fun dismiss(promise: Promise?) {
+ val activity = currentActivity as FragmentActivity?
+ dismissDialog(activity, NAME, promise)
+ }
+
+ override fun open(params: ReadableMap, promise: Promise) {
+ val activity = currentActivity as FragmentActivity?
+ if (activity == null) {
+ promise.reject(
+ RNConstants.ERROR_NO_ACTIVITY,
+ "Tried to open a MaterialRangePicker dialog while not attached to an Activity"
+ )
+ return
+ }
+
+ val fragmentManager = activity.supportFragmentManager
+
+ UiThreadUtil.runOnUiThread {
+ val arguments = createRangePickerArguments(params)
+ val rangePicker =
+ RNMaterialRangePicker(arguments, promise, fragmentManager, reactApplicationContext)
+ rangePicker.open()
+ }
+ }
+}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java
index 07220b79..eddc9412 100644
--- a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNConstants.java
@@ -5,6 +5,8 @@
public final class RNConstants {
public static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
public static final String ARG_VALUE = "value";
+ public static final String ARG_START_TIMESTAMP = "startTimestamp";
+ public static final String ARG_END_TIMESTAMP = "endTimestamp";
public static final String ARG_MINDATE = "minimumDate";
public static final String ARG_MAXDATE = "maximumDate";
public static final String ARG_INTERVAL = "minuteInterval";
@@ -18,6 +20,7 @@ public final class RNConstants {
public static final String ARG_INITIAL_INPUT_MODE = "initialInputMode";
public static final String ARG_FULLSCREEN = "fullscreen";
public static final String ACTION_DATE_SET = "dateSetAction";
+ public static final String ACTION_RANGE_SET = "rangeSetAction";
public static final String ACTION_TIME_SET = "timeSetAction";
public static final String ACTION_DISMISSED = "dismissedAction";
public static final String ACTION_NEUTRAL_BUTTON = "neutralButtonAction";
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java
index 417183e4..fb03cfd6 100644
--- a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java
@@ -24,6 +24,8 @@ public NativeModule getModule(String name, ReactApplicationContext reactContext)
return new MaterialDatePickerModule(reactContext);
} else if (name.equals(MaterialTimePickerModule.NAME)) {
return new MaterialTimePickerModule(reactContext);
+ } else if (name.equals(MaterialRangePickerModule.NAME)) {
+ return new MaterialRangePickerModule(reactContext);
} else {
return null;
}
@@ -78,6 +80,17 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() {
false, // isCxxModule
isTurboModule // isTurboModule
));
+ moduleInfos.put(
+ MaterialRangePickerModule.NAME,
+ new ReactModuleInfo(
+ MaterialRangePickerModule.NAME,
+ MaterialRangePickerModule.NAME,
+ false, // canOverrideExistingModule
+ false, // needsEagerInit
+ false, // hasConstants
+ false, // isCxxModule
+ isTurboModule // isTurboModule
+ ));
return moduleInfos;
};
}
diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialRangePicker.kt b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialRangePicker.kt
new file mode 100644
index 00000000..69bf9fff
--- /dev/null
+++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNMaterialRangePicker.kt
@@ -0,0 +1,207 @@
+package com.reactcommunity.rndatetimepicker
+
+import android.content.DialogInterface
+import android.os.Bundle
+import androidx.core.util.Pair
+import androidx.fragment.app.FragmentManager
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.WritableNativeMap
+import com.google.android.material.datepicker.CalendarConstraints
+import com.google.android.material.datepicker.CalendarConstraints.DateValidator
+import com.google.android.material.datepicker.CompositeDateValidator
+import com.google.android.material.datepicker.DateValidatorPointBackward
+import com.google.android.material.datepicker.DateValidatorPointForward
+import com.google.android.material.datepicker.MaterialDatePicker
+import com.google.android.material.datepicker.MaterialPickerOnPositiveButtonClickListener
+import java.util.Calendar
+
+class RNMaterialRangePicker(
+ private val args: Bundle,
+ private val promise: Promise,
+ private val fragmentManager: FragmentManager,
+ private val reactContext: ReactApplicationContext
+) {
+ private var promiseResolved = false
+ private var rangePicker: MaterialDatePicker>? = null
+ private var builder = MaterialDatePicker.Builder.dateRangePicker()
+
+ fun open() {
+ createRangePicker()
+ addListeners()
+ show()
+ }
+
+ private fun createRangePicker() {
+ setInitialDates()
+ setTitle()
+ setInputMode()
+ setButtons()
+ setConstraints()
+ setFullscreen()
+
+ rangePicker = builder.build()
+ }
+
+ private fun setInitialDates() {
+ var start: Long? = null
+ var end: Long? = null
+
+ if (args.containsKey(RNConstants.ARG_START_TIMESTAMP)) {
+ // override "value" so we can use the same constructor from RNDate
+ args.putLong(RNConstants.ARG_VALUE, args.getLong((RNConstants.ARG_START_TIMESTAMP)))
+ start = RNDate(args).timestamp()
+ }
+
+ if (args.containsKey(RNConstants.ARG_END_TIMESTAMP)) {
+ // override "value" so we can use the same constructor from RNDate
+ args.putLong(RNConstants.ARG_VALUE, args.getLong((RNConstants.ARG_END_TIMESTAMP)))
+ end = RNDate(args).timestamp()
+ }
+
+ val selection = Pair(start, end)
+ builder.setSelection(selection)
+ }
+
+ private fun setTitle() {
+ val title = args.getString(RNConstants.ARG_TITLE)
+ if (!title.isNullOrEmpty()) {
+ builder.setTitleText(args.getString(RNConstants.ARG_TITLE))
+ }
+ }
+
+ private fun setInputMode() {
+ if (args.getString(RNConstants.ARG_INITIAL_INPUT_MODE).isNullOrEmpty()) {
+ builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR)
+ return
+ }
+
+ val inputMode =
+ RNMaterialInputMode.valueOf(
+ args.getString(RNConstants.ARG_INITIAL_INPUT_MODE)!!.uppercase()
+ )
+
+ if (inputMode == RNMaterialInputMode.KEYBOARD) {
+ builder.setInputMode(MaterialDatePicker.INPUT_MODE_TEXT)
+ } else {
+ builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR)
+ }
+ }
+
+ private fun setConstraints() {
+ val constraintsBuilder = CalendarConstraints.Builder()
+
+ if (args.containsKey(RNConstants.FIRST_DAY_OF_WEEK)) {
+ constraintsBuilder.setFirstDayOfWeek(args.getInt(RNConstants.FIRST_DAY_OF_WEEK))
+ }
+
+ val validators = mutableListOf()
+
+ if (args.containsKey(RNConstants.ARG_MINDATE)) {
+ val minDate = Common.minDateWithTimeZone(args)
+ validators.add(DateValidatorPointForward.from(minDate))
+ }
+
+ if (args.containsKey(RNConstants.ARG_MAXDATE)) {
+ val maxDate = Common.maxDateWithTimeZone(args)
+ validators.add(DateValidatorPointBackward.before(maxDate))
+ }
+
+ constraintsBuilder.setValidator(CompositeDateValidator.allOf(validators))
+ builder.setCalendarConstraints(constraintsBuilder.build())
+ }
+
+ private fun setFullscreen() {
+ val isFullscreen = args.getBoolean(RNConstants.ARG_FULLSCREEN)
+
+ if (isFullscreen) {
+ builder.setTheme(com.google.android.material.R.style.ThemeOverlay_Material3_MaterialCalendar_Fullscreen)
+ } else {
+ builder.setTheme(com.google.android.material.R.style.ThemeOverlay_Material3_MaterialCalendar)
+ }
+ }
+
+ private fun setButtons() {
+ val buttons = args.getBundle(RNConstants.ARG_DIALOG_BUTTONS) ?: return
+
+ val negativeButton = buttons.getBundle(Common.NEGATIVE)
+ val positiveButton = buttons.getBundle(Common.POSITIVE)
+
+ if (negativeButton != null) {
+ builder.setNegativeButtonText(negativeButton.getString(Common.LABEL))
+ }
+
+ if (positiveButton != null) {
+ builder.setPositiveButtonText(positiveButton.getString(Common.LABEL))
+ }
+ }
+
+ private fun addListeners() {
+ val listeners = Listeners()
+ rangePicker!!.addOnPositiveButtonClickListener(listeners)
+ rangePicker!!.addOnDismissListener(listeners)
+ }
+
+ private fun show() {
+ rangePicker!!.show(fragmentManager, MaterialRangePickerModule.NAME)
+ }
+
+ private inner class Listeners : MaterialPickerOnPositiveButtonClickListener>,
+ DialogInterface.OnDismissListener {
+ override fun onDismiss(dialog: DialogInterface) {
+ if (promiseResolved || !reactContext.hasActiveReactInstance()) return
+
+ val result = WritableNativeMap()
+ result.putString("action", RNConstants.ACTION_DISMISSED)
+ promise.resolve(result)
+ promiseResolved = true
+ }
+
+ override fun onPositiveButtonClick(selection: Pair) {
+ if (promiseResolved || !reactContext.hasActiveReactInstance()) return
+
+ val result = WritableNativeMap()
+
+ result.putString("action", RNConstants.ACTION_RANGE_SET)
+ result.putDouble("startTimestamp", getStartTimestamp(selection))
+ result.putDouble("endTimestamp", getEndTimestamp(selection))
+ result.putDouble(
+ "utcOffset",
+ getStartTimestamp(selection) / 1000 / 60
+ )
+
+ promise.resolve(result)
+ promiseResolved = true
+ }
+
+ private fun getStartTimestamp(selection: Pair): Double {
+ val newCalendar = Calendar.getInstance(
+ Common.getTimeZone(
+ args
+ )
+ )
+
+ newCalendar.timeInMillis = selection.first
+ newCalendar[Calendar.HOUR_OF_DAY] = 0
+ newCalendar[Calendar.MINUTE] = 0
+ newCalendar[Calendar.SECOND] = 0
+
+ return newCalendar.timeInMillis.toDouble()
+ }
+
+ private fun getEndTimestamp(selection: Pair): Double {
+ val newCalendar = Calendar.getInstance(
+ Common.getTimeZone(
+ args
+ )
+ )
+
+ newCalendar.timeInMillis = selection.first
+ newCalendar[Calendar.HOUR_OF_DAY] = 23
+ newCalendar[Calendar.MINUTE] = 59
+ newCalendar[Calendar.SECOND] = 59
+
+ return newCalendar.timeInMillis.toDouble()
+ }
+ }
+}
diff --git a/android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialRangePickerSpec.java b/android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialRangePickerSpec.java
new file mode 100644
index 00000000..33ffec46
--- /dev/null
+++ b/android/src/paper/java/com/reactcommunity/rndatetimepicker/NativeModuleMaterialRangePickerSpec.java
@@ -0,0 +1,36 @@
+
+/**
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
+ *
+ * Then it was commited. It is here to support the old architecture.
+ * If you use the new architecture, this file won't be included and instead will be generated by the codegen.
+ *
+ * @generated by codegen project: GenerateModuleJavaSpec.js
+ *
+ * @nolint
+ */
+
+package com.reactcommunity.rndatetimepicker;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReactModuleWithSpec;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.turbomodule.core.interfaces.TurboModule;
+
+public abstract class NativeModuleMaterialRangePickerSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule {
+ public NativeModuleMaterialRangePickerSpec(ReactApplicationContext reactContext) {
+ super(reactContext);
+ }
+
+ @ReactMethod
+ @DoNotStrip
+ public abstract void dismiss(Promise promise);
+
+ @ReactMethod
+ @DoNotStrip
+ public abstract void open(ReadableMap params, Promise promise);
+}
diff --git a/docs/images/android_material_range.jpg b/docs/images/android_material_range.jpg
new file mode 100644
index 00000000..dab4ff0e
Binary files /dev/null and b/docs/images/android_material_range.jpg differ
diff --git a/example/App.js b/example/App.js
index ae542c80..c2d8af5d 100644
--- a/example/App.js
+++ b/example/App.js
@@ -13,7 +13,9 @@ import {
Alert,
FlatList,
} from 'react-native';
-import DateTimePicker from '@react-native-community/datetimepicker';
+import DateTimePicker, {
+ MaterialRangePicker,
+} from '@react-native-community/datetimepicker';
import SegmentedControl from './SegmentedControl';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import React, {useRef, useState} from 'react';
@@ -216,6 +218,22 @@ export const App = () => {
setMaximumDate(minimumDate ? undefined : endOfTomorrowUTC);
};
+ const handleShowRangePicker = () => {
+ MaterialRangePicker.open({
+ fullscreen: isFullscreen,
+ initialInputMode: inputMode,
+ maximumDate,
+ minimumDate,
+ title,
+ timeZoneOffsetInMinutes: tzOffsetInMinutes,
+ timeZoneName: tzName,
+ dialogButtons: {
+ negative: {label: 'Nope'},
+ positive: {label: 'Yes'},
+ },
+ });
+ };
+
if (Platform.OS !== 'windows') {
return (
{
title="Show and dismiss picker!"
/>
+
+
+ [android] show range picker
+
+
+
+
+ {
+ return RNMaterialRangePickerAndroid.dismiss();
+}
+
+export const MaterialRangePicker = {open, dismiss};
diff --git a/src/MaterialRangePicker.js b/src/MaterialRangePicker.js
new file mode 100644
index 00000000..42976a6f
--- /dev/null
+++ b/src/MaterialRangePicker.js
@@ -0,0 +1,11 @@
+/**
+ * @format
+ * @flow strict-local
+ */
+import {Platform} from 'react-native';
+
+const warn = () => {
+ console.warn(`MaterialRangePicker is not supported on: ${Platform.OS}`);
+};
+
+export const MaterialRangePicker = {open: warn, dismiss: warn};
diff --git a/src/constants.js b/src/constants.js
index acc91329..4cb16ee9 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -55,6 +55,7 @@ export const DAY_OF_WEEK = Object.freeze({
export const DATE_SET_ACTION = 'dateSetAction';
export const TIME_SET_ACTION = 'timeSetAction';
+export const RANGE_SET_ACTION = 'rangeSetAction';
export const DISMISS_ACTION = 'dismissedAction';
export const NEUTRAL_BUTTON_ACTION = 'neutralButtonAction';
diff --git a/src/eventCreators.js b/src/eventCreators.js
index cd16fbd9..111d04de 100644
--- a/src/eventCreators.js
+++ b/src/eventCreators.js
@@ -1,7 +1,7 @@
/**
* @flow strict-local
*/
-import type {DateTimePickerEvent} from './types';
+import type {DateTimePickerEvent, RangePickerEvent, Range} from './types';
import {ANDROID_EVT_TYPE, EVENT_TYPE_SET} from './constants';
export const createDateTimeSetEvtParams = (
@@ -20,6 +20,20 @@ export const createDateTimeSetEvtParams = (
];
};
+export const createRangeSetEvtParams = (
+ range: Range,
+ utcOffset: number,
+): RangePickerEvent => {
+ return {
+ type: EVENT_TYPE_SET,
+ nativeEvent: {
+ startTimestamp: range.start ? range.start.getTime() : 0,
+ endTimestamp: range.end ? range.end.getTime() : 0,
+ utcOffset,
+ },
+ };
+};
+
export const createDismissEvtParams = (
date: Date,
utcOffset: number,
@@ -36,6 +50,23 @@ export const createDismissEvtParams = (
];
};
+export const createRangeDismissEvtParams = (
+ range: Range,
+ utcOffset: number,
+): [RangePickerEvent, Range] => {
+ return [
+ {
+ type: ANDROID_EVT_TYPE.dismissed,
+ nativeEvent: {
+ startTimestamp: range.start ? range.start.getTime() : 0,
+ endTimestamp: range.end ? range.end.getTime() : 0,
+ utcOffset,
+ },
+ },
+ range,
+ ];
+};
+
export const createNeutralEvtParams = (
date: Date,
utcOffset: number,
diff --git a/src/index.d.ts b/src/index.d.ts
index 701ddb68..dbed70a3 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -206,6 +206,28 @@ export type AndroidNativeProps = Readonly<
}
>;
+export type MaterialRangePickerProps = {
+ title?: string;
+ maximumDate?: Date;
+ minimumDate?: Date;
+ timeZoneOffsetInMinutes?: number;
+ timeZoneName?: string;
+ testID?: string;
+ initialInputMode?: string;
+ dialogButtons?: {
+ positive: {label: string};
+ negative: {label: string};
+ };
+ fullscreen?: boolean;
+ value?: Range;
+ onChange?: (event: RangePickerEvent, range?: Range) => void;
+};
+
+type Range = {
+ start?: Date;
+ end?: Date;
+};
+
export type DatePickerOptions = DateOptions & {
display?: Display;
};
@@ -251,9 +273,15 @@ declare namespace DateTimePickerAndroidType {
const dismiss: (mode: AndroidNativeProps['mode']) => Promise;
}
+declare namespace MaterialRangePickerType {
+ const open: (args: MaterialRangePickerProps) => void;
+ const dismiss: () => Promise;
+}
+
declare const RNDateTimePicker: FC<
IOSNativeProps | AndroidNativeProps | WindowsNativeProps
>;
export default RNDateTimePicker;
export const DateTimePickerAndroid: typeof DateTimePickerAndroidType;
+export const MaterialRangePicker: typeof MaterialRangePickerType;
diff --git a/src/index.js b/src/index.js
index ca4ff882..7c47396a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -5,5 +5,6 @@
import RNDateTimePicker from './datetimepicker';
export * from './eventCreators';
export {DateTimePickerAndroid} from './DateTimePickerAndroid';
+export {MaterialRangePicker} from './MaterialRangePicker';
export default RNDateTimePicker;
diff --git a/src/specs/NativeModuleMaterialRangePicker.js b/src/specs/NativeModuleMaterialRangePicker.js
new file mode 100644
index 00000000..bf78a6ca
--- /dev/null
+++ b/src/specs/NativeModuleMaterialRangePicker.js
@@ -0,0 +1,34 @@
+// @flow strict-local
+
+import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
+import {TurboModuleRegistry} from 'react-native';
+
+export type RangePickerOpenParams = $ReadOnly<{
+ dialogButtons?: $ReadOnly<{string: string}>,
+ initialInputMode?: string,
+ title?: string,
+ maximumDate?: number,
+ minimumDate?: number,
+ startTimestamp?: number,
+ endTimestamp?: number,
+ testID?: string,
+ timeZoneName?: number,
+ timeZoneOffsetInMinutes?: number,
+}>;
+
+type RangeSetAction = 'rangeSetAction' | 'dismissedAction';
+type RangePickerResult = $ReadOnly<{
+ action: RangeSetAction,
+ startTimestamp: number,
+ endTimestamp: number,
+ utcOffset: number,
+}>;
+
+export interface Spec extends TurboModule {
+ +dismiss: () => Promise;
+ +open: (params: RangePickerOpenParams) => Promise;
+}
+
+export default (TurboModuleRegistry.getEnforcing(
+ 'RNCMaterialRangePicker',
+): ?Spec);
diff --git a/src/types.js b/src/types.js
index 8c5f04be..269e6184 100644
--- a/src/types.js
+++ b/src/types.js
@@ -297,3 +297,36 @@ export type WindowsNativeProps = $ReadOnly<{|
minuteInterval?: number,
accessibilityLabel?: string,
|}>;
+
+export type MaterialRangeProps = $ReadOnly<{|
+ title?: string,
+ maximumDate?: Date,
+ minimumDate?: Date,
+ timeZoneOffsetInMinutes?: number,
+ timeZoneName?: string,
+ testID?: string,
+ initialInputMode?: string,
+ dialogButtons?: {
+ positive: {label: string},
+ negative: {label: string},
+ },
+ fullscreen?: boolean,
+ value?: Range,
+ onChange?: ?(event: RangePickerEvent, range?: Range) => void,
+|}>;
+
+export type RangePickerEvent = {
+ type: 'set' | 'dismiss',
+ nativeEvent: $ReadOnly<{
+ startTimestamp: number,
+ endTimestamp: number,
+ utcOffset: number,
+ ...
+ }>,
+ ...
+};
+
+export type Range = $ReadOnly<{|
+ start?: Date,
+ end?: Date,
+|}>;
diff --git a/test/__snapshots__/index.test.js.snap b/test/__snapshots__/index.test.js.snap
index 9d60152f..18e66fcf 100644
--- a/test/__snapshots__/index.test.js.snap
+++ b/test/__snapshots__/index.test.js.snap
@@ -21,9 +21,15 @@ exports[`DateTimePicker namedExports have the expected shape 1`] = `
"dismiss": [Function],
"open": [Function],
},
+ "MaterialRangePicker": {
+ "dismiss": [Function],
+ "open": [Function],
+ },
"createDateTimeSetEvtParams": [Function],
"createDismissEvtParams": [Function],
"createNeutralEvtParams": [Function],
+ "createRangeDismissEvtParams": [Function],
+ "createRangeSetEvtParams": [Function],
"default": [Function],
}
`;