From f799c0824c5c10189e3fe72172956aade7bc7dcd Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 01:56:36 +0200 Subject: [PATCH 01/19] Create draft MoneyRequestAmountInput --- src/components/AmountTextInput.tsx | 11 +- .../TextInputWithCurrencySymbol/types.ts | 8 +- src/components/TimePicker/TimePicker.tsx | 6 +- .../iou/steps/MoneyRequestAmountForm.tsx | 129 ++-------- .../iou/steps/MoneyRequestAmountInput.tsx | 239 ++++++++++++++++++ 5 files changed, 277 insertions(+), 116 deletions(-) create mode 100644 src/pages/iou/steps/MoneyRequestAmountInput.tsx diff --git a/src/components/AmountTextInput.tsx b/src/components/AmountTextInput.tsx index abdef6707327..58dd33bd86a6 100644 --- a/src/components/AmountTextInput.tsx +++ b/src/components/AmountTextInput.tsx @@ -1,7 +1,6 @@ import React from 'react'; import type {ForwardedRef} from 'react'; import type {NativeSyntheticEvent, StyleProp, TextInputKeyPressEventData, TextInputSelectionChangeEventData, TextStyle, ViewStyle} from 'react-native'; -import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import type {TextSelection} from './Composer/types'; import TextInput from './TextInput'; @@ -31,21 +30,23 @@ type AmountTextInputProps = { /** Function to call to handle key presses in the text input */ onKeyPress?: (event: NativeSyntheticEvent) => void; + + /** Style for the container */ + containerStyle?: StyleProp; } & Pick; function AmountTextInput( - {formattedAmount, onChangeAmount, placeholder, selection, onSelectionChange, style, touchableInputWrapperStyle, onKeyPress, ...rest}: AmountTextInputProps, + {formattedAmount, onChangeAmount, placeholder, selection, onSelectionChange, style, touchableInputWrapperStyle, onKeyPress, containerStyle, ...rest}: AmountTextInputProps, ref: ForwardedRef, ) { - const styles = useThemeStyles(); return ( ; + + /** Style for the container */ + containerStyle?: StyleProp; } & Pick; export default TextInputWithCurrencySymbolProps; diff --git a/src/components/TimePicker/TimePicker.tsx b/src/components/TimePicker/TimePicker.tsx index 17cd93db432b..c3cafeb08f37 100644 --- a/src/components/TimePicker/TimePicker.tsx +++ b/src/components/TimePicker/TimePicker.tsx @@ -458,7 +458,8 @@ function TimePicker({defaultValue = '', onSubmit, onInputChange = () => {}}: Tim onSelectionChange={(e) => { setSelectionHour(e.nativeEvent.selection); }} - style={styles.timePickerInput} + style={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius, styles.timePickerInput]} + containerStyle={[styles.borderNone, styles.noLeftBorderRadius, styles.noRightBorderRadius]} touchableInputWrapperStyle={styles.timePickerHeight100} selection={selectionHour} /> @@ -484,7 +485,8 @@ function TimePicker({defaultValue = '', onSubmit, onInputChange = () => {}}: Tim onSelectionChange={(e) => { setSelectionMinute(e.nativeEvent.selection); }} - style={styles.timePickerInput} + style={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius, styles.timePickerInput]} + containerStyle={[styles.borderNone, styles.noLeftBorderRadius, styles.noRightBorderRadius]} touchableInputWrapperStyle={styles.timePickerHeight100} selection={selectionMinute} /> diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index 5d603902b0f1..9eeef4b748a9 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -2,26 +2,23 @@ import {useIsFocused} from '@react-navigation/core'; import type {ForwardedRef} from 'react'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; -import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native'; import BigNumberPad from '@components/BigNumberPad'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; import ScrollView from '@components/ScrollView'; -import TextInputWithCurrencySymbol from '@components/TextInputWithCurrencySymbol'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import * as Browser from '@libs/Browser'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import getOperatingSystem from '@libs/getOperatingSystem'; import type {MaybePhraseKey} from '@libs/Localize'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {BaseTextInputRef} from '@src/components/TextInput/BaseTextInput/types'; import CONST from '@src/CONST'; import type {SelectedTabRequest} from '@src/types/onyx'; +import MoneyRequestAmountInput from './MoneyRequestAmountInput'; type CurrentMoney = {amount: string; currency: string}; @@ -51,19 +48,6 @@ type MoneyRequestAmountFormProps = { selectedTab?: SelectedTabRequest; }; -type Selection = { - start: number; - end: number; -}; - -/** - * Returns the new selection object based on the updated amount's length - */ -const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: number): Selection => { - const cursorPosition = oldSelection.end + (newLength - prevLength); - return {start: cursorPosition, end: cursorPosition}; -}; - const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01; const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean) => isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmount(Math.abs(taxAmount)); @@ -87,12 +71,12 @@ function MoneyRequestAmountForm( ) { const styles = useThemeStyles(); const {isExtraSmallScreenHeight} = useWindowDimensions(); - const {translate, toLocaleDigit, numberFormat} = useLocalize(); + const {translate} = useLocalize(); const textInput = useRef(null); + const moneyRequestAmountInput = useRef<{changeAmount: (amount: string) => void} | null>(null); const isTaxAmountForm = Navigation.getActiveRoute().includes('taxAmount'); - const decimals = CurrencyUtils.getCurrencyDecimals(currency); const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); @@ -107,8 +91,6 @@ function MoneyRequestAmountForm( end: selectedAmountAsString.length, }); - const forwardDeletePressedRef = useRef(false); - const formattedTaxAmount = CurrencyUtils.convertToDisplayString(Math.abs(taxAmount), currency); /** @@ -152,55 +134,6 @@ function MoneyRequestAmountForm( // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedTab, amount]); - /** - * Sets the selection and the amount accordingly to the value passed to the input - * @param {String} newAmount - Changed amount from user input - */ - const setNewAmount = useCallback( - (newAmount: string) => { - // Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value - // More info: https://github.com/Expensify/App/issues/16974 - const newAmountWithoutSpaces = MoneyRequestUtils.stripSpacesFromAmount(newAmount); - // Use a shallow copy of selection to trigger setSelection - // More info: https://github.com/Expensify/App/issues/16385 - if (!MoneyRequestUtils.validateAmount(newAmountWithoutSpaces, decimals)) { - setSelection((prevSelection) => ({...prevSelection})); - return; - } - if (formError) { - setFormError(''); - } - - // setCurrentAmount contains another setState(setSelection) making it error-prone since it is leading to setSelection being called twice for a single setCurrentAmount call. This solution introducing the hasSelectionBeenSet flag was chosen for its simplicity and lower risk of future errors https://github.com/Expensify/App/issues/23300#issuecomment-1766314724. - - let hasSelectionBeenSet = false; - setCurrentAmount((prevAmount) => { - const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces); - const isForwardDelete = prevAmount.length > strippedAmount.length && forwardDeletePressedRef.current; - if (!hasSelectionBeenSet) { - hasSelectionBeenSet = true; - setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); - } - return strippedAmount; - }); - }, - [decimals, formError], - ); - - // Modifies the amount to match the decimals for changed currency. - useEffect(() => { - // If the changed currency supports decimals, we can return - if (MoneyRequestUtils.validateAmount(currentAmount, decimals)) { - return; - } - - // If the changed currency doesn't support decimals, we can strip the decimals - setNewAmount(MoneyRequestUtils.stripDecimalsFromAmount(currentAmount)); - - // we want to update only when decimals change (setNewAmount also changes when decimals change). - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setNewAmount]); - // Removes text selection if user visits currency selector with selection and comes back useEffect(() => { if (!isFocused || wasFocused) { @@ -227,14 +160,14 @@ function MoneyRequestAmountForm( if (currentAmount.length > 0) { const selectionStart = selection.start === selection.end ? selection.start - 1 : selection.start; const newAmount = `${currentAmount.substring(0, selectionStart)}${currentAmount.substring(selection.end)}`; - setNewAmount(MoneyRequestUtils.addLeadingZero(newAmount)); + moneyRequestAmountInput.current?.changeAmount(MoneyRequestUtils.addLeadingZero(newAmount)); } return; } const newAmount = MoneyRequestUtils.addLeadingZero(`${currentAmount.substring(0, selection.start)}${key}${currentAmount.substring(selection.end)}`); - setNewAmount(newAmount); + moneyRequestAmountInput.current?.changeAmount(newAmount); }, - [currentAmount, selection, shouldUpdateSelection, setNewAmount], + [currentAmount, selection, shouldUpdateSelection], ); /** @@ -272,24 +205,6 @@ function MoneyRequestAmountForm( onSubmitButtonPress({amount: currentAmount, currency}); }, [currentAmount, taxAmount, isTaxAmountForm, onSubmitButtonPress, currency, formattedTaxAmount, initializeAmount]); - /** - * Input handler to check for a forward-delete key (or keyboard shortcut) press. - */ - const textInputKeyPress = ({nativeEvent}: NativeSyntheticEvent) => { - const key = nativeEvent?.key.toLowerCase(); - if (Browser.isMobileSafari() && key === CONST.PLATFORM_SPECIFIC_KEYS.CTRL.DEFAULT) { - // Optimistically anticipate forward-delete on iOS Safari (in cases where the Mac Accessiblity keyboard is being - // used for input). If the Control-D shortcut doesn't get sent, the ref will still be reset on the next key press. - forwardDeletePressedRef.current = true; - return; - } - // Control-D on Mac is a keyboard shortcut for forward-delete. See https://support.apple.com/en-us/HT201236 for Mac keyboard shortcuts. - // Also check for the keyboard shortcut on iOS in cases where a hardware keyboard may be connected to the device. - const operatingSystem = getOperatingSystem(); - forwardDeletePressedRef.current = key === 'delete' || ((operatingSystem === CONST.OS.MAC_OS || operatingSystem === CONST.OS.IOS) && nativeEvent?.ctrlKey && key === 'd'); - }; - - const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); const buttonText = isEditing ? translate('common.save') : translate('common.next'); const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); @@ -304,11 +219,19 @@ function MoneyRequestAmountForm( onMouseDown={(event) => onMouseDown(event, [AMOUNT_VIEW_ID])} style={[styles.moneyRequestAmountContainer, styles.flex1, styles.flexRow, styles.w100, styles.alignItemsCenter, styles.justifyContentCenter]} > - { + if (!formError) { + return; + } + setFormError(''); + }} + shouldUpdateSelection={shouldUpdateSelection} ref={(ref) => { if (typeof forwardedRef === 'function') { forwardedRef(ref); @@ -318,19 +241,9 @@ function MoneyRequestAmountForm( } textInput.current = ref; }} - selectedCurrencyCode={currency} - selection={selection} - onSelectionChange={(e: NativeSyntheticEvent) => { - if (!shouldUpdateSelection) { - return; - } - const maxSelection = formattedAmount.length; - const start = Math.min(e.nativeEvent.selection.start, maxSelection); - const end = Math.min(e.nativeEvent.selection.end, maxSelection); - setSelection({start, end}); - }} - onKeyPress={textInputKeyPress} - isCurrencyPressable={isCurrencyPressable} + moneyRequestAmountInputRef={moneyRequestAmountInput} + style={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius, styles.timePickerInput]} + containerStyle={[styles.borderNone, styles.noLeftBorderRadius, styles.noRightBorderRadius]} /> {!!formError && ( void; + + /** Function to call when the amount changes */ + onAmountChange?: (amount: string) => void; + + /** The current tab we have navigated to in the expense modal. String that corresponds to the expense type. */ + selectedTab?: SelectedTabRequest; + + /** Whether to update the selection */ + shouldUpdateSelection?: boolean; + + /** Style for the input */ + style?: StyleProp; + + /** Style for the container */ + containerStyle?: StyleProp; + + /** Reference to moneyRequestAmountInputRef */ + moneyRequestAmountInputRef?: ForwardedRef<{changeAmount: (amount: string) => void}>; +}; + +type Selection = { + start: number; + end: number; +}; + +/** + * Returns the new selection object based on the updated amount's length + */ +const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: number): Selection => { + const cursorPosition = oldSelection.end + (newLength - prevLength); + return {start: cursorPosition, end: cursorPosition}; +}; + +function MoneyRequestAmountInput( + { + amount = 0, + currency = CONST.CURRENCY.USD, + isCurrencyPressable = true, + onCurrencyButtonPress, + selectedTab = CONST.TAB_REQUEST.MANUAL, + onAmountChange, + shouldUpdateSelection = true, + moneyRequestAmountInputRef, + ...props + }: MoneyRequestAmountInputProps, + forwardedRef: ForwardedRef, +) { + const {toLocaleDigit, numberFormat} = useLocalize(); + + const textInput = useRef(null); + + const decimals = CurrencyUtils.getCurrencyDecimals(currency); + const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; + + const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); + + const isFocused = useIsFocused(); + const wasFocused = usePrevious(isFocused); + + const [selection, setSelection] = useState({ + start: selectedAmountAsString.length, + end: selectedAmountAsString.length, + }); + + const forwardDeletePressedRef = useRef(false); + + const initializeAmount = useCallback((newAmount: number) => { + const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmount(newAmount).toString() : ''; + setCurrentAmount(frontendAmount); + setSelection({ + start: frontendAmount.length, + end: frontendAmount.length, + }); + }, []); + + useEffect(() => { + if (!currency || typeof amount !== 'number') { + return; + } + initializeAmount(amount); + // we want to re-initialize the state only when the selected tab or amount changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedTab, amount]); + + /** + * Sets the selection and the amount accordingly to the value passed to the input + * @param {String} newAmount - Changed amount from user input + */ + const setNewAmount = useCallback( + (newAmount: string) => { + // Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value + // More info: https://github.com/Expensify/App/issues/16974 + const newAmountWithoutSpaces = MoneyRequestUtils.stripSpacesFromAmount(newAmount); + // Use a shallow copy of selection to trigger setSelection + // More info: https://github.com/Expensify/App/issues/16385 + if (!MoneyRequestUtils.validateAmount(newAmountWithoutSpaces, decimals)) { + setSelection((prevSelection) => ({...prevSelection})); + return; + } + + // setCurrentAmount contains another setState(setSelection) making it error-prone since it is leading to setSelection being called twice for a single setCurrentAmount call. This solution introducing the hasSelectionBeenSet flag was chosen for its simplicity and lower risk of future errors https://github.com/Expensify/App/issues/23300#issuecomment-1766314724. + + let hasSelectionBeenSet = false; + setCurrentAmount((prevAmount) => { + const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces); + const isForwardDelete = prevAmount.length > strippedAmount.length && forwardDeletePressedRef.current; + if (!hasSelectionBeenSet) { + hasSelectionBeenSet = true; + setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); + } + onAmountChange?.(strippedAmount); + return strippedAmount; + }); + }, + [decimals, onAmountChange], + ); + + useImperativeHandle( + moneyRequestAmountInputRef, + () => + ({ + changeAmount(changeAmountValue: string) { + setNewAmount(changeAmountValue); + }, + } as {changeAmount: (amount: string) => void}), + ); + + // Modifies the amount to match the decimals for changed currency. + useEffect(() => { + // If the changed currency supports decimals, we can return + if (MoneyRequestUtils.validateAmount(currentAmount, decimals)) { + return; + } + + // If the changed currency doesn't support decimals, we can strip the decimals + setNewAmount(MoneyRequestUtils.stripDecimalsFromAmount(currentAmount)); + + // we want to update only when decimals change (setNewAmount also changes when decimals change). + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [setNewAmount]); + + // Removes text selection if user visits currency selector with selection and comes back + useEffect(() => { + if (!isFocused || wasFocused) { + return; + } + + setSelection({ + start: selection.end, + end: selection.end, + }); + }, [selection.end, isFocused, selection, wasFocused]); + + /** + * Input handler to check for a forward-delete key (or keyboard shortcut) press. + */ + const textInputKeyPress = ({nativeEvent}: NativeSyntheticEvent) => { + const key = nativeEvent?.key.toLowerCase(); + if (Browser.isMobileSafari() && key === CONST.PLATFORM_SPECIFIC_KEYS.CTRL.DEFAULT) { + // Optimistically anticipate forward-delete on iOS Safari (in cases where the Mac Accessiblity keyboard is being + // used for input). If the Control-D shortcut doesn't get sent, the ref will still be reset on the next key press. + forwardDeletePressedRef.current = true; + return; + } + // Control-D on Mac is a keyboard shortcut for forward-delete. See https://support.apple.com/en-us/HT201236 for Mac keyboard shortcuts. + // Also check for the keyboard shortcut on iOS in cases where a hardware keyboard may be connected to the device. + const operatingSystem = getOperatingSystem(); + forwardDeletePressedRef.current = key === 'delete' || ((operatingSystem === CONST.OS.MAC_OS || operatingSystem === CONST.OS.IOS) && nativeEvent?.ctrlKey && key === 'd'); + }; + + const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); + + return ( + { + if (typeof forwardedRef === 'function') { + forwardedRef(ref); + } else if (forwardedRef?.current) { + // eslint-disable-next-line no-param-reassign + forwardedRef.current = ref; + } + textInput.current = ref; + }} + selectedCurrencyCode={currency} + selection={selection} + onSelectionChange={(e: NativeSyntheticEvent) => { + if (!shouldUpdateSelection) { + return; + } + const maxSelection = formattedAmount.length; + const start = Math.min(e.nativeEvent.selection.start, maxSelection); + const end = Math.min(e.nativeEvent.selection.end, maxSelection); + setSelection({start, end}); + }} + onKeyPress={textInputKeyPress} + isCurrencyPressable={isCurrencyPressable} + style={props.style} + containerStyle={props.containerStyle} + /> + ); +} + +MoneyRequestAmountInput.displayName = 'MoneyRequestAmountInput'; + +export default React.forwardRef(MoneyRequestAmountInput); +export type {CurrentMoney}; From aa5c56dea65ce6133757c9d577302a0d93e59702 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 02:03:25 +0200 Subject: [PATCH 02/19] Fix bug with styles --- .../BaseTextInputWithCurrencySymbol.tsx | 3 ++- src/pages/iou/steps/MoneyRequestAmountForm.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.tsx b/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.tsx index 5ea8d140c6a0..3e7c5f0bc414 100644 --- a/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.tsx +++ b/src/components/TextInputWithCurrencySymbol/BaseTextInputWithCurrencySymbol.tsx @@ -22,6 +22,7 @@ function BaseTextInputWithCurrencySymbol( isCurrencyPressable = true, hideCurrencySymbol = false, extraSymbol, + style, ...rest }: TextInputWithCurrencySymbolProps, ref: React.ForwardedRef, @@ -60,7 +61,7 @@ function BaseTextInputWithCurrencySymbol( onSelectionChange(event); }} onKeyPress={onKeyPress} - style={styles.pr1} + style={[styles.pr1, style]} // eslint-disable-next-line react/jsx-props-no-spreading {...rest} /> diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index 9eeef4b748a9..cfe7c5f669fb 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -242,7 +242,7 @@ function MoneyRequestAmountForm( textInput.current = ref; }} moneyRequestAmountInputRef={moneyRequestAmountInput} - style={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius, styles.timePickerInput]} + style={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius]} containerStyle={[styles.borderNone, styles.noLeftBorderRadius, styles.noRightBorderRadius]} /> {!!formError && ( From 3aeb011259ec4cecfa8397bda73017a209031b2f Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 02:09:27 +0200 Subject: [PATCH 03/19] Move MoneyRequestAmountInput to components --- .../iou/steps => components}/MoneyRequestAmountInput.tsx | 4 ++-- src/pages/iou/steps/MoneyRequestAmountForm.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/{pages/iou/steps => components}/MoneyRequestAmountInput.tsx (98%) diff --git a/src/pages/iou/steps/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx similarity index 98% rename from src/pages/iou/steps/MoneyRequestAmountInput.tsx rename to src/components/MoneyRequestAmountInput.tsx index cfb0e1f18e90..73adbc56eca2 100644 --- a/src/pages/iou/steps/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -2,16 +2,16 @@ import {useIsFocused} from '@react-navigation/core'; import type {ForwardedRef} from 'react'; import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import type {NativeSyntheticEvent, StyleProp, TextInputSelectionChangeEventData, TextStyle, ViewStyle} from 'react-native'; -import TextInputWithCurrencySymbol from '@components/TextInputWithCurrencySymbol'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import * as Browser from '@libs/Browser'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; -import type {BaseTextInputRef} from '@src/components/TextInput/BaseTextInput/types'; import CONST from '@src/CONST'; import type {SelectedTabRequest} from '@src/types/onyx'; +import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; +import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; type CurrentMoney = {amount: string; currency: string}; diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index cfe7c5f669fb..8a991adb6973 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -5,6 +5,7 @@ import {View} from 'react-native'; import BigNumberPad from '@components/BigNumberPad'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; +import MoneyRequestAmountInput from '@components/MoneyRequestAmountInput'; import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; @@ -18,7 +19,6 @@ import Navigation from '@libs/Navigation/Navigation'; import type {BaseTextInputRef} from '@src/components/TextInput/BaseTextInput/types'; import CONST from '@src/CONST'; import type {SelectedTabRequest} from '@src/types/onyx'; -import MoneyRequestAmountInput from './MoneyRequestAmountInput'; type CurrentMoney = {amount: string; currency: string}; From 727a5545d461e0eab6ecbe1aa37afedf2ebad4bf Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 02:57:53 +0200 Subject: [PATCH 04/19] Refactor MoneyRequestAmountInput --- src/components/MoneyRequestAmountInput.tsx | 78 +++++++------------ .../iou/steps/MoneyRequestAmountForm.tsx | 55 +++++++------ 2 files changed, 53 insertions(+), 80 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 73adbc56eca2..9067f2013d22 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -1,20 +1,25 @@ -import {useIsFocused} from '@react-navigation/core'; import type {ForwardedRef} from 'react'; import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import type {NativeSyntheticEvent, StyleProp, TextInputSelectionChangeEventData, TextStyle, ViewStyle} from 'react-native'; import useLocalize from '@hooks/useLocalize'; -import usePrevious from '@hooks/usePrevious'; import * as Browser from '@libs/Browser'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; -import type {SelectedTabRequest} from '@src/types/onyx'; import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; type CurrentMoney = {amount: string; currency: string}; +type MoneyRequestAmountInputRef = { + setNewAmount: (amountValue: string) => void; + changeSelection: (newSelection: Selection) => void; + changeAmount: (newAmount: string) => void; + getAmount: () => string; + getSelection: () => Selection; +}; + type MoneyRequestAmountInputProps = { /** IOU amount saved in Onyx */ amount?: number; @@ -31,9 +36,6 @@ type MoneyRequestAmountInputProps = { /** Function to call when the amount changes */ onAmountChange?: (amount: string) => void; - /** The current tab we have navigated to in the expense modal. String that corresponds to the expense type. */ - selectedTab?: SelectedTabRequest; - /** Whether to update the selection */ shouldUpdateSelection?: boolean; @@ -44,7 +46,7 @@ type MoneyRequestAmountInputProps = { containerStyle?: StyleProp; /** Reference to moneyRequestAmountInputRef */ - moneyRequestAmountInputRef?: ForwardedRef<{changeAmount: (amount: string) => void}>; + moneyRequestAmountInputRef?: ForwardedRef; }; type Selection = { @@ -66,7 +68,6 @@ function MoneyRequestAmountInput( currency = CONST.CURRENCY.USD, isCurrencyPressable = true, onCurrencyButtonPress, - selectedTab = CONST.TAB_REQUEST.MANUAL, onAmountChange, shouldUpdateSelection = true, moneyRequestAmountInputRef, @@ -83,9 +84,6 @@ function MoneyRequestAmountInput( const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); - const isFocused = useIsFocused(); - const wasFocused = usePrevious(isFocused); - const [selection, setSelection] = useState({ start: selectedAmountAsString.length, end: selectedAmountAsString.length, @@ -93,24 +91,6 @@ function MoneyRequestAmountInput( const forwardDeletePressedRef = useRef(false); - const initializeAmount = useCallback((newAmount: number) => { - const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmount(newAmount).toString() : ''; - setCurrentAmount(frontendAmount); - setSelection({ - start: frontendAmount.length, - end: frontendAmount.length, - }); - }, []); - - useEffect(() => { - if (!currency || typeof amount !== 'number') { - return; - } - initializeAmount(amount); - // we want to re-initialize the state only when the selected tab or amount changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedTab, amount]); - /** * Sets the selection and the amount accordingly to the value passed to the input * @param {String} newAmount - Changed amount from user input @@ -144,15 +124,23 @@ function MoneyRequestAmountInput( [decimals, onAmountChange], ); - useImperativeHandle( - moneyRequestAmountInputRef, - () => - ({ - changeAmount(changeAmountValue: string) { - setNewAmount(changeAmountValue); - }, - } as {changeAmount: (amount: string) => void}), - ); + useImperativeHandle(moneyRequestAmountInputRef, () => ({ + setNewAmount(amountValue: string) { + setNewAmount(amountValue); + }, + changeSelection(newSelection: Selection) { + setSelection(newSelection); + }, + changeAmount(newAmount: string) { + setCurrentAmount(newAmount); + }, + getAmount() { + return currentAmount; + }, + getSelection() { + return selection; + }, + })); // Modifies the amount to match the decimals for changed currency. useEffect(() => { @@ -168,18 +156,6 @@ function MoneyRequestAmountInput( // eslint-disable-next-line react-hooks/exhaustive-deps }, [setNewAmount]); - // Removes text selection if user visits currency selector with selection and comes back - useEffect(() => { - if (!isFocused || wasFocused) { - return; - } - - setSelection({ - start: selection.end, - end: selection.end, - }); - }, [selection.end, isFocused, selection, wasFocused]); - /** * Input handler to check for a forward-delete key (or keyboard shortcut) press. */ @@ -236,4 +212,4 @@ function MoneyRequestAmountInput( MoneyRequestAmountInput.displayName = 'MoneyRequestAmountInput'; export default React.forwardRef(MoneyRequestAmountInput); -export type {CurrentMoney}; +export type {CurrentMoney, MoneyRequestAmountInputRef}; diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index 8a991adb6973..8ce011670261 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -6,6 +6,7 @@ import BigNumberPad from '@components/BigNumberPad'; import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; import MoneyRequestAmountInput from '@components/MoneyRequestAmountInput'; +import type {MoneyRequestAmountInputRef} from '@components/MoneyRequestAmountInput'; import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; @@ -74,23 +75,15 @@ function MoneyRequestAmountForm( const {translate} = useLocalize(); const textInput = useRef(null); - const moneyRequestAmountInput = useRef<{changeAmount: (amount: string) => void} | null>(null); + const moneyRequestAmountInput = useRef(null); const isTaxAmountForm = Navigation.getActiveRoute().includes('taxAmount'); - const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; - - const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); const [formError, setFormError] = useState(''); const [shouldUpdateSelection, setShouldUpdateSelection] = useState(true); const isFocused = useIsFocused(); const wasFocused = usePrevious(isFocused); - const [selection, setSelection] = useState({ - start: selectedAmountAsString.length, - end: selectedAmountAsString.length, - }); - const formattedTaxAmount = CurrencyUtils.convertToDisplayString(Math.abs(taxAmount), currency); /** @@ -102,8 +95,10 @@ function MoneyRequestAmountForm( return; } + const selection = moneyRequestAmountInput.current?.getSelection() ?? {start: 0, end: 0}; + event.preventDefault(); - setSelection({ + moneyRequestAmountInput.current?.changeSelection({ start: selection.end, end: selection.end, }); @@ -116,10 +111,22 @@ function MoneyRequestAmountForm( } }; + useEffect(() => { + if (!isFocused || wasFocused) { + return; + } + const selection = moneyRequestAmountInput.current?.getSelection() ?? {start: 0, end: 0}; + + moneyRequestAmountInput.current?.changeSelection({ + start: selection.end, + end: selection.end, + }); + }, [isFocused, wasFocused]); + const initializeAmount = useCallback((newAmount: number) => { const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmount(newAmount).toString() : ''; - setCurrentAmount(frontendAmount); - setSelection({ + moneyRequestAmountInput.current?.changeAmount(frontendAmount); + moneyRequestAmountInput.current?.changeSelection({ start: frontendAmount.length, end: frontendAmount.length, }); @@ -134,18 +141,6 @@ function MoneyRequestAmountForm( // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedTab, amount]); - // Removes text selection if user visits currency selector with selection and comes back - useEffect(() => { - if (!isFocused || wasFocused) { - return; - } - - setSelection({ - start: selection.end, - end: selection.end, - }); - }, [selection.end, isFocused, selection, wasFocused]); - /** * Update amount with number or Backspace pressed for BigNumberPad. * Validate new amount with decimal number regex up to 6 digits and 2 decimal digit to enable Next button @@ -155,19 +150,21 @@ function MoneyRequestAmountForm( if (shouldUpdateSelection && !textInput.current?.isFocused()) { textInput.current?.focus(); } + const currentAmount = moneyRequestAmountInput.current?.getAmount() ?? ''; + const selection = moneyRequestAmountInput.current?.getSelection() ?? {start: 0, end: 0}; // Backspace button is pressed if (key === '<' || key === 'Backspace') { if (currentAmount.length > 0) { const selectionStart = selection.start === selection.end ? selection.start - 1 : selection.start; const newAmount = `${currentAmount.substring(0, selectionStart)}${currentAmount.substring(selection.end)}`; - moneyRequestAmountInput.current?.changeAmount(MoneyRequestUtils.addLeadingZero(newAmount)); + moneyRequestAmountInput.current?.setNewAmount(MoneyRequestUtils.addLeadingZero(newAmount)); } return; } const newAmount = MoneyRequestUtils.addLeadingZero(`${currentAmount.substring(0, selection.start)}${key}${currentAmount.substring(selection.end)}`); - moneyRequestAmountInput.current?.changeAmount(newAmount); + moneyRequestAmountInput.current?.setNewAmount(newAmount); }, - [currentAmount, selection, shouldUpdateSelection], + [shouldUpdateSelection], ); /** @@ -187,6 +184,7 @@ function MoneyRequestAmountForm( */ const submitAndNavigateToNextPage = useCallback(() => { // Skip the check for tax amount form as 0 is a valid input + const currentAmount = moneyRequestAmountInput.current?.getAmount() ?? ''; if (!currentAmount.length || (!isTaxAmountForm && isAmountInvalid(currentAmount))) { setFormError('iou.error.invalidAmount'); return; @@ -203,7 +201,7 @@ function MoneyRequestAmountForm( initializeAmount(backendAmount); onSubmitButtonPress({amount: currentAmount, currency}); - }, [currentAmount, taxAmount, isTaxAmountForm, onSubmitButtonPress, currency, formattedTaxAmount, initializeAmount]); + }, [taxAmount, isTaxAmountForm, onSubmitButtonPress, currency, formattedTaxAmount, initializeAmount]); const buttonText = isEditing ? translate('common.save') : translate('common.next'); const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); @@ -224,7 +222,6 @@ function MoneyRequestAmountForm( currency={currency} isCurrencyPressable={isCurrencyPressable} onCurrencyButtonPress={onCurrencyButtonPress} - selectedTab={selectedTab} onAmountChange={() => { if (!formError) { return; From 9f6d574c1bcfbad80b4371d5f1b452440be152d4 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 04:02:07 +0200 Subject: [PATCH 05/19] Update styles for new component --- src/components/MoneyRequestAmountInput.tsx | 18 ++++++++++++++++-- .../TextInput/BaseTextInput/index.native.tsx | 12 +++--------- .../TextInput/BaseTextInput/index.tsx | 12 +++--------- .../TextInput/BaseTextInput/types.ts | 3 +++ .../TextInputWithCurrencySymbol/types.ts | 6 ++++++ src/pages/iou/steps/MoneyRequestAmountForm.tsx | 2 +- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 9067f2013d22..8b8a48c36280 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -40,13 +40,22 @@ type MoneyRequestAmountInputProps = { shouldUpdateSelection?: boolean; /** Style for the input */ - style?: StyleProp; + inputStyle?: StyleProp; /** Style for the container */ containerStyle?: StyleProp; /** Reference to moneyRequestAmountInputRef */ moneyRequestAmountInputRef?: ForwardedRef; + + /** Character to be shown before the amount */ + prefixCharacter?: string; + + /** Whether to hide the currency symbol */ + hideCurrencySymbol?: boolean; + + /** Style for the prefix */ + prefixStyle?: StyleProp; }; type Selection = { @@ -69,6 +78,8 @@ function MoneyRequestAmountInput( isCurrencyPressable = true, onCurrencyButtonPress, onAmountChange, + prefixCharacter = '', + hideCurrencySymbol = false, shouldUpdateSelection = true, moneyRequestAmountInputRef, ...props @@ -202,9 +213,12 @@ function MoneyRequestAmountInput( setSelection({start, end}); }} onKeyPress={textInputKeyPress} + hideCurrencySymbol={hideCurrencySymbol} + prefixCharacter={prefixCharacter} isCurrencyPressable={isCurrencyPressable} - style={props.style} + style={props.inputStyle} containerStyle={props.containerStyle} + prefixStyle={props.prefixStyle} /> ); } diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index 3039d7327d37..0f37570561e6 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -57,6 +57,7 @@ function BaseTextInput( multiline = false, autoCorrect = true, prefixCharacter = '', + prefixStyle = [], inputID, isMarkdownEnabled = false, ...props @@ -243,14 +244,7 @@ function BaseTextInput( // Some characters are wider than the others when rendered, e.g. '@' vs '#'. Chosen font-family and font-size // also have an impact on the width of the character, but as long as there's only one font-family and one font-size, // this method will produce reliable results. - const getCharacterPadding = (prefix: string): number => { - switch (prefix) { - case CONST.POLICY.ROOM_PREFIX: - return 10; - default: - throw new Error(`Prefix ${prefix} has no padding assigned.`); - } - }; + const getCharacterPadding = (prefix: string): number => prefix.length * 10; const hasLabel = Boolean(label?.length); const isReadOnly = inputProps.readOnly ?? inputProps.disabled; @@ -322,7 +316,7 @@ function BaseTextInput( {prefixCharacter} diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index 519a52fd85ec..d583830231f8 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -59,6 +59,7 @@ function BaseTextInput( shouldInterceptSwipe = false, autoCorrect = true, prefixCharacter = '', + prefixStyle = [], inputID, isMarkdownEnabled = false, ...inputProps @@ -240,14 +241,7 @@ function BaseTextInput( // Some characters are wider than the others when rendered, e.g. '@' vs '#'. Chosen font-family and font-size // also have an impact on the width of the character, but as long as there's only one font-family and one font-size, // this method will produce reliable results. - const getCharacterPadding = (prefix: string): number => { - switch (prefix) { - case CONST.POLICY.ROOM_PREFIX: - return 10; - default: - throw new Error(`Prefix ${prefix} has no padding assigned.`); - } - }; + const getCharacterPadding = (prefix: string): number => prefix.length * 10; const hasLabel = Boolean(label?.length); const isReadOnly = inputProps.readOnly ?? inputProps.disabled; @@ -342,7 +336,7 @@ function BaseTextInput( {prefixCharacter} diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index 1529fbe4c7c6..40278cad65c9 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -107,6 +107,9 @@ type CustomBaseTextInputProps = { /** Should live markdown be enabled. Changes RNTextInput component to RNMarkdownTextInput */ isMarkdownEnabled?: boolean; + + /** Style for the prefix */ + prefixStyle?: StyleProp; }; type BaseTextInputRef = HTMLFormElement | AnimatedTextInputRef; diff --git a/src/components/TextInputWithCurrencySymbol/types.ts b/src/components/TextInputWithCurrencySymbol/types.ts index 4afa25112939..f365b5fded04 100644 --- a/src/components/TextInputWithCurrencySymbol/types.ts +++ b/src/components/TextInputWithCurrencySymbol/types.ts @@ -41,6 +41,12 @@ type TextInputWithCurrencySymbolProps = { /** Style for the container */ containerStyle?: StyleProp; + + /** Character to be shown before the amount */ + prefixCharacter?: string; + + /** Style for the prefix */ + prefixStyle?: StyleProp; } & Pick; export default TextInputWithCurrencySymbolProps; diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index 8ce011670261..ff1fe84d65b1 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -239,7 +239,7 @@ function MoneyRequestAmountForm( textInput.current = ref; }} moneyRequestAmountInputRef={moneyRequestAmountInput} - style={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius]} + inputStyle={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius]} containerStyle={[styles.borderNone, styles.noLeftBorderRadius, styles.noRightBorderRadius]} /> {!!formError && ( From dcafc98e301f94dc36f3fda111bdc4ad857e6762 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 04:25:50 +0200 Subject: [PATCH 06/19] Refactor styles for MoneyRequestAmountInput --- src/components/TimePicker/TimePicker.tsx | 8 ++++---- src/pages/iou/steps/MoneyRequestAmountForm.tsx | 4 ++-- src/styles/index.ts | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/components/TimePicker/TimePicker.tsx b/src/components/TimePicker/TimePicker.tsx index c3cafeb08f37..6e36c0f824e2 100644 --- a/src/components/TimePicker/TimePicker.tsx +++ b/src/components/TimePicker/TimePicker.tsx @@ -458,8 +458,8 @@ function TimePicker({defaultValue = '', onSubmit, onInputChange = () => {}}: Tim onSelectionChange={(e) => { setSelectionHour(e.nativeEvent.selection); }} - style={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius, styles.timePickerInput]} - containerStyle={[styles.borderNone, styles.noLeftBorderRadius, styles.noRightBorderRadius]} + style={[styles.iouAmountTextInput, styles.timePickerInput]} + containerStyle={[styles.iouAmountTextInputContainer]} touchableInputWrapperStyle={styles.timePickerHeight100} selection={selectionHour} /> @@ -485,8 +485,8 @@ function TimePicker({defaultValue = '', onSubmit, onInputChange = () => {}}: Tim onSelectionChange={(e) => { setSelectionMinute(e.nativeEvent.selection); }} - style={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius, styles.timePickerInput]} - containerStyle={[styles.borderNone, styles.noLeftBorderRadius, styles.noRightBorderRadius]} + style={[styles.iouAmountTextInput, styles.timePickerInput]} + containerStyle={[styles.iouAmountTextInputContainer]} touchableInputWrapperStyle={styles.timePickerHeight100} selection={selectionMinute} /> diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index ff1fe84d65b1..08729ea1542c 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -239,8 +239,8 @@ function MoneyRequestAmountForm( textInput.current = ref; }} moneyRequestAmountInputRef={moneyRequestAmountInput} - inputStyle={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius]} - containerStyle={[styles.borderNone, styles.noLeftBorderRadius, styles.noRightBorderRadius]} + inputStyle={[styles.iouAmountTextInput]} + containerStyle={[styles.iouAmountTextInputContainer]} /> {!!formError && ( color: theme.heading, padding: 0, lineHeight: undefined, + paddingHorizontal: 0, + paddingVertical: 0, + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + borderTopRightRadius: 0, + borderBottomRightRadius: 0, }, 0, ), + iouAmountTextInputContainer: { + borderWidth: 0, + borderBottomWidth: 0, + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + }, + moneyRequestConfirmationAmount: { ...headlineFont, fontSize: variables.fontSizeh1, From 4e8cd5eff49af6a8611dd4e03c55043a67b6cf7b Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 04:41:06 +0200 Subject: [PATCH 07/19] Add prefixContainerStyle --- src/components/MoneyRequestAmountInput.tsx | 4 ++++ src/components/TextInput/BaseTextInput/index.native.tsx | 3 ++- src/components/TextInput/BaseTextInput/index.tsx | 3 ++- src/components/TextInput/BaseTextInput/types.ts | 3 +++ src/components/TextInputWithCurrencySymbol/types.ts | 3 +++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 8b8a48c36280..aca2f5b161a7 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -56,6 +56,9 @@ type MoneyRequestAmountInputProps = { /** Style for the prefix */ prefixStyle?: StyleProp; + + /** Style for the container prefix */ + prefixContainerStyle?: StyleProp; }; type Selection = { @@ -219,6 +222,7 @@ function MoneyRequestAmountInput( style={props.inputStyle} containerStyle={props.containerStyle} prefixStyle={props.prefixStyle} + prefixContainerStyle={props.prefixContainerStyle} /> ); } diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index 0f37570561e6..1417ccfe422a 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -58,6 +58,7 @@ function BaseTextInput( autoCorrect = true, prefixCharacter = '', prefixStyle = [], + prefixContainerStyle = [], inputID, isMarkdownEnabled = false, ...props @@ -313,7 +314,7 @@ function BaseTextInput( )} {!!prefixCharacter && ( - + )} {Boolean(prefixCharacter) && ( - + ; + + /** Style for the container prefix */ + prefixContainerStyle?: StyleProp; }; type BaseTextInputRef = HTMLFormElement | AnimatedTextInputRef; diff --git a/src/components/TextInputWithCurrencySymbol/types.ts b/src/components/TextInputWithCurrencySymbol/types.ts index f365b5fded04..92f097ce1e67 100644 --- a/src/components/TextInputWithCurrencySymbol/types.ts +++ b/src/components/TextInputWithCurrencySymbol/types.ts @@ -47,6 +47,9 @@ type TextInputWithCurrencySymbolProps = { /** Style for the prefix */ prefixStyle?: StyleProp; + + /** Style for the container prefix */ + prefixContainerStyle?: StyleProp; } & Pick; export default TextInputWithCurrencySymbolProps; From 2031e39b3cb23eac553ea66ca6df60d5984e1f78 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 17:53:29 +0200 Subject: [PATCH 08/19] Refactor TextInputs --- src/components/MoneyRequestAmountInput.tsx | 4 ++++ .../TextInput/BaseTextInput/index.native.tsx | 17 +++++------------ .../TextInput/BaseTextInput/index.tsx | 13 +++---------- .../TextInputWithCurrencySymbol/types.ts | 3 +++ src/styles/index.ts | 5 ++++- src/styles/utils/index.ts | 9 +++++++++ 6 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index aca2f5b161a7..36a771e14f8b 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -59,6 +59,9 @@ type MoneyRequestAmountInputProps = { /** Style for the container prefix */ prefixContainerStyle?: StyleProp; + + /** Style for the touchable input wrapper */ + touchableInputWrapperStyle: StyleProp; }; type Selection = { @@ -223,6 +226,7 @@ function MoneyRequestAmountInput( containerStyle={props.containerStyle} prefixStyle={props.prefixStyle} prefixContainerStyle={props.prefixContainerStyle} + touchableInputWrapperStyle={props.touchableInputWrapperStyle} /> ); } diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index 1417ccfe422a..a0acf66f2379 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -239,14 +239,6 @@ function BaseTextInput( setPasswordHidden((prevPasswordHidden) => !prevPasswordHidden); }, []); - // When adding a new prefix character, adjust this method to add expected character width. - // This is because character width isn't known before it's rendered to the screen, and once it's rendered, - // it's too late to calculate it's width because the change in padding would cause a visible jump. - // Some characters are wider than the others when rendered, e.g. '@' vs '#'. Chosen font-family and font-size - // also have an impact on the width of the character, but as long as there's only one font-family and one font-size, - // this method will produce reliable results. - const getCharacterPadding = (prefix: string): number => prefix.length * 10; - const hasLabel = Boolean(label?.length); const isReadOnly = inputProps.readOnly ?? inputProps.disabled; // Disabling this line for safeness as nullish coalescing works only if the value is undefined or null, and errorText can be an empty string @@ -271,16 +263,17 @@ function BaseTextInput( onPress={onPress} tabIndex={-1} accessibilityLabel={label} + // When autoGrowHeight is true we calculate the width for the textInput, so it will break lines properly + // or if multiline is not supplied we calculate the textinput height, using onLayout. + onLayout={onLayout} style={[ autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, variables.componentSizeLarge, typeof maxHeight === 'number' ? maxHeight : 0), !isMultiline && styles.componentHeightLarge, + styles.textInputContainerBorder, touchableInputWrapperStyle, ]} > !prevPasswordHidden); }, []); - // When adding a new prefix character, adjust this method to add expected character width. - // This is because character width isn't known before it's rendered to the screen, and once it's rendered, - // it's too late to calculate it's width because the change in padding would cause a visible jump. - // Some characters are wider than the others when rendered, e.g. '@' vs '#'. Chosen font-family and font-size - // also have an impact on the width of the character, but as long as there's only one font-family and one font-size, - // this method will produce reliable results. - const getCharacterPadding = (prefix: string): number => prefix.length * 10; - const hasLabel = Boolean(label?.length); const isReadOnly = inputProps.readOnly ?? inputProps.disabled; // Disabling this line for safeness as nullish coalescing works only if the value is undefined or null, and errorText can be an empty string @@ -291,16 +283,17 @@ function BaseTextInput( onPress={onPress} tabIndex={-1} accessibilityLabel={label} + onLayout={onLayout} style={[ autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, variables.componentSizeLarge, typeof maxHeight === 'number' ? maxHeight : 0), !isMultiline && styles.componentHeightLarge, + styles.textInputContainerBorder, touchableInputWrapperStyle, ]} > ; + + /** Customizes the touchable wrapper of the TextInput component */ + touchableInputWrapperStyle?: StyleProp; } & Pick; export default TextInputWithCurrencySymbolProps; diff --git a/src/styles/index.ts b/src/styles/index.ts index 496c1ac59348..16003a298434 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1087,9 +1087,12 @@ const styles = (theme: ThemeColors) => justifyContent: 'center', height: '100%', backgroundColor: 'transparent', + overflow: 'hidden', + }, + + textInputContainerBorder: { borderBottomWidth: 2, borderColor: theme.border, - overflow: 'hidden', }, textInputLabel: { diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index e9efc84e8807..b6956185d41f 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1566,6 +1566,15 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ ...(isDisabled && styles.cursorDisabled), ...(isDisabled && styles.buttonOpacityDisabled), }), + /** + * When adding a new prefix character, adjust this method to add expected character width. + * This is because character width isn't known before it's rendered to the screen, and once it's rendered, + * it's too late to calculate it's width because the change in padding would cause a visible jump. + * Some characters are wider than the others when rendered, e.g. '@' vs '#'. Chosen font-family and font-size + * also have an impact on the width of the character, but as long as there's only one font-family and one font-size, + * this method will produce reliable results. + */ + getCharacterPadding: (prefix: string): number => prefix.length * 10, // TODO: remove it when we'll implement the callback to handle this toggle in Expensify/Expensify#368335 getWorkspaceWorkflowsOfflineDescriptionStyle: (descriptionTextStyle: TextStyle | TextStyle[]): StyleProp => ({ From 85e809caeedfa04d74c8dcb4687532308f5da27a Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 18:01:43 +0200 Subject: [PATCH 09/19] Fix comments --- src/styles/index.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 16003a298434..f9e5b728072e 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2957,23 +2957,14 @@ const styles = (theme: ThemeColors) => color: theme.heading, padding: 0, lineHeight: undefined, - paddingHorizontal: 0, - paddingVertical: 0, - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, - borderTopRightRadius: 0, - borderBottomRightRadius: 0, + borderRadius: 0, }, 0, ), iouAmountTextInputContainer: { borderWidth: 0, - borderBottomWidth: 0, - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, - borderTopRightRadius: 0, - borderBottomRightRadius: 0, + borderRadius: 0, }, moneyRequestConfirmationAmount: { From 9880bb8cc83f601f886f468cdf4a06a8f781089a Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 19:23:41 +0200 Subject: [PATCH 10/19] Fix ts issue and refactor some code --- src/components/TextInput/BaseTextInput/index.native.tsx | 9 ++++++--- src/components/TextInput/BaseTextInput/index.tsx | 8 ++++++-- src/styles/index.ts | 1 + src/styles/utils/index.ts | 4 ++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index a0acf66f2379..fc7ec1626e5c 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -250,8 +250,6 @@ function BaseTextInput( styles.textInputContainer, textInputContainerStyles, autoGrow && StyleUtils.getWidthStyle(textInputWidth), - !hideFocusedState && isFocused && styles.borderColorFocus, - (!!hasError || !!errorText) && styles.borderColorDanger, autoGrowHeight && {scrollPaddingTop: typeof maxHeight === 'number' ? 2 * maxHeight : undefined}, ]); @@ -269,13 +267,18 @@ function BaseTextInput( style={[ autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, variables.componentSizeLarge, typeof maxHeight === 'number' ? maxHeight : 0), !isMultiline && styles.componentHeightLarge, - styles.textInputContainerBorder, + {borderColor: newTextInputContainerStyles.borderColor}, + {borderWidth: newTextInputContainerStyles.borderWidth}, + {borderBottomWidth: newTextInputContainerStyles.borderTopWidth}, touchableInputWrapperStyle, + !hideFocusedState && isFocused && styles.borderColorFocus, + (!!hasError || !!errorText) && styles.borderColorDanger, ]} > iouAmountTextInputContainer: { borderWidth: 0, + borderBottomWidth: 0, borderRadius: 0, }, diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index b6956185d41f..0f778536e9c9 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1223,7 +1223,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ ...styles.overflowHidden, // maxHeight is not of the input only but the of the whole input container // which also includes the top padding and bottom border - height: maxHeight - styles.textInputMultilineContainer.paddingTop - styles.textInputContainer.borderBottomWidth, + height: maxHeight - styles.textInputMultilineContainer.paddingTop - styles.textInputContainerBorder.borderBottomWidth, }; }, @@ -1395,7 +1395,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ /** * Return the height of magic code input container */ - getHeightOfMagicCodeInput: (): ViewStyle => ({height: styles.magicCodeInputContainer.minHeight - styles.textInputContainer.borderBottomWidth}), + getHeightOfMagicCodeInput: (): ViewStyle => ({height: styles.magicCodeInputContainer.minHeight - styles.textInputContainerBorder.borderBottomWidth}), /** * Generate fill color of an icon based on its state. From 26b7b002dec797b9e8662ee2da238a7075f980e2 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 19:30:59 +0200 Subject: [PATCH 11/19] Revert some changes related with padding and border --- src/styles/index.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 690bbe757253..e076af40bf3d 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2955,9 +2955,13 @@ const styles = (theme: ThemeColors) => ...headlineFont, fontSize: variables.iouAmountTextSize, color: theme.heading, - padding: 0, lineHeight: undefined, - borderRadius: 0, + paddingHorizontal: 0, + paddingVertical: 0, + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + borderTopRightRadius: 0, + borderBottomRightRadius: 0, }, 0, ), @@ -2965,7 +2969,10 @@ const styles = (theme: ThemeColors) => iouAmountTextInputContainer: { borderWidth: 0, borderBottomWidth: 0, - borderRadius: 0, + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + borderTopRightRadius: 0, + borderBottomRightRadius: 0, }, moneyRequestConfirmationAmount: { From 143dd891920eb0deaa6d8d875a248c7fa5b9a58e Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 21:54:27 +0200 Subject: [PATCH 12/19] Fix bug with AmountForm --- src/components/AmountForm.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 3c255bb5f482..59260a3cbd78 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -225,6 +225,8 @@ function AmountForm( }} onKeyPress={textInputKeyPress} isCurrencyPressable={isCurrencyPressable} + style={[styles.iouAmountTextInput]} + containerStyle={[styles.iouAmountTextInputContainer]} // eslint-disable-next-line react/jsx-props-no-spreading {...rest} /> From 78253574113dda2488cca2d76e59bcf5cb05a27d Mon Sep 17 00:00:00 2001 From: Yauheni Date: Thu, 18 Apr 2024 22:02:19 +0200 Subject: [PATCH 13/19] Add empty space for style utils --- src/styles/utils/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 0f778536e9c9..ecffb2ec19b4 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1566,6 +1566,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ ...(isDisabled && styles.cursorDisabled), ...(isDisabled && styles.buttonOpacityDisabled), }), + /** * When adding a new prefix character, adjust this method to add expected character width. * This is because character width isn't known before it's rendered to the screen, and once it's rendered, From 43b951b5e9c61af970bb7ee5dcfb830efc9ca4c0 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Fri, 19 Apr 2024 16:18:09 +0200 Subject: [PATCH 14/19] Refactor base text input --- .../TextInput/BaseTextInput/index.native.tsx | 14 +++++--------- .../TextInput/BaseTextInput/index.tsx | 17 ++++++----------- src/styles/index.ts | 2 ++ src/styles/utils/index.ts | 17 ++++++++++++++--- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index fc7ec1626e5c..25d91bbabc91 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -57,10 +57,10 @@ function BaseTextInput( multiline = false, autoCorrect = true, prefixCharacter = '', - prefixStyle = [], - prefixContainerStyle = [], inputID, isMarkdownEnabled = false, + prefixContainerStyle = [], + prefixStyle = [], ...props }: BaseTextInputProps, ref: ForwardedRef, @@ -250,6 +250,8 @@ function BaseTextInput( styles.textInputContainer, textInputContainerStyles, autoGrow && StyleUtils.getWidthStyle(textInputWidth), + !hideFocusedState && isFocused && styles.borderColorFocus, + (!!hasError || !!errorText) && styles.borderColorDanger, autoGrowHeight && {scrollPaddingTop: typeof maxHeight === 'number' ? 2 * maxHeight : undefined}, ]); @@ -260,25 +262,19 @@ function BaseTextInput( role={CONST.ROLE.PRESENTATION} onPress={onPress} tabIndex={-1} - accessibilityLabel={label} // When autoGrowHeight is true we calculate the width for the textInput, so it will break lines properly // or if multiline is not supplied we calculate the textinput height, using onLayout. onLayout={onLayout} + accessibilityLabel={label} style={[ autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, variables.componentSizeLarge, typeof maxHeight === 'number' ? maxHeight : 0), !isMultiline && styles.componentHeightLarge, - {borderColor: newTextInputContainerStyles.borderColor}, - {borderWidth: newTextInputContainerStyles.borderWidth}, - {borderBottomWidth: newTextInputContainerStyles.borderTopWidth}, touchableInputWrapperStyle, - !hideFocusedState && isFocused && styles.borderColorFocus, - (!!hasError || !!errorText) && styles.borderColorDanger, ]} > , @@ -247,6 +247,8 @@ function BaseTextInput( styles.textInputContainer, textInputContainerStyles, autoGrow && StyleUtils.getWidthStyle(textInputWidth), + !hideFocusedState && isFocused && styles.borderColorFocus, + (!!hasError || !!errorText) && styles.borderColorDanger, autoGrowHeight && {scrollPaddingTop: typeof maxHeight === 'number' ? 2 * maxHeight : undefined}, ]); const isMultiline = multiline || autoGrowHeight; @@ -281,25 +283,18 @@ function BaseTextInput( onPress={onPress} tabIndex={-1} accessibilityLabel={label} + // When autoGrowHeight is true we calculate the width for the textInput, so it will break lines properly + // or if multiline is not supplied we calculate the textinput height, using onLayout. onLayout={onLayout} style={[ autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, variables.componentSizeLarge, typeof maxHeight === 'number' ? maxHeight : 0), !isMultiline && styles.componentHeightLarge, - styles.textInputContainerBorder, - {borderColor: newTextInputContainerStyles.borderColor}, - {borderWidth: newTextInputContainerStyles.borderWidth}, - {borderBottomWidth: newTextInputContainerStyles.borderBottomWidth}, touchableInputWrapperStyle, - !hideFocusedState && isFocused && styles.borderColorFocus, - (!!hasError || !!errorText) && styles.borderColorDanger, ]} > height: '100%', backgroundColor: 'transparent', overflow: 'hidden', + borderBottomWidth: 2, + borderColor: theme.border, }, textInputContainerBorder: { diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index ecffb2ec19b4..3ac7e4690a20 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1223,7 +1223,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ ...styles.overflowHidden, // maxHeight is not of the input only but the of the whole input container // which also includes the top padding and bottom border - height: maxHeight - styles.textInputMultilineContainer.paddingTop - styles.textInputContainerBorder.borderBottomWidth, + height: maxHeight - styles.textInputMultilineContainer.paddingTop - styles.textInputContainer.borderBottomWidth, }; }, @@ -1395,7 +1395,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ /** * Return the height of magic code input container */ - getHeightOfMagicCodeInput: (): ViewStyle => ({height: styles.magicCodeInputContainer.minHeight - styles.textInputContainerBorder.borderBottomWidth}), + getHeightOfMagicCodeInput: (): ViewStyle => ({height: styles.magicCodeInputContainer.minHeight - styles.textInputContainer.borderBottomWidth}), /** * Generate fill color of an icon based on its state. @@ -1575,7 +1575,18 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ * also have an impact on the width of the character, but as long as there's only one font-family and one font-size, * this method will produce reliable results. */ - getCharacterPadding: (prefix: string): number => prefix.length * 10, + getCharacterPadding: (prefix: string): number => { + let padding = 0; + prefix.split('').forEach((char) => { + if (char === char.toUpperCase()) { + padding += 10; + } else { + padding += 8; + } + }); + + return padding; + }, // TODO: remove it when we'll implement the callback to handle this toggle in Expensify/Expensify#368335 getWorkspaceWorkflowsOfflineDescriptionStyle: (descriptionTextStyle: TextStyle | TextStyle[]): StyleProp => ({ From 1fc3799947532336a734936bccc81ea38d7c02e0 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Fri, 19 Apr 2024 20:58:00 +0200 Subject: [PATCH 15/19] Revert changes related with getCharacterPadding --- src/styles/utils/index.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 3ac7e4690a20..ee40746d76f6 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1575,18 +1575,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ * also have an impact on the width of the character, but as long as there's only one font-family and one font-size, * this method will produce reliable results. */ - getCharacterPadding: (prefix: string): number => { - let padding = 0; - prefix.split('').forEach((char) => { - if (char === char.toUpperCase()) { - padding += 10; - } else { - padding += 8; - } - }); - - return padding; - }, + getCharacterPadding: (prefix: string): number => prefix.length * 10, // TODO: remove it when we'll implement the callback to handle this toggle in Expensify/Expensify#368335 getWorkspaceWorkflowsOfflineDescriptionStyle: (descriptionTextStyle: TextStyle | TextStyle[]): StyleProp => ({ From c7b250fd0e0b454b3f42cc329e009e25ecbaaaf7 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 20 Apr 2024 00:58:51 +0200 Subject: [PATCH 16/19] Add useEffect inside MoneyRequestAmountInput --- src/components/MoneyRequestAmountInput.tsx | 14 ++++++++++++++ src/pages/iou/steps/MoneyRequestAmountForm.tsx | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 48f514922198..585325e506af 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -159,6 +159,20 @@ function MoneyRequestAmountInput( }, })); + useEffect(() => { + if (typeof amount !== 'number') { + return; + } + const frontendAmount = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; + setNewAmount(frontendAmount); + setSelection({ + start: frontendAmount.length, + end: frontendAmount.length, + }); + // we want to re-initialize the state only when the selected tab or amount changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [amount]); + // Modifies the amount to match the decimals for changed currency. useEffect(() => { // If the changed currency supports decimals, we can return diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index 898435c5a93a..7277a07960ff 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -158,9 +158,9 @@ function MoneyRequestAmountForm( return; } initializeAmount(amount); - // we want to re-initialize the state only when the selected tab or amount changes + // we want to re-initialize the state only when the selected tab // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedTab, amount]); + }, [selectedTab]); /** * Update amount with number or Backspace pressed for BigNumberPad. From c3ff30fb09c71fb88f1a24317ca96aa51ff40011 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 20 Apr 2024 01:20:14 +0200 Subject: [PATCH 17/19] Fix comments --- src/components/AmountTextInput.tsx | 2 +- src/components/MoneyRequestAmountInput.tsx | 6 +++--- src/components/TextInput/BaseTextInput/types.ts | 2 +- src/components/TextInputWithCurrencySymbol/types.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/AmountTextInput.tsx b/src/components/AmountTextInput.tsx index 58dd33bd86a6..e0a494ec6fb1 100644 --- a/src/components/AmountTextInput.tsx +++ b/src/components/AmountTextInput.tsx @@ -31,7 +31,7 @@ type AmountTextInputProps = { /** Function to call to handle key presses in the text input */ onKeyPress?: (event: NativeSyntheticEvent) => void; - /** Style for the container */ + /** Style for the TextInput container */ containerStyle?: StyleProp; } & Pick; diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 585325e506af..7a8b32919832 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -57,7 +57,7 @@ type MoneyRequestAmountInputProps = { /** Style for the prefix */ prefixStyle?: StyleProp; - /** Style for the container prefix */ + /** Style for the prefix container */ prefixContainerStyle?: StyleProp; /** Style for the touchable input wrapper */ @@ -160,7 +160,7 @@ function MoneyRequestAmountInput( })); useEffect(() => { - if (typeof amount !== 'number') { + if (!currency || typeof amount !== 'number') { return; } const frontendAmount = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; @@ -169,7 +169,7 @@ function MoneyRequestAmountInput( start: frontendAmount.length, end: frontendAmount.length, }); - // we want to re-initialize the state only when the selected tab or amount changes + // we want to re-initialize the state only when the amount changes // eslint-disable-next-line react-hooks/exhaustive-deps }, [amount]); diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index 7685e78fdf29..59b3d6c40fdd 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -111,7 +111,7 @@ type CustomBaseTextInputProps = { /** Style for the prefix */ prefixStyle?: StyleProp; - /** Style for the container prefix */ + /** Style for the prefix container */ prefixContainerStyle?: StyleProp; }; diff --git a/src/components/TextInputWithCurrencySymbol/types.ts b/src/components/TextInputWithCurrencySymbol/types.ts index db5b12fd7c9b..0b47ae0cfa32 100644 --- a/src/components/TextInputWithCurrencySymbol/types.ts +++ b/src/components/TextInputWithCurrencySymbol/types.ts @@ -48,7 +48,7 @@ type TextInputWithCurrencySymbolProps = { /** Style for the prefix */ prefixStyle?: StyleProp; - /** Style for the container prefix */ + /** Style for the prefix container */ prefixContainerStyle?: StyleProp; /** Customizes the touchable wrapper of the TextInput component */ From 06b51b16ea76c1d1179e25443efffcb745a33686 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 20 Apr 2024 15:28:59 +0200 Subject: [PATCH 18/19] Remove unnecessary spaces --- src/components/MoneyRequestAmountInput.tsx | 2 +- src/components/TextInput/BaseTextInput/types.ts | 2 +- src/components/TextInputWithCurrencySymbol/types.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 7a8b32919832..20d4c4531e46 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -57,7 +57,7 @@ type MoneyRequestAmountInputProps = { /** Style for the prefix */ prefixStyle?: StyleProp; - /** Style for the prefix container */ + /** Style for the prefix container */ prefixContainerStyle?: StyleProp; /** Style for the touchable input wrapper */ diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index 59b3d6c40fdd..8271b47c2a43 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -111,7 +111,7 @@ type CustomBaseTextInputProps = { /** Style for the prefix */ prefixStyle?: StyleProp; - /** Style for the prefix container */ + /** Style for the prefix container */ prefixContainerStyle?: StyleProp; }; diff --git a/src/components/TextInputWithCurrencySymbol/types.ts b/src/components/TextInputWithCurrencySymbol/types.ts index 0b47ae0cfa32..78d4158cf77f 100644 --- a/src/components/TextInputWithCurrencySymbol/types.ts +++ b/src/components/TextInputWithCurrencySymbol/types.ts @@ -48,7 +48,7 @@ type TextInputWithCurrencySymbolProps = { /** Style for the prefix */ prefixStyle?: StyleProp; - /** Style for the prefix container */ + /** Style for the prefix container */ prefixContainerStyle?: StyleProp; /** Customizes the touchable wrapper of the TextInput component */ From a0176b5e6fbbd58c754a9c9dcff1f10d4106dfc9 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 22 Apr 2024 22:57:29 +0200 Subject: [PATCH 19/19] Update function for changing currentAmount after update amount --- src/components/MoneyRequestAmountInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 20d4c4531e46..fcc156db4a96 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -164,7 +164,7 @@ function MoneyRequestAmountInput( return; } const frontendAmount = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; - setNewAmount(frontendAmount); + setCurrentAmount(frontendAmount); setSelection({ start: frontendAmount.length, end: frontendAmount.length,