Repository navigation
[CFI] Open the Category list in a container anchored to its row #102665
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
Merged
puneetlath
merged 14 commits into
Expensify:main
from
DylanDylann:dylann/101573-category-anchored-dropdown
Oct 5, 2026
+1,096
−112
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
1b35860
feat: open the Category list in a container anchored to its row
DylanDylann 47301cd
feat: gate the anchored field dropdowns behind a client flag
DylanDylann 6f6a8bf
fix: drop the unused ExpenseFieldDropdownProps export
DylanDylann e67ccb9
fix: size the category container to its list instead of to a fixed he…
DylanDylann 332125a
fix: hardcode canUseAnchoredFieldDropdowns to false
DylanDylann f64a4e5
fix: open the container above the row whenever that side has more room
DylanDylann 79a05c8
fix: stop the container being dragged on top of the row it belongs to
DylanDylann 836bb79
Flag feature
DylanDylann 0d41855
feat: put the anchored field dropdowns behind a beta
DylanDylann 8dd9e3f
address comments
DylanDylann 5d6e86e
address comment
DylanDylann 7f7695d
Merge branch 'main' into dylann/101573-category-anchored-dropdown
DylanDylann 8273c92
Simplified prop descriptions, removed unnecessary comments
DylanDylann 7173ac9
Merge branch 'main' into dylann/101573-category-anchored-dropdown
DylanDylann File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
src/components/MoneyRequestConfirmationList/sections/CategoryFieldDropdown.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import CategoryPickerModal from '@components/CategoryPicker/CategoryPickerModal'; | ||
| import {useConfirmationFields} from '@components/MoneyRequestConfirmationFields/context'; | ||
| import type {ListItem} from '@components/SelectionList/types'; | ||
|
|
||
| import useOnyx from '@hooks/useOnyx'; | ||
| import useUpdateTransactionCategory from '@hooks/useUpdateTransactionCategory'; | ||
|
|
||
| import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; | ||
|
|
||
| import CONST from '@src/CONST'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import type * as OnyxTypes from '@src/types/onyx'; | ||
|
|
||
| import type {OnyxEntry} from 'react-native-onyx'; | ||
|
|
||
| import React from 'react'; | ||
|
|
||
| import type {ExpenseFieldDropdownRenderProps} from './ExpenseFieldDropdown'; | ||
|
|
||
| type CategoryFieldDropdownProps = ExpenseFieldDropdownRenderProps & { | ||
| /** ID of the expense the picked category is written to */ | ||
| transactionID: string; | ||
|
|
||
| /** Policy the categories belong to */ | ||
| policy: OnyxEntry<OnyxTypes.Policy>; | ||
|
|
||
| /** Category the expense already holds, so the list can mark it */ | ||
| selectedCategory: string; | ||
| }; | ||
|
|
||
| /** | ||
| * The category list, in the container anchored to the expense form's category row. Mounted only once the row is | ||
| * opened, keeping its Onyx subscriptions off the form's first render, and saves through the same hook the | ||
| * full-page selector uses so both leave the expense in the same state. | ||
| */ | ||
| function CategoryFieldDropdown({transactionID, policy, selectedCategory, onClose, ...popoverProps}: CategoryFieldDropdownProps) { | ||
| const {reportID, isEditingSplitBill, action} = useConfirmationFields(); | ||
|
|
||
| const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); | ||
| const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); | ||
| const [draftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`); | ||
| const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${getNonEmptyStringOnyxID(policy?.id)}`); | ||
|
|
||
| const {updateCategory} = useUpdateTransactionCategory({ | ||
| transactionID, | ||
| transaction: draftTransaction ?? transaction, | ||
| report, | ||
| policy, | ||
| policyCategories, | ||
| isEditing: action === CONST.IOU.ACTION.EDIT, | ||
| isEditingSplit: isEditingSplitBill, | ||
| }); | ||
|
|
||
| const handleSelected = (item: ListItem) => { | ||
| // `CategoryPickerModal` hands back an empty item when the selected category is tapped again, clearing it. | ||
| updateCategory(item.searchText ?? ''); | ||
| }; | ||
|
|
||
| return ( | ||
| <CategoryPickerModal | ||
| {...popoverProps} | ||
| onClose={onClose} | ||
| shouldFitContentHeight | ||
| policyID={policy?.id} | ||
| selectedCategory={selectedCategory} | ||
| onSelected={handleSelected} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| export default CategoryFieldDropdown; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡
src/components/CategoryPicker/index.tsx:126renderedRowCountis built from the search-filtered sections (getCategoryListSectionsgetsdebouncedSearchValue), so the pop-over now resizes while the user types. That happens with 12 or more categories, which is when search shows, as soon as results drop below 4 rows.When the container opens above the row, its bottom edge is pinned to the row. As the results shrink, the search input at the top slides down under the user mid-typing, then jumps back up when they clear it.
The count also stays in
CategoryPickerModalstate after close. If you search, pick, and reopen, the pop-over opens at the filtered size for a frame before the remounted picker reports the full count.Screen.Recording.2026-10-01.at.13.53.41.mov
Screen.Recording.2026-10-01.at.13.58.24.mov
Could we report the count only for the full list, so searching never resizes the container ?
cc @Expensify/design for a decision on whether to keep current or go with suggested fix 🙌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have this shrinking/growing behavior already with the exposed filters and inline edit on Spend, right? So I think it's fine as-is, but let's get confirmation from DesignTeam first.