import type { ReactElement, RefObject } from "react"; import type { ControllerProps, DefaultValues, FieldPath, FieldValues, Mode, UseFormReturn, } from "react-hook-form"; import type { IconNames } from "@jobber/design"; import type { KeyboardAwareScrollViewRef } from "react-native-keyboard-controller"; export type FormValues = T; export type FormErrors = FormNetworkErrors | FormUserErrors; export type FormBannerMessage = FormWarningMessage | FormNoticeMessage; export enum FormSubmitErrorType { NetworkError = "NetworkError", UserError = "UserErrors", } export enum FormBannerMessageType { WarningMessage = "WarningMessage", NoticeMessage = "NoticeMessage", } export interface FormBannerErrors { networkError?: string | undefined; bannerError?: { title: string; messages?: string[]; }; } interface FormNetworkErrors { errorType: FormSubmitErrorType.NetworkError; networkErrors: string; } export interface FormUserErrors { errorType: FormSubmitErrorType.UserError; userErrors: Record; } interface FormWarningMessage { messageType: FormBannerMessageType.WarningMessage; message: string; } interface FormNoticeMessage { messageType: FormBannerMessageType.NoticeMessage; message: string; } export type FormRef = | (UseFormReturn & { scrollViewRef?: RefObject; saveButtonHeight?: number; messageBannerHeight?: number; }) | undefined; export interface FormProps { /** * Content to be passed into the form */ children: React.ReactNode; /** * A callback function that is run before invoking onSubmit. Form submission is canceled if the promise resolves to false. */ onBeforeSubmit?: (data: FormValues) => Promise; /** * A callback function that handles the submission of form data. * If an error occurs during submission, it should not be caught and handled silently; the error must be thrown again. * If the submission is successful and no error is thrown, the `onSubmitSuccess` callback will be called. * If an error is thrown, the `onSubmitError` callback will be called. */ onSubmit: (data: FormValues) => Promise; /** * A callback function that handles any error that occurs during "onSubmit" */ onSubmitError: (error: FormErrors) => void; /** * A callback function that handles a successful form submission from "onSubmit" */ onSubmitSuccess: (data: SubmitResponseType) => void; /** * Network or user errors to be displayed as a banner at the top of the form */ bannerErrors?: FormBannerErrors; /** * Status messages to be displayed as a banner at the top of the form */ bannerMessages?: FormBannerMessage[]; /** * Loading when the initial form data is being fetched */ initialLoading?: boolean; /** * The initial values of the form inputs * This should be available as soon as initialLoading is set to false */ initialValues?: DefaultValues; /** * When the validation should happen. * Possible values are "onBlur", "onChange", "onSubmit", "onTouched", and "all". * The default value is "onTouched" */ mode?: Mode; /** * When the validation after submission should happen. * Possible values are "onBlur", "onChange", and "onSubmit". * The default value is "onChange" */ reValidateMode?: Exclude; /** * ref object to access react hook form methods and state */ formRef?: RefObject | undefined>; /** * Label to be displayed for the save button */ saveButtonLabel?: string; /** * @deprecated use `secondaryAction` instead. * Override default save button in the sticky section of the form with another element. */ renderStickySection?: ( onSubmit: () => void, label: string | undefined, isSubmitting: boolean, ) => ReactElement; /** * Adding a key will save a local copy of the form data that will be used to * recover values when the app is backgrounded or has crashed. */ localCacheKey?: string; /** * Forms field names that will not be considered for caching. * Useful for omitting sensitive data. */ localCacheExclude?: string[]; /** * A string or array of strings that can be used to identify the pre-filled * data on the form. This can be used to support local caching for forms that * prefill data without inadvertently applying the cache at the wrong time. * * For example this can be used to when an object is based on data from * another object (Quote being converted into a Job). This will allow * the user to retrieve data from the cache when trying to create the * same object (same Quote being converted into a Job) following an app crash. * * There is still only one copy of data for each `localCacheKey`. * If a user opens the same form the data will only be loaded if the `localCacheId` matches */ localCacheId?: string | string[]; /** * If true, the local cache will be removed when the user navigates away from * the dirty form even when offline. By default, cache is only removed on back when online. * Defaults to false. */ UNSAFE_allowDiscardLocalCacheWhenOffline?: boolean; /** * Secondary Action for ButtonGroup */ secondaryActions?: SecondaryActionProp[]; /** * A number that will pull down the save button when the position is sticky. * Useful when there's a footer or content below the form that is pulling * the button up. */ saveButtonOffset?: number; /** * Forces to render the sticky save button instead of the inline. * The sticky save button is default for iOS but not for Android due to * limitations. Use this prop with caution on Android. */ showStickySaveButton?: boolean; /** * Renders a footer below the save button. */ renderFooter?: React.ReactNode; /** * @internal Do not use this prop. It is a hack and will be removed. * TODO: JOB-147156 This is a HACK for multiline inputs on iOS scrolling issue. * Disables the keyboard aware scroll view. * This is needed to fix issues with multiline inputs on iOS. DO NOT */ disableKeyboardAwareScroll?: boolean; } export type InternalFormProps = Omit< FormProps, "initialLoading" >; export type ValidationRulesByFieldPath = { [p in FieldPath]: ControllerProps["rules"]; }; export interface FormSaveButtonProps { /** * Press handler */ primaryAction: () => Promise | void; /** * Primary Button is loading */ loading: boolean; /** * Label for the save button */ label?: string; /** * Props and information regarding the secondary Action button(s) */ secondaryActions?: SecondaryActionProp[]; /** * Set whether secondary Button is loading */ setSecondaryActionLoading?: (bool: boolean) => void; /** * Callback that is called when the secondary actions bottom sheet is opened. */ onOpenBottomSheet?: () => void; /** * Callback that is called when the secondary actions bottom sheet is closed. */ onCloseBottomSheet?: () => void; } interface SecondaryActionOnPress { onBeforeSubmit?: () => Promise; onSubmit: (formSubmit: FormSaveButtonProps["primaryAction"]) => Promise; onSubmitSuccess?: () => void; onSubmitError?: (error: FormErrors) => void; resetFormOnSubmit?: boolean; } export interface SecondaryActionProp { label: string; icon?: IconNames | undefined; handleAction: SecondaryActionOnPress; destructive?: boolean; }