Browse Source

feat: modal (#376)

master
Henning Hall 4 years ago
committed by GitHub
parent
commit
1f1efe5f18
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 6155 additions and 4034 deletions
  1. +93
    -40
      README.md
  2. +2
    -2
      android/build.gradle
  3. +16
    -6
      android/src/main/java/com/henninghall/date_picker/DatePickerManager.java
  4. +111
    -0
      android/src/main/java/com/henninghall/date_picker/DatePickerModule.java
  5. +7
    -9
      android/src/main/java/com/henninghall/date_picker/DatePickerPackage.java
  6. +9
    -0
      android/src/main/java/com/henninghall/date_picker/DerivedData.java
  7. +40
    -0
      android/src/main/java/com/henninghall/date_picker/Emitter.java
  8. +15
    -3
      android/src/main/java/com/henninghall/date_picker/PickerView.java
  9. +19
    -11
      android/src/main/java/com/henninghall/date_picker/State.java
  10. +9
    -3
      android/src/main/java/com/henninghall/date_picker/Utils.java
  11. +2
    -3
      android/src/main/java/com/henninghall/date_picker/ui/Accessibility.java
  12. +4
    -0
      android/src/main/java/com/henninghall/date_picker/ui/UIManager.java
  13. +5
    -15
      android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java
  14. BIN
      docs/react-native-date-time-picker-android-inline.gif
  15. BIN
      docs/react-native-date-time-picker-ios-inline.gif
  16. BIN
      docs/react-native-datetime-picker-modal-android.gif
  17. BIN
      docs/react-native-datetime-picker-modal-ios.gif
  18. +2
    -5
      examples/detox/android/app/src/main/res/values/styles.xml
  19. +6
    -2
      examples/detox/e2e/init.js
  20. +20
    -0
      examples/detox/e2e/tests/modal.spec.js
  21. +12
    -7
      examples/detox/src/App.js
  22. +5
    -1
      examples/detox/src/examples.js
  23. +31
    -0
      examples/detox/src/examples/Modal.js
  24. +21
    -0
      index.d.ts
  25. +97
    -0
      ios/RNDatePicker/RNDatePickerManager.m
  26. +21
    -11
      npmREADME.md
  27. +2
    -2
      package.json
  28. +49
    -16
      src/DatePickerAndroid.js
  29. +38
    -19
      src/DatePickerIOS.js
  30. +0
    -6
      src/defaultProps.js
  31. +25
    -8
      src/index.js
  32. +21
    -8
      src/propChecker.js
  33. +13
    -2
      src/propTypes.js
  34. +5460
    -3855
      yarn.lock

+ 93
- 40
README.md View File

@ -2,37 +2,52 @@
This is a React Native Date Picker with following main features:
📱 Supporting iOS and Android <br>
🕑 3 different modes: Time, Date, DateTime <br>
🌍 Multiple languages<br>
🎨 Customizable<br>
📱&nbsp; Supporting iOS and Android <br>
🕑&nbsp; 3 different modes: Time, Date, DateTime <br>
🌍&nbsp; Various languages<br>
🎨&nbsp; Customizable<br>
🖼&nbsp; Modal or stand-alone (inline)<br>
## Update 4.0.0
- ✅&nbsp; No breaking changes
- New feature: Modal mode
## Modal
The first option is to use the built-in modal. <a href="#example-1-modal">See code</a>.
<table>
<tr>
<td align="center"><b>iOS</b></td>
</tr>
<tr>
<td><img src="docs/react-native-date-picker.gif" alt="React Native Date Picker" title="React Native Date Picker" height="150px" />
<td><img src="docs/react-native-datetime-picker-modal-ios.gif" alt="React Native DateTime Picker Modal iOS" height="400px" style="margin-left:10px" /></td>
<td><img src="docs/react-native-datetime-picker-modal-android.gif" alt="React Native DateTime Picker Modal Android" height="400px" style="margin-left:10px" />
</td>
</tr>
<tr>
<td align="center" colspan="2"><b>Android</b><br>Choose from 2 different variants</td>
<tr>
<td align="center">iOS</td><td align="center">Android</td>
</tr>
</table>
## Stand-alone
The second option is to use the picker stand-alone. Inlined in a view or a custom made modal. <a href="#example-2-standalone">See code</a>.
<table>
<tr>
<td><img src="docs/react-native-date-picker-android.gif" alt="React Native Date Picker Android" height="150px" style="margin-left:10px" />
<td><img src="docs/react-native-date-time-picker-ios-inline.gif" alt="React Native DateTime Picker" height="400px" style="margin-left:10px" /></td>
<td><img src="docs/react-native-date-time-picker-android-inline.gif" alt="React Native Date Time Picker" height="400px" style="margin-left:10px" />
</td>
<td><img src="docs/react-native-date-picker-android-native.gif" alt="React Native Datepicker" height="150px" style="margin-left:10px" />
</td>
</tr>
<tr>
<td align="center"><code>androidVariant="iosClone"</code></td><td align="center"><code>androidVariant="nativeAndroid"</code></td>
<td align="center">iOS</td><td align="center">Android</td>
</tr>
</table>
## Requirements
- Xcode >= 11.6
- React Native >= 0.57.
- If using React Native 0.64, 0.64.2 or later must be used.
- If using Expo, SDK 42 or later must be used.
@ -92,7 +107,38 @@ eas build -p all --profile development
If you're having troubles, read the <a href="https://expo.canny.io/feature-requests/p/react-native-date-picker">pinned comment here. </a>
## Minimal Example
## Example 1: Modal
```jsx
import React, { useState } from 'react'
import { Button } from 'react-native'
import DatePicker from 'react-native-date-picker'
export default () => {
const [date, setDate] = useState(new Date())
const [open, setOpen] = useState(false)
return (
<>
<Button title="Open" onPress={() => setOpen(true)} />
<DatePicker
modal
open={open}
date={date}
onConfirm={(date) => {
setOpen(false)
setDate(date)
}}
onCancel={() => {
setOpen(false)
}}
/>
</>
)
}
```
## Example 2: Standalone
```jsx
import React, { useState } from 'react'
@ -107,30 +153,33 @@ export default () => {
## Props
| Prop | Description | Screenshots iOS | Screenshot Android |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| date | The currently selected date. |
| onDateChange | Date change handler |
| fadeToColor | Android picker is fading towards this background color. {color, 'none'} |
| maximumDate | Maximum selectable date. <br/> Example: `new Date("2021-12-31")` |
| minimumDate | Minimum selectable date. <br/> Example: `new Date("2021-01-01")` |
| androidVariant | Choose from 2 android style variants. {'iosClone', 'nativeAndroid'} (default: 'iosClone') | | <img src="docs/datetime-mode-android.png" alt="Datepicker ios clone variant" height="120px" /><img src="docs/react-native-date-picker-android.png" alt="Datepicker android native variant"/> |
| minuteInterval | The interval at which minutes can be selected. | <img src="docs/minute-interval-ios.png" alt="Date picker minute interval IOS" height="120px" /> | <img src="docs/minute-interval-android.png" alt="Date picker minute interval Android" height="120px" /> |
| mode | The date picker mode. {'datetime', 'date', 'time'} | <img src="docs/datetime-mode-ios.png" alt="React native date time picker" height="120px" /><img src="docs/date-mode-ios.png" alt="React native datepicker" height="120px" /><img src="docs/time-mode-ios.png" alt="React native time picker" height="120px" /> | <img src="docs/datetime-mode-android.png" alt="react native date time picker android" height="120px" /><img src="docs/date-mode-android.png" alt="react native datepicker android" height="120px" /><img src="docs/time-mode-android.png" alt="react native time picker android" height="120px" /> |
| locale | The locale for the date picker. Changes language, date order and am/pm preferences. Value needs to be a <a title="react native datepicker locale id" href="https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html">Locale ID.</a> | <img src="docs/locale-ios.png" alt="React Native Date picker locale language ios" height="120px" /> | <img src="docs/locale-android.png" alt="React Native Date picker locale language android" height="120px" /> |
| textColor | Changes the text color. ⚠ Colors other than black (#000000) or white (#ffffff) will replace the "Today" string with a date on iOS 13 or higher. | <img src="docs/colors-ios.png" alt="react native datepicker text color background color ios" height="120px" /> | <img src="docs/colors-android.png" alt="Text color background color android" height="120px" /> |
| timeZoneOffsetInMinutes | Timezone offset in minutes (default: device's timezone) |
| dividerHeight | Change the divider height (only supported for iosClone) |
| is24hourSource | Change how the 24h mode (am/pm) should be determined, by device settings or by locale. {'locale', 'device'} (android only, default: 'device') |
| Prop | Description | Screenshots iOS | Screenshot Android |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `date` | The currently selected date. |
| `onDateChange` | Date change handler |
| `fadeToColor` | Android picker is fading towards this background color. {color, 'none'} |
| `maximumDate` | Maximum selectable date. <br/> Example: `new Date("2021-12-31")` |
| `minimumDate` | Minimum selectable date. <br/> Example: `new Date("2021-01-01")` |
| `androidVariant` | Choose from 2 android style variants. `"iosClone"`, `"nativeAndroid"` | | <img src="docs/datetime-mode-android.png" alt="Datepicker ios clone variant" height="120px" /><img src="docs/react-native-date-picker-android.png" alt="Datepicker android native variant"/> |
| `minuteInterval` | The interval at which minutes can be selected. | <img src="docs/minute-interval-ios.png" alt="Date picker minute interval IOS" height="120px" /> | <img src="docs/minute-interval-android.png" alt="Date picker minute interval Android" height="120px" /> |
| `mode` | The date picker mode. `"datetime"`, `"date"`, `"time"` | <img src="docs/datetime-mode-ios.png" alt="React native date time picker" height="120px" /><img src="docs/date-mode-ios.png" alt="React native datepicker" height="120px" /><img src="docs/time-mode-ios.png" alt="React native time picker" height="120px" /> | <img src="docs/datetime-mode-android.png" alt="react native date time picker android" height="120px" /><img src="docs/date-mode-android.png" alt="react native datepicker android" height="120px" /><img src="docs/time-mode-android.png" alt="react native time picker android" height="120px" /> |
| `locale` | The locale for the date picker. Changes language, date order and am/pm preferences. Value needs to be a <a title="react native datepicker locale id" href="https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html">Locale ID.</a> | <img src="docs/locale-ios.png" alt="React Native Date picker locale language ios" height="120px" /> | <img src="docs/locale-android.png" alt="React Native Date picker locale language android" height="120px" /> |
| `textColor` | Changes the text color. ⚠ Colors other than black (#000000) or white (#ffffff) will replace the "Today" string with a date on iOS 13 or higher. | <img src="docs/colors-ios.png" alt="react native datepicker text color background color ios" height="120px" /> | <img src="docs/colors-android.png" alt="Text color background color android" height="120px" /> |
| `timeZoneOffsetInMinutes` | Timezone offset in minutes (default: device's timezone) |
| `dividerHeight` | Change the divider height (only supported for iosClone) |
| `is24hourSource` | Change how the 24h mode (am/pm) should be determined, by device settings or by locale. {'locale', 'device'} (android only, default: 'device') |
| `modal` | Boolean indicating if modal should be used. Default: `"false"`. When enabled, the other modal props needs to be used. <a href="#modal">See example</a>. |
| `open` | Modal only: Boolean indicating if modal should be open. |
| `onConfirm` | Modal only: Date callback when user presses confirm button |
| `onCancel` | Modal only: Callback for when user presses cancel button or closing the modal by pressing outside it. |
| `title` | Modal only: Title text. Can be set to null to remove text. |
| `confirmText` | Modal only: Confirm button text. |
| `cancelText` | Modal only: Cancel button text. |
## Linking
This package supports automatic linking. Usually, the only thing you need to do is to install the package, the cocoapods dependencies (as described above). Then rebuild the project by running `react-native run-ios`, `react-native run-android` or start the build from within Xcode/Android Studio. If you're running a React Native version below 0.60 or your setup is having issues with automatic linking, you can run `npx react-native link react-native-date-picker` and rebuild. In some occations you'll have to manually link the package. Instructions in <a href="https://github.com/henninghall/react-native-date-picker/issues/40">this issue</a>.
## About
📅 &nbsp; React Native Date Picker is a cross platform component working on both iOS and Android. It uses the slightly improved DatePickerIOS on iOS and a custom picker on Android which has similar look and feel. The datetime mode might be particularly interesting if you looking for a way to avoid two different popup pickers on android.
## FAQ
### How do I change the divider color?
@ -169,9 +218,9 @@ On Android there are two design variants to choose from:
<table>
<tr><td align="center"><b>iOS clone</b></td><td align="center"><b>Native Android</b></td>
</tr><tr><td align="center">
<img src="docs/react-native-date-picker-android.gif" alt="date time picker" height="120px" />
<img src="docs/react-native-date-picker-android.gif" alt="date time picker" height="150px" />
</td><td align="center">
<img src="docs/react-native-date-picker-android-native.gif" alt="date time picker" height="120px" />
<img src="docs/react-native-date-picker-android-native.gif" alt="date time picker" height="150px" />
</td></tr>
<tr><td>The so called "iOS clone" looks and works similar to the ios version. It shows normally 5 lines of dates. It is enabled by default.</td><td>
@ -260,8 +309,12 @@ Set mode property to `time` to show the time picker:
/>
```
## Why another React Native datepicker?
## About
React Native Date Picker is a cross platform component for iOS and Android. It uses native code from respective platform to get the genuine look and feel the users expect. A strong motivation for creating this picker was the datetime mode on Android. It's quite unique for the platform and avoids two different picker popups, which normally is necessary. Instead, this datetime mode requires fewer user actions and enables a great user-experience.
## Support this package!
One of the strongest reason to use react native is its cross platform compatibility. Most of the official components are working seamlessly on both platforms but there are some with single platform support only. The react native datepicker is one example where both <a href="https://facebook.github.io/react-native/docs/datepickerios">DatePickerIOS</a> and <a href="https://facebook.github.io/react-native/docs/datepickerandroid">DatePickerAndroid</a> are present. The reason for this is that the default date picker is implemented in separate ways, iOS normally have an integrated view picker wheel where android has different pickers in a dialog format.
If you like this package and want to support it, you can give it <a href="https://openbase.com/js/react-native-date-picker" target="_blank">a review</a> or a github star ⭐
If you want to use these pickers you can combine the official ones or a third party module that already done that for you. If you on the other hand want have a more unified design between your android and ios app, this module is for you. The datetime mode can be particularly helpful to avoid 2 separate picker dialogs on android.
Also, PR's are welcome!

+ 2
- 2
android/build.gradle View File

@ -5,11 +5,11 @@ def safeExtGet(prop, fallback) {
}
android {
compileSdkVersion safeExtGet('compileSdkVersion', 25)
compileSdkVersion safeExtGet('compileSdkVersion', 29)
buildToolsVersion safeExtGet('buildToolsVersion', "25.0.3")
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 17)
minSdkVersion safeExtGet('minSdkVersion', 18)
targetSdkVersion safeExtGet('targetSdkVersion', 25)
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

+ 16
- 6
android/src/main/java/com/henninghall/date_picker/DatePickerManager.java View File

@ -1,7 +1,18 @@
package com.henninghall.date_picker;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.SimpleViewManager;
@ -20,7 +31,6 @@ import com.henninghall.date_picker.props.ModeProp;
import com.henninghall.date_picker.props.TextColorProp;
import com.henninghall.date_picker.props.UtcProp;
import net.time4j.android.ApplicationStarter;
import java.lang.reflect.Method;
import java.util.Map;
@ -29,7 +39,6 @@ public class DatePickerManager extends SimpleViewManager {
private static final String REACT_CLASS = "DatePickerManager";
private static final int SCROLL = 1;
public static ThemedReactContext context;
@Override
public String getName() {
@ -37,10 +46,11 @@ public class DatePickerManager extends SimpleViewManager {
}
@Override
public PickerView createViewInstance(ThemedReactContext reactContext) {
DatePickerManager.context = reactContext;
ApplicationStarter.initialize(reactContext, false); // false = no need to prefetch on time data background tread
return new PickerView();
public PickerView createViewInstance(ThemedReactContext context) {
return new PickerView(new LinearLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT
));
}
@ReactPropGroup(names = { DateProp.name, ModeProp.name, LocaleProp.name, MaximumDateProp.name,

+ 111
- 0
android/src/main/java/com/henninghall/date_picker/DatePickerModule.java View File

@ -0,0 +1,111 @@
package com.henninghall.date_picker;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import net.time4j.android.ApplicationStarter;
public class DatePickerModule extends ReactContextBaseJavaModule {
DatePickerModule(ReactApplicationContext context) {
super(context);
ApplicationStarter.initialize(context, false); // false = no need to prefetch on time data background tread
}
@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
public void openPicker(ReadableMap props){
PickerView picker = createPicker(props);
AlertDialog dialog = createDialog(props, picker);
dialog.show();
}
private AlertDialog createDialog (ReadableMap props, final PickerView picker) {
String title = props.getString("title");
String confirmText = props.getString("confirmText");
final String cancelText = props.getString("cancelText");
final View pickerWithMargin = withTopMargin(picker);
return new AlertDialog.Builder(DatePickerPackage.context.getCurrentActivity())
.setTitle(title)
.setCancelable(true)
.setView(pickerWithMargin)
.setPositiveButton(confirmText, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Emitter.onConfirm(picker.getDate());
dialog.dismiss();
}
})
.setNegativeButton(cancelText, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Emitter.onCancel();
dialog.dismiss();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
Emitter.onCancel();
}
})
.create();
}
private PickerView createPicker(ReadableMap props){
int height = 180;
LinearLayout.LayoutParams rootLayoutParams = new LinearLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
Utils.toDp(height));
PickerView picker = new PickerView(rootLayoutParams);
ReadableMapKeySetIterator iterator = props.keySetIterator();
while(iterator.hasNextKey()){
String key = iterator.nextKey();
Dynamic value = props.getDynamic(key);
if(!key.equals("style")){
try{
picker.updateProp(key, value);
} catch (Exception e){
// ignore invalid prop
}
}
}
picker.update();
return picker;
}
private View withTopMargin(PickerView view) {
LinearLayout linearLayout = new LinearLayout(DatePickerPackage.context);
linearLayout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(view);
linearLayout.setPadding(0, Utils.toDp(20),0,0);
return linearLayout;
}
@Override
public String getName() {
return "RNDatePicker";
}
}

