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 (#7602)

* (PC-33638) feat(chronicle): add see more button on chronicle card

* (PC-33638) feat(chronicle): add anchor on chronicle when see more button used

* (PC-33638) feat(chronicle): add anchor on chronicle when see more button used with InteractionManager

* (PC-33638) refactor(chronicle): use children in ChronicleCard

* (PC-33638) refactor(chronicle): renderItem in ChronicleCardListBase as useCallback

* (PC-33638) refactor(chronicle): renderItem in ChronicleCardListBase with only one prop onSeeMoreButtonPress
  • Loading branch information
clesausse-pass authored Feb 3, 2025
1 parent 744a0c7 commit 78d4233
Show file tree
Hide file tree
Showing 13 changed files with 306 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ComponentMeta } from '@storybook/react'
import React from 'react'
import { View } from 'react-native'

import { ChronicleCard } from 'features/chronicle/components/ChronicleCard/ChronicleCard'
import { ButtonTertiaryBlack } from 'ui/components/buttons/ButtonTertiaryBlack'
import { VariantsTemplate, type Variants, type VariantsStory } from 'ui/storybook/VariantsTemplate'

const meta: ComponentMeta<typeof ChronicleCard> = {
Expand All @@ -24,6 +26,17 @@ const variantConfig: Variants<typeof ChronicleCard> = [
label: 'ChronicleCard default',
props: { ...baseProps },
},
{
label: 'ChronicleCard with see more button',
props: {
...baseProps,
children: (
<View>
<ButtonTertiaryBlack wording="Voir plus" />
</View>
),
},
},
]

