import React, { useMemo, useState } from "react"; import DateTimePicker from "react-native-modal-datetime-picker"; import { Keyboard, Platform } from "react-native"; import type { FieldError, UseControllerProps } from "react-hook-form"; import type { XOR } from "ts-xor"; import type { Clearable } from "@jobber/hooks"; import type { InputFieldWrapperProps } from "../InputFieldWrapper"; import { FormField } from "../FormField"; import { InputPressable } from "../InputPressable"; import { useAtlantisI18n } from "../hooks/useAtlantisI18n"; import type { InputPressableProps } from "../InputPressable/InputPressable"; interface BaseInputDateProps extends Pick, Pick { /** * Defaulted to "always" so user can clear the dates whenever there's a value. */ readonly clearable?: Extract; /** * This label is shown to the user when there's no selected date. */ readonly emptyValueLabel?: string; /** * Maximum date the user can set. */ readonly maxDate?: Date; /** * Minimum date the user can set */ readonly minDate?: Date; /** * VoiceOver will read this string when a user selects the element */ readonly accessibilityLabel?: string; /** * Helps users understand what will happen when they perform an action */ readonly accessibilityHint?: string; } interface FormControlledInputDate extends BaseInputDateProps { /** * Adding a `name` would make this component "Form controlled" and must be * nested within a `
` component. * * Cannot be declared if `value` prop is used. */ readonly name: string; /** * Shows an error message below the field and highlights it red when the * value is invalid. Only applies when nested within a `` component. * * You can see **most** of the rules you can pass in * [React-hook-form Documentation](https://react-hook-form.com/api/useform/register#options). */ readonly validations?: UseControllerProps["rules"]; /** * The initial value for the input. */ readonly defaultValue?: Date; /** * The callback that fires whenever a date gets selected. */ readonly onChange?: (value?: Date | null) => void; } interface DevControlledInputDate extends BaseInputDateProps { /** * The value shown on the field. This gets automatically formatted to the * account's date format. * * Cannot be declared if `name` prop is used. */ readonly value: Date | string | undefined; /** * The callback that fires whenever a date gets selected. */ readonly onChange: (value?: Date) => void; } export type InputDateProps = XOR< FormControlledInputDate, DevControlledInputDate >; function formatInvalidState( error: FieldError | undefined, invalid: InputFieldWrapperProps["invalid"], ): boolean | string { if (invalid) return invalid; if (error && error.message) { return error.message; } return Boolean(error); } const display = Platform.OS === "ios" ? "inline" : "default"; /** * Allow users to select a date using the device date picker. */ export function InputDate(props: InputDateProps) { if (props.name) { return ( name={props.name} defaultValue={props.defaultValue} validations={props.validations} > {({ value, onChange, onBlur }, error) => ( { onChange(newValue); onBlur(); props.onChange?.(newValue); }} invalid={formatInvalidState(error, props.invalid)} /> )} ); } return ; } function InternalInputDate({ clearable = "always", disabled, emptyValueLabel, invalid, maxDate, minDate, placeholder, value, showMiniLabel = true, name, onChange, accessibilityLabel, accessibilityHint, }: InputDateProps) { const [showPicker, setShowPicker] = useState(false); const { t, locale, formatDate } = useAtlantisI18n(); const date = useMemo(() => { if (typeof value === "string") return new Date(value); return value; }, [value]); const formattedDate = useMemo(() => { if (date) { return formatDate(date); } return emptyValueLabel; }, [date, emptyValueLabel]); const canClearDate = formattedDate === emptyValueLabel ? "never" : clearable; const placeholderLabel = placeholder ?? t("date"); return ( <> ); function showDatePicker() { Keyboard.dismiss(); setShowPicker(true); } function handleConfirm(newVal: Date) { setShowPicker(false); onChange?.(newVal); } function handleCancel() { setShowPicker(false); // Ensure a change happens so we trigger the validation of one exists onChange?.(date); } function handleClear() { // Returns null only for Form controlled scenarios due to a limitation of react-hook-form that doesn't allow passing undefined to form values. if (name) { onChange?.(null); } else { onChange?.(undefined); } } }