diff --git a/src/features/chronicle/components/ChronicleCardList/ChronicleCardList.web.tsx b/src/features/chronicle/components/ChronicleCardList/ChronicleCardList.web.tsx index 93c0395c976..efb3b435695 100644 --- a/src/features/chronicle/components/ChronicleCardList/ChronicleCardList.web.tsx +++ b/src/features/chronicle/components/ChronicleCardList/ChronicleCardList.web.tsx @@ -25,6 +25,9 @@ export const ChronicleCardList: FunctionComponent = ({ separatorSize = SEPARATOR_DEFAULT_VALUE, onScroll, style, + offerId, + shouldShowSeeMoreButton, + selectedChronicle, }) => { const { isDesktopViewport } = useTheme() const { width: windowWidth } = useWindowDimensions() @@ -86,6 +89,9 @@ export const ChronicleCardList: FunctionComponent = ({ separatorSize={separatorSize} contentContainerStyle={contentContainerStyle} snapToInterval={isDesktopViewport ? CHRONICLE_CARD_WIDTH : undefined} + offerId={offerId} + shouldShowSeeMoreButton={shouldShowSeeMoreButton} + selectedChronicle={selectedChronicle} /> ) diff --git a/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.native.test.tsx b/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.native.test.tsx index 361af955835..7f05678b2ec 100644 --- a/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.native.test.tsx +++ b/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.native.test.tsx @@ -34,4 +34,16 @@ 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 4a8576629ec..442b885790c 100644 --- a/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.tsx +++ b/src/features/chronicle/components/ChronicleCardList/ChronicleCardListBase.tsx @@ -5,9 +5,18 @@ import React, { useImperativeHandle, useMemo, useRef, + useState, } from 'react' -import { FlatList, FlatListProps, StyleProp, ViewStyle } from 'react-native' -import styled from 'styled-components/native' +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 { ChronicleCardData } from 'features/chronicle/type' import { getSpacing } from 'ui/theme' @@ -32,9 +41,22 @@ export type ChronicleCardListProps = Pick< separatorSize?: number headerComponent?: ReactElement style?: StyleProp + shouldShowSeeMoreButton?: boolean + offerId?: number + selectedChronicle?: ChronicleCardData } -const renderItem = ({ item, cardWidth }: { item: ChronicleCardData; cardWidth?: number }) => { +const renderItem = ({ + item, + cardWidth, + shouldShowSeeMoreButton, + offerId, +}: { + item: ChronicleCardData + cardWidth?: number + shouldShowSeeMoreButton?: boolean + offerId?: number +}) => { return ( ) } @@ -63,13 +87,31 @@ export const ChronicleCardListBase = forwardRef< onContentSizeChange, style, separatorSize = SEPARATOR_DEFAULT_VALUE, + shouldShowSeeMoreButton, + offerId, + selectedChronicle, }, 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), })) useEffect(() => { @@ -78,6 +120,19 @@ 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({ @@ -92,8 +147,10 @@ export const ChronicleCardListBase = forwardRef< ref={listRef} data={data} style={style} - ListHeaderComponent={headerComponent} - renderItem={({ item }) => renderItem({ item, cardWidth })} + ListHeaderComponent={ + headerComponent ? {headerComponent} : null + } + renderItem={({ item }) => renderItem({ item, cardWidth, shouldShowSeeMoreButton, offerId })} keyExtractor={keyExtractor} ItemSeparatorComponent={Separator} contentContainerStyle={contentContainerStyle} @@ -105,6 +162,7 @@ export const ChronicleCardListBase = forwardRef< decelerationRate="fast" snapToInterval={snapToInterval} testID="chronicle-list" + onLayout={handleListLayout} /> ) }) diff --git a/src/features/chronicle/pages/Chronicles/Chronicles.tsx b/src/features/chronicle/pages/Chronicles/Chronicles.tsx index 925f0650b1f..e0420611a4f 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 { ScrollView } from 'react-native' +import { FlatList } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import styled, { useTheme } from 'styled-components/native' @@ -19,6 +19,7 @@ import { TypoDS, getSpacing } from 'ui/theme' export const Chronicles: FunctionComponent = () => { const route = useRoute>() const offerId = route.params?.offerId + const chronicleId = route.params?.chronicleId const { goBack } = useGoBack('Offer', { id: offerId }) const { data: offer } = useOffer({ offerId }) const { data: chronicleCardsData } = useChronicles({ @@ -29,9 +30,12 @@ export const Chronicles: FunctionComponent = () => { const { headerTransition, onScroll } = useOpacityTransition() const { appBarHeight } = useTheme() const { top } = useSafeAreaInsets() + const { contentPage } = useTheme() const headerHeight = appBarHeight + top - const scrollViewRef = useRef(null) + const chroniclesListRef = useRef>(null) + + const selectedChronicle = chronicleCardsData?.find((item) => item.id === chronicleId) if (!offer || !chronicleCardsData) return null @@ -41,21 +45,21 @@ export const Chronicles: FunctionComponent = () => { - Tous les avis} + ref={chroniclesListRef} onScroll={onScroll} - contentContainerStyle={{ paddingTop: headerHeight, paddingBottom: getSpacing(10) }}> - - Tous les avis} - /> - - + contentContainerStyle={{ + paddingTop: headerHeight, + paddingBottom: getSpacing(10), + marginTop: getSpacing(4), + marginHorizontal: contentPage.marginHorizontal, + }} + selectedChronicle={selectedChronicle} + /> ) } @@ -63,8 +67,3 @@ export const Chronicles: FunctionComponent = () => { const StyledTitle2 = styled(TypoDS.Title2)({ marginBottom: getSpacing(6), }) - -const ChroniclesContainer = styled.View(({ theme }) => ({ - marginTop: getSpacing(4), - marginHorizontal: theme.contentPage.marginHorizontal, -})) diff --git a/src/features/chronicle/pages/Chronicles/Chronicles.web.tsx b/src/features/chronicle/pages/Chronicles/Chronicles.web.tsx index ff93989afb6..6c11ab7127a 100644 --- a/src/features/chronicle/pages/Chronicles/Chronicles.web.tsx +++ b/src/features/chronicle/pages/Chronicles/Chronicles.web.tsx @@ -1,5 +1,6 @@ import { useRoute } from '@react-navigation/native' -import React, { FunctionComponent } from 'react' +import React, { FunctionComponent, useRef } from 'react' +import { FlatList } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import styled, { useTheme } from 'styled-components/native' @@ -28,6 +29,7 @@ import { TypoDS, getSpacing } from 'ui/theme' export const Chronicles: FunctionComponent = () => { const route = useRoute>() const offerId = route.params?.offerId + const chronicleId = route.params?.chronicleId const { goBack } = useGoBack('Offer', { id: offerId }) const { data: offer } = useOffer({ offerId }) const subcategoriesMapping = useSubcategoriesMapping() @@ -51,6 +53,8 @@ export const Chronicles: FunctionComponent = () => { const currency = useGetCurrencyToDisplay() const euroToPacificFrancRate = useGetPacificFrancToEuroRate() + const chroniclesListRef = useRef>(null) + const displayedPrice = getDisplayedPrice( prices, currency, @@ -59,16 +63,20 @@ export const Chronicles: FunctionComponent = () => { { fractionDigits: 2 } ) + const selectedChronicle = chronicleCardsData?.find((item) => item.id === chronicleId) + if (!offer || !chronicleCardsData) return null const title = `Tous les avis sur "${offer.name}"` const listComponent = ( Tous les avis} + selectedChronicle={selectedChronicle} /> )