+ 7
- 9
android/src/main/java/com/henninghall/date_picker/DatePickerPackage.java View File

@ -10,25 +10,23 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Created by heng on 16/9/5.
*/
public class DatePickerPackage implements ReactPackage {
public static ReactApplicationContext context;
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
context = reactContext;
return Arrays.<NativeModule>asList(
new DatePickerModule(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
context = reactContext;
return Arrays.<ViewManager> asList(
new DatePickerManager()
);
}
}

+ 9
- 0
android/src/main/java/com/henninghall/date_picker/DerivedData.java View File

@ -2,6 +2,7 @@ package com.henninghall.date_picker;
import android.text.format.DateFormat;
import android.util.Log;
import android.util.TimeUtils;
import com.henninghall.date_picker.models.Mode;
import com.henninghall.date_picker.models.Variant;
@ -10,6 +11,7 @@ import com.henninghall.date_picker.models.WheelType;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import static com.henninghall.date_picker.models.Is24HourSource.*;
@ -125,4 +127,11 @@ public class DerivedData {
return state.getMode() == Mode.time && !usesAmPm();
}
public String getLastDate() {
Calendar lastSelectedDate = state.getLastSelectedDate();
String initialDate = state.getIsoDate();
if(lastSelectedDate != null) return Utils.dateToIso(lastSelectedDate);
return initialDate;
}
}

