diff options
Diffstat (limited to 'src/components/forms/DateField')
-rw-r--r-- | src/components/forms/DateField/index.android.tsx | 108 | ||||
-rw-r--r-- | src/components/forms/DateField/index.tsx | 56 | ||||
-rw-r--r-- | src/components/forms/DateField/index.web.tsx | 64 | ||||
-rw-r--r-- | src/components/forms/DateField/types.ts | 7 | ||||
-rw-r--r-- | src/components/forms/DateField/utils.ts | 16 |
5 files changed, 251 insertions, 0 deletions
diff --git a/src/components/forms/DateField/index.android.tsx b/src/components/forms/DateField/index.android.tsx new file mode 100644 index 000000000..83fa285f5 --- /dev/null +++ b/src/components/forms/DateField/index.android.tsx @@ -0,0 +1,108 @@ +import React from 'react' +import {View, Pressable} from 'react-native' +import DateTimePicker, { + BaseProps as DateTimePickerProps, +} from '@react-native-community/datetimepicker' + +import {useTheme, atoms} from '#/alf' +import {Text} from '#/components/Typography' +import {useInteractionState} from '#/components/hooks/useInteractionState' +import * as TextField from '#/components/forms/TextField' +import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays' + +import {DateFieldProps} from '#/components/forms/DateField/types' +import { + localizeDate, + toSimpleDateString, +} from '#/components/forms/DateField/utils' + +export * as utils from '#/components/forms/DateField/utils' +export const Label = TextField.Label + +export function DateField({ + value, + onChangeDate, + label, + isInvalid, + testID, +}: DateFieldProps) { + const t = useTheme() + const [open, setOpen] = React.useState(false) + const { + state: pressed, + onIn: onPressIn, + onOut: onPressOut, + } = useInteractionState() + const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() + + const {chromeFocus, chromeError, chromeErrorHover} = + TextField.useSharedInputStyles() + + const onChangeInternal = React.useCallback< + Required<DateTimePickerProps>['onChange'] + >( + (_event, date) => { + setOpen(false) + + if (date) { + const formatted = toSimpleDateString(date) + onChangeDate(formatted) + } + }, + [onChangeDate, setOpen], + ) + + return ( + <View style={[atoms.relative, atoms.w_full]}> + <Pressable + aria-label={label} + accessibilityLabel={label} + accessibilityHint={undefined} + onPress={() => setOpen(true)} + onPressIn={onPressIn} + onPressOut={onPressOut} + onFocus={onFocus} + onBlur={onBlur} + style={[ + { + paddingTop: 16, + paddingBottom: 16, + borderColor: 'transparent', + borderWidth: 2, + }, + atoms.flex_row, + atoms.flex_1, + atoms.w_full, + atoms.px_lg, + atoms.rounded_sm, + t.atoms.bg_contrast_50, + focused || pressed ? chromeFocus : {}, + isInvalid ? chromeError : {}, + isInvalid && (focused || pressed) ? chromeErrorHover : {}, + ]}> + <TextField.Icon icon={CalendarDays} /> + + <Text + style={[atoms.text_md, atoms.pl_xs, t.atoms.text, {paddingTop: 3}]}> + {localizeDate(value)} + </Text> + </Pressable> + + {open && ( + <DateTimePicker + aria-label={label} + accessibilityLabel={label} + accessibilityHint={undefined} + testID={`${testID}-datepicker`} + mode="date" + timeZoneName={'Etc/UTC'} + display="spinner" + // @ts-ignore applies in iOS only -prf + themeVariant={t.name === 'dark' ? 'dark' : 'light'} + value={new Date(value)} + onChange={onChangeInternal} + /> + )} + </View> + ) +} diff --git a/src/components/forms/DateField/index.tsx b/src/components/forms/DateField/index.tsx new file mode 100644 index 000000000..c359a9d46 --- /dev/null +++ b/src/components/forms/DateField/index.tsx @@ -0,0 +1,56 @@ +import React from 'react' +import {View} from 'react-native' +import DateTimePicker, { + DateTimePickerEvent, +} from '@react-native-community/datetimepicker' + +import {useTheme, atoms} from '#/alf' +import * as TextField from '#/components/forms/TextField' +import {toSimpleDateString} from '#/components/forms/DateField/utils' +import {DateFieldProps} from '#/components/forms/DateField/types' + +export * as utils from '#/components/forms/DateField/utils' +export const Label = TextField.Label + +/** + * Date-only input. Accepts a date in the format YYYY-MM-DD, and reports date + * changes in the same format. + * + * For dates of unknown format, convert with the + * `utils.toSimpleDateString(Date)` export of this file. + */ +export function DateField({ + value, + onChangeDate, + testID, + label, +}: DateFieldProps) { + const t = useTheme() + + const onChangeInternal = React.useCallback( + (event: DateTimePickerEvent, date: Date | undefined) => { + if (date) { + const formatted = toSimpleDateString(date) + onChangeDate(formatted) + } + }, + [onChangeDate], + ) + + return ( + <View style={[atoms.relative, atoms.w_full]}> + <DateTimePicker + aria-label={label} + accessibilityLabel={label} + accessibilityHint={undefined} + testID={`${testID}-datepicker`} + mode="date" + timeZoneName={'Etc/UTC'} + display="spinner" + themeVariant={t.name === 'dark' ? 'dark' : 'light'} + value={new Date(value)} + onChange={onChangeInternal} + /> + </View> + ) +} diff --git a/src/components/forms/DateField/index.web.tsx b/src/components/forms/DateField/index.web.tsx new file mode 100644 index 000000000..32f38a5d1 --- /dev/null +++ b/src/components/forms/DateField/index.web.tsx @@ -0,0 +1,64 @@ +import React from 'react' +import {TextInput, TextInputProps, StyleSheet} from 'react-native' +// @ts-ignore +import {unstable_createElement} from 'react-native-web' + +import * as TextField from '#/components/forms/TextField' +import {toSimpleDateString} from '#/components/forms/DateField/utils' +import {DateFieldProps} from '#/components/forms/DateField/types' + +export * as utils from '#/components/forms/DateField/utils' +export const Label = TextField.Label + +const InputBase = React.forwardRef<HTMLInputElement, TextInputProps>( + ({style, ...props}, ref) => { + return unstable_createElement('input', { + ...props, + ref, + type: 'date', + style: [ + StyleSheet.flatten(style), + { + background: 'transparent', + border: 0, + }, + ], + }) + }, +) + +InputBase.displayName = 'InputBase' + +const Input = TextField.createInput(InputBase as unknown as typeof TextInput) + +export function DateField({ + value, + onChangeDate, + label, + isInvalid, + testID, +}: DateFieldProps) { + const handleOnChange = React.useCallback( + (e: any) => { + const date = e.target.valueAsDate || e.target.value + + if (date) { + const formatted = toSimpleDateString(date) + onChangeDate(formatted) + } + }, + [onChangeDate], + ) + + return ( + <TextField.Root isInvalid={isInvalid}> + <Input + value={value} + label={label} + onChange={handleOnChange} + onChangeText={() => {}} + testID={testID} + /> + </TextField.Root> + ) +} diff --git a/src/components/forms/DateField/types.ts b/src/components/forms/DateField/types.ts new file mode 100644 index 000000000..129f5672d --- /dev/null +++ b/src/components/forms/DateField/types.ts @@ -0,0 +1,7 @@ +export type DateFieldProps = { + value: string + onChangeDate: (date: string) => void + label: string + isInvalid?: boolean + testID?: string +} diff --git a/src/components/forms/DateField/utils.ts b/src/components/forms/DateField/utils.ts new file mode 100644 index 000000000..c787272fe --- /dev/null +++ b/src/components/forms/DateField/utils.ts @@ -0,0 +1,16 @@ +import {getLocales} from 'expo-localization' + +const LOCALE = getLocales()[0] + +// we need the date in the form yyyy-MM-dd to pass to the input +export function toSimpleDateString(date: Date | string): string { + const _date = typeof date === 'string' ? new Date(date) : date + return _date.toISOString().split('T')[0] +} + +export function localizeDate(date: Date | string): string { + const _date = typeof date === 'string' ? new Date(date) : date + return new Intl.DateTimeFormat(LOCALE.languageTag, { + timeZone: 'UTC', + }).format(_date) +} |