Migrate to expo/ui DatePicker

This commit is contained in:
Oleksii Bulenok
2026-08-18 14:42:19 +02:00
parent 48f96d6421
commit 227ab7c99a
6 changed files with 59 additions and 75 deletions
+1 -1
View File
@@ -121,6 +121,7 @@
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@expo/html-elements": "^0.12.5",
"@expo/ui": "~57.0.11",
"@expo/webpack-config": "^19.0.1",
"@floating-ui/dom": "^1.6.3",
"@floating-ui/react-dom": "^2.0.8",
@@ -230,7 +231,6 @@
"react-is": "19",
"react-keyed-flatten-children": "^5.0.0",
"react-native": "0.86.0",
"react-native-date-picker": "^5.0.13",
"react-native-device-attest": "^0.1.6",
"react-native-drawer-layout": "^4.2.3",
"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"
}
}
}
-1
View File
@@ -38,7 +38,6 @@ patchedDependencies:
'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@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-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
@@ -1,11 +1,13 @@
import {useCallback, useImperativeHandle, useState} from 'react'
import {Keyboard} from 'react-native'
import DatePicker from 'react-native-date-picker'
import {useLingui} from '@lingui/react'
import {DatePickerDialog, Host} from '@expo/ui/jetpack-compose'
import {useTheme} from '#/alf'
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 {DateFieldButton} from './index.shared'
@@ -20,20 +22,18 @@ export function DateField({
placeholder,
label,
isInvalid,
testID,
accessibilityHint,
maximumDate,
minimumDate,
}: DateFieldProps) {
const {i18n} = useLingui()
const t = useTheme()
const [open, setOpen] = useState(false)
/*
* The picker requires a valid date, so when value is empty we open at
* maximumDate (if set) or today. Normalize through toSimpleDateString so a
* date-only value is parsed as UTC midnight, consistent with the picker's
* timeZoneOffsetInMinutes={0} and the maximumDate below.
* date-only value is parsed as UTC midnight, which is what the Material 3
* picker expects for its selected date (it works in UTC day millis).
*/
const initialDate =
value === ''
@@ -42,6 +42,19 @@ export function DateField({
: new Date()
: 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(
(date: Date) => {
setOpen(false)
@@ -86,31 +99,15 @@ export function DateField({
accessibilityHint={accessibilityHint}
/>
{open && (
// Android implementation of DatePicker currently does not change default button colors according to theme and only takes hex values for buttonColor
// Can remove the buttonColor setting if/when this PR is merged: https://github.com/henninghall/react-native-date-picker/pull/871
<DatePicker
modal
open
timeZoneOffsetInMinutes={0}
theme={t.scheme}
buttonColor={t.name === 'light' ? '#000000' : '#ffffff'}
date={initialDate}
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
}
/>
<Host colorScheme={t.scheme}>
<DatePickerDialog
initialDate={initialDate.toISOString()}
selectableDates={selectableDates}
showVariantToggle={false}
onDateSelected={onChangeInternal}
onDismissRequest={onCancel}
/>
</Host>
)}
</>
)
+19 -10
View File
@@ -1,6 +1,6 @@
import {useCallback, useImperativeHandle, useState} from 'react'
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 {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
@@ -16,6 +16,15 @@ import {DateFieldButton} from './index.shared'
export * as utils from '#/components/forms/DateField/utils'
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 objects are converted to strings in the format YYYY-MM-DD.
@@ -78,6 +87,7 @@ export function DateField({
}),
[control, value, fallbackDate],
)
console.log('locale===', i18n.locale, toAppleLocale(i18n.locale))
return (
<>
@@ -101,17 +111,16 @@ export function DateField({
<Dialog.ScrollableInner label={label}>
<View style={a.gap_lg}>
<View style={[a.relative, a.w_full, a.align_center]}>
<DatePicker
timeZoneOffsetInMinutes={0}
theme={t.scheme}
date={new Date(draft)}
onDateChange={onChangeInternal}
<DateTimePicker
style={a.w_full}
value={new Date(draft)}
onValueChange={(_event, date) => onChangeInternal(date)}
mode="date"
locale={i18n.locale}
display="spinner"
timeZoneName="UTC"
themeVariant={t.scheme}
locale={toAppleLocale(i18n.locale)}
testID={`${testID}-datepicker`}
aria-label={label}
accessibilityLabel={label}
accessibilityHint={accessibilityHint}
maximumDate={
maximumDate
? new Date(toSimpleDateString(maximumDate))
+10
View File
@@ -3,3 +3,13 @@ export function toSimpleDateString(date: Date | string): string {
const _date = typeof date === 'string' ? new Date(date) : date
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)
}