+ 40
- 0
android/src/main/java/com/henninghall/date_picker/Emitter.java View File

@ -0,0 +1,40 @@
package com.henninghall.date_picker;
import android.view.View;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import java.util.Calendar;
public class Emitter {
private static RCTEventEmitter eventEmitter(){
return DatePickerPackage.context.getJSModule(RCTEventEmitter.class);
}
private static DeviceEventManagerModule.RCTDeviceEventEmitter deviceEventEmitter(){
return DatePickerPackage.context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
}
public static void onDateChange(Calendar date, String displayValueString, View view) {
WritableMap event = Arguments.createMap();
String dateString = Utils.dateToIso(date);
event.putString("date", dateString);
event.putString("dateString", displayValueString);
eventEmitter().receiveEvent(view.getId(), "dateChange", event);
}
public static void onConfirm(String date) {
WritableMap event = Arguments.createMap();
event.putString("date", date);
deviceEventEmitter().emit("onConfirm", event);
}
public static void onCancel() {
WritableMap event = Arguments.createMap();
deviceEventEmitter().emit("onCancel", event);
}
}

+ 15
- 3
android/src/main/java/com/henninghall/date_picker/PickerView.java View File

@ -1,5 +1,8 @@
package com.henninghall.date_picker;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.facebook.react.bridge.Dynamic;
@ -21,19 +24,24 @@ import java.util.ArrayList;
public class PickerView extends RelativeLayout {
private final ViewGroup.LayoutParams layoutParams;
private UIManager uiManager;
private State state = new State();
private ArrayList<String> updatedProps = new ArrayList<>();
public PickerView() {
super(DatePickerManager.context);
public PickerView(ViewGroup.LayoutParams layoutParams) {
super(DatePickerPackage.context);
this.layoutParams = layoutParams;
}
public void update() {
if (didUpdate(VariantProp.name)) {
this.removeAllViewsInLayout();
inflate(getContext(), state.derived.getRootLayout(), this);
LinearLayout layout = new LinearLayout(getContext());
LayoutInflater.from(getContext()).inflate(state.derived.getRootLayout(), layout);
this.addView(layout, layoutParams);
uiManager = new UIManager(state, this);
}
@ -97,6 +105,10 @@ public class PickerView extends RelativeLayout {
uiManager.scroll(wheelIndex, scrollTimes);
}
public String getDate() {
return state.derived.getLastDate();
}
private final Runnable measureAndLayout = new Runnable() {
@Override
public void run() {

+ 19
- 11
android/src/main/java/com/henninghall/date_picker/State.java View File

@ -26,6 +26,7 @@ import java.util.TimeZone;
public class State {
private Calendar lastSelectedDate = null;
private final DateProp dateProp = new DateProp();
private final ModeProp modeProp = new ModeProp();
private final LocaleProp localeProp = new LocaleProp();
@ -57,15 +58,15 @@ public class State {
}};
public DerivedData derived;
public State(){
public State() {
derived = new DerivedData(this);
}
private Prop getProp(String name){
private Prop getProp(String name) {
return (Prop) props.get(name);
}
void setProp(String propName, Dynamic value){
void setProp(String propName, Dynamic value) {
getProp(propName).setValue(value);
}
@ -89,38 +90,38 @@ public class State {
return (Locale) localeProp.getValue();
}
public Calendar getMinimumDate(){
public Calendar getMinimumDate() {
DateBoundary db = new DateBoundary(getTimeZone(), (String) minimumDateProp.getValue());
return db.get();
}
public Calendar getMaximumDate(){
public Calendar getMaximumDate() {
DateBoundary db = new DateBoundary(getTimeZone(), (String) maximumDateProp.getValue());
return db.get();
}
public TimeZone getTimeZone(){
public TimeZone getTimeZone() {
boolean utc = (boolean) utcProp.getValue();
return utc ? TimeZone.getTimeZone("UTC") : TimeZone.getDefault();
}
public String getDateString() {
public String getIsoDate() {
return (String) dateProp.getValue();
}
public Calendar getDate() {
return Utils.isoToCalendar(getDateString(), getTimeZone());
return Utils.isoToCalendar(getIsoDate(), getTimeZone());
}
public Integer getHeight() {
return (Integer) heightProp.getValue();
}
public String getLocaleLanguageTag(){
public String getLocaleLanguageTag() {
return localeProp.getLanguageTag();
}
public Variant getVariant(){
public Variant getVariant() {
return variantProp.getValue();
}
@ -132,4 +133,11 @@ public class State {
return is24hourSourceProp.getValue();
}
}
public Calendar getLastSelectedDate() {
return lastSelectedDate;
}
public void setLastSelectedDate(Calendar date) {
lastSelectedDate = date;
}
}

+ 9
- 3
android/src/main/java/com/henninghall/date_picker/Utils.java View File

@ -4,6 +4,7 @@ package com.henninghall.date_picker;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import com.facebook.react.bridge.ReactApplicationContext;
import com.henninghall.date_picker.models.WheelType;
import net.time4j.PrettyTime;
@ -19,7 +20,7 @@ import java.util.TimeZone;
public class Utils {
public static boolean deviceUsesAmPm(){
return !DateFormat.is24HourFormat(DatePickerManager.context);
return !DateFormat.is24HourFormat(DatePickerPackage.context);
}
public static String printToday(Locale locale) {
@ -100,8 +101,13 @@ public class Utils {
}
public static String getLocalisedStringFromResources(Locale locale, String tagName) {
int selectedKey = DatePickerManager.context.getResources().getIdentifier(tagName,"string",DatePickerManager.context.getPackageName());
String localisedText = LocaleUtils.getLocaleStringResource(locale, selectedKey, DatePickerManager.context);
ReactApplicationContext context = DatePickerPackage.context;
int selectedKey = context.getResources().getIdentifier(tagName,"string", context.getPackageName());
String localisedText = LocaleUtils.getLocaleStringResource(locale, selectedKey, context);
return localisedText;
}
public static int toDp(int pixels){
return (int) (pixels * DatePickerPackage.context.getResources().getDisplayMetrics().density);
}
}

+ 2
- 3
android/src/main/java/com/henninghall/date_picker/ui/Accessibility.java View File

@ -8,20 +8,19 @@ import android.view.accessibility.AccessibilityManager;
import android.accessibilityservice.AccessibilityServiceInfo;
import cn.carbswang.android.numberpickerview.library.NumberPickerView;
import com.henninghall.date_picker.DatePickerManager;
import com.henninghall.date_picker.DatePickerPackage;
import com.henninghall.date_picker.State;
import com.henninghall.date_picker.Utils;
import com.henninghall.date_picker.wheelFunctions.WheelFunction;
import com.henninghall.date_picker.wheels.Wheel;
import java.util.Locale;
import java.util.Arrays;
import java.util.List;
public class Accessibility {
private final static AccessibilityManager systemManager =
(AccessibilityManager) DatePickerManager.context
(AccessibilityManager) DatePickerPackage.context
.getApplicationContext()
.getSystemService(Context.ACCESSIBILITY_SERVICE);

+ 4
- 0
android/src/main/java/com/henninghall/date_picker/ui/UIManager.java View File

@ -99,4 +99,8 @@ public class UIManager {
public void updateAccessibilityValues() {
wheels.applyOnAll(new Accessibility.SetAccessibilityDelegate(state.getLocale()));
}
public void updateLastSelectedDate(Calendar date) {
state.setLastSelectedDate(date);
}
}

+ 5
- 15
android/src/main/java/com/henninghall/date_picker/ui/WheelChangeListenerImpl.java View File

@ -2,12 +2,8 @@ package com.henninghall.date_picker.ui;
import android.view.View;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.henninghall.date_picker.DatePickerManager;
import com.henninghall.date_picker.Emitter;
import com.henninghall.date_picker.State;
import com.henninghall.date_picker.Utils;
import com.henninghall.date_picker.wheels.Wheel;
import java.text.ParseException;
@ -65,7 +61,10 @@ public class WheelChangeListenerImpl implements WheelChangeListener {
uiManager.updateContentDescription(picker);
emitDateChangeEvent(selectedDate);
String displayData = uiManager.getDisplayValueString();
uiManager.updateLastSelectedDate(selectedDate);
Emitter.onDateChange(selectedDate, displayData, rootView);
}
// Example: Jan 1 returns true, April 31 returns false.
@ -114,13 +113,4 @@ public class WheelChangeListenerImpl implements WheelChangeListener {
return null;
}
private void emitDateChangeEvent(Calendar date) {
WritableMap event = Arguments.createMap();
String dateString = Utils.dateToIso(date);
event.putString("date", dateString);
event.putString("dateString", uiManager.getDisplayValueString());
DatePickerManager.context.getJSModule(RCTEventEmitter.class)
.receiveEvent(rootView.getId(), "dateChange", event);
}
}

BIN
docs/react-native-date-time-picker-android-inline.gif View File

Before After
Width: 370  |  Height: 614  |  Size: 349 KiB

BIN
docs/react-native-date-time-picker-ios-inline.gif View File

Before After
Width: 600  |  Height: 1298  |  Size: 2.6 MiB

BIN
docs/react-native-datetime-picker-modal-android.gif View File

Before After
Width: 366  |  Height: 614  |  Size: 915 KiB

BIN
docs/react-native-datetime-picker-modal-ios.gif View File

Before After
Width: 600  |  Height: 1298  |  Size: 1.1 MiB

+ 2
- 5
examples/detox/android/app/src/main/res/values/styles.xml View File

@ -2,10 +2,7 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorControlNormal">#03b6fc</item>
<!-- <item name="android:textColorPrimary">#03b6fc</item> -->
<item name="colorControlNormal">#03b6fc</item>
</style>
</resources>
</resources>

+ 6
- 2
examples/detox/e2e/init.js View File

@ -1,14 +1,18 @@
const detox = require('detox')
const config = require('../package.json').detox
const adapter = require('detox/runners/mocha/adapter')
const { reset } = require("./utils";)
const { reset } = require('./utils';)
before(async () => {
await detox.init(config)
})
beforeEach(async function () {
await reset()
try {
await reset()
} catch (e) {
// some tests cannot reset, it's ok.
}
await adapter.beforeEach(this)
})

+ 20
- 0
examples/detox/e2e/tests/modal.spec.js View File

@ -0,0 +1,20 @@
describe('Modal', () => {
before(async () => {
await device.reloadReactNative()
await element(by.text('Modal')).tap()
})
it('can open and close modal', async () => {
await element(by.id('openModal')).tap()
await expect(element(by.id('day'))).toBeVisible()
await expect(element(by.id('minutes'))).toBeVisible()
await expect(element(by.id('hour'))).toBeVisible()
await expect(element(by.id('ampm'))).toBeVisible()
await expect(element(by.id('month'))).not.toExist()
await expect(element(by.id('date'))).not.toExist()
await expect(element(by.id('year'))).not.toExist()
await element(by.text('CONFIRM')).tap()
await expect(element(by.id('day'))).not.toBeVisible()
})
})

+ 12
- 7
examples/detox/src/App.js View File

@ -1,11 +1,16 @@
import React, { Component } from 'react'
import { ScrollView, AppRegistry, StyleSheet, Text, TouchableOpacity } from 'react-native'
import {
ScrollView,
AppRegistry,
StyleSheet,
Text,
TouchableOpacity,
} from 'react-native'
import examples from './examples'
class App extends Component {
state = {
picker: undefined,
backgroundColor: '#ffffff',
}
render() {
@ -25,7 +30,7 @@ class App extends Component {
)
}
setBackgroundColor = backgroundColor => this.setState({ backgroundColor })
setBackgroundColor = (backgroundColor) => this.setState({ backgroundColor })
renderPicker = () => {
const Picker = examples[this.state.picker].component
@ -39,10 +44,10 @@ class App extends Component {
renderButtons = () =>
Object.keys(examples)
.filter(key => key !== this.state.picker)
.filter((key) => key !== this.state.picker)
.map(this.renderButton)
renderButton = key => (
renderButton = (key) => (
<TouchableOpacity
key={key}
onPress={() => this.setState({ picker: key })}
@ -52,7 +57,7 @@ class App extends Component {
</TouchableOpacity>
)
renderBackButton = key => (
renderBackButton = (key) => (
<TouchableOpacity
onPress={() => this.setState({ picker: undefined })}
style={{ margin: 10, position: 'absolute', top: 0, left: 10 }}
@ -81,4 +86,4 @@ const styles = StyleSheet.create({
},
})
AppRegistry.registerComponent('example', () => App)
AppRegistry.registerComponent('example', () => App)

+ 5
- 1
examples/detox/src/examples.js View File

@ -1,5 +1,5 @@
import React, { Component } from 'react'
import Minimal from './examples/Minimal'
import Modal from './examples/Modal'
import Advanced from './examples/Advanced'
import TimeMode from './examples/TimeMode'
import { EXAMPLE_KEYS } from './exampleKeys'
@ -22,4 +22,8 @@ export default {
buttonTitle: 'Date mode',
component: DateMode,
},
[EXAMPLE_KEYS.MODAL]: {
buttonTitle: 'Modal',
component: Modal,
},
}

+ 31
- 0
examples/detox/src/examples/Modal.js View File

@ -0,0 +1,31 @@
import React from 'react'
import { Button, View, Text } from 'react-native'
import DatePicker from 'react-native-date-picker'
export default class ModalExample extends React.Component {
state = { date: new Date(), open: false }
render = () => (
<View style={{ alignItems: 'center' }}>
<Button
testID="openModal"
title="Select date"
onPress={() => this.setState({ open: true })}
/>
<DatePicker
modal
open={this.state.open}
date={this.state.date}
onConfirm={(date) => this.setState({ date, open: false })}
onCancel={() => this.setState({ open: false })}
androidVariant="nativeAndroid"
/>
<Text style={{ marginTop: 20, fontSize: 26 }}>
{this.state.date.toISOString().substr(0, 10)}
</Text>
<Text style={{ marginTop: 20, fontSize: 26 }}>
{this.state.date.toLocaleTimeString()}
</Text>
</View>
)
}

+ 21
- 0
index.d.ts View File

@ -79,6 +79,27 @@ export interface DatePickerProps extends ViewProps {
* "device" is default on android and "locale" on iOS. On iOS this cannot be changed.
*/
is24hourSource?: 'locale' | 'device'
/** Enables the built-in modal */
modal?: boolean
/** Modal prop only. Set to true to open the modal */
open?: boolean
/** Modal callback invoked when the user presses the confirm button */
onConfirm?: (date: Date) => void
/** Modal callback invoked when user presses the cancel button or closes the modal by a press outside */
onCancel?: () => void
/** Modal confirm button text */
confirmText?: string
/** Modal cancel button text */
cancelText?: string
/** Modal title. Set to null to remove */
title?: string | null
}
export default class DatePicker extends Component<DatePickerProps> {}

+ 97
- 0
ios/RNDatePicker/RNDatePickerManager.m View File

@ -6,7 +6,9 @@
*/
#import "RNDatePickerManager.h"
#import <React/RCTLog.h>
#import "RCTConvert.h"
#import "DatePicker.h"
@ -25,6 +27,14 @@ RCT_ENUM_CONVERTER(UIDatePickerMode, (@{
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(addListener : (NSString *)eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}
RCT_EXPORT_METHOD(removeListeners : (NSInteger)count) {
// Keep: Required for RN built in Event Emitter Calls.
}
- (UIView *)view
{
return [DatePicker new];
@ -45,4 +55,91 @@ RCT_CUSTOM_VIEW_PROPERTY(textColor, NSString, DatePicker)
[view setTextColorProp:[RCTConvert NSString:json]];
}
RCT_EXPORT_METHOD(openPicker:(NSDictionary *) props
onConfirm:(RCTResponseSenderBlock) onConfirm
onCancel:(RCTResponseSenderBlock) onCancel)
{
dispatch_async(dispatch_get_main_queue(), ^{
bool iPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
CGRect rootBounds = rootViewController.view.bounds;
NSString * title = [RCTConvert NSString:[props objectForKey:@"title"]];
title = [title isEqualToString:@""] ? nil : title;
NSString * confirmText = [RCTConvert NSString:[props objectForKey:@"confirmText"]];
NSString * cancelText = [RCTConvert NSString:[props objectForKey:@"cancelText"]];
DatePicker* picker = [[DatePicker alloc] init];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet];
UIView * alertView = alertController.view;
// height
int heightPx = iPad ? (title ? 300 : 260) : (title ? 370 : 340);
NSLayoutConstraint *heigth = [NSLayoutConstraint constraintWithItem:alertView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:heightPx];
[alertView addConstraint:heigth];
CGRect bounds = picker.bounds;
// picker width
int widthPx = iPad ? 320 : alertController.view.bounds.size.width - 15;
bounds.size.width = widthPx;
// top padding
bounds.origin.y += iPad ? (title ? 20: 5) : (title ? 30 : 10);
[picker setFrame: bounds];
NSDate * _Nonnull date = [RCTConvert NSDate:[props objectForKey:@"date"]];
[picker setDate:date];
NSDate * minimumDate = [RCTConvert NSDate:[props objectForKey:@"minimumDate"]];
if(minimumDate) [picker setMinimumDate:minimumDate];
NSDate * maximumDate = [RCTConvert NSDate:[props objectForKey:@"maximumDate"]];
if(maximumDate) [picker setMaximumDate:maximumDate];
NSString * textColor = [RCTConvert NSString:[props objectForKey:@"textColor"]];
if(textColor) [picker setTextColorProp:textColor];
UIDatePickerMode mode = [RCTConvert UIDatePickerMode:[props objectForKey:@"mode"]];
[picker setDatePickerMode:mode];
NSLocale * locale = [RCTConvert NSLocale:[props objectForKey:@"locale"]];
if(locale) [picker setLocale:locale];
int minuteInterval = [RCTConvert int:[props objectForKey:@"minuteInterval"]];
[picker setMinuteInterval:minuteInterval];
NSTimeZone* timezone = [RCTConvert NSTimeZone:[props valueForKey:@"timeZoneOffsetInMinutes"]];
[picker setTimeZone:timezone];
[alertView addSubview:picker];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:confirmText style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
onConfirm(@[@{ @"timestamp": @(picker.date.timeIntervalSince1970 * 1000.0) }]);
}];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelText style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
onCancel(@[]);
}];
[alertController addAction:cancelAction];
[alertController addAction:confirmAction];
if (@available(iOS 9.0, *)) {
alertController.preferredAction = confirmAction;
}
// ipad needs to display the picker in a popover
if (iPad) {
UIPopoverPresentationController *popPresenter = [alertController popoverPresentationController];
popPresenter.sourceRect = CGRectMake(CGRectGetMidX(rootBounds), CGRectGetMidY(rootBounds),0,0);
popPresenter.sourceView = rootViewController.view;
popPresenter.presentingViewController.preferredContentSize = CGSizeMake(widthPx, heightPx);
[popPresenter setPermittedArrowDirections: (UIPopoverArrowDirection) 0];
}
[rootViewController presentViewController:alertController animated:YES completion:nil];
});
}
@end

+ 21
- 11
npmREADME.md View File

@ -2,23 +2,33 @@
A cross platform <a href="https://github.com/henninghall/react-native-date-picker" title="React Native Date Pickers">react native date picker</a> component for android and ios. It includes 3 different modes: date, time, and datetime. The date picker is customizable and has multiple language support.
<table >
<tr>
<td align="center"><b>iOS</b></td>
</tr>
## Modal
The first option is to use the built-in modal.
<table>
<tr>
<td ><img src="https://github.com/henninghall/react-native-date-picker/raw/master/docs/react-native-date-picker.gif" alt="React Native Date Picker" title="React Native Date Picker" height="150px"/>
<td><img src="https://github.com/henninghall/react-native-date-picker/raw/master/docs/react-native-datetime-picker-modal-ios.gif" alt="React Native DateTime Picker Modal iOS" height="400px" style="margin-left:10px" /></td>
<td><img src="https://github.com/henninghall/react-native-date-picker/raw/master/docs/react-native-datetime-picker-modal-android.gif" alt="React Native DateTime Picker Modal Android" height="400px" style="margin-left:10px" />
</td>
</tr>
<tr>
<td align="center" colspan="2"><b>Android</b><br>Choose from 2 different variants</td>
<tr>
<td align="center">iOS</td><td align="center">Android</td>
</tr>
</table>
## Stand-alone
The second option is to use the picker stand-alone. Inlined in a view or a custom made modal.
<table>
<tr>
<td><img src="https://github.com/henninghall/react-native-date-picker/raw/master/docs/react-native-date-picker-android.gif" alt="React Native Date Picker Android" height="150px" style="margin-left:10px" />
<td><img src="https://github.com/henninghall/react-native-date-picker/raw/master/docs/react-native-date-time-picker-ios-inline.gif" alt="React Native DateTime Picker" height="400px" style="margin-left:10px" /></td>
<td><img src="https://github.com/henninghall/react-native-date-picker/raw/master/docs/react-native-date-time-picker-android-inline.gif" alt="React Native Date Time Picker" height="400px" style="margin-left:10px" />
</td>
<td><img src="https://raw.githubusercontent.com/henninghall/react-native-date-picker/master/docs/react-native-date-picker-android-native.gif" alt="React Native Datepicker" height="150px" style="margin-left:10px" />
</td>
</tr>
<tr>
<td align="center">iOS</td><td align="center">Android</td>
</tr>
</table>

+ 2
- 2
package.json View File

@ -1,7 +1,7 @@
{
"name": "react-native-date-picker",
"version": "3.4.3",
"description": "A Cross Platform React Native Picker",
"version": "4.0.0-3",
"description": "A datetime picker for React Native. In-modal or stand-alone. Supports Android and iOS.",
"main": "src/index.js",
"scripts": {
"prepublishOnly": "mv README.md githubREADME.md && mv npmREADME.md README.md",

+ 49
- 16
src/DatePickerAndroid.js View File

@ -1,5 +1,10 @@
import React from 'react'
import { StyleSheet, requireNativeComponent } from 'react-native'
import {
StyleSheet,
requireNativeComponent,
NativeModules,
NativeEventEmitter,
} from 'react-native'
function addMinutes(date, minutesToAdd) {
return new Date(date.valueOf() + minutesToAdd * 60 * 1000)
@ -16,26 +21,54 @@ const timeModeWidth = 240
const defaultWidth = 310
class DatePickerAndroid extends React.PureComponent {
render() {
return (
<NativeDatePicker
{...this.props}
date={this._date()}
minimumDate={this._minimumDate()}
maximumDate={this._maximumDate()}
onChange={this._onChange}
style={this.getStyle()}
utc={this.props.timeZoneOffsetInMinutes !== undefined}
/>
componentDidMount() {
const { onConfirm, onCancel } = this.props
const eventEmitter = new NativeEventEmitter(NativeModules.RNDatePicker)
this.confirmListener = eventEmitter.addListener(
'onConfirm',
({ date: isoDate }) => {
if (onConfirm) {
onConfirm(this._fromIsoWithTimeZoneOffset(isoDate))
}
}
)
this.cancelListener = eventEmitter.addListener('onCancel', () => {
if (onCancel) onCancel()
})
}
componentWillUnmount() {
this.confirmListener.remove()
this.cancelListener.remove()
}
getStyle = () => {
render() {
const props = this.getProps()
if (props.modal) {
if (props.open) {
NativeModules.RNDatePicker.openPicker(props)
}
return null
}
return <NativeDatePicker {...props} onChange={this._onChange} />
}
getProps = () => ({
...this.props,
date: this._date(),
minimumDate: this._minimumDate(),
maximumDate: this._maximumDate(),
utc: this.props.timeZoneOffsetInMinutes !== undefined,
style: this._getStyle(),
})
_getStyle = () => {
const width = this.props.mode === 'time' ? timeModeWidth : defaultWidth
return [{ width, height }, this.props.style]
}
_onChange = e => {
_onChange = (e) => {
const jsDate = this._fromIsoWithTimeZoneOffset(e.nativeEvent.date)
this.props.onDateChange && this.props.onDateChange(jsDate)
if (this.props.onDateStringChange) {
@ -53,13 +86,13 @@ class DatePickerAndroid extends React.PureComponent {
_date = () => this._toIsoWithTimeZoneOffset(this.props.date)
_fromIsoWithTimeZoneOffset = timestamp => {
_fromIsoWithTimeZoneOffset = (timestamp) => {
const date = new Date(timestamp)
if (this.props.timeZoneOffsetInMinutes === undefined) return date
return addMinutes(date, -this.props.timeZoneOffsetInMinutes)
}
_toIsoWithTimeZoneOffset = date => {
_toIsoWithTimeZoneOffset = (date) => {
if (this.props.timeZoneOffsetInMinutes === undefined)
return date.toISOString()

+ 38
- 19
src/DatePickerIOS.js View File

@ -1,5 +1,10 @@
import React from 'react'
import { StyleSheet, View, requireNativeComponent } from 'react-native'
import React, { useEffect } from 'react'
import {
StyleSheet,
View,
requireNativeComponent,
NativeModules,
} from 'react-native'
const RCTDatePickerIOS = requireNativeComponent('RNDatePicker')
@ -17,37 +22,51 @@ export default class DatePickerIOS extends React.Component {
}
}
_onChange = event => {
_onChange = (event) => {
const nativeTimeStamp = event.nativeEvent.timestamp
this.props.onDateChange &&
this.props.onDateChange(new Date(nativeTimeStamp))
}
_toIosProps = (props) => {
return {
...props,
style: [styles.datePickerIOS, props.style],
date: props.date ? props.date.getTime() : undefined,
locale: props.locale ? props.locale : undefined,
maximumDate: props.maximumDate ? props.maximumDate.getTime() : undefined,
minimumDate: props.minimumDate ? props.minimumDate.getTime() : undefined,
}
}
_onConfirm = ({ timestamp }) => {
this.props.onConfirm(new Date(timestamp))
}
render() {
const { props } = this
const props = this._toIosProps(this.props)
if (props.modal) {
if (props.open) {
NativeModules.RNDatePickerManager.openPicker(
props,
this._onConfirm,
props.onCancel
)
}
return null
}
return (
<RCTDatePickerIOS
testID={props.testID}
key={props.textColor} // preventing "Today" string keep old text color when text color changes
ref={picker => {
ref={(picker) => {
this._picker = picker
}}
style={[styles.datePickerIOS, props.style]}
date={props.date ? props.date.getTime() : undefined}
locale={props.locale ? props.locale : undefined}
maximumDate={
props.maximumDate ? props.maximumDate.getTime() : undefined
}
minimumDate={
props.minimumDate ? props.minimumDate.getTime() : undefined
}
mode={props.mode}
minuteInterval={props.minuteInterval}
timeZoneOffsetInMinutes={props.timeZoneOffsetInMinutes}
onChange={this._onChange}
onStartShouldSetResponder={() => true}
onResponderTerminationRequest={() => false}
textColor={props.textColor}
{...props}
/>
)
}

+ 0
- 6
src/defaultProps.js View File

@ -1,6 +0,0 @@
export default {
mode: 'datetime',
minuteInterval: 1,
androidVariant: 'iosClone',
is24hourSource: 'device',
}

+ 25
- 8
src/index.js View File

@ -3,7 +3,6 @@ import { Platform } from 'react-native'
import DatePickerIOS from './DatePickerIOS'
import DatePickerAndroid from './DatePickerAndroid'
import propTypes from './propTypes'
import defaultProps from './defaultProps'
import { colorToHex } from './colorToHex'
import { throwIfInvalidProps } from './propChecker'
@ -12,20 +11,38 @@ const DatePicker = Platform.select({
ios: DatePickerIOS,
})
DatePicker.defaultProps = defaultProps
DatePicker.propTypes = propTypes
const DatePickerWrapper = props => {
const { textColor, fadeToColor, innerRef, ...rest } = props
const DatePickerWrapper = (props) => {
if (__DEV__) throwIfInvalidProps(props)
return (
<DatePicker
ref={innerRef}
textColor={colorToHex(textColor)}
fadeToColor={colorToHex(fadeToColor)}
{...rest}
ref={props.innerRef}
{...props}
textColor={colorToHex(props.textColor)}
fadeToColor={colorToHex(props.fadeToColor)}
title={getTitle(props)}
confirmText={props.confirmText ? props.confirmText : 'Confirm'}
cancelText={props.cancelText ? props.cancelText : 'Cancel'}
androidVariant={getAndroidVariant(props)}
minuteInterval={props.minuteInterval ? props.minuteInterval : 1}
mode={props.mode ? props.mode : 'datetime'}
/>
)
}
const getAndroidVariant = (props) => {
const { modal, androidVariant } = props
if (androidVariant) return androidVariant
return modal ? 'nativeAndroid' : 'iosClone'
}
const getTitle = (props) => {
const { title, mode } = props
if (title === null) return ''
if (title) return title
if (mode === 'time') return 'Select time'
return 'Select date'
}
export default React.memo(DatePickerWrapper)

+ 21
- 8
src/propChecker.js View File

@ -1,5 +1,5 @@
export function throwIfInvalidProps(props) {
checks.forEach(check => check.validate(props))
checks.forEach((check) => check.validate(props))
}
class PropCheck {
@ -7,7 +7,7 @@ class PropCheck {
this.isInvalid = isInvalid
this.errorText = errorText
}
validate = props => {
validate = (props) => {
if (this.isInvalid(props)) {
throw new Error(
`${this.errorText} Check usage of react-native-date-picker.`
@ -16,8 +16,13 @@ class PropCheck {
}
}
const dateCheck = new PropCheck(
(props) => props && !(props.date instanceof Date),
'Invalid or missing Date prop. Must be a Date object.'
)
const widthCheck = new PropCheck(
props =>
(props) =>
props &&
props.style &&
props.style.width &&
@ -26,7 +31,7 @@ const widthCheck = new PropCheck(
)
const heightCheck = new PropCheck(
props =>
(props) =>
props &&
props.style &&
props.style.height &&
@ -35,15 +40,23 @@ const heightCheck = new PropCheck(
)
const modeCheck = new PropCheck(
props =>
(props) =>
props && props.mode && !['datetime', 'date', 'time'].includes(props.mode),
"Invalid mode. Valid modes: 'datetime', 'date', 'time'"
)
const androidVariantCheck = new PropCheck(
props =>
props && props.androidVariant && !['nativeAndroid', 'iosClone'].includes(props.androidVariant),
(props) =>
props &&
props.androidVariant &&
!['nativeAndroid', 'iosClone'].includes(props.androidVariant),
"Invalid android variant. Valid modes: 'nativeAndroid', 'iosClone'"
)
const checks = [widthCheck, heightCheck, modeCheck, androidVariantCheck]
const checks = [
dateCheck,
widthCheck,
heightCheck,
modeCheck,
androidVariantCheck,
]

+ 13
- 2
src/propTypes.js View File

@ -1,17 +1,27 @@
import { Platform, ViewPropTypes } from 'react-native'
import PropTypes from 'prop-types'
const androidProptypes = {
const androidPropTypes = {
fadeToColor: PropTypes.string,
androidVariant: PropTypes.oneOf(['iosClone', 'nativeAndroid']),
dividerHeight: PropTypes.number,
is24hourSource: PropTypes.oneOf(['locale', 'device']),
}
const modalPropTypes = {
modal: PropTypes.bool,
open: PropTypes.bool,
onConfirm: PropTypes.func,
onCancel: PropTypes.func,
confirmText: PropTypes.string,
cancelText: PropTypes.string,
title: PropTypes.string,
}
const DateType = PropTypes.instanceOf(Date)
export default {
...(Platform === 'android' ? androidProptypes : {}),
...(Platform === 'android' ? androidPropTypes : {}),
date: DateType.isRequired,
onChange: PropTypes.func,
minimumDate: DateType,
@ -23,4 +33,5 @@ export default {
timeZoneOffsetInMinutes: PropTypes.number,
testID: ViewPropTypes.testID,
style: ViewPropTypes.style,
...modalPropTypes,
}

+ 5460
- 3855
yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save