From 4afa5498d80fd65b69c025e8d0a563efcc608a27 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Thu, 9 Jan 2025 13:50:51 +0100 Subject: [PATCH 01/20] fix console errors on moneyrequestconfirmation page and submit expense --- .../MoneyRequestConfirmationListFooter.tsx | 54 +++++++++---------- .../MultiGestureCanvas/usePanGesture.ts | 10 ++++ .../MultiGestureCanvas/usePinchGesture.ts | 8 +++ 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index 644e00378f28..2c634ed4af50 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -326,33 +326,33 @@ function MoneyRequestConfirmationListFooter({ }, { item: ( - - - { - if (!transactionID) { - return; - } - - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute(), reportActionID)); - }} - style={[styles.moneyRequestMenuItem]} - titleStyle={styles.flex1} - disabled={didConfirm} - interactive={!isReadOnly} - numberOfLinesTitle={2} - /> - - + + + + { + if (!transactionID) { + return; + } + + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute(), reportActionID), + ); + }} + style={[styles.moneyRequestMenuItem]} + titleStyle={styles.flex1} + disabled={didConfirm} + interactive={!isReadOnly} + numberOfLinesTitle={2} + /> + + + ), shouldShow: true, isSupplementary: false, diff --git a/src/components/MultiGestureCanvas/usePanGesture.ts b/src/components/MultiGestureCanvas/usePanGesture.ts index f2f33aa87e7e..8e23f58a6c22 100644 --- a/src/components/MultiGestureCanvas/usePanGesture.ts +++ b/src/components/MultiGestureCanvas/usePanGesture.ts @@ -180,9 +180,13 @@ const usePanGesture = ({ .manualActivation(true) .averageTouches(true) .onTouchesUp(() => { + 'worklet'; + previousTouch.set(null); }) .onTouchesMove((evt, state) => { + 'worklet'; + // We only allow panning when the content is zoomed in if (zoomScale.get() > 1 && !shouldDisableTransformationGestures.get()) { state.activate(); @@ -212,9 +216,13 @@ const usePanGesture = ({ } }) .onStart(() => { + 'worklet'; + stopAnimation(); }) .onChange((evt) => { + 'worklet'; + // Since we're running both pinch and pan gesture handlers simultaneously, // we need to make sure that we don't pan when we pinch since we track it as pinch focal gesture. if (evt.numberOfPointers > 1) { @@ -235,6 +243,8 @@ const usePanGesture = ({ } }) .onEnd(() => { + 'worklet'; + // Add pan translation to total offset and reset gesture variables offsetX.set((value) => value + panTranslateX.get()); offsetY.set((value) => value + panTranslateY.get()); diff --git a/src/components/MultiGestureCanvas/usePinchGesture.ts b/src/components/MultiGestureCanvas/usePinchGesture.ts index 7f5cecc4e949..b3d977e8af0c 100644 --- a/src/components/MultiGestureCanvas/usePinchGesture.ts +++ b/src/components/MultiGestureCanvas/usePinchGesture.ts @@ -104,6 +104,8 @@ const usePinchGesture = ({ .enabled(pinchEnabled) // The first argument is not used, but must be defined .onTouchesDown((_evt, state) => { + 'worklet'; + // We don't want to activate pinch gesture when we are swiping in the pager if (!shouldDisableTransformationGestures.get()) { return; @@ -112,6 +114,8 @@ const usePinchGesture = ({ state.fail(); }) .onStart((evt) => { + 'worklet'; + stopAnimation(); // Set the origin focal point of the pinch gesture at the start of the gesture @@ -120,6 +124,8 @@ const usePinchGesture = ({ pinchOrigin.y.set(adjustedFocal.y); }) .onChange((evt) => { + 'worklet'; + // Disable the pinch gesture if one finger is released, // to prevent the content from shaking/jumping if (evt.numberOfPointers !== 2) { @@ -154,6 +160,8 @@ const usePinchGesture = ({ } }) .onEnd(() => { + 'worklet'; + // Add pinch translation to total offset and reset gesture variables offsetX.set((value) => value + pinchTranslateX.get()); offsetY.set((value) => value + pinchTranslateY.get()); From 1af473b80b9569d80351400afc144677a2823d12 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Thu, 9 Jan 2025 17:23:47 +0100 Subject: [PATCH 02/20] continue fixing console errors --- src/components/Attachments/AttachmentCarousel/index.tsx | 4 ++++ .../AttachmentCarousel/useCarouselContextEvents.ts | 2 ++ src/components/MultiGestureCanvas/useTapGestures.ts | 4 ++++ src/components/TextBlock.tsx | 1 + 4 files changed, 11 insertions(+) diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index 50caaac3dd81..d3366dd32df6 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -241,6 +241,8 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi Gesture.Pan() .enabled(canUseTouchScreen) .onUpdate(({translationX}) => { + 'worklet'; + if (!isScrollEnabled.get()) { return; } @@ -252,6 +254,8 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi scrollTo(scrollRef, page * cellWidth - translationX, 0, false); }) .onEnd(({translationX, velocityX}) => { + 'worklet'; + if (!isScrollEnabled.get()) { return; } diff --git a/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts b/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts index 3311f6476194..bb96c2a77138 100644 --- a/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts +++ b/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts @@ -50,6 +50,8 @@ function useCarouselContextEvents(setShouldShowArrows: (show?: SetStateAction { + 'worklet'; + if (!isScrollEnabled.get()) { return; } diff --git a/src/components/MultiGestureCanvas/useTapGestures.ts b/src/components/MultiGestureCanvas/useTapGestures.ts index a918310d2862..39db3b955985 100644 --- a/src/components/MultiGestureCanvas/useTapGestures.ts +++ b/src/components/MultiGestureCanvas/useTapGestures.ts @@ -122,6 +122,8 @@ const useTapGestures = ({ const doubleTapGesture = Gesture.Tap() // The first argument is not used, but must be defined .onTouchesDown((_evt, state) => { + 'worklet'; + if (!shouldDisableTransformationGestures.get()) { return; } @@ -153,6 +155,8 @@ const useTapGestures = ({ .numberOfTaps(1) .maxDuration(125) .onBegin(() => { + 'worklet'; + stopAnimation(); }) .onFinalize((_evt, success) => { diff --git a/src/components/TextBlock.tsx b/src/components/TextBlock.tsx index 8b036f42f4cc..c26bcc029c2b 100644 --- a/src/components/TextBlock.tsx +++ b/src/components/TextBlock.tsx @@ -26,6 +26,7 @@ function TextBlock({color, textStyles, text}: TextBlockProps) { {word} From dcc41ce2404e77ed053cbe5310770ad55f3a2cf4 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Mon, 13 Jan 2025 09:34:05 +0100 Subject: [PATCH 03/20] fix console error related to workletization --- .../Attachments/AttachmentCarousel/index.tsx | 4 ---- .../AttachmentCarousel/useCarouselContextEvents.ts | 2 -- src/components/Lightbox/index.tsx | 2 ++ src/components/MultiGestureCanvas/usePanGesture.ts | 10 ---------- src/components/MultiGestureCanvas/useTapGestures.ts | 4 ---- 5 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index d3366dd32df6..50caaac3dd81 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -241,8 +241,6 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi Gesture.Pan() .enabled(canUseTouchScreen) .onUpdate(({translationX}) => { - 'worklet'; - if (!isScrollEnabled.get()) { return; } @@ -254,8 +252,6 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi scrollTo(scrollRef, page * cellWidth - translationX, 0, false); }) .onEnd(({translationX, velocityX}) => { - 'worklet'; - if (!isScrollEnabled.get()) { return; } diff --git a/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts b/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts index bb96c2a77138..3311f6476194 100644 --- a/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts +++ b/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts @@ -50,8 +50,6 @@ function useCarouselContextEvents(setShouldShowArrows: (show?: SetStateAction { - 'worklet'; - if (!isScrollEnabled.get()) { return; } diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index ec0c6e5efcaa..dcfaa64b00f6 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -195,6 +195,8 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan const scaleChange = useCallback( (scale: number) => { + 'worklet'; + onScaleChangedProp?.(scale); onScaleChangedContext?.(scale); }, diff --git a/src/components/MultiGestureCanvas/usePanGesture.ts b/src/components/MultiGestureCanvas/usePanGesture.ts index 8e23f58a6c22..f2f33aa87e7e 100644 --- a/src/components/MultiGestureCanvas/usePanGesture.ts +++ b/src/components/MultiGestureCanvas/usePanGesture.ts @@ -180,13 +180,9 @@ const usePanGesture = ({ .manualActivation(true) .averageTouches(true) .onTouchesUp(() => { - 'worklet'; - previousTouch.set(null); }) .onTouchesMove((evt, state) => { - 'worklet'; - // We only allow panning when the content is zoomed in if (zoomScale.get() > 1 && !shouldDisableTransformationGestures.get()) { state.activate(); @@ -216,13 +212,9 @@ const usePanGesture = ({ } }) .onStart(() => { - 'worklet'; - stopAnimation(); }) .onChange((evt) => { - 'worklet'; - // Since we're running both pinch and pan gesture handlers simultaneously, // we need to make sure that we don't pan when we pinch since we track it as pinch focal gesture. if (evt.numberOfPointers > 1) { @@ -243,8 +235,6 @@ const usePanGesture = ({ } }) .onEnd(() => { - 'worklet'; - // Add pan translation to total offset and reset gesture variables offsetX.set((value) => value + panTranslateX.get()); offsetY.set((value) => value + panTranslateY.get()); diff --git a/src/components/MultiGestureCanvas/useTapGestures.ts b/src/components/MultiGestureCanvas/useTapGestures.ts index 39db3b955985..a918310d2862 100644 --- a/src/components/MultiGestureCanvas/useTapGestures.ts +++ b/src/components/MultiGestureCanvas/useTapGestures.ts @@ -122,8 +122,6 @@ const useTapGestures = ({ const doubleTapGesture = Gesture.Tap() // The first argument is not used, but must be defined .onTouchesDown((_evt, state) => { - 'worklet'; - if (!shouldDisableTransformationGestures.get()) { return; } @@ -155,8 +153,6 @@ const useTapGestures = ({ .numberOfTaps(1) .maxDuration(125) .onBegin(() => { - 'worklet'; - stopAnimation(); }) .onFinalize((_evt, success) => { From e1d00a0001f999d90e7e3391bb412a4428b67af8 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Mon, 13 Jan 2025 14:57:16 +0100 Subject: [PATCH 04/20] fix console error regarding gesture handler --- src/components/Lightbox/index.tsx | 2 -- src/components/MultiGestureCanvas/usePinchGesture.ts | 4 ---- src/components/MultiGestureCanvas/useTapGestures.ts | 8 ++++++++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index dcfaa64b00f6..ec0c6e5efcaa 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -195,8 +195,6 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan const scaleChange = useCallback( (scale: number) => { - 'worklet'; - onScaleChangedProp?.(scale); onScaleChangedContext?.(scale); }, diff --git a/src/components/MultiGestureCanvas/usePinchGesture.ts b/src/components/MultiGestureCanvas/usePinchGesture.ts index b3d977e8af0c..76ab4499304b 100644 --- a/src/components/MultiGestureCanvas/usePinchGesture.ts +++ b/src/components/MultiGestureCanvas/usePinchGesture.ts @@ -114,8 +114,6 @@ const usePinchGesture = ({ state.fail(); }) .onStart((evt) => { - 'worklet'; - stopAnimation(); // Set the origin focal point of the pinch gesture at the start of the gesture @@ -160,8 +158,6 @@ const usePinchGesture = ({ } }) .onEnd(() => { - 'worklet'; - // Add pinch translation to total offset and reset gesture variables offsetX.set((value) => value + pinchTranslateX.get()); offsetY.set((value) => value + pinchTranslateY.get()); diff --git a/src/components/MultiGestureCanvas/useTapGestures.ts b/src/components/MultiGestureCanvas/useTapGestures.ts index a918310d2862..4544a17cbc17 100644 --- a/src/components/MultiGestureCanvas/useTapGestures.ts +++ b/src/components/MultiGestureCanvas/useTapGestures.ts @@ -122,6 +122,8 @@ const useTapGestures = ({ const doubleTapGesture = Gesture.Tap() // The first argument is not used, but must be defined .onTouchesDown((_evt, state) => { + 'worklet'; + if (!shouldDisableTransformationGestures.get()) { return; } @@ -132,6 +134,8 @@ const useTapGestures = ({ .maxDelay(150) .maxDistance(20) .onEnd((evt) => { + 'worklet'; + const triggerScaleChangedEvent = () => { 'worklet'; @@ -153,9 +157,13 @@ const useTapGestures = ({ .numberOfTaps(1) .maxDuration(125) .onBegin(() => { + 'worklet'; + stopAnimation(); }) .onFinalize((_evt, success) => { + 'worklet'; + if (!success || onTap === undefined) { return; } From 20f245587c2f1fd734bda9b98c58252613a2a842 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Wed, 15 Jan 2025 10:21:58 +0100 Subject: [PATCH 05/20] fix nested buttons console error in avatarwithdisplayname --- src/components/AvatarWithDisplayName.tsx | 10 +++------- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 98e6dd626883..930f9b667ea6 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -127,11 +127,7 @@ function AvatarWithDisplayName({ {!!report && !!title && ( - + {shouldShowSubscriptAvatar ? ( )} - + Date: Thu, 16 Jan 2025 10:28:26 +0100 Subject: [PATCH 06/20] fix console errors regarding nested buttons, not using forwardRef and Url cannot be created from undefined --- src/components/ReportActionItem/ReportPreview.tsx | 8 ++++---- src/components/Search/SearchRouter/SearchRouter.tsx | 7 ++++--- src/libs/actions/Link.ts | 7 +++++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index f9904bec6d2c..f448ad1a5461 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -1,7 +1,7 @@ import truncate from 'lodash/truncate'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; -import {View} from 'react-native'; +import {Platform, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Animated, {useAnimatedStyle, useSharedValue, withDelay, withSpring, withTiming} from 'react-native-reanimated'; import Button from '@components/Button'; @@ -409,7 +409,6 @@ function ReportPreview({ const bankAccountRoute = getBankAccountRoute(chatReport); const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !showRTERViolationMessage && !shouldShowBrokenConnectionViolation; - console.log('shouldShowSettelmentButton', shouldShowSettlementButton); const shouldPromptUserToAddBankAccount = (hasMissingPaymentMethod(userWallet, iouReportID) || hasMissingInvoiceBankAccount(iouReportID)) && !isSettled(iouReportID); const shouldShowRBR = hasErrors && !iouSettled; @@ -541,8 +540,9 @@ function ReportPreview({ onPressOut={() => ControlSelection.unblock()} onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)} shouldUseHapticsOnLongPress - style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]} - role="button" + // This is added to omit console error about nested buttons as its forbidden on web platform + style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox, Platform.OS === 'web' && {cursor: 'pointer'}]} + role={Platform.OS === 'web' ? 'presentation' : 'button'} accessibilityLabel={translate('iou.viewDetails')} > diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 6e28dabc8d4a..08fce2652a21 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -1,5 +1,5 @@ import {useNavigationState} from '@react-navigation/native'; -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import React, {forwardRef, useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import type {TextInputProps} from 'react-native'; import {useOnyx} from 'react-native-onyx'; @@ -71,7 +71,7 @@ type SearchRouterProps = { shouldHideInputCaret?: TextInputProps['caretHidden']; }; -function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) { +function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps, ref: React.Ref) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [, recentSearchesMetadata] = useOnyx(ONYXKEYS.RECENT_SEARCHES); @@ -279,6 +279,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) {shouldUseNarrowLayout && ( , postLoginPath?: string) } function getInternalNewExpensifyPath(href: string) { + if (!href) { + return ''; + } const attrPath = Url.getPathFromURL(href); return (Url.hasSameExpensifyOrigin(href, CONST.NEW_EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONST.STAGING_NEW_EXPENSIFY_URL) || href.startsWith(CONST.DEV_NEW_EXPENSIFY_URL)) && !CONST.PATHS_TO_TREAT_AS_EXTERNAL.find((path) => attrPath.startsWith(path)) @@ -146,6 +149,10 @@ function getInternalNewExpensifyPath(href: string) { } function getInternalExpensifyPath(href: string) { + if (!href) { + return ''; + } + const attrPath = Url.getPathFromURL(href); const hasExpensifyOrigin = Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.STAGING_API_ROOT); if (!hasExpensifyOrigin || attrPath.startsWith(CONFIG.EXPENSIFY.CONCIERGE_URL_PATHNAME) || attrPath.startsWith(CONFIG.EXPENSIFY.DEVPORTAL_URL_PATHNAME)) { From 901b8fcf3fbdac68e4cf6dd5df86daa2c6944c58 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Thu, 16 Jan 2025 13:54:49 +0100 Subject: [PATCH 07/20] fix lint problems --- src/components/AvatarWithDisplayName.tsx | 91 +++++++++---------- .../MoneyRequestConfirmationListFooter.tsx | 57 ++++++------ .../Search/SearchRouter/SearchRouter.tsx | 24 ++--- src/libs/actions/Link.ts | 10 +- 4 files changed, 90 insertions(+), 92 deletions(-) diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 930f9b667ea6..7d130899e8bd 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -1,18 +1,33 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx, withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; +import { + getChatRoomSubtitle, + getDisplayNamesWithTooltips, + getIcons, + getParentNavigationSubtitle, + getReportName, + isChatThread, + isExpenseReport, + isInvoiceReport, + isIOUReport, + isMoneyRequest, + isMoneyRequestReport, + isTrackExpenseReport, + navigateToDetailsPage, + shouldReportShowSubscript, +} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetailsList, Policy, Report, ReportActions} from '@src/types/onyx'; +import type {Policy, Report} from '@src/types/onyx'; import type {Icon} from '@src/types/onyx/OnyxCommon'; import CaretWrapper from './CaretWrapper'; import DisplayNames from './DisplayNames'; @@ -23,15 +38,7 @@ import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import SubscriptAvatar from './SubscriptAvatar'; import Text from './Text'; -type AvatarWithDisplayNamePropsWithOnyx = { - /** All of the actions of the report */ - parentReportActions: OnyxEntry; - - /** Personal details of all users */ - personalDetails: OnyxEntry; -}; - -type AvatarWithDisplayNameProps = AvatarWithDisplayNamePropsWithOnyx & { +type AvatarWithDisplayNameProps = { /** The report currently being looked at */ report: OnyxEntry; @@ -55,41 +62,39 @@ const fallbackIcon: Icon = { id: -1, }; -function AvatarWithDisplayName({ - policy, - report, - parentReportActions, - isAnonymous = false, - size = CONST.AVATAR_SIZE.DEFAULT, - shouldEnableDetailPageNavigation = false, - personalDetails = CONST.EMPTY_OBJECT, -}: AvatarWithDisplayNameProps) { +function AvatarWithDisplayName({policy, report, isAnonymous = false, size = CONST.AVATAR_SIZE.DEFAULT, shouldEnableDetailPageNavigation = false}: AvatarWithDisplayNameProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`); const [invoiceReceiverPolicy] = useOnyx( - `${ONYXKEYS.COLLECTION.POLICY}${parentReport?.invoiceReceiver && 'policyID' in parentReport.invoiceReceiver ? parentReport.invoiceReceiver.policyID : -1}`, + `${ONYXKEYS.COLLECTION.POLICY}${parentReport?.invoiceReceiver && 'policyID' in parentReport.invoiceReceiver ? parentReport.invoiceReceiver.policyID : CONST.DEFAULT_NUMBER_ID}`, ); - const title = ReportUtils.getReportName(report, undefined, undefined, undefined, invoiceReceiverPolicy); - const subtitle = ReportUtils.getChatRoomSubtitle(report); - const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(report); - const isMoneyRequestOrReport = - ReportUtils.isMoneyRequestReport(report) || ReportUtils.isMoneyRequest(report) || ReportUtils.isTrackExpenseReport(report) || ReportUtils.isInvoiceReport(report); - const icons = ReportUtils.getIcons(report, personalDetails, null, '', -1, policy, invoiceReceiverPolicy); - const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails); - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails), false); - const shouldShowSubscriptAvatar = ReportUtils.shouldReportShowSubscript(report); + const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`, {canEvict: false}); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const title = getReportName(report, undefined, undefined, undefined, invoiceReceiverPolicy); + const subtitle = getChatRoomSubtitle(report); + const parentNavigationSubtitleData = getParentNavigationSubtitle(report); + const isMoneyRequestOrReport = isMoneyRequestReport(report) || isMoneyRequest(report) || isTrackExpenseReport(report) || isInvoiceReport(report); + const icons = getIcons(report, personalDetails, null, '', CONST.DEFAULT_NUMBER_ID, policy, invoiceReceiverPolicy); + const ownerPersonalDetails = getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails); + const displayNamesWithTooltips = getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails), false); + const shouldShowSubscriptAvatar = shouldReportShowSubscript(report); const avatarBorderColor = isAnonymous ? theme.highlightBG : theme.componentBG; const actorAccountID = useRef(null); useEffect(() => { - const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; - actorAccountID.current = parentReportAction?.actorAccountID ?? -1; + if (!report?.parentReportActionID) { + actorAccountID.current = CONST.DEFAULT_NUMBER_ID; + return; + } + + const parentReportAction = parentReportActions?.[report?.parentReportActionID]; + actorAccountID.current = parentReportAction?.actorAccountID ?? CONST.DEFAULT_NUMBER_ID; }, [parentReportActions, report]); const goToDetailsPage = useCallback(() => { - ReportUtils.navigateToDetailsPage(report, Navigation.getReportRHPActiveRoute()); + navigateToDetailsPage(report, Navigation.getReportRHPActiveRoute()); }, [report]); const showActorDetails = useCallback(() => { @@ -99,17 +104,17 @@ function AvatarWithDisplayName({ return; } - if (ReportUtils.isExpenseReport(report) && report?.ownerAccountID) { + if (isExpenseReport(report) && report?.ownerAccountID) { Navigation.navigate(ROUTES.PROFILE.getRoute(report.ownerAccountID)); return; } - if (ReportUtils.isIOUReport(report) && report?.reportID) { + if (isIOUReport(report) && report?.reportID) { Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(report.reportID)); return; } - if (ReportUtils.isChatThread(report)) { + if (isChatThread(report)) { // In an ideal situation account ID won't be 0 if (actorAccountID.current && actorAccountID.current > 0) { Navigation.navigate(ROUTES.PROFILE.getRoute(actorAccountID.current)); @@ -194,12 +199,4 @@ function AvatarWithDisplayName({ AvatarWithDisplayName.displayName = 'AvatarWithDisplayName'; -export default withOnyx({ - parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '-1'}`, - canEvict: false, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, -})(AvatarWithDisplayName); +export default AvatarWithDisplayName; diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index 2c634ed4af50..e1714a3f8aa6 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -9,16 +9,17 @@ import type {ValueOf} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; +import {convertToDisplayString} from '@libs/CurrencyUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PerDiemRequestUtils from '@libs/PerDiemRequestUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReceiptUtils from '@libs/ReceiptUtils'; +import {hasEnabledOptions} from '@libs/OptionsListUtils'; +import {getDestinationForDisplay, getSubratesFields, getSubratesForDisplay, getTimeDifferenceIntervals, getTimeForDisplay} from '@libs/PerDiemRequestUtils'; +import {canSendInvoice, getPerDiemCustomUnit, isMultiLevelTags, isPaidGroupPolicy} from '@libs/PolicyUtils'; +import type {ThumbnailAndImageURI} from '@libs/ReceiptUtils'; +import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; -import * as TagsOptionsListUtils from '@libs/TagsOptionsListUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {hasEnabledTags} from '@libs/TagsOptionsListUtils'; +import {getTagForDisplay, getTaxAmount, getTaxName, isAmountMissing, isCreatedMissing, shouldShowAttendees as shouldShowAttendeesTransactionsUtils} from '@libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import CONST from '@src/CONST'; @@ -234,9 +235,9 @@ function MoneyRequestConfirmationListFooter({ // A flag and a toggler for showing the rest of the form fields const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); - const shouldShowTags = useMemo(() => isPolicyExpenseChat && TagsOptionsListUtils.hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]); - const isMultilevelTags = useMemo(() => PolicyUtils.isMultiLevelTags(policyTags), [policyTags]); - const shouldShowAttendees = useMemo(() => TransactionUtils.shouldShowAttendees(iouType, policy), [iouType, policy]); + const shouldShowTags = useMemo(() => isPolicyExpenseChat && hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]); + const isMultilevelTags = useMemo(() => isMultiLevelTags(policyTags), [policyTags]); + const shouldShowAttendees = useMemo(() => shouldShowAttendeesTransactionsUtils(iouType, policy), [iouType, policy]); const senderWorkspace = useMemo(() => { const senderWorkspaceParticipant = selectedParticipants.find((participant) => participant.isSender); @@ -246,7 +247,7 @@ function MoneyRequestConfirmationListFooter({ const canUpdateSenderWorkspace = useMemo(() => { const isInvoiceRoomParticipant = selectedParticipants.some((participant) => participant.isInvoiceRoom); - return PolicyUtils.canSendInvoice(allPolicies, currentUserLogin) && !!transaction?.isFromGlobalCreate && !isInvoiceRoomParticipant; + return canSendInvoice(allPolicies, currentUserLogin) && !!transaction?.isFromGlobalCreate && !isInvoiceRoomParticipant; }, [allPolicies, currentUserLogin, selectedParticipants, transaction?.isFromGlobalCreate]); const isTypeSend = iouType === CONST.IOU.TYPE.PAY; @@ -262,23 +263,23 @@ function MoneyRequestConfirmationListFooter({ // Do not hide fields in case of paying someone const shouldShowAllFields = !!isPerDiemRequest || !!isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || !!isEditingSplitBill; // Calculate the formatted tax amount based on the transaction's tax amount and the IOU currency code - const taxAmount = TransactionUtils.getTaxAmount(transaction, false); - const formattedTaxAmount = CurrencyUtils.convertToDisplayString(taxAmount, iouCurrencyCode); + const taxAmount = getTaxAmount(transaction, false); + const formattedTaxAmount = convertToDisplayString(taxAmount, iouCurrencyCode); // Get the tax rate title based on the policy and transaction - const taxRateTitle = TransactionUtils.getTaxName(policy, transaction); + const taxRateTitle = getTaxName(policy, transaction); // Determine if the merchant error should be displayed const shouldDisplayMerchantError = isMerchantRequired && (shouldDisplayFieldError || formError === 'iou.error.invalidMerchant') && isMerchantEmpty; // The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate") - const shouldShowReceiptEmptyState = iouType === CONST.IOU.TYPE.SUBMIT && PolicyUtils.isPaidGroupPolicy(policy) && !isPerDiemRequest; + const shouldShowReceiptEmptyState = iouType === CONST.IOU.TYPE.SUBMIT && isPaidGroupPolicy(policy) && !isPerDiemRequest; // The per diem custom unit - const perDiemCustomUnit = PolicyUtils.getPerDiemCustomUnit(policy); + const perDiemCustomUnit = getPerDiemCustomUnit(policy); const { image: receiptImage, thumbnail: receiptThumbnail, isThumbnail, fileExtension, isLocalFile, - } = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI); + } = receiptPath && receiptFilename ? getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ThumbnailAndImageURI); const resolvedThumbnail = isLocalFile ? receiptThumbnail : tryResolveUrlFromApiRoot(receiptThumbnail ?? ''); const resolvedReceiptImage = isLocalFile ? receiptImage : tryResolveUrlFromApiRoot(receiptImage ?? ''); @@ -317,8 +318,8 @@ function MoneyRequestConfirmationListFooter({ style={[styles.moneyRequestMenuItem, styles.mt2]} titleStyle={styles.moneyRequestConfirmationAmount} disabled={didConfirm} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} - errorText={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? translate('common.error.enterAmount') : ''} + brickRoadIndicator={shouldDisplayFieldError && isAmountMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + errorText={shouldDisplayFieldError && isAmountMissing(transaction) ? translate('common.error.enterAmount') : ''} /> ), shouldShow: shouldShowSmartScanFields && shouldShowAmountField, @@ -449,8 +450,8 @@ function MoneyRequestConfirmationListFooter({ }} disabled={didConfirm} interactive={!isReadOnly} - brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} - errorText={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? translate('common.error.enterDate') : ''} + brickRoadIndicator={shouldDisplayFieldError && isCreatedMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + errorText={shouldDisplayFieldError && isCreatedMissing(transaction) ? translate('common.error.enterDate') : ''} /> ), shouldShow: shouldShowDate, @@ -486,13 +487,13 @@ function MoneyRequestConfirmationListFooter({ }, ...policyTagLists.map(({name, required, tags}, index) => { const isTagRequired = required ?? false; - const shouldShow = shouldShowTags && (!isMultilevelTags || OptionsListUtils.hasEnabledOptions(tags)); + const shouldShow = shouldShowTags && (!isMultilevelTags || hasEnabledOptions(tags)); return { item: ( { @@ -604,7 +605,7 @@ function MoneyRequestConfirmationListFooter({ }, ]; - const subRates = PerDiemRequestUtils.getSubratesFields(perDiemCustomUnit, transaction); + const subRates = getSubratesFields(perDiemCustomUnit, transaction); const shouldDisplaySubrateError = isPerDiemRequest && (shouldDisplayFieldError || formError === 'iou.error.invalidSubrateLength') && (subRates.length === 0 || (subRates.length === 1 && !subRates.at(0))); @@ -612,7 +613,7 @@ function MoneyRequestConfirmationListFooter({ )); - const {firstDay, tripDays, lastDay} = PerDiemRequestUtils.getTimeDifferenceIntervals(transaction); + const {firstDay, tripDays, lastDay} = getTimeDifferenceIntervals(transaction); const badgeElements = useMemo(() => { const badges: React.JSX.Element[] = []; @@ -779,7 +780,7 @@ function MoneyRequestConfirmationListFooter({ <> { const queryWithSubstitutions = getQueryWithSubstitutions(queryString, autocompleteSubstitutions); - const updatedQuery = SearchQueryUtils.getQueryWithUpdatedValues(queryWithSubstitutions, activeWorkspaceID); + const updatedQuery = getQueryWithUpdatedValues(queryWithSubstitutions, activeWorkspaceID); if (!updatedQuery) { return; } @@ -230,8 +230,8 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps, setAutocompleteSubstitutions(substitutions); } } else if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) { - const trimmedUserSearchQuery = SearchAutocompleteUtils.getQueryWithoutAutocompletedPart(textInputValue); - onSearchQueryChange(`${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(item.searchQuery)} `); + const trimmedUserSearchQuery = getQueryWithoutAutocompletedPart(textInputValue); + onSearchQueryChange(`${trimmedUserSearchQuery}${sanitizeSearchValue(item.searchQuery)} `); if (item.mapKey && item.autocompleteID) { const substitutions = {...autocompleteSubstitutions, [item.mapKey]: item.autocompleteID}; @@ -249,7 +249,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps, if (item?.reportID) { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID)); } else if ('login' in item) { - ReportUserActions.navigateToAndOpenReport(item.login ? [item.login] : [], false); + navigateToAndOpenReport(item.login ? [item.login] : [], false); } } }, diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 2c526aedda94..297ad378527a 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -12,7 +12,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; -import * as Session from './Session'; +import {canAnonymousUserAccessRoute, isAnonymousUser, signOutAndRedirectToSignIn} from './Session'; let isNetworkOffline = false; Onyx.connect({ @@ -26,7 +26,7 @@ Onyx.connect({ key: ONYXKEYS.SESSION, callback: (value) => { currentUserEmail = value?.email ?? ''; - currentUserAccountID = value?.accountID ?? -1; + currentUserAccountID = value?.accountID ?? CONST.DEFAULT_NUMBER_ID; }, }); @@ -175,7 +175,7 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { // the reportID is extracted from the URL and then opened as an internal link, taking the user straight to the chat in the same tab. if (hasExpensifyOrigin && href.indexOf('newdotreport?reportID=') > -1) { const reportID = href.split('newdotreport?reportID=').pop(); - const reportRoute = ROUTES.REPORT_WITH_ID.getRoute(reportID ?? '-1'); + const reportRoute = ROUTES.REPORT_WITH_ID.getRoute(reportID); Navigation.navigate(reportRoute); return; } @@ -183,8 +183,8 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { // If we are handling a New Expensify link then we will assume this should be opened by the app internally. This ensures that the links are opened internally via react-navigation // instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag) if (internalNewExpensifyPath && hasSameOrigin) { - if (Session.isAnonymousUser() && !Session.canAnonymousUserAccessRoute(internalNewExpensifyPath)) { - Session.signOutAndRedirectToSignIn(); + if (isAnonymousUser() && !canAnonymousUserAccessRoute(internalNewExpensifyPath)) { + signOutAndRedirectToSignIn(); return; } Navigation.navigate(internalNewExpensifyPath as Route); From 88c99f424567cbfd74dde118f15aef6654877fb8 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Tue, 21 Jan 2025 14:06:55 +0100 Subject: [PATCH 08/20] fix: error about nested buttons in html --- src/components/Button/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index d943886982e4..eaeb4f8011a3 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -2,7 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import type {ForwardedRef} from 'react'; import React, {useCallback, useMemo, useState} from 'react'; import type {GestureResponderEvent, LayoutChangeEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; -import {ActivityIndicator, View} from 'react-native'; +import {ActivityIndicator, Platform, View} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; @@ -404,6 +404,7 @@ function Button( text && shouldShowRightIcon ? styles.alignItemsStretch : undefined, innerStyles, link && styles.bgTransparent, + Platform.OS === 'web' && styles.cursorPointer, ]} hoverStyle={[ shouldUseDefaultHover && !isDisabled ? styles.buttonDefaultHovered : undefined, @@ -415,7 +416,7 @@ function Button( id={id} testID={testID} accessibilityLabel={accessibilityLabel} - role={CONST.ROLE.BUTTON} + role={Platform.OS === 'web' ? CONST.ROLE.PRESENTATION : CONST.ROLE.BUTTON} hoverDimmingValue={1} onHoverIn={() => setIsHovered(true)} onHoverOut={() => setIsHovered(false)} From 7b7d70b9479061f8b7ba8d731bfc26c17f4b48ef Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Wed, 22 Jan 2025 11:53:02 +0100 Subject: [PATCH 09/20] fix nested button in html on search page --- .../SelectionList/Search/ExpenseItemHeaderNarrow.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx index 617c4e3770dd..ddf38790db85 100644 --- a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx +++ b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx @@ -1,5 +1,5 @@ import React, {memo} from 'react'; -import {View} from 'react-native'; +import {Platform, View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -62,7 +62,7 @@ function ExpenseItemHeaderNarrow({ {!!canSelectMultiple && ( handleCheckboxPress?.()} style={[styles.cursorUnset, StyleUtils.getCheckboxPressableStyle(), isDisabledCheckbox && styles.cursorDisabled, styles.mr1]} From 522c25f9f350ad8f14daa7d38ce1cbfe404bd2bd Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Thu, 23 Jan 2025 15:41:27 +0100 Subject: [PATCH 10/20] fix: resolved comments --- src/components/AvatarWithDisplayName.tsx | 43 +++++++++++-------- src/components/Button/index.tsx | 12 ++++-- src/components/Button/utils/index.ts | 5 +++ src/components/Button/utils/index.web.ts | 7 +++ src/components/Button/utils/types.ts | 7 +++ src/components/ReceiptAudit.tsx | 14 +++++- .../ReportActionItemImages.tsx | 10 ++--- .../ReportActionItem/ReportPreview.tsx | 7 +-- .../SelectionList/Search/ActionCell.tsx | 1 + .../Search/ExpenseItemHeaderNarrow.tsx | 6 +-- 10 files changed, 77 insertions(+), 35 deletions(-) create mode 100644 src/components/Button/utils/index.ts create mode 100644 src/components/Button/utils/index.web.ts create mode 100644 src/components/Button/utils/types.ts diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 7d130899e8bd..13879a942496 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -29,6 +29,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report} from '@src/types/onyx'; import type {Icon} from '@src/types/onyx/OnyxCommon'; +import {getNestedButtonRole} from './Button/utils'; import CaretWrapper from './CaretWrapper'; import DisplayNames from './DisplayNames'; import {FallbackAvatar} from './Icon/Expensicons'; @@ -132,22 +133,28 @@ function AvatarWithDisplayName({policy, report, isAnonymous = false, size = CONS {!!report && !!title && ( - - {shouldShowSubscriptAvatar ? ( - - ) : ( - - )} - + + + {shouldShowSubscriptAvatar ? ( + + ) : ( + + )} + + & { @@ -145,6 +146,9 @@ type ButtonProps = Partial & { /** Whether the Enter keyboard listening is active whether or not the screen that contains the button is focused */ isPressOnEnterActive?: boolean; + + /** Wheater is a nested button inside other button, since nesting buttons isn't valid html */ + isNested?: boolean; }; type KeyboardShortcutComponentProps = Pick; @@ -246,6 +250,7 @@ function Button( link = false, isContentCentered = false, isPressOnEnterActive, + isNested = false, ...rest }: ButtonProps, ref: ForwardedRef, @@ -400,11 +405,10 @@ function Button( isDisabled && !danger && !success ? styles.buttonDisabled : undefined, shouldRemoveRightBorderRadius ? styles.noRightBorderRadius : undefined, shouldRemoveLeftBorderRadius ? styles.noLeftBorderRadius : undefined, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing text && shouldShowRightIcon ? styles.alignItemsStretch : undefined, innerStyles, link && styles.bgTransparent, - Platform.OS === 'web' && styles.cursorPointer, + getNestedButtonStyle(styles, isNested), ]} hoverStyle={[ shouldUseDefaultHover && !isDisabled ? styles.buttonDefaultHovered : undefined, @@ -416,7 +420,7 @@ function Button( id={id} testID={testID} accessibilityLabel={accessibilityLabel} - role={Platform.OS === 'web' ? CONST.ROLE.PRESENTATION : CONST.ROLE.BUTTON} + role={getNestedButtonRole(isNested)} hoverDimmingValue={1} onHoverIn={() => setIsHovered(true)} onHoverOut={() => setIsHovered(false)} diff --git a/src/components/Button/utils/index.ts b/src/components/Button/utils/index.ts new file mode 100644 index 000000000000..1296bd4243f4 --- /dev/null +++ b/src/components/Button/utils/index.ts @@ -0,0 +1,5 @@ +import type {GetButtonRole, GetNestedButtonStyle} from './types'; + +const getNestedButtonStyle: GetNestedButtonStyle = () => undefined; +const getNestedButtonRole: GetButtonRole = () => undefined; +export {getNestedButtonStyle, getNestedButtonRole}; diff --git a/src/components/Button/utils/index.web.ts b/src/components/Button/utils/index.web.ts new file mode 100644 index 000000000000..7f063d284a75 --- /dev/null +++ b/src/components/Button/utils/index.web.ts @@ -0,0 +1,7 @@ +import CONST from '@src/CONST'; +import type {GetButtonRole, GetNestedButtonStyle} from './types'; + +const getNestedButtonStyle: GetNestedButtonStyle = (styles, isNested) => (isNested ? styles.cursorPointer : undefined); +const getNestedButtonRole: GetButtonRole = (isNested) => (isNested ? CONST.ROLE.PRESENTATION : CONST.ROLE.BUTTON); + +export {getNestedButtonStyle, getNestedButtonRole}; diff --git a/src/components/Button/utils/types.ts b/src/components/Button/utils/types.ts new file mode 100644 index 000000000000..cc426c1a41da --- /dev/null +++ b/src/components/Button/utils/types.ts @@ -0,0 +1,7 @@ +import type {Role, StyleProp, ViewStyle} from 'react-native'; + +type GetNestedButtonStyle = (styles: {cursorPointer: ViewStyle}, isNested: boolean) => StyleProp | undefined; + +type GetButtonRole = (isNested: boolean) => Role | undefined; + +export type {GetNestedButtonStyle, GetButtonRole}; diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index 29439911e221..5a56d4028c31 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -50,7 +50,19 @@ function ReceiptAudit({notes, shouldShowAuditResult}: ReceiptAuditProps) { function ReceiptAuditMessages({notes = []}: {notes?: string[]}) { const styles = useThemeStyles(); - return {notes.length > 0 && notes.map((message) => {message})}; + return ( + + {notes.length > 0 && + notes.map((message) => ( + + {message} + + ))} + + ); } export {ReceiptAuditMessages}; diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx index 633dca077070..182b7dc286ef 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.tsx +++ b/src/components/ReportActionItem/ReportActionItemImages.tsx @@ -79,11 +79,11 @@ function ReportActionItemImages({images, size, total, isHovered = false, onPress const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1; const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {}; return ( - - + + showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)} shouldUseHapticsOnLongPress // This is added to omit console error about nested buttons as its forbidden on web platform - style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox, Platform.OS === 'web' && {cursor: 'pointer'}]} - role={Platform.OS === 'web' ? 'presentation' : 'button'} + style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox, getNestedButtonStyle(styles, true)]} + role={getNestedButtonRole(true)} accessibilityLabel={translate('iou.viewDetails')} > diff --git a/src/components/SelectionList/Search/ActionCell.tsx b/src/components/SelectionList/Search/ActionCell.tsx index 6e0270c37e29..f8c4baaf02d7 100644 --- a/src/components/SelectionList/Search/ActionCell.tsx +++ b/src/components/SelectionList/Search/ActionCell.tsx @@ -100,6 +100,7 @@ function ActionCell({ icon={!isChildListItem && action === CONST.SEARCH.ACTION_TYPES.REVIEW ? Expensicons.DotIndicator : undefined} iconFill={theme.danger} iconHoverFill={theme.dangerHover} + isNested /> ) : null; } diff --git a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx index ddf38790db85..86edeaefa049 100644 --- a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx +++ b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx @@ -1,6 +1,7 @@ import React, {memo} from 'react'; -import {Platform, View} from 'react-native'; +import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; +import {getNestedButtonRole} from '@components/Button/utils'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithFeedback} from '@components/Pressable'; @@ -9,7 +10,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {isCorrectSearchUserName} from '@libs/SearchUIUtils'; import variables from '@styles/variables'; -import CONST from '@src/CONST'; import type {SearchPersonalDetails, SearchTransactionAction} from '@src/types/onyx/SearchResults'; import ActionCell from './ActionCell'; import UserInfoCell from './UserInfoCell'; @@ -62,7 +62,7 @@ function ExpenseItemHeaderNarrow({ {!!canSelectMultiple && ( handleCheckboxPress?.()} style={[styles.cursorUnset, StyleUtils.getCheckboxPressableStyle(), isDisabledCheckbox && styles.cursorDisabled, styles.mr1]} From 0511174ab4395a0cb45d4ffcb10eea95ff35f435 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Thu, 23 Jan 2025 17:54:19 +0100 Subject: [PATCH 11/20] fix: tests --- src/components/Button/utils/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Button/utils/index.ts b/src/components/Button/utils/index.ts index 1296bd4243f4..c8c56de98b8f 100644 --- a/src/components/Button/utils/index.ts +++ b/src/components/Button/utils/index.ts @@ -1,5 +1,6 @@ +import CONST from '@src/CONST'; import type {GetButtonRole, GetNestedButtonStyle} from './types'; const getNestedButtonStyle: GetNestedButtonStyle = () => undefined; -const getNestedButtonRole: GetButtonRole = () => undefined; +const getNestedButtonRole: GetButtonRole = () => CONST.ROLE.BUTTON; export {getNestedButtonStyle, getNestedButtonRole}; From 13137cbfeef6b30e99a22cffe403c09944f2651f Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Fri, 24 Jan 2025 11:44:45 +0100 Subject: [PATCH 12/20] fix: resolve comments --- src/components/AvatarWithDisplayName.tsx | 4 ++-- src/components/Button/index.tsx | 6 +++--- src/components/Button/utils/index.ts | 8 ++++---- src/components/Button/utils/index.web.ts | 8 ++++---- src/components/Button/utils/types.ts | 4 ++-- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx | 5 +++-- src/components/ReportActionItem/ReportPreview.tsx | 6 +++--- src/components/SelectionList/Search/ActionCell.tsx | 1 + .../SelectionList/Search/ExpenseItemHeaderNarrow.tsx | 4 ++-- 9 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 13879a942496..ce77b92940b5 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -29,7 +29,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report} from '@src/types/onyx'; import type {Icon} from '@src/types/onyx/OnyxCommon'; -import {getNestedButtonRole} from './Button/utils'; +import {getButtonRole} from './Button/utils'; import CaretWrapper from './CaretWrapper'; import DisplayNames from './DisplayNames'; import {FallbackAvatar} from './Icon/Expensicons'; @@ -136,7 +136,7 @@ function AvatarWithDisplayName({policy, report, isAnonymous = false, size = CONS {shouldShowSubscriptAvatar ? ( diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 182dc90c57d1..ce4032fb14c3 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -17,7 +17,7 @@ import HapticFeedback from '@libs/HapticFeedback'; import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type IconAsset from '@src/types/utils/IconAsset'; -import {getNestedButtonRole, getNestedButtonStyle} from './utils'; +import {getButtonRole, getButtonStyle} from './utils'; import validateSubmitShortcut from './validateSubmitShortcut'; type ButtonProps = Partial & { @@ -408,7 +408,7 @@ function Button( text && shouldShowRightIcon ? styles.alignItemsStretch : undefined, innerStyles, link && styles.bgTransparent, - getNestedButtonStyle(styles, isNested), + getButtonStyle(styles, isNested), ]} hoverStyle={[ shouldUseDefaultHover && !isDisabled ? styles.buttonDefaultHovered : undefined, @@ -420,7 +420,7 @@ function Button( id={id} testID={testID} accessibilityLabel={accessibilityLabel} - role={getNestedButtonRole(isNested)} + role={getButtonRole(isNested)} hoverDimmingValue={1} onHoverIn={() => setIsHovered(true)} onHoverOut={() => setIsHovered(false)} diff --git a/src/components/Button/utils/index.ts b/src/components/Button/utils/index.ts index c8c56de98b8f..4eeafe89dc19 100644 --- a/src/components/Button/utils/index.ts +++ b/src/components/Button/utils/index.ts @@ -1,6 +1,6 @@ import CONST from '@src/CONST'; -import type {GetButtonRole, GetNestedButtonStyle} from './types'; +import type {GetButtonRole, GetButtonStyle} from './types'; -const getNestedButtonStyle: GetNestedButtonStyle = () => undefined; -const getNestedButtonRole: GetButtonRole = () => CONST.ROLE.BUTTON; -export {getNestedButtonStyle, getNestedButtonRole}; +const getButtonStyle: GetButtonStyle = () => undefined; +const getButtonRole: GetButtonRole = () => CONST.ROLE.BUTTON; +export {getButtonStyle, getButtonRole}; diff --git a/src/components/Button/utils/index.web.ts b/src/components/Button/utils/index.web.ts index 7f063d284a75..0eb9740e69bd 100644 --- a/src/components/Button/utils/index.web.ts +++ b/src/components/Button/utils/index.web.ts @@ -1,7 +1,7 @@ import CONST from '@src/CONST'; -import type {GetButtonRole, GetNestedButtonStyle} from './types'; +import type {GetButtonRole, GetButtonStyle} from './types'; -const getNestedButtonStyle: GetNestedButtonStyle = (styles, isNested) => (isNested ? styles.cursorPointer : undefined); -const getNestedButtonRole: GetButtonRole = (isNested) => (isNested ? CONST.ROLE.PRESENTATION : CONST.ROLE.BUTTON); +const getButtonStyle: GetButtonStyle = (styles, isNested) => (isNested ? styles.cursorPointer : undefined); +const getButtonRole: GetButtonRole = (isNested) => (isNested ? CONST.ROLE.PRESENTATION : CONST.ROLE.BUTTON); -export {getNestedButtonStyle, getNestedButtonRole}; +export {getButtonStyle, getButtonRole}; diff --git a/src/components/Button/utils/types.ts b/src/components/Button/utils/types.ts index cc426c1a41da..06d7a54a2073 100644 --- a/src/components/Button/utils/types.ts +++ b/src/components/Button/utils/types.ts @@ -1,7 +1,7 @@ import type {Role, StyleProp, ViewStyle} from 'react-native'; -type GetNestedButtonStyle = (styles: {cursorPointer: ViewStyle}, isNested: boolean) => StyleProp | undefined; +type GetButtonStyle = (styles: {cursorPointer: ViewStyle}, isNested: boolean) => StyleProp | undefined; type GetButtonRole = (isNested: boolean) => Role | undefined; -export type {GetNestedButtonStyle, GetButtonRole}; +export type {GetButtonStyle, GetButtonRole}; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 4c8e941acb87..4d935239fe36 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -3,6 +3,7 @@ import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import type {CustomRendererProps, TBlock} from 'react-native-render-html'; import {AttachmentContext} from '@components/AttachmentContext'; +import {getButtonRole, getButtonStyle} from '@components/Button/utils'; import {isDeletedNode} from '@components/HTMLEngineProvider/htmlEngineUtils'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus'; @@ -103,7 +104,7 @@ function ImageRenderer({tnode}: ImageRendererProps) { {({reportID, accountID, type}) => ( { if (!source || !type) { return; @@ -127,7 +128,7 @@ function ImageRenderer({tnode}: ImageRendererProps) { ); }} shouldUseHapticsOnLongPress - accessibilityRole={CONST.ROLE.BUTTON} + role={getButtonRole(true)} accessibilityLabel={translate('accessibilityHints.viewAttachment')} > {thumbnailImageComponent} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index a6dd6dbb20aa..a067e353031b 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -5,7 +5,7 @@ import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Animated, {useAnimatedStyle, useSharedValue, withDelay, withSpring, withTiming} from 'react-native-reanimated'; import Button from '@components/Button'; -import {getNestedButtonRole, getNestedButtonStyle} from '@components/Button/utils'; +import {getButtonRole, getButtonStyle} from '@components/Button/utils'; import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -542,8 +542,8 @@ function ReportPreview({ onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)} shouldUseHapticsOnLongPress // This is added to omit console error about nested buttons as its forbidden on web platform - style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox, getNestedButtonStyle(styles, true)]} - role={getNestedButtonRole(true)} + style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox, getButtonStyle(styles, true)]} + role={getButtonRole(true)} accessibilityLabel={translate('iou.viewDetails')} > diff --git a/src/components/SelectionList/Search/ActionCell.tsx b/src/components/SelectionList/Search/ActionCell.tsx index f8c4baaf02d7..b8b4b698fcad 100644 --- a/src/components/SelectionList/Search/ActionCell.tsx +++ b/src/components/SelectionList/Search/ActionCell.tsx @@ -115,6 +115,7 @@ function ActionCell({ isLoading={isLoading} success={shouldUseSuccessStyleProp} isDisabled={isOffline} + isNested /> ); } diff --git a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx index 86edeaefa049..e8476611b1da 100644 --- a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx +++ b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx @@ -1,7 +1,7 @@ import React, {memo} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; -import {getNestedButtonRole} from '@components/Button/utils'; +import {getButtonRole} from '@components/Button/utils'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithFeedback} from '@components/Pressable'; @@ -62,7 +62,7 @@ function ExpenseItemHeaderNarrow({ {!!canSelectMultiple && ( handleCheckboxPress?.()} style={[styles.cursorUnset, StyleUtils.getCheckboxPressableStyle(), isDisabledCheckbox && styles.cursorDisabled, styles.mr1]} From 75ad3d2957ee063c6e198f792bec97c203b93586 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Fri, 24 Jan 2025 11:52:28 +0100 Subject: [PATCH 13/20] fix missing key in map --- src/pages/Search/AdvancedSearchFilters.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 005c5c96837b..406267068849 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -519,7 +519,8 @@ function AdvancedSearchFilters() { {filters.map((section, index) => { return ( - <> + // eslint-disable-next-line react/no-array-index-key + {index !== 0 && ( ); })} - + ); })} From 033f142af50b944ee98bdb5b0f8967fcb44f3af8 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Fri, 24 Jan 2025 13:40:42 +0100 Subject: [PATCH 14/20] fix lint --- .../HTMLRenderers/ImageRenderer.tsx | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 4d935239fe36..d9d079549d8d 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -12,9 +12,9 @@ import ThumbnailImage from '@components/ThumbnailImage'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as FileUtils from '@libs/fileDownload/FileUtils'; +import {getFileName, getFileType, splitExtensionFromFileName} from '@libs/fileDownload/FileUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; +import {isArchivedNonExpenseReport} from '@libs/ReportUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -71,12 +71,12 @@ function ImageRenderer({tnode}: ImageRendererProps) { const imageHeight = (htmlAttribs['data-expensify-height'] && parseInt(htmlAttribs['data-expensify-height'], 10)) || undefined; const imagePreviewModalDisabled = htmlAttribs['data-expensify-preview-modal-disabled'] === 'true'; - const fileType = FileUtils.getFileType(attachmentSourceAttribute); + const fileType = getFileType(attachmentSourceAttribute); const fallbackIcon = fileType === CONST.ATTACHMENT_FILE_TYPE.FILE ? Expensicons.Document : Expensicons.GalleryNotFound; const theme = useTheme(); - let fileName = htmlAttribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || FileUtils.getFileName(`${isAttachmentOrReceipt ? attachmentSourceAttribute : htmlAttribs.src}`); - const fileInfo = FileUtils.splitExtensionFromFileName(fileName); + let fileName = htmlAttribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE] || getFileName(`${isAttachmentOrReceipt ? attachmentSourceAttribute : htmlAttribs.src}`); + const fileInfo = splitExtensionFromFileName(fileName); if (!fileInfo.fileExtension) { fileName = `${fileInfo?.fileName || CONST.DEFAULT_IMAGE_FILE_NAME}.jpg`; } @@ -118,14 +118,7 @@ function ImageRenderer({tnode}: ImageRendererProps) { if (isDisabled) { return; } - showContextMenuForReport( - event, - anchor, - report?.reportID, - action, - checkIfContextMenuActive, - ReportUtils.isArchivedNonExpenseReport(report, reportNameValuePairs), - ); + showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, reportNameValuePairs)); }} shouldUseHapticsOnLongPress role={getButtonRole(true)} From 5b4bf77486797191ca0c3116aefbcb97dfbddcee Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Mon, 27 Jan 2025 10:44:38 +0100 Subject: [PATCH 15/20] fix nested buttons on Workspace settings --- src/components/ThreeDotsMenu/index.tsx | 6 ++++-- src/components/ThreeDotsMenu/types.ts | 3 +++ src/pages/workspace/WorkspacesListRow.tsx | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/ThreeDotsMenu/index.tsx b/src/components/ThreeDotsMenu/index.tsx index 5bf8e52642f1..6dc44be378e5 100644 --- a/src/components/ThreeDotsMenu/index.tsx +++ b/src/components/ThreeDotsMenu/index.tsx @@ -1,6 +1,7 @@ import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import {getButtonRole, getButtonStyle} from '@components/Button/utils'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PopoverMenu from '@components/PopoverMenu'; @@ -34,6 +35,7 @@ function ThreeDotsMenu({ hideProductTrainingTooltip, renderProductTrainingTooltipContent, shouldShowProductTrainingTooltip = false, + isNested = false, }: ThreeDotsMenuProps) { const [modal] = useOnyx(ONYXKEYS.MODAL); @@ -104,8 +106,8 @@ function ThreeDotsMenu({ e.preventDefault(); }} ref={buttonRef} - style={[styles.touchableButtonImage, iconStyles]} - role={CONST.ROLE.BUTTON} + style={[styles.touchableButtonImage, iconStyles, getButtonStyle(styles, isNested)]} + role={getButtonRole(isNested)} accessibilityLabel={translate(iconTooltip)} > From 44c8505ef971e578fe98ea2969a6835b1a081634 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Fri, 31 Jan 2025 16:58:22 +0100 Subject: [PATCH 16/20] fix nested buttons errors --- src/components/SelectionList/BaseListItem.tsx | 4 +++- .../SelectionList/Search/TransactionListItemRow.tsx | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index 57fd457b4b00..d9c6106ea393 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -1,5 +1,6 @@ import React, {useRef} from 'react'; import {View} from 'react-native'; +import {getButtonRole, getButtonStyle} from '@components/Button/utils'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -92,7 +93,7 @@ function BaseListItem({ disabled={isDisabled && !item.isSelected} interactive={item.isInteractive} accessibilityLabel={item.text ?? ''} - role={CONST.ROLE.BUTTON} + role={getButtonRole(true)} hoverDimmingValue={1} hoverStyle={[!item.isDisabled && item.isInteractive !== false && styles.hoveredComponentBG, hoverStyle]} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true, [CONST.INNER_BOX_SHADOW_ELEMENT]: true}} @@ -101,6 +102,7 @@ function BaseListItem({ style={[ pressableStyle, isFocused && StyleUtils.getItemBackgroundColorStyle(!!item.isSelected, !!isFocused, !!item.isDisabled, theme.activeComponentBG, theme.hoverComponentBG), + getButtonStyle(styles, true), ]} onFocus={onFocus} onMouseLeave={handleMouseLeave} diff --git a/src/components/SelectionList/Search/TransactionListItemRow.tsx b/src/components/SelectionList/Search/TransactionListItemRow.tsx index 4b424246ea49..a6a91c648f2b 100644 --- a/src/components/SelectionList/Search/TransactionListItemRow.tsx +++ b/src/components/SelectionList/Search/TransactionListItemRow.tsx @@ -2,6 +2,7 @@ import {Str} from 'expensify-common'; import React from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; +import {getButtonRole, getButtonStyle} from '@components/Button/utils'; import Checkbox from '@components/Checkbox'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -298,10 +299,10 @@ function TransactionListItemRow({ {canSelectMultiple && !!shouldShowTransactionCheckbox && ( {!!item.isSelected && ( From f763a8126ac40a20c38753c903f26602430f9087 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Mon, 3 Feb 2025 10:23:39 +0100 Subject: [PATCH 17/20] fix missing key prop --- .../HTMLRenderers/DeletedActionRenderer.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx index 4e6334d90ebd..6c3528437a01 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/DeletedActionRenderer.tsx @@ -43,7 +43,14 @@ function DeletedActionRenderer({tnode}: CustomRendererProps) const data = firstChild && 'data' in firstChild ? firstChild.data : null; if (typeof data === 'string') { - return {data}; + return ( + + {data} + + ); } return props.childElement; }} From a298ed4718227755fc8e44586be6febfae136e83 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Wed, 5 Feb 2025 13:08:12 +0100 Subject: [PATCH 18/20] fix nested buttons warning --- src/components/ReferralProgramCTA.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/ReferralProgramCTA.tsx b/src/components/ReferralProgramCTA.tsx index 808df9c4d94d..b2ceac58e43d 100644 --- a/src/components/ReferralProgramCTA.tsx +++ b/src/components/ReferralProgramCTA.tsx @@ -7,6 +7,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import Navigation from '@src/libs/Navigation/Navigation'; import ROUTES from '@src/ROUTES'; +import {getButtonRole, getButtonStyle} from './Button/utils'; import Icon from './Icon'; import {Close} from './Icon/Expensicons'; import {PressableWithoutFeedback} from './Pressable'; @@ -52,9 +53,19 @@ function ReferralProgramCTA({referralContentType, style, onDismiss}: ReferralPro onPress={() => { Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(referralContentType, Navigation.getActiveRouteWithoutParams())); }} - style={[styles.br2, styles.highlightBG, styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, {gap: 10, padding: 10}, styles.pl5, style]} + style={[ + styles.br2, + styles.highlightBG, + styles.flexRow, + styles.justifyContentBetween, + styles.alignItemsCenter, + {gap: 10, padding: 10}, + styles.pl5, + getButtonStyle(styles, true), + style, + ]} accessibilityLabel="referral" - role={CONST.ROLE.BUTTON} + role={getButtonRole(true)} > {translate(`referralProgram.${referralContentType}.buttonText1`)} From 0ad061def243f50f49ac2b3c3e95439e1be154b9 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Thu, 6 Feb 2025 17:40:33 +0100 Subject: [PATCH 19/20] fix error ref.measureLayout need to be called with native component --- ...e-draggable-flatlist+4.0.1+001+initial.patch} | 0 ...002+fix-console-error-ref-measureLayout.patch | 16 ++++++++++++++++ 2 files changed, 16 insertions(+) rename patches/{react-native-draggable-flatlist+4.0.1.patch => react-native-draggable-flatlist+4.0.1+001+initial.patch} (100%) create mode 100644 patches/react-native-draggable-flatlist+4.0.1+002+fix-console-error-ref-measureLayout.patch diff --git a/patches/react-native-draggable-flatlist+4.0.1.patch b/patches/react-native-draggable-flatlist+4.0.1+001+initial.patch similarity index 100% rename from patches/react-native-draggable-flatlist+4.0.1.patch rename to patches/react-native-draggable-flatlist+4.0.1+001+initial.patch diff --git a/patches/react-native-draggable-flatlist+4.0.1+002+fix-console-error-ref-measureLayout.patch b/patches/react-native-draggable-flatlist+4.0.1+002+fix-console-error-ref-measureLayout.patch new file mode 100644 index 000000000000..883594ac044d --- /dev/null +++ b/patches/react-native-draggable-flatlist+4.0.1+002+fix-console-error-ref-measureLayout.patch @@ -0,0 +1,16 @@ +diff --git a/node_modules/react-native-draggable-flatlist/src/components/NestableDraggableFlatList.tsx b/node_modules/react-native-draggable-flatlist/src/components/NestableDraggableFlatList.tsx +index 1559352..b84ee99 100644 +--- a/node_modules/react-native-draggable-flatlist/src/components/NestableDraggableFlatList.tsx ++++ b/node_modules/react-native-draggable-flatlist/src/components/NestableDraggableFlatList.tsx +@@ -56,6 +56,11 @@ function NestableDraggableFlatListInner( + const onFail = () => { + console.log("## nested draggable list measure fail"); + }; ++ ++ if (typeof nodeHandle === "number" ) { ++ return; ++ } ++ + //@ts-ignore + containerRef.current.measureLayout(nodeHandle, onSuccess, onFail); + }); From dbe79e78b7d7b42e50b6736fd31071ece146e8da Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Tue, 18 Feb 2025 09:54:33 +0100 Subject: [PATCH 20/20] fix typos --- src/components/Button/index.tsx | 2 +- src/components/ThreeDotsMenu/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index e94bc97661be..76ba73152335 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -147,7 +147,7 @@ type ButtonProps = Partial & { /** Whether the Enter keyboard listening is active whether or not the screen that contains the button is focused */ isPressOnEnterActive?: boolean; - /** Wheater is a nested button inside other button, since nesting buttons isn't valid html */ + /** Whether is a nested button inside other button, since nesting buttons isn't valid html */ isNested?: boolean; /** The text displays under the first line */ diff --git a/src/components/ThreeDotsMenu/types.ts b/src/components/ThreeDotsMenu/types.ts index d848981ea344..28ce20da6da9 100644 --- a/src/components/ThreeDotsMenu/types.ts +++ b/src/components/ThreeDotsMenu/types.ts @@ -48,7 +48,7 @@ type ThreeDotsMenuProps = { /** Should we render the tooltip */ shouldShowProductTrainingTooltip?: boolean; - /** Is the menu nested? This prop is used to omit html warning when we nesting button inside button */ + /** Is the menu nested? This prop is used to omit html warning when we are nesting a button inside another button */ isNested?: boolean; };