diff options
author | Samuel Newman <mozzius@protonmail.com> | 2025-02-03 12:27:58 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-03 20:27:58 +0000 |
commit | 23e62b18de9537b50c8b1df2b4744adc030501d0 (patch) | |
tree | 557037a964f2a8f7a3583ae84fc3b67b498c1ed2 | |
parent | 25991af7224cd76a8722f0579c00b73a211a84cc (diff) | |
download | voidsky-23e62b18de9537b50c8b1df2b4744adc030501d0.tar.zst |
Date input improvements (#7639)
* add max date, use modern field for birthday input * rm legacy date input * handle simplifying to simpleDateString internally * update jsdoc
-rw-r--r-- | src/components/dialogs/BirthDateSettings.tsx | 16 | ||||
-rw-r--r-- | src/components/forms/DateField/index.android.tsx | 4 | ||||
-rw-r--r-- | src/components/forms/DateField/index.shared.tsx | 2 | ||||
-rw-r--r-- | src/components/forms/DateField/index.tsx | 24 | ||||
-rw-r--r-- | src/components/forms/DateField/index.web.tsx | 7 | ||||
-rw-r--r-- | src/components/forms/DateField/types.ts | 3 | ||||
-rw-r--r-- | src/screens/Signup/StepInfo/index.tsx | 3 | ||||
-rw-r--r-- | src/view/com/util/forms/DateInput.tsx | 105 | ||||
-rw-r--r-- | src/view/com/util/forms/DateInput.web.tsx | 76 |
9 files changed, 37 insertions, 203 deletions
diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx index 8f47d05b0..9fbf378ac 100644 --- a/src/components/dialogs/BirthDateSettings.tsx +++ b/src/components/dialogs/BirthDateSettings.tsx @@ -12,12 +12,12 @@ import { usePreferencesSetBirthDateMutation, } from '#/state/queries/preferences' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' -import {DateInput} from '#/view/com/util/forms/DateInput' import {atoms as a, useTheme} from '#/alf' import * as Dialog from '#/components/Dialog' +import {DateField} from '#/components/forms/DateField' import {Loader} from '#/components/Loader' +import {Text} from '#/components/Typography' import {Button, ButtonIcon, ButtonText} from '../Button' -import {Text} from '../Typography' export function BirthDateSettingsDialog({ control, @@ -95,17 +95,13 @@ function BirthdayInner({ return ( <View style={a.gap_lg} testID="birthDateSettingsDialog"> <View style={isIOS && [a.w_full, a.align_center]}> - <DateInput - handleAsUTC + <DateField testID="birthdayInput" value={date} - onChange={setDate} - buttonType="default-light" - buttonStyle={[a.rounded_sm]} - buttonLabelType="lg" - accessibilityLabel={_(msg`Birthday`)} + onChangeDate={newDate => setDate(new Date(newDate))} + label={_(msg`Birthday`)} accessibilityHint={_(msg`Enter your birth date`)} - accessibilityLabelledBy="birthDate" + maximumDate={new Date()} /> </View> diff --git a/src/components/forms/DateField/index.android.tsx b/src/components/forms/DateField/index.android.tsx index 58f4d4f89..a6b6993dc 100644 --- a/src/components/forms/DateField/index.android.tsx +++ b/src/components/forms/DateField/index.android.tsx @@ -17,6 +17,7 @@ export function DateField({ isInvalid, testID, accessibilityHint, + maximumDate, }: DateFieldProps) { const t = useTheme() const [open, setOpen] = React.useState(false) @@ -67,6 +68,9 @@ export function DateField({ aria-label={label} accessibilityLabel={label} accessibilityHint={accessibilityHint} + maximumDate={ + maximumDate ? new Date(toSimpleDateString(maximumDate)) : undefined + } /> )} </> diff --git a/src/components/forms/DateField/index.shared.tsx b/src/components/forms/DateField/index.shared.tsx index 7438f5622..7b03ba901 100644 --- a/src/components/forms/DateField/index.shared.tsx +++ b/src/components/forms/DateField/index.shared.tsx @@ -19,7 +19,7 @@ export function DateFieldButton({ accessibilityHint, }: { label: string - value: string + value: string | Date onPress: () => void isInvalid?: boolean accessibilityHint?: string diff --git a/src/components/forms/DateField/index.tsx b/src/components/forms/DateField/index.tsx index 1c78d2abb..eca4d5cbd 100644 --- a/src/components/forms/DateField/index.tsx +++ b/src/components/forms/DateField/index.tsx @@ -16,10 +16,11 @@ export * as utils from '#/components/forms/DateField/utils' export const LabelText = TextField.LabelText /** - * Date-only input. Accepts a date in the format YYYY-MM-DD, and reports date - * changes in the same format. + * 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. + * Returns a string in the format YYYY-MM-DD. * - * For dates of unknown format, convert with the + * To generate a string in the format YYYY-MM-DD from a Date object, use the * `utils.toSimpleDateString(Date)` export of this file. */ export function DateField({ @@ -29,6 +30,7 @@ export function DateField({ label, isInvalid, accessibilityHint, + maximumDate, }: DateFieldProps) { const {_} = useLingui() const t = useTheme() @@ -56,21 +58,29 @@ export function DateField({ isInvalid={isInvalid} accessibilityHint={accessibilityHint} /> - <Dialog.Outer control={control} testID={testID}> + <Dialog.Outer + control={control} + testID={testID} + nativeOptions={{preventExpansion: true}}> <Dialog.Handle /> - <Dialog.Inner label={label}> + <Dialog.ScrollableInner label={label}> <View style={a.gap_lg}> <View style={[a.relative, a.w_full, a.align_center]}> <DatePicker timeZoneOffsetInMinutes={0} theme={t.name === 'light' ? 'light' : 'dark'} - date={new Date(value)} + date={new Date(toSimpleDateString(value))} onDateChange={onChangeInternal} mode="date" testID={`${testID}-datepicker`} aria-label={label} accessibilityLabel={label} accessibilityHint={accessibilityHint} + maximumDate={ + maximumDate + ? new Date(toSimpleDateString(maximumDate)) + : undefined + } /> </View> <Button @@ -84,7 +94,7 @@ export function DateField({ </ButtonText> </Button> </View> - </Dialog.Inner> + </Dialog.ScrollableInner> </Dialog.Outer> </> ) diff --git a/src/components/forms/DateField/index.web.tsx b/src/components/forms/DateField/index.web.tsx index b764620e3..057ea1673 100644 --- a/src/components/forms/DateField/index.web.tsx +++ b/src/components/forms/DateField/index.web.tsx @@ -1,6 +1,6 @@ import React from 'react' import {StyleSheet, TextInput, TextInputProps} from 'react-native' -// @ts-ignore +// @ts-expect-error untyped import {unstable_createElement} from 'react-native-web' import {DateFieldProps} from '#/components/forms/DateField/types' @@ -39,6 +39,7 @@ export function DateField({ isInvalid, testID, accessibilityHint, + maximumDate, }: DateFieldProps) { const handleOnChange = React.useCallback( (e: any) => { @@ -56,12 +57,14 @@ export function DateField({ <TextField.Root isInvalid={isInvalid}> <TextField.Icon icon={CalendarDays} /> <Input - value={value} + value={toSimpleDateString(value)} label={label} onChange={handleOnChange} onChangeText={() => {}} testID={testID} accessibilityHint={accessibilityHint} + // @ts-expect-error not typed as <input type="date"> even though it is one + max={maximumDate ? toSimpleDateString(maximumDate) : undefined} /> </TextField.Root> ) diff --git a/src/components/forms/DateField/types.ts b/src/components/forms/DateField/types.ts index 5400cf903..1784b884f 100644 --- a/src/components/forms/DateField/types.ts +++ b/src/components/forms/DateField/types.ts @@ -1,8 +1,9 @@ export type DateFieldProps = { - value: string + value: string | Date onChangeDate: (date: string) => void label: string isInvalid?: boolean testID?: string accessibilityHint?: string + maximumDate?: string | Date } diff --git a/src/screens/Signup/StepInfo/index.tsx b/src/screens/Signup/StepInfo/index.tsx index fa0c7c8cf..4ad899864 100644 --- a/src/screens/Signup/StepInfo/index.tsx +++ b/src/screens/Signup/StepInfo/index.tsx @@ -206,7 +206,7 @@ export function StepInfo({ </DateField.LabelText> <DateField.DateField testID="date" - value={DateField.utils.toSimpleDateString(state.dateOfBirth)} + value={state.dateOfBirth} onChangeDate={date => { dispatch({ type: 'setDateOfBirth', @@ -215,6 +215,7 @@ export function StepInfo({ }} label={_(msg`Date of birth`)} accessibilityHint={_(msg`Select your date of birth`)} + maximumDate={new Date()} /> </View> <Policies diff --git a/src/view/com/util/forms/DateInput.tsx b/src/view/com/util/forms/DateInput.tsx deleted file mode 100644 index 594bb48f6..000000000 --- a/src/view/com/util/forms/DateInput.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import {useCallback, useState} from 'react' -import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' -import DatePicker from 'react-native-date-picker' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {useLingui} from '@lingui/react' - -import {usePalette} from '#/lib/hooks/usePalette' -import {TypographyVariant} from '#/lib/ThemeContext' -import {useTheme} from '#/lib/ThemeContext' -import {isAndroid, isIOS} from '#/platform/detection' -import {Text} from '../text/Text' -import {Button, ButtonType} from './Button' - -interface Props { - testID?: string - value: Date - onChange: (date: Date) => void - buttonType?: ButtonType - buttonStyle?: StyleProp<ViewStyle> - buttonLabelType?: TypographyVariant - buttonLabelStyle?: StyleProp<TextStyle> - accessibilityLabel: string - accessibilityHint: string - accessibilityLabelledBy?: string - handleAsUTC?: boolean -} - -export function DateInput(props: Props) { - const {i18n} = useLingui() - const [show, setShow] = useState(false) - const theme = useTheme() - const pal = usePalette('default') - - const onChangeInternal = useCallback( - (date: Date) => { - setShow(false) - props.onChange(date) - }, - [setShow, props], - ) - - const onPress = useCallback(() => { - setShow(true) - }, [setShow]) - - const onCancel = useCallback(() => { - setShow(false) - }, []) - - return ( - <View> - {isAndroid && ( - <Button - type={props.buttonType} - style={props.buttonStyle} - onPress={onPress} - accessibilityLabel={props.accessibilityLabel} - accessibilityHint={props.accessibilityHint} - accessibilityLabelledBy={props.accessibilityLabelledBy}> - <View style={styles.button}> - <FontAwesomeIcon - icon={['far', 'calendar']} - style={pal.textLight as FontAwesomeIconStyle} - /> - <Text - type={props.buttonLabelType} - style={[pal.text, props.buttonLabelStyle]}> - {i18n.date(props.value, { - timeZone: props.handleAsUTC ? 'UTC' : undefined, - })} - </Text> - </View> - </Button> - )} - {(isIOS || show) && ( - <DatePicker - timeZoneOffsetInMinutes={0} - modal={isAndroid} - open={isAndroid} - theme={theme.colorScheme} - date={props.value} - onDateChange={onChangeInternal} - onConfirm={onChangeInternal} - onCancel={onCancel} - mode="date" - testID={props.testID ? `${props.testID}-datepicker` : undefined} - accessibilityLabel={props.accessibilityLabel} - accessibilityHint={props.accessibilityHint} - accessibilityLabelledBy={props.accessibilityLabelledBy} - /> - )} - </View> - ) -} - -const styles = StyleSheet.create({ - button: { - flexDirection: 'row', - alignItems: 'center', - gap: 10, - }, -}) diff --git a/src/view/com/util/forms/DateInput.web.tsx b/src/view/com/util/forms/DateInput.web.tsx deleted file mode 100644 index 988d8aee6..000000000 --- a/src/view/com/util/forms/DateInput.web.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import {useCallback, useState} from 'react' -import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' -// @ts-ignore types not available -prf -import {unstable_createElement} from 'react-native-web' - -import {usePalette} from '#/lib/hooks/usePalette' - -interface Props { - testID?: string - value: Date - onChange: (date: Date) => void - buttonType?: string - buttonStyle?: StyleProp<ViewStyle> - buttonLabelType?: string - buttonLabelStyle?: StyleProp<TextStyle> - accessibilityLabel: string - accessibilityHint: string - accessibilityLabelledBy?: string -} - -export function DateInput(props: Props) { - const pal = usePalette('default') - const [value, setValue] = useState(toDateInputValue(props.value)) - - const onChangeInternal = useCallback( - (v: Date) => { - if (!v) { - return - } - setValue(toDateInputValue(v)) - props.onChange(v) - }, - [setValue, props], - ) - - return ( - <View style={[pal.borderDark, styles.container]}> - {unstable_createElement('input', { - type: 'date', - testID: props.testID, - value, - onChange: (e: any) => onChangeInternal(e.currentTarget.valueAsDate), - style: [pal.text, pal.view, pal.border, styles.textInput], - placeholderTextColor: pal.colors.textLight, - accessibilityLabel: props.accessibilityLabel, - accessibilityHint: props.accessibilityHint, - accessibilityLabelledBy: props.accessibilityLabelledBy, - })} - </View> - ) -} - -// we need the date in the form yyyy-MM-dd to pass to the input -function toDateInputValue(d: Date): string { - return d.toISOString().split('T')[0] -} - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - paddingHorizontal: 4, - borderWidth: 1, - borderRadius: 10, - }, - textInput: { - flex: 1, - width: '100%', - paddingVertical: 10, - paddingHorizontal: 10, - fontSize: 17, - letterSpacing: 0.25, - fontWeight: '400', - borderRadius: 10, - borderWidth: 0, - }, -}) |