Skip to content

Commit

Permalink
(PC-32762) feat(XPCine): add disabled dates to calendar (#7276)
Browse files Browse the repository at this point in the history
* refactor(MovieCalendarProvider): give more control towards calendar context dates

* feat: add disabled dates UI

* refactor: separate useGetVenuesByDay in several hooks
  • Loading branch information
xlecunff-pass authored Nov 29, 2024
1 parent f5ecf6e commit c0d7c70
Show file tree
Hide file tree
Showing 24 changed files with 770 additions and 523 deletions.
4 changes: 3 additions & 1 deletion src/features/offer/api/useOffersStocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const useOffersStocks = ({ offerIds }: { offerIds: number[] }) => {
return useQuery<OffersStocksResponseV2>(
[QueryKeys.OFFER, offerIds],
() => getStocksByOfferIds(offerIds, logType),
{ enabled: !!netInfo.isConnected }
{
enabled: !!netInfo.isConnected,
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ComponentMeta } from '@storybook/react'
import React from 'react'
import styled from 'styled-components/native'

import { VariantsTemplate, type Variants, type VariantsStory } from 'ui/storybook/VariantsTemplate'

import { ExpandingFlatList } from './ExpandingFlatList'

const meta: ComponentMeta<typeof ExpandingFlatList> = {
title: 'features/offer/ExpandingFlatList',
component: ExpandingFlatList,
argTypes: {
isLoading: {
control: 'boolean',
defaultValue: false,
},
skeletonListLength: {
control: 'number',
defaultValue: 3,
},
},
}
export default meta

type Item = {
id: number
title: string
}

const mockData: Item[] = [
{ id: 1, title: 'Item 1' },
{ id: 2, title: 'Item 2' },
{ id: 3, title: 'Item 3' },
]

const variantConfig: Variants<typeof ExpandingFlatList<Item>> = [
{
label: 'Default state',
props: {
data: mockData,
renderItem: () => <StyledItem />,
renderSkeleton: () => <StyledItem />,
},
},
]

export const Template: VariantsStory<typeof ExpandingFlatList> = (args) => (
<VariantsTemplate
variants={variantConfig.map((variant) => ({
...variant,
props: {
...variant.props,
...args,
},
}))}
Component={ExpandingFlatList}
/>
)

const StyledItem = styled.View({
borderColor: 'black',
backgroundColor: 'lightgrey',
borderWidth: 1,
height: 50,
width: 200,
})

Template.storyName = 'ExpandingFlatList'
Template.args = {
isLoading: false,
skeletonListLength: 3,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { useCallback, useRef, useState } from 'react'
import { View } from 'react-native'
import Animated, { Easing, useAnimatedStyle, withTiming } from 'react-native-reanimated'

export const ExpandingFlatList = <T,>({
isLoading,
renderSkeleton,
data,
renderItem,
skeletonListLength = 3,
animationDuration = 300,
...props
}: Animated.FlatListPropsWithLayout<T> & {
animationDuration?: number
isLoading?: boolean
skeletonListLength?: number
renderSkeleton: Animated.FlatListPropsWithLayout<T>['renderItem']
}) => {
const { animatedStyle, onContentSizeChange } = useAnimatedHeight(animationDuration)

return (
<View>
<Animated.FlatList
{...props}
data={isLoading ? Array(skeletonListLength).fill(undefined) : data}
renderItem={isLoading ? renderSkeleton : renderItem}
style={animatedStyle}
onContentSizeChange={onContentSizeChange}
/>
</View>
)
}

const useAnimatedHeight = (duration: number) => {
const [contentHeight, setContentHeight] = useState(0)
const isFirstRender = useRef(true)

const animatedStyle = useAnimatedStyle(() => {
if (isFirstRender.current) {
isFirstRender.current = false
return { height: contentHeight }
}

return {
height: withTiming(contentHeight, {
duration,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}),
}
}, [contentHeight])

const onContentSizeChange = useCallback((_width: number, height: number) => {
setContentHeight(height)
}, [])

return { animatedStyle, onContentSizeChange }
}
3 changes: 3 additions & 0 deletions src/features/offer/components/MovieCalendar/MovieCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Props = {
selectedDate: Date | undefined
onTabChange: (date: Date) => void
flatListRef: React.MutableRefObject<FlatList | null>
disabledDates?: Date[]
flatListWidth?: number
onFlatListLayout?: (event: LayoutChangeEvent) => void
itemWidth?: number
Expand All @@ -33,6 +34,7 @@ export const MovieCalendar: React.FC<Props> = ({
onFlatListLayout,
itemWidth,
onItemLayout,
disabledDates,
}) => {
const { isDesktopViewport } = useTheme()
const {
Expand Down Expand Up @@ -94,6 +96,7 @@ export const MovieCalendar: React.FC<Props> = ({
date={date}
selectedDate={selectedDate}
onTabChange={onInternalTabChange}
disabled={disabledDates?.includes(date)}
/>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useMemo } from 'react'
import { View, ViewProps } from 'react-native'
import styled from 'styled-components/native'

Expand All @@ -13,36 +13,53 @@ type Props = {
onTabChange: (date: Date) => void
selectedDate?: Date
onLayout?: ViewProps['onLayout']
disabled?: boolean
}

export const MovieCalendarDay: React.FC<Props> = ({
date,
selectedDate,
onTabChange,
onLayout,
disabled,
}) => {
const { weekDay, dayDate, month, accessibilityLabel, isSelected } = useMovieCalendarDay(
date,
selectedDate
)
const { CalendarText } = StatusPattern[isSelected ? 'selected' : 'default']

const CalendarText = useMemo(() => {
if (disabled) {
return DisabledCalendarText
}
if (isSelected) {
return SelectedCalendarText
}
return DefaultCalendarText
}, [disabled, isSelected])

return (
<CalendarCell onLayout={onLayout} testID="movie-calendar-day" onPress={() => onTabChange(date)}>
<CalendarCell
disabled={disabled}
onLayout={onLayout}
testID="movie-calendar-day"
onPress={() => onTabChange(date)}>
<CalendarTextView accessibilityLabel={accessibilityLabel}>
<CalendarText numberOfLines={1}>{weekDay}</CalendarText>
<CalendarText numberOfLines={1}>{dayDate}</CalendarText>
<CalendarText numberOfLines={1}>{month}</CalendarText>
</CalendarTextView>
{isSelected ? <SelectedBottomBar /> : null}
{isSelected ? <SelectedBottomBar disabled={disabled} /> : null}
</CalendarCell>
)
}

const SelectedBottomBar = styled(MovieCalendarBottomBar)(({ theme }) => ({
backgroundColor: theme.colors.primary,
borderRadius: getSpacing(1),
}))
const SelectedBottomBar = styled(MovieCalendarBottomBar)<{ disabled?: boolean }>(
({ theme, disabled }) => ({
backgroundColor: disabled ? theme.colors.greyMedium : theme.colors.primary,
borderRadius: getSpacing(1),
})
)

const CalendarTextView = styled(View)({
marginHorizontal: getSpacing(4),
Expand All @@ -65,11 +82,6 @@ const SelectedCalendarText = styled(DefaultCalendarText)(({ theme }) => ({
color: theme.colors.primary,
}))

const StatusPattern = {
default: {
CalendarText: DefaultCalendarText,
},
selected: {
CalendarText: SelectedCalendarText,
},
}
const DisabledCalendarText = styled(DefaultCalendarText)(({ theme }) => ({
color: theme.colors.greyMedium,
}))
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import styled from 'styled-components/native'

import { MovieCalendar } from 'features/offer/components/MovieCalendar/MovieCalendar'
import { handleMovieCalendarScroll } from 'features/offer/components/MoviesScreeningCalendar/utils'
import { useNextDays } from 'features/offer/helpers/useNextDays/useNextDays'
import { useDaysSelector } from 'features/offer/helpers/useDaysSelector/useDaysSelector'
import { Anchor } from 'ui/components/anchor/Anchor'
import { useScrollToAnchor } from 'ui/components/anchor/AnchorContext'
import { useLayout } from 'ui/hooks/useLayout'
Expand All @@ -24,6 +24,9 @@ type MovieCalendarContextType = {
selectedDate: Date
goToDate: (date: Date) => void
displayCalendar: (shouldDisplayCalendar: boolean) => void
displayDates: (dates: Date[]) => void
disableDates: (dates: Date[]) => void
dates: Date[]
}

const MovieCalendarContext = createContext<MovieCalendarContextType | undefined>(undefined)
Expand Down Expand Up @@ -72,11 +75,12 @@ const AnimatedCalendarView: React.FC<PropsWithChildren<{ selectedDate: Date }>>
}

export const MovieCalendarProvider: React.FC<{
nbOfDays: number
children: React.ReactNode
containerStyle?: ViewStyle
}> = ({ nbOfDays, containerStyle, children }) => {
const { dates, selectedDate, setSelectedDate } = useNextDays(nbOfDays)
initialDates?: Date[]
}> = ({ containerStyle, children, initialDates = [] }) => {
const { dates, selectedDate, setSelectedDate, setDates } = useDaysSelector(initialDates)
const [disabledDates, setDisabledDates] = useState<Date[]>([])
const flatListRef = useRef<FlatList | null>(null)
const { width: flatListWidth, onLayout: onFlatListLayout } = useLayout()
const { width: itemWidth, onLayout: onItemLayout } = useLayout()
Expand Down Expand Up @@ -117,9 +121,23 @@ export const MovieCalendarProvider: React.FC<{
[scrollToAnchor, setSelectedDate]
)

const displayDates = useCallback(
(dates: Date[]) => {
setDates(dates)
},
[setDates]
)

const value = useMemo(
() => ({ selectedDate, goToDate, displayCalendar: setIsVisible }),
[selectedDate, goToDate]
() => ({
dates,
selectedDate,
goToDate,
displayCalendar: setIsVisible,
displayDates,
disableDates: setDisabledDates,
}),
[dates, selectedDate, goToDate, displayDates]
)

return (
Expand All @@ -130,6 +148,7 @@ export const MovieCalendarProvider: React.FC<{
<MovieCalendar
dates={dates}
selectedDate={selectedDate}
disabledDates={disabledDates}
onTabChange={setSelectedDate}
flatListRef={flatListRef}
flatListWidth={flatListWidth}
Expand All @@ -155,3 +174,25 @@ export const useMovieCalendar = (): MovieCalendarContextType => {
}
return context
}

export const useDisableCalendarDates = (dates: Date[]) => {
const context = useContext(MovieCalendarContext)
if (context === undefined) {
throw new Error('useDisableCalendarDates must be used within a MovieCalendarProvider')
}

useEffect(() => {
context.disableDates(dates)
}, [context, dates])
}

export const useDisplayCalendar = (shouldDisplayCalendar: boolean) => {
const context = useContext(MovieCalendarContext)
if (context === undefined) {
throw new Error('useDisplayCalendar must be used within a MovieCalendarProvider')
}

useEffect(() => {
context.displayCalendar(shouldDisplayCalendar)
}, [context, shouldDisplayCalendar])
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { formatDates } from 'libs/parsers/formatDates'
import { getDisplayPrice } from 'libs/parsers/getDisplayPrice'
import { useCategoryHomeLabelMapping, useCategoryIdMapping } from 'libs/subcategories'
import { useGetCurrencyToDisplay } from 'shared/currency/useGetCurrencyToDisplay'
import { getDates } from 'shared/date/getDates'
import { Offer } from 'shared/offer/types'
import { PassPlaylist } from 'ui/components/PassPlaylist'
import { CustomListRenderItem } from 'ui/components/Playlist'
Expand All @@ -39,6 +40,7 @@ export const MoviesScreeningCalendar: FunctionComponent<Props> = ({ venueOffers
(offer) => offer.offer.subcategoryId !== SubcategoryIdEnum.SEANCE_CINE
)

const next15Dates = getDates(new Date(), 15)
const mapping = useCategoryIdMapping()
const labelMapping = useCategoryHomeLabelMapping()

Expand Down Expand Up @@ -69,7 +71,7 @@ export const MoviesScreeningCalendar: FunctionComponent<Props> = ({ venueOffers
return (
<React.Fragment>
<Spacer.Column numberOfSpaces={4} />
<MovieCalendarProvider nbOfDays={15}>
<MovieCalendarProvider initialDates={next15Dates}>
<VenueCalendar venueOffers={venueOffers} offerIds={offerIds} />
</MovieCalendarProvider>
{nonScreeningOffers.length > 0 ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ describe('useMoviesScreeningsList', () => {
selectedDate: mockSelectedDate,
goToDate: jest.fn(),
displayCalendar: jest.fn(),
dates: [],
disableDates: jest.fn(),
displayDates: jest.fn(),
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ describe('CineBlock', () => {
selectedDate: mockSelectedDate,
goToDate: mockGoToDate,
displayCalendar: mockDisplayCalendar,
dates: [],
disableDates: jest.fn(),
displayDates: jest.fn(),
})

mockUseSubcategoriesMapping.mockReturnValue({
Expand Down
Loading

0 comments on commit c0d7c70

Please sign in to comment.