Migrate to expo/ui DatePicker
This commit is contained in:
+1
-1
@@ -121,6 +121,7 @@
|
|||||||
"@emoji-mart/data": "^1.2.1",
|
"@emoji-mart/data": "^1.2.1",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
"@expo/html-elements": "^0.12.5",
|
"@expo/html-elements": "^0.12.5",
|
||||||
|
"@expo/ui": "~57.0.11",
|
||||||
"@expo/webpack-config": "^19.0.1",
|
"@expo/webpack-config": "^19.0.1",
|
||||||
"@floating-ui/dom": "^1.6.3",
|
"@floating-ui/dom": "^1.6.3",
|
||||||
"@floating-ui/react-dom": "^2.0.8",
|
"@floating-ui/react-dom": "^2.0.8",
|
||||||
@@ -230,7 +231,6 @@
|
|||||||
"react-is": "19",
|
"react-is": "19",
|
||||||
"react-keyed-flatten-children": "^5.0.0",
|
"react-keyed-flatten-children": "^5.0.0",
|
||||||
"react-native": "0.86.0",
|
"react-native": "0.86.0",
|
||||||
"react-native-date-picker": "^5.0.13",
|
|
||||||
"react-native-device-attest": "^0.1.6",
|
"react-native-device-attest": "^0.1.6",
|
||||||
"react-native-drawer-layout": "^4.2.3",
|
"react-native-drawer-layout": "^4.2.3",
|
||||||
"react-native-edge-to-edge": "^1.8.1",
|
"react-native-edge-to-edge": "^1.8.1",
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
diff --git a/ios/RNDatePicker.h b/ios/RNDatePicker.h
|
|
||||||
index 480746eb7acfbe86f67547d9e1de7a5be4d5faf2..13d30cb547195993dfcb4005cc0d248de9ac391a 100644
|
|
||||||
--- a/ios/RNDatePicker.h
|
|
||||||
+++ b/ios/RNDatePicker.h
|
|
||||||
@@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_END
|
|
||||||
#else
|
|
||||||
#import "DatePicker.h"
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
+#include <string>
|
|
||||||
|
|
||||||
@interface RNDatePicker : DatePicker
|
|
||||||
|
|
||||||
@@ -22,4 +23,3 @@ NS_ASSUME_NONNULL_END
|
|
||||||
@end
|
|
||||||
|
|
||||||
#endif
|
|
||||||
-
|
|
||||||
diff --git a/package.json b/package.json
|
|
||||||
index 469386dc2e81ded8994818dd4340409da1396488..36460e4e303ae56534c4d389471fc827433c7028 100644
|
|
||||||
--- a/package.json
|
|
||||||
+++ b/package.json
|
|
||||||
@@ -70,9 +70,6 @@
|
|
||||||
"ios": {
|
|
||||||
"componentProvider": {
|
|
||||||
"RNDatePicker": "RNDatePicker"
|
|
||||||
- },
|
|
||||||
- "modulesProvider": {
|
|
||||||
- "RNDatePicker": "RNDatePickerManager"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,7 +38,6 @@ patchedDependencies:
|
|||||||
'expo-notifications@57.0.7': patches/expo-notifications@57.0.7.patch
|
'expo-notifications@57.0.7': patches/expo-notifications@57.0.7.patch
|
||||||
'expo-updates@57.0.10': patches/expo-updates@57.0.10.patch
|
'expo-updates@57.0.10': patches/expo-updates@57.0.10.patch
|
||||||
expo@57.0.8: patches/expo@57.0.8.patch
|
expo@57.0.8: patches/expo@57.0.8.patch
|
||||||
'react-native-date-picker@5.0.13': patches/react-native-date-picker@5.0.13.patch
|
|
||||||
'react-native-dotenv@3.4.11': patches/react-native-dotenv@3.4.11.patch
|
'react-native-dotenv@3.4.11': patches/react-native-dotenv@3.4.11.patch
|
||||||
'react-native-drawer-layout@4.2.3': patches/react-native-drawer-layout@4.2.3.patch
|
'react-native-drawer-layout@4.2.3': patches/react-native-drawer-layout@4.2.3.patch
|
||||||
'react-native-keyboard-controller@1.21.9': patches/react-native-keyboard-controller@1.21.9.patch
|
'react-native-keyboard-controller@1.21.9': patches/react-native-keyboard-controller@1.21.9.patch
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import {useCallback, useImperativeHandle, useState} from 'react'
|
import {useCallback, useImperativeHandle, useState} from 'react'
|
||||||
import {Keyboard} from 'react-native'
|
import {Keyboard} from 'react-native'
|
||||||
import DatePicker from 'react-native-date-picker'
|
import {DatePickerDialog, Host} from '@expo/ui/jetpack-compose'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
import {type DateFieldProps} from '#/components/forms/DateField/types'
|
import {type DateFieldProps} from '#/components/forms/DateField/types'
|
||||||
import {toSimpleDateString} from '#/components/forms/DateField/utils'
|
import {
|
||||||
|
toLocalMidnight,
|
||||||
|
toSimpleDateString,
|
||||||
|
} from '#/components/forms/DateField/utils'
|
||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
import {DateFieldButton} from './index.shared'
|
import {DateFieldButton} from './index.shared'
|
||||||
|
|
||||||
@@ -20,20 +22,18 @@ export function DateField({
|
|||||||
placeholder,
|
placeholder,
|
||||||
label,
|
label,
|
||||||
isInvalid,
|
isInvalid,
|
||||||
testID,
|
|
||||||
accessibilityHint,
|
accessibilityHint,
|
||||||
maximumDate,
|
maximumDate,
|
||||||
minimumDate,
|
minimumDate,
|
||||||
}: DateFieldProps) {
|
}: DateFieldProps) {
|
||||||
const {i18n} = useLingui()
|
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The picker requires a valid date, so when value is empty we open at
|
* The picker requires a valid date, so when value is empty we open at
|
||||||
* maximumDate (if set) or today. Normalize through toSimpleDateString so a
|
* maximumDate (if set) or today. Normalize through toSimpleDateString so a
|
||||||
* date-only value is parsed as UTC midnight, consistent with the picker's
|
* date-only value is parsed as UTC midnight, which is what the Material 3
|
||||||
* timeZoneOffsetInMinutes={0} and the maximumDate below.
|
* picker expects for its selected date (it works in UTC day millis).
|
||||||
*/
|
*/
|
||||||
const initialDate =
|
const initialDate =
|
||||||
value === ''
|
value === ''
|
||||||
@@ -42,6 +42,19 @@ export function DateField({
|
|||||||
: new Date()
|
: new Date()
|
||||||
: new Date(toSimpleDateString(value))
|
: new Date(toSimpleDateString(value))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unlike the selection, the selectable range is resolved by the native side
|
||||||
|
* in the device time zone, so pass local midnight to keep the intended
|
||||||
|
* calendar day for devices west of UTC.
|
||||||
|
*/
|
||||||
|
const selectableDates =
|
||||||
|
minimumDate || maximumDate
|
||||||
|
? {
|
||||||
|
start: minimumDate ? toLocalMidnight(minimumDate) : undefined,
|
||||||
|
end: maximumDate ? toLocalMidnight(maximumDate) : undefined,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
const onChangeInternal = useCallback(
|
const onChangeInternal = useCallback(
|
||||||
(date: Date) => {
|
(date: Date) => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@@ -86,31 +99,15 @@ export function DateField({
|
|||||||
accessibilityHint={accessibilityHint}
|
accessibilityHint={accessibilityHint}
|
||||||
/>
|
/>
|
||||||
{open && (
|
{open && (
|
||||||
// Android implementation of DatePicker currently does not change default button colors according to theme and only takes hex values for buttonColor
|
<Host colorScheme={t.scheme}>
|
||||||
// Can remove the buttonColor setting if/when this PR is merged: https://github.com/henninghall/react-native-date-picker/pull/871
|
<DatePickerDialog
|
||||||
<DatePicker
|
initialDate={initialDate.toISOString()}
|
||||||
modal
|
selectableDates={selectableDates}
|
||||||
open
|
showVariantToggle={false}
|
||||||
timeZoneOffsetInMinutes={0}
|
onDateSelected={onChangeInternal}
|
||||||
theme={t.scheme}
|
onDismissRequest={onCancel}
|
||||||
buttonColor={t.name === 'light' ? '#000000' : '#ffffff'}
|
/>
|
||||||
date={initialDate}
|
</Host>
|
||||||
onConfirm={onChangeInternal}
|
|
||||||
onCancel={onCancel}
|
|
||||||
mode="date"
|
|
||||||
locale={i18n.locale}
|
|
||||||
is24hourSource="locale"
|
|
||||||
testID={`${testID}-datepicker`}
|
|
||||||
aria-label={label}
|
|
||||||
accessibilityLabel={label}
|
|
||||||
accessibilityHint={accessibilityHint}
|
|
||||||
maximumDate={
|
|
||||||
maximumDate ? new Date(toSimpleDateString(maximumDate)) : undefined
|
|
||||||
}
|
|
||||||
minimumDate={
|
|
||||||
minimumDate ? new Date(toSimpleDateString(minimumDate)) : undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {useCallback, useImperativeHandle, useState} from 'react'
|
import {useCallback, useImperativeHandle, useState} from 'react'
|
||||||
import {Keyboard, View} from 'react-native'
|
import {Keyboard, View} from 'react-native'
|
||||||
import DatePicker from 'react-native-date-picker'
|
import {DateTimePicker} from '@expo/ui/community/datetime-picker'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
@@ -16,6 +16,15 @@ import {DateFieldButton} from './index.shared'
|
|||||||
export * as utils from '#/components/forms/DateField/utils'
|
export * as utils from '#/components/forms/DateField/utils'
|
||||||
export const LabelText = TextField.LabelText
|
export const LabelText = TextField.LabelText
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SwiftUI only accepts identifiers from `Locale.availableIdentifiers`, which
|
||||||
|
* use underscores (`pt_BR`) rather than BCP 47 hyphens (`pt-BR`). Unknown
|
||||||
|
* identifiers make the picker fall back to the system locale.
|
||||||
|
*/
|
||||||
|
function toAppleLocale(locale: string): string {
|
||||||
|
return locale.replace(/-/g, '_')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date-only input. Accepts a string in the format YYYY-MM-DD, or a Date object.
|
* Date-only input. Accepts a string in the format YYYY-MM-DD, or a Date object.
|
||||||
* Date objects are converted to strings in the format YYYY-MM-DD.
|
* Date objects are converted to strings in the format YYYY-MM-DD.
|
||||||
@@ -78,6 +87,7 @@ export function DateField({
|
|||||||
}),
|
}),
|
||||||
[control, value, fallbackDate],
|
[control, value, fallbackDate],
|
||||||
)
|
)
|
||||||
|
console.log('locale===', i18n.locale, toAppleLocale(i18n.locale))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -101,17 +111,16 @@ export function DateField({
|
|||||||
<Dialog.ScrollableInner label={label}>
|
<Dialog.ScrollableInner label={label}>
|
||||||
<View style={a.gap_lg}>
|
<View style={a.gap_lg}>
|
||||||
<View style={[a.relative, a.w_full, a.align_center]}>
|
<View style={[a.relative, a.w_full, a.align_center]}>
|
||||||
<DatePicker
|
<DateTimePicker
|
||||||
timeZoneOffsetInMinutes={0}
|
style={a.w_full}
|
||||||
theme={t.scheme}
|
value={new Date(draft)}
|
||||||
date={new Date(draft)}
|
onValueChange={(_event, date) => onChangeInternal(date)}
|
||||||
onDateChange={onChangeInternal}
|
|
||||||
mode="date"
|
mode="date"
|
||||||
locale={i18n.locale}
|
display="spinner"
|
||||||
|
timeZoneName="UTC"
|
||||||
|
themeVariant={t.scheme}
|
||||||
|
locale={toAppleLocale(i18n.locale)}
|
||||||
testID={`${testID}-datepicker`}
|
testID={`${testID}-datepicker`}
|
||||||
aria-label={label}
|
|
||||||
accessibilityLabel={label}
|
|
||||||
accessibilityHint={accessibilityHint}
|
|
||||||
maximumDate={
|
maximumDate={
|
||||||
maximumDate
|
maximumDate
|
||||||
? new Date(toSimpleDateString(maximumDate))
|
? new Date(toSimpleDateString(maximumDate))
|
||||||
|
|||||||
@@ -3,3 +3,13 @@ export function toSimpleDateString(date: Date | string): string {
|
|||||||
const _date = typeof date === 'string' ? new Date(date) : date
|
const _date = typeof date === 'string' ? new Date(date) : date
|
||||||
return _date.toISOString().split('T')[0]
|
return _date.toISOString().split('T')[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Date at local midnight of the given calendar day. Use when a
|
||||||
|
* native API resolves timestamps in the device time zone rather than UTC, so
|
||||||
|
* the intended YYYY-MM-DD is preserved regardless of the device offset.
|
||||||
|
*/
|
||||||
|
export function toLocalMidnight(date: Date | string): Date {
|
||||||
|
const [year, month, day] = toSimpleDateString(date).split('-').map(Number)
|
||||||
|
return new Date(year, month - 1, day)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user