diff --git a/src/components/CollapsibleHeaderOnKeyboard/index.native.tsx b/src/components/CollapsibleHeaderOnKeyboard/index.native.tsx index 7c90228a642d..9a02f17d6aab 100644 --- a/src/components/CollapsibleHeaderOnKeyboard/index.native.tsx +++ b/src/components/CollapsibleHeaderOnKeyboard/index.native.tsx @@ -17,6 +17,7 @@ const RESTORE_DURATION = 300; // Assumed vertical space for the focused input field — used to reserve space above the keyboard. const VERTICAL_SPACE_FOR_FOCUSED_INPUT = 120; const KEYBOARD_OPENING_PROGRESS_THRESHOLDS = [0.5, 0.7, 0.8, 0.85, 0.9, 0.95, 0.99]; +const MIN_HEADER_HEIGHT_ON_COLLAPSE = 8; function isKeyboardOpeningAtGivenProgress(keyboardProgress: number, prevKeyboardProgress: number, requiredProgress: number[]): boolean { 'worklet'; @@ -32,7 +33,7 @@ function isKeyboardOpeningAtGivenProgress(keyboardProgress: number, prevKeyboard * Intended for landscape mode on phones where the keyboard + header can leave no room for inputs. * Uses height animation (not translateY) so the freed space is reclaimed by the layout below. */ -function CollapsibleHeaderOnKeyboard({children, collapsibleHeaderOffset = 0}: CollapsibleHeaderOnKeyboardProps) { +function CollapsibleHeaderOnKeyboard({children, collapsibleHeaderOffset = 0, alwaysCollapseHeaderOnKeyboard = false}: CollapsibleHeaderOnKeyboardProps) { const isFocused = useIsFocused(); const prevIsFocused = usePrevious(isFocused); // JS ref guards against re-measurement when the Reanimated.View fires onLayout with height=0 @@ -157,7 +158,9 @@ function CollapsibleHeaderOnKeyboard({children, collapsibleHeaderOffset = 0}: Co // Target header height: give the input exactly the space it needs above the keyboard, // the header gets what remains. Clamped to [0, naturalHeight]. const keyboardTop = windowHeightValue + keyboardHeight; - const targetHeight = Math.max(0, keyboardTop - VERTICAL_SPACE_FOR_FOCUSED_INPUT - collapsibleHeaderOffsetSV.get()); + const targetHeight = alwaysCollapseHeaderOnKeyboard + ? MIN_HEADER_HEIGHT_ON_COLLAPSE + : Math.max(MIN_HEADER_HEIGHT_ON_COLLAPSE, keyboardTop - VERTICAL_SPACE_FOR_FOCUSED_INPUT - collapsibleHeaderOffsetSV.get()); const naturalHeightValue = naturalHeight.get(); if (targetHeight >= naturalHeightValue) { diff --git a/src/components/CollapsibleHeaderOnKeyboard/types.ts b/src/components/CollapsibleHeaderOnKeyboard/types.ts index d5281b0d2d6e..7734ac5eb062 100644 --- a/src/components/CollapsibleHeaderOnKeyboard/types.ts +++ b/src/components/CollapsibleHeaderOnKeyboard/types.ts @@ -4,6 +4,12 @@ type CollapsibleHeaderOnKeyboardProps = { * component, keyboard, and focused input — e.g. a tab bar below the list. * The collapse target is reduced by this amount so those elements are not counted twice. */ collapsibleHeaderOffset?: number; + + /** + * If true, the header will always collapse on keyboard open, + * regardless if there is enough space for the input above the keyboard. + */ + alwaysCollapseHeaderOnKeyboard?: boolean; }; // eslint-disable-next-line import/prefer-default-export diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index a5aaecde1750..9a0c6e35a1f5 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -126,6 +126,9 @@ type FormProviderProps = FormProps; + + /** Whether to display the submit button and footer in one row in landscape mode */ + shouldDisplaySubmitButtonAndFooterInOneRowInLandscapeMode?: boolean; }; function FormProvider({ diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index 4b87ad400399..cd742f6de595 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -78,6 +78,9 @@ type FormWrapperProps = ChildrenProps & shouldPreventDefaultFocusOnPressSubmit?: boolean; ref?: ForwardedRef; + + /** Whether to display the submit button and footer in one row in landscape mode */ + shouldDisplaySubmitButtonAndFooterInOneRowInLandscapeMode?: boolean; }; function FormWrapper({ @@ -112,6 +115,7 @@ function FormWrapper({ forwardedFSClass, sentryLabel = CONST.SENTRY_LABEL.FORM.SUBMIT_BUTTON, ref, + shouldDisplaySubmitButtonAndFooterInOneRowInLandscapeMode = false, }: FormWrapperProps) { const styles = useThemeStyles(); const formRef = useRef(null); @@ -217,6 +221,7 @@ function FormWrapper({ shouldBlendOpacity={shouldSubmitButtonBlendOpacity} shouldPreventDefaultFocusOnPress={shouldPreventDefaultFocusOnPressSubmit} sentryLabel={sentryLabel} + shouldDisplaySubmitButtonAndFooterInOneRowInLandscapeMode={shouldDisplaySubmitButtonAndFooterInOneRowInLandscapeMode} /> ); diff --git a/src/components/FormAlertWithSubmitButton.tsx b/src/components/FormAlertWithSubmitButton.tsx index 93b9f5735aa0..311f557eb884 100644 --- a/src/components/FormAlertWithSubmitButton.tsx +++ b/src/components/FormAlertWithSubmitButton.tsx @@ -1,3 +1,4 @@ +import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode'; import useThemeStyles from '@hooks/useThemeStyles'; import getPlatform from '@libs/getPlatform'; @@ -83,6 +84,9 @@ type FormAlertWithSubmitButtonProps = WithSentryLabel & { /** Prevents the button from triggering blur on mouse down. */ shouldPreventDefaultFocusOnPress?: boolean; + + /** Whether to display the submit button and footer in one row in landscape mode */ + shouldDisplaySubmitButtonAndFooterInOneRowInLandscapeMode?: boolean; }; function FormAlertWithSubmitButton({ @@ -108,10 +112,17 @@ function FormAlertWithSubmitButton({ shouldBlendOpacity = false, addButtonBottomPadding = true, shouldPreventDefaultFocusOnPress = false, + shouldDisplaySubmitButtonAndFooterInOneRowInLandscapeMode = false, sentryLabel, }: FormAlertWithSubmitButtonProps) { const styles = useThemeStyles(); - const style = [!shouldRenderFooterAboveSubmit && footerContent && addButtonBottomPadding ? styles.mb3 : {}, buttonStyles]; + const isInLandscapeMode = useIsInLandscapeMode(); + const shouldDisplayButtonAndFooterInOneRow = isInLandscapeMode && shouldDisplaySubmitButtonAndFooterInOneRowInLandscapeMode; + const style = [ + !shouldRenderFooterAboveSubmit && footerContent && addButtonBottomPadding && !shouldDisplayButtonAndFooterInOneRow ? styles.mb3 : undefined, + shouldDisplayButtonAndFooterInOneRow ? styles.flex1 : {}, + buttonStyles, + ]; // Disable pressOnEnter for Android Native to avoid issues with the Samsung keyboard, // where pressing Enter saves the form instead of adding a new line in multiline input. @@ -129,7 +140,7 @@ function FormAlertWithSubmitButton({ errorMessageStyle={errorMessageStyle} > {(isOffline: boolean | undefined) => ( - + {shouldRenderFooterAboveSubmit && footerContent} {isOffline && !enabledWhenOffline ? (