-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Fix/move preselected items to top #80322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 44 commits
722464c
79913be
82578b6
3c359bc
6a442af
05b5547
8f88a11
9d36163
673dce1
c230c97
4cb6618
f85c1cc
5d4f1e6
0db9c28
f7b80f5
3d5b365
1261ff0
6c2077f
7cc43c6
b9845c9
a4c653a
1c6a028
dd9f4ca
2577041
3bf295e
6c8657c
df0ed51
65b1e8a
da409d5
3214915
88b920a
c024773
39950e4
e4a9b40
d589240
fedb58c
8431576
30fa6f6
6f99134
51716ac
d7011e2
02ee08b
fcdb19c
a1194a3
4b40000
ba32165
f56de6f
ba473bb
2813d0d
e63216b
3876e3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,12 @@ | ||
| import {Str} from 'expensify-common'; | ||
| import React, {useState} from 'react'; | ||
| import React, {useMemo, useState} from 'react'; | ||
| import RadioListItem from '@components/SelectionList/ListItem/RadioListItem'; | ||
| import SelectionListWithSections from '@components/SelectionList/SelectionListWithSections'; | ||
| import {useCurrencyListActions, useCurrencyListState} from '@hooks/useCurrencyList'; | ||
| import useInitialSelectionRef from '@hooks/useInitialSelectionRef'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import getMatchScore from '@libs/getMatchScore'; | ||
| import {moveInitialSelectionToTopByValue} from '@libs/SelectionListOrderUtils'; | ||
| import {isEmptyObject} from '@src/types/utils/EmptyObject'; | ||
| import type {CurrencyListItem, CurrencySelectionListProps} from './types'; | ||
|
|
||
|
|
@@ -23,6 +25,17 @@ function CurrencySelectionList({ | |
| const [searchValue, setSearchValue] = useState(''); | ||
| const {translate} = useLocalize(); | ||
| const getUnselectedOptions = (options: CurrencyListItem[]) => options.filter((option) => !option.isSelected); | ||
| const initialSelectedCurrencyCodes = useMemo(() => { | ||
| const codes = new Set<string>(); | ||
| if (initiallySelectedCurrencyCode) { | ||
| codes.add(initiallySelectedCurrencyCode); | ||
| } | ||
| for (const currencyCode of selectedCurrencies) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NAB: |
||
| codes.add(currencyCode); | ||
| } | ||
| return Array.from(codes); | ||
| }, [initiallySelectedCurrencyCode, selectedCurrencies]); | ||
| const initialSelectedCurrencySnapshot = useInitialSelectionRef(initialSelectedCurrencyCodes, {resetDeps: [initialSelectedCurrencyCodes], resetOnFocus: true}); | ||
marufsharifi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| const currencyOptions: CurrencyListItem[] = Object.entries(currencyList).reduce((acc, [currencyCode, currencyInfo]) => { | ||
| const isSelectedCurrency = currencyCode === initiallySelectedCurrencyCode || selectedCurrencies.includes(currencyCode); | ||
|
|
@@ -57,11 +70,19 @@ function CurrencySelectionList({ | |
| .filter((currencyOption) => searchRegex.test(currencyOption.text ?? '') || searchRegex.test(currencyOption.currencyName)) | ||
| .sort((currency1, currency2) => getMatchScore(currency2.text ?? '', searchValue) - getMatchScore(currency1.text ?? '', searchValue)); | ||
|
|
||
| const isEmpty = searchValue.trim() && !filteredCurrencies.length; | ||
| const shouldReorderInitialSelection = !searchValue && initialSelectedCurrencySnapshot.length > 0; | ||
| const displayCurrencies = shouldReorderInitialSelection | ||
| ? moveInitialSelectionToTopByValue( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can just use reorderItemsByInitialSelection instead of moveInitialSelectionToTopByValue. That way we don't need an extra .map() to create a new list |
||
| filteredCurrencies.map((currency) => ({...currency, value: currency.currencyCode})), | ||
| initialSelectedCurrencySnapshot, | ||
| ) | ||
| : filteredCurrencies; | ||
|
|
||
| const isEmpty = searchValue.trim() && !displayCurrencies.length; | ||
| const shouldDisplayRecentlyOptions = !isEmptyObject(recentlyUsedCurrencyOptions) && !searchValue; | ||
| const selectedOptions = filteredCurrencies.filter((option) => option.isSelected); | ||
| const selectedOptions = displayCurrencies.filter((option) => option.isSelected); | ||
| const shouldDisplaySelectedOptionOnTop = selectedOptions.length > 0; | ||
| const unselectedOptions = getUnselectedOptions(filteredCurrencies); | ||
| const unselectedOptions = getUnselectedOptions(displayCurrencies); | ||
| const sections = []; | ||
|
|
||
| if (shouldDisplaySelectedOptionOnTop) { | ||
|
|
@@ -80,12 +101,12 @@ function CurrencySelectionList({ | |
| data: shouldDisplaySelectedOptionOnTop ? getUnselectedOptions(recentlyUsedCurrencyOptions) : recentlyUsedCurrencyOptions, | ||
| sectionIndex: 1, | ||
| }, | ||
| {title: translate('common.all'), data: shouldDisplayRecentlyOptions ? unselectedOptions : filteredCurrencies, sectionIndex: 2}, | ||
| {title: translate('common.all'), data: shouldDisplayRecentlyOptions ? unselectedOptions : displayCurrencies, sectionIndex: 2}, | ||
| ); | ||
| } | ||
| } else if (!isEmpty) { | ||
| sections.push({ | ||
| data: shouldDisplaySelectedOptionOnTop ? unselectedOptions : filteredCurrencies, | ||
| data: shouldDisplaySelectedOptionOnTop ? unselectedOptions : displayCurrencies, | ||
| sectionIndex: 3, | ||
| }); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,10 @@ import Modal from '@components/Modal'; | |
| import ScreenWrapper from '@components/ScreenWrapper'; | ||
| import SelectionList from '@components/SelectionList'; | ||
| import RadioListItem from '@components/SelectionList/ListItem/RadioListItem'; | ||
| import useInitialSelectionRef from '@hooks/useInitialSelectionRef'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import {reorderItemsByInitialSelection} from '@libs/SelectionListOrderUtils'; | ||
| import CONST from '@src/CONST'; | ||
| import type CalendarPickerListItem from './types'; | ||
|
|
||
|
|
@@ -31,13 +33,18 @@ function YearPickerModal({isVisible, years, currentYear = new Date().getFullYear | |
| const styles = useThemeStyles(); | ||
| const {translate} = useLocalize(); | ||
| const [searchText, setSearchText] = useState(''); | ||
| const initialSelectedValues = useInitialSelectionRef([currentYear.toString()], {resetDeps: [isVisible]}); | ||
| const initiallyFocusedYear = initialSelectedValues.at(0); | ||
| const {data, headerMessage} = useMemo(() => { | ||
| const yearsList = searchText === '' ? years : years.filter((year) => year.text?.includes(searchText)); | ||
| const sortedYears = [...yearsList].sort((a, b) => b.value - a.value); | ||
| const orderedYears = | ||
| searchText || sortedYears.length <= CONST.MOVE_SELECTED_ITEMS_TO_TOP_OF_LIST_THRESHOLD ? sortedYears : reorderItemsByInitialSelection(sortedYears, initialSelectedValues); | ||
| return { | ||
| headerMessage: !yearsList.length ? translate('common.noResultsFound') : '', | ||
| data: yearsList.sort((a, b) => b.value - a.value), | ||
| headerMessage: !orderedYears.length ? translate('common.noResultsFound') : '', | ||
| data: orderedYears, | ||
| }; | ||
| }, [years, searchText, translate]); | ||
| }, [initialSelectedValues, searchText, translate, years]); | ||
|
|
||
| useEffect(() => { | ||
| if (isVisible) { | ||
|
|
@@ -84,11 +91,13 @@ function YearPickerModal({isVisible, years, currentYear = new Date().getFullYear | |
| ListItem={RadioListItem} | ||
| onSelectRow={(option) => { | ||
| Keyboard.dismiss(); | ||
| onYearChange?.(option.value); | ||
| onYearChange?.(Number(option.keyForList)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to update this? |
||
| }} | ||
| textInputOptions={textInputOptions} | ||
| initiallyFocusedItemKey={currentYear.toString()} | ||
| initiallyFocusedItemKey={initiallyFocusedYear} | ||
| disableMaintainingScrollPosition | ||
| shouldScrollToFocusedIndex={false} | ||
| shouldScrollToFocusedIndexOnMount={false} | ||
| addBottomSafeAreaPadding | ||
| shouldStopPropagation | ||
| showScrollIndicator | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import React, {useCallback} from 'react'; | ||
| import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem'; | ||
| import SelectionListWithSections from '@components/SelectionList/SelectionListWithSections'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import Navigation from '@libs/Navigation/Navigation'; | ||
| import type {Route} from '@src/ROUTES'; | ||
| import type {RuleSelectionListItem, SelectionItem} from './hooks/useRuleSelectionList'; | ||
| import useRuleSelectionList from './hooks/useRuleSelectionList'; | ||
|
|
||
| type RuleSelectionPickerProps = { | ||
| items: SelectionItem[]; | ||
| initiallySelectedItem?: SelectionItem; | ||
| onSaveSelection: (value?: string) => void; | ||
| backToRoute: Route; | ||
| }; | ||
|
|
||
| function RuleSelectionPicker({items, initiallySelectedItem, onSaveSelection, backToRoute}: RuleSelectionPickerProps) { | ||
| const {translate} = useLocalize(); | ||
| const {sections, noResultsFound, searchTerm, setSearchTerm, initiallyFocusedItemKey} = useRuleSelectionList({ | ||
| items, | ||
| initiallySelectedItem, | ||
| }); | ||
|
|
||
| const onSelectRow = useCallback( | ||
| (item: {text?: string; keyForList?: string; isSelected?: boolean}) => { | ||
| if (!item.text || !item.keyForList) { | ||
| return; | ||
| } | ||
|
|
||
| const isRemovingSelection = !!item.isSelected; | ||
| const newValue = isRemovingSelection ? '' : item.keyForList; | ||
|
|
||
| onSaveSelection(newValue); | ||
| Navigation.goBack(backToRoute); | ||
| }, | ||
| [backToRoute, onSaveSelection], | ||
| ); | ||
|
|
||
| const textInputOptions = { | ||
| value: searchTerm, | ||
| label: translate('common.search'), | ||
| onChangeText: setSearchTerm, | ||
| headerMessage: noResultsFound ? translate('common.noResultsFound') : undefined, | ||
| }; | ||
|
|
||
| return ( | ||
| <SelectionListWithSections<RuleSelectionListItem> | ||
| sections={sections} | ||
| ListItem={SingleSelectListItem} | ||
| onSelectRow={onSelectRow} | ||
| shouldShowTextInput | ||
| textInputOptions={textInputOptions} | ||
| shouldShowLoadingPlaceholder={false} | ||
| initiallyFocusedItemKey={initiallyFocusedItemKey} | ||
| shouldStopPropagation | ||
| shouldScrollToTopOnSelect={false} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| export default RuleSelectionPicker; |
Uh oh!
There was an error while loading. Please reload this page.