From f41a929976714fb74214cce7567857c53d410a21 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Thu, 17 Aug 2023 15:33:57 -0700 Subject: [PATCH 001/284] Improve GSI errors Errors that didn't have specific handling weren't being logged properly. Update logging and print all status codes so we can also see errors we aren't specifically handling. --- .../SignInButtons/GoogleSignIn/index.native.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/SignInButtons/GoogleSignIn/index.native.js b/src/components/SignInButtons/GoogleSignIn/index.native.js index 9e638f0723cf..099fbfde22fd 100644 --- a/src/components/SignInButtons/GoogleSignIn/index.native.js +++ b/src/components/SignInButtons/GoogleSignIn/index.native.js @@ -25,14 +25,18 @@ function googleSignInRequest() { .then((response) => response.idToken) .then((token) => Session.beginGoogleSignIn(token)) .catch((error) => { + // Handle unexpected error shape + if (error === undefined || error.code === undefined) { + Log.alert(`[Google Sign In] Google sign in failed: ${error}`); + } + /** The logged code is useful for debugging any new errors that are not specifically handled. To decode, see: + - The common status codes documentation: https://developers.google.com/android/reference/com/google/android/gms/common/api/CommonStatusCodes + - The Google Sign In codes documentation: https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInStatusCodes + */ if (error.code === statusCodes.SIGN_IN_CANCELLED) { - Log.alert('[Google Sign In] Google sign in cancelled', true, {error}); - } else if (error.code === statusCodes.IN_PROGRESS) { - Log.alert('[Google Sign In] Google sign in already in progress', true, {error}); - } else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) { - Log.alert('[Google Sign In] Google play services not available or outdated', true, {error}); + Log.info('[Google Sign In] Google Sign In cancelled'); } else { - Log.alert('[Google Sign In] Unknown Google sign in error', true, {error}); + Log.alert(`[Google Sign In] Error Code: ${error.code}. ${error.message}`, {}, false); } }); } From ba8a3c100bdd759d7ac318a340b1893055061f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 4 Sep 2023 19:01:36 +0100 Subject: [PATCH 002/284] First version of styles refactor --- src/styles/addOutlineWidth/types.ts | 3 +- src/styles/codeStyles/types.ts | 9 +- src/styles/editedLabelStyles/index.ts | 8 +- src/styles/editedLabelStyles/types.ts | 3 +- src/styles/getModalStyles.ts | 9 +- .../getNavigationModalCardStyles/index.ts | 6 +- .../index.website.ts | 6 +- .../getNavigationModalCardStyles/types.ts | 3 +- .../getReportActionContextMenuStyles.ts | 11 +- src/styles/italic/types.ts | 3 +- src/styles/optionRowStyles/types.ts | 3 +- src/styles/overflowXHidden/types.ts | 4 +- src/styles/pointerEventsAuto/types.ts | 4 +- src/styles/pointerEventsNone/types.ts | 4 +- src/styles/utilities/cursor/types.ts | 4 +- src/styles/utilities/overflow.ts | 4 +- src/styles/utilities/overflowAuto/index.ts | 5 +- src/styles/utilities/overflowAuto/types.ts | 4 +- src/styles/utilities/textUnderline/types.ts | 6 +- src/styles/utilities/userSelect/types.ts | 4 +- src/styles/utilities/visibility/types.ts | 4 +- src/styles/utilities/whiteSpace/types.ts | 4 +- src/styles/utilities/wordBreak/types.ts | 4 +- src/types/modules/react-native.d.ts | 148 ++++++++++++++++++ 24 files changed, 210 insertions(+), 53 deletions(-) create mode 100644 src/types/modules/react-native.d.ts diff --git a/src/styles/addOutlineWidth/types.ts b/src/styles/addOutlineWidth/types.ts index 422d135ef759..fe5d1f5719b2 100644 --- a/src/styles/addOutlineWidth/types.ts +++ b/src/styles/addOutlineWidth/types.ts @@ -1,6 +1,5 @@ -import {CSSProperties} from 'react'; import {TextStyle} from 'react-native'; -type AddOutlineWidth = (obj: TextStyle | CSSProperties, val?: number, error?: boolean) => TextStyle | CSSProperties; +type AddOutlineWidth = (obj: TextStyle, val?: number, error?: boolean) => TextStyle; export default AddOutlineWidth; diff --git a/src/styles/codeStyles/types.ts b/src/styles/codeStyles/types.ts index 7397b3c6543c..25e35f492089 100644 --- a/src/styles/codeStyles/types.ts +++ b/src/styles/codeStyles/types.ts @@ -1,8 +1,7 @@ -import {CSSProperties} from 'react'; import {TextStyle, ViewStyle} from 'react-native'; -type CodeWordWrapperStyles = ViewStyle | CSSProperties; -type CodeWordStyles = TextStyle | CSSProperties; -type CodeTextStyles = TextStyle | CSSProperties; +type CodeWordWrapperStyles = ViewStyle; +type CodeWordStyles = TextStyle; +type CodeTextStyles = TextStyle; -export type {CodeWordWrapperStyles, CodeWordStyles, CodeTextStyles}; +export type {CodeTextStyles, CodeWordStyles, CodeWordWrapperStyles}; diff --git a/src/styles/editedLabelStyles/index.ts b/src/styles/editedLabelStyles/index.ts index 6e6164810b52..b3962e507757 100644 --- a/src/styles/editedLabelStyles/index.ts +++ b/src/styles/editedLabelStyles/index.ts @@ -1,7 +1,11 @@ -import EditedLabelStyles from './types'; +import {TextStyle} from 'react-native'; import display from '../utilities/display'; import flex from '../utilities/flex'; +import EditedLabelStyles from './types'; -const editedLabelStyles: EditedLabelStyles = {...display.dInlineFlex, ...flex.alignItemsBaseline}; +const editedLabelStyles: EditedLabelStyles = { + ...(display.dInlineFlex as TextStyle), + ...flex.alignItemsBaseline, +}; export default editedLabelStyles; diff --git a/src/styles/editedLabelStyles/types.ts b/src/styles/editedLabelStyles/types.ts index 8d96c9216ed2..20bcc8c55f15 100644 --- a/src/styles/editedLabelStyles/types.ts +++ b/src/styles/editedLabelStyles/types.ts @@ -1,6 +1,5 @@ -import {CSSProperties} from 'react'; import {TextStyle} from 'react-native'; -type EditedLabelStyles = CSSProperties | TextStyle; +type EditedLabelStyles = TextStyle; export default EditedLabelStyles; diff --git a/src/styles/getModalStyles.ts b/src/styles/getModalStyles.ts index 32e6cf6f98e7..ceea37ddb85b 100644 --- a/src/styles/getModalStyles.ts +++ b/src/styles/getModalStyles.ts @@ -1,11 +1,10 @@ -import {CSSProperties} from 'react'; import {ViewStyle} from 'react-native'; -import {ValueOf} from 'type-fest'; import {ModalProps} from 'react-native-modal'; +import {ValueOf} from 'type-fest'; import CONST from '../CONST'; -import variables from './variables'; -import themeColors from './themes/default'; import styles from './styles'; +import themeColors from './themes/default'; +import variables from './variables'; function getCenteredModalStyles(windowWidth: number, isSmallScreenWidth: boolean, isFullScreenWhenSmall = false): ViewStyle { return { @@ -26,7 +25,7 @@ type WindowDimensions = { type GetModalStyles = { modalStyle: ViewStyle; - modalContainerStyle: ViewStyle | Pick; + modalContainerStyle: ViewStyle; swipeDirection: ModalProps['swipeDirection']; animationIn: ModalProps['animationIn']; animationOut: ModalProps['animationOut']; diff --git a/src/styles/getNavigationModalCardStyles/index.ts b/src/styles/getNavigationModalCardStyles/index.ts index 8e2bffc1ecfc..d3cf49c6c89c 100644 --- a/src/styles/getNavigationModalCardStyles/index.ts +++ b/src/styles/getNavigationModalCardStyles/index.ts @@ -1,3 +1,7 @@ -export default () => ({ +import GetNavigationModalCardStyles from './types'; + +const getNavigationModalCardStyles: GetNavigationModalCardStyles = () => ({ height: '100%', }); + +export default getNavigationModalCardStyles; diff --git a/src/styles/getNavigationModalCardStyles/index.website.ts b/src/styles/getNavigationModalCardStyles/index.website.ts index 9879e4c1567b..ea76825e5bba 100644 --- a/src/styles/getNavigationModalCardStyles/index.website.ts +++ b/src/styles/getNavigationModalCardStyles/index.website.ts @@ -1,3 +1,4 @@ +import {ViewStyle} from 'react-native'; import GetNavigationModalCardStyles from './types'; const getNavigationModalCardStyles: GetNavigationModalCardStyles = () => ({ @@ -8,7 +9,10 @@ const getNavigationModalCardStyles: GetNavigationModalCardStyles = () => ({ // https://github.com/Expensify/App/issues/20709 width: '100%', height: '100%', - position: 'fixed', + + // NOTE: asserting "fixed" TS type to a valid type, because isn't possible + // to augment "position". + position: 'fixed' as ViewStyle['position'], }); export default getNavigationModalCardStyles; diff --git a/src/styles/getNavigationModalCardStyles/types.ts b/src/styles/getNavigationModalCardStyles/types.ts index 504b659c87b7..68453dc3c3de 100644 --- a/src/styles/getNavigationModalCardStyles/types.ts +++ b/src/styles/getNavigationModalCardStyles/types.ts @@ -1,9 +1,8 @@ -import {CSSProperties} from 'react'; import {ViewStyle} from 'react-native'; import {Merge} from 'type-fest'; type GetNavigationModalCardStylesParams = {isSmallScreenWidth: number}; -type GetNavigationModalCardStyles = (params: GetNavigationModalCardStylesParams) => Merge>; +type GetNavigationModalCardStyles = (params: GetNavigationModalCardStylesParams) => Merge>; export default GetNavigationModalCardStyles; diff --git a/src/styles/getReportActionContextMenuStyles.ts b/src/styles/getReportActionContextMenuStyles.ts index 17f0828ab80c..792213f2b54b 100644 --- a/src/styles/getReportActionContextMenuStyles.ts +++ b/src/styles/getReportActionContextMenuStyles.ts @@ -1,12 +1,11 @@ -import {CSSProperties} from 'react'; import {ViewStyle} from 'react-native'; import styles from './styles'; -import variables from './variables'; import themeColors from './themes/default'; +import variables from './variables'; -type StylesArray = Array; +type StylesArray = ViewStyle[]; -const defaultWrapperStyle: ViewStyle | CSSProperties = { +const defaultWrapperStyle: ViewStyle = { backgroundColor: themeColors.componentBG, }; @@ -18,7 +17,9 @@ const miniWrapperStyle: StylesArray = [ borderWidth: 1, borderColor: themeColors.border, // In Safari, when welcome messages use a code block (triple backticks), they would overlap the context menu below when there is no scrollbar without the transform style. - transform: 'translateZ(0)', + // NOTE: asserting "transform" TS type to a valid type, because isn't possible + // to augment "transform". + transform: 'translateZ(0)' as unknown as ViewStyle['transform'], }, ]; diff --git a/src/styles/italic/types.ts b/src/styles/italic/types.ts index 61e0328e52b6..0935c5844bb3 100644 --- a/src/styles/italic/types.ts +++ b/src/styles/italic/types.ts @@ -1,6 +1,5 @@ -import {CSSProperties} from 'react'; import {TextStyle} from 'react-native'; -type ItalicStyles = (TextStyle | CSSProperties)['fontStyle']; +type ItalicStyles = TextStyle['fontStyle']; export default ItalicStyles; diff --git a/src/styles/optionRowStyles/types.ts b/src/styles/optionRowStyles/types.ts index c08174470701..f645c6038397 100644 --- a/src/styles/optionRowStyles/types.ts +++ b/src/styles/optionRowStyles/types.ts @@ -1,6 +1,5 @@ -import {CSSProperties} from 'react'; import {ViewStyle} from 'react-native'; -type OptionRowStyles = CSSProperties | ViewStyle; +type OptionRowStyles = ViewStyle; export default OptionRowStyles; diff --git a/src/styles/overflowXHidden/types.ts b/src/styles/overflowXHidden/types.ts index 7ac572f0e651..0f13ba552c0c 100644 --- a/src/styles/overflowXHidden/types.ts +++ b/src/styles/overflowXHidden/types.ts @@ -1,5 +1,5 @@ -import {CSSProperties} from 'react'; +import {ViewStyle} from 'react-native'; -type OverflowXHiddenStyles = Partial>; +type OverflowXHiddenStyles = Pick; export default OverflowXHiddenStyles; diff --git a/src/styles/pointerEventsAuto/types.ts b/src/styles/pointerEventsAuto/types.ts index 7c9f0164c936..0ecbc851e000 100644 --- a/src/styles/pointerEventsAuto/types.ts +++ b/src/styles/pointerEventsAuto/types.ts @@ -1,5 +1,5 @@ -import {CSSProperties} from 'react'; +import {ViewStyle} from 'react-native'; -type PointerEventsAutoStyles = Partial>; +type PointerEventsAutoStyles = Pick; export default PointerEventsAutoStyles; diff --git a/src/styles/pointerEventsNone/types.ts b/src/styles/pointerEventsNone/types.ts index f2f5a0d88a41..766ac3f94349 100644 --- a/src/styles/pointerEventsNone/types.ts +++ b/src/styles/pointerEventsNone/types.ts @@ -1,5 +1,5 @@ -import {CSSProperties} from 'react'; +import {ViewStyle} from 'react-native'; -type PointerEventsNone = Partial>; +type PointerEventsNone = Pick; export default PointerEventsNone; diff --git a/src/styles/utilities/cursor/types.ts b/src/styles/utilities/cursor/types.ts index 9ca20c5ae123..98d661491c4b 100644 --- a/src/styles/utilities/cursor/types.ts +++ b/src/styles/utilities/cursor/types.ts @@ -1,4 +1,4 @@ -import {CSSProperties} from 'react'; +import {TextStyle, ViewStyle} from 'react-native'; type CursorStylesKeys = | 'cursorDefault' @@ -13,6 +13,6 @@ type CursorStylesKeys = | 'cursorInitial' | 'cursorText'; -type CursorStyles = Record>>; +type CursorStyles = Record>; export default CursorStyles; diff --git a/src/styles/utilities/overflow.ts b/src/styles/utilities/overflow.ts index 3961758e40bf..ac28df1c944f 100644 --- a/src/styles/utilities/overflow.ts +++ b/src/styles/utilities/overflow.ts @@ -1,4 +1,4 @@ -import {CSSProperties} from 'react'; +import {ViewStyle} from 'react-native'; import overflowAuto from './overflowAuto'; /** @@ -24,4 +24,4 @@ export default { }, overflowAuto, -} satisfies Record; +} satisfies Record; diff --git a/src/styles/utilities/overflowAuto/index.ts b/src/styles/utilities/overflowAuto/index.ts index 0eb19068738f..f958d8000c12 100644 --- a/src/styles/utilities/overflowAuto/index.ts +++ b/src/styles/utilities/overflowAuto/index.ts @@ -1,7 +1,10 @@ +import { ViewStyle } from 'react-native'; import OverflowAutoStyles from './types'; const overflowAuto: OverflowAutoStyles = { - overflow: 'auto', + // NOTE: asserting "auto" TS type to a valid type, because isn't possible + // to augment "overflow". + overflow: 'auto' as ViewStyle['overflow'], }; export default overflowAuto; diff --git a/src/styles/utilities/overflowAuto/types.ts b/src/styles/utilities/overflowAuto/types.ts index faba7c2cbdb8..da7548d49e7b 100644 --- a/src/styles/utilities/overflowAuto/types.ts +++ b/src/styles/utilities/overflowAuto/types.ts @@ -1,5 +1,5 @@ -import {CSSProperties} from 'react'; +import {ViewStyle} from 'react-native'; -type OverflowAutoStyles = Pick; +type OverflowAutoStyles = Pick; export default OverflowAutoStyles; diff --git a/src/styles/utilities/textUnderline/types.ts b/src/styles/utilities/textUnderline/types.ts index ecc09ed0fe09..f71d2bfdaa9a 100644 --- a/src/styles/utilities/textUnderline/types.ts +++ b/src/styles/utilities/textUnderline/types.ts @@ -1,8 +1,8 @@ -import {CSSProperties} from 'react'; +import {TextStyle} from 'react-native'; type TextUnderlineStyles = { - textUnderlinePositionUnder: Pick; - textDecorationSkipInkNone: Pick; + textUnderlinePositionUnder: Pick; + textDecorationSkipInkNone: Pick; }; export default TextUnderlineStyles; diff --git a/src/styles/utilities/userSelect/types.ts b/src/styles/utilities/userSelect/types.ts index 67a8c9c7b9b6..a177bac5a3e7 100644 --- a/src/styles/utilities/userSelect/types.ts +++ b/src/styles/utilities/userSelect/types.ts @@ -1,5 +1,5 @@ -import {CSSProperties} from 'react'; +import {TextStyle} from 'react-native'; -type UserSelectStyles = Record<'userSelectText' | 'userSelectNone', Partial>>; +type UserSelectStyles = Record<'userSelectText' | 'userSelectNone', Pick>; export default UserSelectStyles; diff --git a/src/styles/utilities/visibility/types.ts b/src/styles/utilities/visibility/types.ts index 9dab3d7c752e..872e35195edd 100644 --- a/src/styles/utilities/visibility/types.ts +++ b/src/styles/utilities/visibility/types.ts @@ -1,5 +1,5 @@ -import {CSSProperties} from 'react'; +import {TextStyle, ViewStyle} from 'react-native'; -type VisibilityStyles = Record<'visible' | 'hidden', Partial>>; +type VisibilityStyles = Record<'visible' | 'hidden', Pick>; export default VisibilityStyles; diff --git a/src/styles/utilities/whiteSpace/types.ts b/src/styles/utilities/whiteSpace/types.ts index c42dc14080b1..b10671f04977 100644 --- a/src/styles/utilities/whiteSpace/types.ts +++ b/src/styles/utilities/whiteSpace/types.ts @@ -1,5 +1,5 @@ -import {CSSProperties} from 'react'; +import {TextStyle} from 'react-native'; -type WhiteSpaceStyles = Record<'noWrap' | 'preWrap' | 'pre', Partial>>; +type WhiteSpaceStyles = Record<'noWrap' | 'preWrap' | 'pre', Pick>; export default WhiteSpaceStyles; diff --git a/src/styles/utilities/wordBreak/types.ts b/src/styles/utilities/wordBreak/types.ts index 003a5ca1c869..0ed520ae119d 100644 --- a/src/styles/utilities/wordBreak/types.ts +++ b/src/styles/utilities/wordBreak/types.ts @@ -1,5 +1,5 @@ -import {CSSProperties} from 'react'; +import {TextStyle} from 'react-native'; -type WordBreakStyles = Record<'breakWord' | 'breakAll', Partial>>; +type WordBreakStyles = Record<'breakWord' | 'breakAll', Pick>; export default WordBreakStyles; diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts new file mode 100644 index 000000000000..1fc0617331ce --- /dev/null +++ b/src/types/modules/react-native.d.ts @@ -0,0 +1,148 @@ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ +import {CSSProperties} from 'react'; +import 'react-native'; +import {Merge, MergeDeep, OverrideProperties} from 'type-fest'; + +declare module 'react-native' { + // interface ViewStyle extends CSSProperties {} + // interface ViewProps { + // style?: number; // Doesn't make any difference + // onLayout?: number; + // href?: string; // It appear new prop as suggestion + // focusable?: string; + // } + type NumberOrString = number | string; + + type AnimationDirection = 'alternate' | 'alternate-reverse' | 'normal' | 'reverse'; + type AnimationFillMode = 'none' | 'forwards' | 'backwards' | 'both'; + type AnimationIterationCount = number | 'infinite'; + type AnimationKeyframes = string | object; + type AnimationPlayState = 'paused' | 'running'; + + type AnimationStyles = { + animationDelay?: string | string[]; + animationDirection?: AnimationDirection | AnimationDirection[]; + animationDuration?: string | string[]; + animationFillMode?: AnimationFillMode | AnimationFillMode[]; + animationIterationCount?: AnimationIterationCount | AnimationIterationCount[]; + animationKeyframes?: AnimationKeyframes | AnimationKeyframes[]; + animationPlayState?: AnimationPlayState | AnimationPlayState[]; + animationTimingFunction?: string | string[]; + transitionDelay?: string | string[]; + transitionDuration?: string | string[]; + transitionProperty?: string | string[]; + transitionTimingFunction?: string | string[]; + }; + + /** + * Image + */ + interface WebImageProps { + draggable?: boolean; + } + interface ImageProps extends WebImageProps {} + + /** + * Pressable + */ + interface WebPressableProps { + delayPressIn?: number; + delayPressOut?: number; + onPressMove?: null | ((event: GestureResponderEvent) => void); + onPressEnd?: null | ((event: GestureResponderEvent) => void); + } + interface WebPressableStateCallbackType { + readonly focused: boolean; + readonly hovered: boolean; + readonly pressed: boolean; + } + interface PressableProps extends WebPressableProps {} + interface PressableStateCallbackType extends WebPressableStateCallbackType {} + + /** + * Text + */ + interface WebTextProps { + dataSet?: Record; + dir?: 'auto' | 'ltr' | 'rtl'; + href?: string; + hrefAttrs?: { + download?: boolean; + rel?: string; + target?: string; + }; + lang?: string; + } + interface TextProps extends WebTextProps {} + + /** + * TextInput + */ + interface WebTextInputProps { + dataSet?: Record; + dir?: 'auto' | 'ltr' | 'rtl'; + lang?: string; + disabled?: boolean; + } + interface TextInputProps extends WebTextInputProps {} + + /** + * View + */ + interface WebViewProps { + dataSet?: Record; + dir?: 'ltr' | 'rtl'; + href?: string; + hrefAttrs?: { + download?: boolean; + rel?: string; + target?: string; + }; + tabIndex?: 0 | -1; + } + interface ViewProps extends WebViewProps {} + + interface WebStyle { + wordBreak?: CSSProperties['wordBreak']; + whiteSpace?: CSSProperties['whiteSpace']; + visibility?: CSSProperties['visibility']; + userSelect?: CSSProperties['userSelect']; + WebkitUserSelect?: CSSProperties['WebkitUserSelect']; + textUnderlinePosition?: CSSProperties['textUnderlinePosition']; + textDecorationSkipInk?: CSSProperties['textDecorationSkipInk']; + cursor?: CSSProperties['cursor']; + outlineWidth?: CSSProperties['outlineWidth']; + outlineStyle?: CSSProperties['outlineStyle']; + boxShadow?: CSSProperties['boxShadow']; + } + + interface WebViewStyle { + overscrollBehaviorX?: CSSProperties['overscrollBehaviorX']; + overflowX?: CSSProperties['overflowX']; + } + + // interface FlexStyle { + // overflow?: string; + // } + interface ViewStyle extends WebStyle, WebViewStyle {} + interface TextStyle extends WebStyle {} + interface ImageStyle extends WebStyle {} +} + +interface A { + overflow?: string; +} + +interface B { + overflow?: number; +} + +declare const a: A; + +// interface A extends B {} + +interface C extends Omit { + overflow?: number; +} +declare const a: C; From b1ee74b72cc5bad57fe70dc1be9740933ecefc25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 5 Sep 2023 16:12:01 +0100 Subject: [PATCH 003/284] Add more typings --- src/types/modules/react-native.d.ts | 197 +++++++++++++++++++++------- 1 file changed, 148 insertions(+), 49 deletions(-) diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index 1fc0617331ce..498529cf124a 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -1,40 +1,159 @@ +/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/no-empty-interface */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import {CSSProperties} from 'react'; +import {CSSProperties, FocusEventHandler, KeyboardEventHandler, MouseEventHandler} from 'react'; import 'react-native'; -import {Merge, MergeDeep, OverrideProperties} from 'type-fest'; declare module 'react-native' { - // interface ViewStyle extends CSSProperties {} - // interface ViewProps { - // style?: number; // Doesn't make any difference - // onLayout?: number; - // href?: string; // It appear new prop as suggestion - // focusable?: string; - // } + // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js type NumberOrString = number | string; + type OverscrollBehaviorValue = 'auto' | 'contain' | 'none'; + type idRef = string; + type idRefList = idRef | idRef[]; + + // https://necolas.github.io/react-native-web/docs/accessibility/#accessibility-props-api + // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js + type AccessibilityProps = { + 'aria-activedescendant'?: idRef; + 'aria-atomic'?: boolean; + 'aria-autocomplete'?: 'none' | 'list' | 'inline' | 'both'; + 'aria-busy'?: boolean; + 'aria-checked'?: boolean | 'mixed'; + 'aria-colcount'?: number; + 'aria-colindex'?: number; + 'aria-colspan'?: number; + 'aria-controls'?: idRef; + 'aria-current'?: boolean | 'page' | 'step' | 'location' | 'date' | 'time'; + 'aria-describedby'?: idRef; + 'aria-details'?: idRef; + 'aria-disabled'?: boolean; + 'aria-errormessage'?: idRef; + 'aria-expanded'?: boolean; + 'aria-flowto'?: idRef; + 'aria-haspopup'?: 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree' | false; + 'aria-hidden'?: boolean; + 'aria-invalid'?: boolean; + 'aria-keyshortcuts'?: string[]; + 'aria-label'?: string; + 'aria-labelledby'?: idRef; + 'aria-level'?: number; + 'aria-live'?: 'assertive' | 'none' | 'polite'; + 'aria-modal'?: boolean; + 'aria-multiline'?: boolean; + 'aria-multiselectable'?: boolean; + 'aria-orientation'?: 'horizontal' | 'vertical'; + 'aria-owns'?: idRef; + 'aria-placeholder'?: string; + 'aria-posinset'?: number; + 'aria-pressed'?: boolean | 'mixed'; + 'aria-readonly'?: boolean; + 'aria-required'?: boolean; + 'aria-roledescription'?: string; + 'aria-rowcount'?: number; + 'aria-rowindex'?: number; + 'aria-rowspan'?: number; + 'aria-selected'?: boolean; + 'aria-setsize'?: number; + 'aria-sort'?: 'ascending' | 'descending' | 'none' | 'other'; + 'aria-valuemax'?: number; + 'aria-valuemin'?: number; + 'aria-valuenow'?: number; + 'aria-valuetext'?: string; + role?: string; + + // @deprecated + accessibilityActiveDescendant?: idRef; + accessibilityAtomic?: boolean; + accessibilityAutoComplete?: 'none' | 'list' | 'inline' | 'both'; + accessibilityBusy?: boolean; + accessibilityChecked?: boolean | 'mixed'; + accessibilityColumnCount?: number; + accessibilityColumnIndex?: number; + accessibilityColumnSpan?: number; + accessibilityControls?: idRefList; + accessibilityCurrent?: boolean | 'page' | 'step' | 'location' | 'date' | 'time'; + accessibilityDescribedBy?: idRefList; + accessibilityDetails?: idRef; + accessibilityDisabled?: boolean; + accessibilityErrorMessage?: idRef; + accessibilityExpanded?: boolean; + accessibilityFlowTo?: idRefList; + accessibilityHasPopup?: 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree' | false; + accessibilityHidden?: boolean; + accessibilityInvalid?: boolean; + accessibilityKeyShortcuts?: string[]; + accessibilityLabel?: string; + accessibilityLabelledBy?: idRefList; + accessibilityLevel?: number; + accessibilityLiveRegion?: 'assertive' | 'none' | 'polite'; + accessibilityModal?: boolean; + accessibilityMultiline?: boolean; + accessibilityMultiSelectable?: boolean; + accessibilityOrientation?: 'horizontal' | 'vertical'; + accessibilityOwns?: idRefList; + accessibilityPlaceholder?: string; + accessibilityPosInSet?: number; + accessibilityPressed?: boolean | 'mixed'; + accessibilityReadOnly?: boolean; + accessibilityRequired?: boolean; + accessibilityRole?: string; + accessibilityRoleDescription?: string; + accessibilityRowCount?: number; + accessibilityRowIndex?: number; + accessibilityRowSpan?: number; + accessibilitySelected?: boolean; + accessibilitySetSize?: number; + accessibilitySort?: 'ascending' | 'descending' | 'none' | 'other'; + accessibilityValueMax?: number; + accessibilityValueMin?: number; + accessibilityValueNow?: number; + accessibilityValueText?: string; + }; + + // https://necolas.github.io/react-native-web/docs/interactions/#pointerevent-props-api + // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js + // Extracted from @types/react, index.d.ts + type PointerProps = { + onClick?: MouseEventHandler; + onClickCapture?: MouseEventHandler; + onContextMenu?: MouseEventHandler; + }; - type AnimationDirection = 'alternate' | 'alternate-reverse' | 'normal' | 'reverse'; - type AnimationFillMode = 'none' | 'forwards' | 'backwards' | 'both'; - type AnimationIterationCount = number | 'infinite'; - type AnimationKeyframes = string | object; - type AnimationPlayState = 'paused' | 'running'; - - type AnimationStyles = { - animationDelay?: string | string[]; - animationDirection?: AnimationDirection | AnimationDirection[]; - animationDuration?: string | string[]; - animationFillMode?: AnimationFillMode | AnimationFillMode[]; - animationIterationCount?: AnimationIterationCount | AnimationIterationCount[]; - animationKeyframes?: AnimationKeyframes | AnimationKeyframes[]; - animationPlayState?: AnimationPlayState | AnimationPlayState[]; - animationTimingFunction?: string | string[]; - transitionDelay?: string | string[]; - transitionDuration?: string | string[]; - transitionProperty?: string | string[]; - transitionTimingFunction?: string | string[]; + // TODO: Confirm + type FocusProps = { + onBlur?: FocusEventHandler; + onFocus?: FocusEventHandler; }; + // TODO: Confirm + type KeyboardProps = { + onKeyDown?: KeyboardEventHandler; + onKeyDownCapture?: KeyboardEventHandler; + onKeyUp?: KeyboardEventHandler; + onKeyUpCapture?: KeyboardEventHandler; + }; + + // type AnimationDirection = 'alternate' | 'alternate-reverse' | 'normal' | 'reverse'; + // type AnimationFillMode = 'none' | 'forwards' | 'backwards' | 'both'; + // type AnimationIterationCount = number | 'infinite'; + // type AnimationKeyframes = string | object; + // type AnimationPlayState = 'paused' | 'running'; + + // type AnimationStyles = { + // animationDelay?: string | string[]; + // animationDirection?: AnimationDirection | AnimationDirection[]; + // animationDuration?: string | string[]; + // animationFillMode?: AnimationFillMode | AnimationFillMode[]; + // animationIterationCount?: AnimationIterationCount | AnimationIterationCount[]; + // animationKeyframes?: AnimationKeyframes | AnimationKeyframes[]; + // animationPlayState?: AnimationPlayState | AnimationPlayState[]; + // animationTimingFunction?: string | string[]; + // transitionDelay?: string | string[]; + // transitionDuration?: string | string[]; + // transitionProperty?: string | string[]; + // transitionTimingFunction?: string | string[]; + // }; + /** * Image */ @@ -90,7 +209,7 @@ declare module 'react-native' { /** * View */ - interface WebViewProps { + interface WebViewProps extends AccessibilityProps, PointerProps, FocusProps, KeyboardProps { dataSet?: Record; dir?: 'ltr' | 'rtl'; href?: string; @@ -122,27 +241,7 @@ declare module 'react-native' { overflowX?: CSSProperties['overflowX']; } - // interface FlexStyle { - // overflow?: string; - // } interface ViewStyle extends WebStyle, WebViewStyle {} interface TextStyle extends WebStyle {} interface ImageStyle extends WebStyle {} } - -interface A { - overflow?: string; -} - -interface B { - overflow?: number; -} - -declare const a: A; - -// interface A extends B {} - -interface C extends Omit { - overflow?: number; -} -declare const a: C; From fabe55fdbbec43e1b8a872d538afb42949277c3b Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 11 Sep 2023 15:48:37 -0700 Subject: [PATCH 004/284] WIP render an eReceipt for distance requests --- .../Attachments/AttachmentView/index.js | 20 +++++++ src/components/EReceipt.js | 54 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/components/EReceipt.js diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 47353d915060..584827f499c6 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -17,6 +17,11 @@ import AttachmentViewPdf from './AttachmentViewPdf'; import addEncryptedAuthTokenToURL from '../../../libs/addEncryptedAuthTokenToURL'; import * as StyleUtils from '../../../styles/StyleUtils'; import {attachmentViewPropTypes, attachmentViewDefaultProps} from './propTypes'; +import {useRoute} from '@react-navigation/native'; +import * as ReportUtils from '../../../libs/ReportUtils'; +import * as TransactionUtils from '../../../libs/TransactionUtils'; +import EReceipt from '../../EReceipt'; +import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; const propTypes = { ...attachmentViewPropTypes, @@ -107,6 +112,21 @@ function AttachmentView({ ); } + const currentRoute = useRoute(); + const reportID = _.get(currentRoute, ['params', 'reportID']); + const report = ReportUtils.getReport(reportID); + + // Get the money request transaction + const parentReportAction = ReportActionsUtils.getParentReportAction(report); + const transactionID = _.get(parentReportAction, ['originalMessage', 'IOUTransactionID'], 0); + const transaction = TransactionUtils.getTransaction(transactionID); + console.log('transaction', transaction); + + const shouldShowEReceipt = TransactionUtils.isDistanceRequest(transaction); + if (shouldShowEReceipt) { + return + } + // For this check we use both source and file.name since temporary file source is a blob // both PDFs and images will appear as images when pasted into the the text field const isImage = Str.isImage(source); diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js new file mode 100644 index 000000000000..406825926820 --- /dev/null +++ b/src/components/EReceipt.js @@ -0,0 +1,54 @@ +import React, {useEffect} from 'react'; +import PropTypes from 'prop-types'; +import {View} from 'react-native'; + +import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; +import _ from 'underscore'; +import ONYXKEYS from '../ONYXKEYS'; +import CONST from '../CONST'; +import * as MapboxToken from '../libs/actions/MapboxToken'; +import * as TransactionUtils from '../libs/TransactionUtils'; +import * as Expensicons from './Icon/Expensicons'; +import theme from '../styles/themes/default'; +import styles from '../styles/styles'; +import transactionPropTypes from './transactionPropTypes'; +import BlockingView from './BlockingViews/BlockingView'; +import useNetwork from '../hooks/useNetwork'; +import useLocalize from '../hooks/useLocalize'; + +const propTypes = { + /** The transaction for the eReceipt */ + transaction: transactionPropTypes, +}; + +const defaultProps = { + transaction: {}, +}; + + +function EReceipt({transaction}) { + const {isOffline} = useNetwork(); + const {translate} = useLocalize(); + const {route0: route} = transaction.routes || {}; + + useEffect(() => { + MapboxToken.init(); + return MapboxToken.stop; + }, []); + + return ( + <> + + ); +} + +export default withOnyx({ + transaction: { + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + }, +})(EReceipt); + +EReceipt.displayName = 'EReceipt'; +EReceipt.propTypes = propTypes; +EReceipt.defaultProps = defaultProps; From a6b83fedb4f1f1380cb433ee216da7169453a470 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 11 Sep 2023 16:26:00 -0700 Subject: [PATCH 005/284] Render the receipt image --- src/components/EReceipt.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index 406825926820..20d4c3789838 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -16,6 +16,8 @@ import transactionPropTypes from './transactionPropTypes'; import BlockingView from './BlockingViews/BlockingView'; import useNetwork from '../hooks/useNetwork'; import useLocalize from '../hooks/useLocalize'; +import * as ReceiptUtils from '../libs/ReceiptUtils'; +import Image from './Image'; const propTypes = { /** The transaction for the eReceipt */ @@ -32,13 +34,19 @@ function EReceipt({transaction}) { const {translate} = useLocalize(); const {route0: route} = transaction.routes || {}; - useEffect(() => { - MapboxToken.init(); - return MapboxToken.stop; - }, []); - + const {thumbnail} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); return ( <> + + + ); } From 9d7538c13ae968c886d6b266f91974a88fc7ef7f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 11 Sep 2023 16:33:08 -0700 Subject: [PATCH 006/284] Style and clean up --- .../Attachments/AttachmentView/index.js | 2 +- src/components/EReceipt.js | 31 ++----------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 584827f499c6..e7d1dd5abb6b 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -124,7 +124,7 @@ function AttachmentView({ const shouldShowEReceipt = TransactionUtils.isDistanceRequest(transaction); if (shouldShowEReceipt) { - return + return ; } // For this check we use both source and file.name since temporary file source is a blob diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index 20d4c3789838..2068a5e50f12 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -1,21 +1,8 @@ -import React, {useEffect} from 'react'; -import PropTypes from 'prop-types'; +import React from 'react'; import {View} from 'react-native'; - -import {withOnyx} from 'react-native-onyx'; -import lodashGet from 'lodash/get'; import _ from 'underscore'; -import ONYXKEYS from '../ONYXKEYS'; -import CONST from '../CONST'; -import * as MapboxToken from '../libs/actions/MapboxToken'; -import * as TransactionUtils from '../libs/TransactionUtils'; -import * as Expensicons from './Icon/Expensicons'; -import theme from '../styles/themes/default'; import styles from '../styles/styles'; import transactionPropTypes from './transactionPropTypes'; -import BlockingView from './BlockingViews/BlockingView'; -import useNetwork from '../hooks/useNetwork'; -import useLocalize from '../hooks/useLocalize'; import * as ReceiptUtils from '../libs/ReceiptUtils'; import Image from './Image'; @@ -28,18 +15,11 @@ const defaultProps = { transaction: {}, }; - function EReceipt({transaction}) { - const {isOffline} = useNetwork(); - const {translate} = useLocalize(); - const {route0: route} = transaction.routes || {}; - const {thumbnail} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); return ( <> - + `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - }, -})(EReceipt); - +export default EReceipt; EReceipt.displayName = 'EReceipt'; EReceipt.propTypes = propTypes; EReceipt.defaultProps = defaultProps; From f730d9cb3f6e395b4258d77f7643f6c43d69f9a3 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 13 Sep 2023 12:30:06 +0530 Subject: [PATCH 007/284] upgrade rn-sdk to 8.3.0 --- ios/Podfile.lock | 10 +++++----- package-lock.json | 11 +++++++---- package.json | 2 +- ...4.0.patch => @onfido+react-native-sdk+8.3.0.patch} | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) rename patches/{@onfido+react-native-sdk+7.4.0.patch => @onfido+react-native-sdk+8.3.0.patch} (90%) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2bea672171fe..76bd62b08c5c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -251,9 +251,9 @@ PODS: - nanopb/encode (= 2.30908.0) - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - - Onfido (27.4.0) - - onfido-react-native-sdk (7.4.0): - - Onfido (= 27.4.0) + - Onfido (28.3.0) + - onfido-react-native-sdk (8.3.0): + - Onfido (~> 28.3.0) - React - OpenSSL-Universal (1.1.1100) - Permission-Camera (3.6.1): @@ -1219,8 +1219,8 @@ SPEC CHECKSUMS: MapboxMaps: af50ec61a7eb3b032c3f7962c6bd671d93d2a209 MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 - Onfido: e36f284b865adcf99d9c905590a64ac09d4a576b - onfido-react-native-sdk: 4ecde1a97435dcff9f00a878e3f8d1eb14fabbdc + Onfido: c7d010d9793790d44a07799d9be25aa8e3814ee7 + onfido-react-native-sdk: b346a620af5669f9fecb6dc3052314a35a94ad9f OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c Permission-Camera: bf6791b17c7f614b6826019fcfdcc286d3a107f6 Permission-LocationAccuracy: 76df17de5c6b8bc2eee34e61ee92cdd7a864c73d diff --git a/package-lock.json b/package-lock.json index 9336f8d44eee..4189d93a3b4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", - "@onfido/react-native-sdk": "7.4.0", + "@onfido/react-native-sdk": "8.3.0", "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-community/clipboard": "^1.5.1", @@ -5961,8 +5961,9 @@ } }, "node_modules/@onfido/react-native-sdk": { - "version": "7.4.0", - "license": "MIT", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-8.3.0.tgz", + "integrity": "sha512-nnhuvezd35v08WXUTQlX+gr4pbnNnwNV5KscC/jJrfjGikNUJnhnAHYxfnfJccTn44qUC6vRaKWq2GfpMUnqNA==", "peerDependencies": { "react": ">=17.0.0", "react-native": ">=0.68.2 <1.0.x" @@ -51793,7 +51794,9 @@ } }, "@onfido/react-native-sdk": { - "version": "7.4.0", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-8.3.0.tgz", + "integrity": "sha512-nnhuvezd35v08WXUTQlX+gr4pbnNnwNV5KscC/jJrfjGikNUJnhnAHYxfnfJccTn44qUC6vRaKWq2GfpMUnqNA==", "requires": {} }, "@pkgjs/parseargs": { diff --git a/package.json b/package.json index 021dba72e545..1b3ad1b64208 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", - "@onfido/react-native-sdk": "7.4.0", + "@onfido/react-native-sdk": "8.3.0", "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-community/clipboard": "^1.5.1", diff --git a/patches/@onfido+react-native-sdk+7.4.0.patch b/patches/@onfido+react-native-sdk+8.3.0.patch similarity index 90% rename from patches/@onfido+react-native-sdk+7.4.0.patch rename to patches/@onfido+react-native-sdk+8.3.0.patch index b84225c0f667..12245cb58355 100644 --- a/patches/@onfido+react-native-sdk+7.4.0.patch +++ b/patches/@onfido+react-native-sdk+8.3.0.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/@onfido/react-native-sdk/android/build.gradle b/node_modules/@onfido/react-native-sdk/android/build.gradle -index 781925b..9e16430 100644 +index b4c7106..d5083d3 100644 --- a/node_modules/@onfido/react-native-sdk/android/build.gradle +++ b/node_modules/@onfido/react-native-sdk/android/build.gradle -@@ -134,9 +134,9 @@ afterEvaluate { project -> +@@ -135,9 +135,9 @@ afterEvaluate { project -> group = "Reporting" description = "Generate Jacoco coverage reports after running tests." reports { From 4aef004335b01a621e6aba97119eacebeea64229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 13 Sep 2023 15:14:07 +0100 Subject: [PATCH 008/284] Remove more CSSProperties usage from styles --- src/styles/StyleUtils.ts | 310 +++++++++--------- src/styles/cardStyles/index.ts | 4 +- src/styles/cardStyles/types.ts | 3 +- src/styles/editedLabelStyles/index.ts | 3 +- src/styles/fontWeight/bold/types.ts | 3 +- .../index.website.ts | 3 +- .../getReportActionContextMenuStyles.ts | 3 +- src/styles/getTooltipStyles.ts | 19 +- src/styles/utilities/display.ts | 12 +- src/styles/utilities/overflowAuto/index.ts | 5 +- 10 files changed, 185 insertions(+), 180 deletions(-) diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 8945bc0be058..708a428b5633 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1,19 +1,18 @@ +import {Animated, FlexStyle, PressableStateCallbackType, TextStyle, ViewStyle} from 'react-native'; import {EdgeInsets} from 'react-native-safe-area-context'; -import {Animated, PressableStateCallbackType, TextStyle, ViewStyle} from 'react-native'; -import {CSSProperties} from 'react'; import {ValueOf} from 'type-fest'; import CONST from '../CONST'; +import * as Browser from '../libs/Browser'; +import * as NumberUtils from '../libs/NumberUtils'; +import * as UserUtils from '../libs/UserUtils'; +import colors from './colors'; import fontFamily from './fontFamily'; +import styles from './styles'; import themeColors from './themes/default'; -import variables from './variables'; -import colors from './colors'; +import cursor from './utilities/cursor'; import positioning from './utilities/positioning'; -import styles from './styles'; import spacing from './utilities/spacing'; -import * as UserUtils from '../libs/UserUtils'; -import * as Browser from '../libs/Browser'; -import cursor from './utilities/cursor'; -import * as NumberUtils from '../libs/NumberUtils'; +import variables from './variables'; type ColorValue = ValueOf; type AvatarSizeName = ValueOf; @@ -38,7 +37,7 @@ type ButtonSizeValue = ValueOf; type EmptyAvatarSizeName = ValueOf>; type ButtonStateName = ValueOf; type AvatarSize = {width: number}; -type ParsableStyle = ViewStyle | CSSProperties | ((state: PressableStateCallbackType) => ViewStyle | CSSProperties); +type ParsableStyle = ViewStyle | ((state: PressableStateCallbackType) => ViewStyle); type WorkspaceColorStyle = {backgroundColor: ColorValue; fill: ColorValue}; @@ -115,7 +114,7 @@ const avatarSizes: Record = { [CONST.AVATAR_SIZE.SMALL_NORMAL]: variables.avatarSizeSmallNormal, }; -const emptyAvatarStyles: Record = { +const emptyAvatarStyles: Record = { [CONST.AVATAR_SIZE.SMALL]: styles.emptyAvatarSmall, [CONST.AVATAR_SIZE.MEDIUM]: styles.emptyAvatarMedium, [CONST.AVATAR_SIZE.LARGE]: styles.emptyAvatarLarge, @@ -155,21 +154,21 @@ function getAvatarSize(size: AvatarSizeName): number { /** * Return the height of magic code input container */ -function getHeightOfMagicCodeInput(): ViewStyle | CSSProperties { +function getHeightOfMagicCodeInput(): ViewStyle { return {height: styles.magicCodeInputContainer.minHeight - styles.textInputContainer.borderBottomWidth}; } /** * Return the style from an empty avatar size constant */ -function getEmptyAvatarStyle(size: EmptyAvatarSizeName): ViewStyle | CSSProperties | undefined { +function getEmptyAvatarStyle(size: EmptyAvatarSizeName): ViewStyle | undefined { return emptyAvatarStyles[size]; } /** * Return the width style from an avatar size constant */ -function getAvatarWidthStyle(size: AvatarSizeName): ViewStyle | CSSProperties { +function getAvatarWidthStyle(size: AvatarSizeName): ViewStyle { const avatarSize = getAvatarSize(size); return { width: avatarSize, @@ -179,7 +178,7 @@ function getAvatarWidthStyle(size: AvatarSizeName): ViewStyle | CSSProperties { /** * Return the style from an avatar size constant */ -function getAvatarStyle(size: AvatarSizeName): ViewStyle | CSSProperties { +function getAvatarStyle(size: AvatarSizeName): ViewStyle { const avatarSize = getAvatarSize(size); return { height: avatarSize, @@ -192,7 +191,7 @@ function getAvatarStyle(size: AvatarSizeName): ViewStyle | CSSProperties { /** * Get Font size of '+1' text on avatar overlay */ -function getAvatarExtraFontSizeStyle(size: AvatarSizeName): TextStyle | CSSProperties { +function getAvatarExtraFontSizeStyle(size: AvatarSizeName): TextStyle { return { fontSize: avatarFontSizes[size], }; @@ -201,7 +200,7 @@ function getAvatarExtraFontSizeStyle(size: AvatarSizeName): TextStyle | CSSPrope /** * Get Bordersize of Avatar based on avatar size */ -function getAvatarBorderWidth(size: AvatarSizeName): ViewStyle | CSSProperties { +function getAvatarBorderWidth(size: AvatarSizeName): ViewStyle { return { borderWidth: avatarBorderWidths[size], }; @@ -210,7 +209,7 @@ function getAvatarBorderWidth(size: AvatarSizeName): ViewStyle | CSSProperties { /** * Return the border radius for an avatar */ -function getAvatarBorderRadius(size: AvatarSizeName, type: string): ViewStyle | CSSProperties { +function getAvatarBorderRadius(size: AvatarSizeName, type: string): ViewStyle { if (type === CONST.ICON_TYPE_WORKSPACE) { return {borderRadius: avatarBorderSizes[size]}; } @@ -222,7 +221,7 @@ function getAvatarBorderRadius(size: AvatarSizeName, type: string): ViewStyle | /** * Return the border style for an avatar */ -function getAvatarBorderStyle(size: AvatarSizeName, type: string): ViewStyle | CSSProperties { +function getAvatarBorderStyle(size: AvatarSizeName, type: string): ViewStyle { return { overflow: 'hidden', ...getAvatarBorderRadius(size, type), @@ -232,7 +231,7 @@ function getAvatarBorderStyle(size: AvatarSizeName, type: string): ViewStyle | C /** * Helper method to return old dot default avatar associated with login */ -function getDefaultWorkspaceAvatarColor(workspaceName: string): ViewStyle | CSSProperties { +function getDefaultWorkspaceAvatarColor(workspaceName: string): ViewStyle { const colorHash = UserUtils.hashText(workspaceName.trim(), workspaceColorOptions.length); return workspaceColorOptions[colorHash]; @@ -241,7 +240,7 @@ function getDefaultWorkspaceAvatarColor(workspaceName: string): ViewStyle | CSSP /** * Takes safe area insets and returns padding to use for a View */ -function getSafeAreaPadding(insets?: EdgeInsets, insetsPercentage: number = variables.safeInsertPercentage): ViewStyle | CSSProperties { +function getSafeAreaPadding(insets?: EdgeInsets, insetsPercentage: number = variables.safeInsertPercentage): ViewStyle { return { paddingTop: insets?.top, paddingBottom: (insets?.bottom ?? 0) * insetsPercentage, @@ -253,11 +252,11 @@ function getSafeAreaPadding(insets?: EdgeInsets, insetsPercentage: number = vari /** * Takes safe area insets and returns margin to use for a View */ -function getSafeAreaMargins(insets?: EdgeInsets): ViewStyle | CSSProperties { +function getSafeAreaMargins(insets?: EdgeInsets): ViewStyle { return {marginBottom: (insets?.bottom ?? 0) * variables.safeInsertPercentage}; } -function getZoomCursorStyle(isZoomed: boolean, isDragging: boolean): ViewStyle | CSSProperties { +function getZoomCursorStyle(isZoomed: boolean, isDragging: boolean): ViewStyle { if (!isZoomed) { return styles.cursorZoomIn; } @@ -265,6 +264,7 @@ function getZoomCursorStyle(isZoomed: boolean, isDragging: boolean): ViewStyle | return isDragging ? styles.cursorGrabbing : styles.cursorZoomOut; } +// NOTE: asserting some web style properties to a valid type, because isn't possible to augment them. function getZoomSizingStyle( isZoomed: boolean, imgWidth: number, @@ -273,28 +273,28 @@ function getZoomSizingStyle( containerHeight: number, containerWidth: number, isLoading: boolean, -): ViewStyle | CSSProperties | undefined { +): ViewStyle | undefined { // Hide image until finished loading to prevent showing preview with wrong dimensions if (isLoading || imgWidth === 0 || imgHeight === 0) { return undefined; } - const top = `${Math.max((containerHeight - imgHeight) / 2, 0)}px`; - const left = `${Math.max((containerWidth - imgWidth) / 2, 0)}px`; + const top = `${Math.max((containerHeight - imgHeight) / 2, 0)}px` as ViewStyle['top']; + const left = `${Math.max((containerWidth - imgWidth) / 2, 0)}px` as ViewStyle['left']; // Return different size and offset style based on zoomScale and isZoom. if (isZoomed) { // When both width and height are smaller than container(modal) size, set the height by multiplying zoomScale if it is zoomed in. if (zoomScale >= 1) { return { - height: `${imgHeight * zoomScale}px`, - width: `${imgWidth * zoomScale}px`, + height: `${imgHeight * zoomScale}px` as FlexStyle['height'], + width: `${imgWidth * zoomScale}px` as FlexStyle['width'], }; } // If image height and width are bigger than container size, display image with original size because original size is bigger and position absolute. return { - height: `${imgHeight}px`, - width: `${imgWidth}px`, + height: `${imgHeight}px` as FlexStyle['height'], + width: `${imgWidth}px` as FlexStyle['width'], top, left, }; @@ -303,8 +303,8 @@ function getZoomSizingStyle( // If image is not zoomed in and image size is smaller than container size, display with original size based on offset and position absolute. if (zoomScale > 1) { return { - height: `${imgHeight}px`, - width: `${imgWidth}px`, + height: `${imgHeight}px` as FlexStyle['height'], + width: `${imgWidth}px` as FlexStyle['width'], top, left, }; @@ -312,11 +312,11 @@ function getZoomSizingStyle( // If image is bigger than container size, display full image in the screen with scaled size (fit by container size) and position absolute. // top, left offset should be different when displaying long or wide image. - const scaledTop = `${Math.max((containerHeight - imgHeight * zoomScale) / 2, 0)}px`; - const scaledLeft = `${Math.max((containerWidth - imgWidth * zoomScale) / 2, 0)}px`; + const scaledTop = `${Math.max((containerHeight - imgHeight * zoomScale) / 2, 0)}px` as ViewStyle['top']; + const scaledLeft = `${Math.max((containerWidth - imgWidth * zoomScale) / 2, 0)}px` as ViewStyle['left']; return { - height: `${imgHeight * zoomScale}px`, - width: `${imgWidth * zoomScale}px`, + height: `${imgHeight * zoomScale}px` as FlexStyle['height'], + width: `${imgWidth * zoomScale}px` as FlexStyle['width'], top: scaledTop, left: scaledLeft, }; @@ -325,7 +325,7 @@ function getZoomSizingStyle( /** * Returns auto grow text input style */ -function getWidthStyle(width: number): ViewStyle | CSSProperties { +function getWidthStyle(width: number): ViewStyle { return { width, }; @@ -334,7 +334,7 @@ function getWidthStyle(width: number): ViewStyle | CSSProperties { /** * Returns auto grow height text input style */ -function getAutoGrowHeightInputStyle(textInputHeight: number, maxHeight: number): ViewStyle | CSSProperties { +function getAutoGrowHeightInputStyle(textInputHeight: number, maxHeight: number): ViewStyle { if (textInputHeight > maxHeight) { return { ...styles.pr0, @@ -354,7 +354,7 @@ function getAutoGrowHeightInputStyle(textInputHeight: number, maxHeight: number) /** * Returns a style with backgroundColor and borderColor set to the same color */ -function getBackgroundAndBorderStyle(backgroundColor: string): ViewStyle | CSSProperties { +function getBackgroundAndBorderStyle(backgroundColor: string): ViewStyle { return { backgroundColor, borderColor: backgroundColor, @@ -364,7 +364,7 @@ function getBackgroundAndBorderStyle(backgroundColor: string): ViewStyle | CSSPr /** * Returns a style with the specified backgroundColor */ -function getBackgroundColorStyle(backgroundColor: string): ViewStyle | CSSProperties { +function getBackgroundColorStyle(backgroundColor: string): ViewStyle { return { backgroundColor, }; @@ -373,7 +373,7 @@ function getBackgroundColorStyle(backgroundColor: string): ViewStyle | CSSProper /** * Returns a style for text color */ -function getTextColorStyle(color: string): TextStyle | CSSProperties { +function getTextColorStyle(color: string): TextStyle { return { color, }; @@ -382,7 +382,7 @@ function getTextColorStyle(color: string): TextStyle | CSSProperties { /** * Returns a style with the specified borderColor */ -function getBorderColorStyle(borderColor: string): ViewStyle | CSSProperties { +function getBorderColorStyle(borderColor: string): ViewStyle { return { borderColor, }; @@ -391,7 +391,7 @@ function getBorderColorStyle(borderColor: string): ViewStyle | CSSProperties { /** * Returns the width style for the wordmark logo on the sign in page */ -function getSignInWordmarkWidthStyle(environment: string, isSmallScreenWidth: boolean): ViewStyle | CSSProperties { +function getSignInWordmarkWidthStyle(environment: string, isSmallScreenWidth: boolean): ViewStyle { if (environment === CONST.ENVIRONMENT.DEV) { return isSmallScreenWidth ? {width: variables.signInLogoWidthPill} : {width: variables.signInLogoWidthLargeScreenPill}; } @@ -423,7 +423,7 @@ function hexadecimalToRGBArray(hexadecimal: string): number[] | undefined { /** * Returns a background color with opacity style */ -function getBackgroundColorWithOpacityStyle(backgroundColor: string, opacity: number): ViewStyle | CSSProperties { +function getBackgroundColorWithOpacityStyle(backgroundColor: string, opacity: number): ViewStyle { const result = hexadecimalToRGBArray(backgroundColor); if (result !== undefined) { return { @@ -436,7 +436,7 @@ function getBackgroundColorWithOpacityStyle(backgroundColor: string, opacity: nu /** * Generate a style for the background color of the Badge */ -function getBadgeColorStyle(success: boolean, error: boolean, isPressed = false, isAdHoc = false): ViewStyle | CSSProperties { +function getBadgeColorStyle(success: boolean, error: boolean, isPressed = false, isAdHoc = false): ViewStyle { if (success) { if (isAdHoc) { return isPressed ? styles.badgeAdHocSuccessPressed : styles.badgeAdHocSuccess; @@ -455,7 +455,7 @@ function getBadgeColorStyle(success: boolean, error: boolean, isPressed = false, * @param buttonState - One of {'default', 'hovered', 'pressed'} * @param isMenuItem - whether this button is apart of a list */ -function getButtonBackgroundColorStyle(buttonState: ButtonStateName = CONST.BUTTON_STATES.DEFAULT, isMenuItem = false): ViewStyle | CSSProperties { +function getButtonBackgroundColorStyle(buttonState: ButtonStateName = CONST.BUTTON_STATES.DEFAULT, isMenuItem = false): ViewStyle { switch (buttonState) { case CONST.BUTTON_STATES.PRESSED: return {backgroundColor: themeColors.buttonPressedBG}; @@ -498,7 +498,7 @@ function getAnimatedFABStyle(rotate: Animated.Value, backgroundColor: Animated.V }; } -function getWidthAndHeightStyle(width: number, height: number | undefined = undefined): ViewStyle | CSSProperties { +function getWidthAndHeightStyle(width: number, height: number | undefined = undefined): ViewStyle { return { width, height: height ?? width, @@ -519,7 +519,7 @@ function getModalPaddingStyles({ modalContainerStylePaddingTop, modalContainerStylePaddingBottom, insets, -}: ModalPaddingStylesArgs): ViewStyle | CSSProperties { +}: ModalPaddingStylesArgs): ViewStyle { // use fallback value for safeAreaPaddingBottom to keep padding bottom consistent with padding top. // More info: issue #17376 const safeAreaPaddingBottomWithFallback = insets.bottom === 0 ? modalContainerStylePaddingTop || 0 : safeAreaPaddingBottom; @@ -547,7 +547,7 @@ function getFontFamilyMonospace({fontStyle, fontWeight}: TextStyle): string { /** * Gives the width for Emoji picker Widget */ -function getEmojiPickerStyle(isSmallScreenWidth: boolean): ViewStyle | CSSProperties { +function getEmojiPickerStyle(isSmallScreenWidth: boolean): ViewStyle { if (isSmallScreenWidth) { return { width: CONST.SMALL_EMOJI_PICKER_SIZE.WIDTH, @@ -562,7 +562,7 @@ function getEmojiPickerStyle(isSmallScreenWidth: boolean): ViewStyle | CSSProper /** * Get the random promo color and image for Login page */ -function getLoginPagePromoStyle(): ViewStyle | CSSProperties { +function getLoginPagePromoStyle(): ViewStyle { const promos = [ { backgroundColor: colors.green, @@ -592,7 +592,7 @@ function getLoginPagePromoStyle(): ViewStyle | CSSProperties { /** * Generate the styles for the ReportActionItem wrapper view. */ -function getReportActionItemStyle(isHovered = false, isLoading = false): ViewStyle | CSSProperties { +function getReportActionItemStyle(isHovered = false, isLoading = false): ViewStyle { return { display: 'flex', justifyContent: 'space-between', @@ -608,7 +608,7 @@ function getReportActionItemStyle(isHovered = false, isLoading = false): ViewSty /** * Generate the wrapper styles for the mini ReportActionContextMenu. */ -function getMiniReportActionContextMenuWrapperStyle(isReportActionItemGrouped: boolean): ViewStyle | CSSProperties { +function getMiniReportActionContextMenuWrapperStyle(isReportActionItemGrouped: boolean): ViewStyle { return { ...(isReportActionItemGrouped ? positioning.tn8 : positioning.tn4), ...positioning.r4, @@ -618,7 +618,7 @@ function getMiniReportActionContextMenuWrapperStyle(isReportActionItemGrouped: b }; } -function getPaymentMethodMenuWidth(isSmallScreenWidth: boolean): ViewStyle | CSSProperties { +function getPaymentMethodMenuWidth(isSmallScreenWidth: boolean): ViewStyle { const margin = 20; return {width: !isSmallScreenWidth ? variables.sideBarWidth - margin * 2 : undefined}; } @@ -704,14 +704,14 @@ function getThemeBackgroundColor(bgColor: string = themeColors.appBG): string { /** * Parse styleParam and return Styles array */ -function parseStyleAsArray(styleParam: ViewStyle | CSSProperties | Array): Array { +function parseStyleAsArray(styleParam: ViewStyle | ViewStyle[]): ViewStyle[] { return Array.isArray(styleParam) ? styleParam : [styleParam]; } /** * Parse style function and return Styles object */ -function parseStyleFromFunction(style: ParsableStyle, state: PressableStateCallbackType): Array { +function parseStyleFromFunction(style: ParsableStyle, state: PressableStateCallbackType): ViewStyle[] { const functionAppliedStyle = typeof style === 'function' ? style(state) : style; return parseStyleAsArray(functionAppliedStyle); } @@ -719,8 +719,8 @@ function parseStyleFromFunction(style: ParsableStyle, state: PressableStateCallb /** * Receives any number of object or array style objects and returns them all as an array */ -function combineStyles(...allStyles: Array>) { - let finalStyles: Array> = []; +function combineStyles(...allStyles: Array) { + let finalStyles: ViewStyle[][] = []; allStyles.forEach((style) => { finalStyles = finalStyles.concat(parseStyleAsArray(style)); }); @@ -730,7 +730,7 @@ function combineStyles(...allStyles: Array 0 ? -overlapSize : 0, zIndex: index + 2, @@ -826,7 +826,7 @@ function getHorizontalStackedAvatarStyle(index: number, overlapSize: number): Vi /** * Get computed avatar styles of '+1' overlay based on size */ -function getHorizontalStackedOverlayAvatarStyle(oneAvatarSize: AvatarSize, oneAvatarBorderWidth: number): ViewStyle | CSSProperties { +function getHorizontalStackedOverlayAvatarStyle(oneAvatarSize: AvatarSize, oneAvatarBorderWidth: number): ViewStyle { return { borderWidth: oneAvatarBorderWidth, borderRadius: oneAvatarSize.width, @@ -836,7 +836,7 @@ function getHorizontalStackedOverlayAvatarStyle(oneAvatarSize: AvatarSize, oneAv }; } -function getErrorPageContainerStyle(safeAreaPaddingBottom = 0): ViewStyle | CSSProperties { +function getErrorPageContainerStyle(safeAreaPaddingBottom = 0): ViewStyle { return { backgroundColor: themeColors.componentBG, paddingBottom: 40 + safeAreaPaddingBottom, @@ -846,7 +846,7 @@ function getErrorPageContainerStyle(safeAreaPaddingBottom = 0): ViewStyle | CSSP /** * Gets the correct size for the empty state background image based on screen dimensions */ -function getReportWelcomeBackgroundImageStyle(isSmallScreenWidth: boolean): ViewStyle | CSSProperties { +function getReportWelcomeBackgroundImageStyle(isSmallScreenWidth: boolean): ViewStyle { if (isSmallScreenWidth) { return { height: CONST.EMPTY_STATE_BACKGROUND.SMALL_SCREEN.IMAGE_HEIGHT, @@ -865,7 +865,7 @@ function getReportWelcomeBackgroundImageStyle(isSmallScreenWidth: boolean): View /** * Gets the correct top margin size for the chat welcome message based on screen dimensions */ -function getReportWelcomeTopMarginStyle(isSmallScreenWidth: boolean): ViewStyle | CSSProperties { +function getReportWelcomeTopMarginStyle(isSmallScreenWidth: boolean): ViewStyle { if (isSmallScreenWidth) { return { marginTop: CONST.EMPTY_STATE_BACKGROUND.SMALL_SCREEN.VIEW_HEIGHT, @@ -880,7 +880,7 @@ function getReportWelcomeTopMarginStyle(isSmallScreenWidth: boolean): ViewStyle /** * Returns fontSize style */ -function getFontSizeStyle(fontSize: number): TextStyle | CSSProperties { +function getFontSizeStyle(fontSize: number): TextStyle { return { fontSize, }; @@ -889,7 +889,7 @@ function getFontSizeStyle(fontSize: number): TextStyle | CSSProperties { /** * Returns lineHeight style */ -function getLineHeightStyle(lineHeight: number): TextStyle | CSSProperties { +function getLineHeightStyle(lineHeight: number): TextStyle { return { lineHeight, }; @@ -898,7 +898,7 @@ function getLineHeightStyle(lineHeight: number): TextStyle | CSSProperties { /** * Gets the correct size for the empty state container based on screen dimensions */ -function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean): ViewStyle | CSSProperties { +function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean): ViewStyle { if (isSmallScreenWidth) { return { minHeight: CONST.EMPTY_STATE_BACKGROUND.SMALL_SCREEN.CONTAINER_MINHEIGHT, @@ -917,7 +917,7 @@ function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean): ViewStyle /** * Gets styles for AutoCompleteSuggestion row */ -function getAutoCompleteSuggestionItemStyle(highlightedEmojiIndex: number, rowHeight: number, hovered: boolean, currentEmojiIndex: number): Array { +function getAutoCompleteSuggestionItemStyle(highlightedEmojiIndex: number, rowHeight: number, hovered: boolean, currentEmojiIndex: number): ViewStyle[] { let backgroundColor; if (currentEmojiIndex === highlightedEmojiIndex) { @@ -942,14 +942,20 @@ function getAutoCompleteSuggestionItemStyle(highlightedEmojiIndex: number, rowHe /** * Gets the correct position for the base auto complete suggestion container */ -function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: {left: number; bottom: number; width: number}): ViewStyle | CSSProperties { - return {position: 'fixed', bottom, left, width}; +function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: {left: number; bottom: number; width: number}): ViewStyle { + return { + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + position: 'fixed' as ViewStyle['position'], + bottom, + left, + width, + }; } /** * Gets the correct position for auto complete suggestion container */ -function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldIncludeReportRecipientLocalTimeHeight: boolean): ViewStyle | CSSProperties { +function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldIncludeReportRecipientLocalTimeHeight: boolean): ViewStyle { 'worklet'; const optionalPadding = shouldIncludeReportRecipientLocalTimeHeight ? CONST.RECIPIENT_LOCAL_TIME_HEIGHT : 0; @@ -969,11 +975,11 @@ function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldIncl /** * Select the correct color for text. */ -function getColoredBackgroundStyle(isColored: boolean): ViewStyle | CSSProperties { +function getColoredBackgroundStyle(isColored: boolean): ViewStyle { return {backgroundColor: isColored ? colors.blueLink : undefined}; } -function getEmojiReactionBubbleStyle(isHovered: boolean, hasUserReacted: boolean, isContextMenu = false): ViewStyle | CSSProperties { +function getEmojiReactionBubbleStyle(isHovered: boolean, hasUserReacted: boolean, isContextMenu = false): ViewStyle { let backgroundColor = themeColors.border; if (isHovered) { @@ -999,7 +1005,7 @@ function getEmojiReactionBubbleStyle(isHovered: boolean, hasUserReacted: boolean }; } -function getEmojiReactionBubbleTextStyle(isContextMenu = false): TextStyle | CSSProperties { +function getEmojiReactionBubbleTextStyle(isContextMenu = false): TextStyle { if (isContextMenu) { return { fontSize: 17, @@ -1013,7 +1019,7 @@ function getEmojiReactionBubbleTextStyle(isContextMenu = false): TextStyle | CSS }; } -function getEmojiReactionCounterTextStyle(hasUserReacted: boolean): TextStyle | CSSProperties { +function getEmojiReactionCounterTextStyle(hasUserReacted: boolean): TextStyle { if (hasUserReacted) { return {color: themeColors.reactionActiveText}; } @@ -1026,7 +1032,7 @@ function getEmojiReactionCounterTextStyle(hasUserReacted: boolean): TextStyle | * * @param direction - The direction of the rotation (CONST.DIRECTION.LEFT or CONST.DIRECTION.RIGHT). */ -function getDirectionStyle(direction: string): ViewStyle | CSSProperties { +function getDirectionStyle(direction: string): ViewStyle { if (direction === CONST.DIRECTION.LEFT) { return {transform: [{rotate: '180deg'}]}; } @@ -1037,11 +1043,11 @@ function getDirectionStyle(direction: string): ViewStyle | CSSProperties { /** * Returns a style object with display flex or none basing on the condition value. */ -function displayIfTrue(condition: boolean): ViewStyle | CSSProperties { +function displayIfTrue(condition: boolean): ViewStyle { return {display: condition ? 'flex' : 'none'}; } -function getGoogleListViewStyle(shouldDisplayBorder: boolean): ViewStyle | CSSProperties { +function getGoogleListViewStyle(shouldDisplayBorder: boolean): ViewStyle { if (shouldDisplayBorder) { return { ...styles.borderTopRounded, @@ -1059,7 +1065,7 @@ function getGoogleListViewStyle(shouldDisplayBorder: boolean): ViewStyle | CSSPr /** * Gets the correct height for emoji picker list based on screen dimensions */ -function getEmojiPickerListHeight(hasAdditionalSpace: boolean, windowHeight: number): ViewStyle | CSSProperties { +function getEmojiPickerListHeight(hasAdditionalSpace: boolean, windowHeight: number): ViewStyle { const style = { ...spacing.ph4, height: hasAdditionalSpace ? CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT + CONST.CATEGORY_SHORTCUT_BAR_HEIGHT : CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT, @@ -1079,7 +1085,7 @@ function getEmojiPickerListHeight(hasAdditionalSpace: boolean, windowHeight: num /** * Returns style object for the user mention component based on whether the mention is ours or not. */ -function getMentionStyle(isOurMention: boolean): ViewStyle | CSSProperties { +function getMentionStyle(isOurMention: boolean): ViewStyle { const backgroundColor = isOurMention ? themeColors.ourMentionBG : themeColors.mentionBG; return { backgroundColor, @@ -1098,7 +1104,7 @@ function getMentionTextColor(isOurMention: boolean): string { /** * Returns padding vertical based on number of lines */ -function getComposeTextAreaPadding(numberOfLines: number, isComposerFullSize: boolean): ViewStyle | CSSProperties { +function getComposeTextAreaPadding(numberOfLines: number, isComposerFullSize: boolean): ViewStyle { let paddingValue = 5; // Issue #26222: If isComposerFullSize paddingValue will always be 5 to prevent padding jumps when adding multiple lines. if (!isComposerFullSize) { @@ -1119,14 +1125,14 @@ function getComposeTextAreaPadding(numberOfLines: number, isComposerFullSize: bo /** * Returns style object for the mobile on WEB */ -function getOuterModalStyle(windowHeight: number, viewportOffsetTop: number): ViewStyle | CSSProperties { +function getOuterModalStyle(windowHeight: number, viewportOffsetTop: number): ViewStyle { return Browser.isMobile() ? {maxHeight: windowHeight, marginTop: viewportOffsetTop} : {}; } /** * Returns style object for flexWrap depending on the screen size */ -function getWrappingStyle(isExtraSmallScreenWidth: boolean): ViewStyle | CSSProperties { +function getWrappingStyle(isExtraSmallScreenWidth: boolean): ViewStyle { return { flexWrap: isExtraSmallScreenWidth ? 'wrap' : 'nowrap', }; @@ -1135,7 +1141,7 @@ function getWrappingStyle(isExtraSmallScreenWidth: boolean): ViewStyle | CSSProp /** * Returns the text container styles for menu items depending on if the menu item container a small avatar */ -function getMenuItemTextContainerStyle(isSmallAvatarSubscriptMenu: boolean): ViewStyle | CSSProperties { +function getMenuItemTextContainerStyle(isSmallAvatarSubscriptMenu: boolean): ViewStyle { return { minHeight: isSmallAvatarSubscriptMenu ? variables.avatarSizeSubscript : variables.componentSizeNormal, }; @@ -1144,7 +1150,7 @@ function getMenuItemTextContainerStyle(isSmallAvatarSubscriptMenu: boolean): Vie /** * Returns link styles based on whether the link is disabled or not */ -function getDisabledLinkStyles(isDisabled = false): ViewStyle | CSSProperties { +function getDisabledLinkStyles(isDisabled = false): ViewStyle { const disabledLinkStyles = { color: themeColors.textSupporting, ...cursor.cursorDisabled, @@ -1159,7 +1165,7 @@ function getDisabledLinkStyles(isDisabled = false): ViewStyle | CSSProperties { /** * Returns the checkbox container style */ -function getCheckboxContainerStyle(size: number, borderRadius: number): ViewStyle | CSSProperties { +function getCheckboxContainerStyle(size: number, borderRadius: number): ViewStyle { return { backgroundColor: themeColors.componentBG, height: size, @@ -1176,7 +1182,7 @@ function getCheckboxContainerStyle(size: number, borderRadius: number): ViewStyl /** * Returns style object for the dropbutton height */ -function getDropDownButtonHeight(buttonSize: ButtonSizeValue): ViewStyle | CSSProperties { +function getDropDownButtonHeight(buttonSize: ButtonSizeValue): ViewStyle { if (buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE) { return { height: variables.componentSizeLarge, @@ -1190,7 +1196,7 @@ function getDropDownButtonHeight(buttonSize: ButtonSizeValue): ViewStyle | CSSPr /** * Returns fitting fontSize and lineHeight values in order to prevent large amounts from being cut off on small screen widths. */ -function getAmountFontSizeAndLineHeight(baseFontSize: number, baseLineHeight: number, isSmallScreenWidth: boolean, windowWidth: number): ViewStyle | CSSProperties { +function getAmountFontSizeAndLineHeight(baseFontSize: number, baseLineHeight: number, isSmallScreenWidth: boolean, windowWidth: number): TextStyle { let toSubtract = 0; if (isSmallScreenWidth) { @@ -1224,81 +1230,81 @@ function getTransparentColor(color: string) { } export { + combineStyles, + displayIfTrue, + fade, + getAmountFontSizeAndLineHeight, + getAnimatedFABStyle, + getAutoCompleteSuggestionContainerStyle, + getAutoCompleteSuggestionItemStyle, + getAutoGrowHeightInputStyle, + getAvatarBorderRadius, + getAvatarBorderStyle, + getAvatarBorderWidth, + getAvatarExtraFontSizeStyle, getAvatarSize, - getAvatarWidthStyle, getAvatarStyle, - getAvatarExtraFontSizeStyle, - getAvatarBorderWidth, - getAvatarBorderStyle, - getEmptyAvatarStyle, - getErrorPageContainerStyle, - getSafeAreaPadding, - getSafeAreaMargins, - getZoomCursorStyle, - getZoomSizingStyle, - getWidthStyle, - getAutoGrowHeightInputStyle, + getAvatarWidthStyle, getBackgroundAndBorderStyle, getBackgroundColorStyle, - getTextColorStyle, - getBorderColorStyle, getBackgroundColorWithOpacityStyle, getBadgeColorStyle, - getButtonBackgroundColorStyle, - getIconFillColor, - getAnimatedFABStyle, - getWidthAndHeightStyle, - getModalPaddingStyles, - getFontFamilyMonospace, - getEmojiPickerStyle, - getLoginPagePromoStyle, - getReportActionItemStyle, - getMiniReportActionContextMenuWrapperStyle, - getKeyboardShortcutsModalWidth, - getPaymentMethodMenuWidth, - getThemeBackgroundColor, - parseStyleAsArray, - parseStyleFromFunction, - combineStyles, - getPaddingLeft, - hasSafeAreas, - getHeight, - getMinimumHeight, - getMaximumHeight, - getMaximumWidth, - fade, - getHorizontalStackedAvatarBorderStyle, - getHorizontalStackedAvatarStyle, - getHorizontalStackedOverlayAvatarStyle, - getReportWelcomeBackgroundImageStyle, - getReportWelcomeTopMarginStyle, - getReportWelcomeContainerStyle, getBaseAutoCompleteSuggestionContainerStyle, - getAutoCompleteSuggestionItemStyle, - getAutoCompleteSuggestionContainerStyle, + getBorderColorStyle, + getButtonBackgroundColorStyle, + getCheckboxContainerStyle, getColoredBackgroundStyle, + getComposeTextAreaPadding, getDefaultWorkspaceAvatarColor, - getAvatarBorderRadius, + getDirectionStyle, + getDisabledLinkStyles, + getDropDownButtonHeight, + getEmojiPickerListHeight, + getEmojiPickerStyle, getEmojiReactionBubbleStyle, getEmojiReactionBubbleTextStyle, getEmojiReactionCounterTextStyle, - getDirectionStyle, - displayIfTrue, + getEmptyAvatarStyle, + getErrorPageContainerStyle, + getFontFamilyMonospace, getFontSizeStyle, - getLineHeightStyle, - getSignInWordmarkWidthStyle, getGoogleListViewStyle, - getEmojiPickerListHeight, + getHeight, + getHeightOfMagicCodeInput, + getHorizontalStackedAvatarBorderStyle, + getHorizontalStackedAvatarStyle, + getHorizontalStackedOverlayAvatarStyle, + getIconFillColor, + getKeyboardShortcutsModalWidth, + getLineHeightStyle, + getLoginPagePromoStyle, + getMaximumHeight, + getMaximumWidth, getMentionStyle, getMentionTextColor, - getComposeTextAreaPadding, - getHeightOfMagicCodeInput, - getOuterModalStyle, - getWrappingStyle, getMenuItemTextContainerStyle, - getDisabledLinkStyles, - getCheckboxContainerStyle, - getDropDownButtonHeight, - getAmountFontSizeAndLineHeight, + getMiniReportActionContextMenuWrapperStyle, + getMinimumHeight, + getModalPaddingStyles, + getOuterModalStyle, + getPaddingLeft, + getPaymentMethodMenuWidth, + getReportActionItemStyle, + getReportWelcomeBackgroundImageStyle, + getReportWelcomeContainerStyle, + getReportWelcomeTopMarginStyle, + getSafeAreaMargins, + getSafeAreaPadding, + getSignInWordmarkWidthStyle, + getTextColorStyle, + getThemeBackgroundColor, getTransparentColor, + getWidthAndHeightStyle, + getWidthStyle, + getWrappingStyle, + getZoomCursorStyle, + getZoomSizingStyle, + hasSafeAreas, + parseStyleAsArray, + parseStyleFromFunction, }; diff --git a/src/styles/cardStyles/index.ts b/src/styles/cardStyles/index.ts index 823081b62904..b2ba8d7c24f1 100644 --- a/src/styles/cardStyles/index.ts +++ b/src/styles/cardStyles/index.ts @@ -1,10 +1,12 @@ +import {ViewStyle} from 'react-native'; import GetCardStyles from './types'; /** * Get card style for cardStyleInterpolator */ const getCardStyles: GetCardStyles = (screenWidth) => ({ - position: 'fixed', + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + position: 'fixed' as ViewStyle['position'], width: screenWidth, height: '100%', }); diff --git a/src/styles/cardStyles/types.ts b/src/styles/cardStyles/types.ts index e1598b7696ff..517ab76811bc 100644 --- a/src/styles/cardStyles/types.ts +++ b/src/styles/cardStyles/types.ts @@ -1,6 +1,5 @@ -import {CSSProperties} from 'react'; import {ViewStyle} from 'react-native'; -type GetCardStyles = (screenWidth: number) => Partial>; +type GetCardStyles = (screenWidth: number) => Pick; export default GetCardStyles; diff --git a/src/styles/editedLabelStyles/index.ts b/src/styles/editedLabelStyles/index.ts index b3962e507757..5764735d0dea 100644 --- a/src/styles/editedLabelStyles/index.ts +++ b/src/styles/editedLabelStyles/index.ts @@ -1,10 +1,9 @@ -import {TextStyle} from 'react-native'; import display from '../utilities/display'; import flex from '../utilities/flex'; import EditedLabelStyles from './types'; const editedLabelStyles: EditedLabelStyles = { - ...(display.dInlineFlex as TextStyle), + ...display.dInlineFlex, ...flex.alignItemsBaseline, }; diff --git a/src/styles/fontWeight/bold/types.ts b/src/styles/fontWeight/bold/types.ts index 3c9930a63d87..67258eee719c 100644 --- a/src/styles/fontWeight/bold/types.ts +++ b/src/styles/fontWeight/bold/types.ts @@ -1,6 +1,5 @@ -import {CSSProperties} from 'react'; import {TextStyle} from 'react-native'; -type FontWeightBoldStyles = (TextStyle | CSSProperties)['fontWeight']; +type FontWeightBoldStyles = TextStyle['fontWeight']; export default FontWeightBoldStyles; diff --git a/src/styles/getNavigationModalCardStyles/index.website.ts b/src/styles/getNavigationModalCardStyles/index.website.ts index ea76825e5bba..422a17d0a9a8 100644 --- a/src/styles/getNavigationModalCardStyles/index.website.ts +++ b/src/styles/getNavigationModalCardStyles/index.website.ts @@ -10,8 +10,7 @@ const getNavigationModalCardStyles: GetNavigationModalCardStyles = () => ({ width: '100%', height: '100%', - // NOTE: asserting "fixed" TS type to a valid type, because isn't possible - // to augment "position". + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". position: 'fixed' as ViewStyle['position'], }); diff --git a/src/styles/getReportActionContextMenuStyles.ts b/src/styles/getReportActionContextMenuStyles.ts index 792213f2b54b..33f04b723e25 100644 --- a/src/styles/getReportActionContextMenuStyles.ts +++ b/src/styles/getReportActionContextMenuStyles.ts @@ -17,8 +17,7 @@ const miniWrapperStyle: StylesArray = [ borderWidth: 1, borderColor: themeColors.border, // In Safari, when welcome messages use a code block (triple backticks), they would overlap the context menu below when there is no scrollbar without the transform style. - // NOTE: asserting "transform" TS type to a valid type, because isn't possible - // to augment "transform". + // NOTE: asserting "transform" to a valid type, because isn't possible to augment "transform". transform: 'translateZ(0)' as unknown as ViewStyle['transform'], }, ]; diff --git a/src/styles/getTooltipStyles.ts b/src/styles/getTooltipStyles.ts index 3f9de9c78b97..1e0b41d59aa4 100644 --- a/src/styles/getTooltipStyles.ts +++ b/src/styles/getTooltipStyles.ts @@ -1,12 +1,11 @@ -import {CSSProperties} from 'react'; import {TextStyle, View, ViewStyle} from 'react-native'; -import spacing from './utilities/spacing'; -import styles from './styles'; import colors from './colors'; -import themeColors from './themes/default'; import fontFamily from './fontFamily'; -import variables from './variables'; import roundToNearestMultipleOfFour from './roundToNearestMultipleOfFour'; +import styles from './styles'; +import themeColors from './themes/default'; +import spacing from './utilities/spacing'; +import variables from './variables'; /** This defines the proximity with the edge of the window in which tooltips should not be displayed. * If a tooltip is too close to the edge of the screen, we'll shift it towards the center. */ @@ -96,9 +95,9 @@ function isOverlappingAtTop(tooltip: View | HTMLDivElement, xOffset: number, yOf type TooltipStyles = { animationStyle: ViewStyle; - rootWrapperStyle: ViewStyle | CSSProperties; + rootWrapperStyle: ViewStyle; textStyle: TextStyle; - pointerWrapperStyle: ViewStyle | CSSProperties; + pointerWrapperStyle: ViewStyle; pointerStyle: ViewStyle; }; @@ -238,7 +237,8 @@ export default function getTooltipStyles( transform: [{scale}], }, rootWrapperStyle: { - position: 'fixed', + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + position: 'fixed' as ViewStyle['position'], backgroundColor: themeColors.heading, borderRadius: variables.componentBorderRadiusSmall, ...tooltipVerticalPadding, @@ -260,7 +260,8 @@ export default function getTooltipStyles( lineHeight: variables.lineHeightSmall, }, pointerWrapperStyle: { - position: 'fixed', + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + position: 'fixed' as ViewStyle['position'], top: pointerWrapperTop, left: pointerWrapperLeft, }, diff --git a/src/styles/utilities/display.ts b/src/styles/utilities/display.ts index 868c2bdb0e3b..e236ad4ea14d 100644 --- a/src/styles/utilities/display.ts +++ b/src/styles/utilities/display.ts @@ -1,4 +1,3 @@ -import {CSSProperties} from 'react'; import {ViewStyle} from 'react-native'; /** @@ -21,14 +20,17 @@ export default { }, dInline: { - display: 'inline', + // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". + display: 'inline' as ViewStyle['display'], }, dInlineFlex: { - display: 'inline-flex', + // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". + display: 'inline-flex' as ViewStyle['display'], }, dBlock: { - display: 'block', + // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". + display: 'block' as ViewStyle['display'], }, -} satisfies Record; +} satisfies Record; diff --git a/src/styles/utilities/overflowAuto/index.ts b/src/styles/utilities/overflowAuto/index.ts index f958d8000c12..d73bee877c50 100644 --- a/src/styles/utilities/overflowAuto/index.ts +++ b/src/styles/utilities/overflowAuto/index.ts @@ -1,9 +1,8 @@ -import { ViewStyle } from 'react-native'; +import {ViewStyle} from 'react-native'; import OverflowAutoStyles from './types'; const overflowAuto: OverflowAutoStyles = { - // NOTE: asserting "auto" TS type to a valid type, because isn't possible - // to augment "overflow". + // NOTE: asserting "overflow" to a valid type, because isn't possible to augment "overflow". overflow: 'auto' as ViewStyle['overflow'], }; From 58d4a25f581df52c7a2077f9f3feabdc90b9ec46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 14 Sep 2023 01:12:35 +0100 Subject: [PATCH 009/284] Add more props and styles --- src/types/modules/react-native.d.ts | 276 ++++++++++++++++++---------- 1 file changed, 184 insertions(+), 92 deletions(-) diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index c06c09f38fe5..d0a2de964b8d 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -1,19 +1,18 @@ /* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/no-empty-interface */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import {CSSProperties, FocusEventHandler, KeyboardEventHandler, MouseEventHandler} from 'react'; +import {CSSProperties, FocusEventHandler, KeyboardEventHandler, MouseEventHandler, PointerEventHandler, UIEventHandler, WheelEventHandler} from 'react'; import 'react-native'; declare module 'react-native' { + // <------ REACT NATIVE WEB (0.19.0) ------> // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js - type NumberOrString = number | string; - type OverscrollBehaviorValue = 'auto' | 'contain' | 'none'; type idRef = string; type idRefList = idRef | idRef[]; // https://necolas.github.io/react-native-web/docs/accessibility/#accessibility-props-api // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js - type AccessibilityProps = { + interface AccessibilityProps { 'aria-activedescendant'?: idRef; 'aria-atomic'?: boolean; 'aria-autocomplete'?: 'none' | 'list' | 'inline' | 'both'; @@ -108,142 +107,235 @@ declare module 'react-native' { accessibilityValueMin?: number; accessibilityValueNow?: number; accessibilityValueText?: string; - }; + } // https://necolas.github.io/react-native-web/docs/interactions/#pointerevent-props-api - // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js - // Extracted from @types/react, index.d.ts - type PointerProps = { + // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js + // Extracted types from @types/react, index.d.ts + interface PointerProps { + onAuxClick?: MouseEventHandler; onClick?: MouseEventHandler; - onClickCapture?: MouseEventHandler; onContextMenu?: MouseEventHandler; + onGotPointerCapture?: PointerEventHandler; + onLostPointerCapture?: PointerEventHandler; + onPointerCancel?: PointerEventHandler; + onPointerDown?: PointerEventHandler; + onPointerEnter?: PointerEventHandler; + onPointerMove?: PointerEventHandler; + onPointerLeave?: PointerEventHandler; + onPointerOut?: PointerEventHandler; + onPointerOver?: PointerEventHandler; + onPointerUp?: PointerEventHandler; + onMouseDown?: MouseEventHandler; + onMouseEnter?: MouseEventHandler; + onMouseLeave?: MouseEventHandler; + onMouseMove?: MouseEventHandler; + onMouseOver?: MouseEventHandler; + onMouseOut?: MouseEventHandler; + onMouseUp?: MouseEventHandler; + onScroll?: UIEventHandler; + onWheel?: WheelEventHandler; + } + + // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api + // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/ResponderTouchHistoryStore.js + type TouchRecord = { + currentPageX: number; + currentPageY: number; + currentTimeStamp: number; + previousPageX: number; + previousPageY: number; + previousTimeStamp: number; + startPageX: number; + startPageY: number; + startTimeStamp: number; + touchActive: boolean; + }; + + // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api + // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/ResponderTouchHistoryStore.js + type TouchHistory = Readonly<{ + indexOfSingleActiveTouch: number; + mostRecentTimeStamp: number; + numberActiveTouches: number; + touchBank: TouchRecord[]; + }>; + + // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api + // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/createResponderEvent.js + type ResponderEvent = { + bubbles: boolean; + cancelable: boolean; + currentTarget?: unknown; // changed from "any" to "unknown" + defaultPrevented?: boolean; + dispatchConfig: { + registrationName?: string; + phasedRegistrationNames?: { + bubbled: string; + captured: string; + }; + }; + eventPhase?: number; + isDefaultPrevented: () => boolean; + isPropagationStopped: () => boolean; + isTrusted?: boolean; + preventDefault: () => void; + stopPropagation: () => void; + nativeEvent: TouchEvent; + persist: () => void; + target?: unknown; // changed from "any" to "unknown" + timeStamp: number; + touchHistory: TouchHistory; }; - // TODO: Confirm - type FocusProps = { + // https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api + // Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/ResponderSystem.js + interface ResponderProps { + // Direct responder events dispatched directly to responder. Do not bubble. + onResponderEnd?: (e: ResponderEvent) => void; + onResponderGrant?: (e: ResponderEvent) => void | boolean; + onResponderMove?: (e: ResponderEvent) => void; + onResponderRelease?: (e: ResponderEvent) => void; + onResponderReject?: (e: ResponderEvent) => void; + onResponderStart?: (e: ResponderEvent) => void; + onResponderTerminate?: (e: ResponderEvent) => void; + onResponderTerminationRequest?: (e: ResponderEvent) => boolean; + + // On pointer down, should this element become the responder? + onStartShouldSetResponder?: (e: ResponderEvent) => boolean; + onStartShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + + // On pointer move, should this element become the responder? + onMoveShouldSetResponder?: (e: ResponderEvent) => boolean; + onMoveShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + + // On scroll, should this element become the responder? Do no bubble + onScrollShouldSetResponder?: (e: ResponderEvent) => boolean; + onScrollShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + + // On text selection change, should this element become the responder? + onSelectionChangeShouldSetResponder?: (e: ResponderEvent) => boolean; + onSelectionChangeShouldSetResponderCapture?: (e: ResponderEvent) => boolean; + } + + // https://necolas.github.io/react-native-web/docs/interactions/#focusevent-props-api + // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js + // Extracted types from @types/react, index.d.ts + interface FocusProps { onBlur?: FocusEventHandler; onFocus?: FocusEventHandler; - }; + } - // TODO: Confirm - type KeyboardProps = { + // https://necolas.github.io/react-native-web/docs/interactions/#keyboardevent-props-api + // Extracted properties from react-native-web, packages/react-native-web/src/exports/View/types.js and packages/react-native-web/src/modules/forwardedProps/index.js + // Extracted types from @types/react, index.d.ts + interface KeyboardProps { onKeyDown?: KeyboardEventHandler; onKeyDownCapture?: KeyboardEventHandler; onKeyUp?: KeyboardEventHandler; onKeyUpCapture?: KeyboardEventHandler; - }; - - // type AnimationDirection = 'alternate' | 'alternate-reverse' | 'normal' | 'reverse'; - // type AnimationFillMode = 'none' | 'forwards' | 'backwards' | 'both'; - // type AnimationIterationCount = number | 'infinite'; - // type AnimationKeyframes = string | object; - // type AnimationPlayState = 'paused' | 'running'; - - // type AnimationStyles = { - // animationDelay?: string | string[]; - // animationDirection?: AnimationDirection | AnimationDirection[]; - // animationDuration?: string | string[]; - // animationFillMode?: AnimationFillMode | AnimationFillMode[]; - // animationIterationCount?: AnimationIterationCount | AnimationIterationCount[]; - // animationKeyframes?: AnimationKeyframes | AnimationKeyframes[]; - // animationPlayState?: AnimationPlayState | AnimationPlayState[]; - // animationTimingFunction?: string | string[]; - // transitionDelay?: string | string[]; - // transitionDuration?: string | string[]; - // transitionProperty?: string | string[]; - // transitionTimingFunction?: string | string[]; - // }; + } /** - * Image + * Shared props + * Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js */ - interface WebImageProps { - draggable?: boolean; + interface WebSharedProps extends AccessibilityProps, PointerProps, ResponderProps, FocusProps, KeyboardProps { + dataSet?: Record; + href?: string; + hrefAttrs?: { + download?: boolean; + rel?: string; + target?: string; + }; + tabIndex?: 0 | -1; + lang?: string; } - interface ImageProps extends WebImageProps {} /** - * Pressable + * View + * Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js */ - interface WebPressableProps { - delayPressIn?: number; - delayPressOut?: number; - onPressMove?: null | ((event: GestureResponderEvent) => void); - onPressEnd?: null | ((event: GestureResponderEvent) => void); - } - interface WebPressableStateCallbackType { - readonly focused: boolean; - readonly hovered: boolean; - readonly pressed: boolean; + interface WebViewProps extends WebSharedProps { + dir?: 'ltr' | 'rtl'; } - interface PressableProps extends WebPressableProps {} - interface PressableStateCallbackType extends WebPressableStateCallbackType {} + interface ViewProps extends WebViewProps {} /** * Text + * Extracted from react-native-web, packages/react-native-web/src/exports/Text/types.js */ - interface WebTextProps { - dataSet?: Record; + interface WebTextProps extends WebSharedProps { dir?: 'auto' | 'ltr' | 'rtl'; - href?: string; - hrefAttrs?: { - download?: boolean; - rel?: string; - target?: string; - }; - lang?: string; } interface TextProps extends WebTextProps {} /** * TextInput + * Extracted from react-native-web, packages/react-native-web/src/exports/TextInput/types.js */ - interface WebTextInputProps { - dataSet?: Record; + interface WebTextInputProps extends WebSharedProps { dir?: 'auto' | 'ltr' | 'rtl'; - lang?: string; disabled?: boolean; + enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'; + readOnly?: boolean; } interface TextInputProps extends WebTextInputProps {} /** - * View + * Image + * Extracted from react-native-web, packages/react-native-web/src/exports/Image/types.js */ - interface WebViewProps extends AccessibilityProps, PointerProps, FocusProps, KeyboardProps { - dataSet?: Record; + interface WebImageProps extends WebSharedProps { dir?: 'ltr' | 'rtl'; - href?: string; - hrefAttrs?: { - download?: boolean; - rel?: string; - target?: string; - }; - tabIndex?: 0 | -1; + draggable?: boolean; } - interface ViewProps extends WebViewProps {} + interface ImageProps extends WebImageProps {} + + /** + * ScrollView + * Extracted from react-native-web, packages/react-native-web/src/exports/ScrollView/ScrollViewBase.js + */ + interface WebScrollViewProps extends WebSharedProps {} + interface ScrollViewProps extends WebScrollViewProps {} + + /** + * Pressable + */ + // https://necolas.github.io/react-native-web/docs/pressable/#interactionstate + // Extracted from react-native-web, packages/react-native-web/src/exports/Pressable/index.js + interface WebPressableStateCallbackType { + readonly focused: boolean; + readonly hovered: boolean; + readonly pressed: boolean; + } + interface PressableStateCallbackType extends WebPressableStateCallbackType {} - interface WebStyle { - wordBreak?: CSSProperties['wordBreak']; - whiteSpace?: CSSProperties['whiteSpace']; - visibility?: CSSProperties['visibility']; - userSelect?: CSSProperties['userSelect']; - WebkitUserSelect?: CSSProperties['WebkitUserSelect']; - textUnderlinePosition?: CSSProperties['textUnderlinePosition']; - textDecorationSkipInk?: CSSProperties['textDecorationSkipInk']; - cursor?: CSSProperties['cursor']; - outlineWidth?: CSSProperties['outlineWidth']; - outlineStyle?: CSSProperties['outlineStyle']; - boxShadow?: CSSProperties['boxShadow']; + // Extracted from react-native-web, packages/react-native-web/src/exports/Pressable/index.js + interface WebPressableProps extends WebSharedProps { + delayPressIn?: number; + delayPressOut?: number; + onPressMove?: null | ((event: GestureResponderEvent) => void); + onPressEnd?: null | ((event: GestureResponderEvent) => void); } + interface PressableProps extends WebPressableProps {} - interface WebViewStyle { - overscrollBehaviorX?: CSSProperties['overscrollBehaviorX']; - overflowX?: CSSProperties['overflowX']; + /** + * Styles + */ + // We extend CSSProperties (alias to "csstype" library) which provides all CSS style properties for Web, + // but properties that are already defined on RN won't be overrided / augmented. + interface WebStyle extends CSSProperties { + // https://necolas.github.io/react-native-web/docs/styling/#non-standard-properties + // Exclusive to react-native-web, "pointerEvents" already included on RN + animationKeyframes?: string | Record; + writingDirection?: 'auto' | 'ltr' | 'rtl'; } - interface ViewStyle extends WebStyle, WebViewStyle {} + interface ViewStyle extends WebStyle {} interface TextStyle extends WebStyle {} interface ImageStyle extends WebStyle {} + // <------ REACT NATIVE WEB (0.19.0) ------> interface TextInput { // Typescript type declaration is missing in React Native for setting text selection. From 05d51033203f0316fede6ea0d6f533c6ea2c5fe8 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 13 Sep 2023 12:30:06 +0530 Subject: [PATCH 010/284] upgrade rn-sdk to 8.3.0 --- ios/Podfile.lock | 10 +++++----- package-lock.json | 11 +++++++---- package.json | 2 +- ...4.0.patch => @onfido+react-native-sdk+8.3.0.patch} | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) rename patches/{@onfido+react-native-sdk+7.4.0.patch => @onfido+react-native-sdk+8.3.0.patch} (90%) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index aeb1887223cd..0884383d2614 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -251,9 +251,9 @@ PODS: - nanopb/encode (= 2.30908.0) - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - - Onfido (27.4.0) - - onfido-react-native-sdk (7.4.0): - - Onfido (= 27.4.0) + - Onfido (28.3.0) + - onfido-react-native-sdk (8.3.0): + - Onfido (~> 28.3.0) - React - OpenSSL-Universal (1.1.1100) - Permission-Camera (3.6.1): @@ -1219,8 +1219,8 @@ SPEC CHECKSUMS: MapboxMaps: af50ec61a7eb3b032c3f7962c6bd671d93d2a209 MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 - Onfido: e36f284b865adcf99d9c905590a64ac09d4a576b - onfido-react-native-sdk: 4ecde1a97435dcff9f00a878e3f8d1eb14fabbdc + Onfido: c7d010d9793790d44a07799d9be25aa8e3814ee7 + onfido-react-native-sdk: b346a620af5669f9fecb6dc3052314a35a94ad9f OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c Permission-Camera: bf6791b17c7f614b6826019fcfdcc286d3a107f6 Permission-LocationAccuracy: 76df17de5c6b8bc2eee34e61ee92cdd7a864c73d diff --git a/package-lock.json b/package-lock.json index f36d0e88f52b..14152bafb55d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@kie/act-js": "^2.0.1", "@kie/mock-github": "^1.0.0", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", - "@onfido/react-native-sdk": "7.4.0", + "@onfido/react-native-sdk": "8.3.0", "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-community/clipboard": "^1.5.1", @@ -6106,8 +6106,9 @@ } }, "node_modules/@onfido/react-native-sdk": { - "version": "7.4.0", - "license": "MIT", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-8.3.0.tgz", + "integrity": "sha512-nnhuvezd35v08WXUTQlX+gr4pbnNnwNV5KscC/jJrfjGikNUJnhnAHYxfnfJccTn44qUC6vRaKWq2GfpMUnqNA==", "peerDependencies": { "react": ">=17.0.0", "react-native": ">=0.68.2 <1.0.x" @@ -52195,7 +52196,9 @@ } }, "@onfido/react-native-sdk": { - "version": "7.4.0", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@onfido/react-native-sdk/-/react-native-sdk-8.3.0.tgz", + "integrity": "sha512-nnhuvezd35v08WXUTQlX+gr4pbnNnwNV5KscC/jJrfjGikNUJnhnAHYxfnfJccTn44qUC6vRaKWq2GfpMUnqNA==", "requires": {} }, "@pkgjs/parseargs": { diff --git a/package.json b/package.json index 315d01ea5dc3..c07cf09b7233 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@kie/act-js": "^2.0.1", "@kie/mock-github": "^1.0.0", - "@onfido/react-native-sdk": "7.4.0", + "@onfido/react-native-sdk": "8.3.0", "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-community/clipboard": "^1.5.1", diff --git a/patches/@onfido+react-native-sdk+7.4.0.patch b/patches/@onfido+react-native-sdk+8.3.0.patch similarity index 90% rename from patches/@onfido+react-native-sdk+7.4.0.patch rename to patches/@onfido+react-native-sdk+8.3.0.patch index b84225c0f667..12245cb58355 100644 --- a/patches/@onfido+react-native-sdk+7.4.0.patch +++ b/patches/@onfido+react-native-sdk+8.3.0.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/@onfido/react-native-sdk/android/build.gradle b/node_modules/@onfido/react-native-sdk/android/build.gradle -index 781925b..9e16430 100644 +index b4c7106..d5083d3 100644 --- a/node_modules/@onfido/react-native-sdk/android/build.gradle +++ b/node_modules/@onfido/react-native-sdk/android/build.gradle -@@ -134,9 +134,9 @@ afterEvaluate { project -> +@@ -135,9 +135,9 @@ afterEvaluate { project -> group = "Reporting" description = "Generate Jacoco coverage reports after running tests." reports { From 091104ee92a6f1f239849829b6d0ed3cae34b939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 14 Sep 2023 17:28:11 +0100 Subject: [PATCH 011/284] Refactor styles and implement missing ones --- src/styles/StyleUtils.ts | 14 +++++++------- src/styles/cardStyles/types.ts | 2 +- .../containerComposeStyles/index.native.ts | 4 ++-- src/styles/containerComposeStyles/index.ts | 4 ++-- src/styles/containerComposeStyles/types.ts | 5 +++++ src/styles/fontFamily/bold/index.android.js | 3 --- src/styles/fontFamily/bold/index.android.ts | 5 +++++ src/styles/fontFamily/bold/index.ios.js | 3 --- src/styles/fontFamily/bold/index.ios.ts | 5 +++++ src/styles/fontFamily/bold/index.js | 3 --- src/styles/fontFamily/bold/index.ts | 5 +++++ src/styles/fontFamily/bold/types.ts | 5 +++++ src/styles/fontFamily/types.ts | 4 +++- src/styles/fontWeight/bold/types.ts | 2 +- .../index.desktop.js | 10 ---------- .../index.desktop.ts | 17 +++++++++++++++++ .../getNavigationModalCardStyles/types.ts | 3 +-- src/styles/getReportActionContextMenuStyles.ts | 8 +++----- src/styles/italic/types.ts | 2 +- .../optionAlternateTextPlatformStyles/types.ts | 2 +- src/styles/optionRowStyles/index.native.ts | 5 ++--- src/styles/optionRowStyles/index.ts | 4 ++-- src/styles/optionRowStyles/types.ts | 4 ++-- src/styles/utilities/cursor/types.ts | 4 ++-- src/styles/utilities/visibility/types.ts | 4 ++-- 25 files changed, 74 insertions(+), 53 deletions(-) create mode 100644 src/styles/containerComposeStyles/types.ts delete mode 100644 src/styles/fontFamily/bold/index.android.js create mode 100644 src/styles/fontFamily/bold/index.android.ts delete mode 100644 src/styles/fontFamily/bold/index.ios.js create mode 100644 src/styles/fontFamily/bold/index.ios.ts delete mode 100644 src/styles/fontFamily/bold/index.js create mode 100644 src/styles/fontFamily/bold/index.ts create mode 100644 src/styles/fontFamily/bold/types.ts delete mode 100644 src/styles/getNavigationModalCardStyles/index.desktop.js create mode 100644 src/styles/getNavigationModalCardStyles/index.desktop.ts diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 3daea4555796..1f88829b0e23 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -535,14 +535,14 @@ function getModalPaddingStyles({ }: ModalPaddingStylesArgs): ViewStyle { // use fallback value for safeAreaPaddingBottom to keep padding bottom consistent with padding top. // More info: issue #17376 - const safeAreaPaddingBottomWithFallback = insets.bottom === 0 ? modalContainerStylePaddingTop || 0 : safeAreaPaddingBottom; + const safeAreaPaddingBottomWithFallback = insets.bottom === 0 ? modalContainerStylePaddingTop ?? 0 : safeAreaPaddingBottom; return { - marginTop: (modalContainerStyleMarginTop || 0) + (shouldAddTopSafeAreaMargin ? safeAreaPaddingTop : 0), - marginBottom: (modalContainerStyleMarginBottom || 0) + (shouldAddBottomSafeAreaMargin ? safeAreaPaddingBottomWithFallback : 0), - paddingTop: shouldAddTopSafeAreaPadding ? (modalContainerStylePaddingTop || 0) + safeAreaPaddingTop : modalContainerStylePaddingTop || 0, - paddingBottom: shouldAddBottomSafeAreaPadding ? (modalContainerStylePaddingBottom || 0) + safeAreaPaddingBottomWithFallback : modalContainerStylePaddingBottom || 0, - paddingLeft: safeAreaPaddingLeft || 0, - paddingRight: safeAreaPaddingRight || 0, + marginTop: (modalContainerStyleMarginTop ?? 0) + (shouldAddTopSafeAreaMargin ? safeAreaPaddingTop : 0), + marginBottom: (modalContainerStyleMarginBottom ?? 0) + (shouldAddBottomSafeAreaMargin ? safeAreaPaddingBottomWithFallback : 0), + paddingTop: shouldAddTopSafeAreaPadding ? (modalContainerStylePaddingTop ?? 0) + safeAreaPaddingTop : modalContainerStylePaddingTop ?? 0, + paddingBottom: shouldAddBottomSafeAreaPadding ? (modalContainerStylePaddingBottom ?? 0) + safeAreaPaddingBottomWithFallback : modalContainerStylePaddingBottom ?? 0, + paddingLeft: safeAreaPaddingLeft ?? 0, + paddingRight: safeAreaPaddingRight ?? 0, }; } diff --git a/src/styles/cardStyles/types.ts b/src/styles/cardStyles/types.ts index 517ab76811bc..134b93eae32f 100644 --- a/src/styles/cardStyles/types.ts +++ b/src/styles/cardStyles/types.ts @@ -1,5 +1,5 @@ import {ViewStyle} from 'react-native'; -type GetCardStyles = (screenWidth: number) => Pick; +type GetCardStyles = (screenWidth: number) => ViewStyle; export default GetCardStyles; diff --git a/src/styles/containerComposeStyles/index.native.ts b/src/styles/containerComposeStyles/index.native.ts index 6b6bcf71cfcf..ea525dc652cf 100644 --- a/src/styles/containerComposeStyles/index.native.ts +++ b/src/styles/containerComposeStyles/index.native.ts @@ -1,6 +1,6 @@ -import {StyleProp, ViewStyle} from 'react-native'; import styles from '../styles'; +import ContainerComposeStyles from './types'; -const containerComposeStyles: StyleProp = [styles.textInputComposeSpacing]; +const containerComposeStyles: ContainerComposeStyles = [styles.textInputComposeSpacing]; export default containerComposeStyles; diff --git a/src/styles/containerComposeStyles/index.ts b/src/styles/containerComposeStyles/index.ts index 6968e23a507e..fbbf35a20818 100644 --- a/src/styles/containerComposeStyles/index.ts +++ b/src/styles/containerComposeStyles/index.ts @@ -1,7 +1,7 @@ -import {StyleProp, ViewStyle} from 'react-native'; import styles from '../styles'; +import ContainerComposeStyles from './types'; // We need to set paddingVertical = 0 on web to avoid displaying a normal pointer on some parts of compose box when not in focus -const containerComposeStyles: StyleProp = [styles.textInputComposeSpacing, {paddingVertical: 0}]; +const containerComposeStyles: ContainerComposeStyles = [styles.textInputComposeSpacing, {paddingVertical: 0}]; export default containerComposeStyles; diff --git a/src/styles/containerComposeStyles/types.ts b/src/styles/containerComposeStyles/types.ts new file mode 100644 index 000000000000..278039691b8a --- /dev/null +++ b/src/styles/containerComposeStyles/types.ts @@ -0,0 +1,5 @@ +import {ViewStyle} from 'react-native'; + +type ContainerComposeStyles = ViewStyle[]; + +export default ContainerComposeStyles; diff --git a/src/styles/fontFamily/bold/index.android.js b/src/styles/fontFamily/bold/index.android.js deleted file mode 100644 index 7473e4d7533c..000000000000 --- a/src/styles/fontFamily/bold/index.android.js +++ /dev/null @@ -1,3 +0,0 @@ -const bold = 'ExpensifyNeue-Bold'; - -export default bold; diff --git a/src/styles/fontFamily/bold/index.android.ts b/src/styles/fontFamily/bold/index.android.ts new file mode 100644 index 000000000000..563e063e1bd7 --- /dev/null +++ b/src/styles/fontFamily/bold/index.android.ts @@ -0,0 +1,5 @@ +import FontFamilyBoldStyles from './types'; + +const bold: FontFamilyBoldStyles = 'ExpensifyNeue-Bold'; + +export default bold; diff --git a/src/styles/fontFamily/bold/index.ios.js b/src/styles/fontFamily/bold/index.ios.js deleted file mode 100644 index 3ba35f200d3d..000000000000 --- a/src/styles/fontFamily/bold/index.ios.js +++ /dev/null @@ -1,3 +0,0 @@ -const bold = 'ExpensifyNeue-Regular'; - -export default bold; diff --git a/src/styles/fontFamily/bold/index.ios.ts b/src/styles/fontFamily/bold/index.ios.ts new file mode 100644 index 000000000000..f019dd47fc2a --- /dev/null +++ b/src/styles/fontFamily/bold/index.ios.ts @@ -0,0 +1,5 @@ +import FontFamilyBoldStyles from './types'; + +const bold: FontFamilyBoldStyles = 'ExpensifyNeue-Regular'; + +export default bold; diff --git a/src/styles/fontFamily/bold/index.js b/src/styles/fontFamily/bold/index.js deleted file mode 100644 index 66d3c64f3565..000000000000 --- a/src/styles/fontFamily/bold/index.js +++ /dev/null @@ -1,3 +0,0 @@ -const bold = 'ExpensifyNeue-Regular, Segoe UI Emoji, Noto Color Emoji'; - -export default bold; diff --git a/src/styles/fontFamily/bold/index.ts b/src/styles/fontFamily/bold/index.ts new file mode 100644 index 000000000000..96c9846df985 --- /dev/null +++ b/src/styles/fontFamily/bold/index.ts @@ -0,0 +1,5 @@ +import FontFamilyBoldStyles from './types'; + +const bold: FontFamilyBoldStyles = 'ExpensifyNeue-Regular, Segoe UI Emoji, Noto Color Emoji'; + +export default bold; diff --git a/src/styles/fontFamily/bold/types.ts b/src/styles/fontFamily/bold/types.ts new file mode 100644 index 000000000000..258b23de94a2 --- /dev/null +++ b/src/styles/fontFamily/bold/types.ts @@ -0,0 +1,5 @@ +import {TextStyle} from 'react-native'; + +type FontFamilyBoldStyles = NonNullable; + +export default FontFamilyBoldStyles; diff --git a/src/styles/fontFamily/types.ts b/src/styles/fontFamily/types.ts index 4c9a121e80d7..c688f40927be 100644 --- a/src/styles/fontFamily/types.ts +++ b/src/styles/fontFamily/types.ts @@ -1,3 +1,5 @@ +import {TextStyle} from 'react-native'; + type FontFamilyKeys = | 'EXP_NEUE_ITALIC' | 'EXP_NEUE_BOLD' @@ -10,6 +12,6 @@ type FontFamilyKeys = | 'MONOSPACE_BOLD' | 'MONOSPACE_BOLD_ITALIC'; -type FontFamilyStyles = Record; +type FontFamilyStyles = Record>; export default FontFamilyStyles; diff --git a/src/styles/fontWeight/bold/types.ts b/src/styles/fontWeight/bold/types.ts index 67258eee719c..00e72d0e879c 100644 --- a/src/styles/fontWeight/bold/types.ts +++ b/src/styles/fontWeight/bold/types.ts @@ -1,5 +1,5 @@ import {TextStyle} from 'react-native'; -type FontWeightBoldStyles = TextStyle['fontWeight']; +type FontWeightBoldStyles = NonNullable; export default FontWeightBoldStyles; diff --git a/src/styles/getNavigationModalCardStyles/index.desktop.js b/src/styles/getNavigationModalCardStyles/index.desktop.js deleted file mode 100644 index 54c9790253d6..000000000000 --- a/src/styles/getNavigationModalCardStyles/index.desktop.js +++ /dev/null @@ -1,10 +0,0 @@ -export default () => ({ - // position: fixed is set instead of position absolute to workaround Safari known issues of updating heights in DOM. - // Safari issues: - // https://github.com/Expensify/App/issues/12005 - // https://github.com/Expensify/App/issues/17824 - // https://github.com/Expensify/App/issues/20709 - width: '100%', - height: '100%', - position: 'fixed', -}); diff --git a/src/styles/getNavigationModalCardStyles/index.desktop.ts b/src/styles/getNavigationModalCardStyles/index.desktop.ts new file mode 100644 index 000000000000..422a17d0a9a8 --- /dev/null +++ b/src/styles/getNavigationModalCardStyles/index.desktop.ts @@ -0,0 +1,17 @@ +import {ViewStyle} from 'react-native'; +import GetNavigationModalCardStyles from './types'; + +const getNavigationModalCardStyles: GetNavigationModalCardStyles = () => ({ + // position: fixed is set instead of position absolute to workaround Safari known issues of updating heights in DOM. + // Safari issues: + // https://github.com/Expensify/App/issues/12005 + // https://github.com/Expensify/App/issues/17824 + // https://github.com/Expensify/App/issues/20709 + width: '100%', + height: '100%', + + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + position: 'fixed' as ViewStyle['position'], +}); + +export default getNavigationModalCardStyles; diff --git a/src/styles/getNavigationModalCardStyles/types.ts b/src/styles/getNavigationModalCardStyles/types.ts index 68453dc3c3de..877981dd4dd2 100644 --- a/src/styles/getNavigationModalCardStyles/types.ts +++ b/src/styles/getNavigationModalCardStyles/types.ts @@ -1,8 +1,7 @@ import {ViewStyle} from 'react-native'; -import {Merge} from 'type-fest'; type GetNavigationModalCardStylesParams = {isSmallScreenWidth: number}; -type GetNavigationModalCardStyles = (params: GetNavigationModalCardStylesParams) => Merge>; +type GetNavigationModalCardStyles = (params: GetNavigationModalCardStylesParams) => ViewStyle; export default GetNavigationModalCardStyles; diff --git a/src/styles/getReportActionContextMenuStyles.ts b/src/styles/getReportActionContextMenuStyles.ts index 150c9786aaca..6b4ad8807552 100644 --- a/src/styles/getReportActionContextMenuStyles.ts +++ b/src/styles/getReportActionContextMenuStyles.ts @@ -3,13 +3,11 @@ import styles from './styles'; import themeColors from './themes/default'; import variables from './variables'; -type StylesArray = ViewStyle[]; - const defaultWrapperStyle: ViewStyle = { backgroundColor: themeColors.componentBG, }; -const miniWrapperStyle: StylesArray = [ +const miniWrapperStyle: ViewStyle[] = [ styles.flexRow, defaultWrapperStyle, { @@ -22,7 +20,7 @@ const miniWrapperStyle: StylesArray = [ }, ]; -const bigWrapperStyle: StylesArray = [styles.flexColumn, defaultWrapperStyle]; +const bigWrapperStyle: ViewStyle[] = [styles.flexColumn, defaultWrapperStyle]; /** * Generate the wrapper styles for the ReportActionContextMenu. @@ -30,7 +28,7 @@ const bigWrapperStyle: StylesArray = [styles.flexColumn, defaultWrapperStyle]; * @param isMini * @param isSmallScreenWidth */ -function getReportActionContextMenuStyles(isMini: boolean, isSmallScreenWidth: boolean): StylesArray { +function getReportActionContextMenuStyles(isMini: boolean, isSmallScreenWidth: boolean): ViewStyle[] { if (isMini) { return miniWrapperStyle; } diff --git a/src/styles/italic/types.ts b/src/styles/italic/types.ts index 0935c5844bb3..e9feedbdfac5 100644 --- a/src/styles/italic/types.ts +++ b/src/styles/italic/types.ts @@ -1,5 +1,5 @@ import {TextStyle} from 'react-native'; -type ItalicStyles = TextStyle['fontStyle']; +type ItalicStyles = NonNullable; export default ItalicStyles; diff --git a/src/styles/optionAlternateTextPlatformStyles/types.ts b/src/styles/optionAlternateTextPlatformStyles/types.ts index b2e8e4745fff..aacdef7e3501 100644 --- a/src/styles/optionAlternateTextPlatformStyles/types.ts +++ b/src/styles/optionAlternateTextPlatformStyles/types.ts @@ -1,5 +1,5 @@ import {TextStyle} from 'react-native'; -type OptionAlternateTextPlatformStyles = Partial>; +type OptionAlternateTextPlatformStyles = Pick; export default OptionAlternateTextPlatformStyles; diff --git a/src/styles/optionRowStyles/index.native.ts b/src/styles/optionRowStyles/index.native.ts index 11371509ce73..9c13fdd082a4 100644 --- a/src/styles/optionRowStyles/index.native.ts +++ b/src/styles/optionRowStyles/index.native.ts @@ -1,5 +1,5 @@ -import OptionRowStyles from './types'; import styles from '../styles'; +import CompactContentContainerStyles from './types'; /** * On native platforms, alignItemsBaseline does not work correctly @@ -7,8 +7,7 @@ import styles from '../styles'; * keeping compactContentContainerStyles as it is. * https://github.com/Expensify/App/issues/14148 */ - -const compactContentContainerStyles: OptionRowStyles = styles.alignItemsCenter; +const compactContentContainerStyles: CompactContentContainerStyles = styles.alignItemsCenter; export { // eslint-disable-next-line import/prefer-default-export diff --git a/src/styles/optionRowStyles/index.ts b/src/styles/optionRowStyles/index.ts index fbeca3c702d9..975f4243842e 100644 --- a/src/styles/optionRowStyles/index.ts +++ b/src/styles/optionRowStyles/index.ts @@ -1,7 +1,7 @@ -import OptionRowStyles from './types'; +import CompactContentContainerStyles from './types'; import styles from '../styles'; -const compactContentContainerStyles: OptionRowStyles = styles.alignItemsBaseline; +const compactContentContainerStyles: CompactContentContainerStyles = styles.alignItemsBaseline; export { // eslint-disable-next-line import/prefer-default-export diff --git a/src/styles/optionRowStyles/types.ts b/src/styles/optionRowStyles/types.ts index f645c6038397..fcce41f6bc28 100644 --- a/src/styles/optionRowStyles/types.ts +++ b/src/styles/optionRowStyles/types.ts @@ -1,5 +1,5 @@ import {ViewStyle} from 'react-native'; -type OptionRowStyles = ViewStyle; +type CompactContentContainerStyles = ViewStyle; -export default OptionRowStyles; +export default CompactContentContainerStyles; diff --git a/src/styles/utilities/cursor/types.ts b/src/styles/utilities/cursor/types.ts index 98d661491c4b..e9cfc120b161 100644 --- a/src/styles/utilities/cursor/types.ts +++ b/src/styles/utilities/cursor/types.ts @@ -1,4 +1,4 @@ -import {TextStyle, ViewStyle} from 'react-native'; +import {ViewStyle} from 'react-native'; type CursorStylesKeys = | 'cursorDefault' @@ -13,6 +13,6 @@ type CursorStylesKeys = | 'cursorInitial' | 'cursorText'; -type CursorStyles = Record>; +type CursorStyles = Record>; export default CursorStyles; diff --git a/src/styles/utilities/visibility/types.ts b/src/styles/utilities/visibility/types.ts index 872e35195edd..64bdbdd2cca6 100644 --- a/src/styles/utilities/visibility/types.ts +++ b/src/styles/utilities/visibility/types.ts @@ -1,5 +1,5 @@ -import {TextStyle, ViewStyle} from 'react-native'; +import {ViewStyle} from 'react-native'; -type VisibilityStyles = Record<'visible' | 'hidden', Pick>; +type VisibilityStyles = Record<'visible' | 'hidden', Pick>; export default VisibilityStyles; From e4a3313b9cd29ffe80589430c595bcf6b2bea605 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 15 Sep 2023 13:30:09 +0200 Subject: [PATCH 012/284] Migrate styles.ts --- src/styles/{styles.js => styles.ts} | 100 ++++++++----------- src/styles/themes/{default.js => default.ts} | 20 ++-- src/styles/themes/{light.js => light.ts} | 22 ++-- src/styles/themes/types.ts | 8 ++ src/types/utils/DeepRecord.ts | 7 ++ 5 files changed, 79 insertions(+), 78 deletions(-) rename src/styles/{styles.js => styles.ts} (97%) rename src/styles/themes/{default.js => default.ts} (88%) rename src/styles/themes/{light.js => light.ts} (86%) create mode 100644 src/styles/themes/types.ts create mode 100644 src/types/utils/DeepRecord.ts diff --git a/src/styles/styles.js b/src/styles/styles.ts similarity index 97% rename from src/styles/styles.js rename to src/styles/styles.ts index 0fba61f1e8d9..15c1a6b9269f 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.ts @@ -1,4 +1,3 @@ -import {defaultStyles as defaultPickerStyles} from 'react-native-picker-select/src/styles'; import lodashClamp from 'lodash/clamp'; import fontFamily from './fontFamily'; import addOutlineWidth from './addOutlineWidth'; @@ -26,11 +25,16 @@ import * as Browser from '../libs/Browser'; import cursor from './utilities/cursor'; import userSelect from './utilities/userSelect'; import textUnderline from './utilities/textUnderline'; +import {ThemeDefault} from './themes/types'; +import {AnimatableNumericValue, Animated, TransformsStyle} from 'react-native'; +import {ValueOf} from 'type-fest'; + +type Translation = 'perspective' | 'rotate' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scaleX' | 'scaleY' | 'translateX' | 'translateY' | 'skewX' | 'skewY' | 'matrix'; // touchCallout is an iOS safari only property that controls the display of the callout information when you touch and hold a target const touchCalloutNone = Browser.isMobileSafari() ? {WebkitTouchCallout: 'none'} : {}; -const picker = (theme) => ({ +const picker = (theme: ThemeDefault) => ({ backgroundColor: theme.transparent, color: theme.text, fontFamily: fontFamily.EXP_NEUE, @@ -45,13 +49,13 @@ const picker = (theme) => ({ textAlign: 'left', }); -const link = (theme) => ({ +const link = (theme: ThemeDefault) => ({ color: theme.link, textDecorationColor: theme.link, fontFamily: fontFamily.EXP_NEUE, }); -const baseCodeTagStyles = (theme) => ({ +const baseCodeTagStyles = (theme: ThemeDefault) => ({ borderWidth: 1, borderRadius: 5, borderColor: theme.border, @@ -61,9 +65,9 @@ const baseCodeTagStyles = (theme) => ({ const headlineFont = { fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM, fontWeight: '500', -}; +} as const; -const webViewStyles = (theme) => ({ +const webViewStyles = (theme: ThemeDefault) => ({ // As of react-native-render-html v6, don't declare distinct styles for // custom renderers, the API for custom renderers has changed. Declare the // styles in the below "tagStyles" instead. If you need to reuse those @@ -156,7 +160,7 @@ const webViewStyles = (theme) => ({ }, }); -const styles = (theme) => ({ +const styles = (theme: ThemeDefault) => ({ // Add all of our utility and helper styles ...spacing, ...sizing, @@ -675,7 +679,7 @@ const styles = (theme) => ({ color: theme.text, }, doneDepressed: { - fontSize: defaultPickerStyles.done.fontSize, + fontSize: 17, }, modalViewMiddle: { backgroundColor: theme.border, @@ -792,7 +796,7 @@ const styles = (theme) => ({ color: theme.textSupporting, }, - uploadReceiptView: (isSmallScreenWidth) => ({ + uploadReceiptView: (isSmallScreenWidth: boolean) => ({ borderRadius: variables.componentBorderRadiusLarge, borderWidth: isSmallScreenWidth ? 0 : 2, borderColor: theme.borderFocus, @@ -922,18 +926,12 @@ const styles = (theme) => ({ backgroundColor: theme.buttonDefaultBG, }, - /** - * @param {number} textInputHeight - * @param {number} minHeight - * @param {number} maxHeight - * @returns {object} - */ - autoGrowHeightInputContainer: (textInputHeight, minHeight, maxHeight) => ({ + autoGrowHeightInputContainer: (textInputHeight: number, minHeight: number, maxHeight: number) => ({ height: lodashClamp(textInputHeight, minHeight, maxHeight), minHeight, }), - autoGrowHeightHiddenInput: (maxWidth, maxHeight) => ({ + autoGrowHeightHiddenInput: (maxWidth: number, maxHeight?: number) => ({ maxWidth, maxHeight: maxHeight && maxHeight + 1, overflow: 'hidden', @@ -971,7 +969,7 @@ const styles = (theme) => ({ transformOrigin: 'left center', }, - textInputLabelTransformation: (translateY, translateX, scale) => ({ + textInputLabelTransformation: (translateY: AnimatableNumericValue, translateX: AnimatableNumericValue, scale: AnimatableNumericValue) => ({ transform: [{translateY}, {translateX}, {scale}], }), @@ -1092,7 +1090,7 @@ const styles = (theme) => ({ color: theme.text, }, doneDepressed: { - fontSize: defaultPickerStyles.done.fontSize, + fontSize: 17, }, modalViewMiddle: { backgroundColor: theme.border, @@ -1358,7 +1356,7 @@ const styles = (theme) => ({ textDecorationLine: 'none', }, - RHPNavigatorContainer: (isSmallScreenWidth) => ({ + RHPNavigatorContainer: (isSmallScreenWidth: boolean) => ({ width: isSmallScreenWidth ? '100%' : variables.sideBarWidth, position: 'absolute', right: 0, @@ -1374,17 +1372,17 @@ const styles = (theme) => ({ lineHeight: variables.fontSizeOnlyEmojisHeight, }, - createMenuPositionSidebar: (windowHeight) => ({ + createMenuPositionSidebar: (windowHeight: number) => ({ horizontal: 18, vertical: windowHeight - 100, }), - createMenuPositionProfile: (windowWidth) => ({ + createMenuPositionProfile: (windowWidth: number) => ({ horizontal: windowWidth - 355, ...getPopOverVerticalOffset(162), }), - createMenuPositionReportActionCompose: (windowHeight) => ({ + createMenuPositionReportActionCompose: (windowHeight: number) => ({ horizontal: 18 + variables.sideBarWidth, vertical: windowHeight - 83, }), @@ -1541,7 +1539,7 @@ const styles = (theme) => ({ height: variables.optionsListSectionHeaderHeight, }, - overlayStyles: (current) => ({ + overlayStyles: (current: {progress: Animated.AnimatedInterpolation}) => ({ position: 'fixed', // We need to stretch the overlay to cover the sidebar and the translate animation distance. @@ -2167,7 +2165,7 @@ const styles = (theme) => ({ outline: 'none', }, - getPDFPasswordFormStyle: (isSmallScreenWidth) => ({ + getPDFPasswordFormStyle: (isSmallScreenWidth: boolean) => ({ width: isSmallScreenWidth ? '100%' : 350, ...(isSmallScreenWidth && flex.flex1), }), @@ -2180,7 +2178,7 @@ const styles = (theme) => ({ backgroundColor: theme.modalBackdrop, }, - centeredModalStyles: (isSmallScreenWidth, isFullScreenWhenSmall) => ({ + centeredModalStyles: (isSmallScreenWidth: boolean, isFullScreenWhenSmall: boolean) => ({ borderWidth: isSmallScreenWidth && !isFullScreenWhenSmall ? 1 : 0, marginHorizontal: isSmallScreenWidth ? 0 : 20, }), @@ -2269,7 +2267,6 @@ const styles = (theme) => ({ }, reportDetailsTitleContainer: { - ...flex.dFlex, ...flex.flexColumn, ...flex.alignItemsCenter, paddingHorizontal: 20, @@ -2278,7 +2275,6 @@ const styles = (theme) => ({ reportDetailsRoomInfo: { ...flex.flex1, - ...flex.dFlex, ...flex.flexColumn, ...flex.alignItemsCenter, }, @@ -2315,15 +2311,15 @@ const styles = (theme) => ({ padding: 0, }, - twoFactorAuthCodesBox: ({isExtraSmallScreenWidth, isSmallScreenWidth}) => { - let paddingHorizontal = styles.ph9; + twoFactorAuthCodesBox: ({isExtraSmallScreenWidth, isSmallScreenWidth}: {isExtraSmallScreenWidth: boolean; isSmallScreenWidth: boolean}) => { + let paddingHorizontal = spacing.ph9; if (isSmallScreenWidth) { - paddingHorizontal = styles.ph4; + paddingHorizontal = spacing.ph4; } if (isExtraSmallScreenWidth) { - paddingHorizontal = styles.ph2; + paddingHorizontal = spacing.ph2; } return { @@ -2373,7 +2369,7 @@ const styles = (theme) => ({ minWidth: 110, }, - anonymousRoomFooter: (isSmallSizeLayout) => ({ + anonymousRoomFooter: (isSmallSizeLayout: boolean) => ({ flexDirection: isSmallSizeLayout ? 'column' : 'row', ...(!isSmallSizeLayout && { alignItems: 'center', @@ -2384,7 +2380,7 @@ const styles = (theme) => ({ borderRadius: variables.componentBorderRadiusLarge, overflow: 'hidden', }), - anonymousRoomFooterWordmarkAndLogoContainer: (isSmallSizeLayout) => ({ + anonymousRoomFooterWordmarkAndLogoContainer: (isSmallSizeLayout: boolean) => ({ flexDirection: 'row', alignItems: 'center', ...(isSmallSizeLayout && { @@ -2433,8 +2429,8 @@ const styles = (theme) => ({ borderRadius: 88, }, - rootNavigatorContainerStyles: (isSmallScreenWidth) => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1}), - RHPNavigatorContainerNavigatorContainerStyles: (isSmallScreenWidth) => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1}), + rootNavigatorContainerStyles: (isSmallScreenWidth: boolean) => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1}), + RHPNavigatorContainerNavigatorContainerStyles: (isSmallScreenWidth: boolean) => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1}), avatarInnerTextChat: { color: theme.textLight, @@ -2619,7 +2615,7 @@ const styles = (theme) => ({ backgroundColor: theme.appBG, }, - switchThumbTransformation: (translateX) => ({ + switchThumbTransformation: (translateX: AnimatableNumericValue) => ({ transform: [{translateX}], }), @@ -2831,11 +2827,11 @@ const styles = (theme) => ({ position: 'fixed', }, - growlNotificationTranslateY: (y) => ({ - transform: [{translateY: y}], + growlNotificationTranslateY: (translateY: AnimatableNumericValue) => ({ + transform: [{translateY}], }), - makeSlideInTranslation: (translationType, fromValue) => ({ + makeSlideInTranslation: (translationType: Translation, fromValue: number) => ({ from: { [translationType]: fromValue, }, @@ -3135,7 +3131,7 @@ const styles = (theme) => ({ ...visibility.visible, }, - floatingMessageCounterTransformation: (translateY) => ({ + floatingMessageCounterTransformation: (translateY: AnimatableNumericValue) => ({ transform: [{translateY}], }), @@ -3162,12 +3158,12 @@ const styles = (theme) => ({ flex: 1, }, - threeDotsPopoverOffset: (windowWidth) => ({ + threeDotsPopoverOffset: (windowWidth: number) => ({ ...getPopOverVerticalOffset(60), horizontal: windowWidth - 60, }), - threeDotsPopoverOffsetNoCloseButton: (windowWidth) => ({ + threeDotsPopoverOffsetNoCloseButton: (windowWidth: number) => ({ ...getPopOverVerticalOffset(60), horizontal: windowWidth - 10, }), @@ -3310,7 +3306,7 @@ const styles = (theme) => ({ zIndex: 2, }, - receiptImageWrapper: (receiptImageTopPosition) => ({ + receiptImageWrapper: (receiptImageTopPosition: number) => ({ position: 'absolute', top: receiptImageTopPosition, }), @@ -3579,8 +3575,6 @@ const styles = (theme) => ({ taskTitleMenuItem: { ...writingDirection.ltr, ...headlineFont, - ...spacing.flexWrap, - ...spacing.flex1, fontSize: variables.fontSizeXLarge, maxWidth: '100%', ...wordBreak.breakWord, @@ -3622,8 +3616,6 @@ const styles = (theme) => ({ marginLeft: 'auto', ...spacing.mt1, ...pointerEventsAuto, - ...spacing.dFlex, - ...spacing.alignItemsCenter, }, shareCodePage: { @@ -3735,19 +3727,14 @@ const styles = (theme) => ({ paddingBottom: 12, }, - tabText: (isSelected) => ({ + tabText: (isSelected: boolean) => ({ marginLeft: 8, fontFamily: isSelected ? fontFamily.EXP_NEUE_BOLD : fontFamily.EXP_NEUE, fontWeight: isSelected ? fontWeightBold : 400, color: isSelected ? theme.textLight : theme.textSupporting, }), - /** - * @param {String} backgroundColor - * @param {Number} height - * @returns {Object} - */ - overscrollSpacer: (backgroundColor, height) => ({ + overscrollSpacer: (backgroundColor: string, height: number) => ({ backgroundColor, height, width: '100%', @@ -3910,7 +3897,7 @@ const styles = (theme) => ({ maxWidth: 400, }, - distanceRequestContainer: (maxHeight) => ({ + distanceRequestContainer: (maxHeight: number) => ({ ...flex.flexShrink2, minHeight: variables.optionRowHeight * 2, maxHeight, @@ -3919,7 +3906,6 @@ const styles = (theme) => ({ mapViewContainer: { ...flex.flex1, ...spacing.p4, - ...spacing.flex1, minHeight: 300, maxHeight: 500, }, diff --git a/src/styles/themes/default.js b/src/styles/themes/default.ts similarity index 88% rename from src/styles/themes/default.js rename to src/styles/themes/default.ts index c101a668666b..9b6cca499ae5 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.ts @@ -2,6 +2,7 @@ import colors from '../colors'; import SCREENS from '../../SCREENS'; import ROUTES from '../../ROUTES'; +import type {ThemeBase} from './types'; const darkTheme = { // Figma keys @@ -82,15 +83,14 @@ const darkTheme = { skeletonLHNOut: colors.darkDefaultButton, QRLogo: colors.green400, starDefaultBG: 'rgb(254, 228, 94)', -}; - -darkTheme.PAGE_BACKGROUND_COLORS = { - [SCREENS.HOME]: darkTheme.sidebar, - [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, - [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, - [ROUTES.SETTINGS_STATUS]: colors.green700, - [ROUTES.I_KNOW_A_TEACHER]: colors.tangerine800, - [ROUTES.SETTINGS_SECURITY]: colors.ice500, -}; + PAGE_BACKGROUND_COLORS: { + [SCREENS.HOME]: colors.darkHighlightBackground, + [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, + [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, + [ROUTES.SETTINGS_STATUS]: colors.green700, + [ROUTES.I_KNOW_A_TEACHER]: colors.tangerine800, + [ROUTES.SETTINGS_SECURITY]: colors.ice500, + }, +} satisfies ThemeBase; export default darkTheme; diff --git a/src/styles/themes/light.js b/src/styles/themes/light.ts similarity index 86% rename from src/styles/themes/light.js rename to src/styles/themes/light.ts index 1a945cb84913..a14d6b0b89b4 100644 --- a/src/styles/themes/light.js +++ b/src/styles/themes/light.ts @@ -1,6 +1,7 @@ import colors from '../colors'; import SCREENS from '../../SCREENS'; import ROUTES from '../../ROUTES'; +import type {ThemeDefault} from './types'; const lightTheme = { // Figma keys @@ -62,7 +63,7 @@ const lightTheme = { heroCard: colors.blue400, uploadPreviewActivityIndicator: colors.lightHighlightBackground, dropUIBG: 'rgba(252, 251, 249, 0.92)', - dropTransparentOverlay: 'rgba(255,255,255,0)', + receiptDropUIBG: '', // TODO: add color checkBox: colors.green400, pickerOptionsTextColor: colors.lightPrimaryText, imageCropBackgroundColor: colors.lightIcons, @@ -81,15 +82,14 @@ const lightTheme = { skeletonLHNOut: colors.lightDefaultButtonPressed, QRLogo: colors.green400, starDefaultBG: 'rgb(254, 228, 94)', -}; - -lightTheme.PAGE_BACKGROUND_COLORS = { - [SCREENS.HOME]: lightTheme.sidebar, - [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, - [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, - [ROUTES.SETTINGS_STATUS]: colors.green700, - [ROUTES.I_KNOW_A_TEACHER]: colors.tangerine800, - [ROUTES.SETTINGS_SECURITY]: colors.ice500, -}; + PAGE_BACKGROUND_COLORS: { + [SCREENS.HOME]: colors.lightHighlightBackground, + [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, + [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, + [ROUTES.SETTINGS_STATUS]: colors.green700, + [ROUTES.I_KNOW_A_TEACHER]: colors.tangerine800, + [ROUTES.SETTINGS_SECURITY]: colors.ice500, + }, +} satisfies ThemeDefault; export default lightTheme; diff --git a/src/styles/themes/types.ts b/src/styles/themes/types.ts new file mode 100644 index 000000000000..40b8da361654 --- /dev/null +++ b/src/styles/themes/types.ts @@ -0,0 +1,8 @@ +import DeepRecord from '../../types/utils/DeepRecord'; +import defaultTheme from './default'; + +type ThemeBase = DeepRecord; + +type ThemeDefault = typeof defaultTheme; + +export type {ThemeBase, ThemeDefault}; diff --git a/src/types/utils/DeepRecord.ts b/src/types/utils/DeepRecord.ts new file mode 100644 index 000000000000..fba14c75d679 --- /dev/null +++ b/src/types/utils/DeepRecord.ts @@ -0,0 +1,7 @@ +/** + * Represents a deeply nested record. It maps keys to values, + * and those values can either be of type `TValue` or further nested `DeepRecord` instances. + */ +type DeepRecord = {[key: string]: TValue | DeepRecord}; + +export default DeepRecord; From b8d36f774f04737efe9f04b7524beb39f848db1d Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 15 Sep 2023 13:31:10 +0200 Subject: [PATCH 013/284] Remove unused imports --- src/styles/styles.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 15c1a6b9269f..88b6b161b122 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -26,8 +26,7 @@ import cursor from './utilities/cursor'; import userSelect from './utilities/userSelect'; import textUnderline from './utilities/textUnderline'; import {ThemeDefault} from './themes/types'; -import {AnimatableNumericValue, Animated, TransformsStyle} from 'react-native'; -import {ValueOf} from 'type-fest'; +import {AnimatableNumericValue, Animated} from 'react-native'; type Translation = 'perspective' | 'rotate' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scaleX' | 'scaleY' | 'translateX' | 'translateY' | 'skewX' | 'skewY' | 'matrix'; From 24f363bcd791a037a077cc358dbdb0fbe57f6fd4 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 15 Sep 2023 14:01:45 +0200 Subject: [PATCH 014/284] Fix return types in styles.ts --- src/styles/styles.ts | 95 ++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 88b6b161b122..927dda71626c 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -1,4 +1,10 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import lodashClamp from 'lodash/clamp'; +import {AnimatableNumericValue, Animated, TextStyle, ViewStyle} from 'react-native'; +import {MixedStyleDeclaration, MixedStyleRecord} from 'react-native-render-html'; +import {PickerStyle} from 'react-native-picker-select'; +import {CustomAnimation} from 'react-native-animatable'; +import {EmptyObject} from 'type-fest'; import fontFamily from './fontFamily'; import addOutlineWidth from './addOutlineWidth'; import defaultTheme from './themes/default'; @@ -26,14 +32,13 @@ import cursor from './utilities/cursor'; import userSelect from './utilities/userSelect'; import textUnderline from './utilities/textUnderline'; import {ThemeDefault} from './themes/types'; -import {AnimatableNumericValue, Animated} from 'react-native'; type Translation = 'perspective' | 'rotate' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scaleX' | 'scaleY' | 'translateX' | 'translateY' | 'skewX' | 'skewY' | 'matrix'; // touchCallout is an iOS safari only property that controls the display of the callout information when you touch and hold a target -const touchCalloutNone = Browser.isMobileSafari() ? {WebkitTouchCallout: 'none'} : {}; +const touchCalloutNone: {WebkitTouchCallout: 'none'} | EmptyObject = Browser.isMobileSafari() ? {WebkitTouchCallout: 'none'} : {}; -const picker = (theme: ThemeDefault) => ({ +const picker = (theme: ThemeDefault): TextStyle => ({ backgroundColor: theme.transparent, color: theme.text, fontFamily: fontFamily.EXP_NEUE, @@ -48,13 +53,13 @@ const picker = (theme: ThemeDefault) => ({ textAlign: 'left', }); -const link = (theme: ThemeDefault) => ({ +const link = (theme: ThemeDefault): ViewStyle & MixedStyleDeclaration => ({ color: theme.link, textDecorationColor: theme.link, fontFamily: fontFamily.EXP_NEUE, }); -const baseCodeTagStyles = (theme: ThemeDefault) => ({ +const baseCodeTagStyles = (theme: ThemeDefault): ViewStyle & MixedStyleDeclaration => ({ borderWidth: 1, borderRadius: 5, borderColor: theme.border, @@ -66,7 +71,12 @@ const headlineFont = { fontWeight: '500', } as const; -const webViewStyles = (theme: ThemeDefault) => ({ +type WebViewStyles = { + tagStyles: MixedStyleRecord; + baseFontStyle: MixedStyleDeclaration; +}; + +const webViewStyles = (theme: ThemeDefault): WebViewStyles => ({ // As of react-native-render-html v6, don't declare distinct styles for // custom renderers, the API for custom renderers has changed. Declare the // styles in the below "tagStyles" instead. If you need to reuse those @@ -126,7 +136,7 @@ const webViewStyles = (theme: ThemeDefault) => ({ code: { ...baseCodeTagStyles(theme), - ...codeStyles.codeTextStyle, + ...(codeStyles.codeTextStyle as MixedStyleDeclaration), paddingLeft: 5, paddingRight: 5, fontFamily: fontFamily.MONOSPACE, @@ -159,6 +169,11 @@ const webViewStyles = (theme: ThemeDefault) => ({ }, }); +type AnchorPosition = { + horizontal: number; + vertical: number; +}; + const styles = (theme: ThemeDefault) => ({ // Add all of our utility and helper styles ...spacing, @@ -660,7 +675,7 @@ const styles = (theme: ThemeDefault) => ({ height: 140, }, - pickerSmall: (backgroundColor = theme.highlightBG) => ({ + pickerSmall: (backgroundColor = theme.highlightBG): PickerStyle & {icon: ViewStyle} => ({ inputIOS: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeSmall, @@ -795,7 +810,7 @@ const styles = (theme: ThemeDefault) => ({ color: theme.textSupporting, }, - uploadReceiptView: (isSmallScreenWidth: boolean) => ({ + uploadReceiptView: (isSmallScreenWidth: boolean): ViewStyle => ({ borderRadius: variables.componentBorderRadiusLarge, borderWidth: isSmallScreenWidth ? 0 : 2, borderColor: theme.borderFocus, @@ -925,12 +940,12 @@ const styles = (theme: ThemeDefault) => ({ backgroundColor: theme.buttonDefaultBG, }, - autoGrowHeightInputContainer: (textInputHeight: number, minHeight: number, maxHeight: number) => ({ + autoGrowHeightInputContainer: (textInputHeight: number, minHeight: number, maxHeight: number): ViewStyle => ({ height: lodashClamp(textInputHeight, minHeight, maxHeight), minHeight, }), - autoGrowHeightHiddenInput: (maxWidth: number, maxHeight?: number) => ({ + autoGrowHeightHiddenInput: (maxWidth: number, maxHeight?: number): TextStyle => ({ maxWidth, maxHeight: maxHeight && maxHeight + 1, overflow: 'hidden', @@ -968,7 +983,7 @@ const styles = (theme: ThemeDefault) => ({ transformOrigin: 'left center', }, - textInputLabelTransformation: (translateY: AnimatableNumericValue, translateX: AnimatableNumericValue, scale: AnimatableNumericValue) => ({ + textInputLabelTransformation: (translateY: AnimatableNumericValue, translateX: AnimatableNumericValue, scale: AnimatableNumericValue): TextStyle => ({ transform: [{translateY}, {translateX}, {scale}], }), @@ -1068,7 +1083,7 @@ const styles = (theme: ThemeDefault) => ({ zIndex: 1, }, - picker: (disabled = false, backgroundColor = theme.appBG) => ({ + picker: (disabled = false, backgroundColor = theme.appBG): PickerStyle => ({ iconContainer: { top: Math.round(variables.inputHeight * 0.5) - 11, right: 0, @@ -1296,7 +1311,7 @@ const styles = (theme: ThemeDefault) => ({ width: variables.componentSizeNormal, }, - statusIndicator: (backgroundColor = theme.danger) => ({ + statusIndicator: (backgroundColor = theme.danger): ViewStyle => ({ borderColor: theme.sidebar, backgroundColor, borderRadius: 8, @@ -1355,7 +1370,7 @@ const styles = (theme: ThemeDefault) => ({ textDecorationLine: 'none', }, - RHPNavigatorContainer: (isSmallScreenWidth: boolean) => ({ + RHPNavigatorContainer: (isSmallScreenWidth: boolean): ViewStyle => ({ width: isSmallScreenWidth ? '100%' : variables.sideBarWidth, position: 'absolute', right: 0, @@ -1371,17 +1386,17 @@ const styles = (theme: ThemeDefault) => ({ lineHeight: variables.fontSizeOnlyEmojisHeight, }, - createMenuPositionSidebar: (windowHeight: number) => ({ + createMenuPositionSidebar: (windowHeight: number): AnchorPosition => ({ horizontal: 18, vertical: windowHeight - 100, }), - createMenuPositionProfile: (windowWidth: number) => ({ + createMenuPositionProfile: (windowWidth: number): AnchorPosition => ({ horizontal: windowWidth - 355, ...getPopOverVerticalOffset(162), }), - createMenuPositionReportActionCompose: (windowHeight: number) => ({ + createMenuPositionReportActionCompose: (windowHeight: number): AnchorPosition => ({ horizontal: 18 + variables.sideBarWidth, vertical: windowHeight - 83, }), @@ -1538,8 +1553,9 @@ const styles = (theme: ThemeDefault) => ({ height: variables.optionsListSectionHeaderHeight, }, - overlayStyles: (current: {progress: Animated.AnimatedInterpolation}) => ({ - position: 'fixed', + overlayStyles: (current: {progress: Animated.AnimatedInterpolation}): ViewStyle => ({ + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + position: 'fixed' as ViewStyle['position'], // We need to stretch the overlay to cover the sidebar and the translate animation distance. left: -2 * variables.sideBarWidth, @@ -2164,7 +2180,7 @@ const styles = (theme: ThemeDefault) => ({ outline: 'none', }, - getPDFPasswordFormStyle: (isSmallScreenWidth: boolean) => ({ + getPDFPasswordFormStyle: (isSmallScreenWidth: boolean): ViewStyle => ({ width: isSmallScreenWidth ? '100%' : 350, ...(isSmallScreenWidth && flex.flex1), }), @@ -2177,7 +2193,7 @@ const styles = (theme: ThemeDefault) => ({ backgroundColor: theme.modalBackdrop, }, - centeredModalStyles: (isSmallScreenWidth: boolean, isFullScreenWhenSmall: boolean) => ({ + centeredModalStyles: (isSmallScreenWidth: boolean, isFullScreenWhenSmall: boolean): ViewStyle => ({ borderWidth: isSmallScreenWidth && !isFullScreenWhenSmall ? 1 : 0, marginHorizontal: isSmallScreenWidth ? 0 : 20, }), @@ -2310,7 +2326,7 @@ const styles = (theme: ThemeDefault) => ({ padding: 0, }, - twoFactorAuthCodesBox: ({isExtraSmallScreenWidth, isSmallScreenWidth}: {isExtraSmallScreenWidth: boolean; isSmallScreenWidth: boolean}) => { + twoFactorAuthCodesBox: ({isExtraSmallScreenWidth, isSmallScreenWidth}: {isExtraSmallScreenWidth: boolean; isSmallScreenWidth: boolean}): ViewStyle => { let paddingHorizontal = spacing.ph9; if (isSmallScreenWidth) { @@ -2368,7 +2384,7 @@ const styles = (theme: ThemeDefault) => ({ minWidth: 110, }, - anonymousRoomFooter: (isSmallSizeLayout: boolean) => ({ + anonymousRoomFooter: (isSmallSizeLayout: boolean): ViewStyle & TextStyle => ({ flexDirection: isSmallSizeLayout ? 'column' : 'row', ...(!isSmallSizeLayout && { alignItems: 'center', @@ -2379,7 +2395,7 @@ const styles = (theme: ThemeDefault) => ({ borderRadius: variables.componentBorderRadiusLarge, overflow: 'hidden', }), - anonymousRoomFooterWordmarkAndLogoContainer: (isSmallSizeLayout: boolean) => ({ + anonymousRoomFooterWordmarkAndLogoContainer: (isSmallSizeLayout: boolean): ViewStyle => ({ flexDirection: 'row', alignItems: 'center', ...(isSmallSizeLayout && { @@ -2428,8 +2444,8 @@ const styles = (theme: ThemeDefault) => ({ borderRadius: 88, }, - rootNavigatorContainerStyles: (isSmallScreenWidth: boolean) => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1}), - RHPNavigatorContainerNavigatorContainerStyles: (isSmallScreenWidth: boolean) => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1}), + rootNavigatorContainerStyles: (isSmallScreenWidth: boolean): ViewStyle => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1}), + RHPNavigatorContainerNavigatorContainerStyles: (isSmallScreenWidth: boolean): ViewStyle => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1}), avatarInnerTextChat: { color: theme.textLight, @@ -2614,7 +2630,7 @@ const styles = (theme: ThemeDefault) => ({ backgroundColor: theme.appBG, }, - switchThumbTransformation: (translateX: AnimatableNumericValue) => ({ + switchThumbTransformation: (translateX: AnimatableNumericValue): ViewStyle => ({ transform: [{translateX}], }), @@ -2823,14 +2839,15 @@ const styles = (theme: ThemeDefault) => ({ growlNotificationDesktopContainer: { maxWidth: variables.sideBarWidth, right: 0, - position: 'fixed', + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + position: 'fixed' as ViewStyle['position'], }, - growlNotificationTranslateY: (translateY: AnimatableNumericValue) => ({ + growlNotificationTranslateY: (translateY: AnimatableNumericValue): ViewStyle => ({ transform: [{translateY}], }), - makeSlideInTranslation: (translationType: Translation, fromValue: number) => ({ + makeSlideInTranslation: (translationType: Translation, fromValue: number): CustomAnimation => ({ from: { [translationType]: fromValue, }, @@ -3130,7 +3147,7 @@ const styles = (theme: ThemeDefault) => ({ ...visibility.visible, }, - floatingMessageCounterTransformation: (translateY: AnimatableNumericValue) => ({ + floatingMessageCounterTransformation: (translateY: AnimatableNumericValue): ViewStyle => ({ transform: [{translateY}], }), @@ -3157,12 +3174,12 @@ const styles = (theme: ThemeDefault) => ({ flex: 1, }, - threeDotsPopoverOffset: (windowWidth: number) => ({ + threeDotsPopoverOffset: (windowWidth: number): AnchorPosition => ({ ...getPopOverVerticalOffset(60), horizontal: windowWidth - 60, }), - threeDotsPopoverOffsetNoCloseButton: (windowWidth: number) => ({ + threeDotsPopoverOffsetNoCloseButton: (windowWidth: number): AnchorPosition => ({ ...getPopOverVerticalOffset(60), horizontal: windowWidth - 10, }), @@ -3305,7 +3322,7 @@ const styles = (theme: ThemeDefault) => ({ zIndex: 2, }, - receiptImageWrapper: (receiptImageTopPosition: number) => ({ + receiptImageWrapper: (receiptImageTopPosition: number): ViewStyle => ({ position: 'absolute', top: receiptImageTopPosition, }), @@ -3726,14 +3743,14 @@ const styles = (theme: ThemeDefault) => ({ paddingBottom: 12, }, - tabText: (isSelected: boolean) => ({ + tabText: (isSelected: boolean): TextStyle => ({ marginLeft: 8, fontFamily: isSelected ? fontFamily.EXP_NEUE_BOLD : fontFamily.EXP_NEUE, - fontWeight: isSelected ? fontWeightBold : 400, + fontWeight: isSelected ? fontWeightBold : '400', color: isSelected ? theme.textLight : theme.textSupporting, }), - overscrollSpacer: (backgroundColor: string, height: number) => ({ + overscrollSpacer: (backgroundColor: string, height: number): ViewStyle => ({ backgroundColor, height, width: '100%', @@ -3896,7 +3913,7 @@ const styles = (theme: ThemeDefault) => ({ maxWidth: 400, }, - distanceRequestContainer: (maxHeight: number) => ({ + distanceRequestContainer: (maxHeight: number): ViewStyle => ({ ...flex.flexShrink2, minHeight: variables.optionRowHeight * 2, maxHeight, From 1be0bf59b738c433a78e7fba83d6a5c3b910691e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 15 Sep 2023 17:16:07 +0100 Subject: [PATCH 015/284] Minor fixes in styles.ts --- src/styles/styles.ts | 56 ++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/styles/styles.ts b/src/styles/styles.ts index b55cfd9ad497..93961b20b31f 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -1,42 +1,45 @@ /* eslint-disable @typescript-eslint/naming-convention */ import lodashClamp from 'lodash/clamp'; import {AnimatableNumericValue, Animated, TextStyle, ViewStyle} from 'react-native'; -import {MixedStyleDeclaration, MixedStyleRecord} from 'react-native-render-html'; -import {PickerStyle} from 'react-native-picker-select'; import {CustomAnimation} from 'react-native-animatable'; -import {EmptyObject} from 'type-fest'; -import fontFamily from './fontFamily'; +import {PickerStyle} from 'react-native-picker-select'; +import {MixedStyleDeclaration, MixedStyleRecord} from 'react-native-render-html'; +import CONST from '../CONST'; +import * as Browser from '../libs/Browser'; import addOutlineWidth from './addOutlineWidth'; -import defaultTheme from './themes/default'; +import codeStyles from './codeStyles'; +import fontFamily from './fontFamily'; import fontWeightBold from './fontWeight/bold'; -import variables from './variables'; -import spacing from './utilities/spacing'; -import sizing from './utilities/sizing'; -import flex from './utilities/flex'; +import getPopOverVerticalOffset from './getPopOverVerticalOffset'; +import optionAlternateTextPlatformStyles from './optionAlternateTextPlatformStyles'; +import overflowXHidden from './overflowXHidden'; +import pointerEventsAuto from './pointerEventsAuto'; +import pointerEventsNone from './pointerEventsNone'; +import defaultTheme from './themes/default'; +import {ThemeDefault} from './themes/types'; +import cursor from './utilities/cursor'; import display from './utilities/display'; +import flex from './utilities/flex'; import overflow from './utilities/overflow'; -import whiteSpace from './utilities/whiteSpace'; -import wordBreak from './utilities/wordBreak'; import positioning from './utilities/positioning'; -import codeStyles from './codeStyles'; +import sizing from './utilities/sizing'; +import spacing from './utilities/spacing'; +import textUnderline from './utilities/textUnderline'; +import userSelect from './utilities/userSelect'; import visibility from './utilities/visibility'; +import whiteSpace from './utilities/whiteSpace'; +import wordBreak from './utilities/wordBreak'; import writingDirection from './utilities/writingDirection'; -import optionAlternateTextPlatformStyles from './optionAlternateTextPlatformStyles'; -import pointerEventsNone from './pointerEventsNone'; -import pointerEventsAuto from './pointerEventsAuto'; -import getPopOverVerticalOffset from './getPopOverVerticalOffset'; -import overflowXHidden from './overflowXHidden'; -import CONST from '../CONST'; -import * as Browser from '../libs/Browser'; -import cursor from './utilities/cursor'; -import userSelect from './utilities/userSelect'; -import textUnderline from './utilities/textUnderline'; -import {ThemeDefault} from './themes/types'; +import variables from './variables'; + +type OverlayStylesParams = {progress: Animated.AnimatedInterpolation}; + +type TwoFactorAuthCodesBoxParams = {isExtraSmallScreenWidth: boolean; isSmallScreenWidth: boolean}; type Translation = 'perspective' | 'rotate' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scaleX' | 'scaleY' | 'translateX' | 'translateY' | 'skewX' | 'skewY' | 'matrix'; // touchCallout is an iOS safari only property that controls the display of the callout information when you touch and hold a target -const touchCalloutNone: {WebkitTouchCallout: 'none'} | EmptyObject = Browser.isMobileSafari() ? {WebkitTouchCallout: 'none'} : {}; +const touchCalloutNone: Pick = Browser.isMobileSafari() ? {WebkitTouchCallout: 'none'} : {}; const picker = (theme: ThemeDefault): TextStyle => ({ backgroundColor: theme.transparent, @@ -693,6 +696,7 @@ const styles = (theme: ThemeDefault) => ({ color: theme.text, }, doneDepressed: { + // Extracted from react-native-picker-select, src/styles.js fontSize: 17, }, modalViewMiddle: { @@ -1553,7 +1557,7 @@ const styles = (theme: ThemeDefault) => ({ height: variables.optionsListSectionHeaderHeight, }, - overlayStyles: (current: {progress: Animated.AnimatedInterpolation}): ViewStyle => ({ + overlayStyles: (current: OverlayStylesParams): ViewStyle => ({ // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". position: 'fixed' as ViewStyle['position'], @@ -2326,7 +2330,7 @@ const styles = (theme: ThemeDefault) => ({ padding: 0, }, - twoFactorAuthCodesBox: ({isExtraSmallScreenWidth, isSmallScreenWidth}: {isExtraSmallScreenWidth: boolean; isSmallScreenWidth: boolean}): ViewStyle => { + twoFactorAuthCodesBox: ({isExtraSmallScreenWidth, isSmallScreenWidth}: TwoFactorAuthCodesBoxParams): ViewStyle => { let paddingHorizontal = spacing.ph9; if (isSmallScreenWidth) { From 1338157f3a87cccde5be0b41aa45a0c60459feca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Fri, 15 Sep 2023 23:47:15 +0100 Subject: [PATCH 016/284] Add stricter types to styles.ts --- src/styles/ThemeStylesProvider.tsx | 2 +- src/styles/getModalStyles.ts | 6 +- src/styles/styles.ts | 7650 ++++++++++++++-------------- src/styles/utilities/display.ts | 5 + 4 files changed, 3861 insertions(+), 3802 deletions(-) diff --git a/src/styles/ThemeStylesProvider.tsx b/src/styles/ThemeStylesProvider.tsx index d0db784ca8ca..4e6f91baf34a 100644 --- a/src/styles/ThemeStylesProvider.tsx +++ b/src/styles/ThemeStylesProvider.tsx @@ -5,7 +5,7 @@ import ThemeStylesContext from './ThemeStylesContext'; // TODO: Rename this to "styles" once the app is migrated to theme switching hooks and HOCs import {stylesGenerator as stylesUntyped} from './styles'; -const styles = stylesUntyped as (theme: Record) => Record; +const styles = stylesUntyped; type ThemeStylesProviderProps = { children: React.ReactNode; diff --git a/src/styles/getModalStyles.ts b/src/styles/getModalStyles.ts index ceea37ddb85b..d52d29568c2d 100644 --- a/src/styles/getModalStyles.ts +++ b/src/styles/getModalStyles.ts @@ -7,9 +7,11 @@ import themeColors from './themes/default'; import variables from './variables'; function getCenteredModalStyles(windowWidth: number, isSmallScreenWidth: boolean, isFullScreenWhenSmall = false): ViewStyle { + const modalStyles = styles.centeredModalStyles(isSmallScreenWidth, isFullScreenWhenSmall); + return { - borderWidth: styles.centeredModalStyles(isSmallScreenWidth, isFullScreenWhenSmall).borderWidth, - width: isSmallScreenWidth ? '100%' : windowWidth - styles.centeredModalStyles(isSmallScreenWidth, isFullScreenWhenSmall).marginHorizontal * 2, + borderWidth: modalStyles.borderWidth, + width: isSmallScreenWidth ? '100%' : windowWidth - modalStyles.marginHorizontal * 2, }; } diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 93961b20b31f..c93e96c8fbee 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import {LineLayerStyleProps} from '@rnmapbox/maps/src/utils/MapboxStyles'; import lodashClamp from 'lodash/clamp'; -import {AnimatableNumericValue, Animated, TextStyle, ViewStyle} from 'react-native'; +import {LineLayer} from 'react-map-gl'; +import {AnimatableNumericValue, Animated, ImageStyle, TextStyle, ViewStyle} from 'react-native'; import {CustomAnimation} from 'react-native-animatable'; import {PickerStyle} from 'react-native-picker-select'; import {MixedStyleDeclaration, MixedStyleRecord} from 'react-native-render-html'; @@ -32,3960 +34,4010 @@ import wordBreak from './utilities/wordBreak'; import writingDirection from './utilities/writingDirection'; import variables from './variables'; +type AnchorPosition = { + horizontal: number; + vertical: number; +}; + +type WebViewStyle = { + tagStyles: MixedStyleRecord; + baseFontStyle: MixedStyleDeclaration; +}; + +type CustomPickerStyle = PickerStyle & {icon?: ViewStyle}; + type OverlayStylesParams = {progress: Animated.AnimatedInterpolation}; type TwoFactorAuthCodesBoxParams = {isExtraSmallScreenWidth: boolean; isSmallScreenWidth: boolean}; type Translation = 'perspective' | 'rotate' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scaleX' | 'scaleY' | 'translateX' | 'translateY' | 'skewX' | 'skewY' | 'matrix'; +type OfflineFeedbackStyle = Record<'deleted' | 'pending' | 'error' | 'container' | 'textContainer' | 'text' | 'errorDot', ViewStyle | TextStyle>; + +type MapDirectionStyle = Pick; + +type MapDirectionLayerStyle = Pick; + +type Styles = Record< + string, + | ViewStyle + | TextStyle + | ImageStyle + | WebViewStyle + | OfflineFeedbackStyle + | MapDirectionStyle + | MapDirectionLayerStyle + | ((...args: any[]) => ViewStyle | TextStyle | ImageStyle | AnchorPosition | CustomAnimation | CustomPickerStyle) +>; + // touchCallout is an iOS safari only property that controls the display of the callout information when you touch and hold a target const touchCalloutNone: Pick = Browser.isMobileSafari() ? {WebkitTouchCallout: 'none'} : {}; -const picker = (theme: ThemeDefault): TextStyle => ({ - backgroundColor: theme.transparent, - color: theme.text, - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeNormal, - lineHeight: variables.fontSizeNormalHeight, - paddingBottom: 8, - paddingTop: 23, - paddingLeft: 0, - paddingRight: 25, - height: variables.inputHeight, - borderWidth: 0, - textAlign: 'left', -}); - -const link = (theme: ThemeDefault): ViewStyle & MixedStyleDeclaration => ({ - color: theme.link, - textDecorationColor: theme.link, - fontFamily: fontFamily.EXP_NEUE, -}); - -const baseCodeTagStyles = (theme: ThemeDefault): ViewStyle & MixedStyleDeclaration => ({ - borderWidth: 1, - borderRadius: 5, - borderColor: theme.border, - backgroundColor: theme.textBackground, -}); +const picker = (theme: ThemeDefault) => + ({ + backgroundColor: theme.transparent, + color: theme.text, + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeNormal, + lineHeight: variables.fontSizeNormalHeight, + paddingBottom: 8, + paddingTop: 23, + paddingLeft: 0, + paddingRight: 25, + height: variables.inputHeight, + borderWidth: 0, + textAlign: 'left', + } satisfies TextStyle); + +const link = (theme: ThemeDefault) => + ({ + color: theme.link, + textDecorationColor: theme.link, + fontFamily: fontFamily.EXP_NEUE, + } satisfies ViewStyle & MixedStyleDeclaration); + +const baseCodeTagStyles = (theme: ThemeDefault) => + ({ + borderWidth: 1, + borderRadius: 5, + borderColor: theme.border, + backgroundColor: theme.textBackground, + } satisfies ViewStyle & MixedStyleDeclaration); const headlineFont = { fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM, fontWeight: '500', -} as const; +} satisfies TextStyle; + +const webViewStyles = (theme: ThemeDefault) => + ({ + // As of react-native-render-html v6, don't declare distinct styles for + // custom renderers, the API for custom renderers has changed. Declare the + // styles in the below "tagStyles" instead. If you need to reuse those + // styles from the renderer, just pass the "style" prop to the underlying + // component. + tagStyles: { + em: { + fontFamily: fontFamily.EXP_NEUE, + fontStyle: 'italic', + }, -type WebViewStyles = { - tagStyles: MixedStyleRecord; - baseFontStyle: MixedStyleDeclaration; -}; + del: { + textDecorationLine: 'line-through', + textDecorationStyle: 'solid', + }, + + strong: { + fontFamily: fontFamily.EXP_NEUE, + fontWeight: 'bold', + }, + + a: link(theme), + + ul: { + maxWidth: '100%', + }, + + ol: { + maxWidth: '100%', + }, + + li: { + flexShrink: 1, + }, + + blockquote: { + borderLeftColor: theme.border, + borderLeftWidth: 4, + paddingLeft: 12, + marginTop: 4, + marginBottom: 4, + + // Overwrite default HTML margin for blockquotes + marginLeft: 0, + }, -const webViewStyles = (theme: ThemeDefault): WebViewStyles => ({ - // As of react-native-render-html v6, don't declare distinct styles for - // custom renderers, the API for custom renderers has changed. Declare the - // styles in the below "tagStyles" instead. If you need to reuse those - // styles from the renderer, just pass the "style" prop to the underlying - // component. - tagStyles: { - em: { + pre: { + ...baseCodeTagStyles(theme), + paddingTop: 12, + paddingBottom: 12, + paddingRight: 8, + paddingLeft: 8, + fontFamily: fontFamily.MONOSPACE, + marginTop: 0, + marginBottom: 0, + }, + + code: { + ...baseCodeTagStyles(theme), + ...(codeStyles.codeTextStyle as MixedStyleDeclaration), + paddingLeft: 5, + paddingRight: 5, + fontFamily: fontFamily.MONOSPACE, + fontSize: 13, + }, + + img: { + borderColor: theme.border, + borderRadius: variables.componentBorderRadiusNormal, + borderWidth: 1, + ...touchCalloutNone, + }, + + p: { + marginTop: 0, + marginBottom: 0, + }, + h1: { + fontSize: variables.fontSizeLarge, + marginBottom: 8, + }, + }, + + baseFontStyle: { + color: theme.text, + fontSize: variables.fontSizeNormal, fontFamily: fontFamily.EXP_NEUE, - fontStyle: 'italic', + flex: 1, + lineHeight: variables.fontSizeNormalHeight, + }, + } satisfies WebViewStyle); + +const styles = (theme: ThemeDefault) => + ({ + // Add all of our utility and helper styles + ...spacing, + ...sizing, + ...flex, + ...display, + ...overflow, + ...positioning, + ...wordBreak, + ...whiteSpace, + ...writingDirection, + ...cursor, + ...userSelect, + ...textUnderline, + + rateCol: { + margin: 0, + padding: 0, + flexBasis: '48%', }, - del: { - textDecorationLine: 'line-through', - textDecorationStyle: 'solid', + autoCompleteSuggestionsContainer: { + backgroundColor: theme.appBG, + borderRadius: 8, + borderWidth: 1, + borderColor: theme.border, + justifyContent: 'center', + boxShadow: variables.popoverMenuShadow, + position: 'absolute', + left: 0, + right: 0, + paddingVertical: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING, }, - strong: { - fontFamily: fontFamily.EXP_NEUE, - fontWeight: 'bold', + autoCompleteSuggestionContainer: { + flexDirection: 'row', + alignItems: 'center', }, - a: link(theme), + emojiSuggestionsEmoji: { + fontSize: variables.fontSizeMedium, + width: 51, + textAlign: 'center', + }, + emojiSuggestionsText: { + fontSize: variables.fontSizeMedium, + flex: 1, + ...wordBreak.breakWord, + ...spacing.pr4, + }, - ul: { - maxWidth: '100%', + mentionSuggestionsAvatarContainer: { + width: 24, + height: 24, + alignItems: 'center', + justifyContent: 'center', }, - ol: { - maxWidth: '100%', + mentionSuggestionsText: { + fontSize: variables.fontSizeMedium, + ...spacing.ml2, }, - li: { - flexShrink: 1, + mentionSuggestionsDisplayName: { + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontWeight: fontWeightBold, }, - blockquote: { - borderLeftColor: theme.border, - borderLeftWidth: 4, - paddingLeft: 12, - marginTop: 4, - marginBottom: 4, + mentionSuggestionsHandle: { + color: theme.textSupporting, + }, - // Overwrite default HTML margin for blockquotes - marginLeft: 0, + appIconBorderRadius: { + overflow: 'hidden', + borderRadius: 12, }, - pre: { - ...baseCodeTagStyles(theme), - paddingTop: 12, - paddingBottom: 12, - paddingRight: 8, - paddingLeft: 8, - fontFamily: fontFamily.MONOSPACE, - marginTop: 0, - marginBottom: 0, + unitCol: { + margin: 0, + padding: 0, + marginLeft: '4%', + flexBasis: '48%', }, - code: { - ...baseCodeTagStyles(theme), - ...(codeStyles.codeTextStyle as MixedStyleDeclaration), - paddingLeft: 5, - paddingRight: 5, - fontFamily: fontFamily.MONOSPACE, - fontSize: 13, + webViewStyles: webViewStyles(theme), + + link: link(theme), + + linkMuted: { + color: theme.textSupporting, + textDecorationColor: theme.textSupporting, + fontFamily: fontFamily.EXP_NEUE, }, - img: { - borderColor: theme.border, - borderRadius: variables.componentBorderRadiusNormal, - borderWidth: 1, - ...touchCalloutNone, + linkMutedHovered: { + color: theme.textMutedReversed, }, - p: { - marginTop: 0, - marginBottom: 0, + highlightBG: { + backgroundColor: theme.highlightBG, + }, + + appBG: { + backgroundColor: theme.appBG, }, + h1: { - fontSize: variables.fontSizeLarge, - marginBottom: 8, + color: theme.heading, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontSize: variables.fontSizeh1, + fontWeight: fontWeightBold, }, - }, - baseFontStyle: { - color: theme.text, - fontSize: variables.fontSizeNormal, - fontFamily: fontFamily.EXP_NEUE, - flex: 1, - lineHeight: variables.fontSizeNormalHeight, - }, -}); + h3: { + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontSize: variables.fontSizeNormal, + fontWeight: fontWeightBold, + }, -type AnchorPosition = { - horizontal: number; - vertical: number; -}; + h4: { + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontSize: variables.fontSizeLabel, + fontWeight: fontWeightBold, + }, -const styles = (theme: ThemeDefault) => ({ - // Add all of our utility and helper styles - ...spacing, - ...sizing, - ...flex, - ...display, - ...overflow, - ...positioning, - ...wordBreak, - ...whiteSpace, - ...writingDirection, - ...cursor, - ...userSelect, - ...textUnderline, - ...theme, // TODO: Should we do this? - - rateCol: { - margin: 0, - padding: 0, - flexBasis: '48%', - }, - - autoCompleteSuggestionsContainer: { - backgroundColor: theme.appBG, - borderRadius: 8, - borderWidth: 1, - borderColor: theme.border, - justifyContent: 'center', - boxShadow: variables.popoverMenuShadow, - position: 'absolute', - left: 0, - right: 0, - paddingVertical: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING, - }, - - autoCompleteSuggestionContainer: { - flexDirection: 'row', - alignItems: 'center', - }, - - emojiSuggestionsEmoji: { - fontSize: variables.fontSizeMedium, - width: 51, - textAlign: 'center', - }, - emojiSuggestionsText: { - fontSize: variables.fontSizeMedium, - flex: 1, - ...wordBreak.breakWord, - ...spacing.pr4, - }, - - mentionSuggestionsAvatarContainer: { - width: 24, - height: 24, - alignItems: 'center', - justifyContent: 'center', - }, - - mentionSuggestionsText: { - fontSize: variables.fontSizeMedium, - ...spacing.ml2, - }, - - mentionSuggestionsDisplayName: { - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - }, - - mentionSuggestionsHandle: { - color: theme.textSupporting, - }, - - appIconBorderRadius: { - overflow: 'hidden', - borderRadius: 12, - }, - - unitCol: { - margin: 0, - padding: 0, - marginLeft: '4%', - flexBasis: '48%', - }, - - webViewStyles: webViewStyles(theme), - - link: link(theme), - - linkMuted: { - color: theme.textSupporting, - textDecorationColor: theme.textSupporting, - fontFamily: fontFamily.EXP_NEUE, - }, + textAlignCenter: { + textAlign: 'center', + }, - linkMutedHovered: { - color: theme.textMutedReversed, - }, + textAlignRight: { + textAlign: 'right', + }, - highlightBG: { - backgroundColor: theme.highlightBG, - }, + textAlignLeft: { + textAlign: 'left', + }, - appBG: { - backgroundColor: theme.appBG, - }, + textUnderline: { + textDecorationLine: 'underline', + }, - h1: { - color: theme.heading, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeh1, - fontWeight: fontWeightBold, - }, + label: { + fontSize: variables.fontSizeLabel, + lineHeight: variables.lineHeightLarge, + }, - h3: { - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeNormal, - fontWeight: fontWeightBold, - }, + textLabel: { + color: theme.text, + fontSize: variables.fontSizeLabel, + lineHeight: variables.lineHeightLarge, + }, - h4: { - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeLabel, - fontWeight: fontWeightBold, - }, + mutedTextLabel: { + color: theme.textSupporting, + fontSize: variables.fontSizeLabel, + lineHeight: variables.lineHeightLarge, + }, - textAlignCenter: { - textAlign: 'center', - }, + textMicro: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeSmall, + lineHeight: variables.lineHeightSmall, + }, - textAlignRight: { - textAlign: 'right', - }, + textMicroBold: { + color: theme.text, + fontWeight: fontWeightBold, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontSize: variables.fontSizeSmall, + lineHeight: variables.lineHeightSmall, + }, - textAlignLeft: { - textAlign: 'left', - }, + textMicroSupporting: { + color: theme.textSupporting, + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeSmall, + lineHeight: variables.lineHeightSmall, + }, - textUnderline: { - textDecorationLine: 'underline', - }, + textExtraSmallSupporting: { + color: theme.textSupporting, + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeExtraSmall, + }, - label: { - fontSize: variables.fontSizeLabel, - lineHeight: variables.lineHeightLarge, - }, + textNormal: { + fontSize: variables.fontSizeNormal, + }, - textLabel: { - color: theme.text, - fontSize: variables.fontSizeLabel, - lineHeight: variables.lineHeightLarge, - }, + textLarge: { + fontSize: variables.fontSizeLarge, + }, - mutedTextLabel: { - color: theme.textSupporting, - fontSize: variables.fontSizeLabel, - lineHeight: variables.lineHeightLarge, - }, + textXLarge: { + fontSize: variables.fontSizeXLarge, + }, - textMicro: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeSmall, - lineHeight: variables.lineHeightSmall, - }, + textXXLarge: { + fontSize: variables.fontSizeXXLarge, + }, - textMicroBold: { - color: theme.text, - fontWeight: fontWeightBold, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeSmall, - lineHeight: variables.lineHeightSmall, - }, - - textMicroSupporting: { - color: theme.textSupporting, - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeSmall, - lineHeight: variables.lineHeightSmall, - }, + textXXXLarge: { + fontSize: variables.fontSizeXXXLarge, + }, - textExtraSmallSupporting: { - color: theme.textSupporting, - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeExtraSmall, - }, + textHero: { + fontSize: variables.fontSizeHero, + fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM, + lineHeight: variables.lineHeightHero, + }, - textNormal: { - fontSize: variables.fontSizeNormal, - }, - - textLarge: { - fontSize: variables.fontSizeLarge, - }, - - textXLarge: { - fontSize: variables.fontSizeXLarge, - }, - - textXXLarge: { - fontSize: variables.fontSizeXXLarge, - }, - - textXXXLarge: { - fontSize: variables.fontSizeXXXLarge, - }, - - textHero: { - fontSize: variables.fontSizeHero, - fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM, - lineHeight: variables.lineHeightHero, - }, - - textStrong: { - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - }, - - textItalic: { - fontFamily: fontFamily.EXP_NEUE_ITALIC, - fontStyle: 'italic', - }, - - textHeadline: { - ...headlineFont, - ...whiteSpace.preWrap, - color: theme.heading, - fontSize: variables.fontSizeXLarge, - lineHeight: variables.lineHeightXXLarge, - }, - - textHeadlineH1: { - ...headlineFont, - ...whiteSpace.preWrap, - color: theme.heading, - fontSize: variables.fontSizeh1, - lineHeight: variables.lineHeightSizeh1, - }, - - textDecorationNoLine: { - textDecorationLine: 'none', - }, - - textWhite: { - color: theme.textLight, - }, - - textBlue: { - color: theme.link, - }, - - textUppercase: { - textTransform: 'uppercase', - }, - - textNoWrap: { - ...whiteSpace.noWrap, - }, - - colorReversed: { - color: theme.textReversed, - }, - - colorMutedReversed: { - color: theme.textMutedReversed, - }, - - colorMuted: { - color: theme.textSupporting, - }, - - colorHeading: { - color: theme.heading, - }, - - bgTransparent: { - backgroundColor: 'transparent', - }, - - bgDark: { - backgroundColor: theme.inverse, - }, - - opacity0: { - opacity: 0, - }, - - opacity1: { - opacity: 1, - }, - - textDanger: { - color: theme.danger, - }, - - borderRadiusNormal: { - borderRadius: variables.buttonBorderRadius, - }, - - button: { - backgroundColor: theme.buttonDefaultBG, - borderRadius: variables.buttonBorderRadius, - minHeight: variables.componentSizeLarge, - justifyContent: 'center', - ...spacing.ph3, - }, - - buttonContainer: { - padding: 1, - borderRadius: variables.buttonBorderRadius, - }, - - buttonText: { - color: theme.text, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeNormal, - fontWeight: fontWeightBold, - textAlign: 'center', - flexShrink: 1, - - // It is needed to unset the Lineheight. We don't need it for buttons as button always contains single line of text. - // It allows to vertically center the text. - lineHeight: undefined, - - // Add 1px to the Button text to give optical vertical alignment. - paddingBottom: 1, - }, - - buttonSmall: { - borderRadius: variables.buttonBorderRadius, - minHeight: variables.componentSizeSmall, - paddingTop: 4, - paddingHorizontal: 14, - paddingBottom: 4, - backgroundColor: theme.buttonDefaultBG, - }, - - buttonMedium: { - borderRadius: variables.buttonBorderRadius, - minHeight: variables.componentSizeNormal, - paddingTop: 12, - paddingRight: 16, - paddingBottom: 12, - paddingLeft: 16, - backgroundColor: theme.buttonDefaultBG, - }, - - buttonLarge: { - borderRadius: variables.buttonBorderRadius, - minHeight: variables.componentSizeLarge, - paddingTop: 8, - paddingRight: 10, - paddingBottom: 8, - paddingLeft: 18, - backgroundColor: theme.buttonDefaultBG, - }, - - buttonSmallText: { - fontSize: variables.fontSizeSmall, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - textAlign: 'center', - }, - - buttonMediumText: { - fontSize: variables.fontSizeLabel, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - textAlign: 'center', - }, - - buttonLargeText: { - fontSize: variables.fontSizeNormal, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - textAlign: 'center', - }, + textStrong: { + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontWeight: fontWeightBold, + }, - buttonDefaultHovered: { - backgroundColor: theme.buttonHoveredBG, - borderWidth: 0, - }, + textItalic: { + fontFamily: fontFamily.EXP_NEUE_ITALIC, + fontStyle: 'italic', + }, - buttonSuccess: { - backgroundColor: theme.success, - borderWidth: 0, - }, + textHeadline: { + ...headlineFont, + ...whiteSpace.preWrap, + color: theme.heading, + fontSize: variables.fontSizeXLarge, + lineHeight: variables.lineHeightXXLarge, + }, - buttonOpacityDisabled: { - opacity: 0.5, - }, + textHeadlineH1: { + ...headlineFont, + ...whiteSpace.preWrap, + color: theme.heading, + fontSize: variables.fontSizeh1, + lineHeight: variables.lineHeightSizeh1, + }, - buttonSuccessHovered: { - backgroundColor: theme.successHover, - borderWidth: 0, - }, + textDecorationNoLine: { + textDecorationLine: 'none', + }, - buttonDanger: { - backgroundColor: theme.danger, - borderWidth: 0, - }, + textWhite: { + color: theme.textLight, + }, - buttonDangerHovered: { - backgroundColor: theme.dangerHover, - borderWidth: 0, - }, + textBlue: { + color: theme.link, + }, - buttonDisabled: { - backgroundColor: theme.buttonDefaultBG, - borderWidth: 0, - }, - - buttonDivider: { - height: variables.dropDownButtonDividerHeight, - borderWidth: 0.7, - borderColor: theme.text, - }, - - noBorderRadius: { - borderRadius: 0, - }, - - noRightBorderRadius: { - borderTopRightRadius: 0, - borderBottomRightRadius: 0, - }, - - noLeftBorderRadius: { - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, - }, - - buttonCTA: { - paddingVertical: 6, - ...spacing.mh4, - }, - - buttonCTAIcon: { - marginRight: 22, - - // Align vertically with the Button text - paddingBottom: 1, - paddingTop: 1, - }, - - buttonConfirm: { - margin: 20, - }, - - attachmentButtonBigScreen: { - minWidth: 300, - alignSelf: 'center', - }, - - buttonConfirmText: { - paddingLeft: 20, - paddingRight: 20, - }, - - buttonSuccessText: { - color: theme.textLight, - }, - - buttonDangerText: { - color: theme.textLight, - }, - - hoveredComponentBG: { - backgroundColor: theme.hoverComponentBG, - }, - - activeComponentBG: { - backgroundColor: theme.activeComponentBG, - }, - - fontWeightBold: { - fontWeight: fontWeightBold, - }, - - touchableButtonImage: { - alignItems: 'center', - height: variables.componentSizeNormal, - justifyContent: 'center', - width: variables.componentSizeNormal, - }, - - visuallyHidden: { - ...visibility.hidden, - overflow: 'hidden', - width: 0, - height: 0, - }, - - visibilityHidden: { - ...visibility.hidden, - }, - - loadingVBAAnimation: { - width: 140, - height: 140, - }, - - pickerSmall: (backgroundColor = theme.highlightBG): PickerStyle & {icon: ViewStyle} => ({ - inputIOS: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeSmall, - paddingLeft: 0, - paddingRight: 17, - paddingTop: 6, - paddingBottom: 6, - borderWidth: 0, - color: theme.text, - height: 26, - opacity: 1, - backgroundColor: 'transparent', + textUppercase: { + textTransform: 'uppercase', }, - done: { - color: theme.text, + + textNoWrap: { + ...whiteSpace.noWrap, }, - doneDepressed: { - // Extracted from react-native-picker-select, src/styles.js - fontSize: 17, + + colorReversed: { + color: theme.textReversed, }, - modalViewMiddle: { - backgroundColor: theme.border, - borderTopWidth: 0, + + colorMutedReversed: { + color: theme.textMutedReversed, }, - modalViewBottom: { - backgroundColor: theme.highlightBG, + + colorMuted: { + color: theme.textSupporting, }, - inputWeb: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeSmall, - paddingLeft: 0, - paddingRight: 17, - paddingTop: 6, - paddingBottom: 6, - borderWidth: 0, - color: theme.text, - appearance: 'none', - height: 26, - opacity: 1, - backgroundColor, - ...cursor.cursorPointer, + + colorHeading: { + color: theme.heading, }, - inputAndroid: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeSmall, - paddingLeft: 0, - paddingRight: 17, - paddingTop: 6, - paddingBottom: 6, - borderWidth: 0, - color: theme.text, - height: 26, - opacity: 1, + + bgTransparent: { backgroundColor: 'transparent', }, - iconContainer: { - top: 7, - ...pointerEventsNone, + + bgDark: { + backgroundColor: theme.inverse, }, - icon: { - width: variables.iconSizeExtraSmall, - height: variables.iconSizeExtraSmall, + + opacity0: { + opacity: 0, }, - }), - badge: { - backgroundColor: theme.border, - borderRadius: 14, - height: variables.iconSizeNormal, - flexDirection: 'row', - paddingHorizontal: 7, - alignItems: 'center', - }, + opacity1: { + opacity: 1, + }, - badgeSuccess: { - backgroundColor: theme.success, - }, + textDanger: { + color: theme.danger, + }, - badgeSuccessPressed: { - backgroundColor: theme.successHover, - }, + borderRadiusNormal: { + borderRadius: variables.buttonBorderRadius, + }, - badgeAdHocSuccess: { - backgroundColor: theme.badgeAdHoc, - }, + button: { + backgroundColor: theme.buttonDefaultBG, + borderRadius: variables.buttonBorderRadius, + minHeight: variables.componentSizeLarge, + justifyContent: 'center', + ...spacing.ph3, + }, - badgeAdHocSuccessPressed: { - backgroundColor: theme.badgeAdHocHover, - }, + buttonContainer: { + padding: 1, + borderRadius: variables.buttonBorderRadius, + }, - badgeDanger: { - backgroundColor: theme.danger, - }, + buttonText: { + color: theme.text, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontSize: variables.fontSizeNormal, + fontWeight: fontWeightBold, + textAlign: 'center', + flexShrink: 1, - badgeDangerPressed: { - backgroundColor: theme.dangerPressed, - }, + // It is needed to unset the Lineheight. We don't need it for buttons as button always contains single line of text. + // It allows to vertically center the text. + lineHeight: undefined, - badgeText: { - color: theme.text, - fontSize: variables.fontSizeSmall, - lineHeight: variables.lineHeightNormal, - ...whiteSpace.noWrap, - }, + // Add 1px to the Button text to give optical vertical alignment. + paddingBottom: 1, + }, - border: { - borderWidth: 1, - borderRadius: variables.componentBorderRadius, - borderColor: theme.border, - }, - - borderColorFocus: { - borderColor: theme.borderFocus, - }, - - borderColorDanger: { - borderColor: theme.danger, - }, - - textInputDisabled: { - // Adding disabled color theme to indicate user that the field is not editable. - backgroundColor: theme.highlightBG, - borderBottomWidth: 2, - borderColor: theme.borderLighter, - // Adding browser specefic style to bring consistency between Safari and other platforms. - // Applying the Webkit styles only to browsers as it is not available in native. - ...(Browser.getBrowser() - ? { - WebkitTextFillColor: theme.textSupporting, - WebkitOpacity: 1, - } - : {}), - color: theme.textSupporting, - }, - - uploadReceiptView: (isSmallScreenWidth: boolean): ViewStyle => ({ - borderRadius: variables.componentBorderRadiusLarge, - borderWidth: isSmallScreenWidth ? 0 : 2, - borderColor: theme.borderFocus, - borderStyle: 'dotted', - marginBottom: 20, - marginLeft: 20, - marginRight: 20, - justifyContent: 'center', - alignItems: 'center', - padding: 40, - gap: 4, - flex: 1, - }), - - cameraView: { - flex: 1, - overflow: 'hidden', - padding: 10, - borderRadius: 28, - borderStyle: 'solid', - borderWidth: 8, - backgroundColor: theme.highlightBG, - borderColor: theme.appBG, - }, - - permissionView: { - paddingVertical: 108, - paddingHorizontal: 61, - alignItems: 'center', - justifyContent: 'center', - }, - - headerAnonymousFooter: { - color: theme.heading, - fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM, - fontSize: variables.fontSizeXLarge, - lineHeight: variables.lineHeightXXLarge, - }, - - headerText: { - color: theme.heading, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeNormal, - fontWeight: fontWeightBold, - }, + buttonSmall: { + borderRadius: variables.buttonBorderRadius, + minHeight: variables.componentSizeSmall, + paddingTop: 4, + paddingHorizontal: 14, + paddingBottom: 4, + backgroundColor: theme.buttonDefaultBG, + }, - headerGap: { - height: CONST.DESKTOP_HEADER_PADDING, - }, + buttonMedium: { + borderRadius: variables.buttonBorderRadius, + minHeight: variables.componentSizeNormal, + paddingTop: 12, + paddingRight: 16, + paddingBottom: 12, + paddingLeft: 16, + backgroundColor: theme.buttonDefaultBG, + }, - pushTextRight: { - left: 100000, - }, + buttonLarge: { + borderRadius: variables.buttonBorderRadius, + minHeight: variables.componentSizeLarge, + paddingTop: 8, + paddingRight: 10, + paddingBottom: 8, + paddingLeft: 18, + backgroundColor: theme.buttonDefaultBG, + }, - reportOptions: { - marginLeft: 8, - }, + buttonSmallText: { + fontSize: variables.fontSizeSmall, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontWeight: fontWeightBold, + textAlign: 'center', + }, - chatItemComposeSecondaryRow: { - height: 15, - marginBottom: 5, - marginTop: 5, - }, + buttonMediumText: { + fontSize: variables.fontSizeLabel, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontWeight: fontWeightBold, + textAlign: 'center', + }, - chatItemComposeSecondaryRowSubText: { - color: theme.textSupporting, - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeSmall, - lineHeight: variables.lineHeightSmall, - }, - - chatItemComposeSecondaryRowOffset: { - marginLeft: variables.chatInputSpacing, - }, - - offlineIndicator: { - marginLeft: variables.chatInputSpacing, - }, - - offlineIndicatorMobile: { - paddingLeft: 20, - paddingTop: 5, - paddingBottom: 5, - }, - - offlineIndicatorRow: { - height: 25, - }, - - // Actions - actionAvatar: { - borderRadius: 20, - }, - - componentHeightLarge: { - height: variables.inputHeight, - }, - - calendarHeader: { - height: 50, - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingHorizontal: 15, - paddingRight: 5, - ...userSelect.userSelectNone, - }, - - calendarDayRoot: { - flex: 1, - height: 45, - justifyContent: 'center', - alignItems: 'center', - ...userSelect.userSelectNone, - }, - - calendarDayContainer: { - width: 30, - height: 30, - justifyContent: 'center', - alignItems: 'center', - borderRadius: 15, - overflow: 'hidden', - }, - - calendarDayContainerSelected: { - backgroundColor: theme.buttonDefaultBG, - }, - - autoGrowHeightInputContainer: (textInputHeight: number, minHeight: number, maxHeight: number): ViewStyle => ({ - height: lodashClamp(textInputHeight, minHeight, maxHeight), - minHeight, - }), - - autoGrowHeightHiddenInput: (maxWidth: number, maxHeight?: number): TextStyle => ({ - maxWidth, - maxHeight: maxHeight && maxHeight + 1, - overflow: 'hidden', - }), - - textInputContainer: { - flex: 1, - justifyContent: 'center', - height: '100%', - backgroundColor: 'transparent', - borderBottomWidth: 2, - borderColor: theme.border, - overflow: 'hidden', - }, + buttonLargeText: { + fontSize: variables.fontSizeNormal, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontWeight: fontWeightBold, + textAlign: 'center', + }, - textInputLabel: { - position: 'absolute', - left: 0, - top: 0, - fontSize: variables.fontSizeNormal, - color: theme.textSupporting, - fontFamily: fontFamily.EXP_NEUE, - width: '100%', - }, - - textInputLabelBackground: { - position: 'absolute', - top: 0, - width: '100%', - height: 23, - backgroundColor: theme.componentBG, - }, - - textInputLabelDesktop: { - transformOrigin: 'left center', - }, - - textInputLabelTransformation: (translateY: AnimatableNumericValue, translateX: AnimatableNumericValue, scale: AnimatableNumericValue): TextStyle => ({ - transform: [{translateY}, {translateX}, {scale}], - }), - - baseTextInput: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeNormal, - lineHeight: variables.lineHeightXLarge, - color: theme.text, - paddingTop: 23, - paddingBottom: 8, - paddingLeft: 0, - borderWidth: 0, - }, + buttonDefaultHovered: { + backgroundColor: theme.buttonHoveredBG, + borderWidth: 0, + }, - textInputMultiline: { - scrollPadding: '23px 0 0 0', - }, + buttonSuccess: { + backgroundColor: theme.success, + borderWidth: 0, + }, - textInputMultilineContainer: { - paddingTop: 23, - }, - - textInputAndIconContainer: { - flex: 1, - height: '100%', - zIndex: -1, - flexDirection: 'row', - }, - - textInputDesktop: addOutlineWidth({}, 0), - - textInputIconContainer: { - paddingHorizontal: 11, - justifyContent: 'center', - margin: 1, - }, - - secureInput: { - borderTopRightRadius: 0, - borderBottomRightRadius: 0, - }, - - textInput: { - backgroundColor: 'transparent', - borderRadius: variables.componentBorderRadiusNormal, - height: variables.inputComponentSizeNormal, - borderColor: theme.border, - borderWidth: 1, - color: theme.text, - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeNormal, - paddingLeft: 12, - paddingRight: 12, - paddingTop: 10, - paddingBottom: 10, - textAlignVertical: 'center', - }, - - textInputPrefixWrapper: { - position: 'absolute', - left: 0, - top: 0, - height: variables.inputHeight, - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - paddingTop: 23, - paddingBottom: 8, - }, + buttonOpacityDisabled: { + opacity: 0.5, + }, - textInputPrefix: { - color: theme.text, - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeNormal, - textAlignVertical: 'center', - }, + buttonSuccessHovered: { + backgroundColor: theme.successHover, + borderWidth: 0, + }, - pickerContainer: { - borderBottomWidth: 2, - paddingLeft: 0, - borderStyle: 'solid', - borderColor: theme.border, - justifyContent: 'center', - backgroundColor: 'transparent', - height: variables.inputHeight, - overflow: 'hidden', - }, - - pickerContainerSmall: { - height: variables.inputHeightSmall, - }, - - pickerLabel: { - position: 'absolute', - left: 0, - top: 6, - zIndex: 1, - }, - - picker: (disabled = false, backgroundColor = theme.appBG): PickerStyle => ({ - iconContainer: { - top: Math.round(variables.inputHeight * 0.5) - 11, - right: 0, - ...pointerEventsNone, + buttonDanger: { + backgroundColor: theme.danger, + borderWidth: 0, }, - inputWeb: { - appearance: 'none', - ...(disabled ? cursor.cursorDisabled : cursor.cursorPointer), - ...picker(theme), - backgroundColor, + buttonDangerHovered: { + backgroundColor: theme.dangerHover, + borderWidth: 0, }, - inputIOS: { - ...picker(theme), + buttonDisabled: { + backgroundColor: theme.buttonDefaultBG, + borderWidth: 0, }, - done: { - color: theme.text, + + buttonDivider: { + height: variables.dropDownButtonDividerHeight, + borderWidth: 0.7, + borderColor: theme.text, }, - doneDepressed: { - fontSize: 17, + + noBorderRadius: { + borderRadius: 0, }, - modalViewMiddle: { - backgroundColor: theme.border, - borderTopWidth: 0, + + noRightBorderRadius: { + borderTopRightRadius: 0, + borderBottomRightRadius: 0, }, - modalViewBottom: { - backgroundColor: theme.highlightBG, + + noLeftBorderRadius: { + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, }, - inputAndroid: { - ...picker(theme), + buttonCTA: { + paddingVertical: 6, + ...spacing.mh4, }, - }), - disabledText: { - color: theme.icon, - }, + buttonCTAIcon: { + marginRight: 22, - inputDisabled: { - backgroundColor: theme.highlightBG, - color: theme.icon, - }, + // Align vertically with the Button text + paddingBottom: 1, + paddingTop: 1, + }, - noOutline: addOutlineWidth({}, 0), + buttonConfirm: { + margin: 20, + }, - errorOutline: { - borderColor: theme.danger, - }, + attachmentButtonBigScreen: { + minWidth: 300, + alignSelf: 'center', + }, - textLabelSupporting: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeLabel, - color: theme.textSupporting, - }, + buttonConfirmText: { + paddingLeft: 20, + paddingRight: 20, + }, - textLabelError: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeLabel, - color: theme.textError, - }, - - textReceiptUpload: { - ...headlineFont, - fontSize: variables.fontSizeXLarge, - color: theme.textLight, - textAlign: 'center', - }, - - subTextReceiptUpload: { - fontFamily: fontFamily.EXP_NEUE, - lineHeight: variables.lineHeightLarge, - textAlign: 'center', - color: theme.textLight, - }, + buttonSuccessText: { + color: theme.textLight, + }, - furtherDetailsText: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeSmall, - color: theme.textSupporting, - }, - - lh16: { - lineHeight: 16, - }, - - lh20: { - lineHeight: 20, - }, - - lh140Percent: { - lineHeight: '140%', - }, - - formHelp: { - color: theme.textSupporting, - fontSize: variables.fontSizeLabel, - lineHeight: variables.lineHeightLarge, - marginBottom: 4, - }, - - formError: { - color: theme.textError, - fontSize: variables.fontSizeLabel, - lineHeight: variables.formErrorLineHeight, - marginBottom: 4, - }, - - formSuccess: { - color: theme.success, - fontSize: variables.fontSizeLabel, - lineHeight: 18, - marginBottom: 4, - }, - - desktopRedirectPage: { - backgroundColor: theme.appBG, - minHeight: '100%', - flex: 1, - alignItems: 'center', - }, - - signInPage: { - backgroundColor: theme.highlightBG, - minHeight: '100%', - flex: 1, - }, - - signInPageHeroCenter: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - justifyContent: 'center', - alignItems: 'center', - }, - - signInPageGradient: { - height: '100%', - width: 540, - position: 'absolute', - top: 0, - left: 0, - }, - - signInPageGradientMobile: { - height: 300, - width: 800, - position: 'absolute', - top: 0, - left: 0, - }, - - signInBackground: { - position: 'absolute', - bottom: 0, - left: 0, - minHeight: 700, - }, - - signInPageInner: { - marginLeft: 'auto', - marginRight: 'auto', - height: '100%', - width: '100%', - }, - - signInPageContentTopSpacer: { - maxHeight: 132, - minHeight: 24, - }, - - signInPageContentTopSpacerSmallScreens: { - maxHeight: 132, - minHeight: 45, - }, - - signInPageLeftContainer: { - paddingLeft: 40, - paddingRight: 40, - }, - - signInPageLeftContainerWide: { - maxWidth: variables.sideBarWidth, - }, - - signInPageWelcomeFormContainer: { - maxWidth: CONST.SIGN_IN_FORM_WIDTH, - }, - - signInPageWelcomeTextContainer: { - width: CONST.SIGN_IN_FORM_WIDTH, - }, - - changeExpensifyLoginLinkContainer: { - flexDirection: 'row', - flexWrap: 'wrap', - ...wordBreak.breakWord, - }, - - // Sidebar Styles - sidebar: { - backgroundColor: theme.sidebar, - height: '100%', - }, - - sidebarAnimatedWrapperContainer: { - height: '100%', - position: 'absolute', - }, - - sidebarFooter: { - alignItems: 'center', - display: 'flex', - justifyContent: 'center', - paddingVertical: variables.lineHeightXLarge, - width: '100%', - }, - - sidebarAvatar: { - backgroundColor: theme.icon, - borderRadius: 20, - height: variables.componentSizeNormal, - width: variables.componentSizeNormal, - }, - - statusIndicator: (backgroundColor = theme.danger): ViewStyle => ({ - borderColor: theme.sidebar, - backgroundColor, - borderRadius: 8, - borderWidth: 2, - position: 'absolute', - right: -2, - top: -1, - height: 16, - width: 16, - zIndex: 10, - }), - - floatingActionButtonContainer: { - position: 'absolute', - right: 20, - - // The bottom of the floating action button should align with the bottom of the compose box. - // The value should be equal to the height + marginBottom + marginTop of chatItemComposeSecondaryRow - bottom: 25, - }, - - floatingActionButton: { - backgroundColor: theme.success, - height: variables.componentSizeLarge, - width: variables.componentSizeLarge, - borderRadius: 999, - alignItems: 'center', - justifyContent: 'center', - }, - - sidebarFooterUsername: { - color: theme.heading, - fontSize: variables.fontSizeLabel, - fontWeight: '700', - width: 200, - textOverflow: 'ellipsis', - overflow: 'hidden', - ...whiteSpace.noWrap, - }, - - sidebarFooterLink: { - color: theme.textSupporting, - fontSize: variables.fontSizeSmall, - textDecorationLine: 'none', - fontFamily: fontFamily.EXP_NEUE, - lineHeight: 20, - }, - - sidebarListContainer: { - scrollbarWidth: 'none', - paddingBottom: 4, - }, - - sidebarListItem: { - justifyContent: 'center', - textDecorationLine: 'none', - }, - - RHPNavigatorContainer: (isSmallScreenWidth: boolean): ViewStyle => ({ - width: isSmallScreenWidth ? '100%' : variables.sideBarWidth, - position: 'absolute', - right: 0, - height: '100%', - }), - - onlyEmojisText: { - fontSize: variables.fontSizeOnlyEmojis, - lineHeight: variables.fontSizeOnlyEmojisHeight, - }, - - onlyEmojisTextLineHeight: { - lineHeight: variables.fontSizeOnlyEmojisHeight, - }, - - createMenuPositionSidebar: (windowHeight: number): AnchorPosition => ({ - horizontal: 18, - vertical: windowHeight - 100, - }), - - createMenuPositionProfile: (windowWidth: number): AnchorPosition => ({ - horizontal: windowWidth - 355, - ...getPopOverVerticalOffset(162), - }), - - createMenuPositionReportActionCompose: (windowHeight: number): AnchorPosition => ({ - horizontal: 18 + variables.sideBarWidth, - vertical: windowHeight - 83, - }), - - createMenuPositionRightSidepane: { - right: 18, - bottom: 75, - }, - - createMenuContainer: { - width: variables.sideBarWidth - 40, - paddingVertical: 12, - }, - - createMenuHeaderText: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeLabel, - color: theme.heading, - }, - - popoverMenuItem: { - flexDirection: 'row', - borderRadius: 0, - paddingHorizontal: 20, - paddingVertical: 12, - justifyContent: 'space-between', - width: '100%', - }, - - popoverMenuIcon: { - width: variables.componentSizeNormal, - justifyContent: 'center', - alignItems: 'center', - }, - - popoverMenuText: { - fontSize: variables.fontSizeNormal, - color: theme.heading, - }, - - popoverInnerContainer: { - paddingTop: 0, // adjusting this because the mobile modal adds additional padding that we don't need for our layout - maxHeight: '95%', - }, - - menuItemTextContainer: { - minHeight: variables.componentSizeNormal, - }, - - chatLinkRowPressable: { - minWidth: 0, - textDecorationLine: 'none', - flex: 1, - }, - - sidebarLink: { - textDecorationLine: 'none', - }, - - sidebarLinkInner: { - alignItems: 'center', - flexDirection: 'row', - paddingLeft: 20, - paddingRight: 20, - }, - - sidebarLinkText: { - color: theme.textSupporting, - fontSize: variables.fontSizeNormal, - textDecorationLine: 'none', - overflow: 'hidden', - }, - - sidebarLinkHover: { - backgroundColor: theme.sidebarHover, - }, - - sidebarLinkActive: { - backgroundColor: theme.border, - textDecorationLine: 'none', - }, - - sidebarLinkTextBold: { - fontWeight: '700', - color: theme.heading, - }, - - sidebarLinkActiveText: { - color: theme.textSupporting, - fontSize: variables.fontSizeNormal, - textDecorationLine: 'none', - overflow: 'hidden', - }, + buttonDangerText: { + color: theme.textLight, + }, - optionItemAvatarNameWrapper: { - minWidth: 0, - flex: 1, - }, + hoveredComponentBG: { + backgroundColor: theme.hoverComponentBG, + }, - optionDisplayName: { - fontFamily: fontFamily.EXP_NEUE, - minHeight: variables.alternateTextHeight, - lineHeight: variables.lineHeightXLarge, - ...whiteSpace.noWrap, - }, - - optionDisplayNameCompact: { - minWidth: 'auto', - flexBasis: 'auto', - flexGrow: 0, - flexShrink: 1, - }, - - displayNameTooltipEllipsis: { - position: 'absolute', - opacity: 0, - right: 0, - bottom: 0, - }, - - optionAlternateText: { - minHeight: variables.alternateTextHeight, - lineHeight: variables.lineHeightXLarge, - }, - - optionAlternateTextCompact: { - flexShrink: 1, - flexGrow: 1, - flexBasis: 'auto', - ...optionAlternateTextPlatformStyles, - }, - - optionRow: { - minHeight: variables.optionRowHeight, - paddingTop: 12, - paddingBottom: 12, - }, - - optionRowSelected: { - backgroundColor: theme.activeComponentBG, - }, - - optionRowDisabled: { - color: theme.textSupporting, - }, - - optionRowCompact: { - height: variables.optionRowHeightCompact, - paddingTop: 12, - paddingBottom: 12, - }, - - optionsListSectionHeader: { - height: variables.optionsListSectionHeaderHeight, - }, - - overlayStyles: (current: OverlayStylesParams): ViewStyle => ({ - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". - position: 'fixed' as ViewStyle['position'], - - // We need to stretch the overlay to cover the sidebar and the translate animation distance. - left: -2 * variables.sideBarWidth, - top: 0, - bottom: 0, - right: 0, - backgroundColor: theme.shadow, - opacity: current.progress.interpolate({ - inputRange: [0, 1], - outputRange: [0, variables.overlayOpacity], - extrapolate: 'clamp', - }), - }), - - appContent: { - backgroundColor: theme.appBG, - overflow: 'hidden', - }, - - appContentHeader: { - height: variables.contentHeaderHeight, - justifyContent: 'center', - display: 'flex', - paddingRight: 20, - }, - - appContentHeaderTitle: { - alignItems: 'center', - flexDirection: 'row', - }, - - LHNToggle: { - alignItems: 'center', - height: variables.contentHeaderHeight, - justifyContent: 'center', - paddingRight: 10, - paddingLeft: 20, - }, - - LHNToggleIcon: { - height: 15, - width: 18, - }, - - chatContent: { - flex: 4, - justifyContent: 'flex-end', - }, - - chatContentScrollView: { - flexGrow: 1, - justifyContent: 'flex-start', - paddingBottom: 16, - }, - - // Chat Item - chatItem: { - display: 'flex', - flexDirection: 'row', - paddingTop: 8, - paddingBottom: 8, - paddingLeft: 20, - paddingRight: 20, - }, - - chatItemRightGrouped: { - flexGrow: 1, - flexShrink: 1, - flexBasis: 0, - position: 'relative', - marginLeft: variables.chatInputSpacing, - }, - - chatItemRight: { - flexGrow: 1, - flexShrink: 1, - flexBasis: 0, - position: 'relative', - }, - - chatItemMessageHeader: { - alignItems: 'center', - display: 'flex', - flexDirection: 'row', - flexWrap: 'nowrap', - }, - - chatItemMessageHeaderSender: { - color: theme.heading, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeNormal, - fontWeight: fontWeightBold, - lineHeight: variables.lineHeightXLarge, - ...wordBreak.breakWord, - }, - - chatItemMessageHeaderTimestamp: { - flexShrink: 0, - color: theme.textSupporting, - fontSize: variables.fontSizeSmall, - paddingTop: 2, - }, - - chatItemMessage: { - color: theme.text, - fontSize: variables.fontSizeNormal, - fontFamily: fontFamily.EXP_NEUE, - lineHeight: variables.lineHeightXLarge, - maxWidth: '100%', - ...cursor.cursorAuto, - ...whiteSpace.preWrap, - ...wordBreak.breakWord, - }, - - chatItemComposeWithFirstRow: { - minHeight: 90, - }, - - chatItemFullComposeRow: { - ...sizing.h100, - }, - - chatItemComposeBoxColor: { - borderColor: theme.border, - }, + activeComponentBG: { + backgroundColor: theme.activeComponentBG, + }, - chatItemComposeBoxFocusedColor: { - borderColor: theme.borderFocus, - }, + fontWeightBold: { + fontWeight: fontWeightBold, + }, - chatItemComposeBox: { - backgroundColor: theme.componentBG, - borderWidth: 1, - borderRadius: variables.componentBorderRadiusRounded, - minHeight: variables.componentSizeMedium, - }, - - chatItemFullComposeBox: { - ...flex.flex1, - ...sizing.h100, - }, - - chatFooter: { - paddingLeft: 20, - paddingRight: 20, - display: 'flex', - backgroundColor: theme.appBG, - }, - - chatFooterFullCompose: { - flex: 1, - }, - - chatItemDraft: { - display: 'flex', - flexDirection: 'row', - paddingTop: 8, - paddingBottom: 8, - paddingLeft: 20, - paddingRight: 20, - }, - - chatItemReactionsDraftRight: { - marginLeft: 52, - }, - chatFooterAtTheTop: { - flexGrow: 1, - justifyContent: 'flex-start', - }, - - // Be extremely careful when editing the compose styles, as it is easy to introduce regressions. - // Make sure you run the following tests against any changes: #12669 - textInputCompose: addOutlineWidth( - { - backgroundColor: theme.componentBG, - borderColor: theme.border, - color: theme.text, - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeNormal, - borderWidth: 0, - height: 'auto', - lineHeight: variables.lineHeightXLarge, - ...overflowXHidden, + touchableButtonImage: { + alignItems: 'center', + height: variables.componentSizeNormal, + justifyContent: 'center', + width: variables.componentSizeNormal, + }, - // On Android, multiline TextInput with height: 'auto' will show extra padding unless they are configured with - // paddingVertical: 0, alignSelf: 'center', and textAlignVertical: 'center' + visuallyHidden: { + ...visibility.hidden, + overflow: 'hidden', + width: 0, + height: 0, + }, - paddingHorizontal: variables.avatarChatSpacing, - paddingTop: 0, - paddingBottom: 0, - alignSelf: 'center', - textAlignVertical: 'center', + visibilityHidden: { + ...visibility.hidden, }, - 0, - ), - textInputFullCompose: { - alignSelf: 'stretch', - flex: 1, - maxHeight: '100%', - textAlignVertical: 'top', - }, + loadingVBAAnimation: { + width: 140, + height: 140, + }, - editInputComposeSpacing: { - backgroundColor: theme.transparent, - marginVertical: 8, - }, - - // composer padding should not be modified unless thoroughly tested against the cases in this PR: #12669 - textInputComposeSpacing: { - paddingVertical: 5, - ...flex.flexRow, - flex: 1, - }, - - textInputComposeBorder: { - borderLeftWidth: 1, - borderColor: theme.border, - }, + pickerSmall: (backgroundColor = theme.highlightBG) => + ({ + inputIOS: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeSmall, + paddingLeft: 0, + paddingRight: 17, + paddingTop: 6, + paddingBottom: 6, + borderWidth: 0, + color: theme.text, + height: 26, + opacity: 1, + backgroundColor: 'transparent', + }, + done: { + color: theme.text, + }, + doneDepressed: { + // Extracted from react-native-picker-select, src/styles.js + fontSize: 17, + }, + modalViewMiddle: { + backgroundColor: theme.border, + borderTopWidth: 0, + }, + modalViewBottom: { + backgroundColor: theme.highlightBG, + }, + inputWeb: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeSmall, + paddingLeft: 0, + paddingRight: 17, + paddingTop: 6, + paddingBottom: 6, + borderWidth: 0, + color: theme.text, + appearance: 'none', + height: 26, + opacity: 1, + backgroundColor, + ...cursor.cursorPointer, + }, + inputAndroid: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeSmall, + paddingLeft: 0, + paddingRight: 17, + paddingTop: 6, + paddingBottom: 6, + borderWidth: 0, + color: theme.text, + height: 26, + opacity: 1, + backgroundColor: 'transparent', + }, + iconContainer: { + top: 7, + ...pointerEventsNone, + }, + icon: { + width: variables.iconSizeExtraSmall, + height: variables.iconSizeExtraSmall, + }, + } satisfies CustomPickerStyle), + + badge: { + backgroundColor: theme.border, + borderRadius: 14, + height: variables.iconSizeNormal, + flexDirection: 'row', + paddingHorizontal: 7, + alignItems: 'center', + }, - chatItemSubmitButton: { - alignSelf: 'flex-end', - borderRadius: variables.componentBorderRadiusRounded, - backgroundColor: theme.transparent, - height: 40, - padding: 10, - margin: 3, - justifyContent: 'center', - }, - - emojiPickerContainer: { - backgroundColor: theme.componentBG, - }, - - emojiHeaderContainer: { - backgroundColor: theme.componentBG, - display: 'flex', - height: CONST.EMOJI_PICKER_HEADER_HEIGHT, - justifyContent: 'center', - width: '100%', - }, - - emojiSkinToneTitle: { - width: '100%', - ...spacing.pv1, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - color: theme.heading, - fontSize: variables.fontSizeSmall, - }, - - // Emoji Picker Styles - emojiText: { - textAlign: 'center', - fontSize: variables.emojiSize, - ...spacing.pv0, - ...spacing.ph0, - lineHeight: variables.emojiLineHeight, - }, - - emojiItem: { - width: '12.5%', - textAlign: 'center', - borderRadius: 8, - paddingTop: 2, - paddingBottom: 2, - height: CONST.EMOJI_PICKER_ITEM_HEIGHT, - }, - - emojiItemHighlighted: { - transition: '0.2s ease', - backgroundColor: theme.buttonDefaultBG, - }, - - emojiItemKeyboardHighlighted: { - transition: '0.2s ease', - borderWidth: 1, - borderColor: theme.link, - borderRadius: variables.buttonBorderRadius, - }, - - categoryShortcutButton: { - flex: 1, - borderRadius: 8, - height: CONST.EMOJI_PICKER_ITEM_HEIGHT, - alignItems: 'center', - justifyContent: 'center', - }, - - chatItemEmojiButton: { - alignSelf: 'flex-end', - borderRadius: variables.buttonBorderRadius, - height: 40, - marginVertical: 3, - paddingHorizontal: 10, - justifyContent: 'center', - }, - - editChatItemEmojiWrapper: { - marginRight: 3, - alignSelf: 'flex-end', - }, - - hoveredButton: { - backgroundColor: theme.buttonHoveredBG, - }, - - composerSizeButton: { - alignSelf: 'center', - height: 32, - width: 32, - padding: 6, - margin: 3, - borderRadius: variables.componentBorderRadiusRounded, - backgroundColor: theme.transparent, - justifyContent: 'center', - }, + badgeSuccess: { + backgroundColor: theme.success, + }, - chatItemAttachmentPlaceholder: { - backgroundColor: theme.sidebar, - borderColor: theme.border, - borderWidth: 1, - borderRadius: variables.componentBorderRadiusNormal, - height: 150, - textAlign: 'center', - verticalAlign: 'middle', - width: 200, - }, - - chatSwticherPillWrapper: { - marginTop: 5, - marginRight: 4, - }, - - navigationModalOverlay: { - ...userSelect.userSelectNone, - position: 'absolute', - width: '100%', - height: '100%', - transform: [ - { - translateX: -variables.sideBarWidth, - }, - ], - }, + badgeSuccessPressed: { + backgroundColor: theme.successHover, + }, - sidebarVisible: { - borderRightWidth: 1, - }, + badgeAdHocSuccess: { + backgroundColor: theme.badgeAdHoc, + }, - sidebarHidden: { - width: 0, - borderRightWidth: 0, - }, + badgeAdHocSuccessPressed: { + backgroundColor: theme.badgeAdHocHover, + }, - exampleCheckImage: { - width: '100%', - height: 80, - borderColor: theme.border, - borderWidth: 1, - borderRadius: variables.componentBorderRadiusNormal, - }, - - singleAvatar: { - height: 24, - width: 24, - backgroundColor: theme.icon, - borderRadius: 24, - }, - - singleSubscript: { - height: variables.iconSizeNormal, - width: variables.iconSizeNormal, - backgroundColor: theme.icon, - borderRadius: 20, - zIndex: 1, - }, - - singleAvatarSmall: { - height: 18, - width: 18, - backgroundColor: theme.icon, - borderRadius: 18, - }, - - secondAvatar: { - position: 'absolute', - right: -18, - bottom: -18, - borderWidth: 3, - borderRadius: 30, - borderColor: 'transparent', - }, - - secondAvatarSmall: { - position: 'absolute', - right: -13, - bottom: -13, - borderWidth: 3, - borderRadius: 18, - borderColor: 'transparent', - }, - - secondAvatarSubscript: { - position: 'absolute', - right: -6, - bottom: -6, - }, - - secondAvatarSubscriptCompact: { - position: 'absolute', - bottom: -1, - right: -1, - }, - - secondAvatarSubscriptSmallNormal: { - position: 'absolute', - bottom: 0, - right: 0, - }, - - leftSideLargeAvatar: { - left: 15, - }, - - rightSideLargeAvatar: { - right: 15, - zIndex: 2, - borderWidth: 4, - borderRadius: 100, - }, - - secondAvatarInline: { - bottom: -3, - right: -25, - borderWidth: 3, - borderRadius: 18, - borderColor: theme.cardBorder, - backgroundColor: theme.appBG, - }, - - avatarLarge: { - width: variables.avatarSizeLarge, - height: variables.avatarSizeLarge, - }, - - avatarNormal: { - height: variables.componentSizeNormal, - width: variables.componentSizeNormal, - borderRadius: variables.componentSizeNormal, - }, - - avatarSmall: { - height: variables.avatarSizeSmall, - width: variables.avatarSizeSmall, - borderRadius: variables.avatarSizeSmall, - }, - - avatarInnerText: { - color: theme.textLight, - fontSize: variables.fontSizeSmall, - lineHeight: undefined, - marginLeft: -3, - textAlign: 'center', - }, - - avatarInnerTextSmall: { - color: theme.textLight, - fontSize: variables.fontSizeExtraSmall, - lineHeight: undefined, - marginLeft: -2, - textAlign: 'center', - }, - - avatarSpace: { - top: 3, - left: 3, - }, - - avatar: { - backgroundColor: theme.sidebar, - borderColor: theme.sidebar, - }, - - focusedAvatar: { - backgroundColor: theme.border, - borderColor: theme.border, - }, - - emptyAvatar: { - height: variables.avatarSizeNormal, - width: variables.avatarSizeNormal, - }, - - emptyAvatarSmallNormal: { - height: variables.avatarSizeSmallNormal, - width: variables.avatarSizeSmallNormal, - }, - - emptyAvatarSmall: { - height: variables.avatarSizeSmall, - width: variables.avatarSizeSmall, - }, - - emptyAvatarSmaller: { - height: variables.avatarSizeSmaller, - width: variables.avatarSizeSmaller, - }, - - emptyAvatarMedium: { - height: variables.avatarSizeMedium, - width: variables.avatarSizeMedium, - }, - - emptyAvatarLarge: { - height: variables.avatarSizeLarge, - width: variables.avatarSizeLarge, - }, - - emptyAvatarMargin: { - marginRight: variables.avatarChatSpacing, - }, - - emptyAvatarMarginChat: { - marginRight: variables.avatarChatSpacing - 12, - }, - - emptyAvatarMarginSmall: { - marginRight: variables.avatarChatSpacing - 4, - }, - - emptyAvatarMarginSmaller: { - marginRight: variables.avatarChatSpacing - 4, - }, - - modalViewContainer: { - alignItems: 'center', - flex: 1, - }, - - borderTop: { - borderTopWidth: variables.borderTopWidth, - borderColor: theme.border, - }, + badgeDanger: { + backgroundColor: theme.danger, + }, - borderTopRounded: { - borderTopWidth: 1, - borderColor: theme.border, - borderTopLeftRadius: variables.componentBorderRadiusNormal, - borderTopRightRadius: variables.componentBorderRadiusNormal, - }, + badgeDangerPressed: { + backgroundColor: theme.dangerPressed, + }, - borderBottomRounded: { - borderBottomWidth: 1, - borderColor: theme.border, - borderBottomLeftRadius: variables.componentBorderRadiusNormal, - borderBottomRightRadius: variables.componentBorderRadiusNormal, - }, + badgeText: { + color: theme.text, + fontSize: variables.fontSizeSmall, + lineHeight: variables.lineHeightNormal, + ...whiteSpace.noWrap, + }, - borderBottom: { - borderBottomWidth: 1, - borderColor: theme.border, - }, + border: { + borderWidth: 1, + borderRadius: variables.componentBorderRadius, + borderColor: theme.border, + }, - borderNone: { - borderWidth: 0, - borderBottomWidth: 0, - }, + borderColorFocus: { + borderColor: theme.borderFocus, + }, - borderRight: { - borderRightWidth: 1, - borderColor: theme.border, - }, + borderColorDanger: { + borderColor: theme.danger, + }, - borderLeft: { - borderLeftWidth: 1, - borderColor: theme.border, - }, - - pointerEventsNone, - - pointerEventsAuto, - - headerBar: { - overflow: 'hidden', - justifyContent: 'center', - display: 'flex', - paddingLeft: 20, - height: variables.contentHeaderHeight, - width: '100%', - }, - - imageViewContainer: { - width: '100%', - height: '100%', - alignItems: 'center', - justifyContent: 'center', - }, - - imageModalPDF: { - flex: 1, - backgroundColor: theme.modalBackground, - }, - - PDFView: { - // `display: grid` is not supported in native platforms! - // It's being used on Web/Desktop only to vertically center short PDFs, - // while preventing the overflow of the top of long PDF files. - display: 'grid', - backgroundColor: theme.modalBackground, - width: '100%', - height: '100%', - justifyContent: 'center', - overflow: 'hidden', - alignItems: 'center', - }, - - PDFViewList: { - overflowX: 'hidden', - // There properties disable "focus" effect on list - boxShadow: 'none', - outline: 'none', - }, - - getPDFPasswordFormStyle: (isSmallScreenWidth: boolean): ViewStyle => ({ - width: isSmallScreenWidth ? '100%' : 350, - ...(isSmallScreenWidth && flex.flex1), - }), - - modalCenterContentContainer: { - flex: 1, - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - backgroundColor: theme.modalBackdrop, - }, - - centeredModalStyles: (isSmallScreenWidth: boolean, isFullScreenWhenSmall: boolean): ViewStyle => ({ - borderWidth: isSmallScreenWidth && !isFullScreenWhenSmall ? 1 : 0, - marginHorizontal: isSmallScreenWidth ? 0 : 20, - }), - - imageModalImageCenterContainer: { - alignItems: 'center', - flex: 1, - justifyContent: 'center', - width: '100%', - }, - - defaultAttachmentView: { - backgroundColor: theme.sidebar, - borderRadius: variables.componentBorderRadiusNormal, - borderWidth: 1, - borderColor: theme.border, - flexDirection: 'row', - padding: 20, - alignItems: 'center', - }, - - notFoundSafeArea: { - flex: 1, - backgroundColor: theme.heading, - }, - - notFoundView: { - flex: 1, - alignItems: 'center', - paddingTop: 40, - paddingBottom: 40, - justifyContent: 'space-between', - }, - - notFoundLogo: { - width: 202, - height: 63, - }, - - notFoundContent: { - alignItems: 'center', - }, - - notFoundTextHeader: { - ...headlineFont, - color: theme.heading, - fontSize: variables.fontSizeXLarge, - lineHeight: variables.lineHeightXXLarge, - marginTop: 20, - marginBottom: 8, - textAlign: 'center', - }, - - notFoundTextBody: { - color: theme.componentBG, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - fontSize: 15, - }, - - notFoundButtonText: { - color: theme.link, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - fontSize: 15, - }, - - blockingViewContainer: { - paddingBottom: variables.contentHeaderHeight, - }, - - defaultModalContainer: { - backgroundColor: theme.componentBG, - borderColor: theme.transparent, - }, - - reportActionContextMenuMiniButton: { - ...spacing.p1, - ...spacing.mv1, - ...spacing.mh1, - ...{borderRadius: variables.buttonBorderRadius}, - }, - - reportActionSystemMessageContainer: { - marginLeft: 42, - }, - - reportDetailsTitleContainer: { - ...flex.flexColumn, - ...flex.alignItemsCenter, - paddingHorizontal: 20, - paddingBottom: 20, - }, - - reportDetailsRoomInfo: { - ...flex.flex1, - ...flex.flexColumn, - ...flex.alignItemsCenter, - }, - - reportSettingsVisibilityText: { - textTransform: 'capitalize', - }, - - settingsPageBackground: { - flexDirection: 'column', - width: '100%', - flexGrow: 1, - }, - - settingsPageBody: { - width: '100%', - justifyContent: 'space-around', - }, - - settingsPageColumn: { - width: '100%', - alignItems: 'center', - justifyContent: 'space-around', - }, - - settingsPageContainer: { - justifyContent: 'space-between', - alignItems: 'center', - width: '100%', - }, - - twoFactorAuthSection: { - backgroundColor: theme.appBG, - padding: 0, - }, - - twoFactorAuthCodesBox: ({isExtraSmallScreenWidth, isSmallScreenWidth}: TwoFactorAuthCodesBoxParams): ViewStyle => { - let paddingHorizontal = spacing.ph9; - - if (isSmallScreenWidth) { - paddingHorizontal = spacing.ph4; - } - - if (isExtraSmallScreenWidth) { - paddingHorizontal = spacing.ph2; - } - - return { - alignItems: 'center', - justifyContent: 'center', + textInputDisabled: { + // Adding disabled color theme to indicate user that the field is not editable. backgroundColor: theme.highlightBG, - paddingVertical: 28, - borderRadius: 16, - marginTop: 32, - ...paddingHorizontal, - }; - }, - - twoFactorLoadingContainer: { - alignItems: 'center', - justifyContent: 'center', - height: 210, - }, - - twoFactorAuthCodesContainer: { - alignItems: 'center', - justifyContent: 'center', - flexDirection: 'row', - flexWrap: 'wrap', - gap: 12, - }, - - twoFactorAuthCode: { - fontFamily: fontFamily.MONOSPACE, - width: 112, - textAlign: 'center', - }, - - twoFactorAuthCodesButtonsContainer: { - flexDirection: 'row', - justifyContent: 'center', - gap: 12, - marginTop: 20, - flexWrap: 'wrap', - }, - - twoFactorAuthCodesButton: { - minWidth: 112, - }, - - twoFactorAuthCopyCodeButton: { - minWidth: 110, - }, - - anonymousRoomFooter: (isSmallSizeLayout: boolean): ViewStyle & TextStyle => ({ - flexDirection: isSmallSizeLayout ? 'column' : 'row', - ...(!isSmallSizeLayout && { + borderBottomWidth: 2, + borderColor: theme.borderLighter, + // Adding browser specefic style to bring consistency between Safari and other platforms. + // Applying the Webkit styles only to browsers as it is not available in native. + ...(Browser.getBrowser() + ? { + WebkitTextFillColor: theme.textSupporting, + WebkitOpacity: 1, + } + : {}), + color: theme.textSupporting, + }, + + uploadReceiptView: (isSmallScreenWidth: boolean) => + ({ + borderRadius: variables.componentBorderRadiusLarge, + borderWidth: isSmallScreenWidth ? 0 : 2, + borderColor: theme.borderFocus, + borderStyle: 'dotted', + marginBottom: 20, + marginLeft: 20, + marginRight: 20, + justifyContent: 'center', + alignItems: 'center', + padding: 40, + gap: 4, + flex: 1, + } satisfies ViewStyle), + + cameraView: { + flex: 1, + overflow: 'hidden', + padding: 10, + borderRadius: 28, + borderStyle: 'solid', + borderWidth: 8, + backgroundColor: theme.highlightBG, + borderColor: theme.appBG, + }, + + permissionView: { + paddingVertical: 108, + paddingHorizontal: 61, alignItems: 'center', - justifyContent: 'space-between', - }), - padding: 20, - backgroundColor: theme.sidebar, - borderRadius: variables.componentBorderRadiusLarge, - overflow: 'hidden', - }), - anonymousRoomFooterWordmarkAndLogoContainer: (isSmallSizeLayout: boolean): ViewStyle => ({ - flexDirection: 'row', - alignItems: 'center', - ...(isSmallSizeLayout && { - justifyContent: 'space-between', - marginTop: 16, - }), - }), - anonymousRoomFooterLogo: { - width: 88, - marginLeft: 0, - height: 20, - }, - anonymousRoomFooterLogoTaglineText: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeMedium, - color: theme.textLight, - }, - signInButtonAvatar: { - width: 80, - }, - - anonymousRoomFooterSignInButton: { - width: 110, - }, - - roomHeaderAvatarSize: { - height: variables.componentSizeLarge, - width: variables.componentSizeLarge, - }, - - roomHeaderAvatar: { - backgroundColor: theme.appBG, - borderRadius: 100, - borderColor: theme.componentBG, - borderWidth: 4, - }, - - roomHeaderAvatarOverlay: { - position: 'absolute', - top: 0, - right: 0, - bottom: 0, - left: 0, - backgroundColor: theme.overlay, - opacity: variables.overlayOpacity, - borderRadius: 88, - }, - - rootNavigatorContainerStyles: (isSmallScreenWidth: boolean): ViewStyle => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1}), - RHPNavigatorContainerNavigatorContainerStyles: (isSmallScreenWidth: boolean): ViewStyle => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1}), - - avatarInnerTextChat: { - color: theme.textLight, - fontSize: variables.fontSizeXLarge, - fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM, - textAlign: 'center', - fontWeight: 'normal', - position: 'absolute', - width: 88, - left: -16, - }, - - svgAvatarBorder: { - borderRadius: 100, - overflow: 'hidden', - }, - - displayName: { - fontSize: variables.fontSizeLarge, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - color: theme.heading, - }, - - pageWrapper: { - width: '100%', - alignItems: 'center', - padding: 20, - }, - - avatarSectionWrapper: { - width: '100%', - alignItems: 'center', - paddingHorizontal: 20, - paddingBottom: 20, - }, - - avatarSectionWrapperSkeleton: { - width: '100%', - paddingHorizontal: 20, - paddingBottom: 20, - }, - - selectCircle: { - width: variables.componentSizeSmall, - height: variables.componentSizeSmall, - borderColor: theme.border, - borderWidth: 1, - borderRadius: variables.componentSizeSmall / 2, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: theme.componentBG, - marginLeft: 8, - }, - - unreadIndicatorContainer: { - position: 'absolute', - top: -10, - left: 0, - width: '100%', - height: 20, - paddingHorizontal: 20, - flexDirection: 'row', - alignItems: 'center', - zIndex: 1, - ...cursor.cursorDefault, - }, - - unreadIndicatorLine: { - height: 1, - backgroundColor: theme.unreadIndicator, - flexGrow: 1, - marginRight: 8, - opacity: 0.5, - }, - - threadDividerLine: { - height: 1, - backgroundColor: theme.border, - flexGrow: 1, - marginHorizontal: 20, - }, - - unreadIndicatorText: { - color: theme.unreadIndicator, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeSmall, - fontWeight: fontWeightBold, - textTransform: 'capitalize', - }, - - flipUpsideDown: { - transform: [{rotate: '180deg'}], - }, - - navigationSceneContainer: { - backgroundColor: theme.appBG, - }, - - navigationScreenCardStyle: { - backgroundColor: theme.appBG, - height: '100%', - }, - - navigationSceneFullScreenWrapper: { - borderRadius: variables.componentBorderRadiusCard, - overflow: 'hidden', - height: '100%', - }, - - invisible: { - position: 'absolute', - opacity: 0, - }, - - containerWithSpaceBetween: { - justifyContent: 'space-between', - width: '100%', - flex: 1, - }, - - detailsPageSectionContainer: { - alignSelf: 'flex-start', - }, - - attachmentCarouselContainer: { - height: '100%', - width: '100%', - display: 'flex', - justifyContent: 'center', - ...cursor.cursorUnset, - }, - - attachmentArrow: { - zIndex: 23, - position: 'absolute', - }, - - attachmentRevealButtonContainer: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - ...spacing.ph4, - }, - - arrowIcon: { - height: 40, - width: 40, - alignItems: 'center', - paddingHorizontal: 0, - paddingTop: 0, - paddingBottom: 0, - }, - - detailsPageSectionVersion: { - alignSelf: 'center', - color: theme.textSupporting, - fontSize: variables.fontSizeSmall, - height: 24, - lineHeight: 20, - }, - - switchTrack: { - width: 50, - height: 28, - justifyContent: 'center', - borderRadius: 20, - padding: 15, - backgroundColor: theme.success, - }, - - switchInactive: { - backgroundColor: theme.border, - }, - - switchThumb: { - width: 22, - height: 22, - borderRadius: 11, - position: 'absolute', - left: 4, - backgroundColor: theme.appBG, - }, - - switchThumbTransformation: (translateX: AnimatableNumericValue): ViewStyle => ({ - transform: [{translateX}], - }), - - radioButtonContainer: { - backgroundColor: theme.componentBG, - borderRadius: 10, - height: 20, - width: 20, - borderColor: theme.icon, - borderWidth: 1, - justifyContent: 'center', - alignItems: 'center', - }, - - checkboxPressable: { - borderRadius: 6, - padding: 2, - justifyContent: 'center', - alignItems: 'center', - }, - - checkedContainer: { - backgroundColor: theme.checkBox, - }, - - magicCodeInputContainer: { - flexDirection: 'row', - justifyContent: 'space-between', - minHeight: variables.inputHeight, - }, - - magicCodeInput: { - fontSize: variables.fontSizeXLarge, - color: theme.heading, - lineHeight: variables.inputHeight, - }, - - // Manually style transparent, in iOS Safari, an input in a container with its opacity set to - // 0 (completely transparent) cannot handle user interaction, hence the Paste option is never shown - inputTransparent: { - color: 'transparent', - // These properties are available in browser only - ...(Browser.getBrowser() - ? { - caretColor: 'transparent', - WebkitTextFillColor: 'transparent', - // After setting the input text color to transparent, it acquires the background-color. - // However, it is not possible to override the background-color directly as explained in this resource: https://developer.mozilla.org/en-US/docs/Web/CSS/:autofill - // Therefore, the transition effect needs to be delayed. - transitionDelay: '99999s', - } - : {}), - }, - - iouAmountText: { - ...headlineFont, - fontSize: variables.iouAmountTextSize, - color: theme.heading, - lineHeight: variables.inputHeight, - }, - - iouAmountTextInput: addOutlineWidth( - { - ...headlineFont, - fontSize: variables.iouAmountTextSize, + justifyContent: 'center', + }, + + headerAnonymousFooter: { color: theme.heading, - padding: 0, - lineHeight: undefined, + fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM, + fontSize: variables.fontSizeXLarge, + lineHeight: variables.lineHeightXXLarge, }, - 0, - ), - - moneyRequestConfirmationAmount: { - ...headlineFont, - fontSize: variables.fontSizeh1, - }, - - moneyRequestMenuItem: { - flexDirection: 'row', - borderRadius: 0, - justifyContent: 'space-between', - width: '100%', - paddingHorizontal: 20, - paddingVertical: 12, - }, - - requestPreviewBox: { - marginTop: 12, - maxWidth: variables.sideBarWidth, - }, - - moneyRequestPreviewBox: { - backgroundColor: theme.cardBG, - borderRadius: variables.componentBorderRadiusLarge, - maxWidth: variables.sideBarWidth, - width: '100%', - }, - - moneyRequestPreviewBoxText: { - padding: 16, - }, - - moneyRequestPreviewBoxLoading: { - // When a new IOU request arrives it is very briefly in a loading state, so set the minimum height of the container to 94 to match the rendered height after loading. - // Otherwise, the IOU request pay button will not be fully visible and the user will have to scroll up to reveal the entire IOU request container. - // See https://github.com/Expensify/App/issues/10283. - minHeight: 94, - width: '100%', - }, - - moneyRequestPreviewBoxAvatar: { - marginRight: -10, - marginBottom: 0, - }, - - moneyRequestPreviewAmount: { - ...headlineFont, - ...whiteSpace.preWrap, - color: theme.heading, - }, - - defaultCheckmarkWrapper: { - marginLeft: 8, - alignSelf: 'center', - }, - - iouDetailsContainer: { - flexGrow: 1, - paddingStart: 20, - paddingEnd: 20, - }, - - codeWordWrapper: { - ...codeStyles.codeWordWrapper, - }, - - codeWordStyle: { - borderLeftWidth: 0, - borderRightWidth: 0, - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, - borderTopRightRadius: 0, - borderBottomRightRadius: 0, - paddingLeft: 0, - paddingRight: 0, - justifyContent: 'center', - ...codeStyles.codeWordStyle, - }, - - codeFirstWordStyle: { - borderLeftWidth: 1, - borderTopLeftRadius: 4, - borderBottomLeftRadius: 4, - paddingLeft: 5, - }, - - codeLastWordStyle: { - borderRightWidth: 1, - borderTopRightRadius: 4, - borderBottomRightRadius: 4, - paddingRight: 5, - }, - - fullScreenLoading: { - backgroundColor: theme.componentBG, - opacity: 0.8, - justifyContent: 'center', - alignItems: 'center', - zIndex: 10, - }, - - navigatorFullScreenLoading: { - backgroundColor: theme.highlightBG, - opacity: 1, - }, - - reimbursementAccountFullScreenLoading: { - backgroundColor: theme.componentBG, - opacity: 0.8, - justifyContent: 'flex-start', - alignItems: 'center', - zIndex: 10, - }, - - hiddenElementOutsideOfWindow: { - position: 'absolute', - top: -10000, - left: 0, - opacity: 0, - }, - - growlNotificationWrapper: { - zIndex: 2, - }, - - growlNotificationContainer: { - flex: 1, - justifyContent: 'flex-start', - position: 'absolute', - width: '100%', - top: 20, - ...spacing.pl5, - ...spacing.pr5, - }, - - growlNotificationDesktopContainer: { - maxWidth: variables.sideBarWidth, - right: 0, - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". - position: 'fixed' as ViewStyle['position'], - }, - - growlNotificationTranslateY: (translateY: AnimatableNumericValue): ViewStyle => ({ - transform: [{translateY}], - }), - - makeSlideInTranslation: (translationType: Translation, fromValue: number): CustomAnimation => ({ - from: { - [translationType]: fromValue, - }, - to: { - [translationType]: 0, - }, - }), - - growlNotificationBox: { - backgroundColor: theme.inverse, - borderRadius: variables.componentBorderRadiusNormal, - alignItems: 'center', - flexDirection: 'row', - justifyContent: 'space-between', - shadowColor: theme.shadow, - ...spacing.p5, - }, - - growlNotificationText: { - fontSize: variables.fontSizeNormal, - fontFamily: fontFamily.EXP_NEUE, - width: '90%', - lineHeight: variables.fontSizeNormalHeight, - color: theme.textReversed, - ...spacing.ml4, - }, - - blockquote: { - borderLeftColor: theme.border, - borderLeftWidth: 4, - paddingLeft: 12, - marginVertical: 4, - }, - - noSelect: { - boxShadow: 'none', - outline: 'none', - }, - - cardStyleNavigator: { - overflow: 'hidden', - height: '100%', - }, - - fullscreenCard: { - position: 'absolute', - left: 0, - top: 0, - width: '100%', - height: '100%', - }, - - fullscreenCardWeb: { - left: 'auto', - right: '-24%', - top: '-18%', - height: '120%', - }, - - fullscreenCardWebCentered: { - left: '0', - right: '0', - top: '0', - height: '60%', - }, - - fullscreenCardMobile: { - left: '-20%', - top: '-30%', - width: '150%', - }, - - fullscreenCardMediumScreen: { - left: '-15%', - top: '-30%', - width: '145%', - }, - - smallEditIcon: { - alignItems: 'center', - backgroundColor: theme.buttonHoveredBG, - borderColor: theme.textReversed, - borderRadius: 14, - borderWidth: 3, - color: theme.textReversed, - height: 28, - width: 28, - justifyContent: 'center', - }, - - smallAvatarEditIcon: { - position: 'absolute', - right: -4, - bottom: -4, - }, - - workspaceCard: { - width: '100%', - height: 400, - borderRadius: variables.componentBorderRadiusCard, - overflow: 'hidden', - backgroundColor: theme.heroCard, - }, - - workspaceCardMobile: { - height: 475, - }, - - workspaceCardMediumScreen: { - height: 540, - }, - - workspaceCardMainText: { - fontSize: variables.fontSizeXXXLarge, - fontWeight: 'bold', - lineHeight: variables.fontSizeXXXLarge, - }, - - workspaceCardContent: { - zIndex: 1, - padding: 50, - }, - - workspaceCardContentMediumScreen: { - padding: 25, - }, - - workspaceCardCTA: { - width: 250, - }, - - autoGrowHeightMultilineInput: { - maxHeight: 115, - }, - - peopleRow: { - width: '100%', - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - ...spacing.ph5, - }, - - peopleRowBorderBottom: { - borderColor: theme.border, - borderBottomWidth: 1, - ...spacing.pb2, - }, - - peopleBadge: { - backgroundColor: theme.icon, - ...spacing.ph3, - }, - - peopleBadgeText: { - color: theme.textReversed, - fontSize: variables.fontSizeSmall, - lineHeight: variables.lineHeightNormal, - ...whiteSpace.noWrap, - }, - - offlineFeedback: { - deleted: { - textDecorationLine: 'line-through', - textDecorationStyle: 'solid', - }, - pending: { - opacity: 0.5, + + headerText: { + color: theme.heading, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontSize: variables.fontSizeNormal, + fontWeight: fontWeightBold, + }, + + headerGap: { + height: CONST.DESKTOP_HEADER_PADDING, + }, + + pushTextRight: { + left: 100000, + }, + + reportOptions: { + marginLeft: 8, + }, + + chatItemComposeSecondaryRow: { + height: 15, + marginBottom: 5, + marginTop: 5, + }, + + chatItemComposeSecondaryRowSubText: { + color: theme.textSupporting, + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeSmall, + lineHeight: variables.lineHeightSmall, + }, + + chatItemComposeSecondaryRowOffset: { + marginLeft: variables.chatInputSpacing, + }, + + offlineIndicator: { + marginLeft: variables.chatInputSpacing, + }, + + offlineIndicatorMobile: { + paddingLeft: 20, + paddingTop: 5, + paddingBottom: 5, + }, + + offlineIndicatorRow: { + height: 25, + }, + + // Actions + actionAvatar: { + borderRadius: 20, + }, + + componentHeightLarge: { + height: variables.inputHeight, }, - error: { + + calendarHeader: { + height: 50, flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 15, + paddingRight: 5, + ...userSelect.userSelectNone, + }, + + calendarDayRoot: { + flex: 1, + height: 45, + justifyContent: 'center', alignItems: 'center', + ...userSelect.userSelectNone, }, - container: { - ...spacing.pv2, + + calendarDayContainer: { + width: 30, + height: 30, + justifyContent: 'center', + alignItems: 'center', + borderRadius: 15, + overflow: 'hidden', }, - textContainer: { - flexDirection: 'column', + + calendarDayContainerSelected: { + backgroundColor: theme.buttonDefaultBG, + }, + + autoGrowHeightInputContainer: (textInputHeight: number, minHeight: number, maxHeight: number) => + ({ + height: lodashClamp(textInputHeight, minHeight, maxHeight), + minHeight, + } satisfies ViewStyle), + + autoGrowHeightHiddenInput: (maxWidth: number, maxHeight?: number) => + ({ + maxWidth, + maxHeight: maxHeight && maxHeight + 1, + overflow: 'hidden', + } satisfies TextStyle), + + textInputContainer: { flex: 1, + justifyContent: 'center', + height: '100%', + backgroundColor: 'transparent', + borderBottomWidth: 2, + borderColor: theme.border, + overflow: 'hidden', }, - text: { + + textInputLabel: { + position: 'absolute', + left: 0, + top: 0, + fontSize: variables.fontSizeNormal, color: theme.textSupporting, - textAlignVertical: 'center', - fontSize: variables.fontSizeLabel, + fontFamily: fontFamily.EXP_NEUE, + width: '100%', }, - errorDot: { - marginRight: 12, + + textInputLabelBackground: { + position: 'absolute', + top: 0, + width: '100%', + height: 23, + backgroundColor: theme.componentBG, }, - }, - - dotIndicatorMessage: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }, - - sidebarPopover: { - width: variables.sideBarWidth - 68, - }, - - cardOverlay: { - backgroundColor: theme.overlay, - position: 'absolute', - top: 0, - left: 0, - width: '100%', - height: '100%', - opacity: variables.overlayOpacity, - }, - - communicationsLinkIcon: { - right: -36, - top: 0, - bottom: 0, - }, - - shortTermsBorder: { - borderWidth: 1, - borderColor: theme.border, - }, - shortTermsHorizontalRule: { - borderBottomWidth: 1, - borderColor: theme.border, - ...spacing.mh3, - }, + textInputLabelDesktop: { + transformOrigin: 'left center', + }, - shortTermsLargeHorizontalRule: { - borderWidth: 1, - borderColor: theme.border, - ...spacing.mh3, - }, - - shortTermsRow: { - flexDirection: 'row', - padding: 12, - }, - - termsCenterRight: { - marginTop: 'auto', - marginBottom: 'auto', - }, - - shortTermsBoldHeadingSection: { - paddingRight: 12, - paddingLeft: 12, - marginTop: 12, - }, - - shortTermsHeadline: { - ...headlineFont, - ...whiteSpace.preWrap, - color: theme.heading, - fontSize: variables.fontSizeXXXLarge, - lineHeight: variables.lineHeightXXXLarge, - }, - - longTermsRow: { - flexDirection: 'row', - marginTop: 20, - }, - - collapsibleSectionBorder: { - borderBottomWidth: 2, - borderBottomColor: theme.border, - }, - - communicationsLinkHeight: { - height: variables.communicationsLinkHeight, - }, - - floatingMessageCounterWrapper: { - position: 'absolute', - left: '50%', - top: 0, - zIndex: 100, - ...visibility.hidden, - }, - - floatingMessageCounterWrapperAndroid: { - left: 0, - width: '100%', - alignItems: 'center', - position: 'absolute', - top: 0, - zIndex: 100, - ...visibility.hidden, - }, - - floatingMessageCounterSubWrapperAndroid: { - left: '50%', - width: 'auto', - }, - - floatingMessageCounter: { - left: '-50%', - ...visibility.visible, - }, - - floatingMessageCounterTransformation: (translateY: AnimatableNumericValue): ViewStyle => ({ - transform: [{translateY}], - }), - - confirmationAnimation: { - height: 180, - width: 180, - marginBottom: 20, - }, - - googleSearchTextInputContainer: { - flexDirection: 'column', - }, - - googleSearchSeparator: { - height: 1, - backgroundColor: theme.border, - }, - - googleSearchText: { - color: theme.text, - fontSize: variables.fontSizeNormal, - lineHeight: variables.fontSizeNormalHeight, - fontFamily: fontFamily.EXP_NEUE, - flex: 1, - }, - - threeDotsPopoverOffset: (windowWidth: number): AnchorPosition => ({ - ...getPopOverVerticalOffset(60), - horizontal: windowWidth - 60, - }), - - threeDotsPopoverOffsetNoCloseButton: (windowWidth: number): AnchorPosition => ({ - ...getPopOverVerticalOffset(60), - horizontal: windowWidth - 10, - }), - - invert: { - // It's important to invert the Y AND X axis to prevent a react native issue that can lead to ANRs on android 13 - transform: [{scaleX: -1}, {scaleY: -1}], - }, - - keyboardShortcutModalContainer: { - maxHeight: '100%', - flex: 0, - flexBasis: 'auto', - }, - - keyboardShortcutTableWrapper: { - alignItems: 'center', - flex: 1, - height: 'auto', - maxHeight: '100%', - }, - - keyboardShortcutTableContainer: { - display: 'flex', - width: '100%', - borderColor: theme.border, - height: 'auto', - borderRadius: variables.componentBorderRadius, - borderWidth: 1, - }, + textInputLabelTransformation: (translateY: AnimatableNumericValue, translateX: AnimatableNumericValue, scale: AnimatableNumericValue) => + ({ + transform: [{translateY}, {translateX}, {scale}], + } satisfies TextStyle), - keyboardShortcutTableRow: { - flex: 1, - flexDirection: 'row', - borderColor: theme.border, - flexBasis: 'auto', - alignSelf: 'stretch', - borderTopWidth: 1, - }, - - keyboardShortcutTablePrefix: { - width: '30%', - borderRightWidth: 1, - borderColor: theme.border, - }, + baseTextInput: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeNormal, + lineHeight: variables.lineHeightXLarge, + color: theme.text, + paddingTop: 23, + paddingBottom: 8, + paddingLeft: 0, + borderWidth: 0, + }, - keyboardShortcutTableFirstRow: { - borderTopWidth: 0, - }, + textInputMultiline: { + scrollPadding: '23px 0 0 0', + }, - iPhoneXSafeArea: { - backgroundColor: theme.inverse, - flex: 1, - }, + textInputMultilineContainer: { + paddingTop: 23, + }, - transferBalancePayment: { - borderWidth: 1, - borderRadius: variables.componentBorderRadiusNormal, - borderColor: theme.border, - }, - - transferBalanceSelectedPayment: { - borderColor: theme.iconSuccessFill, - }, - - transferBalanceBalance: { - fontSize: 48, - }, - - closeAccountMessageInput: { - height: 153, - }, - - imageCropContainer: { - overflow: 'hidden', - alignItems: 'center', - justifyContent: 'center', - backgroundColor: theme.imageCropBackgroundColor, - ...cursor.cursorMove, - }, - - sliderKnobTooltipView: { - height: variables.sliderKnobSize, - width: variables.sliderKnobSize, - borderRadius: variables.sliderKnobSize / 2, - }, - - sliderKnob: { - backgroundColor: theme.success, - position: 'absolute', - height: variables.sliderKnobSize, - width: variables.sliderKnobSize, - borderRadius: variables.sliderKnobSize / 2, - left: -(variables.sliderKnobSize / 2), - ...cursor.cursorPointer, - }, - - sliderBar: { - backgroundColor: theme.border, - height: variables.sliderBarHeight, - borderRadius: variables.sliderBarHeight / 2, - alignSelf: 'stretch', - justifyContent: 'center', - }, - - screenCenteredContainer: { - flex: 1, - justifyContent: 'center', - marginBottom: 40, - padding: 16, - }, - - inlineSystemMessage: { - color: theme.textSupporting, - fontSize: variables.fontSizeLabel, - fontFamily: fontFamily.EXP_NEUE, - marginLeft: 6, - }, - - fullScreen: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - }, - - invisibleOverlay: { - backgroundColor: theme.transparent, - zIndex: 1000, - }, - - reportDropOverlay: { - backgroundColor: theme.dropUIBG, - zIndex: 2, - }, - - receiptDropOverlay: { - backgroundColor: theme.receiptDropUIBG, - zIndex: 2, - }, - - receiptImageWrapper: (receiptImageTopPosition: number): ViewStyle => ({ - position: 'absolute', - top: receiptImageTopPosition, - }), - - cardSection: { - backgroundColor: theme.cardBG, - borderRadius: variables.componentBorderRadiusCard, - marginBottom: 20, - marginHorizontal: 16, - padding: 20, - width: 'auto', - textAlign: 'left', - }, - - cardSectionTitle: { - lineHeight: variables.lineHeightXXLarge, - }, - - cardMenuItem: { - paddingLeft: 8, - paddingRight: 0, - borderRadius: variables.buttonBorderRadius, - height: variables.componentSizeLarge, - alignItems: 'center', - }, - - callRequestSection: { - backgroundColor: theme.appBG, - paddingHorizontal: 0, - paddingBottom: 0, - marginHorizontal: 0, - marginBottom: 0, - }, - - archivedReportFooter: { - borderRadius: variables.componentBorderRadius, - ...wordBreak.breakWord, - }, - - saveButtonPadding: { - paddingLeft: 18, - paddingRight: 18, - }, - - deeplinkWrapperContainer: { - padding: 20, - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: theme.appBG, - }, - - deeplinkWrapperMessage: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - - deeplinkWrapperFooter: { - paddingTop: 80, - paddingBottom: 45, - }, - - emojiReactionBubble: { - borderRadius: 28, - alignItems: 'center', - justifyContent: 'center', - flexDirection: 'row', - alignSelf: 'flex-start', - }, - - emojiReactionListHeader: { - marginTop: 8, - paddingBottom: 20, - borderBottomColor: theme.border, - borderBottomWidth: 1, - marginHorizontal: 20, - }, - emojiReactionListHeaderBubble: { - paddingVertical: 2, - paddingHorizontal: 8, - borderRadius: 28, - backgroundColor: theme.border, - alignItems: 'center', - justifyContent: 'center', - flexDirection: 'row', - alignSelf: 'flex-start', - marginRight: 4, - }, - reactionListItem: { - flexDirection: 'row', - paddingVertical: 12, - paddingHorizontal: 20, - }, - reactionListHeaderText: { - color: theme.textSupporting, - marginLeft: 8, - alignSelf: 'center', - }, - - miniQuickEmojiReactionText: { - fontSize: 15, - lineHeight: 20, - textAlignVertical: 'center', - }, - - emojiReactionBubbleText: { - textAlignVertical: 'center', - }, - - reactionCounterText: { - fontSize: 13, - marginLeft: 4, - fontWeight: 'bold', - }, - - fontColorReactionLabel: { - color: theme.tooltipSupportingText, - }, - - reactionEmojiTitle: { - fontSize: variables.iconSizeLarge, - lineHeight: variables.iconSizeXLarge, - }, - - textReactionSenders: { - color: theme.tooltipPrimaryText, - ...wordBreak.breakWord, - }, - - quickReactionsContainer: { - gap: 12, - flexDirection: 'row', - paddingHorizontal: 25, - paddingVertical: 12, - justifyContent: 'space-between', - }, - - reactionListContainer: { - maxHeight: variables.listItemHeightNormal * 5.75, - ...spacing.pv2, - }, - - reactionListContainerFixedWidth: { - maxWidth: variables.popoverWidth, - }, - - validateCodeDigits: { - color: theme.text, - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeXXLarge, - letterSpacing: 4, - }, + textInputAndIconContainer: { + flex: 1, + height: '100%', + zIndex: -1, + flexDirection: 'row', + }, - footerWrapper: { - fontSize: variables.fontSizeNormal, - paddingTop: 64, - maxWidth: 1100, // Match footer across all Expensify platforms - }, - - footerColumnsContainer: { - flex: 1, - flexWrap: 'wrap', - marginBottom: 40, - marginHorizontal: -16, - }, - - footerTitle: { - fontSize: variables.fontSizeLarge, - color: theme.success, - marginBottom: 16, - }, - - footerRow: { - paddingVertical: 4, - marginBottom: 8, - color: theme.textLight, - fontSize: variables.fontSizeMedium, - }, - - footerBottomLogo: { - marginTop: 40, - width: '100%', - }, - - listPickerSeparator: { - height: 1, - backgroundColor: theme.buttonDefaultBG, - }, - - datePickerRoot: { - position: 'relative', - zIndex: 99, - }, - - datePickerPopover: { - backgroundColor: theme.appBG, - width: '100%', - alignSelf: 'center', - zIndex: 100, - marginTop: 8, - }, - - loginHeroHeader: { - fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM, - color: theme.success, - fontWeight: '500', - textAlign: 'center', - }, - - newKansasLarge: { - ...headlineFont, - fontSize: variables.fontSizeXLarge, - lineHeight: variables.lineHeightXXLarge, - }, - - loginHeroBody: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeSignInHeroBody, - color: theme.textLight, - textAlign: 'center', - }, - - linkPreviewWrapper: { - marginTop: 16, - borderLeftWidth: 4, - borderLeftColor: theme.border, - paddingLeft: 12, - }, - - linkPreviewImage: { - flex: 1, - resizeMode: 'contain', - borderRadius: 8, - marginTop: 8, - }, - - linkPreviewLogoImage: { - height: 16, - width: 16, - }, - - validateCodeMessage: { - width: variables.modalContentMaxWidth, - textAlign: 'center', - }, - - whisper: { - backgroundColor: theme.cardBG, - }, - - contextMenuItemPopoverMaxWidth: { - maxWidth: 375, - }, - - formSpaceVertical: { - height: 20, - width: 1, - }, - - taskCheckbox: { - height: 16, - width: 16, - }, - - taskTitleMenuItem: { - ...writingDirection.ltr, - ...headlineFont, - fontSize: variables.fontSizeXLarge, - maxWidth: '100%', - ...wordBreak.breakWord, - }, - - taskDescriptionMenuItem: { - maxWidth: '100%', - ...wordBreak.breakWord, - }, - - taskTitleDescription: { - fontFamily: fontFamily.EXP_NEUE, - fontSize: variables.fontSizeLabel, - color: theme.textSupporting, - lineHeight: variables.lineHeightNormal, - ...spacing.mb1, - }, - - taskMenuItemCheckbox: { - height: 27, - ...spacing.mr3, - }, - - reportHorizontalRule: { - borderBottomWidth: 1, - borderColor: theme.border, - ...spacing.mh5, - ...spacing.mv2, - }, - - assigneeTextStyle: { - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - minHeight: variables.avatarSizeSubscript, - }, - - taskRightIconContainer: { - width: variables.componentSizeNormal, - marginLeft: 'auto', - ...spacing.mt1, - ...pointerEventsAuto, - }, - - shareCodePage: { - paddingHorizontal: 38.5, - }, - - shareCodeContainer: { - width: '100%', - alignItems: 'center', - paddingHorizontal: variables.qrShareHorizontalPadding, - paddingVertical: 20, - borderRadius: 20, - overflow: 'hidden', - borderColor: theme.borderFocus, - borderWidth: 2, - backgroundColor: theme.highlightBG, - }, - - splashScreenHider: { - backgroundColor: theme.splashBG, - alignItems: 'center', - justifyContent: 'center', - }, - - headerEnvBadge: { - marginLeft: 0, - marginBottom: 2, - height: 12, - paddingLeft: 4, - paddingRight: 4, - alignItems: 'center', - }, - - headerEnvBadgeText: { - fontSize: 7, - fontWeight: fontWeightBold, - lineHeight: undefined, - }, - - expensifyQrLogo: { - alignSelf: 'stretch', - height: 27, - marginBottom: 20, - }, - - qrShareTitle: { - marginTop: 15, - textAlign: 'center', - }, - - loginButtonRow: { - justifyContent: 'center', - width: '100%', - ...flex.flexRow, - }, - - loginButtonRowSmallScreen: { - justifyContent: 'center', - width: '100%', - marginBottom: 10, - ...flex.flexRow, - }, - - appleButtonContainer: { - width: 40, - height: 40, - marginRight: 20, - }, - - signInIconButton: { - margin: 10, - marginTop: 0, - padding: 2, - }, - - googleButtonContainer: { - colorScheme: 'light', - width: 40, - height: 40, - marginLeft: 12, - alignItems: 'center', - overflow: 'hidden', - }, - - googlePillButtonContainer: { - colorScheme: 'light', - height: 40, - width: 219, - }, - - thirdPartyLoadingContainer: { - alignItems: 'center', - justifyContent: 'center', - height: 450, - }, - - tabSelectorButton: { - height: variables.tabSelectorButtonHeight, - padding: variables.tabSelectorButtonPadding, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - borderRadius: variables.buttonBorderRadius, - }, - - tabSelector: { - flexDirection: 'row', - paddingHorizontal: 20, - paddingBottom: 12, - }, - - tabText: (isSelected: boolean): TextStyle => ({ - marginLeft: 8, - fontFamily: isSelected ? fontFamily.EXP_NEUE_BOLD : fontFamily.EXP_NEUE, - fontWeight: isSelected ? fontWeightBold : '400', - color: isSelected ? theme.textLight : theme.textSupporting, - }), - - overscrollSpacer: (backgroundColor: string, height: number): ViewStyle => ({ - backgroundColor, - height, - width: '100%', - position: 'absolute', - top: -height, - left: 0, - right: 0, - }), - - dualColorOverscrollSpacer: { - position: 'absolute', - top: 0, - left: 0, - width: '100%', - height: '100%', - zIndex: -1, - }, - - willChangeTransform: { - willChange: 'transform', - }, - - dropDownButtonCartIconContainerPadding: { - paddingRight: 0, - paddingLeft: 0, - }, - - dropDownButtonArrowContain: { - marginLeft: 12, - marginRight: 14, - }, - - dropDownButtonCartIconView: { - borderTopRightRadius: variables.buttonBorderRadius, - borderBottomRightRadius: variables.buttonBorderRadius, - ...flex.flexRow, - ...flex.alignItemsCenter, - }, - - emojiPickerButtonDropdown: { - justifyContent: 'center', - backgroundColor: theme.activeComponentBG, - width: 86, - height: 52, - borderRadius: 26, - alignItems: 'center', - paddingLeft: 10, - paddingRight: 4, - marginBottom: 32, - alignSelf: 'flex-start', - }, - - emojiPickerButtonDropdownIcon: { - fontSize: 30, - }, - - moneyRequestImage: { - height: 200, - borderRadius: 16, - margin: 20, - }, - - reportPreviewBox: { - backgroundColor: theme.cardBG, - borderRadius: variables.componentBorderRadiusLarge, - maxWidth: variables.sideBarWidth, - width: '100%', - }, - - reportPreviewBoxHoverBorder: { - borderColor: theme.border, - backgroundColor: theme.border, - }, - - reportPreviewBoxBody: { - padding: 16, - }, - - reportActionItemImages: { - flexDirection: 'row', - borderWidth: 4, - borderColor: theme.transparent, - borderTopLeftRadius: variables.componentBorderRadiusLarge, - borderTopRightRadius: variables.componentBorderRadiusLarge, - borderBottomLeftRadius: variables.componentBorderRadiusLarge, - borderBottomRightRadius: variables.componentBorderRadiusLarge, - overflow: 'hidden', - height: 200, - }, - - reportActionItemImage: { - flex: 1, - width: '100%', - height: '100%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }, - - reportActionItemImageBorder: { - borderRightWidth: 2, - borderColor: theme.cardBG, - }, - - reportActionItemImagesMore: { - position: 'absolute', - borderRadius: 18, - backgroundColor: theme.cardBG, - width: 36, - height: 36, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }, - - moneyRequestHeaderStatusBarBadge: { - paddingHorizontal: 8, - borderRadius: variables.componentBorderRadiusSmall, - height: variables.inputHeightSmall, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - backgroundColor: theme.border, - marginRight: 12, - }, - - staticHeaderImage: { - minHeight: 240, - }, - - emojiPickerButtonDropdownContainer: { - flexDirection: 'row', - alignItems: 'center', - }, - - rotate90: { - transform: [{rotate: '90deg'}], - }, - - emojiStatusLHN: { - fontSize: 22, - }, - sidebarStatusAvatarContainer: { - height: 44, - width: 84, - backgroundColor: theme.componentBG, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - borderRadius: 42, - paddingHorizontal: 2, - marginVertical: -2, - marginRight: -2, - }, - sidebarStatusAvatar: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - - moneyRequestViewImage: { - ...spacing.mh5, - ...spacing.mv3, - overflow: 'hidden', - borderWidth: 2, - borderColor: theme.cardBG, - borderRadius: variables.componentBorderRadiusLarge, - height: 200, - maxWidth: 400, - }, - - distanceRequestContainer: (maxHeight: number): ViewStyle => ({ - ...flex.flexShrink2, - minHeight: variables.optionRowHeight * 2, - maxHeight, - }), - - mapViewContainer: { - ...flex.flex1, - ...spacing.p4, - minHeight: 300, - maxHeight: 500, - }, - - mapView: { - flex: 1, - borderRadius: 20, - overflow: 'hidden', - }, - - mapViewOverlay: { - flex: 1, - position: 'absolute', - left: 0, - top: 0, - borderRadius: variables.componentBorderRadiusLarge, - overflow: 'hidden', - backgroundColor: theme.highlightBG, - ...sizing.w100, - ...sizing.h100, - }, - - confirmationListMapItem: { - ...spacing.m5, - height: 200, - }, - - mapDirection: { - lineColor: theme.success, - lineWidth: 7, - }, - - mapDirectionLayer: { - layout: {'line-join': 'round', 'line-cap': 'round'}, - paint: {'line-color': theme.success, 'line-width': 7}, - }, - - mapPendingView: { - backgroundColor: theme.highlightBG, - ...flex.flex1, - borderRadius: variables.componentBorderRadiusLarge, - }, - userReportStatusEmoji: { - fontSize: variables.fontSizeNormal, - marginRight: 4, - }, - draggableTopBar: { - height: 30, - width: '100%', - }, -}); + textInputDesktop: addOutlineWidth({}, 0), + + textInputIconContainer: { + paddingHorizontal: 11, + justifyContent: 'center', + margin: 1, + }, + + secureInput: { + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + }, + + textInput: { + backgroundColor: 'transparent', + borderRadius: variables.componentBorderRadiusNormal, + height: variables.inputComponentSizeNormal, + borderColor: theme.border, + borderWidth: 1, + color: theme.text, + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeNormal, + paddingLeft: 12, + paddingRight: 12, + paddingTop: 10, + paddingBottom: 10, + textAlignVertical: 'center', + }, + + textInputPrefixWrapper: { + position: 'absolute', + left: 0, + top: 0, + height: variables.inputHeight, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + paddingTop: 23, + paddingBottom: 8, + }, + + textInputPrefix: { + color: theme.text, + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeNormal, + textAlignVertical: 'center', + }, + + pickerContainer: { + borderBottomWidth: 2, + paddingLeft: 0, + borderStyle: 'solid', + borderColor: theme.border, + justifyContent: 'center', + backgroundColor: 'transparent', + height: variables.inputHeight, + overflow: 'hidden', + }, + + pickerContainerSmall: { + height: variables.inputHeightSmall, + }, + + pickerLabel: { + position: 'absolute', + left: 0, + top: 6, + zIndex: 1, + }, + + picker: (disabled = false, backgroundColor = theme.appBG) => + ({ + iconContainer: { + top: Math.round(variables.inputHeight * 0.5) - 11, + right: 0, + ...pointerEventsNone, + }, + + inputWeb: { + appearance: 'none', + ...(disabled ? cursor.cursorDisabled : cursor.cursorPointer), + ...picker(theme), + backgroundColor, + }, + + inputIOS: { + ...picker(theme), + }, + done: { + color: theme.text, + }, + doneDepressed: { + fontSize: 17, + }, + modalViewMiddle: { + backgroundColor: theme.border, + borderTopWidth: 0, + }, + modalViewBottom: { + backgroundColor: theme.highlightBG, + }, + + inputAndroid: { + ...picker(theme), + }, + } satisfies CustomPickerStyle), + + disabledText: { + color: theme.icon, + }, + + inputDisabled: { + backgroundColor: theme.highlightBG, + color: theme.icon, + }, + + noOutline: addOutlineWidth({}, 0), + + errorOutline: { + borderColor: theme.danger, + }, + + textLabelSupporting: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeLabel, + color: theme.textSupporting, + }, + + textLabelError: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeLabel, + color: theme.textError, + }, + + textReceiptUpload: { + ...headlineFont, + fontSize: variables.fontSizeXLarge, + color: theme.textLight, + textAlign: 'center', + }, + + subTextReceiptUpload: { + fontFamily: fontFamily.EXP_NEUE, + lineHeight: variables.lineHeightLarge, + textAlign: 'center', + color: theme.textLight, + }, + + furtherDetailsText: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeSmall, + color: theme.textSupporting, + }, + + lh16: { + lineHeight: 16, + }, + + lh20: { + lineHeight: 20, + }, + + lh140Percent: { + lineHeight: '140%', + }, + + formHelp: { + color: theme.textSupporting, + fontSize: variables.fontSizeLabel, + lineHeight: variables.lineHeightLarge, + marginBottom: 4, + }, + + formError: { + color: theme.textError, + fontSize: variables.fontSizeLabel, + lineHeight: variables.formErrorLineHeight, + marginBottom: 4, + }, + + formSuccess: { + color: theme.success, + fontSize: variables.fontSizeLabel, + lineHeight: 18, + marginBottom: 4, + }, + + desktopRedirectPage: { + backgroundColor: theme.appBG, + minHeight: '100%', + flex: 1, + alignItems: 'center', + }, + + signInPage: { + backgroundColor: theme.highlightBG, + minHeight: '100%', + flex: 1, + }, + + signInPageHeroCenter: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + }, + + signInPageGradient: { + height: '100%', + width: 540, + position: 'absolute', + top: 0, + left: 0, + }, + + signInPageGradientMobile: { + height: 300, + width: 800, + position: 'absolute', + top: 0, + left: 0, + }, + + signInBackground: { + position: 'absolute', + bottom: 0, + left: 0, + minHeight: 700, + }, + + signInPageInner: { + marginLeft: 'auto', + marginRight: 'auto', + height: '100%', + width: '100%', + }, + + signInPageContentTopSpacer: { + maxHeight: 132, + minHeight: 24, + }, + + signInPageContentTopSpacerSmallScreens: { + maxHeight: 132, + minHeight: 45, + }, + + signInPageLeftContainer: { + paddingLeft: 40, + paddingRight: 40, + }, + + signInPageLeftContainerWide: { + maxWidth: variables.sideBarWidth, + }, + + signInPageWelcomeFormContainer: { + maxWidth: CONST.SIGN_IN_FORM_WIDTH, + }, + + signInPageWelcomeTextContainer: { + width: CONST.SIGN_IN_FORM_WIDTH, + }, + + changeExpensifyLoginLinkContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + ...wordBreak.breakWord, + }, + + // Sidebar Styles + sidebar: { + backgroundColor: theme.sidebar, + height: '100%', + }, + + sidebarAnimatedWrapperContainer: { + height: '100%', + position: 'absolute', + }, + + sidebarFooter: { + alignItems: 'center', + display: 'flex', + justifyContent: 'center', + paddingVertical: variables.lineHeightXLarge, + width: '100%', + }, + + sidebarAvatar: { + backgroundColor: theme.icon, + borderRadius: 20, + height: variables.componentSizeNormal, + width: variables.componentSizeNormal, + }, + + statusIndicator: (backgroundColor = theme.danger) => + ({ + borderColor: theme.sidebar, + backgroundColor, + borderRadius: 8, + borderWidth: 2, + position: 'absolute', + right: -2, + top: -1, + height: 16, + width: 16, + zIndex: 10, + } satisfies ViewStyle), + + floatingActionButtonContainer: { + position: 'absolute', + right: 20, + + // The bottom of the floating action button should align with the bottom of the compose box. + // The value should be equal to the height + marginBottom + marginTop of chatItemComposeSecondaryRow + bottom: 25, + }, + + floatingActionButton: { + backgroundColor: theme.success, + height: variables.componentSizeLarge, + width: variables.componentSizeLarge, + borderRadius: 999, + alignItems: 'center', + justifyContent: 'center', + }, + + sidebarFooterUsername: { + color: theme.heading, + fontSize: variables.fontSizeLabel, + fontWeight: '700', + width: 200, + textOverflow: 'ellipsis', + overflow: 'hidden', + ...whiteSpace.noWrap, + }, + + sidebarFooterLink: { + color: theme.textSupporting, + fontSize: variables.fontSizeSmall, + textDecorationLine: 'none', + fontFamily: fontFamily.EXP_NEUE, + lineHeight: 20, + }, + + sidebarListContainer: { + scrollbarWidth: 'none', + paddingBottom: 4, + }, + + sidebarListItem: { + justifyContent: 'center', + textDecorationLine: 'none', + }, + + RHPNavigatorContainer: (isSmallScreenWidth: boolean) => + ({ + width: isSmallScreenWidth ? '100%' : variables.sideBarWidth, + position: 'absolute', + right: 0, + height: '100%', + } satisfies ViewStyle), + + onlyEmojisText: { + fontSize: variables.fontSizeOnlyEmojis, + lineHeight: variables.fontSizeOnlyEmojisHeight, + }, + + onlyEmojisTextLineHeight: { + lineHeight: variables.fontSizeOnlyEmojisHeight, + }, + + createMenuPositionSidebar: (windowHeight: number) => + ({ + horizontal: 18, + vertical: windowHeight - 100, + } satisfies AnchorPosition), + + createMenuPositionProfile: (windowWidth: number) => + ({ + horizontal: windowWidth - 355, + ...getPopOverVerticalOffset(162), + } satisfies AnchorPosition), + + createMenuPositionReportActionCompose: (windowHeight: number) => + ({ + horizontal: 18 + variables.sideBarWidth, + vertical: windowHeight - 83, + } satisfies AnchorPosition), + + createMenuPositionRightSidepane: { + right: 18, + bottom: 75, + }, + + createMenuContainer: { + width: variables.sideBarWidth - 40, + paddingVertical: 12, + }, + + createMenuHeaderText: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeLabel, + color: theme.heading, + }, + + popoverMenuItem: { + flexDirection: 'row', + borderRadius: 0, + paddingHorizontal: 20, + paddingVertical: 12, + justifyContent: 'space-between', + width: '100%', + }, + + popoverMenuIcon: { + width: variables.componentSizeNormal, + justifyContent: 'center', + alignItems: 'center', + }, + + popoverMenuText: { + fontSize: variables.fontSizeNormal, + color: theme.heading, + }, + + popoverInnerContainer: { + paddingTop: 0, // adjusting this because the mobile modal adds additional padding that we don't need for our layout + maxHeight: '95%', + }, + + menuItemTextContainer: { + minHeight: variables.componentSizeNormal, + }, + + chatLinkRowPressable: { + minWidth: 0, + textDecorationLine: 'none', + flex: 1, + }, + + sidebarLink: { + textDecorationLine: 'none', + }, + + sidebarLinkInner: { + alignItems: 'center', + flexDirection: 'row', + paddingLeft: 20, + paddingRight: 20, + }, + + sidebarLinkText: { + color: theme.textSupporting, + fontSize: variables.fontSizeNormal, + textDecorationLine: 'none', + overflow: 'hidden', + }, + + sidebarLinkHover: { + backgroundColor: theme.sidebarHover, + }, + + sidebarLinkActive: { + backgroundColor: theme.border, + textDecorationLine: 'none', + }, + + sidebarLinkTextBold: { + fontWeight: '700', + color: theme.heading, + }, + + sidebarLinkActiveText: { + color: theme.textSupporting, + fontSize: variables.fontSizeNormal, + textDecorationLine: 'none', + overflow: 'hidden', + }, + + optionItemAvatarNameWrapper: { + minWidth: 0, + flex: 1, + }, + + optionDisplayName: { + fontFamily: fontFamily.EXP_NEUE, + minHeight: variables.alternateTextHeight, + lineHeight: variables.lineHeightXLarge, + ...whiteSpace.noWrap, + }, + + optionDisplayNameCompact: { + minWidth: 'auto', + flexBasis: 'auto', + flexGrow: 0, + flexShrink: 1, + }, + + displayNameTooltipEllipsis: { + position: 'absolute', + opacity: 0, + right: 0, + bottom: 0, + }, + + optionAlternateText: { + minHeight: variables.alternateTextHeight, + lineHeight: variables.lineHeightXLarge, + }, + + optionAlternateTextCompact: { + flexShrink: 1, + flexGrow: 1, + flexBasis: 'auto', + ...optionAlternateTextPlatformStyles, + }, + + optionRow: { + minHeight: variables.optionRowHeight, + paddingTop: 12, + paddingBottom: 12, + }, + + optionRowSelected: { + backgroundColor: theme.activeComponentBG, + }, + + optionRowDisabled: { + color: theme.textSupporting, + }, + + optionRowCompact: { + height: variables.optionRowHeightCompact, + paddingTop: 12, + paddingBottom: 12, + }, + + optionsListSectionHeader: { + height: variables.optionsListSectionHeaderHeight, + }, + + overlayStyles: (current: OverlayStylesParams) => + ({ + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + position: 'fixed' as ViewStyle['position'], + + // We need to stretch the overlay to cover the sidebar and the translate animation distance. + left: -2 * variables.sideBarWidth, + top: 0, + bottom: 0, + right: 0, + backgroundColor: theme.shadow, + opacity: current.progress.interpolate({ + inputRange: [0, 1], + outputRange: [0, variables.overlayOpacity], + extrapolate: 'clamp', + }), + } satisfies ViewStyle), + + appContent: { + backgroundColor: theme.appBG, + overflow: 'hidden', + }, + + appContentHeader: { + height: variables.contentHeaderHeight, + justifyContent: 'center', + display: 'flex', + paddingRight: 20, + }, + + appContentHeaderTitle: { + alignItems: 'center', + flexDirection: 'row', + }, + + LHNToggle: { + alignItems: 'center', + height: variables.contentHeaderHeight, + justifyContent: 'center', + paddingRight: 10, + paddingLeft: 20, + }, + + LHNToggleIcon: { + height: 15, + width: 18, + }, + + chatContent: { + flex: 4, + justifyContent: 'flex-end', + }, + + chatContentScrollView: { + flexGrow: 1, + justifyContent: 'flex-start', + paddingBottom: 16, + }, + + // Chat Item + chatItem: { + display: 'flex', + flexDirection: 'row', + paddingTop: 8, + paddingBottom: 8, + paddingLeft: 20, + paddingRight: 20, + }, + + chatItemRightGrouped: { + flexGrow: 1, + flexShrink: 1, + flexBasis: 0, + position: 'relative', + marginLeft: variables.chatInputSpacing, + }, + + chatItemRight: { + flexGrow: 1, + flexShrink: 1, + flexBasis: 0, + position: 'relative', + }, + + chatItemMessageHeader: { + alignItems: 'center', + display: 'flex', + flexDirection: 'row', + flexWrap: 'nowrap', + }, + + chatItemMessageHeaderSender: { + color: theme.heading, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontSize: variables.fontSizeNormal, + fontWeight: fontWeightBold, + lineHeight: variables.lineHeightXLarge, + ...wordBreak.breakWord, + }, + + chatItemMessageHeaderTimestamp: { + flexShrink: 0, + color: theme.textSupporting, + fontSize: variables.fontSizeSmall, + paddingTop: 2, + }, + + chatItemMessage: { + color: theme.text, + fontSize: variables.fontSizeNormal, + fontFamily: fontFamily.EXP_NEUE, + lineHeight: variables.lineHeightXLarge, + maxWidth: '100%', + ...cursor.cursorAuto, + ...whiteSpace.preWrap, + ...wordBreak.breakWord, + }, + + chatItemComposeWithFirstRow: { + minHeight: 90, + }, + + chatItemFullComposeRow: { + ...sizing.h100, + }, + + chatItemComposeBoxColor: { + borderColor: theme.border, + }, + + chatItemComposeBoxFocusedColor: { + borderColor: theme.borderFocus, + }, + + chatItemComposeBox: { + backgroundColor: theme.componentBG, + borderWidth: 1, + borderRadius: variables.componentBorderRadiusRounded, + minHeight: variables.componentSizeMedium, + }, + + chatItemFullComposeBox: { + ...flex.flex1, + ...sizing.h100, + }, + + chatFooter: { + paddingLeft: 20, + paddingRight: 20, + display: 'flex', + backgroundColor: theme.appBG, + }, + + chatFooterFullCompose: { + flex: 1, + }, + + chatItemDraft: { + display: 'flex', + flexDirection: 'row', + paddingTop: 8, + paddingBottom: 8, + paddingLeft: 20, + paddingRight: 20, + }, + + chatItemReactionsDraftRight: { + marginLeft: 52, + }, + chatFooterAtTheTop: { + flexGrow: 1, + justifyContent: 'flex-start', + }, + + // Be extremely careful when editing the compose styles, as it is easy to introduce regressions. + // Make sure you run the following tests against any changes: #12669 + textInputCompose: addOutlineWidth( + { + backgroundColor: theme.componentBG, + borderColor: theme.border, + color: theme.text, + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeNormal, + borderWidth: 0, + height: 'auto', + lineHeight: variables.lineHeightXLarge, + ...overflowXHidden, + + // On Android, multiline TextInput with height: 'auto' will show extra padding unless they are configured with + // paddingVertical: 0, alignSelf: 'center', and textAlignVertical: 'center' + + paddingHorizontal: variables.avatarChatSpacing, + paddingTop: 0, + paddingBottom: 0, + alignSelf: 'center', + textAlignVertical: 'center', + }, + 0, + ), + + textInputFullCompose: { + alignSelf: 'stretch', + flex: 1, + maxHeight: '100%', + textAlignVertical: 'top', + }, + + editInputComposeSpacing: { + backgroundColor: theme.transparent, + marginVertical: 8, + }, + + // composer padding should not be modified unless thoroughly tested against the cases in this PR: #12669 + textInputComposeSpacing: { + paddingVertical: 5, + ...flex.flexRow, + flex: 1, + }, + + textInputComposeBorder: { + borderLeftWidth: 1, + borderColor: theme.border, + }, + + chatItemSubmitButton: { + alignSelf: 'flex-end', + borderRadius: variables.componentBorderRadiusRounded, + backgroundColor: theme.transparent, + height: 40, + padding: 10, + margin: 3, + justifyContent: 'center', + }, + + emojiPickerContainer: { + backgroundColor: theme.componentBG, + }, + + emojiHeaderContainer: { + backgroundColor: theme.componentBG, + display: 'flex', + height: CONST.EMOJI_PICKER_HEADER_HEIGHT, + justifyContent: 'center', + width: '100%', + }, + + emojiSkinToneTitle: { + width: '100%', + ...spacing.pv1, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontWeight: fontWeightBold, + color: theme.heading, + fontSize: variables.fontSizeSmall, + }, + + // Emoji Picker Styles + emojiText: { + textAlign: 'center', + fontSize: variables.emojiSize, + ...spacing.pv0, + ...spacing.ph0, + lineHeight: variables.emojiLineHeight, + }, + + emojiItem: { + width: '12.5%', + textAlign: 'center', + borderRadius: 8, + paddingTop: 2, + paddingBottom: 2, + height: CONST.EMOJI_PICKER_ITEM_HEIGHT, + }, + + emojiItemHighlighted: { + transition: '0.2s ease', + backgroundColor: theme.buttonDefaultBG, + }, + + emojiItemKeyboardHighlighted: { + transition: '0.2s ease', + borderWidth: 1, + borderColor: theme.link, + borderRadius: variables.buttonBorderRadius, + }, + + categoryShortcutButton: { + flex: 1, + borderRadius: 8, + height: CONST.EMOJI_PICKER_ITEM_HEIGHT, + alignItems: 'center', + justifyContent: 'center', + }, + + chatItemEmojiButton: { + alignSelf: 'flex-end', + borderRadius: variables.buttonBorderRadius, + height: 40, + marginVertical: 3, + paddingHorizontal: 10, + justifyContent: 'center', + }, + + editChatItemEmojiWrapper: { + marginRight: 3, + alignSelf: 'flex-end', + }, + + hoveredButton: { + backgroundColor: theme.buttonHoveredBG, + }, + + composerSizeButton: { + alignSelf: 'center', + height: 32, + width: 32, + padding: 6, + margin: 3, + borderRadius: variables.componentBorderRadiusRounded, + backgroundColor: theme.transparent, + justifyContent: 'center', + }, + + chatItemAttachmentPlaceholder: { + backgroundColor: theme.sidebar, + borderColor: theme.border, + borderWidth: 1, + borderRadius: variables.componentBorderRadiusNormal, + height: 150, + textAlign: 'center', + verticalAlign: 'middle', + width: 200, + }, + + chatSwticherPillWrapper: { + marginTop: 5, + marginRight: 4, + }, + + navigationModalOverlay: { + ...userSelect.userSelectNone, + position: 'absolute', + width: '100%', + height: '100%', + transform: [ + { + translateX: -variables.sideBarWidth, + }, + ], + }, + + sidebarVisible: { + borderRightWidth: 1, + }, + + sidebarHidden: { + width: 0, + borderRightWidth: 0, + }, + + exampleCheckImage: { + width: '100%', + height: 80, + borderColor: theme.border, + borderWidth: 1, + borderRadius: variables.componentBorderRadiusNormal, + }, + + singleAvatar: { + height: 24, + width: 24, + backgroundColor: theme.icon, + borderRadius: 24, + }, + + singleSubscript: { + height: variables.iconSizeNormal, + width: variables.iconSizeNormal, + backgroundColor: theme.icon, + borderRadius: 20, + zIndex: 1, + }, + + singleAvatarSmall: { + height: 18, + width: 18, + backgroundColor: theme.icon, + borderRadius: 18, + }, + + secondAvatar: { + position: 'absolute', + right: -18, + bottom: -18, + borderWidth: 3, + borderRadius: 30, + borderColor: 'transparent', + }, + + secondAvatarSmall: { + position: 'absolute', + right: -13, + bottom: -13, + borderWidth: 3, + borderRadius: 18, + borderColor: 'transparent', + }, + + secondAvatarSubscript: { + position: 'absolute', + right: -6, + bottom: -6, + }, + + secondAvatarSubscriptCompact: { + position: 'absolute', + bottom: -1, + right: -1, + }, + + secondAvatarSubscriptSmallNormal: { + position: 'absolute', + bottom: 0, + right: 0, + }, + + leftSideLargeAvatar: { + left: 15, + }, + + rightSideLargeAvatar: { + right: 15, + zIndex: 2, + borderWidth: 4, + borderRadius: 100, + }, + + secondAvatarInline: { + bottom: -3, + right: -25, + borderWidth: 3, + borderRadius: 18, + borderColor: theme.cardBorder, + backgroundColor: theme.appBG, + }, + + avatarLarge: { + width: variables.avatarSizeLarge, + height: variables.avatarSizeLarge, + }, + + avatarNormal: { + height: variables.componentSizeNormal, + width: variables.componentSizeNormal, + borderRadius: variables.componentSizeNormal, + }, + + avatarSmall: { + height: variables.avatarSizeSmall, + width: variables.avatarSizeSmall, + borderRadius: variables.avatarSizeSmall, + }, + + avatarInnerText: { + color: theme.textLight, + fontSize: variables.fontSizeSmall, + lineHeight: undefined, + marginLeft: -3, + textAlign: 'center', + }, + + avatarInnerTextSmall: { + color: theme.textLight, + fontSize: variables.fontSizeExtraSmall, + lineHeight: undefined, + marginLeft: -2, + textAlign: 'center', + }, + + avatarSpace: { + top: 3, + left: 3, + }, + + avatar: { + backgroundColor: theme.sidebar, + borderColor: theme.sidebar, + }, + + focusedAvatar: { + backgroundColor: theme.border, + borderColor: theme.border, + }, + + emptyAvatar: { + height: variables.avatarSizeNormal, + width: variables.avatarSizeNormal, + }, + + emptyAvatarSmallNormal: { + height: variables.avatarSizeSmallNormal, + width: variables.avatarSizeSmallNormal, + }, + + emptyAvatarSmall: { + height: variables.avatarSizeSmall, + width: variables.avatarSizeSmall, + }, + + emptyAvatarSmaller: { + height: variables.avatarSizeSmaller, + width: variables.avatarSizeSmaller, + }, + + emptyAvatarMedium: { + height: variables.avatarSizeMedium, + width: variables.avatarSizeMedium, + }, + + emptyAvatarLarge: { + height: variables.avatarSizeLarge, + width: variables.avatarSizeLarge, + }, + + emptyAvatarMargin: { + marginRight: variables.avatarChatSpacing, + }, + + emptyAvatarMarginChat: { + marginRight: variables.avatarChatSpacing - 12, + }, + + emptyAvatarMarginSmall: { + marginRight: variables.avatarChatSpacing - 4, + }, + + emptyAvatarMarginSmaller: { + marginRight: variables.avatarChatSpacing - 4, + }, + + modalViewContainer: { + alignItems: 'center', + flex: 1, + }, + + borderTop: { + borderTopWidth: variables.borderTopWidth, + borderColor: theme.border, + }, + + borderTopRounded: { + borderTopWidth: 1, + borderColor: theme.border, + borderTopLeftRadius: variables.componentBorderRadiusNormal, + borderTopRightRadius: variables.componentBorderRadiusNormal, + }, + + borderBottomRounded: { + borderBottomWidth: 1, + borderColor: theme.border, + borderBottomLeftRadius: variables.componentBorderRadiusNormal, + borderBottomRightRadius: variables.componentBorderRadiusNormal, + }, + + borderBottom: { + borderBottomWidth: 1, + borderColor: theme.border, + }, + + borderNone: { + borderWidth: 0, + borderBottomWidth: 0, + }, + + borderRight: { + borderRightWidth: 1, + borderColor: theme.border, + }, + + borderLeft: { + borderLeftWidth: 1, + borderColor: theme.border, + }, + + pointerEventsNone, + + pointerEventsAuto, + + headerBar: { + overflow: 'hidden', + justifyContent: 'center', + display: 'flex', + paddingLeft: 20, + height: variables.contentHeaderHeight, + width: '100%', + }, + + imageViewContainer: { + width: '100%', + height: '100%', + alignItems: 'center', + justifyContent: 'center', + }, + + imageModalPDF: { + flex: 1, + backgroundColor: theme.modalBackground, + }, + + PDFView: { + // `display: grid` is not supported in native platforms! + // It's being used on Web/Desktop only to vertically center short PDFs, + // while preventing the overflow of the top of long PDF files. + ...display.dGrid, + backgroundColor: theme.modalBackground, + width: '100%', + height: '100%', + justifyContent: 'center', + overflow: 'hidden', + alignItems: 'center', + }, + + PDFViewList: { + overflowX: 'hidden', + // There properties disable "focus" effect on list + boxShadow: 'none', + outline: 'none', + }, + + getPDFPasswordFormStyle: (isSmallScreenWidth: boolean) => + ({ + width: isSmallScreenWidth ? '100%' : 350, + ...(isSmallScreenWidth && flex.flex1), + } satisfies ViewStyle), + + modalCenterContentContainer: { + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: theme.modalBackdrop, + }, + + centeredModalStyles: (isSmallScreenWidth: boolean, isFullScreenWhenSmall: boolean) => + ({ + borderWidth: isSmallScreenWidth && !isFullScreenWhenSmall ? 1 : 0, + marginHorizontal: isSmallScreenWidth ? 0 : 20, + } satisfies ViewStyle), + + imageModalImageCenterContainer: { + alignItems: 'center', + flex: 1, + justifyContent: 'center', + width: '100%', + }, + + defaultAttachmentView: { + backgroundColor: theme.sidebar, + borderRadius: variables.componentBorderRadiusNormal, + borderWidth: 1, + borderColor: theme.border, + flexDirection: 'row', + padding: 20, + alignItems: 'center', + }, + + notFoundSafeArea: { + flex: 1, + backgroundColor: theme.heading, + }, + + notFoundView: { + flex: 1, + alignItems: 'center', + paddingTop: 40, + paddingBottom: 40, + justifyContent: 'space-between', + }, + + notFoundLogo: { + width: 202, + height: 63, + }, + + notFoundContent: { + alignItems: 'center', + }, + + notFoundTextHeader: { + ...headlineFont, + color: theme.heading, + fontSize: variables.fontSizeXLarge, + lineHeight: variables.lineHeightXXLarge, + marginTop: 20, + marginBottom: 8, + textAlign: 'center', + }, + + notFoundTextBody: { + color: theme.componentBG, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontWeight: fontWeightBold, + fontSize: 15, + }, + + notFoundButtonText: { + color: theme.link, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontWeight: fontWeightBold, + fontSize: 15, + }, + + blockingViewContainer: { + paddingBottom: variables.contentHeaderHeight, + }, + + defaultModalContainer: { + backgroundColor: theme.componentBG, + borderColor: theme.transparent, + }, + + reportActionContextMenuMiniButton: { + ...spacing.p1, + ...spacing.mv1, + ...spacing.mh1, + ...{borderRadius: variables.buttonBorderRadius}, + }, + + reportActionSystemMessageContainer: { + marginLeft: 42, + }, + + reportDetailsTitleContainer: { + ...flex.flexColumn, + ...flex.alignItemsCenter, + paddingHorizontal: 20, + paddingBottom: 20, + }, + + reportDetailsRoomInfo: { + ...flex.flex1, + ...flex.flexColumn, + ...flex.alignItemsCenter, + }, + + reportSettingsVisibilityText: { + textTransform: 'capitalize', + }, + + settingsPageBackground: { + flexDirection: 'column', + width: '100%', + flexGrow: 1, + }, + + settingsPageBody: { + width: '100%', + justifyContent: 'space-around', + }, + + settingsPageColumn: { + width: '100%', + alignItems: 'center', + justifyContent: 'space-around', + }, + + settingsPageContainer: { + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + }, + + twoFactorAuthSection: { + backgroundColor: theme.appBG, + padding: 0, + }, + + twoFactorAuthCodesBox: ({isExtraSmallScreenWidth, isSmallScreenWidth}: TwoFactorAuthCodesBoxParams) => { + let paddingHorizontal = spacing.ph9; + + if (isSmallScreenWidth) { + paddingHorizontal = spacing.ph4; + } + + if (isExtraSmallScreenWidth) { + paddingHorizontal = spacing.ph2; + } + + return { + alignItems: 'center', + justifyContent: 'center', + backgroundColor: theme.highlightBG, + paddingVertical: 28, + borderRadius: 16, + marginTop: 32, + ...paddingHorizontal, + } satisfies ViewStyle; + }, + + twoFactorLoadingContainer: { + alignItems: 'center', + justifyContent: 'center', + height: 210, + }, + + twoFactorAuthCodesContainer: { + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'row', + flexWrap: 'wrap', + gap: 12, + }, + + twoFactorAuthCode: { + fontFamily: fontFamily.MONOSPACE, + width: 112, + textAlign: 'center', + }, + + twoFactorAuthCodesButtonsContainer: { + flexDirection: 'row', + justifyContent: 'center', + gap: 12, + marginTop: 20, + flexWrap: 'wrap', + }, + + twoFactorAuthCodesButton: { + minWidth: 112, + }, + + twoFactorAuthCopyCodeButton: { + minWidth: 110, + }, + + anonymousRoomFooter: (isSmallSizeLayout: boolean) => + ({ + flexDirection: isSmallSizeLayout ? 'column' : 'row', + ...(!isSmallSizeLayout && { + alignItems: 'center', + justifyContent: 'space-between', + }), + padding: 20, + backgroundColor: theme.sidebar, + borderRadius: variables.componentBorderRadiusLarge, + overflow: 'hidden', + } satisfies ViewStyle & TextStyle), + anonymousRoomFooterWordmarkAndLogoContainer: (isSmallSizeLayout: boolean) => + ({ + flexDirection: 'row', + alignItems: 'center', + ...(isSmallSizeLayout && { + justifyContent: 'space-between', + marginTop: 16, + }), + } satisfies ViewStyle), + anonymousRoomFooterLogo: { + width: 88, + marginLeft: 0, + height: 20, + }, + anonymousRoomFooterLogoTaglineText: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeMedium, + color: theme.textLight, + }, + signInButtonAvatar: { + width: 80, + }, + + anonymousRoomFooterSignInButton: { + width: 110, + }, + + roomHeaderAvatarSize: { + height: variables.componentSizeLarge, + width: variables.componentSizeLarge, + }, + + roomHeaderAvatar: { + backgroundColor: theme.appBG, + borderRadius: 100, + borderColor: theme.componentBG, + borderWidth: 4, + }, + + roomHeaderAvatarOverlay: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: theme.overlay, + opacity: variables.overlayOpacity, + borderRadius: 88, + }, + + rootNavigatorContainerStyles: (isSmallScreenWidth: boolean) => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1} satisfies ViewStyle), + RHPNavigatorContainerNavigatorContainerStyles: (isSmallScreenWidth: boolean) => ({marginLeft: isSmallScreenWidth ? 0 : variables.sideBarWidth, flex: 1} satisfies ViewStyle), + + avatarInnerTextChat: { + color: theme.textLight, + fontSize: variables.fontSizeXLarge, + fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM, + textAlign: 'center', + fontWeight: 'normal', + position: 'absolute', + width: 88, + left: -16, + }, + + svgAvatarBorder: { + borderRadius: 100, + overflow: 'hidden', + }, + + displayName: { + fontSize: variables.fontSizeLarge, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontWeight: fontWeightBold, + color: theme.heading, + }, + + pageWrapper: { + width: '100%', + alignItems: 'center', + padding: 20, + }, + + avatarSectionWrapper: { + width: '100%', + alignItems: 'center', + paddingHorizontal: 20, + paddingBottom: 20, + }, + + avatarSectionWrapperSkeleton: { + width: '100%', + paddingHorizontal: 20, + paddingBottom: 20, + }, + + selectCircle: { + width: variables.componentSizeSmall, + height: variables.componentSizeSmall, + borderColor: theme.border, + borderWidth: 1, + borderRadius: variables.componentSizeSmall / 2, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: theme.componentBG, + marginLeft: 8, + }, + + unreadIndicatorContainer: { + position: 'absolute', + top: -10, + left: 0, + width: '100%', + height: 20, + paddingHorizontal: 20, + flexDirection: 'row', + alignItems: 'center', + zIndex: 1, + ...cursor.cursorDefault, + }, + + unreadIndicatorLine: { + height: 1, + backgroundColor: theme.unreadIndicator, + flexGrow: 1, + marginRight: 8, + opacity: 0.5, + }, + + threadDividerLine: { + height: 1, + backgroundColor: theme.border, + flexGrow: 1, + marginHorizontal: 20, + }, + + unreadIndicatorText: { + color: theme.unreadIndicator, + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontSize: variables.fontSizeSmall, + fontWeight: fontWeightBold, + textTransform: 'capitalize', + }, + + flipUpsideDown: { + transform: [{rotate: '180deg'}], + }, + + navigationSceneContainer: { + backgroundColor: theme.appBG, + }, + + navigationScreenCardStyle: { + backgroundColor: theme.appBG, + height: '100%', + }, + + navigationSceneFullScreenWrapper: { + borderRadius: variables.componentBorderRadiusCard, + overflow: 'hidden', + height: '100%', + }, + + invisible: { + position: 'absolute', + opacity: 0, + }, + + containerWithSpaceBetween: { + justifyContent: 'space-between', + width: '100%', + flex: 1, + }, + + detailsPageSectionContainer: { + alignSelf: 'flex-start', + }, + + attachmentCarouselContainer: { + height: '100%', + width: '100%', + display: 'flex', + justifyContent: 'center', + ...cursor.cursorUnset, + }, + + attachmentArrow: { + zIndex: 23, + position: 'absolute', + }, + + attachmentRevealButtonContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + ...spacing.ph4, + }, + + arrowIcon: { + height: 40, + width: 40, + alignItems: 'center', + paddingHorizontal: 0, + paddingTop: 0, + paddingBottom: 0, + }, + + detailsPageSectionVersion: { + alignSelf: 'center', + color: theme.textSupporting, + fontSize: variables.fontSizeSmall, + height: 24, + lineHeight: 20, + }, + + switchTrack: { + width: 50, + height: 28, + justifyContent: 'center', + borderRadius: 20, + padding: 15, + backgroundColor: theme.success, + }, + + switchInactive: { + backgroundColor: theme.border, + }, + + switchThumb: { + width: 22, + height: 22, + borderRadius: 11, + position: 'absolute', + left: 4, + backgroundColor: theme.appBG, + }, + + switchThumbTransformation: (translateX: AnimatableNumericValue) => + ({ + transform: [{translateX}], + } satisfies ViewStyle), + + radioButtonContainer: { + backgroundColor: theme.componentBG, + borderRadius: 10, + height: 20, + width: 20, + borderColor: theme.icon, + borderWidth: 1, + justifyContent: 'center', + alignItems: 'center', + }, + + checkboxPressable: { + borderRadius: 6, + padding: 2, + justifyContent: 'center', + alignItems: 'center', + }, + + checkedContainer: { + backgroundColor: theme.checkBox, + }, + + magicCodeInputContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + minHeight: variables.inputHeight, + }, + + magicCodeInput: { + fontSize: variables.fontSizeXLarge, + color: theme.heading, + lineHeight: variables.inputHeight, + }, + + // Manually style transparent, in iOS Safari, an input in a container with its opacity set to + // 0 (completely transparent) cannot handle user interaction, hence the Paste option is never shown + inputTransparent: { + color: 'transparent', + // These properties are available in browser only + ...(Browser.getBrowser() + ? { + caretColor: 'transparent', + WebkitTextFillColor: 'transparent', + // After setting the input text color to transparent, it acquires the background-color. + // However, it is not possible to override the background-color directly as explained in this resource: https://developer.mozilla.org/en-US/docs/Web/CSS/:autofill + // Therefore, the transition effect needs to be delayed. + transitionDelay: '99999s', + } + : {}), + }, + + iouAmountText: { + ...headlineFont, + fontSize: variables.iouAmountTextSize, + color: theme.heading, + lineHeight: variables.inputHeight, + }, + + iouAmountTextInput: addOutlineWidth( + { + ...headlineFont, + fontSize: variables.iouAmountTextSize, + color: theme.heading, + padding: 0, + lineHeight: undefined, + }, + 0, + ), + + moneyRequestConfirmationAmount: { + ...headlineFont, + fontSize: variables.fontSizeh1, + }, + + moneyRequestMenuItem: { + flexDirection: 'row', + borderRadius: 0, + justifyContent: 'space-between', + width: '100%', + paddingHorizontal: 20, + paddingVertical: 12, + }, + + requestPreviewBox: { + marginTop: 12, + maxWidth: variables.sideBarWidth, + }, + + moneyRequestPreviewBox: { + backgroundColor: theme.cardBG, + borderRadius: variables.componentBorderRadiusLarge, + maxWidth: variables.sideBarWidth, + width: '100%', + }, + + moneyRequestPreviewBoxText: { + padding: 16, + }, + + moneyRequestPreviewBoxLoading: { + // When a new IOU request arrives it is very briefly in a loading state, so set the minimum height of the container to 94 to match the rendered height after loading. + // Otherwise, the IOU request pay button will not be fully visible and the user will have to scroll up to reveal the entire IOU request container. + // See https://github.com/Expensify/App/issues/10283. + minHeight: 94, + width: '100%', + }, + + moneyRequestPreviewBoxAvatar: { + marginRight: -10, + marginBottom: 0, + }, + + moneyRequestPreviewAmount: { + ...headlineFont, + ...whiteSpace.preWrap, + color: theme.heading, + }, + + defaultCheckmarkWrapper: { + marginLeft: 8, + alignSelf: 'center', + }, + + iouDetailsContainer: { + flexGrow: 1, + paddingStart: 20, + paddingEnd: 20, + }, + + codeWordWrapper: { + ...codeStyles.codeWordWrapper, + }, + + codeWordStyle: { + borderLeftWidth: 0, + borderRightWidth: 0, + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + paddingLeft: 0, + paddingRight: 0, + justifyContent: 'center', + ...codeStyles.codeWordStyle, + }, + + codeFirstWordStyle: { + borderLeftWidth: 1, + borderTopLeftRadius: 4, + borderBottomLeftRadius: 4, + paddingLeft: 5, + }, + + codeLastWordStyle: { + borderRightWidth: 1, + borderTopRightRadius: 4, + borderBottomRightRadius: 4, + paddingRight: 5, + }, + + fullScreenLoading: { + backgroundColor: theme.componentBG, + opacity: 0.8, + justifyContent: 'center', + alignItems: 'center', + zIndex: 10, + }, + + navigatorFullScreenLoading: { + backgroundColor: theme.highlightBG, + opacity: 1, + }, + + reimbursementAccountFullScreenLoading: { + backgroundColor: theme.componentBG, + opacity: 0.8, + justifyContent: 'flex-start', + alignItems: 'center', + zIndex: 10, + }, + + hiddenElementOutsideOfWindow: { + position: 'absolute', + top: -10000, + left: 0, + opacity: 0, + }, + + growlNotificationWrapper: { + zIndex: 2, + }, + + growlNotificationContainer: { + flex: 1, + justifyContent: 'flex-start', + position: 'absolute', + width: '100%', + top: 20, + ...spacing.pl5, + ...spacing.pr5, + }, + + growlNotificationDesktopContainer: { + maxWidth: variables.sideBarWidth, + right: 0, + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + position: 'fixed' as ViewStyle['position'], + }, + + growlNotificationTranslateY: (translateY: AnimatableNumericValue) => + ({ + transform: [{translateY}], + } satisfies ViewStyle), + + makeSlideInTranslation: (translationType: Translation, fromValue: number) => + ({ + from: { + [translationType]: fromValue, + }, + to: { + [translationType]: 0, + }, + } satisfies CustomAnimation), + + growlNotificationBox: { + backgroundColor: theme.inverse, + borderRadius: variables.componentBorderRadiusNormal, + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + shadowColor: theme.shadow, + ...spacing.p5, + }, + + growlNotificationText: { + fontSize: variables.fontSizeNormal, + fontFamily: fontFamily.EXP_NEUE, + width: '90%', + lineHeight: variables.fontSizeNormalHeight, + color: theme.textReversed, + ...spacing.ml4, + }, + + blockquote: { + borderLeftColor: theme.border, + borderLeftWidth: 4, + paddingLeft: 12, + marginVertical: 4, + }, + + noSelect: { + boxShadow: 'none', + outline: 'none', + }, + + cardStyleNavigator: { + overflow: 'hidden', + height: '100%', + }, + + fullscreenCard: { + position: 'absolute', + left: 0, + top: 0, + width: '100%', + height: '100%', + }, + + fullscreenCardWeb: { + left: 'auto', + right: '-24%', + top: '-18%', + height: '120%', + }, + + fullscreenCardWebCentered: { + left: 0, + right: 0, + top: 0, + height: '60%', + }, + + fullscreenCardMobile: { + left: '-20%', + top: '-30%', + width: '150%', + }, + + fullscreenCardMediumScreen: { + left: '-15%', + top: '-30%', + width: '145%', + }, + + smallEditIcon: { + alignItems: 'center', + backgroundColor: theme.buttonHoveredBG, + borderColor: theme.textReversed, + borderRadius: 14, + borderWidth: 3, + color: theme.textReversed, + height: 28, + width: 28, + justifyContent: 'center', + }, + + smallAvatarEditIcon: { + position: 'absolute', + right: -4, + bottom: -4, + }, + + workspaceCard: { + width: '100%', + height: 400, + borderRadius: variables.componentBorderRadiusCard, + overflow: 'hidden', + backgroundColor: theme.heroCard, + }, + + workspaceCardMobile: { + height: 475, + }, + + workspaceCardMediumScreen: { + height: 540, + }, + + workspaceCardMainText: { + fontSize: variables.fontSizeXXXLarge, + fontWeight: 'bold', + lineHeight: variables.fontSizeXXXLarge, + }, + + workspaceCardContent: { + zIndex: 1, + padding: 50, + }, + + workspaceCardContentMediumScreen: { + padding: 25, + }, + + workspaceCardCTA: { + width: 250, + }, + + autoGrowHeightMultilineInput: { + maxHeight: 115, + }, + + peopleRow: { + width: '100%', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + ...spacing.ph5, + }, + + peopleRowBorderBottom: { + borderColor: theme.border, + borderBottomWidth: 1, + ...spacing.pb2, + }, + + peopleBadge: { + backgroundColor: theme.icon, + ...spacing.ph3, + }, + + peopleBadgeText: { + color: theme.textReversed, + fontSize: variables.fontSizeSmall, + lineHeight: variables.lineHeightNormal, + ...whiteSpace.noWrap, + }, + + offlineFeedback: { + deleted: { + textDecorationLine: 'line-through', + textDecorationStyle: 'solid', + }, + pending: { + opacity: 0.5, + }, + error: { + flexDirection: 'row', + alignItems: 'center', + }, + container: { + ...spacing.pv2, + }, + textContainer: { + flexDirection: 'column', + flex: 1, + }, + text: { + color: theme.textSupporting, + textAlignVertical: 'center', + fontSize: variables.fontSizeLabel, + }, + errorDot: { + marginRight: 12, + }, + }, + + dotIndicatorMessage: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + }, + + sidebarPopover: { + width: variables.sideBarWidth - 68, + }, + + cardOverlay: { + backgroundColor: theme.overlay, + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + opacity: variables.overlayOpacity, + }, + + communicationsLinkIcon: { + right: -36, + top: 0, + bottom: 0, + }, + + shortTermsBorder: { + borderWidth: 1, + borderColor: theme.border, + }, + + shortTermsHorizontalRule: { + borderBottomWidth: 1, + borderColor: theme.border, + ...spacing.mh3, + }, + + shortTermsLargeHorizontalRule: { + borderWidth: 1, + borderColor: theme.border, + ...spacing.mh3, + }, + + shortTermsRow: { + flexDirection: 'row', + padding: 12, + }, + + termsCenterRight: { + marginTop: 'auto', + marginBottom: 'auto', + }, + + shortTermsBoldHeadingSection: { + paddingRight: 12, + paddingLeft: 12, + marginTop: 12, + }, + + shortTermsHeadline: { + ...headlineFont, + ...whiteSpace.preWrap, + color: theme.heading, + fontSize: variables.fontSizeXXXLarge, + lineHeight: variables.lineHeightXXXLarge, + }, + + longTermsRow: { + flexDirection: 'row', + marginTop: 20, + }, + + collapsibleSectionBorder: { + borderBottomWidth: 2, + borderBottomColor: theme.border, + }, + + communicationsLinkHeight: { + height: variables.communicationsLinkHeight, + }, + + floatingMessageCounterWrapper: { + position: 'absolute', + left: '50%', + top: 0, + zIndex: 100, + ...visibility.hidden, + }, + + floatingMessageCounterWrapperAndroid: { + left: 0, + width: '100%', + alignItems: 'center', + position: 'absolute', + top: 0, + zIndex: 100, + ...visibility.hidden, + }, + + floatingMessageCounterSubWrapperAndroid: { + left: '50%', + width: 'auto', + }, + + floatingMessageCounter: { + left: '-50%', + ...visibility.visible, + }, + + floatingMessageCounterTransformation: (translateY: AnimatableNumericValue) => + ({ + transform: [{translateY}], + } satisfies ViewStyle), + + confirmationAnimation: { + height: 180, + width: 180, + marginBottom: 20, + }, + + googleSearchTextInputContainer: { + flexDirection: 'column', + }, + + googleSearchSeparator: { + height: 1, + backgroundColor: theme.border, + }, + + googleSearchText: { + color: theme.text, + fontSize: variables.fontSizeNormal, + lineHeight: variables.fontSizeNormalHeight, + fontFamily: fontFamily.EXP_NEUE, + flex: 1, + }, + + threeDotsPopoverOffset: (windowWidth: number) => + ({ + ...getPopOverVerticalOffset(60), + horizontal: windowWidth - 60, + } satisfies AnchorPosition), + + threeDotsPopoverOffsetNoCloseButton: (windowWidth: number) => + ({ + ...getPopOverVerticalOffset(60), + horizontal: windowWidth - 10, + } satisfies AnchorPosition), + + invert: { + // It's important to invert the Y AND X axis to prevent a react native issue that can lead to ANRs on android 13 + transform: [{scaleX: -1}, {scaleY: -1}], + }, + + keyboardShortcutModalContainer: { + maxHeight: '100%', + flex: 0, + flexBasis: 'auto', + }, + + keyboardShortcutTableWrapper: { + alignItems: 'center', + flex: 1, + height: 'auto', + maxHeight: '100%', + }, + + keyboardShortcutTableContainer: { + display: 'flex', + width: '100%', + borderColor: theme.border, + height: 'auto', + borderRadius: variables.componentBorderRadius, + borderWidth: 1, + }, + + keyboardShortcutTableRow: { + flex: 1, + flexDirection: 'row', + borderColor: theme.border, + flexBasis: 'auto', + alignSelf: 'stretch', + borderTopWidth: 1, + }, + + keyboardShortcutTablePrefix: { + width: '30%', + borderRightWidth: 1, + borderColor: theme.border, + }, + + keyboardShortcutTableFirstRow: { + borderTopWidth: 0, + }, + + iPhoneXSafeArea: { + backgroundColor: theme.inverse, + flex: 1, + }, + + transferBalancePayment: { + borderWidth: 1, + borderRadius: variables.componentBorderRadiusNormal, + borderColor: theme.border, + }, + + transferBalanceSelectedPayment: { + borderColor: theme.iconSuccessFill, + }, + + transferBalanceBalance: { + fontSize: 48, + }, + + closeAccountMessageInput: { + height: 153, + }, + + imageCropContainer: { + overflow: 'hidden', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: theme.imageCropBackgroundColor, + ...cursor.cursorMove, + }, + + sliderKnobTooltipView: { + height: variables.sliderKnobSize, + width: variables.sliderKnobSize, + borderRadius: variables.sliderKnobSize / 2, + }, + + sliderKnob: { + backgroundColor: theme.success, + position: 'absolute', + height: variables.sliderKnobSize, + width: variables.sliderKnobSize, + borderRadius: variables.sliderKnobSize / 2, + left: -(variables.sliderKnobSize / 2), + ...cursor.cursorPointer, + }, + + sliderBar: { + backgroundColor: theme.border, + height: variables.sliderBarHeight, + borderRadius: variables.sliderBarHeight / 2, + alignSelf: 'stretch', + justifyContent: 'center', + }, + + screenCenteredContainer: { + flex: 1, + justifyContent: 'center', + marginBottom: 40, + padding: 16, + }, + + inlineSystemMessage: { + color: theme.textSupporting, + fontSize: variables.fontSizeLabel, + fontFamily: fontFamily.EXP_NEUE, + marginLeft: 6, + }, + + fullScreen: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + }, + + invisibleOverlay: { + backgroundColor: theme.transparent, + zIndex: 1000, + }, + + reportDropOverlay: { + backgroundColor: theme.dropUIBG, + zIndex: 2, + }, + + receiptDropOverlay: { + backgroundColor: theme.receiptDropUIBG, + zIndex: 2, + }, + + receiptImageWrapper: (receiptImageTopPosition: number) => + ({ + position: 'absolute', + top: receiptImageTopPosition, + } satisfies ViewStyle), + + cardSection: { + backgroundColor: theme.cardBG, + borderRadius: variables.componentBorderRadiusCard, + marginBottom: 20, + marginHorizontal: 16, + padding: 20, + width: 'auto', + textAlign: 'left', + }, + + cardSectionTitle: { + lineHeight: variables.lineHeightXXLarge, + }, + + cardMenuItem: { + paddingLeft: 8, + paddingRight: 0, + borderRadius: variables.buttonBorderRadius, + height: variables.componentSizeLarge, + alignItems: 'center', + }, + + callRequestSection: { + backgroundColor: theme.appBG, + paddingHorizontal: 0, + paddingBottom: 0, + marginHorizontal: 0, + marginBottom: 0, + }, + + archivedReportFooter: { + borderRadius: variables.componentBorderRadius, + ...wordBreak.breakWord, + }, + + saveButtonPadding: { + paddingLeft: 18, + paddingRight: 18, + }, + + deeplinkWrapperContainer: { + padding: 20, + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: theme.appBG, + }, + + deeplinkWrapperMessage: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + + deeplinkWrapperFooter: { + paddingTop: 80, + paddingBottom: 45, + }, + + emojiReactionBubble: { + borderRadius: 28, + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'row', + alignSelf: 'flex-start', + }, + + emojiReactionListHeader: { + marginTop: 8, + paddingBottom: 20, + borderBottomColor: theme.border, + borderBottomWidth: 1, + marginHorizontal: 20, + }, + emojiReactionListHeaderBubble: { + paddingVertical: 2, + paddingHorizontal: 8, + borderRadius: 28, + backgroundColor: theme.border, + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'row', + alignSelf: 'flex-start', + marginRight: 4, + }, + reactionListItem: { + flexDirection: 'row', + paddingVertical: 12, + paddingHorizontal: 20, + }, + reactionListHeaderText: { + color: theme.textSupporting, + marginLeft: 8, + alignSelf: 'center', + }, + + miniQuickEmojiReactionText: { + fontSize: 15, + lineHeight: 20, + textAlignVertical: 'center', + }, + + emojiReactionBubbleText: { + textAlignVertical: 'center', + }, + + reactionCounterText: { + fontSize: 13, + marginLeft: 4, + fontWeight: 'bold', + }, + + fontColorReactionLabel: { + color: theme.tooltipSupportingText, + }, + + reactionEmojiTitle: { + fontSize: variables.iconSizeLarge, + lineHeight: variables.iconSizeXLarge, + }, + + textReactionSenders: { + color: theme.tooltipPrimaryText, + ...wordBreak.breakWord, + }, + + quickReactionsContainer: { + gap: 12, + flexDirection: 'row', + paddingHorizontal: 25, + paddingVertical: 12, + justifyContent: 'space-between', + }, + + reactionListContainer: { + maxHeight: variables.listItemHeightNormal * 5.75, + ...spacing.pv2, + }, + + reactionListContainerFixedWidth: { + maxWidth: variables.popoverWidth, + }, + + validateCodeDigits: { + color: theme.text, + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeXXLarge, + letterSpacing: 4, + }, + + footerWrapper: { + fontSize: variables.fontSizeNormal, + paddingTop: 64, + maxWidth: 1100, // Match footer across all Expensify platforms + }, + + footerColumnsContainer: { + flex: 1, + flexWrap: 'wrap', + marginBottom: 40, + marginHorizontal: -16, + }, + + footerTitle: { + fontSize: variables.fontSizeLarge, + color: theme.success, + marginBottom: 16, + }, + + footerRow: { + paddingVertical: 4, + marginBottom: 8, + color: theme.textLight, + fontSize: variables.fontSizeMedium, + }, + + footerBottomLogo: { + marginTop: 40, + width: '100%', + }, + + listPickerSeparator: { + height: 1, + backgroundColor: theme.buttonDefaultBG, + }, + + datePickerRoot: { + position: 'relative', + zIndex: 99, + }, + + datePickerPopover: { + backgroundColor: theme.appBG, + width: '100%', + alignSelf: 'center', + zIndex: 100, + marginTop: 8, + }, + + loginHeroHeader: { + fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM, + color: theme.success, + fontWeight: '500', + textAlign: 'center', + }, + + newKansasLarge: { + ...headlineFont, + fontSize: variables.fontSizeXLarge, + lineHeight: variables.lineHeightXXLarge, + }, + + loginHeroBody: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeSignInHeroBody, + color: theme.textLight, + textAlign: 'center', + }, + + linkPreviewWrapper: { + marginTop: 16, + borderLeftWidth: 4, + borderLeftColor: theme.border, + paddingLeft: 12, + }, + + linkPreviewImage: { + flex: 1, + resizeMode: 'contain', + borderRadius: 8, + marginTop: 8, + }, + + linkPreviewLogoImage: { + height: 16, + width: 16, + }, + + validateCodeMessage: { + width: variables.modalContentMaxWidth, + textAlign: 'center', + }, + + whisper: { + backgroundColor: theme.cardBG, + }, + + contextMenuItemPopoverMaxWidth: { + maxWidth: 375, + }, + + formSpaceVertical: { + height: 20, + width: 1, + }, + + taskCheckbox: { + height: 16, + width: 16, + }, + + taskTitleMenuItem: { + ...writingDirection.ltr, + ...headlineFont, + fontSize: variables.fontSizeXLarge, + maxWidth: '100%', + ...wordBreak.breakWord, + }, + + taskDescriptionMenuItem: { + maxWidth: '100%', + ...wordBreak.breakWord, + }, + + taskTitleDescription: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeLabel, + color: theme.textSupporting, + lineHeight: variables.lineHeightNormal, + ...spacing.mb1, + }, + + taskMenuItemCheckbox: { + height: 27, + ...spacing.mr3, + }, + + reportHorizontalRule: { + borderBottomWidth: 1, + borderColor: theme.border, + ...spacing.mh5, + ...spacing.mv2, + }, + + assigneeTextStyle: { + fontFamily: fontFamily.EXP_NEUE_BOLD, + fontWeight: fontWeightBold, + minHeight: variables.avatarSizeSubscript, + }, + + taskRightIconContainer: { + width: variables.componentSizeNormal, + marginLeft: 'auto', + ...spacing.mt1, + ...pointerEventsAuto, + }, + + shareCodePage: { + paddingHorizontal: 38.5, + }, + + shareCodeContainer: { + width: '100%', + alignItems: 'center', + paddingHorizontal: variables.qrShareHorizontalPadding, + paddingVertical: 20, + borderRadius: 20, + overflow: 'hidden', + borderColor: theme.borderFocus, + borderWidth: 2, + backgroundColor: theme.highlightBG, + }, + + splashScreenHider: { + backgroundColor: theme.splashBG, + alignItems: 'center', + justifyContent: 'center', + }, + + headerEnvBadge: { + marginLeft: 0, + marginBottom: 2, + height: 12, + paddingLeft: 4, + paddingRight: 4, + alignItems: 'center', + }, + + headerEnvBadgeText: { + fontSize: 7, + fontWeight: fontWeightBold, + lineHeight: undefined, + }, + + expensifyQrLogo: { + alignSelf: 'stretch', + height: 27, + marginBottom: 20, + }, + + qrShareTitle: { + marginTop: 15, + textAlign: 'center', + }, + + loginButtonRow: { + justifyContent: 'center', + width: '100%', + ...flex.flexRow, + }, + + loginButtonRowSmallScreen: { + justifyContent: 'center', + width: '100%', + marginBottom: 10, + ...flex.flexRow, + }, + + appleButtonContainer: { + width: 40, + height: 40, + marginRight: 20, + }, + + signInIconButton: { + margin: 10, + marginTop: 0, + padding: 2, + }, + + googleButtonContainer: { + colorScheme: 'light', + width: 40, + height: 40, + marginLeft: 12, + alignItems: 'center', + overflow: 'hidden', + }, + + googlePillButtonContainer: { + colorScheme: 'light', + height: 40, + width: 219, + }, + + thirdPartyLoadingContainer: { + alignItems: 'center', + justifyContent: 'center', + height: 450, + }, + + tabSelectorButton: { + height: variables.tabSelectorButtonHeight, + padding: variables.tabSelectorButtonPadding, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + borderRadius: variables.buttonBorderRadius, + }, + + tabSelector: { + flexDirection: 'row', + paddingHorizontal: 20, + paddingBottom: 12, + }, + + tabText: (isSelected: boolean) => + ({ + marginLeft: 8, + fontFamily: isSelected ? fontFamily.EXP_NEUE_BOLD : fontFamily.EXP_NEUE, + fontWeight: isSelected ? fontWeightBold : '400', + color: isSelected ? theme.textLight : theme.textSupporting, + } satisfies TextStyle), + + overscrollSpacer: (backgroundColor: string, height: number) => + ({ + backgroundColor, + height, + width: '100%', + position: 'absolute', + top: -height, + left: 0, + right: 0, + } satisfies ViewStyle), + + dualColorOverscrollSpacer: { + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + zIndex: -1, + }, + + willChangeTransform: { + willChange: 'transform', + }, + + dropDownButtonCartIconContainerPadding: { + paddingRight: 0, + paddingLeft: 0, + }, + + dropDownButtonArrowContain: { + marginLeft: 12, + marginRight: 14, + }, + + dropDownButtonCartIconView: { + borderTopRightRadius: variables.buttonBorderRadius, + borderBottomRightRadius: variables.buttonBorderRadius, + ...flex.flexRow, + ...flex.alignItemsCenter, + }, + + emojiPickerButtonDropdown: { + justifyContent: 'center', + backgroundColor: theme.activeComponentBG, + width: 86, + height: 52, + borderRadius: 26, + alignItems: 'center', + paddingLeft: 10, + paddingRight: 4, + marginBottom: 32, + alignSelf: 'flex-start', + }, + + emojiPickerButtonDropdownIcon: { + fontSize: 30, + }, + + moneyRequestImage: { + height: 200, + borderRadius: 16, + margin: 20, + }, + + reportPreviewBox: { + backgroundColor: theme.cardBG, + borderRadius: variables.componentBorderRadiusLarge, + maxWidth: variables.sideBarWidth, + width: '100%', + }, + + reportPreviewBoxHoverBorder: { + borderColor: theme.border, + backgroundColor: theme.border, + }, + + reportPreviewBoxBody: { + padding: 16, + }, + + reportActionItemImages: { + flexDirection: 'row', + borderWidth: 4, + borderColor: theme.transparent, + borderTopLeftRadius: variables.componentBorderRadiusLarge, + borderTopRightRadius: variables.componentBorderRadiusLarge, + borderBottomLeftRadius: variables.componentBorderRadiusLarge, + borderBottomRightRadius: variables.componentBorderRadiusLarge, + overflow: 'hidden', + height: 200, + }, + + reportActionItemImage: { + flex: 1, + width: '100%', + height: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + + reportActionItemImageBorder: { + borderRightWidth: 2, + borderColor: theme.cardBG, + }, + + reportActionItemImagesMore: { + position: 'absolute', + borderRadius: 18, + backgroundColor: theme.cardBG, + width: 36, + height: 36, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + + moneyRequestHeaderStatusBarBadge: { + paddingHorizontal: 8, + borderRadius: variables.componentBorderRadiusSmall, + height: variables.inputHeightSmall, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: theme.border, + marginRight: 12, + }, + + staticHeaderImage: { + minHeight: 240, + }, + + emojiPickerButtonDropdownContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + + rotate90: { + transform: [{rotate: '90deg'}], + }, + + emojiStatusLHN: { + fontSize: 22, + }, + sidebarStatusAvatarContainer: { + height: 44, + width: 84, + backgroundColor: theme.componentBG, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + borderRadius: 42, + paddingHorizontal: 2, + marginVertical: -2, + marginRight: -2, + }, + sidebarStatusAvatar: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + + moneyRequestViewImage: { + ...spacing.mh5, + ...spacing.mv3, + overflow: 'hidden', + borderWidth: 2, + borderColor: theme.cardBG, + borderRadius: variables.componentBorderRadiusLarge, + height: 200, + maxWidth: 400, + }, + + distanceRequestContainer: (maxHeight: number) => + ({ + ...flex.flexShrink2, + minHeight: variables.optionRowHeight * 2, + maxHeight, + } satisfies ViewStyle), + + mapViewContainer: { + ...flex.flex1, + ...spacing.p4, + minHeight: 300, + maxHeight: 500, + }, + + mapView: { + flex: 1, + borderRadius: 20, + overflow: 'hidden', + }, + + mapViewOverlay: { + flex: 1, + position: 'absolute', + left: 0, + top: 0, + borderRadius: variables.componentBorderRadiusLarge, + overflow: 'hidden', + backgroundColor: theme.highlightBG, + ...sizing.w100, + ...sizing.h100, + }, + + confirmationListMapItem: { + ...spacing.m5, + height: 200, + }, + + mapDirection: { + lineColor: theme.success, + lineWidth: 7, + }, + + mapDirectionLayer: { + layout: {'line-join': 'round', 'line-cap': 'round'}, + paint: {'line-color': theme.success, 'line-width': 7}, + }, + + mapPendingView: { + backgroundColor: theme.highlightBG, + ...flex.flex1, + borderRadius: variables.componentBorderRadiusLarge, + }, + userReportStatusEmoji: { + fontSize: variables.fontSizeNormal, + marginRight: 4, + }, + draggableTopBar: { + height: 30, + width: '100%', + }, + } satisfies Styles); // For now we need to export the styles function that takes the theme as an argument // as something named different than "styles", because a lot of files import the "defaultStyles" diff --git a/src/styles/utilities/display.ts b/src/styles/utilities/display.ts index e236ad4ea14d..88b0e0cd5689 100644 --- a/src/styles/utilities/display.ts +++ b/src/styles/utilities/display.ts @@ -33,4 +33,9 @@ export default { // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". display: 'block' as ViewStyle['display'], }, + + dGrid: { + // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". + display: 'grid' as ViewStyle['display'], + }, } satisfies Record; From 5feb1fed6262fdc6f925e63f601acb8de0d6a186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Sat, 16 Sep 2023 00:03:21 +0100 Subject: [PATCH 017/284] Isolate some web-only styles --- src/styles/StyleUtils.ts | 19 ++++++++++++------- src/styles/cardStyles/index.ts | 5 ++--- .../index.desktop.ts | 5 ++--- .../index.website.ts | 5 ++--- src/styles/getTooltipStyles.ts | 7 +++---- src/styles/styles.ts | 6 ++---- src/styles/utilities/display.ts | 12 ++++++++++++ src/styles/utilities/overflowAuto/index.ts | 3 +++ src/styles/utilities/positioning.ts | 8 ++++++++ 9 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 550ae71365af..9736d79a4a4e 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -40,7 +40,7 @@ type ParsableStyle = ViewStyle | ((state: PressableStateCallbackType) => ViewSty type WorkspaceColorStyle = {backgroundColor: ColorValue; fill: ColorValue}; -type ModalPaddingStylesArgs = { +type ModalPaddingStylesParams = { shouldAddBottomSafeAreaMargin: boolean; shouldAddTopSafeAreaMargin: boolean; shouldAddBottomSafeAreaPadding: boolean; @@ -56,13 +56,19 @@ type ModalPaddingStylesArgs = { insets: EdgeInsets; }; -type AvatarBorderStyleArgs = { +type AvatarBorderStyleParams = { isHovered: boolean; isPressed: boolean; isInReportAction: boolean; shouldUseCardBackground: boolean; }; +type GetBaseAutoCompleteSuggestionContainerStyleParams = { + left: number; + bottom: number; + width: number; +}; + const workspaceColorOptions: WorkspaceColorStyle[] = [ {backgroundColor: colors.blue200, fill: colors.blue700}, {backgroundColor: colors.blue400, fill: colors.blue800}, @@ -531,7 +537,7 @@ function getModalPaddingStyles({ modalContainerStylePaddingTop, modalContainerStylePaddingBottom, insets, -}: ModalPaddingStylesArgs): ViewStyle { +}: ModalPaddingStylesParams): ViewStyle { // use fallback value for safeAreaPaddingBottom to keep padding bottom consistent with padding top. // More info: issue #17376 const safeAreaPaddingBottomWithFallback = insets.bottom === 0 ? modalContainerStylePaddingTop ?? 0 : safeAreaPaddingBottom; @@ -785,7 +791,7 @@ function getKeyboardShortcutsModalWidth(isSmallScreenWidth: boolean): ViewStyle return {maxWidth: 600}; } -function getHorizontalStackedAvatarBorderStyle({isHovered, isPressed, isInReportAction = false, shouldUseCardBackground = false}: AvatarBorderStyleArgs): ViewStyle { +function getHorizontalStackedAvatarBorderStyle({isHovered, isPressed, isInReportAction = false, shouldUseCardBackground = false}: AvatarBorderStyleParams): ViewStyle { let borderColor = shouldUseCardBackground ? themeColors.cardBG : themeColors.appBG; if (isHovered) { @@ -928,10 +934,9 @@ function getAutoCompleteSuggestionItemStyle(highlightedEmojiIndex: number, rowHe /** * Gets the correct position for the base auto complete suggestion container */ -function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: {left: number; bottom: number; width: number}): ViewStyle { +function getBaseAutoCompleteSuggestionContainerStyle({left, bottom, width}: GetBaseAutoCompleteSuggestionContainerStyleParams): ViewStyle { return { - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". - position: 'fixed' as ViewStyle['position'], + ...positioning.pFixed, bottom, left, width, diff --git a/src/styles/cardStyles/index.ts b/src/styles/cardStyles/index.ts index b2ba8d7c24f1..1f28ca4f4b78 100644 --- a/src/styles/cardStyles/index.ts +++ b/src/styles/cardStyles/index.ts @@ -1,12 +1,11 @@ -import {ViewStyle} from 'react-native'; +import positioning from '../utilities/positioning'; import GetCardStyles from './types'; /** * Get card style for cardStyleInterpolator */ const getCardStyles: GetCardStyles = (screenWidth) => ({ - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". - position: 'fixed' as ViewStyle['position'], + ...positioning.pFixed, width: screenWidth, height: '100%', }); diff --git a/src/styles/getNavigationModalCardStyles/index.desktop.ts b/src/styles/getNavigationModalCardStyles/index.desktop.ts index 422a17d0a9a8..75b5636f9bd8 100644 --- a/src/styles/getNavigationModalCardStyles/index.desktop.ts +++ b/src/styles/getNavigationModalCardStyles/index.desktop.ts @@ -1,4 +1,4 @@ -import {ViewStyle} from 'react-native'; +import positioning from '../utilities/positioning'; import GetNavigationModalCardStyles from './types'; const getNavigationModalCardStyles: GetNavigationModalCardStyles = () => ({ @@ -10,8 +10,7 @@ const getNavigationModalCardStyles: GetNavigationModalCardStyles = () => ({ width: '100%', height: '100%', - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". - position: 'fixed' as ViewStyle['position'], + ...positioning.pFixed, }); export default getNavigationModalCardStyles; diff --git a/src/styles/getNavigationModalCardStyles/index.website.ts b/src/styles/getNavigationModalCardStyles/index.website.ts index 422a17d0a9a8..75b5636f9bd8 100644 --- a/src/styles/getNavigationModalCardStyles/index.website.ts +++ b/src/styles/getNavigationModalCardStyles/index.website.ts @@ -1,4 +1,4 @@ -import {ViewStyle} from 'react-native'; +import positioning from '../utilities/positioning'; import GetNavigationModalCardStyles from './types'; const getNavigationModalCardStyles: GetNavigationModalCardStyles = () => ({ @@ -10,8 +10,7 @@ const getNavigationModalCardStyles: GetNavigationModalCardStyles = () => ({ width: '100%', height: '100%', - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". - position: 'fixed' as ViewStyle['position'], + ...positioning.pFixed, }); export default getNavigationModalCardStyles; diff --git a/src/styles/getTooltipStyles.ts b/src/styles/getTooltipStyles.ts index 7747486a6eb3..f390d658cb5d 100644 --- a/src/styles/getTooltipStyles.ts +++ b/src/styles/getTooltipStyles.ts @@ -3,6 +3,7 @@ import fontFamily from './fontFamily'; import roundToNearestMultipleOfFour from './roundToNearestMultipleOfFour'; import styles from './styles'; import themeColors from './themes/default'; +import positioning from './utilities/positioning'; import spacing from './utilities/spacing'; import variables from './variables'; @@ -236,8 +237,7 @@ export default function getTooltipStyles( transform: [{scale}], }, rootWrapperStyle: { - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". - position: 'fixed' as ViewStyle['position'], + ...positioning.pFixed, backgroundColor: themeColors.heading, borderRadius: variables.componentBorderRadiusSmall, ...tooltipVerticalPadding, @@ -259,8 +259,7 @@ export default function getTooltipStyles( lineHeight: variables.lineHeightSmall, }, pointerWrapperStyle: { - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". - position: 'fixed' as ViewStyle['position'], + ...positioning.pFixed, top: pointerWrapperTop, left: pointerWrapperLeft, }, diff --git a/src/styles/styles.ts b/src/styles/styles.ts index c93e96c8fbee..1c663352569a 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -1596,8 +1596,7 @@ const styles = (theme: ThemeDefault) => overlayStyles: (current: OverlayStylesParams) => ({ - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". - position: 'fixed' as ViewStyle['position'], + ...positioning.pFixed, // We need to stretch the overlay to cover the sidebar and the translate animation distance. left: -2 * variables.sideBarWidth, @@ -2886,8 +2885,7 @@ const styles = (theme: ThemeDefault) => growlNotificationDesktopContainer: { maxWidth: variables.sideBarWidth, right: 0, - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". - position: 'fixed' as ViewStyle['position'], + ...positioning.pFixed, }, growlNotificationTranslateY: (translateY: AnimatableNumericValue) => diff --git a/src/styles/utilities/display.ts b/src/styles/utilities/display.ts index 88b0e0cd5689..124cd87bc67a 100644 --- a/src/styles/utilities/display.ts +++ b/src/styles/utilities/display.ts @@ -19,21 +19,33 @@ export default { display: 'none', }, + /** + * Web-only style. + */ dInline: { // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". display: 'inline' as ViewStyle['display'], }, + /** + * Web-only style. + */ dInlineFlex: { // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". display: 'inline-flex' as ViewStyle['display'], }, + /** + * Web-only style. + */ dBlock: { // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". display: 'block' as ViewStyle['display'], }, + /** + * Web-only style. + */ dGrid: { // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". display: 'grid' as ViewStyle['display'], diff --git a/src/styles/utilities/overflowAuto/index.ts b/src/styles/utilities/overflowAuto/index.ts index d73bee877c50..c87ea5437199 100644 --- a/src/styles/utilities/overflowAuto/index.ts +++ b/src/styles/utilities/overflowAuto/index.ts @@ -1,6 +1,9 @@ import {ViewStyle} from 'react-native'; import OverflowAutoStyles from './types'; +/** + * Web-only style. + */ const overflowAuto: OverflowAutoStyles = { // NOTE: asserting "overflow" to a valid type, because isn't possible to augment "overflow". overflow: 'auto' as ViewStyle['overflow'], diff --git a/src/styles/utilities/positioning.ts b/src/styles/utilities/positioning.ts index 651d2a12f2ea..7cd58c52618b 100644 --- a/src/styles/utilities/positioning.ts +++ b/src/styles/utilities/positioning.ts @@ -11,6 +11,14 @@ export default { pAbsolute: { position: 'absolute', }, + /** + * Web-only style. + */ + pFixed: { + // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + position: 'fixed' as ViewStyle['position'], + }, + t0: { top: 0, }, From 4023821b3f47345840b0bda1a4ad99c7b939793c Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 20 Sep 2023 10:41:27 +0800 Subject: [PATCH 018/284] Only search when online --- src/ONYXKEYS.ts | 3 + src/components/OptionsListSkeletonView.js | 33 +---- .../OptionsSelector/BaseOptionsSelector.js | 127 ++++++++++-------- src/libs/actions/Report.js | 30 +++++ src/pages/SearchPage.js | 27 ++++ 5 files changed, 137 insertions(+), 83 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index af060ea58901..0274d523094e 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -27,6 +27,9 @@ const ONYXKEYS = { /** Boolean flag set whenever the sidebar has loaded */ IS_SIDEBAR_LOADED: 'isSidebarLoaded', + /** Boolean flag set whenever we are searching for reports in the server */ + IS_SEARCHING_FOR_REPORTS: 'isSearchingForReports', + /** Note: These are Persisted Requests - not all requests in the main queue as the key name might lead one to believe */ PERSISTED_REQUESTS: 'networkRequestQueue', diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index 15c66affe84d..833cbc0f372e 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -1,11 +1,9 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import {Rect, Circle} from 'react-native-svg'; -import SkeletonViewContentLoader from 'react-content-loader/native'; import CONST from '../CONST'; -import themeColors from '../styles/themes/default'; import styles from '../styles/styles'; +import OptionsListSkeletonRow from './OptionsListSkeletonRow'; const propTypes = { /** Whether to animate the skeleton view */ @@ -56,32 +54,11 @@ class OptionsListSkeletonView extends React.Component { lineWidth = '25%'; } skeletonViewItems.push( - - - - - , + shouldAnimate={this.props.shouldAnimate} + lineWidth={lineWidth} + />, ); } diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index bff9f8b6d7d0..0a370cd7b2d5 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -17,6 +17,9 @@ import {propTypes as optionsSelectorPropTypes, defaultProps as optionsSelectorDe import setSelection from '../../libs/setSelection'; import compose from '../../libs/compose'; import getPlatform from '../../libs/getPlatform'; +import OptionsListSkeletonRow from '../OptionsListSkeletonRow'; +import Text from '../Text'; +import FormHelpMessage from '../FormHelpMessage'; const propTypes = { /** padding bottom style of safe area */ @@ -344,63 +347,77 @@ class BaseOptionsSelector extends Component { const shouldShowDefaultConfirmButton = !this.props.footerContent && defaultConfirmButtonText; const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : this.props.safeAreaPaddingBottomStyle; const textInput = ( - (this.textInput = el)} - value={this.props.value} - label={this.props.textInputLabel} - accessibilityLabel={this.props.textInputLabel} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - onChangeText={this.props.onChangeText} - onSubmitEditing={this.selectFocusedOption} - placeholder={this.props.placeholderText} - maxLength={this.props.maxLength} - keyboardType={this.props.keyboardType} - onBlur={(e) => { - if (!this.props.shouldFocusOnSelectRow) { - return; - } - this.relatedTarget = e.relatedTarget; - }} - selectTextOnFocus - blurOnSubmit={Boolean(this.state.allOptions.length)} - spellCheck={false} - /> + <> + (this.textInput = el)} + value={this.props.value} + label={this.props.textInputLabel} + accessibilityLabel={this.props.textInputLabel} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + onChangeText={this.props.onChangeText} + onSubmitEditing={this.selectFocusedOption} + placeholder={this.props.placeholderText} + maxLength={this.props.maxLength} + keyboardType={this.props.keyboardType} + onBlur={(e) => { + if (!this.props.shouldFocusOnSelectRow) { + return; + } + this.relatedTarget = e.relatedTarget; + }} + selectTextOnFocus + blurOnSubmit={Boolean(this.state.allOptions.length)} + spellCheck={false} + /> + {this.props.textInputAlert && ( + + )} + + + ); const optionsList = ( - (this.list = el)} - optionHoveredStyle={this.props.optionHoveredStyle} - onSelectRow={this.props.onSelectRow ? this.selectRow : undefined} - sections={this.props.sections} - focusedIndex={this.state.focusedIndex} - selectedOptions={this.props.selectedOptions} - canSelectMultipleOptions={this.props.canSelectMultipleOptions} - shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton} - multipleOptionSelectorButtonText={this.props.multipleOptionSelectorButtonText} - onAddToSelection={this.props.onAddToSelection} - hideSectionHeaders={this.props.hideSectionHeaders} - headerMessage={this.props.headerMessage} - boldStyle={this.props.boldStyle} - showTitleTooltip={this.props.showTitleTooltip} - isDisabled={this.props.isDisabled} - shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator} - highlightSelectedOptions={this.props.highlightSelectedOptions} - onLayout={() => { - if (this.props.selectedOptions.length === 0) { - this.scrollToIndex(this.state.focusedIndex, false); - } - - if (this.props.onLayout) { - this.props.onLayout(); - } - }} - contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} - listContainerStyles={this.props.listContainerStyles} - listStyles={this.props.listStyles} - isLoading={!this.props.shouldShowOptions} - showScrollIndicator={this.props.showScrollIndicator} - isRowMultilineSupported={this.props.isRowMultilineSupported} - /> + <> + {this.props.shouldShowLoader && } + (this.list = el)} + optionHoveredStyle={this.props.optionHoveredStyle} + onSelectRow={this.props.onSelectRow ? this.selectRow : undefined} + sections={this.props.sections} + focusedIndex={this.state.focusedIndex} + selectedOptions={this.props.selectedOptions} + canSelectMultipleOptions={this.props.canSelectMultipleOptions} + shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton} + multipleOptionSelectorButtonText={this.props.multipleOptionSelectorButtonText} + onAddToSelection={this.props.onAddToSelection} + hideSectionHeaders={this.props.hideSectionHeaders} + headerMessage={this.props.headerMessage} + boldStyle={this.props.boldStyle} + showTitleTooltip={this.props.showTitleTooltip} + isDisabled={this.props.isDisabled} + shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator} + highlightSelectedOptions={this.props.highlightSelectedOptions} + onLayout={() => { + if (this.props.selectedOptions.length === 0) { + this.scrollToIndex(this.state.focusedIndex, false); + } + + if (this.props.onLayout) { + this.props.onLayout(); + } + }} + contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} + listContainerStyles={this.props.listContainerStyles} + listStyles={this.props.listStyles} + isLoading={!this.props.shouldShowOptions} + showScrollIndicator={this.props.showScrollIndicator} + isRowMultilineSupported={this.props.isRowMultilineSupported} + /> + ); return ( 0) { + this.throttledSearchInServer(searchValue); + } + + // When the user searches we will this.setState({searchValue}, this.debouncedUpdateOptions); } @@ -116,6 +123,17 @@ class SearchPage extends Component { return sections; } + /** + * @param {string} searchValue + */ + searchInServer(searchValue) { + if (this.props.network.isOffline) { + return; + } + + Report.searchInServer(searchValue); + } + searchRendered() { Timing.end(CONST.TIMING.SEARCH_RENDER); Performance.markEnd(CONST.TIMING.SEARCH_RENDER); @@ -184,9 +202,11 @@ class SearchPage extends Component { showTitleTooltip shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} + textInputAlert={this.props.network.isOffline ? 'You appear to be offline. Search results are limited until you come back online.' : ''} onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus + shouldShowLoader={this.props.isSearchingForReports} /> @@ -212,5 +232,12 @@ export default compose( betas: { key: ONYXKEYS.BETAS, }, + isSearchingForReports: { + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + initWithStoredValues: false, + }, + network: { + key: ONYXKEYS.NETWORK, + } }), )(SearchPage); From dae32f291bf7793d257cf85874b8752968b77ce7 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 20 Sep 2023 12:52:44 +0800 Subject: [PATCH 019/284] Remove Text --- src/components/OptionsSelector/BaseOptionsSelector.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 0a370cd7b2d5..022eaab1ba58 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -18,7 +18,6 @@ import setSelection from '../../libs/setSelection'; import compose from '../../libs/compose'; import getPlatform from '../../libs/getPlatform'; import OptionsListSkeletonRow from '../OptionsListSkeletonRow'; -import Text from '../Text'; import FormHelpMessage from '../FormHelpMessage'; const propTypes = { From c0e5658b4092bb23282ddb6169bff290d06fcbba Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 20 Sep 2023 12:53:03 +0800 Subject: [PATCH 020/284] Add missing file --- src/components/OptionsListSkeletonRow.js | 48 ++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/components/OptionsListSkeletonRow.js diff --git a/src/components/OptionsListSkeletonRow.js b/src/components/OptionsListSkeletonRow.js new file mode 100644 index 000000000000..9df9a610bc14 --- /dev/null +++ b/src/components/OptionsListSkeletonRow.js @@ -0,0 +1,48 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Rect, Circle} from 'react-native-svg'; +import SkeletonViewContentLoader from 'react-content-loader/native'; +import themeColors from '../styles/themes/default'; +import CONST from '../CONST'; +import styles from '../styles/styles'; + +const propTypes = { + /** Whether to animate the skeleton view */ + shouldAnimate: PropTypes.bool.isRequired, + + /** Line width string */ + lineWidth: PropTypes.string.isRequired, +}; + +function OptionsListSkeletonRow({lineWidth, shouldAnimate}) { + return ( + + + + + + ); +} + +OptionsListSkeletonRow.propTypes = propTypes; +export default OptionsListSkeletonRow; \ No newline at end of file From 61e4199178325c68b07ae16ce567d4340b01fc86 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 20 Sep 2023 13:01:52 +0800 Subject: [PATCH 021/284] Run prettier --- src/components/OptionsListSkeletonRow.js | 2 +- .../OptionsSelector/BaseOptionsSelector.js | 9 ++-- src/libs/actions/Report.js | 50 ++++++++++--------- src/pages/SearchPage.js | 2 +- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/components/OptionsListSkeletonRow.js b/src/components/OptionsListSkeletonRow.js index 9df9a610bc14..d7968ef1ba80 100644 --- a/src/components/OptionsListSkeletonRow.js +++ b/src/components/OptionsListSkeletonRow.js @@ -45,4 +45,4 @@ function OptionsListSkeletonRow({lineWidth, shouldAnimate}) { } OptionsListSkeletonRow.propTypes = propTypes; -export default OptionsListSkeletonRow; \ No newline at end of file +export default OptionsListSkeletonRow; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 022eaab1ba58..760f86db799a 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -375,13 +375,16 @@ class BaseOptionsSelector extends Component { isError={false} /> )} - - ); const optionsList = ( <> - {this.props.shouldShowLoader && } + {this.props.shouldShowLoader && ( + + )} (this.list = el)} optionHoveredStyle={this.props.optionHoveredStyle} diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 51fe69acb6fc..016b5928a8f6 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2063,29 +2063,33 @@ function clearPrivateNotesError(reportID, accountID) { * @param {string} searchInput */ function searchInServer(searchInput) { - API.read('SearchForReports', {searchInput}, { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - value: true, - } - ], - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - value: false, - } - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - value: false, - } - ] - }); + API.read( + 'SearchForReports', + {searchInput}, + { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + value: true, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + value: false, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + value: false, + }, + ], + }, + ); } export { diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index fb95f3d24f7e..432c64d51be5 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -238,6 +238,6 @@ export default compose( }, network: { key: ONYXKEYS.NETWORK, - } + }, }), )(SearchPage); From 05a58d8ecbbea76e1b21a9f989d72e0bc278906f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 20 Sep 2023 17:07:13 +0100 Subject: [PATCH 022/284] Address review comments --- src/styles/StyleUtils.ts | 42 +++++++++---------- src/styles/addOutlineWidth/index.ts | 4 +- src/styles/addOutlineWidth/types.ts | 2 +- .../getReportActionContextMenuStyles.ts | 2 +- src/styles/utilities/display.ts | 8 ++-- src/styles/utilities/overflowAuto/index.ts | 2 +- src/styles/utilities/positioning.ts | 2 +- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 9736d79a4a4e..3c131b9645f2 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1,4 +1,4 @@ -import {Animated, FlexStyle, PressableStateCallbackType, TextStyle, ViewStyle} from 'react-native'; +import {Animated, DimensionValue, PressableStateCallbackType, TextStyle, ViewStyle} from 'react-native'; import {EdgeInsets} from 'react-native-safe-area-context'; import {ValueOf} from 'type-fest'; import CONST from '../CONST'; @@ -272,7 +272,7 @@ function getZoomCursorStyle(isZoomed: boolean, isDragging: boolean): ViewStyle { return isDragging ? styles.cursorGrabbing : styles.cursorZoomOut; } -// NOTE: asserting some web style properties to a valid type, because isn't possible to augment them. +// NOTE: asserting some web style properties to a valid type, because it isn't possible to augment them. function getZoomSizingStyle( isZoomed: boolean, imgWidth: number, @@ -286,23 +286,23 @@ function getZoomSizingStyle( if (isLoading || imgWidth === 0 || imgHeight === 0) { return undefined; } - const top = `${Math.max((containerHeight - imgHeight) / 2, 0)}px` as ViewStyle['top']; - const left = `${Math.max((containerWidth - imgWidth) / 2, 0)}px` as ViewStyle['left']; + const top = `${Math.max((containerHeight - imgHeight) / 2, 0)}px` as DimensionValue; + const left = `${Math.max((containerWidth - imgWidth) / 2, 0)}px` as DimensionValue; // Return different size and offset style based on zoomScale and isZoom. if (isZoomed) { // When both width and height are smaller than container(modal) size, set the height by multiplying zoomScale if it is zoomed in. if (zoomScale >= 1) { return { - height: `${imgHeight * zoomScale}px` as FlexStyle['height'], - width: `${imgWidth * zoomScale}px` as FlexStyle['width'], + height: `${imgHeight * zoomScale}px` as DimensionValue, + width: `${imgWidth * zoomScale}px` as DimensionValue, }; } // If image height and width are bigger than container size, display image with original size because original size is bigger and position absolute. return { - height: `${imgHeight}px` as FlexStyle['height'], - width: `${imgWidth}px` as FlexStyle['width'], + height: `${imgHeight}px` as DimensionValue, + width: `${imgWidth}px` as DimensionValue, top, left, }; @@ -311,8 +311,8 @@ function getZoomSizingStyle( // If image is not zoomed in and image size is smaller than container size, display with original size based on offset and position absolute. if (zoomScale > 1) { return { - height: `${imgHeight}px` as FlexStyle['height'], - width: `${imgWidth}px` as FlexStyle['width'], + height: `${imgHeight}px` as DimensionValue, + width: `${imgWidth}px` as DimensionValue, top, left, }; @@ -320,11 +320,11 @@ function getZoomSizingStyle( // If image is bigger than container size, display full image in the screen with scaled size (fit by container size) and position absolute. // top, left offset should be different when displaying long or wide image. - const scaledTop = `${Math.max((containerHeight - imgHeight * zoomScale) / 2, 0)}px` as ViewStyle['top']; - const scaledLeft = `${Math.max((containerWidth - imgWidth * zoomScale) / 2, 0)}px` as ViewStyle['left']; + const scaledTop = `${Math.max((containerHeight - imgHeight * zoomScale) / 2, 0)}px` as DimensionValue; + const scaledLeft = `${Math.max((containerWidth - imgWidth * zoomScale) / 2, 0)}px` as DimensionValue; return { - height: `${imgHeight * zoomScale}px` as FlexStyle['height'], - width: `${imgWidth * zoomScale}px` as FlexStyle['width'], + height: `${imgHeight * zoomScale}px` as DimensionValue, + width: `${imgWidth * zoomScale}px` as DimensionValue, top: scaledTop, left: scaledLeft, }; @@ -448,8 +448,8 @@ function getBackgroundColorWithOpacityStyle(backgroundColor: string, opacity: nu /** * Generate a style for the background color of the Badge */ -function getBadgeColorStyle(success: boolean, error: boolean, isPressed = false, isAdHoc = false): ViewStyle { - if (success) { +function getBadgeColorStyle(isSuccess: boolean, isError: boolean, isPressed = false, isAdHoc = false): ViewStyle { + if (isSuccess) { if (isAdHoc) { // TODO: Remove this "eslint-disable-next" once the theme switching migration is done and styles are fully typed (GH Issue: https://github.com/Expensify/App/issues/27337) // eslint-disable-next-line @typescript-eslint/no-unsafe-return @@ -459,7 +459,7 @@ function getBadgeColorStyle(success: boolean, error: boolean, isPressed = false, // eslint-disable-next-line @typescript-eslint/no-unsafe-return return isPressed ? styles.badgeSuccessPressed : styles.badgeSuccess; } - if (error) { + if (isError) { // TODO: Remove this "eslint-disable-next" once the theme switching migration is done and styles are fully typed (GH Issue: https://github.com/Expensify/App/issues/27337) // eslint-disable-next-line @typescript-eslint/no-unsafe-return return isPressed ? styles.badgeDangerPressed : styles.badgeDanger; @@ -516,7 +516,7 @@ function getAnimatedFABStyle(rotate: Animated.Value, backgroundColor: Animated.V }; } -function getWidthAndHeightStyle(width: number, height: number | undefined = undefined): ViewStyle { +function getWidthAndHeightStyle(width: number, height?: number): ViewStyle { return { width, height: height ?? width, @@ -909,12 +909,12 @@ function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean): ViewStyle /** * Gets styles for AutoCompleteSuggestion row */ -function getAutoCompleteSuggestionItemStyle(highlightedEmojiIndex: number, rowHeight: number, hovered: boolean, currentEmojiIndex: number): ViewStyle[] { +function getAutoCompleteSuggestionItemStyle(highlightedEmojiIndex: number, rowHeight: number, isHovered: boolean, currentEmojiIndex: number): ViewStyle[] { let backgroundColor; if (currentEmojiIndex === highlightedEmojiIndex) { backgroundColor = themeColors.activeComponentBG; - } else if (hovered) { + } else if (isHovered) { backgroundColor = themeColors.hoverComponentBG; } @@ -1023,7 +1023,7 @@ function getEmojiReactionCounterTextStyle(hasUserReacted: boolean): TextStyle { * * @param direction - The direction of the rotation (CONST.DIRECTION.LEFT or CONST.DIRECTION.RIGHT). */ -function getDirectionStyle(direction: string): ViewStyle { +function getDirectionStyle(direction: ValueOf): ViewStyle { if (direction === CONST.DIRECTION.LEFT) { return {transform: [{rotate: '180deg'}]}; } diff --git a/src/styles/addOutlineWidth/index.ts b/src/styles/addOutlineWidth/index.ts index 257a3de2b6cd..6fe2ecf85978 100644 --- a/src/styles/addOutlineWidth/index.ts +++ b/src/styles/addOutlineWidth/index.ts @@ -9,11 +9,11 @@ import AddOutlineWidth from './types'; /** * Adds the addOutlineWidth property to an object to be used when styling */ -const addOutlineWidth: AddOutlineWidth = (obj, val, error = false) => ({ +const addOutlineWidth: AddOutlineWidth = (obj, val, hasError = false) => ({ ...obj, outlineWidth: val, outlineStyle: val ? 'auto' : 'none', - boxShadow: val !== 0 ? `0px 0px 0px ${val}px ${error ? themeDefault.danger : themeDefault.borderFocus}` : 'none', + boxShadow: val !== 0 ? `0px 0px 0px ${val}px ${hasError ? themeDefault.danger : themeDefault.borderFocus}` : 'none', }); export default addOutlineWidth; diff --git a/src/styles/addOutlineWidth/types.ts b/src/styles/addOutlineWidth/types.ts index fe5d1f5719b2..7a3a86506959 100644 --- a/src/styles/addOutlineWidth/types.ts +++ b/src/styles/addOutlineWidth/types.ts @@ -1,5 +1,5 @@ import {TextStyle} from 'react-native'; -type AddOutlineWidth = (obj: TextStyle, val?: number, error?: boolean) => TextStyle; +type AddOutlineWidth = (obj: TextStyle, val?: number, hasError?: boolean) => TextStyle; export default AddOutlineWidth; diff --git a/src/styles/getReportActionContextMenuStyles.ts b/src/styles/getReportActionContextMenuStyles.ts index 6b4ad8807552..cd3843169a43 100644 --- a/src/styles/getReportActionContextMenuStyles.ts +++ b/src/styles/getReportActionContextMenuStyles.ts @@ -15,7 +15,7 @@ const miniWrapperStyle: ViewStyle[] = [ borderWidth: 1, borderColor: themeColors.border, // In Safari, when welcome messages use a code block (triple backticks), they would overlap the context menu below when there is no scrollbar without the transform style. - // NOTE: asserting "transform" to a valid type, because isn't possible to augment "transform". + // NOTE: asserting "transform" to a valid type, because it isn't possible to augment "transform". transform: 'translateZ(0)' as unknown as ViewStyle['transform'], }, ]; diff --git a/src/styles/utilities/display.ts b/src/styles/utilities/display.ts index 124cd87bc67a..f14a25d641b1 100644 --- a/src/styles/utilities/display.ts +++ b/src/styles/utilities/display.ts @@ -23,7 +23,7 @@ export default { * Web-only style. */ dInline: { - // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". + // NOTE: asserting "display" to a valid type, because it isn't possible to augment "display". display: 'inline' as ViewStyle['display'], }, @@ -31,7 +31,7 @@ export default { * Web-only style. */ dInlineFlex: { - // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". + // NOTE: asserting "display" to a valid type, because it isn't possible to augment "display". display: 'inline-flex' as ViewStyle['display'], }, @@ -39,7 +39,7 @@ export default { * Web-only style. */ dBlock: { - // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". + // NOTE: asserting "display" to a valid type, because it isn't possible to augment "display". display: 'block' as ViewStyle['display'], }, @@ -47,7 +47,7 @@ export default { * Web-only style. */ dGrid: { - // NOTE: asserting "display" to a valid type, because isn't possible to augment "display". + // NOTE: asserting "display" to a valid type, because it isn't possible to augment "display". display: 'grid' as ViewStyle['display'], }, } satisfies Record; diff --git a/src/styles/utilities/overflowAuto/index.ts b/src/styles/utilities/overflowAuto/index.ts index c87ea5437199..1e7ac8ed8246 100644 --- a/src/styles/utilities/overflowAuto/index.ts +++ b/src/styles/utilities/overflowAuto/index.ts @@ -5,7 +5,7 @@ import OverflowAutoStyles from './types'; * Web-only style. */ const overflowAuto: OverflowAutoStyles = { - // NOTE: asserting "overflow" to a valid type, because isn't possible to augment "overflow". + // NOTE: asserting "overflow" to a valid type, because it isn't possible to augment "overflow". overflow: 'auto' as ViewStyle['overflow'], }; diff --git a/src/styles/utilities/positioning.ts b/src/styles/utilities/positioning.ts index 7cd58c52618b..26e6198a5827 100644 --- a/src/styles/utilities/positioning.ts +++ b/src/styles/utilities/positioning.ts @@ -15,7 +15,7 @@ export default { * Web-only style. */ pFixed: { - // NOTE: asserting "position" to a valid type, because isn't possible to augment "position". + // NOTE: asserting "position" to a valid type, because it isn't possible to augment "position". position: 'fixed' as ViewStyle['position'], }, From 0d9a313de199df2554deb4f6ade28be73144e09f Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 21 Sep 2023 10:24:53 +0800 Subject: [PATCH 023/284] Slow down server search --- src/pages/SearchPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 432c64d51be5..9fae2fe2cd4a 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -59,7 +59,7 @@ class SearchPage extends Component { const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getSearchOptions(props.reports, props.personalDetails, '', props.betas); - this.throttledSearchInServer = _.throttle(this.searchInServer.bind(this), 300, {leading: false}); + this.throttledSearchInServer = _.throttle(this.searchInServer.bind(this), 1000, {leading: false}); this.state = { searchValue: '', From 71689bd7edd91cd94c4d871f7d5546d2c2bfb3b1 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 21 Sep 2023 15:10:42 +0800 Subject: [PATCH 024/284] Add amount and thumbnail image --- src/components/EReceipt.js | 19 +++++++++++++------ src/styles/styles.js | 7 +++++++ src/styles/themes/default.js | 1 + 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index 2068a5e50f12..1d4057c6e437 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -1,10 +1,13 @@ import React from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; +import Text from './Text'; import styles from '../styles/styles'; import transactionPropTypes from './transactionPropTypes'; import * as ReceiptUtils from '../libs/ReceiptUtils'; -import Image from './Image'; +import * as ReportUtils from '../libs/ReportUtils'; +import * as CurrencyUtils from '../libs/CurrencyUtils'; +import tryResolveUrlFromApiRoot from '../libs/tryResolveUrlFromApiRoot'; +import ThumbnailImage from './ThumbnailImage'; const propTypes = { /** The transaction for the eReceipt */ @@ -17,16 +20,20 @@ const defaultProps = { function EReceipt({transaction}) { const {thumbnail} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); + const {amount: transactionAmount, currency: transactionCurrency} = ReportUtils.getTransactionDetails(transaction); + const formattedTransactionAmount = CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency); + const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); return ( <> - - + + {formattedTransactionAmount} ); } diff --git a/src/styles/styles.js b/src/styles/styles.js index 4a2472913fd2..58a6ada7de0c 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3553,6 +3553,13 @@ const styles = (theme) => ({ lineHeight: variables.lineHeightXXLarge, }, + eReceiptAmount: { + ...headlineFont, + fontSize: variables.fontSizeXXXLarge, + lineHeight: variables.lineHeightXXXLarge, + color: theme.eReceiptAmount, + }, + loginHeroBody: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeSignInHeroBody, diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js index 5ff997684304..75d3ded6b545 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.js @@ -83,6 +83,7 @@ const darkTheme = { QRLogo: colors.green400, starDefaultBG: 'rgb(254, 228, 94)', loungeAccessOverlay: colors.blue800, + eReceiptAmount: colors.green400, }; darkTheme.PAGE_BACKGROUND_COLORS = { From 8095338a5e17b95d7aaea97d38b5831ffe29d0e2 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 21 Sep 2023 15:29:28 +0800 Subject: [PATCH 025/284] Add merchant --- src/components/EReceipt.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index 1d4057c6e437..bca23ae83e24 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -20,7 +20,7 @@ const defaultProps = { function EReceipt({transaction}) { const {thumbnail} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); - const {amount: transactionAmount, currency: transactionCurrency} = ReportUtils.getTransactionDetails(transaction); + const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant} = ReportUtils.getTransactionDetails(transaction); const formattedTransactionAmount = CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); return ( @@ -34,6 +34,7 @@ function EReceipt({transaction}) { /> {formattedTransactionAmount} + {transactionMerchant} ); } From d9ddb6f3def934ab57c0a2803855212f8b9acedf Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 21 Sep 2023 15:40:40 +0800 Subject: [PATCH 026/284] Display waypoints --- src/components/EReceipt.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index bca23ae83e24..58d49f5a54be 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -1,13 +1,17 @@ import React from 'react'; import {View} from 'react-native'; +import lodashGet from 'lodash/get'; +import _ from 'underscore'; import Text from './Text'; import styles from '../styles/styles'; import transactionPropTypes from './transactionPropTypes'; import * as ReceiptUtils from '../libs/ReceiptUtils'; import * as ReportUtils from '../libs/ReportUtils'; import * as CurrencyUtils from '../libs/CurrencyUtils'; +import * as TransactionUtils from '../libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '../libs/tryResolveUrlFromApiRoot'; import ThumbnailImage from './ThumbnailImage'; +import useLocalize from '../hooks/useLocalize'; const propTypes = { /** The transaction for the eReceipt */ @@ -19,10 +23,12 @@ const defaultProps = { }; function EReceipt({transaction}) { + const {translate} = useLocalize(); const {thumbnail} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant} = ReportUtils.getTransactionDetails(transaction); const formattedTransactionAmount = CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); + const waypoints = lodashGet(transaction, 'comment.waypoints', {}); return ( <> @@ -35,6 +41,25 @@ function EReceipt({transaction}) { {formattedTransactionAmount} {transactionMerchant} + <> + {_.map(waypoints, (waypoint, key) => { + const index = TransactionUtils.getWaypointIndex(key); + let descriptionKey = 'distance.waypointDescription.'; + if (index === 0) { + descriptionKey += 'start'; + } else if (index === _.size(waypoints) - 1) { + descriptionKey += 'finish'; + } else { + descriptionKey += 'stop'; + } + return ( + + {translate(descriptionKey)} + {waypoint.address || ''} + + ); + })} + ); } From 71ff545f73fac187bf1db127243daf1a2c53794f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 21 Sep 2023 15:50:53 +0800 Subject: [PATCH 027/284] Remove unused import --- src/styles/themes/default.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js index 75d3ded6b545..561a1bafb532 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.js @@ -1,7 +1,6 @@ /* eslint-disable no-unused-vars */ import colors from '../colors'; import SCREENS from '../../SCREENS'; -import ROUTES from '../../ROUTES'; const darkTheme = { // Figma keys From 215c03eda0b4bbe33f0b9e8c891418342936da4c Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 21 Sep 2023 15:51:13 +0800 Subject: [PATCH 028/284] Style e receipt merchant text --- src/components/EReceipt.js | 2 +- src/styles/styles.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index 58d49f5a54be..3094ef59bf9c 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -40,7 +40,7 @@ function EReceipt({transaction}) { /> {formattedTransactionAmount} - {transactionMerchant} + {transactionMerchant} <> {_.map(waypoints, (waypoint, key) => { const index = TransactionUtils.getWaypointIndex(key); diff --git a/src/styles/styles.js b/src/styles/styles.js index 58a6ada7de0c..0b7aff4037fe 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3560,6 +3560,13 @@ const styles = (theme) => ({ color: theme.eReceiptAmount, }, + eReceiptMerchant: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeXLarge, + lineHeight: variables.lineHeightXLarge, + color: theme.text, + }, + loginHeroBody: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeSignInHeroBody, From 90ad2292efab5db14689ba5f0aa11fe6964b381b Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 21 Sep 2023 15:57:30 +0800 Subject: [PATCH 029/284] Style eReceipt waypoint titles --- src/components/EReceipt.js | 2 +- src/styles/styles.js | 9 ++++++++- src/styles/themes/default.js | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index 3094ef59bf9c..ba9dd13ae276 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -54,7 +54,7 @@ function EReceipt({transaction}) { } return ( - {translate(descriptionKey)} + {translate(descriptionKey)} {waypoint.address || ''} ); diff --git a/src/styles/styles.js b/src/styles/styles.js index 0b7aff4037fe..d17547341df0 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3557,7 +3557,7 @@ const styles = (theme) => ({ ...headlineFont, fontSize: variables.fontSizeXXXLarge, lineHeight: variables.lineHeightXXXLarge, - color: theme.eReceiptAmount, + color: theme.textBrand, }, eReceiptMerchant: { @@ -3567,6 +3567,13 @@ const styles = (theme) => ({ color: theme.text, }, + eReceiptWaypointTitle: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeSmall, + lineHeight: variables.lineHeightSmall, + color: theme.textBrand, + }, + loginHeroBody: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeSignInHeroBody, diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js index 561a1bafb532..eac8b087ead0 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.js @@ -82,7 +82,7 @@ const darkTheme = { QRLogo: colors.green400, starDefaultBG: 'rgb(254, 228, 94)', loungeAccessOverlay: colors.blue800, - eReceiptAmount: colors.green400, + textBrand: colors.green400, }; darkTheme.PAGE_BACKGROUND_COLORS = { From c1aa7b37a9a84eeb6fffbab0bb5a72e09d8f1ef0 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 21 Sep 2023 16:05:51 +0800 Subject: [PATCH 030/284] Style eReceipt waypoint address --- src/components/EReceipt.js | 2 +- src/styles/styles.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index ba9dd13ae276..bd26b0a5b4b0 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -55,7 +55,7 @@ function EReceipt({transaction}) { return ( {translate(descriptionKey)} - {waypoint.address || ''} + {waypoint.address || ''} ); })} diff --git a/src/styles/styles.js b/src/styles/styles.js index d17547341df0..c15b0f8b9dcf 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3574,6 +3574,13 @@ const styles = (theme) => ({ color: theme.textBrand, }, + eReceiptWaypointAddress: { + fontFamily: fontFamily.MONOSPACE, + fontSize: variables.fontSizeNormal, + lineHeight: variables.lineHeightNormal, + color: theme.textColorfulBackground, + }, + loginHeroBody: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeSignInHeroBody, From 5bb7b9eb6661903993300ded689d3f19c4e06ef2 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 21 Sep 2023 16:10:18 +0800 Subject: [PATCH 031/284] Add date text --- src/components/EReceipt.js | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index bd26b0a5b4b0..b84be5dc8be7 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -25,7 +25,7 @@ const defaultProps = { function EReceipt({transaction}) { const {translate} = useLocalize(); const {thumbnail} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); - const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant} = ReportUtils.getTransactionDetails(transaction); + const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant, created: transactionDate} = ReportUtils.getTransactionDetails(transaction); const formattedTransactionAmount = CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const waypoints = lodashGet(transaction, 'comment.waypoints', {}); @@ -41,25 +41,25 @@ function EReceipt({transaction}) { {formattedTransactionAmount} {transactionMerchant} - <> - {_.map(waypoints, (waypoint, key) => { - const index = TransactionUtils.getWaypointIndex(key); - let descriptionKey = 'distance.waypointDescription.'; - if (index === 0) { - descriptionKey += 'start'; - } else if (index === _.size(waypoints) - 1) { - descriptionKey += 'finish'; - } else { - descriptionKey += 'stop'; - } - return ( - - {translate(descriptionKey)} - {waypoint.address || ''} - - ); - })} - + {_.map(waypoints, (waypoint, key) => { + const index = TransactionUtils.getWaypointIndex(key); + let descriptionKey = 'distance.waypointDescription.'; + if (index === 0) { + descriptionKey += 'start'; + } else if (index === _.size(waypoints) - 1) { + descriptionKey += 'finish'; + } else { + descriptionKey += 'stop'; + } + return ( + + {translate(descriptionKey)} + {waypoint.address || ''} + + ); + })} + {translate('common.date')} + {transactionDate} ); } From dd0fe7ebe01a713d0ba42004d9fc07c9f249160b Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 21 Sep 2023 16:18:38 +0800 Subject: [PATCH 032/284] WIP add logo --- src/components/EReceipt.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index b84be5dc8be7..cd5959b1a41d 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -12,6 +12,7 @@ import * as TransactionUtils from '../libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '../libs/tryResolveUrlFromApiRoot'; import ThumbnailImage from './ThumbnailImage'; import useLocalize from '../hooks/useLocalize'; +import fontFamily from '../styles/fontFamily'; const propTypes = { /** The transaction for the eReceipt */ @@ -60,6 +61,14 @@ function EReceipt({transaction}) { })} {translate('common.date')} {transactionDate} + + + ); } From f69bf9936eb47e7bb9d2a671b06020ab5fc91efa Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 Sep 2023 08:37:03 +0800 Subject: [PATCH 033/284] Add logo and guaranteed eReceipt --- src/components/EReceipt.js | 7 +++++-- src/languages/en.ts | 3 +++ src/styles/styles.js | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index cd5959b1a41d..1f24144031d4 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -12,7 +12,9 @@ import * as TransactionUtils from '../libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '../libs/tryResolveUrlFromApiRoot'; import ThumbnailImage from './ThumbnailImage'; import useLocalize from '../hooks/useLocalize'; -import fontFamily from '../styles/fontFamily'; +import Icon from './Icon'; +import themeColors from '../styles/themes/default'; +import * as Expensicons from './Icon/Expensicons'; const propTypes = { /** The transaction for the eReceipt */ @@ -65,9 +67,10 @@ function EReceipt({transaction}) { + {translate('eReceipt.guaranteed')} ); diff --git a/src/languages/en.ts b/src/languages/en.ts index f7c028d2a106..495e7b0f7105 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1778,4 +1778,7 @@ export default { selectSuggestedAddress: 'Please select a suggested address', }, }, + eReceipt: { + guaranteed: 'Guaranteed eReceipt', + }, } satisfies TranslationBase; diff --git a/src/styles/styles.js b/src/styles/styles.js index c15b0f8b9dcf..de7cfec48ef7 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3581,6 +3581,13 @@ const styles = (theme) => ({ color: theme.textColorfulBackground, }, + eReceiptGuaranteed: { + fontFamily: fontFamily.MONOSPACE, + fontSize: variables.fontSizeSmall, + lineHeight: variables.lineHeightSmall, + color: theme.textColorfulBackground, + }, + loginHeroBody: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeSignInHeroBody, From 3a67ed468d4721a7d819c92bb9c082683b416a83 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 Sep 2023 08:57:16 +0800 Subject: [PATCH 034/284] Set up wrapping views with padding --- src/components/EReceipt.js | 80 +++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index 1f24144031d4..d89439e32326 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -33,46 +33,48 @@ function EReceipt({transaction}) { const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const waypoints = lodashGet(transaction, 'comment.waypoints', {}); return ( - <> - - + + + + + + {formattedTransactionAmount} + {transactionMerchant} + {_.map(waypoints, (waypoint, key) => { + const index = TransactionUtils.getWaypointIndex(key); + let descriptionKey = 'distance.waypointDescription.'; + if (index === 0) { + descriptionKey += 'start'; + } else if (index === _.size(waypoints) - 1) { + descriptionKey += 'finish'; + } else { + descriptionKey += 'stop'; + } + return ( + + {translate(descriptionKey)} + {waypoint.address || ''} + + ); + })} + {translate('common.date')} + {transactionDate} + + + {translate('eReceipt.guaranteed')} + - {formattedTransactionAmount} - {transactionMerchant} - {_.map(waypoints, (waypoint, key) => { - const index = TransactionUtils.getWaypointIndex(key); - let descriptionKey = 'distance.waypointDescription.'; - if (index === 0) { - descriptionKey += 'start'; - } else if (index === _.size(waypoints) - 1) { - descriptionKey += 'finish'; - } else { - descriptionKey += 'stop'; - } - return ( - - {translate(descriptionKey)} - {waypoint.address || ''} - - ); - })} - {translate('common.date')} - {transactionDate} - - - {translate('eReceipt.guaranteed')} - - + ); } From 74664ceab6450c5411d8bdab9cc533cf11d61dd4 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 Sep 2023 10:12:10 +0800 Subject: [PATCH 035/284] Fix lint issues / clean up --- src/components/Attachments/AttachmentView/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index e7d1dd5abb6b..b0e22c237eaf 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -69,6 +69,7 @@ function AttachmentView({ isWorkspaceAvatar, }) { const [loadComplete, setLoadComplete] = useState(false); + const currentRoute = useRoute(); // Handles case where source is a component (ex: SVG) if (_.isFunction(source)) { @@ -112,7 +113,6 @@ function AttachmentView({ ); } - const currentRoute = useRoute(); const reportID = _.get(currentRoute, ['params', 'reportID']); const report = ReportUtils.getReport(reportID); @@ -120,8 +120,6 @@ function AttachmentView({ const parentReportAction = ReportActionsUtils.getParentReportAction(report); const transactionID = _.get(parentReportAction, ['originalMessage', 'IOUTransactionID'], 0); const transaction = TransactionUtils.getTransaction(transactionID); - console.log('transaction', transaction); - const shouldShowEReceipt = TransactionUtils.isDistanceRequest(transaction); if (shouldShowEReceipt) { return ; From 6e1440edff178f5be01b29cef62756565971c6c6 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 Sep 2023 10:12:35 +0800 Subject: [PATCH 036/284] Add the eReceipt background --- assets/images/eReceipt_background.svg | 1635 +++++++++++++++++++++++++ src/components/EReceipt.js | 25 +- src/styles/styles.js | 28 + src/styles/themes/default.js | 1 + src/styles/utilities/spacing.ts | 4 + 5 files changed, 1686 insertions(+), 7 deletions(-) create mode 100644 assets/images/eReceipt_background.svg diff --git a/assets/images/eReceipt_background.svg b/assets/images/eReceipt_background.svg new file mode 100644 index 000000000000..257489bd6fdd --- /dev/null +++ b/assets/images/eReceipt_background.svg @@ -0,0 +1,1635 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index d89439e32326..5f5619cb22e4 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -15,6 +15,7 @@ import useLocalize from '../hooks/useLocalize'; import Icon from './Icon'; import themeColors from '../styles/themes/default'; import * as Expensicons from './Icon/Expensicons'; +import EReceiptBackground from '../../assets/images/eReceipt_background.svg'; const propTypes = { /** The transaction for the eReceipt */ @@ -33,8 +34,14 @@ function EReceipt({transaction}) { const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const waypoints = lodashGet(transaction, 'comment.waypoints', {}); return ( - - + + + + + - {formattedTransactionAmount} - {transactionMerchant} + + {formattedTransactionAmount} + {transactionMerchant} + {_.map(waypoints, (waypoint, key) => { const index = TransactionUtils.getWaypointIndex(key); let descriptionKey = 'distance.waypointDescription.'; @@ -56,14 +65,16 @@ function EReceipt({transaction}) { descriptionKey += 'stop'; } return ( - + {translate(descriptionKey)} {waypoint.address || ''} ); })} - {translate('common.date')} - {transactionDate} + + {translate('common.date')} + {transactionDate} + ({ color: theme.textColorfulBackground, }, + eReceiptBackgroundContainer: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + }, + + eReceiptBackground: { + ...sizing.w100, + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + minHeight: 540, + }, + + eReceiptPanel: { + ...spacing.p5, + ...spacing.pb8, + ...flex.flex1, + backgroundColor: theme.panelBackground, + borderRadius: 20, + }, + loginHeroBody: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeSignInHeroBody, diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js index eac8b087ead0..5e06753d719a 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.js @@ -83,6 +83,7 @@ const darkTheme = { starDefaultBG: 'rgb(254, 228, 94)', loungeAccessOverlay: colors.blue800, textBrand: colors.green400, + panelBackground: colors.green800, }; darkTheme.PAGE_BACKGROUND_COLORS = { diff --git a/src/styles/utilities/spacing.ts b/src/styles/utilities/spacing.ts index a3667f05ac06..a47efe504326 100644 --- a/src/styles/utilities/spacing.ts +++ b/src/styles/utilities/spacing.ts @@ -55,6 +55,10 @@ export default { marginHorizontal: -20, }, + mv0: { + marginVertical: 0, + }, + mv1: { marginVertical: 4, }, From f8ef14475d4abb35300cea6a4898138c3611d713 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 Sep 2023 15:20:08 +0800 Subject: [PATCH 037/284] The eReceipt is specific to distance requests --- src/components/Attachments/AttachmentView/index.js | 6 +++--- src/components/{EReceipt.js => DistanceEReceipt.js} | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) rename src/components/{EReceipt.js => DistanceEReceipt.js} (95%) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index b0e22c237eaf..aca92bf7ba5d 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -3,6 +3,7 @@ import {View, ActivityIndicator} from 'react-native'; import _ from 'underscore'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; +import {useRoute} from '@react-navigation/native'; import styles from '../../../styles/styles'; import Icon from '../../Icon'; import * as Expensicons from '../../Icon/Expensicons'; @@ -17,10 +18,9 @@ import AttachmentViewPdf from './AttachmentViewPdf'; import addEncryptedAuthTokenToURL from '../../../libs/addEncryptedAuthTokenToURL'; import * as StyleUtils from '../../../styles/StyleUtils'; import {attachmentViewPropTypes, attachmentViewDefaultProps} from './propTypes'; -import {useRoute} from '@react-navigation/native'; import * as ReportUtils from '../../../libs/ReportUtils'; import * as TransactionUtils from '../../../libs/TransactionUtils'; -import EReceipt from '../../EReceipt'; +import DistanceEReceipt from '../../DistanceEReceipt'; import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; const propTypes = { @@ -122,7 +122,7 @@ function AttachmentView({ const transaction = TransactionUtils.getTransaction(transactionID); const shouldShowEReceipt = TransactionUtils.isDistanceRequest(transaction); if (shouldShowEReceipt) { - return ; + return ; } // For this check we use both source and file.name since temporary file source is a blob diff --git a/src/components/EReceipt.js b/src/components/DistanceEReceipt.js similarity index 95% rename from src/components/EReceipt.js rename to src/components/DistanceEReceipt.js index 5f5619cb22e4..c8d428dde000 100644 --- a/src/components/EReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -26,7 +26,7 @@ const defaultProps = { transaction: {}, }; -function EReceipt({transaction}) { +function DistanceEReceipt({transaction}) { const {translate} = useLocalize(); const {thumbnail} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant, created: transactionDate} = ReportUtils.getTransactionDetails(transaction); @@ -89,7 +89,7 @@ function EReceipt({transaction}) { ); } -export default EReceipt; -EReceipt.displayName = 'EReceipt'; -EReceipt.propTypes = propTypes; -EReceipt.defaultProps = defaultProps; +export default DistanceEReceipt; +DistanceEReceipt.displayName = 'DistanceEReceipt'; +DistanceEReceipt.propTypes = propTypes; +DistanceEReceipt.defaultProps = defaultProps; From ab4db9793693bd7e57b254a3fd025657f61ee81d Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 Sep 2023 15:38:36 +0800 Subject: [PATCH 038/284] Set up gap between main sections --- src/components/DistanceEReceipt.js | 46 ++++++++++++++++-------------- src/styles/utilities/spacing.ts | 4 +++ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index c8d428dde000..0543cbc00794 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -42,7 +42,7 @@ function DistanceEReceipt({transaction}) { pointerEvents="none" /> - + - + {formattedTransactionAmount} {transactionMerchant} - {_.map(waypoints, (waypoint, key) => { - const index = TransactionUtils.getWaypointIndex(key); - let descriptionKey = 'distance.waypointDescription.'; - if (index === 0) { - descriptionKey += 'start'; - } else if (index === _.size(waypoints) - 1) { - descriptionKey += 'finish'; - } else { - descriptionKey += 'stop'; - } - return ( - - {translate(descriptionKey)} - {waypoint.address || ''} - - ); - })} - - {translate('common.date')} - {transactionDate} + + {_.map(waypoints, (waypoint, key) => { + const index = TransactionUtils.getWaypointIndex(key); + let descriptionKey = 'distance.waypointDescription.'; + if (index === 0) { + descriptionKey += 'start'; + } else if (index === _.size(waypoints) - 1) { + descriptionKey += 'finish'; + } else { + descriptionKey += 'stop'; + } + return ( + + {translate(descriptionKey)} + {waypoint.address || ''} + + ); + })} + + {translate('common.date')} + {transactionDate} + Date: Fri, 22 Sep 2023 16:19:11 +0800 Subject: [PATCH 039/284] Spacing and layout within sections --- src/components/DistanceEReceipt.js | 17 ++++++++++------- src/styles/utilities/spacing.ts | 4 ++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 0543cbc00794..0f83adc86ef2 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -50,11 +50,11 @@ function DistanceEReceipt({transaction}) { shouldDynamicallyResize={false} /> - + {formattedTransactionAmount} {transactionMerchant} - + {_.map(waypoints, (waypoint, key) => { const index = TransactionUtils.getWaypointIndex(key); let descriptionKey = 'distance.waypointDescription.'; @@ -66,21 +66,24 @@ function DistanceEReceipt({transaction}) { descriptionKey += 'stop'; } return ( - + {translate(descriptionKey)} {waypoint.address || ''} ); })} - + {translate('common.date')} {transactionDate} - + diff --git a/src/styles/utilities/spacing.ts b/src/styles/utilities/spacing.ts index e87d8cf85b31..a998b3302b3a 100644 --- a/src/styles/utilities/spacing.ts +++ b/src/styles/utilities/spacing.ts @@ -171,6 +171,10 @@ export default { marginLeft: -32, }, + mt0: { + marginTop: 0, + }, + mt1: { marginTop: 4, }, From 8b8afe9c1aea0cd85ba57e50bed19bc49ca805ad Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 Sep 2023 16:35:58 +0800 Subject: [PATCH 040/284] Set width on eReceipt for large screens --- src/components/DistanceEReceipt.js | 2 +- src/styles/styles.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 0f83adc86ef2..ab9065a8c49f 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -34,7 +34,7 @@ function DistanceEReceipt({transaction}) { const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const waypoints = lodashGet(transaction, 'comment.waypoints', {}); return ( - + ({ eReceiptPanel: { ...spacing.p5, ...spacing.pb8, - ...flex.flex1, backgroundColor: theme.panelBackground, borderRadius: 20, + width: 335, }, loginHeroBody: { From 630a6fdf8c8f2c2bb63042d0f778e15e51cdc282 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 25 Sep 2023 14:33:55 +0800 Subject: [PATCH 041/284] Make NewChatPage also search --- src/languages/en.ts | 3 +++ src/languages/es.ts | 3 +++ src/libs/actions/Report.js | 14 +++++++++++++- src/pages/NewChatPage.js | 14 +++++++++++++- src/pages/SearchPage.js | 17 ++--------------- 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f7c028d2a106..6f0258a3063e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1554,6 +1554,9 @@ export default { screenShare: 'Screen share', screenShareRequest: 'Expensify is inviting you to a screen share', }, + search: { + offline: 'You appear to be offline; search results are limited.', + }, genericErrorPage: { title: 'Uh-oh, something went wrong!', body: { diff --git a/src/languages/es.ts b/src/languages/es.ts index a68f33a33730..1cc74b4db2aa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1576,6 +1576,9 @@ export default { screenShare: 'Compartir pantalla', screenShareRequest: 'Expensify te está invitando a compartir la pantalla', }, + search: { + offline: 'You appear to be offline; search results are limited.', + }, genericErrorPage: { title: '¡Uh-oh, algo salió mal!', body: { diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 8d11dd1163af..14837304081a 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2131,9 +2131,15 @@ function clearPrivateNotesError(reportID, accountID) { } /** + * @private * @param {string} searchInput */ function searchInServer(searchInput) { + // We do not try to make this request while offline because it sets a loading indicator optimistically + if (isNetworkOffline) { + return; + } + API.read( 'SearchForReports', {searchInput}, @@ -2163,8 +2169,14 @@ function searchInServer(searchInput) { ); } +/** + * @private + * @param {string} searchInput + */ +const throttledSearchInServer = _.throttle(searchInServer, 1000, {leading: false}); + export { - searchInServer, + throttledSearchInServer, addComment, addAttachment, reconnect, diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index cb54aa8e5a7b..bd206eb65346 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -20,6 +20,7 @@ import compose from '../libs/compose'; import personalDetailsPropType from './personalDetailsPropType'; import reportPropTypes from './reportPropTypes'; import variables from '../styles/variables'; +import useNetwork from '../hooks/useNetwork'; const propTypes = { /** Beta features list */ @@ -44,12 +45,13 @@ const defaultProps = { const excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE); -function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) { +function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, isSearchingForReports}) { const [searchTerm, setSearchTerm] = useState(''); const [filteredRecentReports, setFilteredRecentReports] = useState([]); const [filteredPersonalDetails, setFilteredPersonalDetails] = useState([]); const [filteredUserToInvite, setFilteredUserToInvite] = useState(); const [selectedOptions, setSelectedOptions] = useState([]); + const {isOffline} = useNetwork(); const maxParticipantsReached = selectedOptions.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const headerMessage = OptionsListUtils.getHeaderMessage( @@ -167,6 +169,10 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) // eslint-disable-next-line react-hooks/exhaustive-deps }, [reports, personalDetails, searchTerm]); + // When search term updates we will fetch any reports + useEffect(() => { + Report.throttledSearchInServer(searchTerm); + }, [searchTerm]); return ( 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')} + textInputAlert={isOffline ? translate('search.offline') : ''} onConfirmSelection={createGroup} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} + shouldShowLoader={isSearchingForReports} /> @@ -229,5 +237,9 @@ export default compose( betas: { key: ONYXKEYS.BETAS, }, + isSearchingForReports: { + key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, + initWithStoredValues: false, + }, }), )(NewChatPage); diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 9fae2fe2cd4a..7dbddc166f47 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -59,8 +59,6 @@ class SearchPage extends Component { const {recentReports, personalDetails, userToInvite} = OptionsListUtils.getSearchOptions(props.reports, props.personalDetails, '', props.betas); - this.throttledSearchInServer = _.throttle(this.searchInServer.bind(this), 1000, {leading: false}); - this.state = { searchValue: '', recentReports, @@ -78,7 +76,7 @@ class SearchPage extends Component { onChangeText(searchValue = '') { if (searchValue.length > 0) { - this.throttledSearchInServer(searchValue); + Report.throttledSearchInServer(searchValue); } // When the user searches we will @@ -123,17 +121,6 @@ class SearchPage extends Component { return sections; } - /** - * @param {string} searchValue - */ - searchInServer(searchValue) { - if (this.props.network.isOffline) { - return; - } - - Report.searchInServer(searchValue); - } - searchRendered() { Timing.end(CONST.TIMING.SEARCH_RENDER); Performance.markEnd(CONST.TIMING.SEARCH_RENDER); @@ -202,7 +189,7 @@ class SearchPage extends Component { showTitleTooltip shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} - textInputAlert={this.props.network.isOffline ? 'You appear to be offline. Search results are limited until you come back online.' : ''} + textInputAlert={this.props.network.isOffline ? this.props.translate('search.offline') : ''} onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus From 8c28a58bfb93ce1434adecf244fbb6f75a048bbf Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 25 Sep 2023 15:41:20 +0800 Subject: [PATCH 042/284] Center eReceipt vertically on page --- src/components/DistanceEReceipt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index ab9065a8c49f..a96e8fb4c31b 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -34,7 +34,7 @@ function DistanceEReceipt({transaction}) { const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const waypoints = lodashGet(transaction, 'comment.waypoints', {}); return ( - + Date: Mon, 25 Sep 2023 16:05:24 +0800 Subject: [PATCH 043/284] Use colors directly since it's a specific purpose --- src/styles/styles.js | 6 +++--- src/styles/themes/default.js | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/styles/styles.js b/src/styles/styles.js index 936e16464fac..47407cb5c2a8 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3557,7 +3557,7 @@ const styles = (theme) => ({ ...headlineFont, fontSize: variables.fontSizeXXXLarge, lineHeight: variables.lineHeightXXXLarge, - color: theme.textBrand, + color: colors.green400, }, eReceiptMerchant: { @@ -3571,7 +3571,7 @@ const styles = (theme) => ({ fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeSmall, lineHeight: variables.lineHeightSmall, - color: theme.textBrand, + color: colors.green400, }, eReceiptWaypointAddress: { @@ -3611,7 +3611,7 @@ const styles = (theme) => ({ eReceiptPanel: { ...spacing.p5, ...spacing.pb8, - backgroundColor: theme.panelBackground, + color: colors.green800, borderRadius: 20, width: 335, }, diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js index 5e06753d719a..807d14d0a7aa 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.js @@ -82,8 +82,6 @@ const darkTheme = { QRLogo: colors.green400, starDefaultBG: 'rgb(254, 228, 94)', loungeAccessOverlay: colors.blue800, - textBrand: colors.green400, - panelBackground: colors.green800, }; darkTheme.PAGE_BACKGROUND_COLORS = { From a1aa3458a0c6e4821496851262fe90d4d094b25b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 25 Sep 2023 10:22:10 +0200 Subject: [PATCH 044/284] chore: move Pusher to TS --- .../Pusher/{EventType.js => EventType.ts} | 0 .../{index.native.js => index.native.ts} | 0 .../Pusher/library/{index.js => index.ts} | 0 src/libs/Pusher/{pusher.js => pusher.ts} | 143 +++++++----------- src/types/modules/pusher.d.ts | 8 + 5 files changed, 65 insertions(+), 86 deletions(-) rename src/libs/Pusher/{EventType.js => EventType.ts} (100%) rename src/libs/Pusher/library/{index.native.js => index.native.ts} (100%) rename src/libs/Pusher/library/{index.js => index.ts} (100%) rename src/libs/Pusher/{pusher.js => pusher.ts} (77%) create mode 100644 src/types/modules/pusher.d.ts diff --git a/src/libs/Pusher/EventType.js b/src/libs/Pusher/EventType.ts similarity index 100% rename from src/libs/Pusher/EventType.js rename to src/libs/Pusher/EventType.ts diff --git a/src/libs/Pusher/library/index.native.js b/src/libs/Pusher/library/index.native.ts similarity index 100% rename from src/libs/Pusher/library/index.native.js rename to src/libs/Pusher/library/index.native.ts diff --git a/src/libs/Pusher/library/index.js b/src/libs/Pusher/library/index.ts similarity index 100% rename from src/libs/Pusher/library/index.js rename to src/libs/Pusher/library/index.ts diff --git a/src/libs/Pusher/pusher.js b/src/libs/Pusher/pusher.ts similarity index 77% rename from src/libs/Pusher/pusher.js rename to src/libs/Pusher/pusher.ts index 43fde187d00b..09fd2d9b34bc 100644 --- a/src/libs/Pusher/pusher.js +++ b/src/libs/Pusher/pusher.ts @@ -1,10 +1,32 @@ import Onyx from 'react-native-onyx'; -import _ from 'underscore'; +import {Channel, ChannelAuthorizerGenerator, Options} from 'pusher-js/with-encryption'; +import isObject from 'lodash/isObject'; import ONYXKEYS from '../../ONYXKEYS'; import Pusher from './library'; import TYPE from './EventType'; import Log from '../Log'; +type States = { + previous: string; + current: string; +}; + +type Args = { + appKey: string; + cluster: string; + authEndpoint: string; +}; + +type SocketEventCallback = (eventName: string, data?: T) => void; + +type PusherWithAuthParams = Pusher & { + config: { + auth?: { + params?: unknown; + }; + }; +}; + let shouldForceOffline = false; Onyx.connect({ key: ONYXKEYS.NETWORK, @@ -16,33 +38,23 @@ Onyx.connect({ }, }); -let socket; +let socket: PusherWithAuthParams | null; let pusherSocketID = ''; -const socketEventCallbacks = []; -let customAuthorizer; +const socketEventCallbacks: SocketEventCallback[] = []; +let customAuthorizer: ChannelAuthorizerGenerator; /** * Trigger each of the socket event callbacks with the event information - * - * @param {String} eventName - * @param {*} data */ -function callSocketEventCallbacks(eventName, data) { - _.each(socketEventCallbacks, (cb) => cb(eventName, data)); +function callSocketEventCallbacks(eventName: string, data?: T) { + socketEventCallbacks.forEach((cb) => cb(eventName, data)); } /** * Initialize our pusher lib - * - * @param {Object} args - * @param {String} args.appKey - * @param {String} args.cluster - * @param {String} args.authEndpoint - * @param {Object} [params] - * @public - * @returns {Promise} resolves when Pusher has connected + * @returns resolves when Pusher has connected */ -function init(args, params) { +function init(args: Args, params?: unknown): Promise { return new Promise((resolve) => { if (socket) { return resolve(); @@ -55,7 +67,7 @@ function init(args, params) { // } // }; - const options = { + const options: Options = { cluster: args.cluster, authEndpoint: args.authEndpoint, }; @@ -77,12 +89,13 @@ function init(args, params) { } // Listen for connection errors and log them - socket.connection.bind('error', (error) => { + // TODO: check if true + socket.connection.bind('error', (error: string) => { callSocketEventCallbacks('error', error); }); socket.connection.bind('connected', () => { - pusherSocketID = socket.connection.socket_id; + pusherSocketID = socket?.connection.socket_id ?? ''; callSocketEventCallbacks('connected'); resolve(); }); @@ -91,7 +104,7 @@ function init(args, params) { callSocketEventCallbacks('disconnected'); }); - socket.connection.bind('state_change', (states) => { + socket.connection.bind('state_change', (states: States) => { callSocketEventCallbacks('state_change', states); }); }); @@ -99,12 +112,8 @@ function init(args, params) { /** * Returns a Pusher channel for a channel name - * - * @param {String} channelName - * - * @returns {Channel} */ -function getChannel(channelName) { +function getChannel(channelName: string): Channel | undefined { if (!socket) { return; } @@ -114,27 +123,22 @@ function getChannel(channelName) { /** * Binds an event callback to a channel + eventName - * @param {Pusher.Channel} channel - * @param {String} eventName - * @param {Function} [eventCallback] - * - * @private */ -function bindEventToChannel(channel, eventName, eventCallback = () => {}) { +function bindEventToChannel(channel: Channel | undefined, eventName: string, eventCallback: (data: T) => void = () => {}) { if (!eventName) { return; } - - const chunkedDataEvents = {}; - const callback = (eventData) => { + // TODO: Create a seperate type + const chunkedDataEvents: Record = {}; + const callback = (eventData: string | Record) => { if (shouldForceOffline) { Log.info('[Pusher] Ignoring a Push event because shouldForceOffline = true'); return; } - - let data; + // TODO: create a seperate type + let data: {id?: string; chunk?: unknown; final?: boolean; index: number}; try { - data = _.isObject(eventData) ? eventData : JSON.parse(eventData); + data = isObject(eventData) ? eventData : JSON.parse(eventData); } catch (err) { Log.alert('[Pusher] Unable to parse single JSON event data from Pusher', {error: err, eventData}); return; @@ -164,7 +168,7 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}) { // Only call the event callback if we've received the last packet and we don't have any holes in the complete // packet. - if (chunkedEvent.receivedFinal && chunkedEvent.chunks.length === _.keys(chunkedEvent.chunks).length) { + if (chunkedEvent.receivedFinal && chunkedEvent.chunks.length === Object.keys(chunkedEvent.chunks).length) { try { eventCallback(JSON.parse(chunkedEvent.chunks.join(''))); } catch (err) { @@ -181,22 +185,14 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}) { } }; - channel.bind(eventName, callback); + channel?.bind(eventName, callback); } /** * Subscribe to a channel and an event - * - * @param {String} channelName - * @param {String} eventName - * @param {Function} [eventCallback] - * @param {Function} [onResubscribe] Callback to be called when reconnection happen - * - * @return {Promise} - * - * @public + * @param [onResubscribe] Callback to be called when reconnection happen */ -function subscribe(channelName, eventName, eventCallback = () => {}, onResubscribe = () => {}) { +function subscribe(channelName: string, eventName: string, eventCallback = () => {}, onResubscribe = () => {}): Promise { return new Promise((resolve, reject) => { // We cannot call subscribe() before init(). Prevent any attempt to do this on dev. if (!socket) { @@ -226,7 +222,7 @@ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscri onResubscribe(); }); - channel.bind('pusher:subscription_error', (data = {}) => { + channel.bind('pusher:subscription_error', (data: {type?: string; error?: string; status?: string} = {}) => { const {type, error, status} = data; Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', { channelName, @@ -245,12 +241,8 @@ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscri /** * Unsubscribe from a channel and optionally a specific event - * - * @param {String} channelName - * @param {String} [eventName] - * @public */ -function unsubscribe(channelName, eventName = '') { +function unsubscribe(channelName: string, eventName = '') { const channel = getChannel(channelName); if (!channel) { @@ -269,18 +261,14 @@ function unsubscribe(channelName, eventName = '') { Log.info('[Pusher] Unsubscribing from channel', false, {channelName}); channel.unbind(); - socket.unsubscribe(channelName); + socket?.unsubscribe(channelName); } } /** * Are we already in the process of subscribing to this channel? - * - * @param {String} channelName - * - * @returns {Boolean} */ -function isAlreadySubscribing(channelName) { +function isAlreadySubscribing(channelName: string): boolean { if (!socket) { return false; } @@ -291,12 +279,8 @@ function isAlreadySubscribing(channelName) { /** * Are we already subscribed to this channel? - * - * @param {String} channelName - * - * @returns {Boolean} */ -function isSubscribed(channelName) { +function isSubscribed(channelName: string): boolean { if (!socket) { return false; } @@ -307,12 +291,8 @@ function isSubscribed(channelName) { /** * Sends an event over a specific event/channel in pusher. - * - * @param {String} channelName - * @param {String} eventName - * @param {Object} payload */ -function sendEvent(channelName, eventName, payload) { +function sendEvent(channelName: string, eventName: string, payload: T) { // Check to see if we are subscribed to this channel before sending the event. Sending client events over channels // we are not subscribed too will throw errors and cause reconnection attempts. Subscriptions are not instant and // can happen later than we expect. @@ -320,15 +300,13 @@ function sendEvent(channelName, eventName, payload) { return; } - socket.send_event(eventName, payload, channelName); + socket?.send_event(eventName, payload, channelName); } /** * Register a method that will be triggered when a socket event happens (like disconnecting) - * - * @param {Function} cb */ -function registerSocketEventCallback(cb) { +function registerSocketEventCallback(cb: SocketEventCallback) { socketEventCallbacks.push(cb); } @@ -336,10 +314,8 @@ function registerSocketEventCallback(cb) { * A custom authorizer allows us to take a more fine-grained approach to * authenticating Pusher. e.g. we can handle failed attempts to authorize * with an expired authToken and retry the attempt. - * - * @param {Function} authorizer */ -function registerCustomAuthorizer(authorizer) { +function registerCustomAuthorizer(authorizer: ChannelAuthorizerGenerator) { customAuthorizer = authorizer; } @@ -371,18 +347,13 @@ function reconnect() { socket.connect(); } -/** - * @returns {String} - */ -function getPusherSocketID() { +function getPusherSocketID(): string { return pusherSocketID; } if (window) { /** * Pusher socket for debugging purposes - * - * @returns {Function} */ window.getPusherInstance = () => socket; } diff --git a/src/types/modules/pusher.d.ts b/src/types/modules/pusher.d.ts new file mode 100644 index 000000000000..b54a0508c309 --- /dev/null +++ b/src/types/modules/pusher.d.ts @@ -0,0 +1,8 @@ +import Pusher from 'pusher-js/types/src/core/pusher'; + +declare global { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions + interface Window { + getPusherInstance: () => Pusher | null; + } +} From 16f3d79c508c05ee7e979348c4cb636fb55749e1 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 25 Sep 2023 16:24:01 +0800 Subject: [PATCH 045/284] Fix missing colors import --- src/styles/styles.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/styles.js b/src/styles/styles.js index 47407cb5c2a8..7b09f7c2e8cc 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -26,6 +26,7 @@ import * as Browser from '../libs/Browser'; import cursor from './utilities/cursor'; import userSelect from './utilities/userSelect'; import textUnderline from './utilities/textUnderline'; +import colors from './colors'; // touchCallout is an iOS safari only property that controls the display of the callout information when you touch and hold a target const touchCalloutNone = Browser.isMobileSafari() ? {WebkitTouchCallout: 'none'} : {}; From 7aef1259f94efeff401093b1f18856a390f29cef Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 25 Sep 2023 16:55:29 +0800 Subject: [PATCH 046/284] Fix background color --- src/styles/styles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/styles.js b/src/styles/styles.js index 7b09f7c2e8cc..bf66e8c378fd 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3612,7 +3612,7 @@ const styles = (theme) => ({ eReceiptPanel: { ...spacing.p5, ...spacing.pb8, - color: colors.green800, + backgroundColor: colors.green800, borderRadius: 20, width: 335, }, From 00108d1aa7bf8c8ba63d662527cf13eb085b4f47 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 25 Sep 2023 16:55:52 +0800 Subject: [PATCH 047/284] Make large eReceipts scrollable --- src/components/DistanceEReceipt.js | 110 +++++++++++++++-------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index a96e8fb4c31b..115eac5b3939 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -1,5 +1,5 @@ import React from 'react'; -import {View} from 'react-native'; +import {View, ScrollView} from 'react-native'; import lodashGet from 'lodash/get'; import _ from 'underscore'; import Text from './Text'; @@ -35,61 +35,63 @@ function DistanceEReceipt({transaction}) { const waypoints = lodashGet(transaction, 'comment.waypoints', {}); return ( - - - - - - - - - {formattedTransactionAmount} - {transactionMerchant} - - - {_.map(waypoints, (waypoint, key) => { - const index = TransactionUtils.getWaypointIndex(key); - let descriptionKey = 'distance.waypointDescription.'; - if (index === 0) { - descriptionKey += 'start'; - } else if (index === _.size(waypoints) - 1) { - descriptionKey += 'finish'; - } else { - descriptionKey += 'stop'; - } - return ( - - {translate(descriptionKey)} - {waypoint.address || ''} - - ); - })} - - {translate('common.date')} - {transactionDate} + + + + + + + + + + {formattedTransactionAmount} + {transactionMerchant} + + + {_.map(waypoints, (waypoint, key) => { + const index = TransactionUtils.getWaypointIndex(key); + let descriptionKey = 'distance.waypointDescription.'; + if (index === 0) { + descriptionKey += 'start'; + } else if (index === _.size(waypoints) - 1) { + descriptionKey += 'finish'; + } else { + descriptionKey += 'stop'; + } + return ( + + {translate(descriptionKey)} + {waypoint.address || ''} + + ); + })} + + {translate('common.date')} + {transactionDate} + + + + + {translate('eReceipt.guaranteed')} - - - {translate('eReceipt.guaranteed')} - - + ); } From d2305c241ae86d1f5c4adbe592d238a2ab484456 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 25 Sep 2023 17:21:10 +0800 Subject: [PATCH 048/284] Fix center eReceipt vertically with scroll view --- src/components/DistanceEReceipt.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 115eac5b3939..03aa5bf82ce5 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -34,8 +34,8 @@ function DistanceEReceipt({transaction}) { const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const waypoints = lodashGet(transaction, 'comment.waypoints', {}); return ( - - + + Date: Mon, 25 Sep 2023 11:54:46 +0200 Subject: [PATCH 049/284] fix: created separate types --- src/libs/Pusher/pusher.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts index 09fd2d9b34bc..b795f3abcccd 100644 --- a/src/libs/Pusher/pusher.ts +++ b/src/libs/Pusher/pusher.ts @@ -17,6 +17,10 @@ type Args = { authEndpoint: string; }; +type ChunkedDataEvents = {chunks: unknown[]; receivedFinal: boolean}; + +type EventData = {id?: string; chunk?: unknown; final?: boolean; index: number}; + type SocketEventCallback = (eventName: string, data?: T) => void; type PusherWithAuthParams = Pusher & { @@ -128,15 +132,15 @@ function bindEventToChannel(channel: Channel | undefined, eventName: string, eve if (!eventName) { return; } - // TODO: Create a seperate type - const chunkedDataEvents: Record = {}; + + const chunkedDataEvents: Record = {}; const callback = (eventData: string | Record) => { if (shouldForceOffline) { Log.info('[Pusher] Ignoring a Push event because shouldForceOffline = true'); return; } - // TODO: create a seperate type - let data: {id?: string; chunk?: unknown; final?: boolean; index: number}; + + let data: EventData; try { data = isObject(eventData) ? eventData : JSON.parse(eventData); } catch (err) { From ea1b3e0afa662e862dd63cb95f42a0536c7b73a2 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 25 Sep 2023 13:41:28 +0200 Subject: [PATCH 050/284] fix: create types file --- src/libs/Pusher/library/index.native.ts | 5 +++-- src/libs/Pusher/library/index.ts | 5 +++-- src/libs/Pusher/library/types.ts | 3 +++ src/libs/Pusher/pusher.ts | 10 +++++----- 4 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 src/libs/Pusher/library/types.ts diff --git a/src/libs/Pusher/library/index.native.ts b/src/libs/Pusher/library/index.native.ts index 7b87d0c8bdfb..20f99ba0b0a5 100644 --- a/src/libs/Pusher/library/index.native.ts +++ b/src/libs/Pusher/library/index.native.ts @@ -2,6 +2,7 @@ * We use the pusher-js/react-native module to support pusher on native environments. * @see: https://github.com/pusher/pusher-js */ -import Pusher from 'pusher-js/react-native'; +import PusherNative from 'pusher-js/react-native'; +import Pusher from './types'; -export default Pusher; +export default PusherNative as unknown as Pusher; diff --git a/src/libs/Pusher/library/index.ts b/src/libs/Pusher/library/index.ts index 12cfae7df02f..f770acdec568 100644 --- a/src/libs/Pusher/library/index.ts +++ b/src/libs/Pusher/library/index.ts @@ -2,6 +2,7 @@ * We use the standard pusher-js module to support pusher on web environments. * @see: https://github.com/pusher/pusher-js */ -import Pusher from 'pusher-js/with-encryption'; +import PusherWeb from 'pusher-js/with-encryption'; +import Pusher from './types'; -export default Pusher; +export default PusherWeb as unknown as Pusher; diff --git a/src/libs/Pusher/library/types.ts b/src/libs/Pusher/library/types.ts new file mode 100644 index 000000000000..ae3ed28a9701 --- /dev/null +++ b/src/libs/Pusher/library/types.ts @@ -0,0 +1,3 @@ +import Pusher from 'pusher-js/with-encryption'; + +export default Pusher; diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts index b795f3abcccd..7b4448aa14cc 100644 --- a/src/libs/Pusher/pusher.ts +++ b/src/libs/Pusher/pusher.ts @@ -2,7 +2,7 @@ import Onyx from 'react-native-onyx'; import {Channel, ChannelAuthorizerGenerator, Options} from 'pusher-js/with-encryption'; import isObject from 'lodash/isObject'; import ONYXKEYS from '../../ONYXKEYS'; -import Pusher from './library'; +import Pusher from './library/types'; import TYPE from './EventType'; import Log from '../Log'; @@ -94,21 +94,21 @@ function init(args: Args, params?: unknown): Promise { // Listen for connection errors and log them // TODO: check if true - socket.connection.bind('error', (error: string) => { + socket?.connection.bind('error', (error: string) => { callSocketEventCallbacks('error', error); }); - socket.connection.bind('connected', () => { + socket?.connection.bind('connected', () => { pusherSocketID = socket?.connection.socket_id ?? ''; callSocketEventCallbacks('connected'); resolve(); }); - socket.connection.bind('disconnected', () => { + socket?.connection.bind('disconnected', () => { callSocketEventCallbacks('disconnected'); }); - socket.connection.bind('state_change', (states: States) => { + socket?.connection.bind('state_change', (states: States) => { callSocketEventCallbacks('state_change', states); }); }); From d60c00d80a16cd0aa294a090fc91cd0a0c1c15bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 25 Sep 2023 12:56:03 +0100 Subject: [PATCH 051/284] Improve some StyleUtils functions --- src/styles/StyleUtils.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 3c131b9645f2..0ca8ba320487 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1,4 +1,4 @@ -import {Animated, DimensionValue, PressableStateCallbackType, TextStyle, ViewStyle} from 'react-native'; +import {Animated, DimensionValue, ImageStyle, PressableStateCallbackType, TextStyle, ViewStyle} from 'react-native'; import {EdgeInsets} from 'react-native-safe-area-context'; import {ValueOf} from 'type-fest'; import CONST from '../CONST'; @@ -13,6 +13,9 @@ import positioning from './utilities/positioning'; import spacing from './utilities/spacing'; import variables from './variables'; +type AllStyles = ViewStyle | TextStyle | ImageStyle; +type ParsableStyle = AllStyles | ((state: PressableStateCallbackType) => AllStyles); + type ColorValue = ValueOf; type AvatarSizeName = ValueOf; type AvatarSizeValue = ValueOf< @@ -36,7 +39,6 @@ type ButtonSizeValue = ValueOf; type EmptyAvatarSizeName = ValueOf>; type ButtonStateName = ValueOf; type AvatarSize = {width: number}; -type ParsableStyle = ViewStyle | ((state: PressableStateCallbackType) => ViewStyle); type WorkspaceColorStyle = {backgroundColor: ColorValue; fill: ColorValue}; @@ -696,14 +698,14 @@ function getThemeBackgroundColor(bgColor: string = themeColors.appBG): string { /** * Parse styleParam and return Styles array */ -function parseStyleAsArray(styleParam: ViewStyle | ViewStyle[]): ViewStyle[] { +function parseStyleAsArray(styleParam: T | T[]): T[] { return Array.isArray(styleParam) ? styleParam : [styleParam]; } /** * Parse style function and return Styles object */ -function parseStyleFromFunction(style: ParsableStyle, state: PressableStateCallbackType): ViewStyle[] { +function parseStyleFromFunction(style: ParsableStyle, state: PressableStateCallbackType): AllStyles[] { const functionAppliedStyle = typeof style === 'function' ? style(state) : style; return parseStyleAsArray(functionAppliedStyle); } @@ -711,8 +713,8 @@ function parseStyleFromFunction(style: ParsableStyle, state: PressableStateCallb /** * Receives any number of object or array style objects and returns them all as an array */ -function combineStyles(...allStyles: Array) { - let finalStyles: ViewStyle[][] = []; +function combineStyles(...allStyles: Array): T[] { + let finalStyles: T[] = []; allStyles.forEach((style) => { finalStyles = finalStyles.concat(parseStyleAsArray(style)); }); From 327109e8d074eb09ecc3cfc24dbc14fdf23dee48 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 26 Sep 2023 09:47:22 +0800 Subject: [PATCH 052/284] Add spanish translation --- src/languages/es.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/languages/es.ts b/src/languages/es.ts index a68f33a33730..bf18c0dce2d2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2261,4 +2261,7 @@ export default { selectSuggestedAddress: 'Por favor, selecciona una dirección sugerida', }, }, + eReceipt: { + guaranteed: 'eRecibo garantizado', + }, } satisfies EnglishTranslation; From 001799d5d7ca99fd92bd82ff3f04df1ac90f75c6 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 26 Sep 2023 10:56:33 +0800 Subject: [PATCH 053/284] Align background with top of eReceipt --- src/components/DistanceEReceipt.js | 10 ++++------ src/styles/styles.js | 14 +------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 03aa5bf82ce5..74e46fe5abb5 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -37,12 +37,10 @@ function DistanceEReceipt({transaction}) { - - - + ({ color: theme.textColorfulBackground, }, - eReceiptBackgroundContainer: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - justifyContent: 'center', - alignItems: 'center', - }, - eReceiptBackground: { ...sizing.w100, + borderRadius: 20, position: 'absolute', top: 0, left: 0, - right: 0, - bottom: 0, - minHeight: 540, }, eReceiptPanel: { From 87c8457e0be08b61c5dc6c14ff433dd5b794daac Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 26 Sep 2023 11:00:44 +0800 Subject: [PATCH 054/284] Remove receipt image border for prettier loading --- src/components/DistanceEReceipt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 74e46fe5abb5..e8b4e0ecfc44 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -41,7 +41,7 @@ function DistanceEReceipt({transaction}) { style={styles.eReceiptBackground} pointerEvents="none" /> - + Date: Tue, 26 Sep 2023 12:03:29 +0700 Subject: [PATCH 055/284] hide three dot in header when request is deleted --- src/components/MoneyRequestHeader.js | 3 ++- src/libs/ReportUtils.js | 2 +- src/pages/home/ReportScreen.js | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 9db9c87c4eb1..930d6dd52de1 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -18,6 +18,7 @@ import ConfirmModal from './ConfirmModal'; import useLocalize from '../hooks/useLocalize'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import * as TransactionUtils from '../libs/TransactionUtils'; +import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import reportActionPropTypes from '../pages/home/report/reportActionPropTypes'; import transactionPropTypes from './transactionPropTypes'; import useWindowDimensions from '../hooks/useWindowDimensions'; @@ -84,7 +85,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, ); - if (isSingleTransactionView && !isDeletedParentAction) { + if (isSingleTransactionView) { headerView = ( Date: Tue, 26 Sep 2023 10:40:13 +0200 Subject: [PATCH 056/284] fix: resolve comments --- src/libs/Pusher/EventType.ts | 2 +- src/libs/Pusher/library/index.native.ts | 2 +- src/libs/Pusher/library/index.ts | 2 +- src/libs/Pusher/library/types.ts | 4 +++- src/libs/Pusher/pusher.ts | 30 +++++++++++++++---------- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/libs/Pusher/EventType.ts b/src/libs/Pusher/EventType.ts index 85ccc5e17242..89e8a0ca0260 100644 --- a/src/libs/Pusher/EventType.ts +++ b/src/libs/Pusher/EventType.ts @@ -11,4 +11,4 @@ export default { MULTIPLE_EVENT_TYPE: { ONYX_API_UPDATE: 'onyxApiUpdate', }, -}; +} as const; diff --git a/src/libs/Pusher/library/index.native.ts b/src/libs/Pusher/library/index.native.ts index 20f99ba0b0a5..2f82f7980a7f 100644 --- a/src/libs/Pusher/library/index.native.ts +++ b/src/libs/Pusher/library/index.native.ts @@ -5,4 +5,4 @@ import PusherNative from 'pusher-js/react-native'; import Pusher from './types'; -export default PusherNative as unknown as Pusher; +export default PusherNative satisfies Pusher; diff --git a/src/libs/Pusher/library/index.ts b/src/libs/Pusher/library/index.ts index f770acdec568..7e0ff9e36b6d 100644 --- a/src/libs/Pusher/library/index.ts +++ b/src/libs/Pusher/library/index.ts @@ -5,4 +5,4 @@ import PusherWeb from 'pusher-js/with-encryption'; import Pusher from './types'; -export default PusherWeb as unknown as Pusher; +export default PusherWeb satisfies Pusher; diff --git a/src/libs/Pusher/library/types.ts b/src/libs/Pusher/library/types.ts index ae3ed28a9701..bb79339a4aae 100644 --- a/src/libs/Pusher/library/types.ts +++ b/src/libs/Pusher/library/types.ts @@ -1,3 +1,5 @@ -import Pusher from 'pusher-js/with-encryption'; +import PusherClass from 'pusher-js/with-encryption'; + +type Pusher = typeof PusherClass; export default Pusher; diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts index 7b4448aa14cc..37ac046cdd7f 100644 --- a/src/libs/Pusher/pusher.ts +++ b/src/libs/Pusher/pusher.ts @@ -1,10 +1,12 @@ import Onyx from 'react-native-onyx'; import {Channel, ChannelAuthorizerGenerator, Options} from 'pusher-js/with-encryption'; import isObject from 'lodash/isObject'; +import {LiteralUnion} from 'type-fest'; import ONYXKEYS from '../../ONYXKEYS'; -import Pusher from './library/types'; +import Pusher from './library'; import TYPE from './EventType'; import Log from '../Log'; +import DeepValueOf from '../../types/utils/DeepValueOf'; type States = { previous: string; @@ -21,9 +23,11 @@ type ChunkedDataEvents = {chunks: unknown[]; receivedFinal: boolean}; type EventData = {id?: string; chunk?: unknown; final?: boolean; index: number}; -type SocketEventCallback = (eventName: string, data?: T) => void; +type SocketEventName = LiteralUnion<'error' | 'connected' | 'disconnected' | 'state_change', string>; -type PusherWithAuthParams = Pusher & { +type SocketEventCallback = (eventName: SocketEventName, data?: unknown) => void; + +type PusherWithAuthParams = InstanceType & { config: { auth?: { params?: unknown; @@ -31,6 +35,10 @@ type PusherWithAuthParams = Pusher & { }; }; +type PusherEventName = LiteralUnion, string>; + +type PusherSubscribtionErrorData = {type?: string; error?: string; status?: string}; + let shouldForceOffline = false; Onyx.connect({ key: ONYXKEYS.NETWORK, @@ -50,7 +58,7 @@ let customAuthorizer: ChannelAuthorizerGenerator; /** * Trigger each of the socket event callbacks with the event information */ -function callSocketEventCallbacks(eventName: string, data?: T) { +function callSocketEventCallbacks(eventName: SocketEventName, data?: unknown) { socketEventCallbacks.forEach((cb) => cb(eventName, data)); } @@ -81,7 +89,6 @@ function init(args: Args, params?: unknown): Promise { } socket = new Pusher(args.appKey, options); - // If we want to pass params in our requests to api.php we'll need to add it to socket.config.auth.params // as per the documentation // (https://pusher.com/docs/channels/using_channels/connection#channels-options-parameter). @@ -93,8 +100,7 @@ function init(args: Args, params?: unknown): Promise { } // Listen for connection errors and log them - // TODO: check if true - socket?.connection.bind('error', (error: string) => { + socket?.connection.bind('error', (error: unknown) => { callSocketEventCallbacks('error', error); }); @@ -128,7 +134,7 @@ function getChannel(channelName: string): Channel | undefined { /** * Binds an event callback to a channel + eventName */ -function bindEventToChannel(channel: Channel | undefined, eventName: string, eventCallback: (data: T) => void = () => {}) { +function bindEventToChannel(channel: Channel | undefined, eventName: PusherEventName, eventCallback: (data: unknown) => void = () => {}) { if (!eventName) { return; } @@ -196,7 +202,7 @@ function bindEventToChannel(channel: Channel | undefined, eventName: string, eve * Subscribe to a channel and an event * @param [onResubscribe] Callback to be called when reconnection happen */ -function subscribe(channelName: string, eventName: string, eventCallback = () => {}, onResubscribe = () => {}): Promise { +function subscribe(channelName: string, eventName: PusherEventName, eventCallback = () => {}, onResubscribe = () => {}): Promise { return new Promise((resolve, reject) => { // We cannot call subscribe() before init(). Prevent any attempt to do this on dev. if (!socket) { @@ -226,7 +232,7 @@ function subscribe(channelName: string, eventName: string, eventCallback = () => onResubscribe(); }); - channel.bind('pusher:subscription_error', (data: {type?: string; error?: string; status?: string} = {}) => { + channel.bind('pusher:subscription_error', (data: PusherSubscribtionErrorData = {}) => { const {type, error, status} = data; Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', { channelName, @@ -246,7 +252,7 @@ function subscribe(channelName: string, eventName: string, eventCallback = () => /** * Unsubscribe from a channel and optionally a specific event */ -function unsubscribe(channelName: string, eventName = '') { +function unsubscribe(channelName: string, eventName: PusherEventName = '') { const channel = getChannel(channelName); if (!channel) { @@ -296,7 +302,7 @@ function isSubscribed(channelName: string): boolean { /** * Sends an event over a specific event/channel in pusher. */ -function sendEvent(channelName: string, eventName: string, payload: T) { +function sendEvent(channelName: string, eventName: PusherEventName, payload: T) { // Check to see if we are subscribed to this channel before sending the event. Sending client events over channels // we are not subscribed too will throw errors and cause reconnection attempts. Subscriptions are not instant and // can happen later than we expect. From c8989de00f59bea6e3e541ecdf35ccf69d1c3a83 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 26 Sep 2023 13:07:13 +0200 Subject: [PATCH 057/284] fix: fixed types issues --- src/libs/Pusher/library/types.ts | 5 +++++ src/libs/Pusher/pusher.ts | 11 +++++------ src/libs/PusherConnectionManager.ts | 11 ++++++----- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/libs/Pusher/library/types.ts b/src/libs/Pusher/library/types.ts index bb79339a4aae..cc8c70fccdbb 100644 --- a/src/libs/Pusher/library/types.ts +++ b/src/libs/Pusher/library/types.ts @@ -1,5 +1,10 @@ import PusherClass from 'pusher-js/with-encryption'; +import {LiteralUnion} from 'type-fest'; type Pusher = typeof PusherClass; +type SocketEventName = LiteralUnion<'error' | 'connected' | 'disconnected' | 'state_change', string>; + export default Pusher; + +export type {SocketEventName}; diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts index 37ac046cdd7f..87a53014ecc0 100644 --- a/src/libs/Pusher/pusher.ts +++ b/src/libs/Pusher/pusher.ts @@ -7,6 +7,7 @@ import Pusher from './library'; import TYPE from './EventType'; import Log from '../Log'; import DeepValueOf from '../../types/utils/DeepValueOf'; +import {SocketEventName} from './library/types'; type States = { previous: string; @@ -23,9 +24,7 @@ type ChunkedDataEvents = {chunks: unknown[]; receivedFinal: boolean}; type EventData = {id?: string; chunk?: unknown; final?: boolean; index: number}; -type SocketEventName = LiteralUnion<'error' | 'connected' | 'disconnected' | 'state_change', string>; - -type SocketEventCallback = (eventName: SocketEventName, data?: unknown) => void; +type SocketEventCallback = (eventName: SocketEventName, data?: T) => void; type PusherWithAuthParams = InstanceType & { config: { @@ -52,13 +51,13 @@ Onyx.connect({ let socket: PusherWithAuthParams | null; let pusherSocketID = ''; -const socketEventCallbacks: SocketEventCallback[] = []; +const socketEventCallbacks: Array> = []; let customAuthorizer: ChannelAuthorizerGenerator; /** * Trigger each of the socket event callbacks with the event information */ -function callSocketEventCallbacks(eventName: SocketEventName, data?: unknown) { +function callSocketEventCallbacks(eventName: SocketEventName, data?: T) { socketEventCallbacks.forEach((cb) => cb(eventName, data)); } @@ -316,7 +315,7 @@ function sendEvent(channelName: string, eventName: PusherEventName, payload: /** * Register a method that will be triggered when a socket event happens (like disconnecting) */ -function registerSocketEventCallback(cb: SocketEventCallback) { +function registerSocketEventCallback(cb: SocketEventCallback) { socketEventCallbacks.push(cb); } diff --git a/src/libs/PusherConnectionManager.ts b/src/libs/PusherConnectionManager.ts index 4ab08d6dc760..18684157c4b3 100644 --- a/src/libs/PusherConnectionManager.ts +++ b/src/libs/PusherConnectionManager.ts @@ -1,11 +1,12 @@ import {ValueOf} from 'type-fest'; +import {ChannelAuthorizationCallback} from 'pusher-js/with-encryption'; import * as Pusher from './Pusher/pusher'; import * as Session from './actions/Session'; import Log from './Log'; import CONST from '../CONST'; +import {SocketEventName} from './Pusher/library/types'; type EventCallbackError = {type: ValueOf; data: {code: number}}; -type CustomAuthorizerChannel = {name: string}; function init() { /** @@ -14,13 +15,13 @@ function init() { * current valid token to generate the signed auth response * needed to subscribe to Pusher channels. */ - Pusher.registerCustomAuthorizer((channel: CustomAuthorizerChannel) => ({ - authorize: (socketID: string, callback: () => void) => { - Session.authenticatePusher(socketID, channel.name, callback); + Pusher.registerCustomAuthorizer((channel) => ({ + authorize: (socketId: string, callback: ChannelAuthorizationCallback) => { + Session.authenticatePusher(socketId, channel.name, callback); }, })); - Pusher.registerSocketEventCallback((eventName: string, error: EventCallbackError) => { + Pusher.registerSocketEventCallback((eventName: SocketEventName, error?: EventCallbackError) => { switch (eventName) { case 'error': { const errorType = error?.type; From 111859f784793d6429fc867598aac5b031bb1e1a Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 26 Sep 2023 13:50:20 +0200 Subject: [PATCH 058/284] fix: problem with socketEventCallbacks --- src/libs/Pusher/pusher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts index 87a53014ecc0..d0894b2faba7 100644 --- a/src/libs/Pusher/pusher.ts +++ b/src/libs/Pusher/pusher.ts @@ -316,7 +316,7 @@ function sendEvent(channelName: string, eventName: PusherEventName, payload: * Register a method that will be triggered when a socket event happens (like disconnecting) */ function registerSocketEventCallback(cb: SocketEventCallback) { - socketEventCallbacks.push(cb); + socketEventCallbacks.push(cb as SocketEventCallback); } /** From d891e7991c45ffe2bf638046e1b9beb4152d61bf Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 27 Sep 2023 11:50:22 +0800 Subject: [PATCH 059/284] Fix distance request next button while offline --- src/components/DistanceRequest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 5e9b73f2eb3a..4c732e025fac 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -297,9 +297,9 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, success style={[styles.w100, styles.mb4, styles.ph4, styles.flexShrink0]} onPress={navigateToNextPage} - isDisabled={_.size(validatedWaypoints) < 2 || hasRouteError || isLoadingRoute} + isDisabled={_.size(validatedWaypoints) < 2 || (!isOffline && (hasRouteError || isLoadingRoute))} text={translate('common.next')} - isLoading={isLoadingRoute || shouldFetchRoute} + isLoading={!isOffline && (isLoadingRoute || shouldFetchRoute)} /> ); From 183e3fed5d63c5245e7aa78ea96b972a5311b02d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 14:15:00 +0700 Subject: [PATCH 060/284] Trim the value when going back on connect bank account --- src/components/AddressSearch/index.js | 8 ++++ src/pages/ReimbursementAccount/AddressForm.js | 2 + src/pages/ReimbursementAccount/CompanyStep.js | 38 ++++++++++++++----- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 1b4200572664..20cb31be623f 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -257,6 +257,10 @@ function AddressSearch(props) { props.onInputChange(values); } + if (_.isFunction(props.onValueChange)) { + props.onValueChange(values); + } + props.onPress(values); }; @@ -337,6 +341,10 @@ function AddressSearch(props) { props.onInputChange({street: text}); } + if (_.isFunction(props.onValueChange)) { + props.onValueChange({street: text}); + } + // If the text is empty, we set displayListViewBorder to false to prevent UI flickering if (_.isEmpty(text)) { setDisplayListViewBorder(false); diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js index d8fbc0290136..47f93dd8f9cb 100644 --- a/src/pages/ReimbursementAccount/AddressForm.js +++ b/src/pages/ReimbursementAccount/AddressForm.js @@ -103,6 +103,7 @@ function AddressForm(props) { value={props.values.street} defaultValue={props.defaultValues.street} onInputChange={props.onFieldChange} + onValueChange={props.onFieldChange} errorText={props.errors.street ? props.translate('bankAccount.error.addressStreet') : ''} hint={props.translate('common.noPO')} renamedInputKeys={props.inputKeys} @@ -129,6 +130,7 @@ function AddressForm(props) { value={props.values.state} defaultValue={props.defaultValues.state || ''} onInputChange={(value) => props.onFieldChange({state: value})} + onValueChange={(value) => props.onFieldChange({state: value})} errorText={props.errors.state ? props.translate('bankAccount.error.addressState') : ''} /> diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 0ca9b1b7ea92..d3365afe8a23 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; -import React, {useMemo} from 'react'; +import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; import Str from 'expensify-common/lib/str'; import {withOnyx} from 'react-native-onyx'; @@ -54,6 +54,23 @@ const defaultProps = { }; function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaultStateForField, onBackButtonPress, translate, session, user, policyID}) { + const defaultWebsite = useMemo(() => (lodashGet(user, 'isFromPublicDomain', false) ? 'https://' : `https://www.${Str.extractEmailDomain(session.email, '')}`), [user, session]); + const [companyInfomation, setCompanyInfomation] = useState({ + street: getDefaultStateForField('addressStreet'), + city: getDefaultStateForField('addressCity'), + state: getDefaultStateForField('addressState'), + zipCode: getDefaultStateForField('addressZipCode'), + companyPhone: getDefaultStateForField('companyPhone'), + website: getDefaultStateForField('website', defaultWebsite), + }); + + const onFieldChange = (field) => { + setCompanyInfomation((prevCompanyInformation) => ({ + ...prevCompanyInformation, + ...field, + })); + }; + /** * @param {Array} fieldNames * @@ -64,8 +81,6 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul ..._.pick(reimbursementAccountDraft, ...fieldNames), }); - const defaultWebsite = useMemo(() => (lodashGet(user, 'isFromPublicDomain', false) ? 'https://' : `https://www.${Str.extractEmailDomain(session.email, '')}`), [user, session]); - /** * @param {Object} values - form input values passed by the Form component * @returns {Object} - Object containing the errors for each inputID, e.g. {inputID1: error1, inputID2: error2} @@ -173,11 +188,11 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul /> @@ -196,7 +212,8 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul containerStyles={[styles.mt4]} keyboardType={CONST.KEYBOARD_TYPE.PHONE_PAD} placeholder={translate('common.phoneNumberPlaceholder')} - defaultValue={getDefaultStateForField('companyPhone')} + value={companyInfomation.companyPhone} + onValueChange={(value) => onFieldChange({companyPhone: value})} shouldSaveDraft /> onFieldChange({website: value})} shouldSaveDraft hint={translate('common.websiteExample')} keyboardType={CONST.KEYBOARD_TYPE.URL} From 99477df8ca0ff2d52093bbdf5e96459b548988f1 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 16:49:41 +0700 Subject: [PATCH 061/284] use draft if the default value is empty --- src/pages/ReimbursementAccount/CompanyStep.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index d3365afe8a23..26cc2b49cefc 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -56,12 +56,12 @@ const defaultProps = { function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaultStateForField, onBackButtonPress, translate, session, user, policyID}) { const defaultWebsite = useMemo(() => (lodashGet(user, 'isFromPublicDomain', false) ? 'https://' : `https://www.${Str.extractEmailDomain(session.email, '')}`), [user, session]); const [companyInfomation, setCompanyInfomation] = useState({ - street: getDefaultStateForField('addressStreet'), - city: getDefaultStateForField('addressCity'), - state: getDefaultStateForField('addressState'), - zipCode: getDefaultStateForField('addressZipCode'), - companyPhone: getDefaultStateForField('companyPhone'), - website: getDefaultStateForField('website', defaultWebsite), + street: getDefaultStateForField('addressStreet') || reimbursementAccountDraft.addressStreet, + city: getDefaultStateForField('addressCity') || reimbursementAccountDraft.addressCity, + state: getDefaultStateForField('addressState') || reimbursementAccountDraft.addressState, + zipCode: getDefaultStateForField('addressZipCode') || reimbursementAccountDraft.addressZipCode, + companyPhone: getDefaultStateForField('companyPhone') || reimbursementAccountDraft.companyPhone, + website: getDefaultStateForField('website', defaultWebsite) || reimbursementAccountDraft.website, }); const onFieldChange = (field) => { From b9e14d743570ac924ebe4487b4b97ce0389f0d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 27 Sep 2023 11:52:08 +0100 Subject: [PATCH 062/284] Minor fix in theme files --- src/styles/themes/default.ts | 3 ++- src/styles/themes/light.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index 1fec07f4caab..4060f942198b 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -1,5 +1,6 @@ import SCREENS from '../../SCREENS'; import colors from '../colors'; +import type {ThemeBase} from './types'; const darkTheme = { // Figma keys @@ -91,6 +92,6 @@ const darkTheme = { [SCREENS.SETTINGS.STATUS]: colors.green700, [SCREENS.SETTINGS.ROOT]: colors.darkHighlightBackground, }, -}; +} satisfies ThemeBase; export default darkTheme; diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index f40da44d75c4..76f5435b9ef2 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -1,5 +1,6 @@ import SCREENS from '../../SCREENS'; import colors from '../colors'; +import type {ThemeDefault} from './types'; const lightTheme = { // Figma keys @@ -91,6 +92,6 @@ const lightTheme = { [SCREENS.SETTINGS.STATUS]: colors.green700, [SCREENS.SETTINGS.ROOT]: colors.lightHighlightBackground, }, -}; +} satisfies ThemeDefault; export default lightTheme; From 04e7887b3a51e72c3a16e010d7d488368e101810 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 19:12:52 +0700 Subject: [PATCH 063/284] fix typo --- src/pages/ReimbursementAccount/CompanyStep.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 26cc2b49cefc..4e3b0f0b6f92 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -55,7 +55,7 @@ const defaultProps = { function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaultStateForField, onBackButtonPress, translate, session, user, policyID}) { const defaultWebsite = useMemo(() => (lodashGet(user, 'isFromPublicDomain', false) ? 'https://' : `https://www.${Str.extractEmailDomain(session.email, '')}`), [user, session]); - const [companyInfomation, setCompanyInfomation] = useState({ + const [companyInformation, setCompanyInformation] = useState({ street: getDefaultStateForField('addressStreet') || reimbursementAccountDraft.addressStreet, city: getDefaultStateForField('addressCity') || reimbursementAccountDraft.addressCity, state: getDefaultStateForField('addressState') || reimbursementAccountDraft.addressState, @@ -65,7 +65,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul }); const onFieldChange = (field) => { - setCompanyInfomation((prevCompanyInformation) => ({ + setCompanyInformation((prevCompanyInformation) => ({ ...prevCompanyInformation, ...field, })); @@ -189,10 +189,10 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul onFieldChange({companyPhone: value})} shouldSaveDraft /> @@ -222,7 +222,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul accessibilityLabel={translate('companyStep.companyWebsite')} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} containerStyles={[styles.mt4]} - value={companyInfomation.website} + value={companyInformation.website} onValueChange={(value) => onFieldChange({website: value})} shouldSaveDraft hint={translate('common.websiteExample')} From 161abe512cc809bae6ac4fe1d0280b1409f10e7d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 21:10:26 +0700 Subject: [PATCH 064/284] save value to draft after updating data successfully --- src/components/AddressSearch/index.js | 8 ---- src/libs/actions/BankAccounts.js | 1 + .../actions/ReimbursementAccount/index.js | 1 + src/pages/ReimbursementAccount/AddressForm.js | 2 - src/pages/ReimbursementAccount/CompanyStep.js | 39 ++++++------------- .../ReimbursementAccountPage.js | 39 +++++++++++++++++++ 6 files changed, 52 insertions(+), 38 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 20cb31be623f..1b4200572664 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -257,10 +257,6 @@ function AddressSearch(props) { props.onInputChange(values); } - if (_.isFunction(props.onValueChange)) { - props.onValueChange(values); - } - props.onPress(values); }; @@ -341,10 +337,6 @@ function AddressSearch(props) { props.onInputChange({street: text}); } - if (_.isFunction(props.onValueChange)) { - props.onValueChange({street: text}); - } - // If the text is empty, we set displayListViewBorder to false to prevent UI flickering if (_.isEmpty(text)) { setDisplayListViewBorder(false); diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index b1cb09a8a5e2..3ac414a1c39a 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -79,6 +79,7 @@ function getVBBADataForOnyx() { value: { isLoading: false, errors: null, + shouldUpdateDataToDraft: true, }, }, ], diff --git a/src/libs/actions/ReimbursementAccount/index.js b/src/libs/actions/ReimbursementAccount/index.js index 49ff30e7be8e..e720053bfcd5 100644 --- a/src/libs/actions/ReimbursementAccount/index.js +++ b/src/libs/actions/ReimbursementAccount/index.js @@ -31,6 +31,7 @@ function setWorkspaceIDForReimbursementAccount(workspaceID) { */ function updateReimbursementAccountDraft(bankAccountData) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, bankAccountData); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldUpdateDataToDraft: false}); } /** diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js index 47f93dd8f9cb..d8fbc0290136 100644 --- a/src/pages/ReimbursementAccount/AddressForm.js +++ b/src/pages/ReimbursementAccount/AddressForm.js @@ -103,7 +103,6 @@ function AddressForm(props) { value={props.values.street} defaultValue={props.defaultValues.street} onInputChange={props.onFieldChange} - onValueChange={props.onFieldChange} errorText={props.errors.street ? props.translate('bankAccount.error.addressStreet') : ''} hint={props.translate('common.noPO')} renamedInputKeys={props.inputKeys} @@ -130,7 +129,6 @@ function AddressForm(props) { value={props.values.state} defaultValue={props.defaultValues.state || ''} onInputChange={(value) => props.onFieldChange({state: value})} - onValueChange={(value) => props.onFieldChange({state: value})} errorText={props.errors.state ? props.translate('bankAccount.error.addressState') : ''} /> diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 4e3b0f0b6f92..d4fdd0b1492e 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import lodashGet from 'lodash/get'; -import React, {useMemo, useState} from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import Str from 'expensify-common/lib/str'; import {withOnyx} from 'react-native-onyx'; @@ -54,23 +54,6 @@ const defaultProps = { }; function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaultStateForField, onBackButtonPress, translate, session, user, policyID}) { - const defaultWebsite = useMemo(() => (lodashGet(user, 'isFromPublicDomain', false) ? 'https://' : `https://www.${Str.extractEmailDomain(session.email, '')}`), [user, session]); - const [companyInformation, setCompanyInformation] = useState({ - street: getDefaultStateForField('addressStreet') || reimbursementAccountDraft.addressStreet, - city: getDefaultStateForField('addressCity') || reimbursementAccountDraft.addressCity, - state: getDefaultStateForField('addressState') || reimbursementAccountDraft.addressState, - zipCode: getDefaultStateForField('addressZipCode') || reimbursementAccountDraft.addressZipCode, - companyPhone: getDefaultStateForField('companyPhone') || reimbursementAccountDraft.companyPhone, - website: getDefaultStateForField('website', defaultWebsite) || reimbursementAccountDraft.website, - }); - - const onFieldChange = (field) => { - setCompanyInformation((prevCompanyInformation) => ({ - ...prevCompanyInformation, - ...field, - })); - }; - /** * @param {Array} fieldNames * @@ -81,6 +64,8 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul ..._.pick(reimbursementAccountDraft, ...fieldNames), }); + const defaultWebsite = useMemo(() => (lodashGet(user, 'isFromPublicDomain', false) ? 'https://' : `https://www.${Str.extractEmailDomain(session.email, '')}`), [user, session]); + /** * @param {Object} values - form input values passed by the Form component * @returns {Object} - Object containing the errors for each inputID, e.g. {inputID1: error1, inputID2: error2} @@ -172,6 +157,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul onSubmit={submit} scrollContextEnabled submitButtonText={translate('common.saveAndContinue')} + shouldPriorityDefaultValue style={[styles.mh5, styles.flexGrow1]} > {translate('companyStep.subtitle')} @@ -188,11 +174,11 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul /> @@ -212,8 +197,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul containerStyles={[styles.mt4]} keyboardType={CONST.KEYBOARD_TYPE.PHONE_PAD} placeholder={translate('common.phoneNumberPlaceholder')} - value={companyInformation.companyPhone} - onValueChange={(value) => onFieldChange({companyPhone: value})} + defaultValuevalue={getDefaultStateForField('companyPhone')} shouldSaveDraft /> onFieldChange({website: value})} + defaultValue={getDefaultStateForField('website', defaultWebsite)} shouldSaveDraft hint={translate('common.websiteExample')} keyboardType={CONST.KEYBOARD_TYPE.URL} diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index afcd84ffa660..5c97a4f838fc 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -158,6 +158,9 @@ class ReimbursementAccountPage extends React.Component { } const currentStepRouteParam = this.getStepToOpenFromRouteParams(); + if (this.props.reimbursementAccount.shouldUpdateDataToDraft) { + BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsOfCurrentStep(currentStepRouteParam))); + } if (currentStepRouteParam === currentStep) { // The route is showing the correct step, no need to update the route param or clear errors. return; @@ -177,6 +180,42 @@ class ReimbursementAccountPage extends React.Component { Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(this.getRouteForCurrentStep(currentStep), policyId, backTo)); } + getFieldsOfCurrentStep(currentStep) { + switch (currentStep) { + case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: + return ['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']; + case CONST.BANK_ACCOUNT.STEP.COMPANY: + return [ + 'companyName', + 'addressStreet', + 'addressZipCode', + 'addressCity', + 'addressState', + 'companyPhone', + 'website', + 'companyTaxID', + 'incorporationType', + 'incorporationDate', + 'incorporationState', + ]; + case CONST.BANK_ACCOUNT.STEP.REQUESTOR: + return ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode']; + default: + return []; + } + } + + /** + * @param {Array} fieldNames + * + * @returns {*} + */ + getBankAccountFields(fieldNames) { + return { + ..._.pick(lodashGet(this.props.reimbursementAccount, 'achData'), ...fieldNames), + }; + } + /* * Calculates the state used to show the "Continue with setup" view. If a bank account setup is already in progress and * no specific further step was passed in the url we'll show the workspace bank account reset modal if the user wishes to start over From 15ef37fc86555ce31bc63d0d09f9db41ff66a570 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 21:11:28 +0700 Subject: [PATCH 065/284] fix merge main --- src/pages/ReimbursementAccount/CompanyStep.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index d4fdd0b1492e..0ca9b1b7ea92 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -157,7 +157,6 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul onSubmit={submit} scrollContextEnabled submitButtonText={translate('common.saveAndContinue')} - shouldPriorityDefaultValue style={[styles.mh5, styles.flexGrow1]} > {translate('companyStep.subtitle')} @@ -197,7 +196,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul containerStyles={[styles.mt4]} keyboardType={CONST.KEYBOARD_TYPE.PHONE_PAD} placeholder={translate('common.phoneNumberPlaceholder')} - defaultValuevalue={getDefaultStateForField('companyPhone')} + defaultValue={getDefaultStateForField('companyPhone')} shouldSaveDraft /> Date: Wed, 27 Sep 2023 21:17:10 +0700 Subject: [PATCH 066/284] fix jest --- src/pages/ReimbursementAccount/CompanyStep.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 0ca9b1b7ea92..409af4302042 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -187,6 +187,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul }} shouldSaveDraft streetTranslationKey="common.companyAddress" + /> Date: Wed, 27 Sep 2023 21:17:47 +0700 Subject: [PATCH 067/284] fix lint --- src/pages/ReimbursementAccount/CompanyStep.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 409af4302042..0ca9b1b7ea92 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -187,7 +187,6 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul }} shouldSaveDraft streetTranslationKey="common.companyAddress" - /> Date: Wed, 27 Sep 2023 22:02:18 +0700 Subject: [PATCH 068/284] add comment --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 5c97a4f838fc..2ddef05d2378 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -158,6 +158,7 @@ class ReimbursementAccountPage extends React.Component { } const currentStepRouteParam = this.getStepToOpenFromRouteParams(); + // Update the data that is returned from back-end to draft value if (this.props.reimbursementAccount.shouldUpdateDataToDraft) { BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsOfCurrentStep(currentStepRouteParam))); } From 3c4be813712699352cf45d2d1178267a0e8fda9e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 23:27:25 +0700 Subject: [PATCH 069/284] create a const for fields of the step --- src/CONST.ts | 7 ++++++ .../ReimbursementAccountPage.js | 23 +------------------ 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index e487514f150e..c4fd7ca5716d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -161,6 +161,13 @@ const CONST = { MISSING_INCORPORATION_STATE: '402 Missing incorporationState in additionalData', MISSING_INCORPORATION_TYPE: '402 Missing incorporationType in additionalData', }, + FIELDS: { + BankAccountStep: ['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings'], + CompanyStep: ['companyName', 'addressStreet', 'addressZipCode', 'addressCity', 'addressState', 'companyPhone', 'website','companyTaxID', 'incorporationType', 'incorporationDate', 'incorporationState'], + RequestorStep: ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode'], + ACHContractStep: [], + ENABLE: [] + }, STEP: { // In the order they appear in the VBA flow BANK_ACCOUNT: 'BankAccountStep', diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 5c97a4f838fc..e229353b43d2 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -181,28 +181,7 @@ class ReimbursementAccountPage extends React.Component { } getFieldsOfCurrentStep(currentStep) { - switch (currentStep) { - case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: - return ['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']; - case CONST.BANK_ACCOUNT.STEP.COMPANY: - return [ - 'companyName', - 'addressStreet', - 'addressZipCode', - 'addressCity', - 'addressState', - 'companyPhone', - 'website', - 'companyTaxID', - 'incorporationType', - 'incorporationDate', - 'incorporationState', - ]; - case CONST.BANK_ACCOUNT.STEP.REQUESTOR: - return ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode']; - default: - return []; - } + return CONST.BANK_ACCOUNT.FIELDS[currentStep] || []; } /** From 936289ca05e0ff663161c49f6082968e3283038c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 23:31:53 +0700 Subject: [PATCH 070/284] fix lint --- src/CONST.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index c4fd7ca5716d..19582e702cb0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -163,10 +163,22 @@ const CONST = { }, FIELDS: { BankAccountStep: ['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings'], - CompanyStep: ['companyName', 'addressStreet', 'addressZipCode', 'addressCity', 'addressState', 'companyPhone', 'website','companyTaxID', 'incorporationType', 'incorporationDate', 'incorporationState'], + CompanyStep: [ + 'companyName', + 'addressStreet', + 'addressZipCode', + 'addressCity', + 'addressState', + 'companyPhone', + 'website', + 'companyTaxID', + 'incorporationType', + 'incorporationDate', + 'incorporationState', + ], RequestorStep: ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode'], ACHContractStep: [], - ENABLE: [] + ENABLE: [], }, STEP: { // In the order they appear in the VBA flow From 25c23663f165ac11f7610a09f94158dbf76b90af Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 27 Sep 2023 23:35:43 +0700 Subject: [PATCH 071/284] reuse the const --- src/pages/ReimbursementAccount/CompanyStep.js | 16 ++-------------- src/pages/ReimbursementAccount/RequestorStep.js | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 0ca9b1b7ea92..70042f903e76 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -71,19 +71,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul * @returns {Object} - Object containing the errors for each inputID, e.g. {inputID1: error1, inputID2: error2} */ const validate = (values) => { - const requiredFields = [ - 'companyName', - 'addressStreet', - 'addressZipCode', - 'addressCity', - 'addressState', - 'companyPhone', - 'website', - 'companyTaxID', - 'incorporationType', - 'incorporationDate', - 'incorporationState', - ]; + const requiredFields = CONST.BANK_ACCOUNT.FIELDS.CompanyStep; const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { @@ -124,7 +112,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul bankAccountID: lodashGet(reimbursementAccount, 'achData.bankAccountID') || 0, // Fields from BankAccount step - ...getBankAccountFields(['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']), + ...getBankAccountFields(CONST.BANK_ACCOUNT.FIELDS.BankAccountStep), // Fields from Company step ...values, diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index 53ca279c2cb2..2983062a8160 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -29,7 +29,7 @@ const propTypes = { shouldShowOnfido: PropTypes.bool.isRequired, }; -const REQUIRED_FIELDS = ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode']; +const REQUIRED_FIELDS = CONST.BANK_ACCOUNT.FIELDS.RequestorStep; const INPUT_KEYS = { firstName: 'firstName', lastName: 'lastName', From 32d4091197b6448fef44ec7cd7317f4f02c666f7 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 28 Sep 2023 17:23:03 +0800 Subject: [PATCH 072/284] Fix isDistanceRequest for optimistic transactions --- src/libs/actions/IOU.js | 9 ++++++--- src/pages/iou/DistanceRequestPage.js | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index b346900b37dc..555cfd79a2f4 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -2102,9 +2102,12 @@ function setMoneyRequestReceipt(receiptPath, receiptSource) { Onyx.merge(ONYXKEYS.IOU, {receiptPath, receiptSource, merchant: ''}); } -function createEmptyTransaction() { +function setUpDistanceTransaction() { const transactionID = NumberUtils.rand64(); - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {transactionID}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { + transactionID, + comment: {type: CONST.TRANSACTION.TYPE.CUSTOM_UNIT, customUnit: {name: CONST.CUSTOM_UNITS.NAME_DISTANCE}}, + }); Onyx.merge(ONYXKEYS.IOU, {transactionID}); } @@ -2180,7 +2183,7 @@ export { setMoneyRequestBillable, setMoneyRequestParticipants, setMoneyRequestReceipt, - createEmptyTransaction, + setUpDistanceTransaction, navigateToNextPage, replaceReceipt, }; diff --git a/src/pages/iou/DistanceRequestPage.js b/src/pages/iou/DistanceRequestPage.js index 39b068975c77..2d35b500bd51 100644 --- a/src/pages/iou/DistanceRequestPage.js +++ b/src/pages/iou/DistanceRequestPage.js @@ -49,7 +49,7 @@ function DistanceRequestPage({iou, report, route}) { if (iou.transactionID) { return; } - IOU.createEmptyTransaction(); + IOU.setUpDistanceTransaction(); }, [iou.transactionID]); return ( From 54a25c4707648f1d371d67799b0fde1064de23ec Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 28 Sep 2023 17:42:57 +0800 Subject: [PATCH 073/284] Use placeholders for offline distance eReceipts --- src/components/DistanceEReceipt.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index e8b4e0ecfc44..dc059cc1015c 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -16,6 +16,8 @@ import Icon from './Icon'; import themeColors from '../styles/themes/default'; import * as Expensicons from './Icon/Expensicons'; import EReceiptBackground from '../../assets/images/eReceipt_background.svg'; +import useNetwork from '../hooks/useNetwork'; +import PendingMapView from './MapView/PendingMapView'; const propTypes = { /** The transaction for the eReceipt */ @@ -28,9 +30,11 @@ const defaultProps = { function DistanceEReceipt({transaction}) { const {translate} = useLocalize(); + const {isOffline} = useNetwork(); const {thumbnail} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant, created: transactionDate} = ReportUtils.getTransactionDetails(transaction); - const formattedTransactionAmount = CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency); + const hasRoute = TransactionUtils.hasRoute(transaction); + const formattedTransactionAmount = hasRoute ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : translate('common.tbd'); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const waypoints = lodashGet(transaction, 'comment.waypoints', {}); return ( @@ -42,12 +46,16 @@ function DistanceEReceipt({transaction}) { pointerEvents="none" /> - + {isOffline ? ( + + ) : ( + + )} {formattedTransactionAmount} From 5c5f717ce230b1653d7bc32e7bb13b20b4b1ec5b Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 28 Sep 2023 18:16:31 +0800 Subject: [PATCH 074/284] Use withOnyx for transaction vs deprecated utils --- .../Attachments/AttachmentView/index.js | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 1b05861b581e..a08f72b9c604 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -3,7 +3,6 @@ import {View, ActivityIndicator} from 'react-native'; import _ from 'underscore'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; -import {useRoute} from '@react-navigation/native'; import styles from '../../../styles/styles'; import Icon from '../../Icon'; import * as Expensicons from '../../Icon/Expensicons'; @@ -18,10 +17,8 @@ import AttachmentViewPdf from './AttachmentViewPdf'; import addEncryptedAuthTokenToURL from '../../../libs/addEncryptedAuthTokenToURL'; import * as StyleUtils from '../../../styles/StyleUtils'; import {attachmentViewPropTypes, attachmentViewDefaultProps} from './propTypes'; -import * as ReportUtils from '../../../libs/ReportUtils'; import * as TransactionUtils from '../../../libs/TransactionUtils'; import DistanceEReceipt from '../../DistanceEReceipt'; -import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; import useNetwork from '../../../hooks/useNetwork'; const propTypes = { @@ -69,10 +66,9 @@ function AttachmentView({ isFocused, isWorkspaceAvatar, fallbackSource, + transaction, }) { const [loadComplete, setLoadComplete] = useState(false); - const currentRoute = useRoute(); - const [imageError, setImageError] = useState(false); useNetwork({onReconnect: () => setImageError(false)}); @@ -119,15 +115,7 @@ function AttachmentView({ ); } - const reportID = _.get(currentRoute, ['params', 'reportID']); - const report = ReportUtils.getReport(reportID); - - // Get the money request transaction - const parentReportAction = ReportActionsUtils.getParentReportAction(report); - const transactionID = _.get(parentReportAction, ['originalMessage', 'IOUTransactionID'], 0); - const transaction = TransactionUtils.getTransaction(transactionID); - const shouldShowEReceipt = TransactionUtils.isDistanceRequest(transaction); - if (shouldShowEReceipt) { + if (TransactionUtils.isDistanceRequest(transaction)) { return ; } @@ -185,4 +173,20 @@ AttachmentView.propTypes = propTypes; AttachmentView.defaultProps = defaultProps; AttachmentView.displayName = 'AttachmentView'; -export default compose(memo, withLocalize)(AttachmentView); +export default compose( + memo, + withLocalize, + withOnyx({ + parentReport: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, + }, + parentReportAction: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${(report.parentReportID, report.parentReportActionID)}`, + selector: (reportActions, props) => props && props.parentReport && reportActions && reportActions[props.parentReport.parentReportActionID], + canEvict: false, + }, + transaction: { + key: ({parentReportAction}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`, + }, + }), +)(AttachmentView); From 6caa7962e00d548ecb542d1737c21c5485801a08 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 28 Sep 2023 17:17:56 +0700 Subject: [PATCH 075/284] revert const --- src/CONST.ts | 19 --------------- src/pages/ReimbursementAccount/CompanyStep.js | 16 +++++++++++-- .../ReimbursementAccountPage.js | 23 ++++++++++++++++++- .../ReimbursementAccount/RequestorStep.js | 2 +- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 19582e702cb0..e487514f150e 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -161,25 +161,6 @@ const CONST = { MISSING_INCORPORATION_STATE: '402 Missing incorporationState in additionalData', MISSING_INCORPORATION_TYPE: '402 Missing incorporationType in additionalData', }, - FIELDS: { - BankAccountStep: ['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings'], - CompanyStep: [ - 'companyName', - 'addressStreet', - 'addressZipCode', - 'addressCity', - 'addressState', - 'companyPhone', - 'website', - 'companyTaxID', - 'incorporationType', - 'incorporationDate', - 'incorporationState', - ], - RequestorStep: ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode'], - ACHContractStep: [], - ENABLE: [], - }, STEP: { // In the order they appear in the VBA flow BANK_ACCOUNT: 'BankAccountStep', diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 70042f903e76..0ca9b1b7ea92 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -71,7 +71,19 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul * @returns {Object} - Object containing the errors for each inputID, e.g. {inputID1: error1, inputID2: error2} */ const validate = (values) => { - const requiredFields = CONST.BANK_ACCOUNT.FIELDS.CompanyStep; + const requiredFields = [ + 'companyName', + 'addressStreet', + 'addressZipCode', + 'addressCity', + 'addressState', + 'companyPhone', + 'website', + 'companyTaxID', + 'incorporationType', + 'incorporationDate', + 'incorporationState', + ]; const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { @@ -112,7 +124,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul bankAccountID: lodashGet(reimbursementAccount, 'achData.bankAccountID') || 0, // Fields from BankAccount step - ...getBankAccountFields(CONST.BANK_ACCOUNT.FIELDS.BankAccountStep), + ...getBankAccountFields(['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']), // Fields from Company step ...values, diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 5c61cbe1fa54..2ddef05d2378 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -182,7 +182,28 @@ class ReimbursementAccountPage extends React.Component { } getFieldsOfCurrentStep(currentStep) { - return CONST.BANK_ACCOUNT.FIELDS[currentStep] || []; + switch (currentStep) { + case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: + return ['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']; + case CONST.BANK_ACCOUNT.STEP.COMPANY: + return [ + 'companyName', + 'addressStreet', + 'addressZipCode', + 'addressCity', + 'addressState', + 'companyPhone', + 'website', + 'companyTaxID', + 'incorporationType', + 'incorporationDate', + 'incorporationState', + ]; + case CONST.BANK_ACCOUNT.STEP.REQUESTOR: + return ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode']; + default: + return []; + } } /** diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index 2983062a8160..53ca279c2cb2 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -29,7 +29,7 @@ const propTypes = { shouldShowOnfido: PropTypes.bool.isRequired, }; -const REQUIRED_FIELDS = CONST.BANK_ACCOUNT.FIELDS.RequestorStep; +const REQUIRED_FIELDS = ['firstName', 'lastName', 'dob', 'ssnLast4', 'requestorAddressStreet', 'requestorAddressCity', 'requestorAddressState', 'requestorAddressZipCode']; const INPUT_KEYS = { firstName: 'firstName', lastName: 'lastName', From db6e4dad0ed580451ca409cb5e51f59bc2f5872f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 28 Sep 2023 18:23:28 +0800 Subject: [PATCH 076/284] Update comment --- src/components/DistanceEReceipt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index dc059cc1015c..4a92079bcce5 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -20,7 +20,7 @@ import useNetwork from '../hooks/useNetwork'; import PendingMapView from './MapView/PendingMapView'; const propTypes = { - /** The transaction for the eReceipt */ + /** The transaction for the distance request */ transaction: transactionPropTypes, }; From 270eddf232361a0bcb1cdb06acf88c93dd070b18 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 28 Sep 2023 17:23:34 +0700 Subject: [PATCH 077/284] fix jest --- src/pages/ReimbursementAccount/CompanyStep.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 0ca9b1b7ea92..16ff5cbd48e1 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -84,6 +84,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul 'incorporationDate', 'incorporationState', ]; + const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { From 12f5f05c66a80f9402de07c34ff72ffbfbd20cac Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 28 Sep 2023 17:23:56 +0700 Subject: [PATCH 078/284] fix lint --- src/pages/ReimbursementAccount/CompanyStep.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 16ff5cbd48e1..0ca9b1b7ea92 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -84,7 +84,6 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul 'incorporationDate', 'incorporationState', ]; - const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields); if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { From 178093c5cc6c52caced8bd69775576c82b009e46 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 28 Sep 2023 12:54:09 +0200 Subject: [PATCH 079/284] bump onyx --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 42755b09f8b6..94292ea3f48a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,7 +90,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.94", + "react-native-onyx": "^1.0.95", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -41204,9 +41204,9 @@ } }, "node_modules/react-native-onyx": { - "version": "1.0.94", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.94.tgz", - "integrity": "sha512-Xoh9LTdoCNLQjyeLB6HkBwyf5ipkSjnETLVijSIWKnecbZS8/fQehUuGz+yEk9I0xVEn43IhmnkQ+yqQvV9vEg==", + "version": "1.0.95", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.95.tgz", + "integrity": "sha512-mGcMkFWJ0V1P04/BNy3bLw17telQR8srbweqGJVennGdvlbaHqUTTKqa37i9tR7uu1O4bDHey01UKTysb9jRZQ==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -77269,9 +77269,9 @@ } }, "react-native-onyx": { - "version": "1.0.94", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.94.tgz", - "integrity": "sha512-Xoh9LTdoCNLQjyeLB6HkBwyf5ipkSjnETLVijSIWKnecbZS8/fQehUuGz+yEk9I0xVEn43IhmnkQ+yqQvV9vEg==", + "version": "1.0.95", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.95.tgz", + "integrity": "sha512-mGcMkFWJ0V1P04/BNy3bLw17telQR8srbweqGJVennGdvlbaHqUTTKqa37i9tR7uu1O4bDHey01UKTysb9jRZQ==", "requires": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", diff --git a/package.json b/package.json index 3b88d603ba52..e0245808c79f 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.94", + "react-native-onyx": "^1.0.95", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", From a66dbd93af2d0cbcb9c7960619e0a1986306c872 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 28 Sep 2023 20:36:21 +0800 Subject: [PATCH 080/284] Update translations --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/libs/actions/App.js | 3 ++- src/pages/NewChatPage.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 32f1315c44c0..440d96fc0160 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1562,7 +1562,7 @@ export default { screenShareRequest: 'Expensify is inviting you to a screen share', }, search: { - offline: 'You appear to be offline; search results are limited.', + resultsAreLimited: 'Search results are limited.', }, genericErrorPage: { title: 'Uh-oh, something went wrong!', diff --git a/src/languages/es.ts b/src/languages/es.ts index acf7f16d7b7c..6286a4453d53 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1584,7 +1584,7 @@ export default { screenShareRequest: 'Expensify te está invitando a compartir la pantalla', }, search: { - offline: 'You appear to be offline; search results are limited.', + resultsAreLimited: 'Los resultados de búsqueda están limitados.', }, genericErrorPage: { title: '¡Uh-oh, algo salió mal!', diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index b8be35aa1919..f2017a982fb0 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -204,7 +204,8 @@ function getOnyxDataForOpenOrReconnect(isOpenApp = false) { */ function openApp() { getPolicyParamsForOpenOrReconnect().then((policyParams) => { - API.read('OpenApp', policyParams, getOnyxDataForOpenOrReconnect(true)); + const params = {enablePriorityModeFilter: true, ...policyParams}; + API.read('OpenApp', params, getOnyxDataForOpenOrReconnect(true)); }); } diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index aa6fa8dfbbc3..30cc27845144 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -208,7 +208,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i shouldShowOptions={isOptionsDataReady} shouldShowConfirmButton confirmButtonText={selectedOptions.length > 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')} - textInputAlert={isOffline ? translate('search.offline') : ''} + textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''} onConfirmSelection={createGroup} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} From 7877d5edbbcf62154e1cd72447e7992eb6b08b6f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 29 Sep 2023 09:53:26 +0800 Subject: [PATCH 081/284] Fix and simplify transaction access --- .../AttachmentCarousel/CarouselItem.js | 1 + .../Attachments/AttachmentView/index.js | 15 ++++++--------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index 3aeef8482e2d..6cecfb03fd83 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -97,6 +97,7 @@ function CarouselItem({item, isFocused, onPress}) { isFocused={isFocused} onPress={onPress} isUsedInCarousel + transactionID={item.transactionID} /> diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index a08f72b9c604..b8699764173c 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -20,6 +20,8 @@ import {attachmentViewPropTypes, attachmentViewDefaultProps} from './propTypes'; import * as TransactionUtils from '../../../libs/TransactionUtils'; import DistanceEReceipt from '../../DistanceEReceipt'; import useNetwork from '../../../hooks/useNetwork'; +import {withOnyx} from 'react-native-onyx'; +import ONYXKEYS from '../../../ONYXKEYS'; const propTypes = { ...attachmentViewPropTypes, @@ -40,6 +42,8 @@ const propTypes = { /** Denotes whether it is a workspace avatar or not */ isWorkspaceAvatar: PropTypes.bool, + + transactionID: PropTypes.number, }; const defaultProps = { @@ -49,6 +53,7 @@ const defaultProps = { onToggleKeyboard: () => {}, containerStyles: [], isWorkspaceAvatar: false, + transactionID: 0, }; function AttachmentView({ @@ -177,16 +182,8 @@ export default compose( memo, withLocalize, withOnyx({ - parentReport: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, - }, - parentReportAction: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${(report.parentReportID, report.parentReportActionID)}`, - selector: (reportActions, props) => props && props.parentReport && reportActions && reportActions[props.parentReport.parentReportActionID], - canEvict: false, - }, transaction: { - key: ({parentReportAction}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`, + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, }, }), )(AttachmentView); From b2c3f9e7705bfdbb6fc77f1811f58f835e07ff2f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 29 Sep 2023 10:00:48 +0800 Subject: [PATCH 082/284] Fix prop type --- src/components/Attachments/AttachmentView/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index b8699764173c..f6bc0160e1af 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -43,7 +43,7 @@ const propTypes = { /** Denotes whether it is a workspace avatar or not */ isWorkspaceAvatar: PropTypes.bool, - transactionID: PropTypes.number, + transactionID: PropTypes.string, }; const defaultProps = { From 6963757f5975277919ea36b3ac30a10346992062 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 29 Sep 2023 10:28:40 +0800 Subject: [PATCH 083/284] Fix online and offline transaction amount --- src/components/DistanceEReceipt.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 4a92079bcce5..abc88e1306a2 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -33,8 +33,7 @@ function DistanceEReceipt({transaction}) { const {isOffline} = useNetwork(); const {thumbnail} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant, created: transactionDate} = ReportUtils.getTransactionDetails(transaction); - const hasRoute = TransactionUtils.hasRoute(transaction); - const formattedTransactionAmount = hasRoute ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : translate('common.tbd'); + const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : translate('common.tbd'); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const waypoints = lodashGet(transaction, 'comment.waypoints', {}); return ( From 1d3a8b9d10165c3ef93d391a04ff15efc46dbfdf Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 29 Sep 2023 10:33:39 +0800 Subject: [PATCH 084/284] Load thumbnail only when image source is ready --- src/components/DistanceEReceipt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index abc88e1306a2..6e91edcca5e4 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -45,7 +45,7 @@ function DistanceEReceipt({transaction}) { pointerEvents="none" /> - {isOffline ? ( + {isOffline || !Boolean(thumbnailSource) ? ( ) : ( Date: Fri, 29 Sep 2023 12:53:36 +0800 Subject: [PATCH 085/284] Ensure waypoints are ordered properly for eReceipt --- src/components/DistanceEReceipt.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 6e91edcca5e4..3a79f33effe1 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import {View, ScrollView} from 'react-native'; import lodashGet from 'lodash/get'; import _ from 'underscore'; @@ -36,6 +36,17 @@ function DistanceEReceipt({transaction}) { const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : translate('common.tbd'); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const waypoints = lodashGet(transaction, 'comment.waypoints', {}); + const sortedWaypoints = useMemo(() => { + // The waypoint keys are sometimes out of order + return _.chain(waypoints) + .keys() + .sort((keyA, keyB) => { + return TransactionUtils.getWaypointIndex(keyA) - TransactionUtils.getWaypointIndex(keyB); + }) + .map((key) => ({[key]: waypoints[key]})) + .reduce((result, obj) => Object.assign(result, obj), {}) + .value(); + }, [waypoints]); return ( @@ -61,7 +72,7 @@ function DistanceEReceipt({transaction}) { {transactionMerchant} - {_.map(waypoints, (waypoint, key) => { + {_.map(sortedWaypoints, (waypoint, key) => { const index = TransactionUtils.getWaypointIndex(key); let descriptionKey = 'distance.waypointDescription.'; if (index === 0) { From a58a10e841ea3f064e52872b5389052815a115c7 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 29 Sep 2023 14:20:54 +0700 Subject: [PATCH 086/284] fix no update when clicking on go back --- src/libs/actions/BankAccounts.js | 11 ++++++----- src/libs/actions/ReimbursementAccount/index.js | 2 +- .../ReimbursementAccount/ReimbursementAccountPage.js | 6 ++++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index 3ac414a1c39a..e89b7cfb3ad8 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -57,10 +57,10 @@ function clearOnfidoToken() { /** * Helper method to build the Onyx data required during setup of a Verified Business Bank Account - * + * @param {String} currentStep * @returns {Object} */ -function getVBBADataForOnyx() { +function getVBBADataForOnyx(currentStep) { return { optimisticData: [ { @@ -80,6 +80,7 @@ function getVBBADataForOnyx() { isLoading: false, errors: null, shouldUpdateDataToDraft: true, + stepToUpdate: currentStep, }, }, ], @@ -223,7 +224,7 @@ function deletePaymentBankAccount(bankAccountID) { * @param {Boolean} [params.isOnfidoSetupComplete] */ function updatePersonalInformationForBankAccount(params) { - API.write('UpdatePersonalInformationForBankAccount', params, getVBBADataForOnyx()); + API.write('UpdatePersonalInformationForBankAccount', params, getVBBADataForOnyx(CONST.BANK_ACCOUNT.STEP.REQUESTOR)); } /** @@ -340,7 +341,7 @@ function openReimbursementAccountPage(stepToOpen, subStep, localCurrentStep) { * @param {String} policyID */ function updateCompanyInformationForBankAccount(bankAccount, policyID) { - API.write('UpdateCompanyInformationForBankAccount', {...bankAccount, policyID}, getVBBADataForOnyx()); + API.write('UpdateCompanyInformationForBankAccount', {...bankAccount, policyID}, getVBBADataForOnyx(CONST.BANK_ACCOUNT.STEP.COMPANY)); } /** @@ -377,7 +378,7 @@ function connectBankAccountManually(bankAccountID, accountNumber, routingNumber, routingNumber, plaidMask, }, - getVBBADataForOnyx(), + getVBBADataForOnyx(CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT), ); } diff --git a/src/libs/actions/ReimbursementAccount/index.js b/src/libs/actions/ReimbursementAccount/index.js index e720053bfcd5..51c5f4532185 100644 --- a/src/libs/actions/ReimbursementAccount/index.js +++ b/src/libs/actions/ReimbursementAccount/index.js @@ -31,7 +31,7 @@ function setWorkspaceIDForReimbursementAccount(workspaceID) { */ function updateReimbursementAccountDraft(bankAccountData) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, bankAccountData); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldUpdateDataToDraft: false}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldUpdateDataToDraft: false, stepTopUpdate: null}); } /** diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 2ddef05d2378..2457cc77136e 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -157,11 +157,13 @@ class ReimbursementAccountPage extends React.Component { return; } - const currentStepRouteParam = this.getStepToOpenFromRouteParams(); // Update the data that is returned from back-end to draft value if (this.props.reimbursementAccount.shouldUpdateDataToDraft) { - BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsOfCurrentStep(currentStepRouteParam))); + const stepToUpdate = this.props.reimbursementAccount.stepToUpdate; + BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsOfCurrentStep(stepToUpdate))); } + + const currentStepRouteParam = this.getStepToOpenFromRouteParams(); if (currentStepRouteParam === currentStep) { // The route is showing the correct step, no need to update the route param or clear errors. return; From f2b02f1ee2f1e98ded7c8300264fead9092e1fc3 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 29 Sep 2023 14:23:34 +0700 Subject: [PATCH 087/284] don't call update when unnecessary --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 2457cc77136e..2c3e43c6e8e5 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -158,8 +158,8 @@ class ReimbursementAccountPage extends React.Component { } // Update the data that is returned from back-end to draft value - if (this.props.reimbursementAccount.shouldUpdateDataToDraft) { - const stepToUpdate = this.props.reimbursementAccount.stepToUpdate; + const stepToUpdate = this.props.reimbursementAccount.stepToUpdate; + if (this.props.reimbursementAccount.shouldUpdateDataToDraft && stepToUpdate) { BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsOfCurrentStep(stepToUpdate))); } From b62df64019c81536f09915b4eb94e703ce9882ec Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 29 Sep 2023 15:51:35 +0800 Subject: [PATCH 088/284] Set the rate to TBD when it's unavailable --- src/libs/DistanceRequestUtils.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/DistanceRequestUtils.js b/src/libs/DistanceRequestUtils.js index 7744ee3d8bb1..34a5e68e21a2 100644 --- a/src/libs/DistanceRequestUtils.js +++ b/src/libs/DistanceRequestUtils.js @@ -89,8 +89,7 @@ const getDistanceMerchant = (hasRoute, distanceInMeters, unit, rate, currency, t const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); const unitString = distanceInUnits === 1 ? singularDistanceUnit : distanceUnit; - - const ratePerUnit = PolicyUtils.getUnitRateValue({rate}, toLocaleDigit); + const ratePerUnit = Boolean(rate) ? PolicyUtils.getUnitRateValue({rate}, toLocaleDigit) : translate('common.tbd'); const currencySymbol = CurrencyUtils.getCurrencySymbol(currency) || `${currency} `; return `${distanceInUnits} ${unitString} @ ${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; From 1bbde1ae4ac28c8daf5e7b604b67e6fee911962f Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 29 Sep 2023 17:17:33 +0800 Subject: [PATCH 089/284] Use debounce --- src/libs/actions/Report.js | 5 +++-- src/pages/NewChatPage.js | 2 +- src/pages/SearchPage.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 615b0ad6bfae..b05b8510b7c5 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1,6 +1,7 @@ import {InteractionManager} from 'react-native'; import _ from 'underscore'; import lodashGet from 'lodash/get'; +import lodashDebounce from 'lodash/debounce'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Onyx from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; @@ -2210,10 +2211,10 @@ function searchInServer(searchInput) { * @private * @param {string} searchInput */ -const throttledSearchInServer = _.throttle(searchInServer, 1000, {leading: false}); +const debouncedSearchInServer = lodashDebounce(searchInServer, 300, {leading: false}); export { - throttledSearchInServer, + debouncedSearchInServer, addComment, addAttachment, reconnect, diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 30cc27845144..058efcc57e71 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -171,7 +171,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i // When search term updates we will fetch any reports useEffect(() => { - Report.throttledSearchInServer(searchTerm); + Report.debouncedSearchInServer(searchTerm); }, [searchTerm]); return ( 0) { - Report.throttledSearchInServer(searchValue); + Report.debouncedSearchInServer(searchValue); } // When the user searches we will From ad952aeae1c60a986978d8e4cafc62d9968206e2 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 29 Sep 2023 17:34:35 +0800 Subject: [PATCH 090/284] Make some requested changes --- src/components/OptionsSelector/BaseOptionsSelector.js | 2 +- src/pages/NewChatPage.js | 11 +++++++---- src/pages/SearchPage.js | 7 +++++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index b183f6181009..8ab7e0fda941 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -368,7 +368,7 @@ class BaseOptionsSelector extends Component { blurOnSubmit={Boolean(this.state.allOptions.length)} spellCheck={false} /> - {this.props.textInputAlert && ( + {Boolean(this.props.textInputAlert) && ( { - Report.debouncedSearchInServer(searchTerm); - }, [searchTerm]); + const setSearchTermAndDebounceSearchInServer = useCallback((text) => { + if (text) { + Report.debouncedSearchInServer(text); + } + setSearchTerm(text); + }, []); return ( createChat(option)} - onChangeText={setSearchTerm} + onChangeText={setSearchTermAndDebounceSearchInServer} headerMessage={headerMessage} boldStyle shouldFocusOnSelectRow={!Browser.isMobile()} diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 9a81a2f3d89e..decbb10b6d62 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -20,6 +20,7 @@ import compose from '../libs/compose'; import personalDetailsPropType from './personalDetailsPropType'; import reportPropTypes from './reportPropTypes'; import Performance from '../libs/Performance'; +import networkPropTypes from '../components/networkPropTypes'; const propTypes = { /* Onyx Props */ @@ -37,6 +38,8 @@ const propTypes = { ...windowDimensionsPropTypes, ...withLocalizePropTypes, + + ...networkPropTypes, }; const defaultProps = { @@ -75,7 +78,7 @@ class SearchPage extends Component { } onChangeText(searchValue = '') { - if (searchValue.length > 0) { + if (searchValue.length) { Report.debouncedSearchInServer(searchValue); } @@ -192,7 +195,7 @@ class SearchPage extends Component { showTitleTooltip shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} - textInputAlert={this.props.network.isOffline ? this.props.translate('search.offline') : ''} + textInputAlert={this.props.network.isOffline ? `${this.props.translate('common.youAppearToBeOffline')} ${this.props.translate('search.resultsAreLimited')}` : ''} onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus From ff263377d62b023d82b8c7be3dcf93300a01aa2c Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Sat, 30 Sep 2023 12:51:55 +0200 Subject: [PATCH 091/284] fix: tests --- tests/actions/IOUTest.js | 14 +++++++------- tests/actions/ReportTest.js | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index 3df3b137bab3..63fd7a0dd78b 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -1638,7 +1638,7 @@ describe('actions/IOU', () => { expect(resultAction.message).toEqual(REPORT_ACTION.message); expect(resultAction.person).toEqual(REPORT_ACTION.person); - expect(resultAction.pendingAction).toBeNull(); + expect(resultAction.pendingAction).toBeUndefined(); await waitForBatchedUpdates(); @@ -1647,7 +1647,7 @@ describe('actions/IOU', () => { // Then check the loading state of our action const resultActionAfterUpdate = reportActions[reportActionID]; - expect(resultActionAfterUpdate.pendingAction).toBeNull(); + expect(resultActionAfterUpdate.pendingAction).toBeUndefined(); // When we attempt to delete a money request from the IOU report fetch.pause(); @@ -1818,7 +1818,7 @@ describe('actions/IOU', () => { // Then the report should have 2 actions expect(_.size(reportActions)).toBe(2); const resultActionAfter = reportActions[reportActionID]; - expect(resultActionAfter.pendingAction).toBeNull(); + expect(resultActionAfter.pendingAction).toBeUndefined(); fetch.pause(); // When deleting money request @@ -1903,7 +1903,7 @@ describe('actions/IOU', () => { expect(resultAction.message).toEqual(REPORT_ACTION.message); expect(resultAction.person).toEqual(REPORT_ACTION.person); - expect(resultAction.pendingAction).toBeNull(); + expect(resultAction.pendingAction).toBeUndefined(); await waitForBatchedUpdates(); @@ -1913,7 +1913,7 @@ describe('actions/IOU', () => { let resultActionAfterUpdate = reportActions[reportActionID]; // Verify that our action is no longer in the loading state - expect(resultActionAfterUpdate.pendingAction).toBeNull(); + expect(resultActionAfterUpdate.pendingAction).toBeUndefined(); await waitForBatchedUpdates(); @@ -1935,7 +1935,7 @@ describe('actions/IOU', () => { expect(resultAction.message).toEqual(REPORT_ACTION.message); expect(resultAction.person).toEqual(REPORT_ACTION.person); - expect(resultAction.pendingAction).toBeNull(); + expect(resultAction.pendingAction).toBeUndefined(); await waitForBatchedUpdates(); @@ -1945,7 +1945,7 @@ describe('actions/IOU', () => { resultActionAfterUpdate = reportActions[reportActionID]; // Verify that our action is no longer in the loading state - expect(resultActionAfterUpdate.pendingAction).toBeNull(); + expect(resultActionAfterUpdate.pendingAction).toBeUndefined(); fetch.pause(); // When we delete the money request diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 06d8304111cb..aab454228d03 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -92,7 +92,7 @@ describe('actions/Report', () => { expect(resultAction.message).toEqual(REPORT_ACTION.message); expect(resultAction.person).toEqual(REPORT_ACTION.person); - expect(resultAction.pendingAction).toBeNull(); + expect(resultAction.pendingAction).toBeUndefined(); // We subscribed to the Pusher channel above and now we need to simulate a reportComment action // Pusher event so we can verify that action was handled correctly and merged into the reportActions. @@ -129,7 +129,7 @@ describe('actions/Report', () => { const resultAction = reportActions[reportActionID]; // Verify that our action is no longer in the loading state - expect(resultAction.pendingAction).toBeNull(); + expect(resultAction.pendingAction).toBeUndefined(); }); }); @@ -607,7 +607,7 @@ describe('actions/Report', () => { // Expect the reaction to have null where the users reaction used to be expect(reportActionsReactions).toHaveProperty(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`); const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`]; - expect(reportActionReaction[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeNull(); + expect(reportActionReaction[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeUndefined(); }) .then(() => { reportAction = _.first(_.values(reportActions)); @@ -649,7 +649,7 @@ describe('actions/Report', () => { // Expect the reaction to have null where the users reaction used to be expect(reportActionsReactions).toHaveProperty(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`); const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`]; - expect(reportActionReaction[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeNull(); + expect(reportActionReaction[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeUndefined(); }); }); }); @@ -716,7 +716,7 @@ describe('actions/Report', () => { // Expect the reaction to have null where the users reaction used to be expect(reportActionsReactions).toHaveProperty(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${resultAction.reportActionID}`); const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${resultAction.reportActionID}`]; - expect(reportActionReaction[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeNull(); + expect(reportActionReaction[EMOJI.name].users[TEST_USER_ACCOUNT_ID]).toBeUndefined(); }); }); }); From bcd1a6552c6182946417e54c756e55834d2c76ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 2 Oct 2023 16:13:17 +0100 Subject: [PATCH 092/284] Minor fixes --- src/components/SelectionList/BaseSelectionList.js | 3 +-- src/libs/ComposerUtils/updateNumberOfLines/index.native.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 849aac2e32d6..5cde73fe6ce8 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -22,7 +22,6 @@ import Log from '../../libs/Log'; import OptionsListSkeletonView from '../OptionsListSkeletonView'; import useActiveElement from '../../hooks/useActiveElement'; import BaseListItem from './BaseListItem'; -import themeColors from '../../styles/themes/default'; import ArrowKeyFocusManager from '../ArrowKeyFocusManager'; const propTypes = { @@ -405,7 +404,7 @@ function BaseSelectionList({ onScrollBeginDrag={onScrollBeginDrag} keyExtractor={(item) => item.keyForList} extraData={focusedIndex} - indicatorStyle={'white'} + indicatorStyle="white" keyboardShouldPersistTaps="always" showsVerticalScrollIndicator={showScrollIndicator} initialNumToRender={12} diff --git a/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts b/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts index b22135b4f767..b5c28cfc79e8 100644 --- a/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts +++ b/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts @@ -8,7 +8,7 @@ import UpdateNumberOfLines from './types'; * divide by line height to get the total number of rows for the textarea. */ const updateNumberOfLines: UpdateNumberOfLines = (props, event) => { - const lineHeight = styles.textInputCompose.lineHeight; + const lineHeight = styles.textInputCompose.lineHeight ?? 0; const paddingTopAndBottom = styles.textInputComposeSpacing.paddingVertical * 2; const inputHeight = event?.nativeEvent?.contentSize?.height ?? null; if (!inputHeight) { From 6005765f66bcea9f8716200f233cd5ddcd30cf1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 2 Oct 2023 18:26:27 +0100 Subject: [PATCH 093/284] Fix findUnusedKeys to look for the correct styles file --- .github/scripts/findUnusedKeys.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/findUnusedKeys.sh b/.github/scripts/findUnusedKeys.sh index 77c3ea25326b..b1d471f632e7 100755 --- a/.github/scripts/findUnusedKeys.sh +++ b/.github/scripts/findUnusedKeys.sh @@ -6,7 +6,7 @@ LIB_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../../ && pwd)" readonly SRC_DIR="${LIB_PATH}/src" readonly STYLES_DIR="${LIB_PATH}/src/styles" -readonly STYLES_FILE="${LIB_PATH}/src/styles/styles.js" +readonly STYLES_FILE="${LIB_PATH}/src/styles/styles.ts" readonly UTILITIES_STYLES_FILE="${LIB_PATH}/src/styles/utilities" readonly STYLES_KEYS_FILE="${LIB_PATH}/scripts/style_keys_list_temp.txt" readonly UTILITY_STYLES_KEYS_FILE="${LIB_PATH}/scripts/utility_keys_list_temp.txt" @@ -348,7 +348,7 @@ echo "🔍 Looking for styles." find_utility_styles_store_prefix find_utility_usage_as_styles -# Find and store keys from styles.js +# Find and store keys from styles.ts find_styles_object_and_store_keys "$STYLES_FILE" find_styles_functions_and_store_keys "$STYLES_FILE" collect_theme_keys_from_styles "$STYLES_FILE" From 98724e9070da0843f03e561573282098a098771e Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 2 Oct 2023 10:11:26 -1000 Subject: [PATCH 094/284] Make requested changes --- src/pages/NewChatPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 9a7364df96e1..ee6ce3ce18ab 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -170,8 +170,8 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i }, [reports, personalDetails, searchTerm]); // When search term updates we will fetch any reports - const setSearchTermAndDebounceSearchInServer = useCallback((text) => { - if (text) { + const setSearchTermAndDebounceSearchInServer = useCallback((text = '') => { + if (text.length) { Report.debouncedSearchInServer(text); } setSearchTerm(text); From 25d66cc3fbf2e97dbc289f13fa0493fca45818cd Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 2 Oct 2023 17:07:21 -0700 Subject: [PATCH 095/284] Fix crash / prop types with default receipt source --- .../Attachments/AttachmentCarousel/CarouselItem.js | 5 +++-- src/components/Attachments/AttachmentView/index.js | 2 +- src/components/Attachments/propTypes.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index 6cecfb03fd83..2a97b3327e9c 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -10,6 +10,7 @@ import Button from '../../Button'; import AttachmentView from '../AttachmentView'; import SafeAreaConsumer from '../../SafeAreaConsumer'; import ReportAttachmentsContext from '../../../pages/home/report/ReportAttachmentsContext'; +import * as AttachmentsPropTypes from '../propTypes'; const propTypes = { /** Attachment required information such as the source and file name */ @@ -20,8 +21,8 @@ const propTypes = { /** Whether source URL requires authentication */ isAuthTokenRequired: PropTypes.bool, - /** The source (URL) of the attachment */ - source: PropTypes.string, + /** URL to full-sized attachment or SVG function */ + source: AttachmentsPropTypes.attachmentSourcePropType.isRequired, /** Additional information about the attachment file */ file: PropTypes.shape({ diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index f6bc0160e1af..63111b75a17f 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -101,7 +101,7 @@ function AttachmentView({ // Check both source and file.name since PDFs dragged into the the text field // will appear with a source that is a blob - if (Str.isPDF(source) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { + if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; return ( diff --git a/src/components/Attachments/propTypes.js b/src/components/Attachments/propTypes.js index b3f0af6ab217..97717a1e51da 100644 --- a/src/components/Attachments/propTypes.js +++ b/src/components/Attachments/propTypes.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -const attachmentSourcePropType = PropTypes.oneOfType([PropTypes.string, PropTypes.func]); +const attachmentSourcePropType = PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.number]); const attachmentFilePropType = PropTypes.shape({ name: PropTypes.string, }); From 3ab1772ca5522ef10aa79cff5860a2d05c0594c9 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 2 Oct 2023 14:35:59 -1000 Subject: [PATCH 096/284] add missing useCallback --- src/pages/NewChatPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index ee6ce3ce18ab..84222d1e26ac 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {useState, useEffect, useMemo} from 'react'; +import React, {useState, useEffect, useMemo, useCallback} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; From 97e5bb13e862a71771c1ee2d5a21eaf32a5f474f Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 2 Oct 2023 17:45:45 -1000 Subject: [PATCH 097/284] Change UX so we are showing loading row below options instead of above --- src/components/OptionsList/BaseOptionsList.js | 14 ++++++++++++-- src/components/OptionsList/optionsListPropTypes.js | 4 ++++ .../OptionsSelector/BaseOptionsSelector.js | 8 +------- src/pages/SearchPage.js | 9 ++++++++- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 5a40c28a86c9..25965ef5c54c 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -10,6 +10,7 @@ import Text from '../Text'; import {propTypes as optionsListPropTypes, defaultProps as optionsListDefaultProps} from './optionsListPropTypes'; import OptionsListSkeletonView from '../OptionsListSkeletonView'; import usePrevious from '../../hooks/usePrevious'; +import OptionsListSkeletonRow from '../OptionsListSkeletonRow'; const propTypes = { /** Determines whether the keyboard gets dismissed in response to a drag */ @@ -65,6 +66,7 @@ function BaseOptionsList({ isDisabled, innerRef, isRowMultilineSupported, + shouldShowHeaderMessage, }) { const flattenedData = useRef(); const previousSections = usePrevious(sections); @@ -189,6 +191,15 @@ function BaseOptionsList({ return option.name === item.searchText; }); + if (item.loadingRow) { + return ( + + ); + } + return ( ; }; - return ( {isLoading ? ( ) : ( <> - {headerMessage ? ( + {shouldShowHeaderMessage && headerMessage ? ( {headerMessage} diff --git a/src/components/OptionsList/optionsListPropTypes.js b/src/components/OptionsList/optionsListPropTypes.js index a2479c878041..967f30d44119 100644 --- a/src/components/OptionsList/optionsListPropTypes.js +++ b/src/components/OptionsList/optionsListPropTypes.js @@ -84,6 +84,9 @@ const propTypes = { /** Whether to wrap large text up to 2 lines */ isRowMultilineSupported: PropTypes.bool, + + /** Whether to show the header message below the input */ + shouldShowHeaderMessage: PropTypes.bool, }; const defaultProps = { @@ -109,6 +112,7 @@ const defaultProps = { shouldDisableRowInnerPadding: false, showScrollIndicator: false, isRowMultilineSupported: false, + shouldShowHeaderMessage: true, }; export {propTypes, defaultProps}; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index bc545b7c0ebf..b6ee91e23454 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -17,7 +17,6 @@ import {propTypes as optionsSelectorPropTypes, defaultProps as optionsSelectorDe import setSelection from '../../libs/setSelection'; import compose from '../../libs/compose'; import getPlatform from '../../libs/getPlatform'; -import OptionsListSkeletonRow from '../OptionsListSkeletonRow'; import FormHelpMessage from '../FormHelpMessage'; const propTypes = { @@ -394,12 +393,6 @@ class BaseOptionsSelector extends Component { ); const optionsList = ( <> - {this.props.shouldShowLoader && ( - - )} (this.list = el)} optionHoveredStyle={this.props.optionHoveredStyle} @@ -432,6 +425,7 @@ class BaseOptionsSelector extends Component { isLoading={!this.props.shouldShowOptions} showScrollIndicator={this.props.showScrollIndicator} isRowMultilineSupported={this.props.isRowMultilineSupported} + shouldShowHeaderMessage={this.props.shouldShowHeaderMessage} /> ); diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index decbb10b6d62..318294517a97 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -121,6 +121,13 @@ class SearchPage extends Component { }); } + if (this.props.isSearchingForReports) { + sections.push({ + data: [{loadingRow: true}], + shouldShow: true, + indexOffset, + }) + } return sections; } @@ -199,7 +206,7 @@ class SearchPage extends Component { onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus - shouldShowLoader={this.props.isSearchingForReports} + shouldShowHeaderMessage={!this.props.isSearchingForReports} /> From c0239e2ccfb8a57f8c114024ca77516ee5c97c10 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Tue, 3 Oct 2023 12:22:34 +0200 Subject: [PATCH 098/284] added a new condition to handle inner function with returning object in a new line --- .github/scripts/findUnusedKeys.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/scripts/findUnusedKeys.sh b/.github/scripts/findUnusedKeys.sh index b1d471f632e7..1411fffc8389 100755 --- a/.github/scripts/findUnusedKeys.sh +++ b/.github/scripts/findUnusedKeys.sh @@ -210,7 +210,12 @@ find_theme_style_and_store_keys() { fi # Check if we are inside an arrow function - if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ || "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + if [[ "$line" =~ ^[[:space:]]*([a-zA-Zgv 0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ || "$line" =~ ^[[:space:]]*([a-zA-Zgv 0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + inside_arrow_function=true + continue + fi + + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then inside_arrow_function=true continue fi From 0de5e86f97edc2f65c2c07ae90ab0660a20dd699 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 3 Oct 2023 17:38:41 +0700 Subject: [PATCH 099/284] add default value for param --- src/libs/actions/BankAccounts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index e89b7cfb3ad8..a90e4d066e95 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -60,7 +60,7 @@ function clearOnfidoToken() { * @param {String} currentStep * @returns {Object} */ -function getVBBADataForOnyx(currentStep) { +function getVBBADataForOnyx(currentStep = undefined) { return { optimisticData: [ { From b4bfd76fbaab87253809f3e707108731ba804516 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 3 Oct 2023 17:39:14 +0700 Subject: [PATCH 100/284] add check for shouldUpdateDataToDraft --- src/libs/actions/BankAccounts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index a90e4d066e95..3e2b064ec48e 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -57,7 +57,7 @@ function clearOnfidoToken() { /** * Helper method to build the Onyx data required during setup of a Verified Business Bank Account - * @param {String} currentStep + * @param {String | undefined} currentStep * @returns {Object} */ function getVBBADataForOnyx(currentStep = undefined) { @@ -79,7 +79,7 @@ function getVBBADataForOnyx(currentStep = undefined) { value: { isLoading: false, errors: null, - shouldUpdateDataToDraft: true, + shouldUpdateDataToDraft: !!currentStep, stepToUpdate: currentStep, }, }, From 1c1dd14f79f302d98c411e729d233282ea919244 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 3 Oct 2023 18:07:35 +0700 Subject: [PATCH 101/284] rename variable --- src/libs/actions/BankAccounts.js | 3 +-- src/libs/actions/ReimbursementAccount/index.js | 2 +- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index 3e2b064ec48e..e7d9816292c3 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -79,8 +79,7 @@ function getVBBADataForOnyx(currentStep = undefined) { value: { isLoading: false, errors: null, - shouldUpdateDataToDraft: !!currentStep, - stepToUpdate: currentStep, + stepToUpdateToDraft: currentStep, }, }, ], diff --git a/src/libs/actions/ReimbursementAccount/index.js b/src/libs/actions/ReimbursementAccount/index.js index 51c5f4532185..5598ecb96503 100644 --- a/src/libs/actions/ReimbursementAccount/index.js +++ b/src/libs/actions/ReimbursementAccount/index.js @@ -31,7 +31,7 @@ function setWorkspaceIDForReimbursementAccount(workspaceID) { */ function updateReimbursementAccountDraft(bankAccountData) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, bankAccountData); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldUpdateDataToDraft: false, stepTopUpdate: null}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldUpdateDataToDraft: false, stepToUpdateToDraft: undefined}); } /** diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 2c3e43c6e8e5..f2572ada51be 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -158,8 +158,8 @@ class ReimbursementAccountPage extends React.Component { } // Update the data that is returned from back-end to draft value - const stepToUpdate = this.props.reimbursementAccount.stepToUpdate; - if (this.props.reimbursementAccount.shouldUpdateDataToDraft && stepToUpdate) { + const stepToUpdateToDraft = this.props.reimbursementAccount.stepToUpdate; + if (stepToUpdateToDraft) { BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsOfCurrentStep(stepToUpdate))); } From 401e05d46d76d21999447e14f59490c0c9ae49df Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 3 Oct 2023 18:20:10 +0700 Subject: [PATCH 102/284] fix lint --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index f2572ada51be..4e45c72f676f 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -160,7 +160,7 @@ class ReimbursementAccountPage extends React.Component { // Update the data that is returned from back-end to draft value const stepToUpdateToDraft = this.props.reimbursementAccount.stepToUpdate; if (stepToUpdateToDraft) { - BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsOfCurrentStep(stepToUpdate))); + BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsOfCurrentStep(stepToUpdateToDraft))); } const currentStepRouteParam = this.getStepToOpenFromRouteParams(); From cc35473ec3a3bdd8d85667eadc0f7f8e0da7394d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 3 Oct 2023 18:32:37 +0700 Subject: [PATCH 103/284] remove unuse field --- src/libs/actions/ReimbursementAccount/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/ReimbursementAccount/index.js b/src/libs/actions/ReimbursementAccount/index.js index 5598ecb96503..4471d19c8151 100644 --- a/src/libs/actions/ReimbursementAccount/index.js +++ b/src/libs/actions/ReimbursementAccount/index.js @@ -31,7 +31,7 @@ function setWorkspaceIDForReimbursementAccount(workspaceID) { */ function updateReimbursementAccountDraft(bankAccountData) { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, bankAccountData); - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {shouldUpdateDataToDraft: false, stepToUpdateToDraft: undefined}); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {stepToUpdateToDraft: undefined}); } /** From 03144804461a8605a56546ca5c3faa756fd53971 Mon Sep 17 00:00:00 2001 From: dukenv0307 <129500732+dukenv0307@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:37:39 +0700 Subject: [PATCH 104/284] Update src/pages/ReimbursementAccount/ReimbursementAccountPage.js Co-authored-by: Olly --- src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 4e45c72f676f..f09963129bb1 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -158,7 +158,7 @@ class ReimbursementAccountPage extends React.Component { } // Update the data that is returned from back-end to draft value - const stepToUpdateToDraft = this.props.reimbursementAccount.stepToUpdate; + const stepToUpdateToDraft = this.props.reimbursementAccount.stepToUpdateToDraft; if (stepToUpdateToDraft) { BankAccounts.updateReimbursementAccountDraft(this.getBankAccountFields(this.getFieldsOfCurrentStep(stepToUpdateToDraft))); } From 2d653b80cd36969ef970479f1d00fce11d9c884d Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 3 Oct 2023 08:06:14 -0700 Subject: [PATCH 105/284] Fix eReceipt background on Android --- assets/images/eReceipt_background.svg | 32 +++++++++++++-------------- src/styles/styles.js | 1 + 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/assets/images/eReceipt_background.svg b/assets/images/eReceipt_background.svg index 257489bd6fdd..5070ed3b2f24 100644 --- a/assets/images/eReceipt_background.svg +++ b/assets/images/eReceipt_background.svg @@ -1,24 +1,24 @@ + viewBox="0 0 335 540" style="enable-background:new 0 0 335 540;" xml:space="preserve"> - + - + - + - - + + - + diff --git a/src/styles/styles.js b/src/styles/styles.js index 40dfa19d5448..6605b6c0ce53 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3280,6 +3280,7 @@ const styles = (theme) => ({ position: 'absolute', top: 0, left: 0, + height: 540, }, eReceiptPanel: { From b9dca4c3c07fb40451547fa4e3c38a37ddf7ccef Mon Sep 17 00:00:00 2001 From: laurenreidexpensify <62073721+laurenreidexpensify@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:02:06 +0100 Subject: [PATCH 106/284] Create removing-users.md --- .../removing-users.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docs/articles/expensify-classic/manage-employees-and-report-approvals/removing-users.md diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/removing-users.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/removing-users.md new file mode 100644 index 000000000000..0db950680aa8 --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/removing-users.md @@ -0,0 +1,34 @@ +--- +title: Coming Soon +description: Coming Soon +--- + +Removing a member from a workspace disables their ability to use the workspace. Please note that it does not delete their account or deactivate the Expensify Card. + +## How to Remove a Workspace Member +1. Important: Make sure the employee has submitted all Draft reports and the reports have been approved, reimbursed, etc. +2. Go to Settings > Workspaces > Group > [Workspace Name] > Members > Workspace Members +3. Select the member you'd like to remove and click the **Remove** button at the top of the Members table. +4. If this member was an approver, make sure that reports are not routing to them in the workflow. + +# FAQ + +## Will reports from this member on this workspace still be available? +Yes, as long as the reports have been submitted. You can navigate to the Reports page and enter the member's email in the search field to find them. However, Draft reports will be removed from the workspace, so these will no longer be visible to the Workspace Admin. + +## Can members still access their reports on a workspace after they have been removed? +Yes. Any report that has been approved will now show the workspace as “(not shared)” in their account. If it is a Draft Report they will still be able to edit it and add it to a new workspace. If the report is Approved or Reimbursed they will not be able to edit it further. + +## Who can remove members from a workspace? +Only Workspace Admins. It is not possible for a member to add or remove themselves from a workspace. It is not possible for a Domain Admin who is not also a Workspace Admin to remove a member from a workspace. + +## How do I remove a member from a workspace if I am seeing an error message? +If a member is a **preferred exporter, billing owner, report approver** or has **processing reports**, to remove them the workspace you will first need to: + +* **Preferred Exporter** - go to Settings > Workspaces > Group > [Workspace Name] > Connections > Configure and select a different Workspace Admin in the dropdown for **Preferred Exporter**. +* **Billing Owner** - take over billing on the Settings > Workspaces > Group > [Workspace Name] > Overview page. +* **Processing reports** - approve or reject the member’s reports on your Reports page. +* **Approval Workflow** - remove them as a workflow approver on your Settings > Workspaces > Group > [Workspace Name] > Members > Approval Mode > page by changing the "**Submit reports to**" field. + +## How do I remove a user completely from a company account? +If you have a Control Workspace and have Domain Control enabled, you will need to remove them from the domain to delete members' accounts entirely and deactivate the Expensify Card. From af3fec77c3ee39c185aad5a39ef2cde7141a0950 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 10:48:03 -1000 Subject: [PATCH 107/284] Remove unneeded changes. Fix propTypes --- src/components/OptionsList/BaseOptionsList.js | 1 + .../OptionsSelector/BaseOptionsSelector.js | 68 +++++++++---------- src/pages/SearchPage.js | 3 +- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 25965ef5c54c..2521962756a5 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -247,6 +247,7 @@ function BaseOptionsList({ return ; }; + return ( {isLoading ? ( diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index b6ee91e23454..9d2ec7d493c6 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -392,42 +392,40 @@ class BaseOptionsSelector extends Component { ); const optionsList = ( - <> - (this.list = el)} - optionHoveredStyle={this.props.optionHoveredStyle} - onSelectRow={this.props.onSelectRow ? this.selectRow : undefined} - sections={this.props.sections} - focusedIndex={this.state.focusedIndex} - selectedOptions={this.props.selectedOptions} - canSelectMultipleOptions={this.props.canSelectMultipleOptions} - shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton} - multipleOptionSelectorButtonText={this.props.multipleOptionSelectorButtonText} - onAddToSelection={this.addToSelection} - hideSectionHeaders={this.props.hideSectionHeaders} - headerMessage={this.props.headerMessage} - boldStyle={this.props.boldStyle} - showTitleTooltip={this.props.showTitleTooltip} - isDisabled={this.props.isDisabled} - shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator} - highlightSelectedOptions={this.props.highlightSelectedOptions} - onLayout={() => { - if (this.props.selectedOptions.length === 0) { - this.scrollToIndex(this.state.focusedIndex, false); + (this.list = el)} + optionHoveredStyle={this.props.optionHoveredStyle} + onSelectRow={this.props.onSelectRow ? this.selectRow : undefined} + sections={this.props.sections} + focusedIndex={this.state.focusedIndex} + selectedOptions={this.props.selectedOptions} + canSelectMultipleOptions={this.props.canSelectMultipleOptions} + shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton} + multipleOptionSelectorButtonText={this.props.multipleOptionSelectorButtonText} + onAddToSelection={this.addToSelection} + hideSectionHeaders={this.props.hideSectionHeaders} + headerMessage={this.props.headerMessage} + boldStyle={this.props.boldStyle} + showTitleTooltip={this.props.showTitleTooltip} + isDisabled={this.props.isDisabled} + shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator} + highlightSelectedOptions={this.props.highlightSelectedOptions} + onLayout={() => { + if (this.props.selectedOptions.length === 0) { + this.scrollToIndex(this.state.focusedIndex, false); + } + if (this.props.onLayout) { + this.props.onLayout(); } - if (this.props.onLayout) { - this.props.onLayout(); - } - }} - contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} - listContainerStyles={this.props.listContainerStyles} - listStyles={this.props.listStyles} - isLoading={!this.props.shouldShowOptions} - showScrollIndicator={this.props.showScrollIndicator} - isRowMultilineSupported={this.props.isRowMultilineSupported} - shouldShowHeaderMessage={this.props.shouldShowHeaderMessage} - /> - + }} + contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} + listContainerStyles={this.props.listContainerStyles} + listStyles={this.props.listStyles} + isLoading={!this.props.shouldShowOptions} + showScrollIndicator={this.props.showScrollIndicator} + isRowMultilineSupported={this.props.isRowMultilineSupported} + shouldShowHeaderMessage={this.props.shouldShowHeaderMessage} + /> ); return ( Date: Tue, 3 Oct 2023 10:49:18 -1000 Subject: [PATCH 108/284] Undo bad whitespace change --- src/components/OptionsSelector/BaseOptionsSelector.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 9d2ec7d493c6..415600397e74 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -415,9 +415,9 @@ class BaseOptionsSelector extends Component { this.scrollToIndex(this.state.focusedIndex, false); } if (this.props.onLayout) { - this.props.onLayout(); - } - }} + this.props.onLayout(); + } + }} contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} listContainerStyles={this.props.listContainerStyles} listStyles={this.props.listStyles} From 06531cb188c3ef142883fc1fc48de4fe457bc190 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 10:49:44 -1000 Subject: [PATCH 109/284] undo another whitespace change --- src/components/OptionsSelector/BaseOptionsSelector.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 415600397e74..0daf1d5e6d48 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -414,6 +414,7 @@ class BaseOptionsSelector extends Component { if (this.props.selectedOptions.length === 0) { this.scrollToIndex(this.state.focusedIndex, false); } + if (this.props.onLayout) { this.props.onLayout(); } From 95d436b7b98b83c2dc47bb9c518838b306189b50 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 12:51:42 -1000 Subject: [PATCH 110/284] Fix UI race issue with No results found --- src/libs/actions/Report.js | 25 +++++++++++++++---------- src/pages/NewChatPage.js | 6 +++--- src/pages/SearchPage.js | 6 +++--- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 0dc40baabe84..659479253ae5 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2172,9 +2172,10 @@ function clearPrivateNotesError(reportID, accountID) { * @private * @param {string} searchInput */ -function searchInServer(searchInput) { +function searchForReports(searchInput) { // We do not try to make this request while offline because it sets a loading indicator optimistically if (isNetworkOffline) { + Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false); return; } @@ -2182,13 +2183,6 @@ function searchInServer(searchInput) { 'SearchForReports', {searchInput}, { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - value: true, - }, - ], successData: [ { onyxMethod: Onyx.METHOD.MERGE, @@ -2211,10 +2205,21 @@ function searchInServer(searchInput) { * @private * @param {string} searchInput */ -const debouncedSearchInServer = lodashDebounce(searchInServer, 300, {leading: false}); +const debouncedSearchInServer = lodashDebounce(searchForReports, 300, {leading: false}); + +/** + * @param {string} searchInput + */ +function searchInServer(searchInput) { + // Why not set this in optimistic data? It won't run until the API request happens and while the API request is debounced + // we want to show the loading state right away. Otherwise, we will see a flashing UI where the client options are sorted and + // tell the user there are no options, then we start searching, and tell them there are no options again. + Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, true); + debouncedSearchInServer(searchInput); +} export { - debouncedSearchInServer, + searchInServer, addComment, addAttachment, reconnect, diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 84222d1e26ac..2b34a077fb0b 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -170,9 +170,9 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i }, [reports, personalDetails, searchTerm]); // When search term updates we will fetch any reports - const setSearchTermAndDebounceSearchInServer = useCallback((text = '') => { + const setSearchTermAndSearchInServer = useCallback((text = '') => { if (text.length) { - Report.debouncedSearchInServer(text); + Report.searchInServer(text); } setSearchTerm(text); }, []); @@ -204,7 +204,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i selectedOptions={selectedOptions} value={searchTerm} onSelectRow={(option) => createChat(option)} - onChangeText={setSearchTermAndDebounceSearchInServer} + onChangeText={setSearchTermAndSearchInServer} headerMessage={headerMessage} boldStyle shouldFocusOnSelectRow={!Browser.isMobile()} diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 8ee9aa8c3f57..f57f86c50189 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import React, {Component} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; +import Onyx, {withOnyx} from 'react-native-onyx'; import OptionsSelector from '../components/OptionsSelector'; import * as OptionsListUtils from '../libs/OptionsListUtils'; import * as ReportUtils from '../libs/ReportUtils'; @@ -80,7 +80,7 @@ class SearchPage extends Component { onChangeText(searchValue = '') { if (searchValue.length) { - Report.debouncedSearchInServer(searchValue); + Report.searchInServer(searchValue); } // When the user searches we will @@ -127,7 +127,7 @@ class SearchPage extends Component { data: [{loadingRow: true}], shouldShow: true, indexOffset, - }) + }); } return sections; } From 3d1071046b52f07eab742b6f8ed031a5b75a2260 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 13:20:53 -1000 Subject: [PATCH 111/284] Add loadingRow to NewChat page --- src/pages/NewChatPage.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 2b34a077fb0b..a58748aa7546 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -104,8 +104,16 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i }); } + if (isSearchingForReports) { + sectionsList.push({ + data: [{loadingRow: true}], + shouldShow: true, + indexOffset, + }); + } + return sectionsList; - }, [translate, filteredPersonalDetails, filteredRecentReports, filteredUserToInvite, maxParticipantsReached, selectedOptions]); + }, [translate, filteredPersonalDetails, filteredRecentReports, filteredUserToInvite, maxParticipantsReached, selectedOptions, isSearchingForReports]); /** * Removes a selected option from list if already selected. If not already selected add this option to the list. @@ -215,7 +223,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i onConfirmSelection={createGroup} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} - shouldShowLoader={isSearchingForReports} + shouldShowHeaderMessage={!isSearchingForReports} /> From 1e86e514f8b9cba795398562863fce8fe22ea99c Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 3 Oct 2023 16:27:17 -0700 Subject: [PATCH 112/284] Fix linter --- .../AttachmentCarousel/CarouselItem.js | 3 +++ .../Attachments/AttachmentView/index.js | 4 +++- src/components/DistanceEReceipt.js | 24 +++++++++---------- src/libs/DistanceRequestUtils.js | 2 +- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index 2a97b3327e9c..096b6d60d428 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -32,6 +32,9 @@ const propTypes = { /** Whether the attachment has been flagged */ hasBeenFlagged: PropTypes.bool, + + /** The id of the transaction related to the attachment */ + transactionID: PropTypes.string, }).isRequired, /** Whether the attachment is currently being viewed in the carousel */ diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 63111b75a17f..8d88efa06714 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -3,6 +3,7 @@ import {View, ActivityIndicator} from 'react-native'; import _ from 'underscore'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; +import {withOnyx} from 'react-native-onyx'; import styles from '../../../styles/styles'; import Icon from '../../Icon'; import * as Expensicons from '../../Icon/Expensicons'; @@ -20,7 +21,6 @@ import {attachmentViewPropTypes, attachmentViewDefaultProps} from './propTypes'; import * as TransactionUtils from '../../../libs/TransactionUtils'; import DistanceEReceipt from '../../DistanceEReceipt'; import useNetwork from '../../../hooks/useNetwork'; -import {withOnyx} from 'react-native-onyx'; import ONYXKEYS from '../../../ONYXKEYS'; const propTypes = { @@ -43,6 +43,8 @@ const propTypes = { /** Denotes whether it is a workspace avatar or not */ isWorkspaceAvatar: PropTypes.bool, + /** The id of the transaction related to the attachment */ + // eslint-disable-next-line react/no-unused-prop-types transactionID: PropTypes.string, }; diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 3a79f33effe1..c461db397b3b 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -36,17 +36,17 @@ function DistanceEReceipt({transaction}) { const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : translate('common.tbd'); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const waypoints = lodashGet(transaction, 'comment.waypoints', {}); - const sortedWaypoints = useMemo(() => { - // The waypoint keys are sometimes out of order - return _.chain(waypoints) - .keys() - .sort((keyA, keyB) => { - return TransactionUtils.getWaypointIndex(keyA) - TransactionUtils.getWaypointIndex(keyB); - }) - .map((key) => ({[key]: waypoints[key]})) - .reduce((result, obj) => Object.assign(result, obj), {}) - .value(); - }, [waypoints]); + const sortedWaypoints = useMemo( + () => + // The waypoint keys are sometimes out of order + _.chain(waypoints) + .keys() + .sort((keyA, keyB) => TransactionUtils.getWaypointIndex(keyA) - TransactionUtils.getWaypointIndex(keyB)) + .map((key) => ({[key]: waypoints[key]})) + .reduce((result, obj) => _.assign(result, obj), {}) + .value(), + [waypoints], + ); return ( @@ -56,7 +56,7 @@ function DistanceEReceipt({transaction}) { pointerEvents="none" /> - {isOffline || !Boolean(thumbnailSource) ? ( + {isOffline || !thumbnailSource ? ( ) : ( Date: Tue, 3 Oct 2023 13:34:17 -1000 Subject: [PATCH 113/284] Clean up --- .../OptionsSelector/BaseOptionsSelector.js | 61 +++++++++---------- src/pages/SearchPage.js | 1 - 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index e024ce6135d8..0e53683e2e99 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -360,38 +360,30 @@ class BaseOptionsSelector extends Component { const shouldShowDefaultConfirmButton = !this.props.footerContent && defaultConfirmButtonText; const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : this.props.safeAreaPaddingBottomStyle; const textInput = ( - <> - (this.textInput = el)} - value={this.props.value} - label={this.props.textInputLabel} - accessibilityLabel={this.props.textInputLabel} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - onChangeText={this.props.onChangeText} - onSubmitEditing={this.selectFocusedOption} - placeholder={this.props.placeholderText} - maxLength={this.props.maxLength} - keyboardType={this.props.keyboardType} - onBlur={(e) => { - if (!this.props.shouldFocusOnSelectRow) { - return; - } - this.relatedTarget = e.relatedTarget; - }} - selectTextOnFocus - blurOnSubmit={Boolean(this.state.allOptions.length)} - spellCheck={false} - shouldInterceptSwipe={this.props.shouldTextInputInterceptSwipe} - /> - {Boolean(this.props.textInputAlert) && ( - - )} - + (this.textInput = el)} + value={this.props.value} + label={this.props.textInputLabel} + accessibilityLabel={this.props.textInputLabel} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + onChangeText={this.props.onChangeText} + onSubmitEditing={this.selectFocusedOption} + placeholder={this.props.placeholderText} + maxLength={this.props.maxLength} + keyboardType={this.props.keyboardType} + onBlur={(e) => { + if (!this.props.shouldFocusOnSelectRow) { + return; + } + this.relatedTarget = e.relatedTarget; + }} + selectTextOnFocus + blurOnSubmit={Boolean(this.state.allOptions.length)} + spellCheck={false} + shouldInterceptSwipe={this.props.shouldTextInputInterceptSwipe} + /> ); + const optionsList = ( (this.list = el)} @@ -451,6 +443,13 @@ class BaseOptionsSelector extends Component { {this.props.children} {this.props.shouldShowTextInput && textInput} + {Boolean(this.props.textInputAlert) && ( + + )} {optionsList} diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index f57f86c50189..46f086bfd854 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -83,7 +83,6 @@ class SearchPage extends Component { Report.searchInServer(searchValue); } - // When the user searches we will this.setState({searchValue}, this.debouncedUpdateOptions); } From 92c0ed48fcfad44f6ca21302ccb93927e37286d4 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 13:35:10 -1000 Subject: [PATCH 114/284] remove new line --- src/components/OptionsSelector/BaseOptionsSelector.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 0e53683e2e99..fa1c1415b256 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -383,7 +383,6 @@ class BaseOptionsSelector extends Component { shouldInterceptSwipe={this.props.shouldTextInputInterceptSwipe} /> ); - const optionsList = ( (this.list = el)} From 058f38e10f5829b995ee0d0cee4f78e5abd41edf Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 3 Oct 2023 17:33:07 -0700 Subject: [PATCH 115/284] Prevent top of merchant text being cut off --- src/styles/styles.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/styles/styles.js b/src/styles/styles.js index 6605b6c0ce53..a29636cbf796 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3249,7 +3249,10 @@ const styles = (theme) => ({ eReceiptMerchant: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeXLarge, - lineHeight: variables.lineHeightXLarge, + + // Setting the line height caused the top of the text to be cut off. Here is an issue to investigate and fix this. + // https://github.com/Expensify/App/issues/28772 + // lineHeight: variables.lineHeightXLarge, color: theme.text, }, From b101e17d4fcf8829d124669a850e6b5d19dbcf81 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 3 Oct 2023 16:21:26 -1000 Subject: [PATCH 116/284] Style --- src/pages/SearchPage.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 46f086bfd854..59b26d7469b5 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import React, {Component} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import OptionsSelector from '../components/OptionsSelector'; import * as OptionsListUtils from '../libs/OptionsListUtils'; import * as ReportUtils from '../libs/ReportUtils'; @@ -202,7 +202,9 @@ class SearchPage extends Component { showTitleTooltip shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} - textInputAlert={this.props.network.isOffline ? `${this.props.translate('common.youAppearToBeOffline')} ${this.props.translate('search.resultsAreLimited')}` : ''} + textInputAlert={ + this.props.network.isOffline ? `${this.props.translate('common.youAppearToBeOffline')} ${this.props.translate('search.resultsAreLimited')}` : '' + } onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus From abb6b2066a8cf0a45234373b87c98ac6c899fde8 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 4 Oct 2023 14:53:28 +0700 Subject: [PATCH 117/284] add description --- src/libs/actions/BankAccounts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index e7d9816292c3..fbc720630290 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -57,7 +57,7 @@ function clearOnfidoToken() { /** * Helper method to build the Onyx data required during setup of a Verified Business Bank Account - * @param {String | undefined} currentStep + * @param {String | undefined} currentStep The step that we need to update the data from BE to draft value * @returns {Object} */ function getVBBADataForOnyx(currentStep = undefined) { From bec41b8ab84ee7d6343f95cdcc8d76859a20e36e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 4 Oct 2023 16:29:12 +0700 Subject: [PATCH 118/284] add more detail description --- src/libs/actions/BankAccounts.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index fbc720630290..3b6507fd938f 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -57,7 +57,7 @@ function clearOnfidoToken() { /** * Helper method to build the Onyx data required during setup of a Verified Business Bank Account - * @param {String | undefined} currentStep The step that we need to update the data from BE to draft value + * @param {String | undefined} currentStep The step that we need to update the data from backend to draft value * @returns {Object} */ function getVBBADataForOnyx(currentStep = undefined) { @@ -79,6 +79,9 @@ function getVBBADataForOnyx(currentStep = undefined) { value: { isLoading: false, errors: null, + // The value of some fields of the currentStep are changed i.e. being trimmed or phone number is parsed when backend returns the data + // So we need to add this field in successData to update the current data from reimbursement account to the draft value of the form + // if currentStep is undefined that means the step doesn't need update to the drafts stepToUpdateToDraft: currentStep, }, }, From 4e75345b7ab7e066cb0c56b1f3ef6f94ec84dc4e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 4 Oct 2023 16:31:12 +0700 Subject: [PATCH 119/284] fix typo --- src/libs/actions/BankAccounts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index 3b6507fd938f..be71082b7592 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -81,7 +81,7 @@ function getVBBADataForOnyx(currentStep = undefined) { errors: null, // The value of some fields of the currentStep are changed i.e. being trimmed or phone number is parsed when backend returns the data // So we need to add this field in successData to update the current data from reimbursement account to the draft value of the form - // if currentStep is undefined that means the step doesn't need update to the drafts + // if currentStep is undefined that means the step doesn't need update to the draft stepToUpdateToDraft: currentStep, }, }, From e201823ba6cb6188f05a58a1b71c7ab80f7b8dc4 Mon Sep 17 00:00:00 2001 From: Kamil Owczarz Date: Wed, 4 Oct 2023 12:27:53 +0200 Subject: [PATCH 120/284] Improve new form validation --- src/components/Form/FormProvider.js | 70 ++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index 5261d1258ad0..a8f5d5c4dfca 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -11,6 +11,7 @@ import compose from '../../libs/compose'; import {withNetwork} from '../OnyxProvider'; import stylePropTypes from '../../styles/stylePropTypes'; import networkPropTypes from '../networkPropTypes'; +import CONST from '../../CONST'; const propTypes = { /** A unique Onyx key identifying the form */ @@ -98,19 +99,75 @@ function getInitialValueByType(valueType) { } } -function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, children, formState, network, enabledWhenOffline, onSubmit, ...rest}) { +function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnChange, children, formState, network, enabledWhenOffline, onSubmit, ...rest}) { const inputRefs = useRef(null); const touchedInputs = useRef({}); const [inputValues, setInputValues] = useState({}); const [errors, setErrors] = useState({}); + const hasServerError = useMemo(() => Boolean(formState) && !_.isEmpty(formState.errors), [formState]); const onValidate = useCallback( - (values) => { + (values, shouldClearServerError = true) => { + const trimmedStringValues = {}; + _.each(values, (inputValue, inputID) => { + if (_.isString(inputValue)) { + trimmedStringValues[inputID] = inputValue.trim(); + } else { + trimmedStringValues[inputID] = inputValue; + } + }); + + if (shouldClearServerError) { + FormActions.setErrors(formID, null); + } + FormActions.setErrorFields(formID, null); + const validateErrors = validate(values); - setErrors(validateErrors); - return validateErrors; + + // Validate the input for html tags. It should supercede any other error + _.each(trimmedStringValues, (inputValue, inputID) => { + // If the input value is empty OR is non-string, we don't need to validate it for HTML tags + if (!inputValue || !_.isString(inputValue)) { + return; + } + const foundHtmlTagIndex = inputValue.search(CONST.VALIDATE_FOR_HTML_TAG_REGEX); + const leadingSpaceIndex = inputValue.search(CONST.VALIDATE_FOR_LEADINGSPACES_HTML_TAG_REGEX); + + // Return early if there are no HTML characters + if (leadingSpaceIndex === -1 && foundHtmlTagIndex === -1) { + return; + } + + const matchedHtmlTags = inputValue.match(CONST.VALIDATE_FOR_HTML_TAG_REGEX); + let isMatch = _.some(CONST.WHITELISTED_TAGS, (r) => r.test(inputValue)); + // Check for any matches that the original regex (foundHtmlTagIndex) matched + if (matchedHtmlTags) { + // Check if any matched inputs does not match in WHITELISTED_TAGS list and return early if needed. + for (let i = 0; i < matchedHtmlTags.length; i++) { + const htmlTag = matchedHtmlTags[i]; + isMatch = _.some(CONST.WHITELISTED_TAGS, (r) => r.test(htmlTag)); + if (!isMatch) { + break; + } + } + } + // Add a validation error here because it is a string value that contains HTML characters + validateErrors[inputID] = 'common.error.invalidCharacter'; + }); + + if (!_.isObject(validateErrors)) { + throw new Error('Validate callback must return an empty object or an object with shape {inputID: error}'); + } + + const touchedInputErrors = _.pick(validateErrors, (inputValue, inputID) => Boolean(touchedInputs.current[inputID])); + + if (!_.isEqual(errors, touchedInputErrors)) { + setErrors(touchedInputErrors); + } + + return touchedInputErrors; }, - [validate], + [errors, formID, validate], ); /** @@ -195,7 +252,7 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c setTimeout(() => { setTouchedInput(inputID); if (shouldValidateOnBlur) { - onValidate(inputValues); + onValidate(inputValues, !hasServerError); } }, 200); } @@ -237,6 +294,7 @@ function FormProvider({validate, shouldValidateOnBlur, shouldValidateOnChange, c {/* eslint-disable react/jsx-props-no-spreading */} Date: Wed, 4 Oct 2023 08:58:22 -0700 Subject: [PATCH 121/284] Line height can be larger by convention --- src/styles/styles.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/styles/styles.js b/src/styles/styles.js index 56593e97817a..104eff3e9874 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3260,10 +3260,7 @@ const styles = (theme) => ({ eReceiptMerchant: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeXLarge, - - // Setting the line height caused the top of the text to be cut off. Here is an issue to investigate and fix this. - // https://github.com/Expensify/App/issues/28772 - // lineHeight: variables.lineHeightXLarge, + lineHeight: variables.lineHeightXXLarge, color: theme.text, }, From 50349b28868034813ceb4fbfcbfe7e8a4cbd9d1f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 4 Oct 2023 11:49:57 -0700 Subject: [PATCH 122/284] Show scroll bar at the full width of the modal --- src/components/DistanceEReceipt.js | 7 +++++-- src/styles/styles.js | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index c461db397b3b..ee725ea54f67 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -48,8 +48,11 @@ function DistanceEReceipt({transaction}) { [waypoints], ); return ( - - + + ({ eReceiptPanel: { ...spacing.p5, ...spacing.pb8, + ...spacing.m5, backgroundColor: colors.green800, borderRadius: 20, width: 335, From 007d2d3defdf0287c0fb9b91bc561b438f464668 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 4 Oct 2023 11:49:08 -1000 Subject: [PATCH 123/284] Show loading spinner instead of skeleton --- src/components/OptionsList/BaseOptionsList.js | 15 ++++----------- .../OptionsList/optionsListPropTypes.js | 6 +++--- .../OptionsSelector/BaseOptionsSelector.js | 3 ++- src/components/TextInput/BaseTextInput.js | 5 ++++- src/pages/NewChatPage.js | 12 ++---------- src/pages/SearchPage.js | 9 +-------- 6 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 2521962756a5..358b4a8c77e1 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -66,7 +66,7 @@ function BaseOptionsList({ isDisabled, innerRef, isRowMultilineSupported, - shouldShowHeaderMessage, + isLoadingNewOptions, }) { const flattenedData = useRef(); const previousSections = usePrevious(sections); @@ -191,15 +191,6 @@ function BaseOptionsList({ return option.name === item.searchText; }); - if (item.loadingRow) { - return ( - - ); - } - return ( ) : ( <> - {shouldShowHeaderMessage && headerMessage ? ( + {/* If we are loading new options we will avoid showing any header message. This is mostly because one of the header messages says there are no options. */} + {/* This is confusing because we might be in the process of loading fresh options from the server. */} + {!isLoadingNewOptions && headerMessage ? ( {headerMessage} diff --git a/src/components/OptionsList/optionsListPropTypes.js b/src/components/OptionsList/optionsListPropTypes.js index 967f30d44119..fb40311dfe76 100644 --- a/src/components/OptionsList/optionsListPropTypes.js +++ b/src/components/OptionsList/optionsListPropTypes.js @@ -85,8 +85,8 @@ const propTypes = { /** Whether to wrap large text up to 2 lines */ isRowMultilineSupported: PropTypes.bool, - /** Whether to show the header message below the input */ - shouldShowHeaderMessage: PropTypes.bool, + /** Whether we are loading new options */ + isLoadingNewOptions: PropTypes.bool, }; const defaultProps = { @@ -112,7 +112,7 @@ const defaultProps = { shouldDisableRowInnerPadding: false, showScrollIndicator: false, isRowMultilineSupported: false, - shouldShowHeaderMessage: true, + isLoadingNewOptions: true, }; export {propTypes, defaultProps}; diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index fa1c1415b256..6e0339e4859c 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -381,6 +381,7 @@ class BaseOptionsSelector extends Component { blurOnSubmit={Boolean(this.state.allOptions.length)} spellCheck={false} shouldInterceptSwipe={this.props.shouldTextInputInterceptSwipe} + isLoading={this.props.isLoadingNewOptions} /> ); const optionsList = ( @@ -417,7 +418,7 @@ class BaseOptionsSelector extends Component { isLoading={!this.props.shouldShowOptions} showScrollIndicator={this.props.showScrollIndicator} isRowMultilineSupported={this.props.isRowMultilineSupported} - shouldShowHeaderMessage={this.props.shouldShowHeaderMessage} + isLoadingNewOptions={this.props.isLoadingNewOptions} /> ); return ( diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 91591de7f045..28baed106b1c 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import React, {useState, useRef, useEffect, useCallback, useMemo} from 'react'; -import {Animated, View, StyleSheet} from 'react-native'; +import {Animated, View, StyleSheet, ActivityIndicator} from 'react-native'; import Str from 'expensify-common/lib/str'; import RNTextInput from '../RNTextInput'; import TextInputLabel from './TextInputLabel'; @@ -368,6 +368,9 @@ function BaseTextInput(props) { // `dataset.submitOnEnter` is used to indicate that pressing Enter on this input should call the submit callback. dataSet={{submitOnEnter: isMultiline && props.submitOnEnter}} /> + {props.isLoading && ( + + )} {Boolean(props.secureTextEntry) && ( diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 59b26d7469b5..1cf1e97e5990 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -121,13 +121,6 @@ class SearchPage extends Component { }); } - if (this.props.isSearchingForReports) { - sections.push({ - data: [{loadingRow: true}], - shouldShow: true, - indexOffset, - }); - } return sections; } @@ -208,7 +201,7 @@ class SearchPage extends Component { onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus - shouldShowHeaderMessage={!this.props.isSearchingForReports} + isLoadingNewOptions={this.props.isSearchingForReports} /> From 4e6826a7faf852647d6c4fbd624d880f5f2dce9e Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 4 Oct 2023 11:51:14 -1000 Subject: [PATCH 124/284] Undo skeleton row changes --- src/components/OptionsList/BaseOptionsList.js | 1 - src/components/OptionsListSkeletonRow.js | 48 ------------------- src/components/OptionsListSkeletonView.js | 33 +++++++++++-- 3 files changed, 28 insertions(+), 54 deletions(-) delete mode 100644 src/components/OptionsListSkeletonRow.js diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 358b4a8c77e1..9fa25be0868a 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -10,7 +10,6 @@ import Text from '../Text'; import {propTypes as optionsListPropTypes, defaultProps as optionsListDefaultProps} from './optionsListPropTypes'; import OptionsListSkeletonView from '../OptionsListSkeletonView'; import usePrevious from '../../hooks/usePrevious'; -import OptionsListSkeletonRow from '../OptionsListSkeletonRow'; const propTypes = { /** Determines whether the keyboard gets dismissed in response to a drag */ diff --git a/src/components/OptionsListSkeletonRow.js b/src/components/OptionsListSkeletonRow.js deleted file mode 100644 index d7968ef1ba80..000000000000 --- a/src/components/OptionsListSkeletonRow.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {Rect, Circle} from 'react-native-svg'; -import SkeletonViewContentLoader from 'react-content-loader/native'; -import themeColors from '../styles/themes/default'; -import CONST from '../CONST'; -import styles from '../styles/styles'; - -const propTypes = { - /** Whether to animate the skeleton view */ - shouldAnimate: PropTypes.bool.isRequired, - - /** Line width string */ - lineWidth: PropTypes.string.isRequired, -}; - -function OptionsListSkeletonRow({lineWidth, shouldAnimate}) { - return ( - - - - - - ); -} - -OptionsListSkeletonRow.propTypes = propTypes; -export default OptionsListSkeletonRow; diff --git a/src/components/OptionsListSkeletonView.js b/src/components/OptionsListSkeletonView.js index 833cbc0f372e..15c66affe84d 100644 --- a/src/components/OptionsListSkeletonView.js +++ b/src/components/OptionsListSkeletonView.js @@ -1,9 +1,11 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; +import {Rect, Circle} from 'react-native-svg'; +import SkeletonViewContentLoader from 'react-content-loader/native'; import CONST from '../CONST'; +import themeColors from '../styles/themes/default'; import styles from '../styles/styles'; -import OptionsListSkeletonRow from './OptionsListSkeletonRow'; const propTypes = { /** Whether to animate the skeleton view */ @@ -54,11 +56,32 @@ class OptionsListSkeletonView extends React.Component { lineWidth = '25%'; } skeletonViewItems.push( - , + animate={this.props.shouldAnimate} + height={CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT} + backgroundColor={themeColors.skeletonLHNIn} + foregroundColor={themeColors.skeletonLHNOut} + style={styles.mr5} + > + + + + , ); } From 96400edcbe72105e0e4b948673864296161efa04 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 4 Oct 2023 13:08:18 -1000 Subject: [PATCH 125/284] run prettier --- src/components/TextInput/BaseTextInput.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js index 28baed106b1c..826e9ed5e957 100644 --- a/src/components/TextInput/BaseTextInput.js +++ b/src/components/TextInput/BaseTextInput.js @@ -369,7 +369,11 @@ function BaseTextInput(props) { dataSet={{submitOnEnter: isMultiline && props.submitOnEnter}} /> {props.isLoading && ( - + )} {Boolean(props.secureTextEntry) && ( Date: Thu, 5 Oct 2023 09:49:38 +0200 Subject: [PATCH 126/284] chore: update onyx --- package-lock.json | 14 +++++++------- package.json | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5751fe3ea8c8..edb81edcd4b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.98", + "react-native-onyx": "^1.0.100", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -41286,9 +41286,9 @@ } }, "node_modules/react-native-onyx": { - "version": "1.0.98", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.98.tgz", - "integrity": "sha512-2wJNmZVBJs2Y0p1G/es4tQZnplJR8rOyVbHv9KZaq/SXluLUnIovttf1MMhVXidDLT+gcE+u20Mck/Gpb8bY0w==", + "version": "1.0.100", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.100.tgz", + "integrity": "sha512-m4bOF/uOtYpfL83fqoWhw7TYV4oKGXt0sfGoel/fhaT1HzXKheXc//ibt/G3VrTCf5nmRv7bXgsXkWjUYLH3UQ==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -77470,9 +77470,9 @@ } }, "react-native-onyx": { - "version": "1.0.98", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.98.tgz", - "integrity": "sha512-2wJNmZVBJs2Y0p1G/es4tQZnplJR8rOyVbHv9KZaq/SXluLUnIovttf1MMhVXidDLT+gcE+u20Mck/Gpb8bY0w==", + "version": "1.0.100", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.100.tgz", + "integrity": "sha512-m4bOF/uOtYpfL83fqoWhw7TYV4oKGXt0sfGoel/fhaT1HzXKheXc//ibt/G3VrTCf5nmRv7bXgsXkWjUYLH3UQ==", "requires": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", diff --git a/package.json b/package.json index 5eb19d8d2bbf..1d96344f572b 100644 --- a/package.json +++ b/package.json @@ -113,8 +113,8 @@ "react-collapse": "^5.1.0", "react-content-loader": "^6.1.0", "react-dom": "18.1.0", - "react-map-gl": "^7.1.3", "react-error-boundary": "^4.0.11", + "react-map-gl": "^7.1.3", "react-native": "0.72.4", "react-native-blob-util": "^0.17.3", "react-native-collapsible": "^1.6.0", @@ -135,7 +135,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.98", + "react-native-onyx": "^1.0.100", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", From dac13616502aa1b2b1506be958c5e798fde45cd9 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 5 Oct 2023 10:40:20 +0200 Subject: [PATCH 127/284] fix: MigrationTest --- tests/unit/MigrationTest.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index bed273213c90..cbf78e0152b0 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -37,18 +37,12 @@ describe('Migrations', () => { }) .then(PersonalDetailsByAccountID) .then(() => { - expect(LogSpy).toHaveBeenCalledWith( - `[Migrate Onyx] Skipped migration PersonalDetailsByAccountID for ${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1 because there were no reportActions`, - ); - expect(LogSpy).toHaveBeenCalledWith( - `[Migrate Onyx] Skipped migration PersonalDetailsByAccountID for ${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2 because there were no reportActions`, - ); const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, waitForCollectionCallback: true, callback: (allReportActions) => { Onyx.disconnect(connectionID); - _.each(allReportActions, (reportActionsForReport) => expect(reportActionsForReport).toBeNull()); + _.each(allReportActions, (reportActionsForReport) => expect(reportActionsForReport).toBeUndefined()); }, }); })); @@ -377,8 +371,8 @@ describe('Migrations', () => { waitForCollectionCallback: true, callback: (allPolicyMemberLists) => { Onyx.disconnect(connectionID); - expect(allPolicyMemberLists[`${ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST}1`]).toBeNull(); - expect(allPolicyMemberLists[`${ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST}2`]).toBeNull(); + expect(allPolicyMemberLists[`${ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST}1`]).toBeUndefined(); + expect(allPolicyMemberLists[`${ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST}2`]).toBeUndefined(); }, }); })); @@ -403,7 +397,7 @@ describe('Migrations', () => { key: DEPRECATED_ONYX_KEYS.PERSONAL_DETAILS, callback: (allPersonalDetails) => { Onyx.disconnect(connectionID); - expect(allPersonalDetails).toBeNull(); + expect(allPersonalDetails).toBeUndefined(); }, }); })); @@ -554,8 +548,8 @@ describe('Migrations', () => { Onyx.disconnect(connectionID); const expectedReportAction = {}; expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toMatchObject(expectedReportAction); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toMatchObject(expectedReportAction); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toBeUndefined(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toBeUndefined(); expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction); }, }); @@ -597,8 +591,8 @@ describe('Migrations', () => { }, }; expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction1); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toBeNull(); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toBeUndefined(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toBeUndefined(); expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction4); }, }); @@ -620,10 +614,10 @@ describe('Migrations', () => { callback: (allReportActions) => { Onyx.disconnect(connectionID); const expectedReportAction = {}; - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toBeUndefined(); expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toMatchObject(expectedReportAction); expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toMatchObject(expectedReportAction); - expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toBeUndefined(); }, }); })); From cec1f18eea65fe6f02abb00bb0253702d7ed4b26 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 5 Oct 2023 10:54:49 +0200 Subject: [PATCH 128/284] fix: MigrationTest --- tests/unit/MigrationTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index cbf78e0152b0..eb70d94e141d 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -397,7 +397,7 @@ describe('Migrations', () => { key: DEPRECATED_ONYX_KEYS.PERSONAL_DETAILS, callback: (allPersonalDetails) => { Onyx.disconnect(connectionID); - expect(allPersonalDetails).toBeUndefined(); + expect(allPersonalDetails).toBeNull(); }, }); })); From ca4bd906a1b674a82f7c884e193ec9b853bb02fa Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 5 Oct 2023 11:05:07 +0200 Subject: [PATCH 129/284] fix: last test --- tests/unit/MigrationTest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index eb70d94e141d..d0e7f19d3d3f 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -371,8 +371,8 @@ describe('Migrations', () => { waitForCollectionCallback: true, callback: (allPolicyMemberLists) => { Onyx.disconnect(connectionID); - expect(allPolicyMemberLists[`${ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST}1`]).toBeUndefined(); - expect(allPolicyMemberLists[`${ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST}2`]).toBeUndefined(); + + expect(allPolicyMemberLists).toBeFalsy(); }, }); })); From 9a905ef8dcee4a6209f11557486bc19a3ad3a8d3 Mon Sep 17 00:00:00 2001 From: laurenreidexpensify <62073721+laurenreidexpensify@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:49:12 +0100 Subject: [PATCH 130/284] Update removing-users.md title description --- .../manage-employees-and-report-approvals/removing-users.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/removing-users.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/removing-users.md index 0db950680aa8..76ebba9ef76b 100644 --- a/docs/articles/expensify-classic/manage-employees-and-report-approvals/removing-users.md +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/removing-users.md @@ -1,6 +1,6 @@ --- -title: Coming Soon -description: Coming Soon +title: Remove a Workspace Member +description: How to remove a member from a Workspace in Expensify --- Removing a member from a workspace disables their ability to use the workspace. Please note that it does not delete their account or deactivate the Expensify Card. From 326029adb5b6bebe38edd2fd80b2a8418a0aa2d7 Mon Sep 17 00:00:00 2001 From: Pujan Date: Thu, 5 Oct 2023 15:42:23 +0530 Subject: [PATCH 131/284] use isInModal for the footer --- src/pages/signin/SignInPageLayout/Footer.js | 7 +++++-- src/pages/signin/SignInPageLayout/index.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/signin/SignInPageLayout/Footer.js b/src/pages/signin/SignInPageLayout/Footer.js index 62733f3cec9e..8237c0530769 100644 --- a/src/pages/signin/SignInPageLayout/Footer.js +++ b/src/pages/signin/SignInPageLayout/Footer.js @@ -23,9 +23,12 @@ const propTypes = { ...windowDimensionsPropTypes, ...withLocalizePropTypes, scrollPageToTop: PropTypes.func.isRequired, + isInModal: PropTypes.bool, }; -const defaultProps = {}; +const defaultProps = { + isInModal: false, +}; const navigateHome = (scrollPageToTop) => { scrollPageToTop(); @@ -150,7 +153,7 @@ const columns = ({scrollPageToTop}) => [ ]; function Footer(props) { - const isVertical = props.isSmallScreenWidth; + const isVertical = props.isSmallScreenWidth || props.isInModal; const imageDirection = isVertical ? styles.flexRow : styles.flexColumn; const imageStyle = isVertical ? styles.pr0 : styles.alignSelfCenter; const columnDirection = isVertical ? styles.flexColumn : styles.flexRow; diff --git a/src/pages/signin/SignInPageLayout/index.js b/src/pages/signin/SignInPageLayout/index.js index 1f70364c307b..60d5f4e1a000 100644 --- a/src/pages/signin/SignInPageLayout/index.js +++ b/src/pages/signin/SignInPageLayout/index.js @@ -170,7 +170,7 @@ function SignInPageLayout(props) { -