From beca6d038768470d45ced7a91f45e28f50845ae9 Mon Sep 17 00:00:00 2001 From: Thiago Brezinski Date: Wed, 13 Sep 2023 14:26:16 +0100 Subject: [PATCH 01/18] chore: wip --- src/libs/OptionsListUtils.js | 13 +++---- .../MoneyRequestParticipantsSelector.js | 36 ++++++++++--------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 7629a1acc0a6..cf519fb75d14 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1212,32 +1212,33 @@ function getShareDestinationOptions( * Format personalDetails or userToInvite to be shown in the list * * @param {Object} member - personalDetails or userToInvite - * @param {Boolean} isSelected - whether the item is selected + * @param {Object} config - keys to overwrite the default values * @returns {Object} */ -function formatMemberForList(member, isSelected) { +function formatMemberForList(member, config = {}) { if (!member) { return undefined; } - const avatarSource = lodashGet(member, 'participantsList[0].avatar', '') || lodashGet(member, 'avatar', ''); + const avatarSource = lodashGet(member, 'participantsList[0].avatar', '') || lodashGet(member, 'icons[0].source', '') || lodashGet(member, 'avatar', ''); const accountID = lodashGet(member, 'accountID', ''); return { text: lodashGet(member, 'text', '') || lodashGet(member, 'displayName', ''), alternateText: lodashGet(member, 'alternateText', '') || lodashGet(member, 'login', ''), keyForList: lodashGet(member, 'keyForList', '') || String(accountID), - isSelected, + isSelected: false, isDisabled: false, accountID, login: lodashGet(member, 'login', ''), rightElement: null, avatar: { source: UserUtils.getAvatar(avatarSource, accountID), - name: lodashGet(member, 'participantsList[0].login', '') || lodashGet(member, 'displayName', ''), - type: 'avatar', + name: lodashGet(member, 'participantsList[0].login', '') || lodashGet(member, 'icons[0].name', '') || lodashGet(member, 'displayName', ''), + type: CONST.ICON_TYPE_AVATAR, }, pendingAction: lodashGet(member, 'pendingAction'), + ...config, }; } diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index ef1d6565f595..390eb0187e90 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -4,13 +4,13 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import * as OptionsListUtils from '../../../../libs/OptionsListUtils'; import * as ReportUtils from '../../../../libs/ReportUtils'; -import OptionsSelector from '../../../../components/OptionsSelector'; import ONYXKEYS from '../../../../ONYXKEYS'; import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; import compose from '../../../../libs/compose'; import CONST from '../../../../CONST'; import personalDetailsPropType from '../../../personalDetailsPropType'; import reportPropTypes from '../../../reportPropTypes'; +import SelectionList from '../../../../components/SelectionList'; const propTypes = { /** Beta features list */ @@ -28,9 +28,6 @@ const propTypes = { /** All reports shared with the user */ reports: PropTypes.objectOf(reportPropTypes), - /** padding bottom style of safe area */ - safeAreaPaddingBottomStyle: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), - /** The type of IOU report, i.e. bill, request, send */ iouType: PropTypes.string.isRequired, @@ -41,7 +38,6 @@ const propTypes = { }; const defaultProps = { - safeAreaPaddingBottomStyle: {}, personalDetails: {}, reports: {}, betas: [], @@ -57,10 +53,14 @@ class MoneyRequestParticipantsSelector extends Component { const {recentReports, personalDetails, userToInvite} = this.getRequestOptions(); + const formattedRecentReports = _.map(recentReports, (report) => OptionsListUtils.formatMemberForList(report)); + const formattedPersonalDetails = _.map(personalDetails, (personalDetail) => OptionsListUtils.formatMemberForList(personalDetail)); + const formattedUserToInvite = OptionsListUtils.formatMemberForList(userToInvite); + this.state = { - recentReports, - personalDetails, - userToInvite, + recentReports: formattedRecentReports, + personalDetails: formattedPersonalDetails, + userToInvite: formattedUserToInvite, searchTerm: '', }; } @@ -133,11 +133,15 @@ class MoneyRequestParticipantsSelector extends Component { updateOptionsWithSearchTerm(searchTerm = '') { const {recentReports, personalDetails, userToInvite} = this.getRequestOptions(searchTerm); + const formattedRecentReports = _.map(recentReports, (report) => OptionsListUtils.formatMemberForList(report)); + const formattedPersonalDetails = _.map(personalDetails, (personalDetail) => OptionsListUtils.formatMemberForList(personalDetail)); + const formattedUserToInvite = OptionsListUtils.formatMemberForList(userToInvite); + this.setState({ + recentReports: formattedRecentReports, + personalDetails: formattedPersonalDetails, + userToInvite: formattedUserToInvite, searchTerm, - recentReports, - userToInvite, - personalDetails, }); } @@ -160,16 +164,14 @@ class MoneyRequestParticipantsSelector extends Component { const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(this.props.personalDetails); return ( - ); } From 37f66e44d67d94049f6a7715db92001f5f150297 Mon Sep 17 00:00:00 2001 From: Thiago Brezinski Date: Wed, 13 Sep 2023 20:12:49 +0100 Subject: [PATCH 02/18] refactor(selection-list): create BaseListItem --- src/components/SelectionList/BaseListItem.js | 95 +++++++++++++++++++ .../SelectionList/BaseSelectionList.js | 40 +++++--- src/components/SelectionList/RadioListItem.js | 46 ++------- src/components/SelectionList/UserListItem.js | 82 ++++------------ 4 files changed, 147 insertions(+), 116 deletions(-) create mode 100644 src/components/SelectionList/BaseListItem.js diff --git a/src/components/SelectionList/BaseListItem.js b/src/components/SelectionList/BaseListItem.js new file mode 100644 index 000000000000..c6a3a948eab9 --- /dev/null +++ b/src/components/SelectionList/BaseListItem.js @@ -0,0 +1,95 @@ +import React from 'react'; +import {View} from 'react-native'; +import PressableWithFeedback from '../Pressable/PressableWithFeedback'; +import styles from '../../styles/styles'; +import Icon from '../Icon'; +import * as Expensicons from '../Icon/Expensicons'; +import themeColors from '../../styles/themes/default'; +import {radioListItemPropTypes} from './selectionListPropTypes'; +import * as StyleUtils from '../../styles/StyleUtils'; +import UserListItem from './UserListItem'; +import RadioListItem from './RadioListItem'; +import OfflineWithFeedback from '../OfflineWithFeedback'; + +function BaseListItem({item, isFocused = false, isDisabled = false, showTooltip, canSelectMultiple, onSelectRow, onDismissError = () => {}}) { + const isUserItem = Boolean(item.avatar); + const ListItem = isUserItem ? UserListItem : RadioListItem; + + return ( + onDismissError(item)} + pendingAction={item.pendingAction} + errors={item.errors} + errorRowStyles={styles.ph5} + > + onSelectRow(item)} + disabled={isDisabled} + accessibilityLabel={item.text} + accessibilityRole="button" + hoverDimmingValue={1} + hoverStyle={styles.hoveredComponentBG} + focusStyle={styles.hoveredComponentBG} + > + + {canSelectMultiple && ( + + {item.isSelected && ( + + )} + + )} + + + + {!canSelectMultiple && item.isSelected && ( + + + + + + )} + + + + ); +} + +BaseListItem.displayName = 'BaseListItem'; +BaseListItem.propTypes = radioListItemPropTypes; + +export default BaseListItem; diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 2cf0d2d72695..2ed0ecca1962 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -24,6 +24,7 @@ import useLocalize from '../../hooks/useLocalize'; import Log from '../../libs/Log'; import OptionsListSkeletonView from '../OptionsListSkeletonView'; import useActiveElement from '../../hooks/useActiveElement'; +import BaseListItem from './BaseListItem'; const propTypes = { ...keyboardStatePropTypes, @@ -243,31 +244,42 @@ function BaseSelectionList({ const renderItem = ({item, index, section}) => { const normalizedIndex = index + lodashGet(section, 'indexOffset', 0); - const isDisabled = section.isDisabled; + const isDisabled = section.isDisabled || item.isDisabled; const isFocused = !isDisabled && focusedIndex === normalizedIndex; // We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade. const showTooltip = normalizedIndex < 10; - if (canSelectMultiple) { - return ( - selectRow(item, index)} - onDismissError={onDismissError} - showTooltip={showTooltip} - /> - ); - } - return ( - selectRow(item, index)} /> ); + + // if (canSelectMultiple) { + // return ( + // selectRow(item, index)} + // onDismissError={onDismissError} + // showTooltip={showTooltip} + // /> + // ); + // } + // + // return ( + // selectRow(item, index)} + // /> + // ); }; /** Focuses the text input when the component comes into focus and after any navigation animations finish. */ diff --git a/src/components/SelectionList/RadioListItem.js b/src/components/SelectionList/RadioListItem.js index 92e3e84b66c8..83d0fc922f08 100644 --- a/src/components/SelectionList/RadioListItem.js +++ b/src/components/SelectionList/RadioListItem.js @@ -1,50 +1,18 @@ import React from 'react'; import {View} from 'react-native'; -import PressableWithFeedback from '../Pressable/PressableWithFeedback'; import styles from '../../styles/styles'; import Text from '../Text'; -import Icon from '../Icon'; -import * as Expensicons from '../Icon/Expensicons'; -import themeColors from '../../styles/themes/default'; import {radioListItemPropTypes} from './selectionListPropTypes'; -function RadioListItem({item, isFocused = false, isDisabled = false, onSelectRow}) { +function RadioListItem({item, isFocused = false}) { return ( - onSelectRow(item)} - disabled={isDisabled} - accessibilityLabel={item.text} - accessibilityRole="button" - hoverDimmingValue={1} - hoverStyle={styles.hoveredComponentBG} - focusStyle={styles.hoveredComponentBG} - > - - - - {item.text} - + + {item.text} - {Boolean(item.alternateText) && ( - {item.alternateText} - )} - - - {item.isSelected && ( - - - - - - )} - - + {Boolean(item.alternateText) && ( + {item.alternateText} + )} + ); } diff --git a/src/components/SelectionList/UserListItem.js b/src/components/SelectionList/UserListItem.js index dd90fc750510..64c2dae2a78c 100644 --- a/src/components/SelectionList/UserListItem.js +++ b/src/components/SelectionList/UserListItem.js @@ -1,24 +1,15 @@ import React from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; import lodashGet from 'lodash/get'; -import PressableWithFeedback from '../Pressable/PressableWithFeedback'; import styles from '../../styles/styles'; import Text from '../Text'; import {userListItemPropTypes} from './selectionListPropTypes'; import Avatar from '../Avatar'; -import OfflineWithFeedback from '../OfflineWithFeedback'; import CONST from '../../CONST'; -import * as StyleUtils from '../../styles/StyleUtils'; -import Icon from '../Icon'; -import * as Expensicons from '../Icon/Expensicons'; -import themeColors from '../../styles/themes/default'; import Tooltip from '../Tooltip'; import UserDetailsTooltip from '../UserDetailsTooltip'; -function UserListItem({item, isFocused = false, showTooltip, onSelectRow, onDismissError = () => {}}) { - const hasError = !_.isEmpty(item.errors); - +function UserListItem({item, isFocused = false, showTooltip}) { const avatar = ( onDismissError(item)} - pendingAction={item.pendingAction} - errors={item.errors} - errorRowStyles={styles.ph5} - > - onSelectRow(item)} - disabled={item.isDisabled} - accessibilityLabel={item.text} - accessibilityRole="checkbox" - accessibilityState={{checked: item.isSelected}} - hoverDimmingValue={1} - hoverStyle={styles.hoveredComponentBG} - focusStyle={styles.hoveredComponentBG} - > - - {item.isSelected && ( - - )} - - {Boolean(item.avatar) && - (showTooltip ? ( - - {avatar} - - ) : ( - avatar - ))} - - {showTooltip ? {text} : text} - {Boolean(item.alternateText) && (showTooltip ? {alternateText} : alternateText)} - - {Boolean(item.rightElement) && item.rightElement} - - + <> + {Boolean(item.avatar) && + (showTooltip ? ( + + {avatar} + + ) : ( + avatar + ))} + + {showTooltip ? {text} : text} + {Boolean(item.alternateText) && (showTooltip ? {alternateText} : alternateText)} + + {Boolean(item.rightElement) && item.rightElement} + ); } From 9d3cb2ea931660ba0ea92657adcb42105dd0e463 Mon Sep 17 00:00:00 2001 From: Thiago Brezinski Date: Mon, 18 Sep 2023 15:31:01 +0100 Subject: [PATCH 03/18] feat(selection-list): request money --- .../MoneyRequestConfirmationList.js | 55 ++-- src/components/SelectionList/BaseListItem.js | 11 +- .../SelectionList/BaseSelectionList.js | 255 +++++++++--------- src/components/SelectionList/UserListItem.js | 27 +- .../SelectionList/selectionListPropTypes.js | 71 +++-- src/components/SubscriptAvatar.js | 91 ++++--- src/libs/OptionsListUtils.js | 118 ++++---- .../iou/steps/MoneyRequestConfirmPage.js | 33 ++- .../MoneyRequestParticipantsSelector.js | 18 +- src/pages/workspace/WorkspaceMembersPage.js | 12 +- 10 files changed, 369 insertions(+), 322 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index da98d324681e..c4cee1e23b48 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -9,7 +9,6 @@ import styles from '../styles/styles'; import * as ReportUtils from '../libs/ReportUtils'; import * as OptionsListUtils from '../libs/OptionsListUtils'; import Permissions from '../libs/Permissions'; -import OptionsSelector from './OptionsSelector'; import ONYXKEYS from '../ONYXKEYS'; import compose from '../libs/compose'; import CONST from '../CONST'; @@ -29,12 +28,14 @@ import themeColors from '../styles/themes/default'; import Image from './Image'; import useLocalize from '../hooks/useLocalize'; import * as ReceiptUtils from '../libs/ReceiptUtils'; +import * as LocalePhoneNumber from '../libs/LocalePhoneNumber'; import categoryPropTypes from './categoryPropTypes'; import tagPropTypes from './tagPropTypes'; import ConfirmedRoute from './ConfirmedRoute'; import transactionPropTypes from './transactionPropTypes'; import DistanceRequestUtils from '../libs/DistanceRequestUtils'; import * as IOU from '../libs/actions/IOU'; +import SelectionList from './SelectionList'; const propTypes = { /** Callback to inform parent modal of success */ @@ -221,7 +222,10 @@ function MoneyRequestConfirmationList(props) { const getParticipantsWithAmount = useCallback( (participantsList) => { const iouAmount = IOUUtils.calculateAmount(participantsList.length, props.iouAmount, props.iouCurrencyCode); - return OptionsListUtils.getIOUConfirmationOptionsFromParticipants(participantsList, CurrencyUtils.convertToDisplayString(iouAmount, props.iouCurrencyCode)); + + return _.map(participantsList, (participant) => + OptionsListUtils.formatMemberForList(participant, {descriptiveText: CurrencyUtils.convertToDisplayString(iouAmount, props.iouCurrencyCode)}), + ); }, [props.iouAmount, props.iouCurrencyCode], ); @@ -251,10 +255,12 @@ function MoneyRequestConfirmationList(props) { const optionSelectorSections = useMemo(() => { const sections = []; + + // TODO: REVIEW const unselectedParticipants = _.filter(props.selectedParticipants, (participant) => !participant.selected); if (props.hasMultipleParticipants) { const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipants); - let formattedParticipantsList = _.union(formattedSelectedParticipants, unselectedParticipants); + let formattedParticipantsList = _.map(_.union(formattedSelectedParticipants, unselectedParticipants), OptionsListUtils.formatMemberForList); if (!canModifyParticipants) { formattedParticipantsList = _.map(formattedParticipantsList, (participant) => ({ @@ -264,10 +270,10 @@ function MoneyRequestConfirmationList(props) { } const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, props.iouAmount, props.iouCurrencyCode, true); - const formattedPayeeOption = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail( - payeePersonalDetails, - CurrencyUtils.convertToDisplayString(myIOUAmount, props.iouCurrencyCode), - ); + const formattedPayeeOption = OptionsListUtils.formatMemberForList(payeePersonalDetails, { + descriptiveText: CurrencyUtils.convertToDisplayString(myIOUAmount, props.iouCurrencyCode), + login: LocalePhoneNumber.formatPhoneNumber(payeePersonalDetails.login), + }); sections.push( { @@ -291,7 +297,8 @@ function MoneyRequestConfirmationList(props) { })); sections.push({ title: translate('common.to'), - data: formattedSelectedParticipants, + // Here we know we only have 1 participant, so it's safe to get the first index + data: [OptionsListUtils.formatMemberForList(formattedSelectedParticipants[0])], shouldShow: true, indexOffset: 0, }); @@ -310,13 +317,6 @@ function MoneyRequestConfirmationList(props) { canModifyParticipants, ]); - const selectedOptions = useMemo(() => { - if (!props.hasMultipleParticipants) { - return []; - } - return [...selectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetails)]; - }, [selectedParticipants, props.hasMultipleParticipants, payeePersonalDetails]); - useEffect(() => { if (!props.isDistanceRequest) { return; @@ -378,6 +378,7 @@ function MoneyRequestConfirmationList(props) { [selectedParticipants, onSendMoney, onConfirm, props.iouType], ); + // TODO: See footer const footerContent = useMemo(() => { if (props.isReadOnly) { return; @@ -387,6 +388,8 @@ function MoneyRequestConfirmationList(props) { const shouldDisableButton = selectedParticipants.length === 0; const recipient = props.selectedParticipants[0] || {}; + console.log('shouldShowSettlementButton', shouldShowSettlementButton); + return shouldShowSettlementButton ? ( {props.isDistanceRequest && ( @@ -532,7 +529,7 @@ function MoneyRequestConfirmationList(props) { )} )} - + ); } diff --git a/src/components/SelectionList/BaseListItem.js b/src/components/SelectionList/BaseListItem.js index 49035fc56a5b..e5ccdd70bbe1 100644 --- a/src/components/SelectionList/BaseListItem.js +++ b/src/components/SelectionList/BaseListItem.js @@ -1,18 +1,19 @@ import React from 'react'; import {View} from 'react-native'; +import lodashGet from 'lodash/get'; import PressableWithFeedback from '../Pressable/PressableWithFeedback'; import styles from '../../styles/styles'; import Icon from '../Icon'; import * as Expensicons from '../Icon/Expensicons'; import themeColors from '../../styles/themes/default'; -import {radioListItemPropTypes} from './selectionListPropTypes'; +import {baseListItemPropTypes} from './selectionListPropTypes'; import * as StyleUtils from '../../styles/StyleUtils'; import UserListItem from './UserListItem'; import RadioListItem from './RadioListItem'; import OfflineWithFeedback from '../OfflineWithFeedback'; function BaseListItem({item, isFocused = false, isDisabled = false, showTooltip, canSelectMultiple, onSelectRow, onDismissError = () => {}}) { - const isUserItem = Boolean(item.avatar); + const isUserItem = lodashGet(item, 'icons.length', 0) > 0; const ListItem = isUserItem ? UserListItem : RadioListItem; return ( @@ -29,7 +30,6 @@ function BaseListItem({item, isFocused = false, isDisabled = false, showTooltip, accessibilityRole="button" hoverDimmingValue={1} hoverStyle={styles.hoveredComponentBG} - focusStyle={styles.hoveredComponentBG} > @@ -92,6 +93,6 @@ function BaseListItem({item, isFocused = false, isDisabled = false, showTooltip, } BaseListItem.displayName = 'BaseListItem'; -BaseListItem.propTypes = radioListItemPropTypes; +BaseListItem.propTypes = baseListItemPropTypes; export default BaseListItem; diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js index 9219600dd85b..ee8fb05706f6 100644 --- a/src/components/SelectionList/BaseSelectionList.js +++ b/src/components/SelectionList/BaseSelectionList.js @@ -11,8 +11,6 @@ import ArrowKeyFocusManager from '../ArrowKeyFocusManager'; import CONST from '../../CONST'; import variables from '../../styles/variables'; import {propTypes as selectionListPropTypes} from './selectionListPropTypes'; -import RadioListItem from './RadioListItem'; -import UserListItem from './UserListItem'; import useKeyboardShortcut from '../../hooks/useKeyboardShortcut'; import SafeAreaConsumer from '../SafeAreaConsumer'; import withKeyboardState, {keyboardStatePropTypes} from '../withKeyboardState'; @@ -25,6 +23,7 @@ import Log from '../../libs/Log'; import OptionsListSkeletonView from '../OptionsListSkeletonView'; import useActiveElement from '../../hooks/useActiveElement'; import BaseListItem from './BaseListItem'; +import useArrowKeyFocusManager from '../../hooks/useArrowKeyFocusManager'; const propTypes = { ...keyboardStatePropTypes, @@ -49,10 +48,13 @@ function BaseSelectionList({ headerMessage = '', confirmButtonText = '', onConfirm, + footerContent, showScrollIndicator = false, showLoadingPlaceholder = false, showConfirmButton = false, isKeyboardShown = false, + disableKeyboardShortcuts = false, + children, }) { const {translate} = useLocalize(); const firstLayoutRef = useRef(true); @@ -136,16 +138,13 @@ function BaseSelectionList({ }; }, [canSelectMultiple, sections]); - // If `initiallyFocusedOptionKey` is not passed, we fall back to `-1`, to avoid showing the highlight on the first member - const [focusedIndex, setFocusedIndex] = useState(() => _.findIndex(flattenedSections.allOptions, (option) => option.keyForList === initiallyFocusedOptionKey)); - /** * Scrolls to the desired item index in the section list * * @param {Number} index - the index of the item to scroll to * @param {Boolean} animated - whether to animate the scroll */ - const scrollToIndex = (index, animated) => { + const scrollToIndex = useCallback((index, animated = true) => { const item = flattenedSections.allOptions[index]; if (!listRef.current || !item) { @@ -166,7 +165,20 @@ function BaseSelectionList({ } listRef.current.scrollToLocation({sectionIndex: adjustedSectionIndex, itemIndex, animated, viewOffset: variables.contentHeaderHeight}); - }; + + // If this function changes, it causes `useArrowKeyFocusManager` to fire `onFocusedIndexChange`, + // making the list scroll back to the focused index when the keyboard disappears. If we don't disable + // dependencies here, we would need to make sure that the `sections` is stable in every usage of this component. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({ + maxIndex: flattenedSections.allOptions.length - 1, + onFocusedIndexChange: scrollToIndex, + initialFocusedIndex: _.findIndex(flattenedSections.allOptions, (option) => option.keyForList === initiallyFocusedOptionKey), + disabledIndexes: flattenedSections.disabledOptionsIndexes, + isActive: !disableKeyboardShortcuts, + }); const selectRow = (item, index) => { // In single-selection lists we don't care about updating the focused index, because the list is closed after selecting an item @@ -219,6 +231,14 @@ function BaseSelectionList({ const getItemLayout = (data, flatDataArrayIndex) => { const targetItem = flattenedSections.itemLayouts[flatDataArrayIndex]; + if (!targetItem) { + return { + length: 0, + offset: 0, + index: flatDataArrayIndex, + }; + } + return { length: targetItem.length, offset: targetItem.offset, @@ -257,29 +277,9 @@ function BaseSelectionList({ showTooltip={showTooltip} canSelectMultiple={canSelectMultiple} onSelectRow={() => selectRow(item, index)} + onDismissError={onDismissError} /> ); - - // if (canSelectMultiple) { - // return ( - // selectRow(item, index)} - // onDismissError={onDismissError} - // showTooltip={showTooltip} - // /> - // ); - // } - // - // return ( - // selectRow(item, index)} - // /> - // ); }; /** Focuses the text input when the component comes into focus and after any navigation animations finish. */ @@ -301,120 +301,113 @@ function BaseSelectionList({ useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, selectFocusedOption, { captureOnInputs: true, shouldBubble: () => !flattenedSections.allOptions[focusedIndex], - isActive: !activeElement, + isActive: !disableKeyboardShortcuts && !activeElement, }); /** Calls confirm action when pressing CTRL (CMD) + Enter */ useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.CTRL_ENTER, onConfirm, { captureOnInputs: true, shouldBubble: () => !flattenedSections.allOptions[focusedIndex], - isActive: Boolean(onConfirm), + isActive: !disableKeyboardShortcuts && Boolean(onConfirm), }); return ( - { - setFocusedIndex(newFocusedIndex); - scrollToIndex(newFocusedIndex, true); - }} - > - - {({safeAreaPaddingBottomStyle}) => ( - - {shouldShowTextInput && ( - - - - )} - {Boolean(headerMessage) && ( - - {headerMessage} - - )} - {flattenedSections.allOptions.length === 0 && showLoadingPlaceholder ? ( - - ) : ( - <> - {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( - + {({safeAreaPaddingBottomStyle}) => ( + + {shouldShowTextInput && ( + + + + )} + {Boolean(headerMessage) && ( + + {headerMessage} + + )} + {flattenedSections.allOptions.length === 0 && showLoadingPlaceholder ? ( + + ) : ( + <> + {!headerMessage && canSelectMultiple && shouldShowSelectAll && ( + + - - - {translate('workspace.people.selectAll')} - - - )} - item.keyForList} - extraData={focusedIndex} - indicatorStyle="white" - keyboardShouldPersistTaps="always" - showsVerticalScrollIndicator={showScrollIndicator} - initialNumToRender={12} - maxToRenderPerBatch={5} - windowSize={5} - viewabilityConfig={{viewAreaCoveragePercentThreshold: 95}} - testID="selection-list" - onLayout={() => { - if (!firstLayoutRef.current) { - return; - } - scrollToIndex(focusedIndex, false); - firstLayoutRef.current = false; - }} - /> - - )} - {showConfirmButton && ( - -