Skip to content
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

Fix: Chats - Selected chat is hidden when navigating via keyboard #51754

Merged
merged 4 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr
}
contentContainerStyle={[contentContainerStyle, styles.pb3]}
scrollEventThrottle={1}
shouldKeepFocusedItemAtTopOfViewableArea={type === CONST.SEARCH.DATA_TYPES.CHAT}
/>
);
}
Expand Down
39 changes: 33 additions & 6 deletions src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ function BaseSelectionList<TItem extends ListItem>(
scrollEventThrottle,
contentContainerStyle,
shouldHighlightSelectedItem = false,
shouldKeepFocusedItemAtTopOfViewableArea = false,
}: BaseSelectionListProps<TItem>,
ref: ForwardedRef<SelectionListHandle>,
) {
Expand All @@ -126,6 +127,20 @@ function BaseSelectionList<TItem extends ListItem>(
const [currentPage, setCurrentPage] = useState(1);
const isTextInputFocusedRef = useRef<boolean>(false);
const {singleExecution} = useSingleExecution();
const [itemHeights, setItemHeights] = useState<Record<string, number>>({});

const onItemLayout = (event: LayoutChangeEvent, itemKey: string | null | undefined) => {
if (!itemKey) {
return;
}

const {height} = event.nativeEvent.layout;

setItemHeights((prevHeights) => ({
...prevHeights,
[itemKey]: height,
}));
};

const incrementPage = () => setCurrentPage((prev) => prev + 1);

Expand All @@ -151,7 +166,7 @@ function BaseSelectionList<TItem extends ListItem>(
const selectedOptions: TItem[] = [];

sections.forEach((section, sectionIndex) => {
const sectionHeaderHeight = variables.optionsListSectionHeaderHeight;
const sectionHeaderHeight = !!section.title || !!section.CustomSectionHeader ? variables.optionsListSectionHeaderHeight : 0;
itemLayouts.push({length: sectionHeaderHeight, offset});
offset += sectionHeaderHeight;

Expand All @@ -175,7 +190,7 @@ function BaseSelectionList<TItem extends ListItem>(
disabledIndex += 1;

// Account for the height of the item in getItemLayout
const fullItemHeight = getItemHeight(item);
const fullItemHeight = item?.keyForList && itemHeights[item.keyForList] ? itemHeights[item.keyForList] : getItemHeight(item);
itemLayouts.push({length: fullItemHeight, offset});
offset += fullItemHeight;

Expand Down Expand Up @@ -207,7 +222,7 @@ function BaseSelectionList<TItem extends ListItem>(
itemLayouts,
allSelected: selectedOptions.length > 0 && selectedOptions.length === allOptions.length - disabledOptionsIndexes.length,
};
}, [canSelectMultiple, sections, customListHeader, customListHeaderHeight, getItemHeight]);
}, [canSelectMultiple, sections, customListHeader, customListHeaderHeight, itemHeights, getItemHeight]);

const [slicedSections, ShowMoreButtonInstance] = useMemo(() => {
let remainingOptionsLimit = CONST.MAX_SELECTION_LIST_PAGE_LENGTH * currentPage;
Expand Down Expand Up @@ -257,8 +272,20 @@ function BaseSelectionList<TItem extends ListItem>(

const itemIndex = item.index ?? -1;
const sectionIndex = item.sectionIndex ?? -1;
let viewOffsetToKeepFocusedItemAtTopOfViewableArea = 0;

// Since there are always two items above the focused item in viewable area, and items can grow beyond the screen size
// in searchType chat, the focused item may move out of view. To prevent this, we will ensure that the focused item remains at
// the top of the viewable area at all times by adjusting the viewOffset.
if (shouldKeepFocusedItemAtTopOfViewableArea) {
const firstPreviousItem = index > 0 ? flattenedSections.allOptions.at(index - 1) : undefined;
const firstPreviousItemHeight = firstPreviousItem && firstPreviousItem.keyForList ? itemHeights[firstPreviousItem.keyForList] : 0;
const secondPreviousItem = index > 1 ? flattenedSections.allOptions.at(index - 2) : undefined;
const secondPreviousItemHeight = secondPreviousItem && secondPreviousItem?.keyForList ? itemHeights[secondPreviousItem.keyForList] : 0;
viewOffsetToKeepFocusedItemAtTopOfViewableArea = firstPreviousItemHeight + secondPreviousItemHeight;
}

listRef.current.scrollToLocation({sectionIndex, itemIndex, animated, viewOffset: variables.contentHeaderHeight});
listRef.current.scrollToLocation({sectionIndex, itemIndex, animated, viewOffset: variables.contentHeaderHeight - viewOffsetToKeepFocusedItemAtTopOfViewableArea});
},

// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
Expand Down Expand Up @@ -450,7 +477,7 @@ function BaseSelectionList<TItem extends ListItem>(
};

return (
<>
<View onLayout={(event: LayoutChangeEvent) => onItemLayout(event, item?.keyForList)}>
<ListItem
item={item}
isFocused={isItemFocused}
Expand Down Expand Up @@ -486,7 +513,7 @@ function BaseSelectionList<TItem extends ListItem>(
wrapperStyle={listItemWrapperStyle}
/>
{item.footerContent && item.footerContent}
</>
</View>
);
};

Expand Down
3 changes: 3 additions & 0 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,9 @@ type BaseSelectionListProps<TItem extends ListItem> = Partial<ChildrenProps> & {

/** Whether we highlight all the selected items */
shouldHighlightSelectedItem?: boolean;

/** Determines if the focused item should remain at the top of the viewable area when navigating with arrow keys */
shouldKeepFocusedItemAtTopOfViewableArea?: boolean;
} & TRightHandSideComponent<TItem>;

type SelectionListHandle = {
Expand Down
Loading