Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix scroll bar on IOS #49700

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 26 additions & 53 deletions src/components/Form/FormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@ import lodashIsEqual from 'lodash/isEqual';
import type {ForwardedRef, MutableRefObject, ReactNode, RefAttributes} from 'react';
import React, {createRef, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import type {NativeSyntheticEvent, StyleProp, TextInputSubmitEditingEventData, ViewStyle} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import * as ValidationUtils from '@libs/ValidationUtils';
import Visibility from '@libs/Visibility';
import * as FormActions from '@userActions/FormActions';
import CONST from '@src/CONST';
import type {OnyxFormKey} from '@src/ONYXKEYS';
import type {OnyxFormDraftKey, OnyxFormKey} from '@src/ONYXKEYS';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Form} from '@src/types/form';
import type {Network} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {RegisterInput} from './FormContext';
import FormContext from './FormContext';
Expand Down Expand Up @@ -41,46 +39,34 @@ function getInitialValueByType(valueType?: ValueTypeKey): InitialDefaultValue {
}
}

type FormProviderOnyxProps = {
/** Contains the form state that must be accessed outside the component */
formState: OnyxEntry<Form>;
type FormProviderProps<TFormID extends OnyxFormKey = OnyxFormKey> = FormProps<TFormID> & {
/** Children to render. */
children: ((props: {inputValues: FormOnyxValues<TFormID>}) => ReactNode) | ReactNode;

/** Contains draft values for each input in the form */
draftValues: OnyxEntry<Form>;
/** Callback to validate the form */
validate?: (values: FormOnyxValues<TFormID>) => FormInputErrors<TFormID>;

/** Information about the network */
network: OnyxEntry<Network>;
};

type FormProviderProps<TFormID extends OnyxFormKey = OnyxFormKey> = FormProviderOnyxProps &
FormProps<TFormID> & {
/** Children to render. */
children: ((props: {inputValues: FormOnyxValues<TFormID>}) => ReactNode) | ReactNode;

/** Callback to validate the form */
validate?: (values: FormOnyxValues<TFormID>) => FormInputErrors<TFormID>;
/** Should validate function be called when input loose focus */
shouldValidateOnBlur?: boolean;

/** Should validate function be called when input loose focus */
shouldValidateOnBlur?: boolean;
/** Should validate function be called when the value of the input is changed */
shouldValidateOnChange?: boolean;

/** Should validate function be called when the value of the input is changed */
shouldValidateOnChange?: boolean;
/** Whether to remove invisible characters from strings before validation and submission */
shouldTrimValues?: boolean;

/** Whether to remove invisible characters from strings before validation and submission */
shouldTrimValues?: boolean;
/** Styles that will be applied to the submit button only */
submitButtonStyles?: StyleProp<ViewStyle>;

/** Styles that will be applied to the submit button only */
submitButtonStyles?: StyleProp<ViewStyle>;
/** Whether to apply flex to the submit button */
submitFlexEnabled?: boolean;

/** Whether to apply flex to the submit button */
submitFlexEnabled?: boolean;
/** Whether button is disabled */
isSubmitDisabled?: boolean;

/** Whether button is disabled */
isSubmitDisabled?: boolean;

/** Whether HTML is allowed in form inputs */
allowHTML?: boolean;
};
/** Whether HTML is allowed in form inputs */
allowHTML?: boolean;
};

function FormProvider(
{
Expand All @@ -89,17 +75,17 @@ function FormProvider(
shouldValidateOnBlur = true,
shouldValidateOnChange = true,
children,
formState,
network,
enabledWhenOffline = false,
draftValues,
onSubmit,
shouldTrimValues = true,
allowHTML = false,
...rest
}: FormProviderProps,
forwardedRef: ForwardedRef<FormRef>,
) {
const [network] = useOnyx(ONYXKEYS.NETWORK);
const [formState] = useOnyx<OnyxFormKey, Form>(`${formID}`);
const [draftValues] = useOnyx<OnyxFormDraftKey, Form>(`${formID}Draft`);
const {preferredLocale, translate} = useLocalize();
const inputRefs = useRef<InputRefs>({});
const touchedInputs = useRef<Record<string, boolean>>({});
Expand Down Expand Up @@ -404,19 +390,6 @@ function FormProvider(

FormProvider.displayName = 'Form';

export default withOnyx<FormProviderProps, FormProviderOnyxProps>({
network: {
key: ONYXKEYS.NETWORK,
},
// withOnyx typings are not able to handle such generic cases like this one, since it's a generic component we need to cast the keys to any
formState: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any
key: ({formID}) => formID as any,
},
draftValues: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any
key: (props) => `${props.formID}Draft` as any,
},
})(forwardRef(FormProvider)) as <TFormID extends OnyxFormKey>(props: Omit<FormProviderProps<TFormID> & RefAttributes<FormRef>, keyof FormProviderOnyxProps>) => ReactNode;
export default forwardRef(FormProvider) as <TFormID extends OnyxFormKey>(props: FormProviderProps<TFormID> & RefAttributes<FormRef>) => ReactNode;

export type {FormProviderProps};
20 changes: 17 additions & 3 deletions src/stories/Form.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {Meta, StoryFn} from '@storybook/react';
import React, {useState} from 'react';
import type {ComponentType} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import AddressSearch from '@components/AddressSearch';
import CheckboxWithLabel from '@components/CheckboxWithLabel';
import DatePicker from '@components/DatePicker';
Expand All @@ -18,8 +19,10 @@ import * as FormActions from '@userActions/FormActions';
import CONST from '@src/CONST';
import type {OnyxFormValuesMapping} from '@src/ONYXKEYS';
import {defaultStyles} from '@src/styles';
import type {Form} from '@src/types/form';
import type {Network} from '@src/types/onyx';

type FormStory = StoryFn<FormProviderProps>;
type FormStory = StoryFn<FormProviderProps & FormProviderOnyxProps>;

type StorybookFormValues = {
routingNumber?: string;
Expand All @@ -32,6 +35,17 @@ type StorybookFormValues = {
checkbox?: boolean;
};

type FormProviderOnyxProps = {
/** Contains the form state that must be accessed outside the component */
formState: OnyxEntry<Form>;

/** Contains draft values for each input in the form */
draftValues: OnyxEntry<Form>;

/** Information about the network */
network: OnyxEntry<Network>;
};

type StorybookFormErrors = Partial<Record<keyof StorybookFormValues, string>>;

const STORYBOOK_FORM_ID = 'TestForm' as keyof OnyxFormValuesMapping;
Expand All @@ -50,7 +64,7 @@ const story: Meta<typeof FormProvider> = {
},
};

function Template(props: FormProviderProps) {
function Template(props: FormProviderProps & FormProviderOnyxProps) {
// Form consumes data from Onyx, so we initialize Onyx with the necessary data here
NetworkConnection.setOfflineStatus(false);
FormActions.setIsLoading(props.formID, !!props.formState?.isLoading);
Expand Down Expand Up @@ -162,7 +176,7 @@ function Template(props: FormProviderProps) {
/**
* Story to exhibit the native event handlers for TextInput in the Form Component
*/
function WithNativeEventHandler(props: FormProviderProps) {
function WithNativeEventHandler(props: FormProviderProps & FormProviderOnyxProps) {
const [log, setLog] = useState('');

// Form consumes data from Onyx, so we initialize Onyx with the necessary data here
Expand Down
Loading