From 9ae30dfa8f52a8455358e44c66948292ec6cd0f8 Mon Sep 17 00:00:00 2001 From: clesausse-pass <187269096+clesausse-pass@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:03:38 +0100 Subject: [PATCH] (PC-33638) feat(chronicle): add anchor on chronicle when see more button used with InteractionManager --- .../ChronicleCardList.web.tsx | 44 ++++++++++------ .../ChronicleCardListBase.native.test.tsx | 12 ----- .../ChronicleCardListBase.tsx | 52 +++---------------- .../chronicle/pages/Chronicles/Chronicles.tsx | 20 +++++-- .../pages/Chronicles/Chronicles.web.tsx | 20 +++++-- 5 files changed, 67 insertions(+), 81 deletions(-) diff --git a/src/features/chronicle/components/ChronicleCardList/ChronicleCardList.web.tsx b/src/features/chronicle/components/ChronicleCardList/ChronicleCardList.web.tsx index b8015f32562..13a9c2756e0 100644 --- a/src/features/chronicle/components/ChronicleCardList/ChronicleCardList.web.tsx +++ b/src/features/chronicle/components/ChronicleCardList/ChronicleCardList.web.tsx @@ -1,10 +1,11 @@ -import React, { FunctionComponent, useRef } from 'react' +import React, { forwardRef, useImperativeHandle, useRef } from 'react' import { NativeScrollEvent, NativeSyntheticEvent, useWindowDimensions } from 'react-native' import { FlatList } from 'react-native-gesture-handler' import { useTheme } from 'styled-components' import styled from 'styled-components/native' import { CHRONICLE_CARD_WIDTH } from 'features/chronicle/constant' +import { ChronicleCardData } from 'features/chronicle/type' import { useHorizontalFlatListScroll } from 'ui/hooks/useHorizontalFlatListScroll' import { PlaylistArrowButton } from 'ui/Playlist/PlaylistArrowButton' @@ -14,24 +15,35 @@ import { SEPARATOR_DEFAULT_VALUE, } from './ChronicleCardListBase' -export const ChronicleCardList: FunctionComponent = ({ - data, - horizontal = true, - cardWidth, - contentContainerStyle, - headerComponent, - separatorSize = SEPARATOR_DEFAULT_VALUE, - onScroll, - style, - offerId, - shouldShowSeeMoreButton, - selectedChronicle, -}) => { +export const ChronicleCardList = forwardRef< + Partial>, + ChronicleCardListProps +>(function ChronicleCardList( + { + data, + horizontal = true, + cardWidth, + contentContainerStyle, + onScroll, + headerComponent, + style, + separatorSize = SEPARATOR_DEFAULT_VALUE, + shouldShowSeeMoreButton, + offerId, + onLayout, + }, + ref +) { const { isDesktopViewport } = useTheme() const { width: windowWidth } = useWindowDimensions() const listRef = useRef(null) + useImperativeHandle(ref, () => ({ + scrollToOffset: (params) => listRef.current?.scrollToOffset(params), + scrollToIndex: (params) => listRef.current?.scrollToIndex(params), + })) + const { onScroll: internalScrollHandler, handleScrollNext, @@ -81,11 +93,11 @@ export const ChronicleCardList: FunctionComponent = ({ snapToInterval={isDesktopViewport ? CHRONICLE_CARD_WIDTH : undefined} offerId={offerId} shouldShowSeeMoreButton={shouldShowSeeMoreButton} - selectedChronicle={selectedChronicle} + onLayout={onLayout} /> ) -} +}) const Container = styled.View({ justifyContent: 'center', diff --git a/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.native.test.tsx b/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.native.test.tsx index 7f05678b2ec..361af955835 100644 --- a/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.native.test.tsx +++ b/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.native.test.tsx @@ -34,16 +34,4 @@ describe('ChronicleCardListBase', () => { expect(screen.getByText('La Nature Sauvage')).toBeOnTheScreen() }) - - it('should scroll to the selected chronicle when defined', () => { - render( - - ) - - expect(screen.getByText('La Magie des Étoiles')).toBeOnTheScreen() - }) }) diff --git a/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.tsx b/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.tsx index 442b885790c..9ef9c7acb98 100644 --- a/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.tsx +++ b/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.tsx @@ -5,18 +5,9 @@ import React, { useImperativeHandle, useMemo, useRef, - useState, } from 'react' -import { - FlatList, - FlatListProps, - LayoutChangeEvent, - StyleProp, - View, - ViewStyle, -} from 'react-native' -import { useSafeAreaInsets } from 'react-native-safe-area-context' -import styled, { useTheme } from 'styled-components/native' +import { FlatList, FlatListProps, StyleProp, ViewStyle } from 'react-native' +import styled from 'styled-components/native' import { ChronicleCardData } from 'features/chronicle/type' import { getSpacing } from 'ui/theme' @@ -35,6 +26,7 @@ export type ChronicleCardListProps = Pick< | 'snapToInterval' | 'onScroll' | 'onContentSizeChange' + | 'onLayout' > & { offset?: number cardWidth?: number @@ -43,7 +35,6 @@ export type ChronicleCardListProps = Pick< style?: StyleProp shouldShowSeeMoreButton?: boolean offerId?: number - selectedChronicle?: ChronicleCardData } const renderItem = ({ @@ -89,29 +80,15 @@ export const ChronicleCardListBase = forwardRef< separatorSize = SEPARATOR_DEFAULT_VALUE, shouldShowSeeMoreButton, offerId, - selectedChronicle, + onLayout, }, ref ) { const listRef = useRef(null) - const [isFlatListReady, setIsFlatListReady] = useState(false) - const [headerHeight, setHeaderHeight] = useState(0) - const [listHeight, setListHeight] = useState(1) - const { top } = useSafeAreaInsets() - const { appBarHeight } = useTheme() - - const handleHeaderLayout = (event: LayoutChangeEvent) => { - setHeaderHeight(event.nativeEvent.layout.height) - } - - const handleListLayout = (event: LayoutChangeEvent) => { - setListHeight(event.nativeEvent.layout.height) - setIsFlatListReady(true) - } useImperativeHandle(ref, () => ({ scrollToOffset: (params) => listRef.current?.scrollToOffset(params), - scrollToItem: (params) => listRef.current?.scrollToItem(params), + scrollToIndex: (params) => listRef.current?.scrollToIndex(params), })) useEffect(() => { @@ -120,19 +97,6 @@ export const ChronicleCardListBase = forwardRef< } }, [offset]) - useEffect(() => { - if (listRef.current && isFlatListReady && !!selectedChronicle) { - const flatListTop = top + appBarHeight + headerHeight - const viewPosition = flatListTop / listHeight - - listRef.current.scrollToItem({ - item: selectedChronicle, - animated: true, - viewPosition, - }) - } - }, [appBarHeight, headerHeight, selectedChronicle, isFlatListReady, listHeight, top]) - const Separator = useMemo( () => styled.View({ @@ -147,9 +111,7 @@ export const ChronicleCardListBase = forwardRef< ref={listRef} data={data} style={style} - ListHeaderComponent={ - headerComponent ? {headerComponent} : null - } + ListHeaderComponent={headerComponent} renderItem={({ item }) => renderItem({ item, cardWidth, shouldShowSeeMoreButton, offerId })} keyExtractor={keyExtractor} ItemSeparatorComponent={Separator} @@ -162,7 +124,7 @@ export const ChronicleCardListBase = forwardRef< decelerationRate="fast" snapToInterval={snapToInterval} testID="chronicle-list" - onLayout={handleListLayout} + onLayout={onLayout} /> ) }) diff --git a/src/features/chronicle/pages/Chronicles/Chronicles.tsx b/src/features/chronicle/pages/Chronicles/Chronicles.tsx index e0420611a4f..bc2aa158c11 100644 --- a/src/features/chronicle/pages/Chronicles/Chronicles.tsx +++ b/src/features/chronicle/pages/Chronicles/Chronicles.tsx @@ -1,6 +1,6 @@ import { useRoute } from '@react-navigation/native' -import React, { FunctionComponent, useRef } from 'react' -import { FlatList } from 'react-native' +import React, { FunctionComponent, useCallback, useRef } from 'react' +import { FlatList, InteractionManager } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import styled, { useTheme } from 'styled-components/native' @@ -35,7 +35,19 @@ export const Chronicles: FunctionComponent = () => { const chroniclesListRef = useRef>(null) - const selectedChronicle = chronicleCardsData?.find((item) => item.id === chronicleId) + const selectedChronicle = chronicleCardsData?.findIndex((item) => item.id === chronicleId) ?? -1 + + const handleLayout = useCallback(() => { + if (selectedChronicle !== -1) { + InteractionManager.runAfterInteractions(() => { + chroniclesListRef.current?.scrollToIndex({ + index: selectedChronicle, + animated: true, + viewOffset: headerHeight, + }) + }) + } + }, [selectedChronicle, headerHeight]) if (!offer || !chronicleCardsData) return null @@ -58,7 +70,7 @@ export const Chronicles: FunctionComponent = () => { marginTop: getSpacing(4), marginHorizontal: contentPage.marginHorizontal, }} - selectedChronicle={selectedChronicle} + onLayout={handleLayout} /> ) diff --git a/src/features/chronicle/pages/Chronicles/Chronicles.web.tsx b/src/features/chronicle/pages/Chronicles/Chronicles.web.tsx index 6c11ab7127a..fe3aee1937f 100644 --- a/src/features/chronicle/pages/Chronicles/Chronicles.web.tsx +++ b/src/features/chronicle/pages/Chronicles/Chronicles.web.tsx @@ -1,6 +1,6 @@ import { useRoute } from '@react-navigation/native' -import React, { FunctionComponent, useRef } from 'react' -import { FlatList } from 'react-native' +import React, { FunctionComponent, useCallback, useRef } from 'react' +import { FlatList, InteractionManager } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import styled, { useTheme } from 'styled-components/native' @@ -63,7 +63,19 @@ export const Chronicles: FunctionComponent = () => { { fractionDigits: 2 } ) - const selectedChronicle = chronicleCardsData?.find((item) => item.id === chronicleId) + const selectedChronicle = chronicleCardsData?.findIndex((item) => item.id === chronicleId) ?? -1 + + const handleLayout = useCallback(() => { + if (selectedChronicle !== -1) { + InteractionManager.runAfterInteractions(() => { + chroniclesListRef.current?.scrollToIndex({ + index: selectedChronicle, + animated: true, + viewOffset: headerHeight, + }) + }) + } + }, [selectedChronicle, headerHeight]) if (!offer || !chronicleCardsData) return null @@ -76,7 +88,7 @@ export const Chronicles: FunctionComponent = () => { onScroll={onScroll} paddingTop={headerHeight} headerComponent={Tous les avis} - selectedChronicle={selectedChronicle} + onLayout={handleLayout} /> )