From 403fc658b1e2195accd5b61331c81735be6d63f9 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 21 May 2024 17:48:24 +0700 Subject: [PATCH 01/16] feat: empty ui for create flows --- .../todd-with-phones.svg | 667 ++++++++++++++++++ src/components/EmptySelectionListContent.tsx | 29 + src/components/Icon/Illustrations.ts | 2 + .../SelectionList/BaseSelectionList.tsx | 15 +- src/components/SelectionList/types.ts | 3 + src/languages/en.ts | 38 + src/languages/es.ts | 38 + src/pages/NewChatPage.tsx | 2 + .../MoneyRequestParticipantsSelector.tsx | 2 + src/pages/tasks/TaskAssigneeSelectorModal.tsx | 2 + .../TaskShareDestinationSelectorModal.tsx | 2 + src/styles/variables.ts | 2 + 12 files changed, 800 insertions(+), 2 deletions(-) create mode 100644 assets/images/product-illustrations/todd-with-phones.svg create mode 100644 src/components/EmptySelectionListContent.tsx diff --git a/assets/images/product-illustrations/todd-with-phones.svg b/assets/images/product-illustrations/todd-with-phones.svg new file mode 100644 index 000000000000..5992a4f408d7 --- /dev/null +++ b/assets/images/product-illustrations/todd-with-phones.svg @@ -0,0 +1,667 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx new file mode 100644 index 000000000000..2a7702829621 --- /dev/null +++ b/src/components/EmptySelectionListContent.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import useLocalize from '@hooks/useLocalize'; +import variables from '@styles/variables'; +import type {IOUType} from '@src/CONST'; +import BlockingView from './BlockingViews/BlockingView'; +import * as Illustrations from './Icon/Illustrations'; + +type EmptySelectionListContentProps = { + content: IOUType | 'startChat' | 'assignTask'; +}; + +function EmptySelectionListContent({content}: EmptySelectionListContentProps) { + const {translate} = useLocalize(); + + return ( + + ); +} + +EmptySelectionListContent.displayName = 'EmptySelectionListContent'; + +export default EmptySelectionListContent; diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 8d3f53be9396..73424b848de3 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -29,6 +29,7 @@ import TadaYellow from '@assets/images/product-illustrations/tada--yellow.svg'; import TeleScope from '@assets/images/product-illustrations/telescope.svg'; import ThreeLeggedLaptopWoman from '@assets/images/product-illustrations/three_legged_laptop_woman.svg'; import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg'; +import ToddWithPhones from '@assets/images/product-illustrations/todd-with-phones.svg'; import Abacus from '@assets/images/simple-illustrations/simple-illustration__abacus.svg'; import Accounting from '@assets/images/simple-illustrations/simple-illustration__accounting.svg'; import Alert from '@assets/images/simple-illustrations/simple-illustration__alert.svg'; @@ -125,6 +126,7 @@ export { TadaYellow, TadaBlue, ToddBehindCloud, + ToddWithPhones, GpsTrackOrange, ShieldYellow, MoneyReceipts, diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 77b296740f2c..f71f811a89c3 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -53,6 +53,7 @@ function BaseSelectionList( headerContent, footerContent, listFooterContent, + listEmptyContent, showScrollIndicator = true, showLoadingPlaceholder = false, showConfirmButton = false, @@ -367,6 +368,16 @@ function BaseSelectionList( ); }; + const renderListEmptyContent = () => { + if (showLoadingPlaceholder) { + return ; + } + if (!textInputValue && !showLoadingPlaceholder && !!listEmptyContent) { + return listEmptyContent; + } + return null; + }; + const scrollToFocusedIndexOnFirstRender = useCallback( (nativeEvent: LayoutChangeEvent) => { if (shouldUseDynamicMaxToRenderPerBatch) { @@ -565,8 +576,8 @@ function BaseSelectionList( )} {!!headerContent && headerContent} - {flattenedSections.allOptions.length === 0 && showLoadingPlaceholder ? ( - + {flattenedSections.allOptions.length === 0 ? ( + renderListEmptyContent() ) : ( <> {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 0a4b0532b581..fca7544d1e0e 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -327,6 +327,9 @@ type BaseSelectionListProps = Partial & { /** Custom content to display in the footer of list component. If present ShowMore button won't be displayed */ listFooterContent?: React.JSX.Element | null; + /** Custom content to display when the list is empty after finish loading */ + listEmptyContent?: React.JSX.Element | null; + /** Whether to use dynamic maxToRenderPerBatch depending on the visible number of elements */ shouldUseDynamicMaxToRenderPerBatch?: boolean; diff --git a/src/languages/en.ts b/src/languages/en.ts index f8d122f3b69a..a61d2f64dddf 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -416,6 +416,44 @@ export default { nameEmailOrPhoneNumber: 'Name, email, or phone number', findMember: 'Find a member', }, + emptyList: { + [CONST.IOU.TYPE.SUBMIT]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.SPLIT]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.TRACK]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.PAY]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.INVOICE]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.SEND]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.REQUEST]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + startChat: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + assignTask: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + }, videoChatButtonAndMenu: { tooltip: 'Start a call', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 536a6182f393..7bc4694ac68f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -407,6 +407,44 @@ export default { nameEmailOrPhoneNumber: 'Nombre, email o número de teléfono', findMember: 'Encuentra un miembro', }, + emptyList: { + [CONST.IOU.TYPE.SUBMIT]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.SPLIT]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.TRACK]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.PAY]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.INVOICE]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.SEND]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + [CONST.IOU.TYPE.REQUEST]: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + startChat: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + assignTask: { + title: 'Get paid back', + subtitle: 'Submit your expense to any email or phone number.', + }, + }, videoChatButtonAndMenu: { tooltip: 'Iniciar una llamada', }, diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 555a7c855d42..d889e2488264 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -3,6 +3,7 @@ import reject from 'lodash/reject'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; +import EmptySelectionListContent from '@components/EmptySelectionListContent'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; import {useOptionsList} from '@components/OptionListContextProvider'; @@ -312,6 +313,7 @@ function NewChatPage({isGroupChat}: NewChatPageProps) { onConfirm={(e, option) => (selectedOptions.length > 0 ? createGroup() : createChat(option))} rightHandSideComponent={itemRightSideComponent} footerContent={footerContent} + listEmptyContent={} showLoadingPlaceholder={!areOptionsInitialized} shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} isLoadingNewOptions={!!isSearchingForReports} diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index b525a2c1e3dd..c4cff3487d21 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -5,6 +5,7 @@ import React, {memo, useCallback, useEffect, useMemo} from 'react'; import type {GestureResponderEvent} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; +import EmptySelectionListContent from '@components/EmptySelectionListContent'; import FormHelpMessage from '@components/FormHelpMessage'; import {usePersonalDetails} from '@components/OnyxProvider'; import {useOptionsList} from '@components/OptionListContextProvider'; @@ -359,6 +360,7 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} onSelectRow={(item) => (isIOUSplit ? addParticipantToSelection(item) : addSingleParticipant(item))} footerContent={footerContent} + listEmptyContent={} headerMessage={headerMessage} showLoadingPlaceholder={!areOptionsInitialized || !didScreenTransitionEnd} canSelectMultiple={isIOUSplit && isAllowedToSplit} diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 3116b8a84152..42ca454081d2 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -6,6 +6,7 @@ import {View} from 'react-native'; import {useOnyx, withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import EmptySelectionListContent from '@components/EmptySelectionListContent'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useBetas, useSession} from '@components/OnyxProvider'; import {useOptionsList} from '@components/OptionListContextProvider'; @@ -223,6 +224,7 @@ function TaskAssigneeSelectorModal({reports, task}: TaskAssigneeSelectorModalPro textInputLabel={translate('selectionList.nameEmailOrPhoneNumber')} showLoadingPlaceholder={!areOptionsInitialized} isLoadingNewOptions={!!isSearchingForReports} + listEmptyContent={} /> diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.tsx b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx index 4d731d59a9e7..276fd3e27466 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.tsx +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx @@ -1,6 +1,7 @@ import React, {useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import EmptySelectionListContent from '@components/EmptySelectionListContent'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useOptionsList} from '@components/OptionListContextProvider'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -112,6 +113,7 @@ function TaskShareDestinationSelectorModal() { showLoadingPlaceholder={areOptionsInitialized && debouncedSearchValue.trim() === '' ? options.sections.length === 0 : !didScreenTransitionEnd} isLoadingNewOptions={!!isSearchingForReports} textInputHint={textInputHint} + listEmptyContent={} /> diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 7ab469af9533..815f0631dc4c 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -129,6 +129,8 @@ export default { emptyLHNIconHeight: 16, emptyWorkspaceIconWidth: 84, emptyWorkspaceIconHeight: 84, + emptyListIconWidth: 176, + emptyListIconHeight: 178, modalTopIconWidth: 200, modalTopIconHeight: 164, modalTopBigIconHeight: 244, From dbbe56aafb2f3bfe63587625b65a980361610e87 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 22 May 2024 17:35:40 +0700 Subject: [PATCH 02/16] fix illustration overflow --- src/components/EmptySelectionListContent.tsx | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index 2a7702829621..ae16f3a1595c 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import type {IOUType} from '@src/CONST'; import BlockingView from './BlockingViews/BlockingView'; @@ -10,17 +12,20 @@ type EmptySelectionListContentProps = { }; function EmptySelectionListContent({content}: EmptySelectionListContentProps) { + const styles = useThemeStyles(); const {translate} = useLocalize(); return ( - + + + ); } From d8aab6451c5f56527b8e488c13608e3d5722b88e Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 24 May 2024 20:18:14 +0700 Subject: [PATCH 03/16] fix: icon is clipped in ios --- src/components/EmptySelectionListContent.tsx | 2 +- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/styles/variables.ts | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index ae16f3a1595c..cab3ab522fa0 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -20,7 +20,7 @@ function EmptySelectionListContent({content}: EmptySelectionListContentProps) { Date: Fri, 24 May 2024 20:26:35 +0700 Subject: [PATCH 04/16] modify line break --- src/pages/iou/request/MoneyRequestParticipantsSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index c4cff3487d21..51c17b5f8cfd 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -351,7 +351,7 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic return ( Date: Wed, 29 May 2024 17:06:06 +0700 Subject: [PATCH 05/16] fix lint --- src/pages/iou/request/MoneyRequestParticipantsSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 09e69380417f..5e923c1b762a 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -351,7 +351,7 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic return ( Date: Thu, 30 May 2024 16:05:58 +0700 Subject: [PATCH 06/16] update copies and only show empty ui in some create flows --- src/components/EmptySelectionListContent.tsx | 32 +++++++++++--- src/languages/en.ts | 42 ++++++------------- src/languages/es.ts | 42 ++++++------------- src/pages/NewChatPage.tsx | 2 - .../MoneyRequestParticipantsSelector.tsx | 2 +- src/pages/tasks/TaskAssigneeSelectorModal.tsx | 2 - .../TaskShareDestinationSelectorModal.tsx | 2 - 7 files changed, 52 insertions(+), 72 deletions(-) diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index cab3ab522fa0..016ecf91eec1 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -1,29 +1,51 @@ import React from 'react'; import {View} from 'react-native'; +import type {TupleToUnion} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; -import type {IOUType} from '@src/CONST'; +import CONST from '@src/CONST'; import BlockingView from './BlockingViews/BlockingView'; import * as Illustrations from './Icon/Illustrations'; +import Text from './Text'; +import TextLink from './TextLink'; type EmptySelectionListContentProps = { - content: IOUType | 'startChat' | 'assignTask'; + contentType: string; }; -function EmptySelectionListContent({content}: EmptySelectionListContentProps) { +const CONTENT_TYPES = [CONST.IOU.TYPE.SUBMIT, CONST.IOU.TYPE.SPLIT, CONST.IOU.TYPE.PAY]; +type ContentType = TupleToUnion; + +function isContentType(contentType: unknown): contentType is ContentType { + return CONTENT_TYPES.includes(contentType as ContentType); +} + +function EmptySelectionListContent({contentType}: EmptySelectionListContentProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + if (!isContentType(contentType)) { + return null; + } + + const EmptySubtitle = ( + + {translate(`emptyList.${contentType}.subtitleText1`)} + {translate(`emptyList.${contentType}.subtitleText2`)} + {translate(`emptyList.${contentType}.subtitleText3`)} + + ); + return ( ); diff --git a/src/languages/en.ts b/src/languages/en.ts index d7734bab76a3..0a0bc2ad47f7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -417,40 +417,22 @@ export default { }, emptyList: { [CONST.IOU.TYPE.SUBMIT]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or\nphone number.', + title: 'Submit an expense', + subtitleText1: 'Submit to someone and ', + subtitleText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}`, + subtitleText3: ' when they become a customer.', }, [CONST.IOU.TYPE.SPLIT]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - [CONST.IOU.TYPE.TRACK]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', + title: 'Split an expense', + subtitleText1: 'Split with a friend and ', + subtitleText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}`, + subtitleText3: ' when they become a customer.', }, [CONST.IOU.TYPE.PAY]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - [CONST.IOU.TYPE.INVOICE]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - [CONST.IOU.TYPE.SEND]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - [CONST.IOU.TYPE.REQUEST]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - startChat: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - assignTask: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', + title: 'Pay someone', + subtitleText1: 'Pay anyone and ', + subtitleText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}`, + subtitleText3: ' when they become a customer.', }, }, videoChatButtonAndMenu: { diff --git a/src/languages/es.ts b/src/languages/es.ts index cfcf6539dfb0..b5ba5eda7e60 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -408,40 +408,22 @@ export default { }, emptyList: { [CONST.IOU.TYPE.SUBMIT]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or\nphone number.', + title: 'Presentar un gasto', + subtitleText1: 'Presente un gasto a alguien y ', + subtitleText2: `recibe ${CONST.REFERRAL_PROGRAM.REVENUE} dólares`, + subtitleText3: ' cuando se convierta en client.', }, [CONST.IOU.TYPE.SPLIT]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - [CONST.IOU.TYPE.TRACK]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', + title: 'Dividir un gasto', + subtitleText1: 'Divide con un amigo y ', + subtitleText2: `recibe ${CONST.REFERRAL_PROGRAM.REVENUE} dólares`, + subtitleText3: ' cuando se convierta en client.', }, [CONST.IOU.TYPE.PAY]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - [CONST.IOU.TYPE.INVOICE]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - [CONST.IOU.TYPE.SEND]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - [CONST.IOU.TYPE.REQUEST]: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - startChat: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', - }, - assignTask: { - title: 'Get paid back', - subtitle: 'Submit your expense to any email or phone number.', + title: 'Pagar a alguien', + subtitleText1: 'Paga a quien quieras y ', + subtitleText2: `recibe ${CONST.REFERRAL_PROGRAM.REVENUE} dólares`, + subtitleText3: ' cuando se convierta en client.', }, }, videoChatButtonAndMenu: { diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 4644afc890a8..b2008ecc2cca 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -3,7 +3,6 @@ import reject from 'lodash/reject'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; -import EmptySelectionListContent from '@components/EmptySelectionListContent'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; import {useOptionsList} from '@components/OptionListContextProvider'; @@ -314,7 +313,6 @@ function NewChatPage({isGroupChat}: NewChatPageProps) { onConfirm={(e, option) => (selectedOptions.length > 0 ? createGroup() : createChat(option))} rightHandSideComponent={itemRightSideComponent} footerContent={footerContent} - listEmptyContent={} showLoadingPlaceholder={!areOptionsInitialized} shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} isLoadingNewOptions={!!isSearchingForReports} diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 5e923c1b762a..54ff31b24c46 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -361,7 +361,7 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic onSelectRow={(item) => (isIOUSplit ? addParticipantToSelection(item) : addSingleParticipant(item))} shouldDebounceRowSelect footerContent={footerContent} - listEmptyContent={} + listEmptyContent={} headerMessage={headerMessage} showLoadingPlaceholder={!areOptionsInitialized || !didScreenTransitionEnd} canSelectMultiple={isIOUSplit && isAllowedToSplit} diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 0b06747ab277..9cff3694fc5d 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -6,7 +6,6 @@ import {View} from 'react-native'; import {useOnyx, withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import EmptySelectionListContent from '@components/EmptySelectionListContent'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useBetas, useSession} from '@components/OnyxProvider'; import {useOptionsList} from '@components/OptionListContextProvider'; @@ -225,7 +224,6 @@ function TaskAssigneeSelectorModal({reports, task}: TaskAssigneeSelectorModalPro textInputLabel={translate('selectionList.nameEmailOrPhoneNumber')} showLoadingPlaceholder={!areOptionsInitialized} isLoadingNewOptions={!!isSearchingForReports} - listEmptyContent={} /> diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.tsx b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx index ed3c5393632b..55c2f54b5e48 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.tsx +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx @@ -1,7 +1,6 @@ import React, {useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import EmptySelectionListContent from '@components/EmptySelectionListContent'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useOptionsList} from '@components/OptionListContextProvider'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -114,7 +113,6 @@ function TaskShareDestinationSelectorModal() { showLoadingPlaceholder={areOptionsInitialized && debouncedSearchValue.trim() === '' ? options.sections.length === 0 : !didScreenTransitionEnd} isLoadingNewOptions={!!isSearchingForReports} textInputHint={textInputHint} - listEmptyContent={} /> From b06f0be0f1a6f668c58a3f17096fc86bd1ccfde6 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 10 Jun 2024 16:27:08 +0700 Subject: [PATCH 07/16] remove native ListEmptyContent --- src/components/SelectionList/BaseSelectionList.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 71175cf0077d..0dab698d45a5 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -105,7 +105,6 @@ function BaseSelectionList( const itemFocusTimeoutRef = useRef(null); const [currentPage, setCurrentPage] = useState(1); const isTextInputFocusedRef = useRef(false); - const isEmptyList = sections.length === 0; const incrementPage = () => setCurrentPage((prev) => prev + 1); @@ -718,9 +717,6 @@ function BaseSelectionList( style={(!maxToRenderPerBatch || (shouldHideListOnInitialRender && isInitialSectionListRender)) && styles.opacity0} ListHeaderComponent={listHeaderContent} ListFooterComponent={listFooterContent ?? ShowMoreButtonInstance} - ListEmptyComponent={listEmptyContent} - contentContainerStyle={isEmptyList && listEmptyContent ? styles.flexGrow1 : undefined} - scrollEnabled={!isEmptyList || !listEmptyContent} onEndReached={onEndReached} onEndReachedThreshold={onEndReachedThreshold} /> From 2dad76625e74105d777395b730e4e2a46cc140cc Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 3 Jul 2024 17:33:21 +0700 Subject: [PATCH 08/16] reapply changes --- src/components/SelectionList/BaseSelectionList.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index f9c0a334b7c0..cad29d8e1990 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -470,7 +470,7 @@ function BaseSelectionList( const renderListEmptyContent = () => { if (showLoadingPlaceholder) { - return ; + return ; } if (!textInputValue && !showLoadingPlaceholder && !!listEmptyContent) { return listEmptyContent; @@ -701,8 +701,8 @@ function BaseSelectionList( )} {!!headerContent && headerContent} - {flattenedSections.allOptions.length === 0 && showLoadingPlaceholder ? ( - + {flattenedSections.allOptions.length === 0 ? ( + renderListEmptyContent() ) : ( <> {!listHeaderContent && header()} From 7534ebc3ecc3bdcbcf724937a4e804782e0365b2 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 11 Jul 2024 15:06:47 +0700 Subject: [PATCH 09/16] let empty state ui be obsecured by keyboard --- src/components/EmptySelectionListContent.tsx | 17 ++++++++-- .../MoneyRequestParticipantsSelector.tsx | 33 ++++++++++++++++--- .../step/IOURequestStepParticipants.tsx | 12 ++++++- .../iou/request/step/StepScreenWrapper.tsx | 12 ++++++- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index 016ecf91eec1..5a00c6a03ca5 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import {View} from 'react-native'; import type {TupleToUnion} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; @@ -11,7 +11,11 @@ import Text from './Text'; import TextLink from './TextLink'; type EmptySelectionListContentProps = { + /** Type of selection list */ contentType: string; + + /** Callback to trigger when list emptiness changes (i.e. component mounts and unmounts) */ + onEmptyChange?: (isEmpty: boolean) => void; }; const CONTENT_TYPES = [CONST.IOU.TYPE.SUBMIT, CONST.IOU.TYPE.SPLIT, CONST.IOU.TYPE.PAY]; @@ -21,10 +25,19 @@ function isContentType(contentType: unknown): contentType is ContentType { return CONTENT_TYPES.includes(contentType as ContentType); } -function EmptySelectionListContent({contentType}: EmptySelectionListContentProps) { +function EmptySelectionListContent({contentType, onEmptyChange}: EmptySelectionListContentProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + useEffect(() => { + if (!onEmptyChange) { + return; + } + + onEmptyChange(true); + return () => onEmptyChange(false); + }, [onEmptyChange]); + if (!isContentType(contentType)) { return null; } diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 74e67f43e334..3b04bad151a3 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -1,7 +1,7 @@ import lodashIsEqual from 'lodash/isEqual'; import lodashPick from 'lodash/pick'; import lodashReject from 'lodash/reject'; -import React, {memo, useCallback, useEffect, useMemo} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; import type {GestureResponderEvent} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -39,6 +39,9 @@ type MoneyRequestParticipantsSelectorProps = { /** Callback to add participants in MoneyRequestModal */ onParticipantsAdded: (value: Participant[]) => void; + /** Callback to fire when list emptiness changes */ + onEmptyListChange?: (isEmpty: boolean) => void; + /** Selected participants from MoneyRequestModal with login */ participants?: Participant[] | typeof CONST.EMPTY_ARRAY; @@ -52,7 +55,15 @@ type MoneyRequestParticipantsSelectorProps = { action: IOUAction; }; -function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onFinish, onParticipantsAdded, iouType, iouRequestType, action}: MoneyRequestParticipantsSelectorProps) { +function MoneyRequestParticipantsSelector({ + participants = CONST.EMPTY_ARRAY, + onFinish, + onParticipantsAdded, + onEmptyListChange: onEmptyListChangeProp, + iouType, + iouRequestType, + action, +}: MoneyRequestParticipantsSelectorProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); @@ -68,13 +79,14 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF const {options, areOptionsInitialized} = useOptionsList({ shouldInitialize: didScreenTransitionEnd, }); + const [isEmptyList, setIsEmptyList] = useState(false); const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const isIOUSplit = iouType === CONST.IOU.TYPE.SPLIT; const isCategorizeOrShareAction = [CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].some((option) => option === action); - const shouldShowReferralBanner = !isDismissed && iouType !== CONST.IOU.TYPE.INVOICE; + const shouldShowReferralBanner = !isDismissed && iouType !== CONST.IOU.TYPE.INVOICE && !isEmptyList; useEffect(() => { Report.searchInServer(debouncedSearchTerm.trim()); @@ -400,6 +412,14 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF [isIOUSplit, addParticipantToSelection, addSingleParticipant], ); + const onEmptyListChange = useCallback( + (isEmpty: boolean) => { + setIsEmptyList(isEmpty); + onEmptyListChangeProp?.(isEmpty); + }, + [onEmptyListChangeProp], + ); + return ( } + listEmptyContent={ + + } headerMessage={header} showLoadingPlaceholder={!areOptionsInitialized || !didScreenTransitionEnd} canSelectMultiple={isIOUSplit && isAllowedToSplit} diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 7fbc8d260f8a..707711bc6490 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -1,5 +1,5 @@ import {useIsFocused} from '@react-navigation/core'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -45,6 +45,8 @@ function IOURequestStepParticipants({ const {translate} = useLocalize(); const styles = useThemeStyles(); const isFocused = useIsFocused(); + const [shouldEnableKeyboardAvoidingView, setShouldEnableKeyboardAvoidingView] = useState(); + const [shouldEnableMaxHeight, setShouldEnableMaxHeight] = useState(); // We need to set selectedReportID if user has navigated back from confirmation page and navigates to confirmation page with already selected participant const selectedReportID = useRef(participants?.length === 1 ? participants[0]?.reportID ?? reportID : reportID); @@ -138,6 +140,11 @@ function IOURequestStepParticipants({ } }, [iouType, transactionID, transaction, reportID, action, participants]); + const onEmptyListChange = useCallback((isEmpty: boolean) => { + setShouldEnableKeyboardAvoidingView(!isEmpty); + setShouldEnableMaxHeight(!isEmpty); + }, []); + const navigateBack = useCallback(() => { IOUUtils.navigateToStartMoneyRequestStep(iouRequestType, iouType, transactionID, reportID, action); }, [iouRequestType, iouType, transactionID, reportID, action]); @@ -158,6 +165,8 @@ function IOURequestStepParticipants({ shouldShowWrapper testID={IOURequestStepParticipants.displayName} includeSafeAreaPaddingBottom={false} + shouldEnableKeyboardAvoidingView={shouldEnableKeyboardAvoidingView} + shouldEnableMaxHeight={shouldEnableMaxHeight} > {skipConfirmation && ( ; }; @@ -44,6 +50,8 @@ function StepScreenWrapper({ shouldShowWrapper, shouldShowNotFoundPage, includeSafeAreaPaddingBottom, + shouldEnableKeyboardAvoidingView, + shouldEnableMaxHeight, }: StepScreenWrapperProps) { const styles = useThemeStyles(); @@ -56,7 +64,9 @@ function StepScreenWrapper({ includeSafeAreaPaddingBottom={includeSafeAreaPaddingBottom} onEntryTransitionEnd={onEntryTransitionEnd} testID={testID} - shouldEnableMaxHeight={DeviceCapabilities.canUseTouchScreen()} + shouldEnableKeyboardAvoidingView={shouldEnableKeyboardAvoidingView} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + shouldEnableMaxHeight={shouldEnableMaxHeight || DeviceCapabilities.canUseTouchScreen()} > {({insets, safeAreaPaddingBottomStyle, didScreenTransitionEnd}) => ( From 2e39825ba5213af06354c7744b7088bd770f33ac Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 24 Jul 2024 16:11:19 +0700 Subject: [PATCH 10/16] revert: let empty state ui be obsecured by keyboard --- .../MoneyRequestParticipantsSelector.tsx | 23 ++++--------------- .../step/IOURequestStepParticipants.tsx | 12 +--------- .../iou/request/step/StepScreenWrapper.tsx | 12 +--------- 3 files changed, 6 insertions(+), 41 deletions(-) diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index b897903f4e8e..ed4f7be61393 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -39,9 +39,6 @@ type MoneyRequestParticipantsSelectorProps = { /** Callback to add participants in MoneyRequestModal */ onParticipantsAdded: (value: Participant[]) => void; - /** Callback to fire when list emptiness changes */ - onEmptyListChange?: (isEmpty: boolean) => void; - /** Selected participants from MoneyRequestModal with login */ participants?: Participant[] | typeof CONST.EMPTY_ARRAY; @@ -55,15 +52,7 @@ type MoneyRequestParticipantsSelectorProps = { action: IOUAction; }; -function MoneyRequestParticipantsSelector({ - participants = CONST.EMPTY_ARRAY, - onFinish, - onParticipantsAdded, - onEmptyListChange: onEmptyListChangeProp, - iouType, - iouRequestType, - action, -}: MoneyRequestParticipantsSelectorProps) { +function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onFinish, onParticipantsAdded, iouType, iouRequestType, action}: MoneyRequestParticipantsSelectorProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); @@ -423,13 +412,9 @@ function MoneyRequestParticipantsSelector({ [isIOUSplit, addParticipantToSelection, addSingleParticipant], ); - const onEmptyListChange = useCallback( - (isEmpty: boolean) => { - setIsEmptyList(isEmpty); - onEmptyListChangeProp?.(isEmpty); - }, - [onEmptyListChangeProp], - ); + const onEmptyListChange = useCallback((isEmpty: boolean) => { + setIsEmptyList(isEmpty); + }, []); return ( (); - const [shouldEnableMaxHeight, setShouldEnableMaxHeight] = useState(); // We need to set selectedReportID if user has navigated back from confirmation page and navigates to confirmation page with already selected participant const selectedReportID = useRef(participants?.length === 1 ? participants[0]?.reportID ?? reportID : reportID); @@ -140,11 +138,6 @@ function IOURequestStepParticipants({ } }, [iouType, transactionID, transaction, reportID, action, participants]); - const onEmptyListChange = useCallback((isEmpty: boolean) => { - setShouldEnableKeyboardAvoidingView(!isEmpty); - setShouldEnableMaxHeight(!isEmpty); - }, []); - const navigateBack = useCallback(() => { IOUUtils.navigateToStartMoneyRequestStep(iouRequestType, iouType, transactionID, reportID, action); }, [iouRequestType, iouType, transactionID, reportID, action]); @@ -165,8 +158,6 @@ function IOURequestStepParticipants({ shouldShowWrapper testID={IOURequestStepParticipants.displayName} includeSafeAreaPaddingBottom={false} - shouldEnableKeyboardAvoidingView={shouldEnableKeyboardAvoidingView} - shouldEnableMaxHeight={shouldEnableMaxHeight} > {skipConfirmation && ( ; }; @@ -50,8 +44,6 @@ function StepScreenWrapper({ shouldShowWrapper, shouldShowNotFoundPage, includeSafeAreaPaddingBottom, - shouldEnableKeyboardAvoidingView, - shouldEnableMaxHeight, }: StepScreenWrapperProps) { const styles = useThemeStyles(); @@ -64,9 +56,7 @@ function StepScreenWrapper({ includeSafeAreaPaddingBottom={includeSafeAreaPaddingBottom} onEntryTransitionEnd={onEntryTransitionEnd} testID={testID} - shouldEnableKeyboardAvoidingView={shouldEnableKeyboardAvoidingView} - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - shouldEnableMaxHeight={shouldEnableMaxHeight || DeviceCapabilities.canUseTouchScreen()} + shouldEnableMaxHeight={DeviceCapabilities.canUseTouchScreen()} > {({insets, safeAreaPaddingBottomStyle, didScreenTransitionEnd}) => ( From 4a837df2a9e9e4543bfe1096d967b663a501507b Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 24 Jul 2024 16:14:32 +0700 Subject: [PATCH 11/16] resize icon to 120x125 --- src/components/EmptySelectionListContent.tsx | 4 ++-- src/styles/variables.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index 5a00c6a03ca5..d8892b11cefc 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -54,8 +54,8 @@ function EmptySelectionListContent({contentType, onEmptyChange}: EmptySelectionL Date: Thu, 1 Aug 2024 19:07:08 +0700 Subject: [PATCH 12/16] fix: empty ui jumps --- src/components/EmptySelectionListContent.tsx | 16 ++-------------- .../SelectionList/BaseSelectionList.tsx | 16 +++++++++++++++- src/components/SelectionList/types.ts | 3 +++ .../request/MoneyRequestParticipantsSelector.tsx | 10 +++------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index d8892b11cefc..91853ae5f3d8 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React from 'react'; import {View} from 'react-native'; import type {TupleToUnion} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; @@ -13,9 +13,6 @@ import TextLink from './TextLink'; type EmptySelectionListContentProps = { /** Type of selection list */ contentType: string; - - /** Callback to trigger when list emptiness changes (i.e. component mounts and unmounts) */ - onEmptyChange?: (isEmpty: boolean) => void; }; const CONTENT_TYPES = [CONST.IOU.TYPE.SUBMIT, CONST.IOU.TYPE.SPLIT, CONST.IOU.TYPE.PAY]; @@ -25,19 +22,10 @@ function isContentType(contentType: unknown): contentType is ContentType { return CONTENT_TYPES.includes(contentType as ContentType); } -function EmptySelectionListContent({contentType, onEmptyChange}: EmptySelectionListContentProps) { +function EmptySelectionListContent({contentType}: EmptySelectionListContentProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - useEffect(() => { - if (!onEmptyChange) { - return; - } - - onEmptyChange(true); - return () => onEmptyChange(false); - }, [onEmptyChange]); - if (!isContentType(contentType)) { return null; } diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 1d3f4dfddb22..ad93c00b42e3 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -62,6 +62,7 @@ function BaseSelectionList( footerContent, listFooterContent, listEmptyContent, + onListEmptyChange, showScrollIndicator = true, showLoadingPlaceholder = false, showConfirmButton = false, @@ -481,11 +482,24 @@ function BaseSelectionList( ); }; + const shouldShowListEmptyContent = useMemo( + () => flattenedSections.allOptions.length === 0 && !!listEmptyContent && !textInputValue && !showLoadingPlaceholder && headerMessage !== translate('common.noResultsFound'), + [textInputValue, showLoadingPlaceholder, headerMessage, listEmptyContent], + ); + + useEffect(() => { + if (!onListEmptyChange) { + return; + } + + onListEmptyChange(shouldShowListEmptyContent); + }, [shouldShowListEmptyContent]); + const renderListEmptyContent = () => { if (showLoadingPlaceholder) { return ; } - if (!textInputValue && !showLoadingPlaceholder && !!listEmptyContent) { + if (shouldShowListEmptyContent) { return listEmptyContent; } return null; diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index a5985efd5621..570245735d75 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -416,6 +416,9 @@ type BaseSelectionListProps = Partial & { /** Custom content to display when the list is empty after finish loading */ listEmptyContent?: React.JSX.Element | null; + /** Callback to fire when the list emptiness changes after finish loading */ + onListEmptyChange?: (isEmpty: boolean) => void; + /** Whether to use dynamic maxToRenderPerBatch depending on the visible number of elements */ shouldUseDynamicMaxToRenderPerBatch?: boolean; diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index ed4f7be61393..8968a30c94ea 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -412,7 +412,7 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF [isIOUSplit, addParticipantToSelection, addSingleParticipant], ); - const onEmptyListChange = useCallback((isEmpty: boolean) => { + const onListEmptyChange = useCallback((isEmpty: boolean) => { setIsEmptyList(isEmpty); }, []); @@ -429,12 +429,8 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF onSelectRow={onSelectRow} shouldDebounceRowSelect footerContent={footerContent} - listEmptyContent={ - - } + listEmptyContent={} + onListEmptyChange={onListEmptyChange} headerMessage={header} showLoadingPlaceholder={!areOptionsInitialized || !didScreenTransitionEnd} canSelectMultiple={isIOUSplit && isAllowedToSplit} From 6c015dd5893e9fd928512946864b31be1f222859 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 9 Aug 2024 23:53:20 +0700 Subject: [PATCH 13/16] fix: blink ui --- .../SelectionList/BaseSelectionList.tsx | 15 +------- src/components/SelectionList/types.ts | 6 ++-- .../MoneyRequestParticipantsSelector.tsx | 34 +++++++++++++------ 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index e76ce7ba75f9..9fb32b0d502e 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -62,7 +62,6 @@ function BaseSelectionList( footerContent, listFooterContent, listEmptyContent, - onListEmptyChange, showScrollIndicator = true, showLoadingPlaceholder = false, showConfirmButton = false, @@ -98,6 +97,7 @@ function BaseSelectionList( shouldDelayFocus = true, shouldUpdateFocusedIndex = false, onLongPressRow, + shouldShowListEmptyContent = false, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -475,19 +475,6 @@ function BaseSelectionList( ); }; - const shouldShowListEmptyContent = useMemo( - () => flattenedSections.allOptions.length === 0 && !!listEmptyContent && !textInputValue && !showLoadingPlaceholder && headerMessage !== translate('common.noResultsFound'), - [textInputValue, showLoadingPlaceholder, headerMessage, listEmptyContent], - ); - - useEffect(() => { - if (!onListEmptyChange) { - return; - } - - onListEmptyChange(shouldShowListEmptyContent); - }, [shouldShowListEmptyContent]); - const renderListEmptyContent = () => { if (showLoadingPlaceholder) { return ; diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 81ae992435ed..ed1813a07dad 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -413,9 +413,6 @@ type BaseSelectionListProps = Partial & { /** Custom content to display when the list is empty after finish loading */ listEmptyContent?: React.JSX.Element | null; - /** Callback to fire when the list emptiness changes after finish loading */ - onListEmptyChange?: (isEmpty: boolean) => void; - /** Whether to use dynamic maxToRenderPerBatch depending on the visible number of elements */ shouldUseDynamicMaxToRenderPerBatch?: boolean; @@ -492,6 +489,9 @@ type BaseSelectionListProps = Partial & { /** Callback to fire when the item is long pressed */ onLongPressRow?: (item: TItem) => void; + + /** Whether to show the empty list content */ + shouldShowListEmptyContent?: boolean; } & TRightHandSideComponent; type SelectionListHandle = { diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index a651f2af3feb..7c918b2be80a 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -1,7 +1,7 @@ import lodashIsEqual from 'lodash/isEqual'; import lodashPick from 'lodash/pick'; import lodashReject from 'lodash/reject'; -import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; +import React, {memo, useCallback, useEffect, useMemo} from 'react'; import type {GestureResponderEvent} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -68,16 +68,12 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF const {options, areOptionsInitialized} = useOptionsList({ shouldInitialize: didScreenTransitionEnd, }); - const [isEmptyList, setIsEmptyList] = useState(false); - const cleanSearchTerm = useMemo(() => debouncedSearchTerm.trim().toLowerCase(), [debouncedSearchTerm]); const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const isIOUSplit = iouType === CONST.IOU.TYPE.SPLIT; const isCategorizeOrShareAction = [CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].some((option) => option === action); - const shouldShowReferralBanner = !isDismissed && iouType !== CONST.IOU.TYPE.INVOICE && !isEmptyList; - useEffect(() => { Report.searchInServer(debouncedSearchTerm.trim()); }, [debouncedSearchTerm]); @@ -342,6 +338,26 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF [shouldShowSplitBillErrorMessage, onFinish, addSingleParticipant, participants], ); + const showLoadingPlaceholder = useMemo(() => !areOptionsInitialized || !didScreenTransitionEnd, [areOptionsInitialized, didScreenTransitionEnd]); + + const optionLength = useMemo(() => { + if (!areOptionsInitialized) { + return 0; + } + let length = 0; + sections.forEach((section) => { + length += section.data.length; + }); + return length; + }, [areOptionsInitialized, sections]); + + const shouldShowListEmptyContent = useMemo( + () => optionLength === 0 && !searchTerm && !showLoadingPlaceholder && header !== translate('common.noResultsFound'), + [optionLength, searchTerm, showLoadingPlaceholder, header, translate], + ); + + const shouldShowReferralBanner = !isDismissed && iouType !== CONST.IOU.TYPE.INVOICE && !shouldShowListEmptyContent; + const footerContent = useMemo(() => { if (isDismissed && !shouldShowSplitBillErrorMessage && !participants.length) { return; @@ -415,10 +431,6 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF [isIOUSplit, addParticipantToSelection, addSingleParticipant], ); - const onListEmptyChange = useCallback((isEmpty: boolean) => { - setIsEmptyList(isEmpty); - }, []); - return ( } - onListEmptyChange={onListEmptyChange} headerMessage={header} - showLoadingPlaceholder={!areOptionsInitialized || !didScreenTransitionEnd} + showLoadingPlaceholder={showLoadingPlaceholder} canSelectMultiple={isIOUSplit && isAllowedToSplit} isLoadingNewOptions={!!isSearchingForReports} + shouldShowListEmptyContent={shouldShowListEmptyContent} /> ); } From 5f9cb383aed86888bc02728abc62c2c3335283e5 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 13 Aug 2024 10:51:41 +0700 Subject: [PATCH 14/16] flicker issue --- src/components/SelectionList/BaseSelectionList.tsx | 2 +- src/pages/iou/request/MoneyRequestParticipantsSelector.tsx | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 9fb32b0d502e..57b34f4caf57 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -682,7 +682,7 @@ function BaseSelectionList( )} {/* If we are loading new options we will avoid showing any header message. This is mostly because one of the header messages says there are no options. */} {/* This is misleading because we might be in the process of loading fresh options from the server. */} - {(!isLoadingNewOptions || headerMessage !== translate('common.noResultsFound')) && !!headerMessage && ( + {(((!isLoadingNewOptions || headerMessage !== translate('common.noResultsFound')) && !!headerMessage) || flattenedSections.allOptions.length === 0) && ( {headerMessage} diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 7c918b2be80a..4710dfbb9998 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -351,10 +351,7 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF return length; }, [areOptionsInitialized, sections]); - const shouldShowListEmptyContent = useMemo( - () => optionLength === 0 && !searchTerm && !showLoadingPlaceholder && header !== translate('common.noResultsFound'), - [optionLength, searchTerm, showLoadingPlaceholder, header, translate], - ); + const shouldShowListEmptyContent = useMemo(() => optionLength === 0 && !showLoadingPlaceholder, [optionLength, showLoadingPlaceholder]); const shouldShowReferralBanner = !isDismissed && iouType !== CONST.IOU.TYPE.INVOICE && !shouldShowListEmptyContent; From 10a792e18439748ad8228b9ba54f15e6422fa0e9 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 13 Aug 2024 15:21:49 +0700 Subject: [PATCH 15/16] add padding bottom --- src/components/EmptySelectionListContent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index 91853ae5f3d8..1d59c09b83b3 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -47,6 +47,7 @@ function EmptySelectionListContent({contentType}: EmptySelectionListContentProps title={translate(`emptyList.${contentType}.title`)} shouldShowLink={false} CustomSubtitle={EmptySubtitle} + containerStyle={styles.mb8} /> ); From 023eba131ca9efd12d6d3d3450fb0728240a4cd5 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 14 Aug 2024 11:28:12 +0700 Subject: [PATCH 16/16] increase padding --- src/components/EmptySelectionListContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/EmptySelectionListContent.tsx b/src/components/EmptySelectionListContent.tsx index 1d59c09b83b3..9bae189ac183 100644 --- a/src/components/EmptySelectionListContent.tsx +++ b/src/components/EmptySelectionListContent.tsx @@ -47,7 +47,7 @@ function EmptySelectionListContent({contentType}: EmptySelectionListContentProps title={translate(`emptyList.${contentType}.title`)} shouldShowLink={false} CustomSubtitle={EmptySubtitle} - containerStyle={styles.mb8} + containerStyle={[styles.mb8, styles.ph15]} /> );