Skip to content

Commit

Permalink
(PC-33638) feat(chronicle): add anchor on chronicle when see more but…
Browse files Browse the repository at this point in the history
…ton used with InteractionManager
  • Loading branch information
clesausse-pass committed Jan 30, 2025
1 parent 8929c99 commit 9ae30df
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -14,24 +15,35 @@ import {
SEPARATOR_DEFAULT_VALUE,
} from './ChronicleCardListBase'

export const ChronicleCardList: FunctionComponent<ChronicleCardListProps> = ({
data,
horizontal = true,
cardWidth,
contentContainerStyle,
headerComponent,
separatorSize = SEPARATOR_DEFAULT_VALUE,
onScroll,
style,
offerId,
shouldShowSeeMoreButton,
selectedChronicle,
}) => {
export const ChronicleCardList = forwardRef<
Partial<FlatList<ChronicleCardData>>,
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<FlatList>(null)

useImperativeHandle(ref, () => ({
scrollToOffset: (params) => listRef.current?.scrollToOffset(params),
scrollToIndex: (params) => listRef.current?.scrollToIndex(params),
}))

const {
onScroll: internalScrollHandler,
handleScrollNext,
Expand Down Expand Up @@ -81,11 +93,11 @@ export const ChronicleCardList: FunctionComponent<ChronicleCardListProps> = ({
snapToInterval={isDesktopViewport ? CHRONICLE_CARD_WIDTH : undefined}
offerId={offerId}
shouldShowSeeMoreButton={shouldShowSeeMoreButton}
selectedChronicle={selectedChronicle}
onLayout={onLayout}
/>
</Container>
)
}
})

const Container = styled.View({
justifyContent: 'center',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,4 @@ describe('ChronicleCardListBase', () => {

expect(screen.getByText('La Nature Sauvage')).toBeOnTheScreen()
})

it('should scroll to the selected chronicle when defined', () => {
render(
<ChronicleCardListBase
data={chroniclesSnap}
selectedChronicle={chroniclesSnap[4]}
ref={ref}
/>
)

expect(screen.getByText('La Magie des Étoiles')).toBeOnTheScreen()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -35,6 +26,7 @@ export type ChronicleCardListProps = Pick<
| 'snapToInterval'
| 'onScroll'
| 'onContentSizeChange'
| 'onLayout'
> & {
offset?: number
cardWidth?: number
Expand All @@ -43,7 +35,6 @@ export type ChronicleCardListProps = Pick<
style?: StyleProp<ViewStyle>
shouldShowSeeMoreButton?: boolean
offerId?: number
selectedChronicle?: ChronicleCardData
}

const renderItem = ({
Expand Down Expand Up @@ -89,29 +80,15 @@ export const ChronicleCardListBase = forwardRef<
separatorSize = SEPARATOR_DEFAULT_VALUE,
shouldShowSeeMoreButton,
offerId,
selectedChronicle,
onLayout,
},
ref
) {
const listRef = useRef<FlatList>(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(() => {
Expand All @@ -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({
Expand All @@ -147,9 +111,7 @@ export const ChronicleCardListBase = forwardRef<
ref={listRef}
data={data}
style={style}
ListHeaderComponent={
headerComponent ? <View onLayout={handleHeaderLayout}>{headerComponent}</View> : null
}
ListHeaderComponent={headerComponent}
renderItem={({ item }) => renderItem({ item, cardWidth, shouldShowSeeMoreButton, offerId })}
keyExtractor={keyExtractor}
ItemSeparatorComponent={Separator}
Expand All @@ -162,7 +124,7 @@ export const ChronicleCardListBase = forwardRef<
decelerationRate="fast"
snapToInterval={snapToInterval}
testID="chronicle-list"
onLayout={handleListLayout}
onLayout={onLayout}
/>
)
})
20 changes: 16 additions & 4 deletions src/features/chronicle/pages/Chronicles/Chronicles.tsx
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -35,7 +35,19 @@ export const Chronicles: FunctionComponent = () => {

const chroniclesListRef = useRef<FlatList<ChronicleCardData>>(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

Expand All @@ -58,7 +70,7 @@ export const Chronicles: FunctionComponent = () => {
marginTop: getSpacing(4),
marginHorizontal: contentPage.marginHorizontal,
}}
selectedChronicle={selectedChronicle}
onLayout={handleLayout}
/>
</React.Fragment>
)
Expand Down
20 changes: 16 additions & 4 deletions src/features/chronicle/pages/Chronicles/Chronicles.web.tsx
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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

Expand All @@ -76,7 +88,7 @@ export const Chronicles: FunctionComponent = () => {
onScroll={onScroll}
paddingTop={headerHeight}
headerComponent={<StyledTitle2>Tous les avis</StyledTitle2>}
selectedChronicle={selectedChronicle}
onLayout={handleLayout}
/>
)

Expand Down

0 comments on commit 9ae30df

Please sign in to comment.