const Template: VariantsStory<typeof ChronicleCard> = (args) => (
Expand Down
22 changes: 17 additions & 5 deletions src/features/chronicle/components/ChronicleCard/ChronicleCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { FunctionComponent } from 'react'
import React, { FunctionComponent, PropsWithChildren } from 'react'
import styled from 'styled-components/native'

import { ChronicleCardData } from 'features/chronicle/type'
Expand All @@ -10,9 +10,11 @@ import { TypoDS, getShadow, getSpacing } from 'ui/theme'

const CHRONICLE_THUMBNAIL_SIZE = getSpacing(14)

type Props = ChronicleCardData & {
cardWidth?: number
}
type Props = PropsWithChildren<
ChronicleCardData & {
cardWidth?: number
}
>

export const ChronicleCard: FunctionComponent<Props> = ({
id,
Expand All @@ -21,6 +23,7 @@ export const ChronicleCard: FunctionComponent<Props> = ({
description,
date,
cardWidth,
children,
}) => {
return (
<Container gap={3} testID={`chronicle-card-${id.toString()}`} width={cardWidth}>
Expand All @@ -32,7 +35,10 @@ export const ChronicleCard: FunctionComponent<Props> = ({
/>
<Separator.Horizontal />
<Description>{description}</Description>
<PublicationDate>{date}</PublicationDate>
<BottomCardContainer>
<PublicationDate>{date}</PublicationDate>
{children}
</BottomCardContainer>
</Container>
)
}
Expand All @@ -58,6 +64,12 @@ const Description = styled(TypoDS.BodyAccentS)(({ theme }) => ({
flexGrow: 1,
}))

const BottomCardContainer = styled.View({
flexDirection: 'row',
justifyContent: 'space-between',
})

const PublicationDate = styled(TypoDS.BodyAccentXs)(({ theme }) => ({
color: theme.colors.greyDark,
alignSelf: 'center',
}))
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import colorAlpha from 'color-alpha'
import React, { FunctionComponent, useRef } from 'react'
import React, { forwardRef, useImperativeHandle, useRef } from 'react'
import { NativeScrollEvent, NativeSyntheticEvent, useWindowDimensions, View } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import LinearGradient from 'react-native-linear-gradient'
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 @@ -16,21 +17,34 @@ import {
SEPARATOR_DEFAULT_VALUE,
} from './ChronicleCardListBase'

export const ChronicleCardList: FunctionComponent<ChronicleCardListProps> = ({
data,
horizontal = true,
cardWidth,
contentContainerStyle,
headerComponent,
separatorSize = SEPARATOR_DEFAULT_VALUE,
onScroll,
style,
}) => {
export const ChronicleCardList = forwardRef<
Partial<FlatList<ChronicleCardData>>,
ChronicleCardListProps
>(function ChronicleCardList(
{
data,
horizontal = true,
cardWidth,
contentContainerStyle,
onScroll,
headerComponent,
style,
separatorSize = SEPARATOR_DEFAULT_VALUE,
onSeeMoreButtonPress,
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 @@ -86,10 +100,12 @@ export const ChronicleCardList: FunctionComponent<ChronicleCardListProps> = ({
separatorSize={separatorSize}
contentContainerStyle={contentContainerStyle}
snapToInterval={isDesktopViewport ? CHRONICLE_CARD_WIDTH : undefined}
onSeeMoreButtonPress={onSeeMoreButtonPress}
onLayout={onLayout}
/>
</View>
)
}
})

const ArrowWrapper = styled.View.attrs({
pointerEvents: 'box-none',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React, { createRef } from 'react'
import { FlatList } from 'react-native-gesture-handler'
import { ReactTestInstance } from 'react-test-renderer'

import { CHRONICLE_CARD_WIDTH } from 'features/chronicle/constant'
import { chroniclesSnap } from 'features/chronicle/fixtures/chroniclesSnap'
import { render, screen } from 'tests/utils'
import { render, screen, userEvent } from 'tests/utils'

import { ChronicleCardListBase } from './ChronicleCardListBase'

const user = userEvent.setup()

jest.useFakeTimers()

describe('ChronicleCardListBase', () => {
const ref = createRef<FlatList>()

Expand Down Expand Up @@ -34,4 +39,51 @@ describe('ChronicleCardListBase', () => {

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

it('should display "Voir plus" button on all cards when onPressSeeMoreButton defined', () => {
render(
<ChronicleCardListBase
data={chroniclesSnap}
offset={CHRONICLE_CARD_WIDTH}
horizontal
ref={ref}
onSeeMoreButtonPress={jest.fn()}
/>
)

expect(screen.getAllByText('Voir plus')).toHaveLength(10)
})

it('should not display "Voir plus" button on all cards when onSeeMoreButtonPress not defined', () => {
render(
<ChronicleCardListBase
data={chroniclesSnap}
offset={CHRONICLE_CARD_WIDTH}
horizontal
ref={ref}
/>
)

expect(screen.queryByText('Voir plus')).not.toBeOnTheScreen()
})

it('should handle onSeeMoreButtonPress when pressing "Voir plus" button', async () => {
const mockOnSeeMoreButtonPress = jest.fn()
render(
<ChronicleCardListBase
data={chroniclesSnap}
offset={CHRONICLE_CARD_WIDTH}
horizontal
ref={ref}
onSeeMoreButtonPress={mockOnSeeMoreButtonPress}
/>
)

const seeMoreButtons = screen.getAllByText('Voir plus')

// Using as because links is never undefined and the typing is not correct
await user.press(seeMoreButtons[2] as ReactTestInstance)

expect(mockOnSeeMoreButtonPress).toHaveBeenCalledTimes(1)
})
})
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import React, {
ReactElement,
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
} from 'react'
import { FlatList, FlatListProps, StyleProp, ViewStyle } from 'react-native'
import { FlatList, FlatListProps, ListRenderItem, StyleProp, View, ViewStyle } from 'react-native'
import styled from 'styled-components/native'

import { ChronicleCardData } from 'features/chronicle/type'
import { ButtonTertiaryBlack } from 'ui/components/buttons/ButtonTertiaryBlack'
import { styledButton } from 'ui/components/buttons/styledButton'
import { PlainMore } from 'ui/svg/icons/PlainMore'
import { getSpacing } from 'ui/theme'

import { ChronicleCard } from '../ChronicleCard/ChronicleCard'
Expand All @@ -26,25 +30,14 @@ export type ChronicleCardListProps = Pick<
| 'snapToInterval'
| 'onScroll'
| 'onContentSizeChange'
| 'onLayout'
> & {
offset?: number
cardWidth?: number
separatorSize?: number
headerComponent?: ReactElement
style?: StyleProp<ViewStyle>
}

const renderItem = ({ item, cardWidth }: { item: ChronicleCardData; cardWidth?: number }) => {
return (
<ChronicleCard
id={item.id}
title={item.title}
subtitle={item.subtitle}
description={item.description}
date={item.date}
cardWidth={cardWidth}
/>
)
onSeeMoreButtonPress?: (chronicleId: number) => void
}

export const ChronicleCardListBase = forwardRef<
Expand All @@ -63,13 +56,16 @@ export const ChronicleCardListBase = forwardRef<
onContentSizeChange,
style,
separatorSize = SEPARATOR_DEFAULT_VALUE,
onSeeMoreButtonPress,
onLayout,
},
ref
) {
const listRef = useRef<FlatList>(null)

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

useEffect(() => {
Expand All @@ -87,13 +83,37 @@ export const ChronicleCardListBase = forwardRef<
[separatorSize, horizontal]
)

const renderItem = useCallback<ListRenderItem<ChronicleCardData>>(
({ item }) => {
return (
<ChronicleCard
id={item.id}
title={item.title}
subtitle={item.subtitle}
description={item.description}
date={item.date}
cardWidth={cardWidth}>
{onSeeMoreButtonPress ? (
<View>
<StyledButtonTertiaryBlack
wording="Voir plus"
onPress={() => onSeeMoreButtonPress(item.id)}
/>
</View>
) : null}
</ChronicleCard>
)
},
[cardWidth, onSeeMoreButtonPress]
)

return (
<FlatList
ref={listRef}
data={data}
style={style}
ListHeaderComponent={headerComponent}
renderItem={({ item }) => renderItem({ item, cardWidth })}
renderItem={renderItem}
keyExtractor={keyExtractor}
ItemSeparatorComponent={Separator}
contentContainerStyle={contentContainerStyle}
Expand All @@ -105,6 +125,17 @@ export const ChronicleCardListBase = forwardRef<
decelerationRate="fast"
snapToInterval={snapToInterval}
testID="chronicle-list"
onLayout={onLayout}
/>
)
})

const StyledPlainMore = styled(PlainMore).attrs(({ theme }) => ({
size: theme.icons.sizes.extraSmall,
}))``

const StyledButtonTertiaryBlack = styledButton(ButtonTertiaryBlack).attrs({
icon: StyledPlainMore,
iconPosition: 'right',
buttonHeight: 'extraSmall',
})``
Loading

0 comments on commit 78d4233

Please sign in to comment.