From c446d64bfaec0073fb58d978fcfd86c76bde38e9 Mon Sep 17 00:00:00 2001 From: Amoralchik Date: Thu, 16 Jan 2025 16:21:43 +0200 Subject: [PATCH 01/10] Add custom emoji functionality and for onboarding task --- src/CONST.ts | 14 +- src/components/FloatingActionButton.tsx | 72 ++++-- .../BaseHTMLEngineProvider.tsx | 1 + .../HTMLRenderers/CustomEmojiRenderer.tsx | 13 + .../HTMLEngineProvider/HTMLRenderers/index.ts | 2 + src/libs/ReportUtils.ts | 18 +- src/libs/actions/Report.ts | 1 + .../FloatingActionButtonAndPopover.tsx | 230 ++++++++++-------- .../SidebarScreen/FloatingActionEmoji.tsx | 8 + src/styles/index.ts | 9 + 10 files changed, 227 insertions(+), 141 deletions(-) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx create mode 100644 src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 9fde47bca7bf..e1e39fa1dff4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -121,7 +121,7 @@ const onboardingEmployerOrSubmitMessage: OnboardingMessage = { '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + @@ -144,7 +144,7 @@ const combinedTrackSubmitOnboardingEmployerOrSubmitMessage: OnboardingMessage = '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + @@ -175,7 +175,7 @@ const onboardingPersonalSpendMessage: OnboardingMessage = { '\n' + 'Here’s how to track an expense:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button.\n' + '2. Choose *Track expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Click *Track*.\n' + @@ -197,7 +197,7 @@ const combinedTrackSubmitOnboardingPersonalSpendMessage: OnboardingMessage = { '\n' + 'Here’s how to track an expense:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Click "Just track it (don\'t submit it)".\n' + @@ -5127,7 +5127,7 @@ const CONST = { '\n' + 'Here’s how to start a chat:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button.\n' + '2. Choose *Start chat*.\n' + '3. Enter emails or phone numbers.\n' + '\n' + @@ -5144,7 +5144,7 @@ const CONST = { '\n' + 'Here’s how to request money:\n' + '\n' + - '1. Hit the green *+* button.\n' + + '1. Press the button\n' + '2. Choose *Start chat*.\n' + '3. Enter any email, SMS, or name of who you want to split with.\n' + '4. From within the chat, hit the *+* button on the message bar, and hit *Split expense*.\n' + @@ -5194,7 +5194,7 @@ const CONST = { '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 04bc4847a00f..aab270ae71b1 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -58,9 +58,12 @@ type FloatingActionButtonProps = { /* An accessibility role for the button */ role: Role; + + /* An accessibility render as emoji for the button */ + isEmoji?: boolean; }; -function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: FloatingActionButtonProps, ref: ForwardedRef) { +function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEmoji}: FloatingActionButtonProps, ref: ForwardedRef) { const {success, buttonDefaultBG, textLight, textDark} = useTheme(); const styles = useThemeStyles(); const borderRadius = styles.floatingActionButton.borderRadius; @@ -117,6 +120,46 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo onPress(event); }; + const floatingActionStyle = isEmoji ? styles.floatingActionEmoji : styles.floatingActionButton; + const iconSizeStyle = isEmoji ? variables.iconSizeExtraSmall : variables.iconSizeNormal; + const nativePositionFix = Platform.OS === 'web' ? [] : {height: '6%'}; + const pressebleStyles = isEmoji ? [nativePositionFix] : [styles.h100, styles.bottomTabBarItem]; + const animatedStyles = [floatingActionStyle, isEmoji ? {} : animatedStyle]; + + const button = ( + { + fabPressable.current = el ?? null; + if (buttonRef && 'current' in buttonRef) { + buttonRef.current = el ?? null; + } + }} + style={pressebleStyles} + accessibilityLabel={accessibilityLabel} + onPress={toggleFabAction} + onLongPress={() => {}} + role={role} + shouldUseHapticsOnLongPress={false} + > + + + + + + + ); + + if (isEmoji) { + return button; + } + return ( - { - fabPressable.current = el ?? null; - if (buttonRef && 'current' in buttonRef) { - buttonRef.current = el ?? null; - } - }} - style={[styles.h100, styles.bottomTabBarItem]} - accessibilityLabel={accessibilityLabel} - onPress={toggleFabAction} - onLongPress={() => {}} - role={role} - shouldUseHapticsOnLongPress={false} - > - - - - - - + {button} ); } diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx index b4002767524f..b7b0572181b6 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx @@ -75,6 +75,7 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim 'mention-user': HTMLElementModel.fromCustomModel({tagName: 'mention-user', contentModel: HTMLContentModel.textual}), 'mention-report': HTMLElementModel.fromCustomModel({tagName: 'mention-report', contentModel: HTMLContentModel.textual}), 'mention-here': HTMLElementModel.fromCustomModel({tagName: 'mention-here', contentModel: HTMLContentModel.textual}), + 'custom-emoji': HTMLElementModel.fromCustomModel({tagName: 'custom-emoji', contentModel: HTMLContentModel.textual}), 'next-step': HTMLElementModel.fromCustomModel({ tagName: 'next-step', mixedUAStyles: {...styles.textLabelSupporting, ...styles.lh16}, diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx new file mode 100644 index 000000000000..5a739060a160 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; +import FloatingActionEmoji from '@pages/home/sidebar/SidebarScreen/FloatingActionEmoji'; + +function CustomEmojiRenderer({tnode}: CustomRendererProps) { + if (tnode.attributes.emoji === 'action-menu-icon') { + return ; + } + + return ''; +} + +export default CustomEmojiRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts index ce24584048b0..4d589591c03f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts @@ -1,6 +1,7 @@ import type {CustomTagRendererRecord} from 'react-native-render-html'; import AnchorRenderer from './AnchorRenderer'; import CodeRenderer from './CodeRenderer'; +import CustomEmojiRenderer from './CustomEmojiRenderer'; import EditedRenderer from './EditedRenderer'; import EmojiRenderer from './EmojiRenderer'; import ImageRenderer from './ImageRenderer'; @@ -28,6 +29,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = { 'mention-user': MentionUserRenderer, 'mention-report': MentionReportRenderer, 'mention-here': MentionHereRenderer, + 'custom-emoji': CustomEmojiRenderer, emoji: EmojiRenderer, 'next-step-email': NextStepEmailRenderer, /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 57951ece5c21..7a5f12317634 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4522,7 +4522,7 @@ function completeShortMention(text: string): string { * For comments shorter than or equal to 10k chars, convert the comment from MD into HTML because that's how it is stored in the database * For longer comments, skip parsing, but still escape the text, and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!! */ -function getParsedComment(text: string, parsingDetails?: ParsingDetails): string { +function getParsedComment(text: string, parsingDetails?: ParsingDetails, isAllowCustomEmoji?: boolean): string { let isGroupPolicyReport = false; if (parsingDetails?.reportID) { const currentReport = getReportOrDraftReport(parsingDetails?.reportID); @@ -4538,9 +4538,16 @@ function getParsedComment(text: string, parsingDetails?: ParsingDetails): string const textWithMention = completeShortMention(text); - return text.length <= CONST.MAX_MARKUP_LENGTH - ? Parser.replace(textWithMention, {shouldEscapeText: parsingDetails?.shouldEscapeText, disabledRules: isGroupPolicyReport ? [] : ['reportMentions']}) - : lodashEscape(text); + let result = + text.length <= CONST.MAX_MARKUP_LENGTH + ? Parser.replace(textWithMention, {shouldEscapeText: parsingDetails?.shouldEscapeText, disabledRules: isGroupPolicyReport ? [] : ['reportMentions']}) + : lodashEscape(text); + + if (isAllowCustomEmoji) { + result = result.replace(/<custom-emoji emoji="/g, ''); + } + + return result; } function getUploadingAttachmentHtml(file?: FileObject): string { @@ -6342,6 +6349,7 @@ function buildOptimisticTaskReport( description?: string, policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + isAllowCustomEmoji?: boolean, ): OptimisticTaskReport { const participants: Participants = { [ownerAccountID]: { @@ -6356,7 +6364,7 @@ function buildOptimisticTaskReport( return { reportID: generateReportID(), reportName: title, - description: getParsedComment(description ?? ''), + description: getParsedComment(description ?? '', undefined, isAllowCustomEmoji), ownerAccountID, participants, managerID: assigneeAccountID, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 709d160a6f08..a1b3f2fadcf9 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3695,6 +3695,7 @@ function prepareOnboardingOptimisticData( taskDescription, targetChatPolicyID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + true, // is Allow render custom emoji ); const emailCreatingAction = engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ? allPersonalDetails?.[actorAccountID]?.login ?? CONST.EMAIL.CONCIERGE : CONST.EMAIL.CONCIERGE; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 5c082e3d50f7..1a9c0cfaa788 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -68,6 +68,9 @@ type FloatingActionButtonAndPopoverProps = { /* Callback function before the menu is hidden */ onHideCreateMenu?: () => void; + + /* An accessibility render as emoji for the button */ + isEmoji?: boolean; }; type FloatingActionButtonAndPopoverRef = { @@ -167,7 +170,7 @@ const getQuickActionTitle = (action: QuickActionName): TranslationPaths => { * Responsible for rendering the {@link PopoverMenu}, and the accompanying * FAB that can open or close the menu. */ -function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef) { +function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isEmoji}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -452,109 +455,132 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl const canModifyTask = Task.canModifyTask(viewTourTaskReport, currentUserPersonalDetails.accountID); const canActionTask = Task.canActionTask(viewTourTaskReport, currentUserPersonalDetails.accountID); - return ( - - interceptAnonymousUser(Report.startNewChat), - }, - ...(canSendInvoice - ? [ - { - icon: Expensicons.InvoiceGeneric, - text: translate('workspace.invoices.sendInvoice'), - shouldCallAfterModalHide: shouldRedirectToExpensifyClassic, - onSelected: () => - interceptAnonymousUser(() => { - if (shouldRedirectToExpensifyClassic) { - setModalVisible(true); - return; - } - - IOU.startMoneyRequest( - CONST.IOU.TYPE.INVOICE, - // When starting to create an invoice from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used - // for all of the routes in the creation flow. - ReportUtils.generateReportID(), - ); - }), - }, - ] - : []), - ...(canUseSpotnanaTravel - ? [ - { - icon: Expensicons.Suitcase, - text: translate('travel.bookTravel'), - onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS)), - }, - ] - : []), - ...(!hasSeenTour - ? [ - { - icon: Expensicons.Binoculars, - iconStyles: styles.popoverIconCircle, - iconFill: theme.icon, - text: translate('tour.takeATwoMinuteTour'), - description: translate('tour.exploreExpensify'), - onSelected: () => { - Link.openExternalLink(navatticURL); - Welcome.setSelfTourViewed(Session.isAnonymousUser()); - if (viewTourTaskReport && canModifyTask && canActionTask) { - Task.completeTask(viewTourTaskReport); + const popoverMenu = ( + interceptAnonymousUser(Report.startNewChat), + }, + ...(canSendInvoice + ? [ + { + icon: Expensicons.InvoiceGeneric, + text: translate('workspace.invoices.sendInvoice'), + shouldCallAfterModalHide: shouldRedirectToExpensifyClassic, + onSelected: () => + interceptAnonymousUser(() => { + if (shouldRedirectToExpensifyClassic) { + setModalVisible(true); + return; } - }, - }, - ] - : []), - ...(!isLoading && shouldShowNewWorkspaceButton - ? [ - { - displayInDefaultIconColor: true, - contentFit: 'contain' as ImageContentFit, - icon: Expensicons.NewWorkspace, - iconWidth: variables.w46, - iconHeight: variables.h40, - text: translate('workspace.new.newWorkspace'), - description: translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()), + + IOU.startMoneyRequest( + CONST.IOU.TYPE.INVOICE, + // When starting to create an invoice from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used + // for all of the routes in the creation flow. + ReportUtils.generateReportID(), + ); + }), + }, + ] + : []), + ...(canUseSpotnanaTravel + ? [ + { + icon: Expensicons.Suitcase, + text: translate('travel.bookTravel'), + onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS)), + }, + ] + : []), + ...(!hasSeenTour + ? [ + { + icon: Expensicons.Binoculars, + iconStyles: styles.popoverIconCircle, + iconFill: theme.icon, + text: translate('tour.takeATwoMinuteTour'), + description: translate('tour.exploreExpensify'), + onSelected: () => { + Link.openExternalLink(navatticURL); + Welcome.setSelfTourViewed(Session.isAnonymousUser()); + if (viewTourTaskReport && canModifyTask && canActionTask) { + Task.completeTask(viewTourTaskReport); + } }, - ] - : []), - ...quickActionMenuItems, - ]} - withoutOverlay - anchorRef={fabRef} - /> - { - setModalVisible(false); - Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX); - }} - onCancel={() => setModalVisible(false)} - title={translate('sidebarScreen.redirectToExpensifyClassicModal.title')} - confirmText={translate('exitSurvey.goToExpensifyClassic')} - cancelText={translate('common.cancel')} - /> - + }, + ] + : []), + ...(!isLoading && shouldShowNewWorkspaceButton + ? [ + { + displayInDefaultIconColor: true, + contentFit: 'contain' as ImageContentFit, + icon: Expensicons.NewWorkspace, + iconWidth: variables.w46, + iconHeight: variables.h40, + text: translate('workspace.new.newWorkspace'), + description: translate('workspace.new.getTheExpensifyCardAndMore'), + onSelected: () => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()), + }, + ] + : []), + ...quickActionMenuItems, + ]} + withoutOverlay + anchorRef={fabRef} + /> + ); + const confirmModal = ( + { + setModalVisible(false); + Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX); + }} + onCancel={() => setModalVisible(false)} + title={translate('sidebarScreen.redirectToExpensifyClassicModal.title')} + confirmText={translate('exitSurvey.goToExpensifyClassic')} + cancelText={translate('common.cancel')} + /> + ); + const floatingActionButton = ( + + ); + + if (isEmoji) { + return ( + <> + + {popoverMenu} + {confirmModal} + + {floatingActionButton} + + ); + } + + return ( + + {popoverMenu} + {confirmModal} + {floatingActionButton} ); } diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx new file mode 100644 index 000000000000..fa5217acb083 --- /dev/null +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; + +function FloatingActionEmoji() { + return ; +} + +export default FloatingActionEmoji; diff --git a/src/styles/index.ts b/src/styles/index.ts index 06feb42b3fe2..6857c727fad1 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1658,6 +1658,15 @@ const styles = (theme: ThemeColors) => justifyContent: 'center', }, + floatingActionEmoji: { + backgroundColor: theme.success, + height: variables.iconSizeSmall, + width: variables.iconSizeSmall, + borderRadius: 999, + alignItems: 'center', + justifyContent: 'center', + }, + sidebarFooterUsername: { color: theme.heading, fontSize: variables.fontSizeLabel, From 238cfa80263289850fa7cdda2f5376c2ff73ab51 Mon Sep 17 00:00:00 2001 From: Amoralchik Date: Fri, 17 Jan 2025 13:11:23 +0200 Subject: [PATCH 02/10] Add GlobalCreate svg and update FloatingActionButton to use it --- assets/images/customEmoji/global-create.svg | 14 ++++ src/components/FloatingActionButton.tsx | 87 ++++++++++++--------- src/styles/index.ts | 9 --- 3 files changed, 63 insertions(+), 47 deletions(-) create mode 100644 assets/images/customEmoji/global-create.svg diff --git a/assets/images/customEmoji/global-create.svg b/assets/images/customEmoji/global-create.svg new file mode 100644 index 000000000000..60b46eb97aed --- /dev/null +++ b/assets/images/customEmoji/global-create.svg @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index aab270ae71b1..ee29a37a0674 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -6,6 +6,7 @@ import {Platform} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import Svg, {Path} from 'react-native-svg'; +import GlobalCreateIcon from '@assets/images/customEmoji/global-create.svg'; import useBottomTabIsFocused from '@hooks/useBottomTabIsFocused'; import useIsCurrentRouteHome from '@hooks/useIsCurrentRouteHome'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -15,6 +16,7 @@ import getPlatform from '@libs/getPlatform'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ImageSVG from './ImageSVG'; import {PressableWithoutFeedback} from './Pressable'; import {useProductTrainingContext} from './ProductTrainingContext'; import EducationalTooltip from './Tooltip/EducationalTooltip'; @@ -120,44 +122,28 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEm onPress(event); }; - const floatingActionStyle = isEmoji ? styles.floatingActionEmoji : styles.floatingActionButton; - const iconSizeStyle = isEmoji ? variables.iconSizeExtraSmall : variables.iconSizeNormal; - const nativePositionFix = Platform.OS === 'web' ? [] : {height: '6%'}; - const pressebleStyles = isEmoji ? [nativePositionFix] : [styles.h100, styles.bottomTabBarItem]; - const animatedStyles = [floatingActionStyle, isEmoji ? {} : animatedStyle]; - - const button = ( - { - fabPressable.current = el ?? null; - if (buttonRef && 'current' in buttonRef) { - buttonRef.current = el ?? null; - } - }} - style={pressebleStyles} - accessibilityLabel={accessibilityLabel} - onPress={toggleFabAction} - onLongPress={() => {}} - role={role} - shouldUseHapticsOnLongPress={false} - > - - - - - - - ); - if (isEmoji) { - return button; + return ( + { + fabPressable.current = el ?? null; + if (buttonRef && 'current' in buttonRef) { + buttonRef.current = el ?? null; + } + }} + style={{verticalAlign: 'bottom'}} + accessibilityLabel={accessibilityLabel} + onPress={toggleFabAction} + onLongPress={() => {}} + role={role} + shouldUseHapticsOnLongPress={false} + > + + + ); } return ( @@ -172,7 +158,32 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEm wrapperStyle={styles.productTrainingTooltipWrapper} shouldHideOnNavigate={false} > - {button} + { + fabPressable.current = el ?? null; + if (buttonRef && 'current' in buttonRef) { + buttonRef.current = el ?? null; + } + }} + style={[styles.h100, styles.bottomTabBarItem]} + accessibilityLabel={accessibilityLabel} + onPress={toggleFabAction} + onLongPress={() => {}} + role={role} + shouldUseHapticsOnLongPress={false} + > + + + + + + ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index 6857c727fad1..06feb42b3fe2 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1658,15 +1658,6 @@ const styles = (theme: ThemeColors) => justifyContent: 'center', }, - floatingActionEmoji: { - backgroundColor: theme.success, - height: variables.iconSizeSmall, - width: variables.iconSizeSmall, - borderRadius: 999, - alignItems: 'center', - justifyContent: 'center', - }, - sidebarFooterUsername: { color: theme.heading, fontSize: variables.fontSizeLabel, From b1e3dbcf8262090bd89242c6008dc3a7bfddc8ef Mon Sep 17 00:00:00 2001 From: Amoralchik Date: Mon, 20 Jan 2025 19:59:56 +0200 Subject: [PATCH 03/10] Fix FloatingActionButton emoji styles for ios and android --- src/components/FloatingActionButton.tsx | 3 ++- src/styles/index.ts | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index ee29a37a0674..6bb94665738a 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -131,7 +131,7 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEm buttonRef.current = el ?? null; } }} - style={{verticalAlign: 'bottom'}} + style={(styles.floatingActionButtonEmoji, Platform.OS !== 'web' && {height: '5%'})} accessibilityLabel={accessibilityLabel} onPress={toggleFabAction} onLongPress={() => {}} @@ -140,6 +140,7 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEm > diff --git a/src/styles/index.ts b/src/styles/index.ts index dbae34f440af..e703ea73c37c 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1658,6 +1658,12 @@ const styles = (theme: ThemeColors) => justifyContent: 'center', }, + floatingActionButtonEmoji: { + alignItems: 'center', + justifyContent: 'center', + verticalAlign: 'bottom', + }, + sidebarFooterUsername: { color: theme.heading, fontSize: variables.fontSizeLabel, From 885da3efa100804edb5ce43c8f452c5b1d96c998 Mon Sep 17 00:00:00 2001 From: Amoralchik Date: Tue, 21 Jan 2025 19:25:18 +0200 Subject: [PATCH 04/10] Fix FloatingActionButton emoji position --- src/components/FloatingActionButton.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 6bb94665738a..88ffaa53c1b9 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -123,6 +123,8 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEm }; if (isEmoji) { + const positionFix = Platform.OS !== 'web' && {height: '5%'}; + const floatingStyles = {...styles.floatingActionButtonEmoji, ...positionFix}; return ( { @@ -131,7 +133,7 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEm buttonRef.current = el ?? null; } }} - style={(styles.floatingActionButtonEmoji, Platform.OS !== 'web' && {height: '5%'})} + style={floatingStyles} accessibilityLabel={accessibilityLabel} onPress={toggleFabAction} onLongPress={() => {}} From 4a1e6feda9df14a3279670269c38bfebdc44ea63 Mon Sep 17 00:00:00 2001 From: Amoralchik Date: Wed, 22 Jan 2025 19:45:01 +0200 Subject: [PATCH 05/10] Refactor custom emoji handling via FABProvider --- src/App.tsx | 2 + src/CONST.ts | 14 +- src/components/FABPopoverProvider.tsx | 25 ++ src/components/FloatingActionButton.tsx | 34 +-- .../CustomEmojiWithDefaultPressableAction.tsx | 45 ++++ .../HTMLRenderers/CustomEmojiRenderer.tsx | 35 ++- src/libs/ReportUtils.ts | 19 +- src/libs/actions/Report.ts | 2 +- .../FloatingActionButtonAndPopover.tsx | 235 ++++++++---------- .../SidebarScreen/FloatingActionEmoji.tsx | 8 - src/styles/index.ts | 2 +- 11 files changed, 223 insertions(+), 198 deletions(-) create mode 100644 src/components/FABPopoverProvider.tsx create mode 100644 src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx delete mode 100644 src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx diff --git a/src/App.tsx b/src/App.tsx index 40028a10a2da..41d1ae3e55c4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,7 @@ import ComposeProviders from './components/ComposeProviders'; import CustomStatusBarAndBackground from './components/CustomStatusBarAndBackground'; import CustomStatusBarAndBackgroundContextProvider from './components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider'; import ErrorBoundary from './components/ErrorBoundary'; +import FABPopoverProvider from './components/FABPopoverProvider'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import InitialURLContextProvider from './components/InitialURLContextProvider'; import KeyboardProvider from './components/KeyboardProvider'; @@ -98,6 +99,7 @@ function App({url}: AppProps) { KeyboardProvider, SearchRouterContextProvider, ProductTrainingContextProvider, + FABPopoverProvider, ]} > diff --git a/src/CONST.ts b/src/CONST.ts index 455e83dcb683..30a3bcebdd24 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -121,7 +121,7 @@ const onboardingEmployerOrSubmitMessage: OnboardingMessage = { '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Press the button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + @@ -144,7 +144,7 @@ const combinedTrackSubmitOnboardingEmployerOrSubmitMessage: OnboardingMessage = '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Press the button\n' + + '1. Press the button\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + @@ -175,7 +175,7 @@ const onboardingPersonalSpendMessage: OnboardingMessage = { '\n' + 'Here’s how to track an expense:\n' + '\n' + - '1. Press the button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Click *Create*.\n' + @@ -197,7 +197,7 @@ const combinedTrackSubmitOnboardingPersonalSpendMessage: OnboardingMessage = { '\n' + 'Here’s how to track an expense:\n' + '\n' + - '1. Press the button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Click "Just track it (don\'t submit it)".\n' + @@ -5131,7 +5131,7 @@ const CONST = { '\n' + 'Here’s how to start a chat:\n' + '\n' + - '1. Press the button.\n' + + '1. Press the button.\n' + '2. Choose *Start chat*.\n' + '3. Enter emails or phone numbers.\n' + '\n' + @@ -5148,7 +5148,7 @@ const CONST = { '\n' + 'Here’s how to request money:\n' + '\n' + - '1. Press the button\n' + + '1. Press the button\n' + '2. Choose *Start chat*.\n' + '3. Enter any email, SMS, or name of who you want to split with.\n' + '4. From within the chat, click the *+* button on the message bar, and click *Split expense*.\n' + @@ -5198,7 +5198,7 @@ const CONST = { '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Press the button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + diff --git a/src/components/FABPopoverProvider.tsx b/src/components/FABPopoverProvider.tsx new file mode 100644 index 000000000000..a7061162f31f --- /dev/null +++ b/src/components/FABPopoverProvider.tsx @@ -0,0 +1,25 @@ +import type {ReactNode} from 'react'; +import React, {createContext, useMemo, useState} from 'react'; + +type FABPopoverContextValue = { + isCreateMenuActive: boolean; + setIsCreateMenuActive: (value: boolean) => void; +}; + +const FABPopoverContext = createContext({ + isCreateMenuActive: false, + setIsCreateMenuActive: () => {}, +}); + +type FABPopoverProviderProps = { + children: ReactNode; +}; + +function FABPopoverProvider({children}: FABPopoverProviderProps) { + const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); + const value = useMemo(() => ({isCreateMenuActive, setIsCreateMenuActive}), [isCreateMenuActive]); + return {children}; +} + +export default FABPopoverProvider; +export {FABPopoverContext}; diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 88ffaa53c1b9..04bc4847a00f 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -6,7 +6,6 @@ import {Platform} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import Svg, {Path} from 'react-native-svg'; -import GlobalCreateIcon from '@assets/images/customEmoji/global-create.svg'; import useBottomTabIsFocused from '@hooks/useBottomTabIsFocused'; import useIsCurrentRouteHome from '@hooks/useIsCurrentRouteHome'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -16,7 +15,6 @@ import getPlatform from '@libs/getPlatform'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ImageSVG from './ImageSVG'; import {PressableWithoutFeedback} from './Pressable'; import {useProductTrainingContext} from './ProductTrainingContext'; import EducationalTooltip from './Tooltip/EducationalTooltip'; @@ -60,12 +58,9 @@ type FloatingActionButtonProps = { /* An accessibility role for the button */ role: Role; - - /* An accessibility render as emoji for the button */ - isEmoji?: boolean; }; -function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEmoji}: FloatingActionButtonProps, ref: ForwardedRef) { +function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: FloatingActionButtonProps, ref: ForwardedRef) { const {success, buttonDefaultBG, textLight, textDark} = useTheme(); const styles = useThemeStyles(); const borderRadius = styles.floatingActionButton.borderRadius; @@ -122,33 +117,6 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEm onPress(event); }; - if (isEmoji) { - const positionFix = Platform.OS !== 'web' && {height: '5%'}; - const floatingStyles = {...styles.floatingActionButtonEmoji, ...positionFix}; - return ( - { - fabPressable.current = el ?? null; - if (buttonRef && 'current' in buttonRef) { - buttonRef.current = el ?? null; - } - }} - style={floatingStyles} - accessibilityLabel={accessibilityLabel} - onPress={toggleFabAction} - onLongPress={() => {}} - role={role} - shouldUseHapticsOnLongPress={false} - > - - - ); - } - return ( + ); + const {isCreateMenuActive, setIsCreateMenuActive} = useContext(FABPopoverContext); + + if (emojiKey === 'action-menu-icon') { + return ( + setIsCreateMenuActive(!isCreateMenuActive)} + style={styles.customEmoji} + accessible + accessibilityRole="button" + accessibilityLabel="Press to create a new item" + > + + + ); + } + return image; +} + +export default CustomEmojiWithDefaultPressableAction; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx index 5a739060a160..1f741ef6e78c 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx @@ -1,13 +1,38 @@ import React from 'react'; +import type {FC} from 'react'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; -import FloatingActionEmoji from '@pages/home/sidebar/SidebarScreen/FloatingActionEmoji'; +import type {SvgProps} from 'react-native-svg'; +import GlobalCreateIcon from '@assets/images/customEmoji/global-create.svg'; +import CustomEmojiWithDefaultPressableAction from '@components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction'; +import ImageSVG from '@components/ImageSVG'; +import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; + +// eslint-disable-next-line rulesdir/no-inline-named-export +export const emojiMap: Record> = { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'action-menu-icon': GlobalCreateIcon, +}; function CustomEmojiRenderer({tnode}: CustomRendererProps) { - if (tnode.attributes.emoji === 'action-menu-icon') { - return ; - } + const styles = useThemeStyles(); + const emojiKey = tnode.attributes.emoji; - return ''; + if (emojiMap[emojiKey]) { + if ('pressablewithdefaultaction' in tnode.attributes) { + return ; + } + + return ( + + ); + } + return null; } export default CustomEmojiRenderer; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4b1e07fda072..9afa356eaf7b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4536,7 +4536,7 @@ function completeShortMention(text: string): string { * For comments shorter than or equal to 10k chars, convert the comment from MD into HTML because that's how it is stored in the database * For longer comments, skip parsing, but still escape the text, and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!! */ -function getParsedComment(text: string, parsingDetails?: ParsingDetails, isAllowCustomEmoji?: boolean): string { +function getParsedComment(text: string, parsingDetails?: ParsingDetails): string { let isGroupPolicyReport = false; if (parsingDetails?.reportID) { const currentReport = getReportOrDraftReport(parsingDetails?.reportID); @@ -4552,16 +4552,9 @@ function getParsedComment(text: string, parsingDetails?: ParsingDetails, isAllow const textWithMention = completeShortMention(text); - let result = - text.length <= CONST.MAX_MARKUP_LENGTH - ? Parser.replace(textWithMention, {shouldEscapeText: parsingDetails?.shouldEscapeText, disabledRules: isGroupPolicyReport ? [] : ['reportMentions']}) - : lodashEscape(text); - - if (isAllowCustomEmoji) { - result = result.replace(/<custom-emoji emoji="/g, ''); - } - - return result; + return text.length <= CONST.MAX_MARKUP_LENGTH + ? Parser.replace(textWithMention, {shouldEscapeText: parsingDetails?.shouldEscapeText, disabledRules: isGroupPolicyReport ? [] : ['reportMentions']}) + : lodashEscape(text); } function getUploadingAttachmentHtml(file?: FileObject): string { @@ -6363,7 +6356,7 @@ function buildOptimisticTaskReport( description?: string, policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, - isAllowCustomEmoji?: boolean, + shouldEscapeText?: boolean, ): OptimisticTaskReport { const participants: Participants = { [ownerAccountID]: { @@ -6378,7 +6371,7 @@ function buildOptimisticTaskReport( return { reportID: generateReportID(), reportName: title, - description: getParsedComment(description ?? '', undefined, isAllowCustomEmoji), + description: getParsedComment(description ?? '', {shouldEscapeText}), ownerAccountID, participants, managerID: assigneeAccountID, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 26ea8e4e990d..c7c9d7e3d59a 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3692,7 +3692,7 @@ function prepareOnboardingOptimisticData( taskDescription, targetChatPolicyID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, - true, // is Allow render custom emoji + false, ); const emailCreatingAction = engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ? allPersonalDetails?.[actorAccountID]?.login ?? CONST.EMAIL.CONCIERGE : CONST.EMAIL.CONCIERGE; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index e4040d3c29ed..9166391ff993 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,12 +1,13 @@ import {useIsFocused as useIsFocusedOriginal, useNavigationState} from '@react-navigation/native'; import type {ImageContentFit} from 'expo-image'; import type {ForwardedRef} from 'react'; -import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; +import React, {forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import type {SvgProps} from 'react-native-svg'; import ConfirmModal from '@components/ConfirmModal'; +import {FABPopoverContext} from '@components/FABPopoverProvider'; import FloatingActionButton from '@components/FloatingActionButton'; import * as Expensicons from '@components/Icon/Expensicons'; import type {PopoverMenuItem} from '@components/PopoverMenu'; @@ -67,9 +68,6 @@ type FloatingActionButtonAndPopoverProps = { /* Callback function before the menu is hidden */ onHideCreateMenu?: () => void; - - /* An accessibility render as emoji for the button */ - isEmoji?: boolean; }; type FloatingActionButtonAndPopoverRef = { @@ -169,7 +167,7 @@ const getQuickActionTitle = (action: QuickActionName): TranslationPaths => { * Responsible for rendering the {@link PopoverMenu}, and the accompanying * FAB that can open or close the menu. */ -function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isEmoji}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef) { +function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -192,7 +190,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isE const [quickActionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${quickActionReport?.policyID}`); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: (c) => mapOnyxCollectionItems(c, policySelector)}); - const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); + const {isCreateMenuActive, setIsCreateMenuActive} = useContext(FABPopoverContext); const [modalVisible, setModalVisible] = useState(false); const fabRef = useRef(null); const {windowHeight} = useWindowDimensions(); @@ -451,132 +449,109 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isE const canModifyTask = canModifyTaskUtils(viewTourTaskReport, currentUserPersonalDetails.accountID); const canActionTask = canActionTaskUtils(viewTourTaskReport, currentUserPersonalDetails.accountID); - const popoverMenu = ( - interceptAnonymousUser(startNewChat), - }, - ...(canSendInvoice - ? [ - { - icon: Expensicons.InvoiceGeneric, - text: translate('workspace.invoices.sendInvoice'), - shouldCallAfterModalHide: shouldRedirectToExpensifyClassic, - onSelected: () => - interceptAnonymousUser(() => { - if (shouldRedirectToExpensifyClassic) { - setModalVisible(true); - return; - } - - startMoneyRequest( - CONST.IOU.TYPE.INVOICE, - // When starting to create an invoice from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used - // for all of the routes in the creation flow. - generateReportID(), - ); - }), - }, - ] - : []), - ...(canUseSpotnanaTravel - ? [ - { - icon: Expensicons.Suitcase, - text: translate('travel.bookTravel'), - onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS)), - }, - ] - : []), - ...(!hasSeenTour - ? [ - { - icon: Expensicons.Binoculars, - iconStyles: styles.popoverIconCircle, - iconFill: theme.icon, - text: translate('tour.takeATwoMinuteTour'), - description: translate('tour.exploreExpensify'), - onSelected: () => { - openExternalLink(navatticURL); - setSelfTourViewed(isAnonymousUser()); - if (viewTourTaskReport && canModifyTask && canActionTask) { - completeTask(viewTourTaskReport); - } - }, - }, - ] - : []), - ...(!isLoading && shouldShowNewWorkspaceButton - ? [ - { - displayInDefaultIconColor: true, - contentFit: 'contain' as ImageContentFit, - icon: Expensicons.NewWorkspace, - iconWidth: variables.w46, - iconHeight: variables.h40, - text: translate('workspace.new.newWorkspace'), - description: translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_CONFIRMATION.getRoute(Navigation.getActiveRoute()))), - }, - ] - : []), - ...quickActionMenuItems, - ]} - withoutOverlay - anchorRef={fabRef} - /> - ); - const confirmModal = ( - { - setModalVisible(false); - openOldDotLink(CONST.OLDDOT_URLS.INBOX); - }} - onCancel={() => setModalVisible(false)} - title={translate('sidebarScreen.redirectToExpensifyClassicModal.title')} - confirmText={translate('exitSurvey.goToExpensifyClassic')} - cancelText={translate('common.cancel')} - /> - ); - const floatingActionButton = ( - - ); - - if (isEmoji) { - return ( - <> - - {popoverMenu} - {confirmModal} - - {floatingActionButton} - - ); - } - return ( - {popoverMenu} - {confirmModal} - {floatingActionButton} + interceptAnonymousUser(startNewChat), + }, + ...(canSendInvoice + ? [ + { + icon: Expensicons.InvoiceGeneric, + text: translate('workspace.invoices.sendInvoice'), + shouldCallAfterModalHide: shouldRedirectToExpensifyClassic, + onSelected: () => + interceptAnonymousUser(() => { + if (shouldRedirectToExpensifyClassic) { + setModalVisible(true); + return; + } + + startMoneyRequest( + CONST.IOU.TYPE.INVOICE, + // When starting to create an invoice from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used + // for all of the routes in the creation flow. + generateReportID(), + ); + }), + }, + ] + : []), + ...(canUseSpotnanaTravel + ? [ + { + icon: Expensicons.Suitcase, + text: translate('travel.bookTravel'), + onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS)), + }, + ] + : []), + ...(!hasSeenTour + ? [ + { + icon: Expensicons.Binoculars, + iconStyles: styles.popoverIconCircle, + iconFill: theme.icon, + text: translate('tour.takeATwoMinuteTour'), + description: translate('tour.exploreExpensify'), + onSelected: () => { + openExternalLink(navatticURL); + setSelfTourViewed(isAnonymousUser()); + if (viewTourTaskReport && canModifyTask && canActionTask) { + completeTask(viewTourTaskReport); + } + }, + }, + ] + : []), + ...(!isLoading && shouldShowNewWorkspaceButton + ? [ + { + displayInDefaultIconColor: true, + contentFit: 'contain' as ImageContentFit, + icon: Expensicons.NewWorkspace, + iconWidth: variables.w46, + iconHeight: variables.h40, + text: translate('workspace.new.newWorkspace'), + description: translate('workspace.new.getTheExpensifyCardAndMore'), + onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_CONFIRMATION.getRoute(Navigation.getActiveRoute()))), + }, + ] + : []), + ...quickActionMenuItems, + ]} + withoutOverlay + anchorRef={fabRef} + /> + { + setModalVisible(false); + openOldDotLink(CONST.OLDDOT_URLS.INBOX); + }} + onCancel={() => setModalVisible(false)} + title={translate('sidebarScreen.redirectToExpensifyClassicModal.title')} + confirmText={translate('exitSurvey.goToExpensifyClassic')} + cancelText={translate('common.cancel')} + /> + ); } diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx deleted file mode 100644 index fa5217acb083..000000000000 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; - -function FloatingActionEmoji() { - return ; -} - -export default FloatingActionEmoji; diff --git a/src/styles/index.ts b/src/styles/index.ts index e703ea73c37c..fdde55074b1c 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1658,7 +1658,7 @@ const styles = (theme: ThemeColors) => justifyContent: 'center', }, - floatingActionButtonEmoji: { + customEmoji: { alignItems: 'center', justifyContent: 'center', verticalAlign: 'bottom', From 4686987b3f4795f63aa5e9be4e69b52c24468b16 Mon Sep 17 00:00:00 2001 From: Amoralchik Date: Fri, 24 Jan 2025 22:03:23 +0200 Subject: [PATCH 06/10] Fix emoji position for non-web platforms in CustomEmoji components --- .../CustomEmojiWithDefaultPressableAction.tsx | 10 ++++------ .../HTMLRenderers/CustomEmojiRenderer.tsx | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx b/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx index 99e2d2d82a9e..823363123bc3 100644 --- a/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx +++ b/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx @@ -1,4 +1,5 @@ import React, {useContext} from 'react'; +import {Platform} from 'react-native'; import {FABPopoverContext} from '@components/FABPopoverProvider'; import ImageSVG from '@components/ImageSVG'; import {PressableWithoutFeedback} from '@components/Pressable'; @@ -12,6 +13,7 @@ type CustomEmojiWithDefaultPressableActionProps = { function CustomEmojiWithDefaultPressableAction({emojiKey}: CustomEmojiWithDefaultPressableActionProps) { const styles = useThemeStyles(); + const positionFix = Platform.OS !== 'web' && {height: '5%'}; const image = ( setIsCreateMenuActive(!isCreateMenuActive)} - style={styles.customEmoji} + style={[styles.customEmoji, positionFix]} accessible accessibilityRole="button" accessibilityLabel="Press to create a new item" > - + {image} ); } diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx index 1f741ef6e78c..497b9e854a00 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type {FC} from 'react'; +import {Platform} from 'react-native'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import type {SvgProps} from 'react-native-svg'; import GlobalCreateIcon from '@assets/images/customEmoji/global-create.svg'; @@ -17,6 +18,7 @@ export const emojiMap: Record> = { function CustomEmojiRenderer({tnode}: CustomRendererProps) { const styles = useThemeStyles(); const emojiKey = tnode.attributes.emoji; + const positionFix = Platform.OS !== 'web' && {height: '5%'}; if (emojiMap[emojiKey]) { if ('pressablewithdefaultaction' in tnode.attributes) { @@ -25,7 +27,7 @@ function CustomEmojiRenderer({tnode}: CustomRendererProps) { return ( Date: Wed, 29 Jan 2025 02:15:11 +0200 Subject: [PATCH 07/10] refactor CustomEmojiWithDefaultPressableAction to use FloatingActionButtonAndPopover --- src/App.tsx | 2 - src/components/FABPopoverProvider.tsx | 25 -- .../CustomEmojiWithDefaultPressableAction.tsx | 33 +-- .../FloatingActionButtonAndPopover.tsx | 234 ++++++++++-------- 4 files changed, 146 insertions(+), 148 deletions(-) delete mode 100644 src/components/FABPopoverProvider.tsx diff --git a/src/App.tsx b/src/App.tsx index b21e92ab2fc1..420901e4999e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,6 @@ import ComposeProviders from './components/ComposeProviders'; import CustomStatusBarAndBackground from './components/CustomStatusBarAndBackground'; import CustomStatusBarAndBackgroundContextProvider from './components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider'; import ErrorBoundary from './components/ErrorBoundary'; -import FABPopoverProvider from './components/FABPopoverProvider'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import InitialURLContextProvider from './components/InitialURLContextProvider'; import {InputBlurContextProvider} from './components/InputBlurContext'; @@ -100,7 +99,6 @@ function App({url}: AppProps) { KeyboardProvider, SearchRouterContextProvider, ProductTrainingContextProvider, - FABPopoverProvider, InputBlurContextProvider, ]} > diff --git a/src/components/FABPopoverProvider.tsx b/src/components/FABPopoverProvider.tsx deleted file mode 100644 index a7061162f31f..000000000000 --- a/src/components/FABPopoverProvider.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type {ReactNode} from 'react'; -import React, {createContext, useMemo, useState} from 'react'; - -type FABPopoverContextValue = { - isCreateMenuActive: boolean; - setIsCreateMenuActive: (value: boolean) => void; -}; - -const FABPopoverContext = createContext({ - isCreateMenuActive: false, - setIsCreateMenuActive: () => {}, -}); - -type FABPopoverProviderProps = { - children: ReactNode; -}; - -function FABPopoverProvider({children}: FABPopoverProviderProps) { - const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); - const value = useMemo(() => ({isCreateMenuActive, setIsCreateMenuActive}), [isCreateMenuActive]); - return {children}; -} - -export default FABPopoverProvider; -export {FABPopoverContext}; diff --git a/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx b/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx index 823363123bc3..4221572c80ed 100644 --- a/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx +++ b/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx @@ -1,9 +1,8 @@ -import React, {useContext} from 'react'; -import {Platform} from 'react-native'; -import {FABPopoverContext} from '@components/FABPopoverProvider'; +import React from 'react'; +import {Platform, View} from 'react-native'; import ImageSVG from '@components/ImageSVG'; -import {PressableWithoutFeedback} from '@components/Pressable'; import useThemeStyles from '@hooks/useThemeStyles'; +import FloatingActionButtonAndPopover from '@pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover'; import variables from '@styles/variables'; import {emojiMap} from './HTMLRenderers/CustomEmojiRenderer'; @@ -16,27 +15,19 @@ function CustomEmojiWithDefaultPressableAction({emojiKey}: CustomEmojiWithDefaul const positionFix = Platform.OS !== 'web' && {height: '5%'}; const image = ( - + + + ); - const {isCreateMenuActive, setIsCreateMenuActive} = useContext(FABPopoverContext); if (emojiKey === 'action-menu-icon') { - return ( - setIsCreateMenuActive(!isCreateMenuActive)} - style={[styles.customEmoji, positionFix]} - accessible - accessibilityRole="button" - accessibilityLabel="Press to create a new item" - > - {image} - - ); + return {image}; } + return image; } diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 732ce54ecf4f..5f3c685ff4c0 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -1,17 +1,17 @@ import {useIsFocused as useIsFocusedOriginal, useNavigationState} from '@react-navigation/native'; import type {ImageContentFit} from 'expo-image'; -import type {ForwardedRef} from 'react'; -import React, {forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; -import {View} from 'react-native'; +import type {ForwardedRef, ReactNode} from 'react'; +import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; +import {Platform, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import type {SvgProps} from 'react-native-svg'; import ConfirmModal from '@components/ConfirmModal'; -import {FABPopoverContext} from '@components/FABPopoverProvider'; import FloatingActionButton from '@components/FloatingActionButton'; import * as Expensicons from '@components/Icon/Expensicons'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import PopoverMenu from '@components/PopoverMenu'; +import {PressableWithoutFeedback} from '@components/Pressable'; import {useProductTrainingContext} from '@components/ProductTrainingContext'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useEnvironment from '@hooks/useEnvironment'; @@ -68,6 +68,10 @@ type FloatingActionButtonAndPopoverProps = { /* Callback function before the menu is hidden */ onHideCreateMenu?: () => void; + + isEmoji?: boolean; + + children?: ReactNode; }; type FloatingActionButtonAndPopoverRef = { @@ -167,7 +171,7 @@ const getQuickActionTitle = (action: QuickActionName): TranslationPaths => { * Responsible for rendering the {@link PopoverMenu}, and the accompanying * FAB that can open or close the menu. */ -function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef) { +function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isEmoji, children}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -190,7 +194,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl const [quickActionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${quickActionReport?.policyID}`); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: (c) => mapOnyxCollectionItems(c, policySelector)}); - const {isCreateMenuActive, setIsCreateMenuActive} = useContext(FABPopoverContext); + const [isCreateMenuActive, setIsCreateMenuActive] = useState(false); const [modalVisible, setModalVisible] = useState(false); const fabRef = useRef(null); const {windowHeight} = useWindowDimensions(); @@ -463,102 +467,132 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl const canModifyTask = canModifyTaskUtils(viewTourTaskReport, currentUserPersonalDetails.accountID); const canActionTask = canActionTaskUtils(viewTourTaskReport, currentUserPersonalDetails.accountID); - return ( - - interceptAnonymousUser(startNewChat), - }, - ...(canSendInvoice - ? [ - { - icon: Expensicons.InvoiceGeneric, - text: translate('workspace.invoices.sendInvoice'), - shouldCallAfterModalHide: shouldRedirectToExpensifyClassic, - onSelected: () => - interceptAnonymousUser(() => { - if (shouldRedirectToExpensifyClassic) { - setModalVisible(true); - return; - } - - startMoneyRequest( - CONST.IOU.TYPE.INVOICE, - // When starting to create an invoice from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used - // for all of the routes in the creation flow. - generateReportID(), - ); - }), - }, - ] - : []), - ...(canUseSpotnanaTravel - ? [ - { - icon: Expensicons.Suitcase, - text: translate('travel.bookTravel'), - onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS)), - }, - ] - : []), - ...(!hasSeenTour - ? [ - { - icon: Expensicons.Binoculars, - iconStyles: styles.popoverIconCircle, - iconFill: theme.icon, - text: translate('tour.takeATwoMinuteTour'), - description: translate('tour.exploreExpensify'), - onSelected: () => { - openExternalLink(navatticURL); - setSelfTourViewed(isAnonymousUser()); - if (viewTourTaskReport && canModifyTask && canActionTask) { - completeTask(viewTourTaskReport); + const popoverMenu = ( + interceptAnonymousUser(startNewChat), + }, + ...(canSendInvoice + ? [ + { + icon: Expensicons.InvoiceGeneric, + text: translate('workspace.invoices.sendInvoice'), + shouldCallAfterModalHide: shouldRedirectToExpensifyClassic, + onSelected: () => + interceptAnonymousUser(() => { + if (shouldRedirectToExpensifyClassic) { + setModalVisible(true); + return; } - }, - }, - ] - : []), - ...(!isLoading && shouldShowNewWorkspaceButton - ? [ - { - displayInDefaultIconColor: true, - contentFit: 'contain' as ImageContentFit, - icon: Expensicons.NewWorkspace, - iconWidth: variables.w46, - iconHeight: variables.h40, - text: translate('workspace.new.newWorkspace'), - description: translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_CONFIRMATION.getRoute(Navigation.getActiveRoute()))), + + startMoneyRequest( + CONST.IOU.TYPE.INVOICE, + // When starting to create an invoice from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used + // for all of the routes in the creation flow. + generateReportID(), + ); + }), + }, + ] + : []), + ...(canUseSpotnanaTravel + ? [ + { + icon: Expensicons.Suitcase, + text: translate('travel.bookTravel'), + onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS)), + }, + ] + : []), + ...(!hasSeenTour + ? [ + { + icon: Expensicons.Binoculars, + iconStyles: styles.popoverIconCircle, + iconFill: theme.icon, + text: translate('tour.takeATwoMinuteTour'), + description: translate('tour.exploreExpensify'), + onSelected: () => { + openExternalLink(navatticURL); + setSelfTourViewed(isAnonymousUser()); + if (viewTourTaskReport && canModifyTask && canActionTask) { + completeTask(viewTourTaskReport); + } }, - ] - : []), - ...quickActionMenuItems, - ]} - withoutOverlay - anchorRef={fabRef} - /> - { - setModalVisible(false); - openOldDotLink(CONST.OLDDOT_URLS.INBOX); - }} - onCancel={() => setModalVisible(false)} - title={translate('sidebarScreen.redirectToExpensifyClassicModal.title')} - confirmText={translate('exitSurvey.goToExpensifyClassic')} - cancelText={translate('common.cancel')} - /> + }, + ] + : []), + ...(!isLoading && shouldShowNewWorkspaceButton + ? [ + { + displayInDefaultIconColor: true, + contentFit: 'contain' as ImageContentFit, + icon: Expensicons.NewWorkspace, + iconWidth: variables.w46, + iconHeight: variables.h40, + text: translate('workspace.new.newWorkspace'), + description: translate('workspace.new.getTheExpensifyCardAndMore'), + onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_CONFIRMATION.getRoute(Navigation.getActiveRoute()))), + }, + ] + : []), + ...quickActionMenuItems, + ]} + withoutOverlay + anchorRef={fabRef} + /> + ); + + const confirmModal = ( + { + setModalVisible(false); + openOldDotLink(CONST.OLDDOT_URLS.INBOX); + }} + onCancel={() => setModalVisible(false)} + title={translate('sidebarScreen.redirectToExpensifyClassicModal.title')} + confirmText={translate('exitSurvey.goToExpensifyClassic')} + cancelText={translate('common.cancel')} + /> + ); + + const positionFix = Platform.OS !== 'web' && {height: '5%'}; + + if (isEmoji) { + return ( + <> + + {popoverMenu} + {confirmModal} + + + {children} + + + ); + } + + return ( + + {popoverMenu} + {confirmModal} Date: Wed, 29 Jan 2025 16:24:06 +0200 Subject: [PATCH 08/10] refactor CustomEmojiWithDefaultPressableAction to accept children for rendering emoji content --- .../CustomEmojiWithDefaultPressableAction.tsx | 29 +++++-------------- .../HTMLRenderers/CustomEmojiRenderer.tsx | 24 ++++++++------- .../FloatingActionButtonAndPopover.tsx | 2 ++ 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx b/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx index 4221572c80ed..bc71c2b1b837 100644 --- a/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx +++ b/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx @@ -1,34 +1,21 @@ +import type {ReactNode} from 'react'; import React from 'react'; -import {Platform, View} from 'react-native'; -import ImageSVG from '@components/ImageSVG'; -import useThemeStyles from '@hooks/useThemeStyles'; import FloatingActionButtonAndPopover from '@pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover'; -import variables from '@styles/variables'; -import {emojiMap} from './HTMLRenderers/CustomEmojiRenderer'; type CustomEmojiWithDefaultPressableActionProps = { + /* Key name identifying the emoji */ emojiKey: string; -}; - -function CustomEmojiWithDefaultPressableAction({emojiKey}: CustomEmojiWithDefaultPressableActionProps) { - const styles = useThemeStyles(); - const positionFix = Platform.OS !== 'web' && {height: '5%'}; - const image = ( - - - - ); + /* Emoji content to render */ + children: ReactNode; +}; +function CustomEmojiWithDefaultPressableAction({emojiKey, children}: CustomEmojiWithDefaultPressableActionProps) { if (emojiKey === 'action-menu-icon') { - return {image}; + return {children}; } - return image; + return children; } export default CustomEmojiWithDefaultPressableAction; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx index 497b9e854a00..6487bff6086e 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type {FC} from 'react'; -import {Platform} from 'react-native'; +import {Platform, View} from 'react-native'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import type {SvgProps} from 'react-native-svg'; import GlobalCreateIcon from '@assets/images/customEmoji/global-create.svg'; @@ -21,19 +21,23 @@ function CustomEmojiRenderer({tnode}: CustomRendererProps) { const positionFix = Platform.OS !== 'web' && {height: '5%'}; if (emojiMap[emojiKey]) { + const image = ( + + + + ); + if ('pressablewithdefaultaction' in tnode.attributes) { - return ; + return {image}; } - return ( - - ); + return image; } + return null; } diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 5f3c685ff4c0..93d52fed90d2 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -69,8 +69,10 @@ type FloatingActionButtonAndPopoverProps = { /* Callback function before the menu is hidden */ onHideCreateMenu?: () => void; + /* Render the FAB as an emoji */ isEmoji?: boolean; + /* Emoji content to render when isEmoji */ children?: ReactNode; }; From 60d06bfbdfe1a1ae0f7e99f125cfb77a6eeeadef Mon Sep 17 00:00:00 2001 From: Amoralchik Date: Fri, 31 Jan 2025 17:21:42 +0200 Subject: [PATCH 09/10] refactor emoji keys in onboarding messages and components according to review --- src/CONST.ts | 14 +++++++------- .../CustomEmojiWithDefaultPressableAction.tsx | 2 +- .../HTMLRenderers/CustomEmojiRenderer.tsx | 12 +++++------- src/libs/ReportUtils.ts | 2 +- .../FloatingActionButtonAndPopover.tsx | 2 +- src/styles/index.ts | 2 ++ 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 95015389af27..1027c915f52f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -121,7 +121,7 @@ const onboardingEmployerOrSubmitMessage: OnboardingMessage = { '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Press the button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + @@ -144,7 +144,7 @@ const combinedTrackSubmitOnboardingEmployerOrSubmitMessage: OnboardingMessage = '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Press the button\n' + + '1. Press the button\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + @@ -175,7 +175,7 @@ const onboardingPersonalSpendMessage: OnboardingMessage = { '\n' + 'Here’s how to track an expense:\n' + '\n' + - '1. Press the button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Click "Just track it (don\'t submit it)".\n' + @@ -198,7 +198,7 @@ const combinedTrackSubmitOnboardingPersonalSpendMessage: OnboardingMessage = { '\n' + 'Here’s how to track an expense:\n' + '\n' + - '1. Press the button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Click "Just track it (don\'t submit it)".\n' + @@ -5172,7 +5172,7 @@ const CONST = { '\n' + 'Here’s how to start a chat:\n' + '\n' + - '1. Press the button.\n' + + '1. Press the button.\n' + '2. Choose *Start chat*.\n' + '3. Enter emails or phone numbers.\n' + '\n' + @@ -5189,7 +5189,7 @@ const CONST = { '\n' + 'Here’s how to request money:\n' + '\n' + - '1. Press the button\n' + + '1. Press the button\n' + '2. Choose *Start chat*.\n' + '3. Enter any email, SMS, or name of who you want to split with.\n' + '4. From within the chat, click the *+* button on the message bar, and click *Split expense*.\n' + @@ -5239,7 +5239,7 @@ const CONST = { '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Press the button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + diff --git a/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx b/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx index bc71c2b1b837..8cd33eab6c90 100644 --- a/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx +++ b/src/components/HTMLEngineProvider/CustomEmojiWithDefaultPressableAction.tsx @@ -11,7 +11,7 @@ type CustomEmojiWithDefaultPressableActionProps = { }; function CustomEmojiWithDefaultPressableAction({emojiKey, children}: CustomEmojiWithDefaultPressableActionProps) { - if (emojiKey === 'action-menu-icon') { + if (emojiKey === 'actionMenuIcon') { return {children}; } diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx index 6487bff6086e..dab8c89013dd 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type {FC} from 'react'; -import {Platform, View} from 'react-native'; +import {View} from 'react-native'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import type {SvgProps} from 'react-native-svg'; import GlobalCreateIcon from '@assets/images/customEmoji/global-create.svg'; @@ -9,20 +9,17 @@ import ImageSVG from '@components/ImageSVG'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; -// eslint-disable-next-line rulesdir/no-inline-named-export -export const emojiMap: Record> = { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'action-menu-icon': GlobalCreateIcon, +const emojiMap: Record> = { + actionMenuIcon: GlobalCreateIcon, }; function CustomEmojiRenderer({tnode}: CustomRendererProps) { const styles = useThemeStyles(); const emojiKey = tnode.attributes.emoji; - const positionFix = Platform.OS !== 'web' && {height: '5%'}; if (emojiMap[emojiKey]) { const image = ( - + ) { } export default CustomEmojiRenderer; +export {emojiMap}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index cad4dfc01862..87466340a70a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6412,7 +6412,7 @@ function buildOptimisticTaskReport( description?: string, policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, - shouldEscapeText?: boolean, + shouldEscapeText = true, ): OptimisticTaskReport { const participants: Participants = { [ownerAccountID]: { diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 93d52fed90d2..c66cf10524ee 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -472,7 +472,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isE const popoverMenu = ( alignItems: 'center', justifyContent: 'center', verticalAlign: 'bottom', + ...(getPlatform() === CONST.PLATFORM.IOS || getPlatform() === CONST.PLATFORM.ANDROID ? {marginBottom: -variables.iconSizeNormal / 4} : {}), }, sidebarFooterUsername: { From f14215ec9882b7293d09d8b11deb986f18b5534e Mon Sep 17 00:00:00 2001 From: Amoralchik Date: Fri, 31 Jan 2025 19:39:28 +0200 Subject: [PATCH 10/10] refactor FloatingActionButtonAndPopover to remove platform-specific styling --- .../SidebarScreen/FloatingActionButtonAndPopover.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index c66cf10524ee..ca90fc45997e 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -2,7 +2,7 @@ import {useIsFocused as useIsFocusedOriginal, useNavigationState} from '@react-n import type {ImageContentFit} from 'expo-image'; import type {ForwardedRef, ReactNode} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; -import {Platform, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import type {SvgProps} from 'react-native-svg'; @@ -569,8 +569,6 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isE /> ); - const positionFix = Platform.OS !== 'web' && {height: '5%'}; - if (isEmoji) { return ( <> @@ -579,7 +577,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isE {confirmModal}