Skip to content

Commit 2c7d261

Browse files
add Infinit scroller to notifications list and design system
1 parent d788f69 commit 2c7d261

File tree

10 files changed

+2209
-2018
lines changed

10 files changed

+2209
-2018
lines changed

packages/components/modules/notifications/common/constants.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ export const NOTIFICATION_VERB = {
55
}
66

77
export const USER_NOTIFICATIONS_KEY = 'user_notifications'
8+
9+
export const NUMBER_OF_NOTIFICATIONS_TO_LOAD_NEXT = 5

packages/components/modules/notifications/native/NotificationsList/index.tsx

+21-6
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ import React, { FC, Suspense, useMemo } from 'react'
22

33
import { LoadingScreen } from '@baseapp-frontend/design-system/components/native/displays'
44
import { Text } from '@baseapp-frontend/design-system/components/native/typographies'
5-
import { View } from '@baseapp-frontend/design-system/components/native/views'
5+
import { InfiniteScrollerView, View } from '@baseapp-frontend/design-system/components/native/views'
66
import { useTheme } from '@baseapp-frontend/design-system/providers/native'
77

8-
import { FlatList } from 'react-native'
98
import { useLazyLoadQuery, usePaginationFragment } from 'react-relay'
109

11-
import { NotificationsListFragment$key } from '../../../../__generated__/NotificationsListFragment.graphql'
10+
import {
11+
NotificationsListFragment$data,
12+
NotificationsListFragment$key,
13+
} from '../../../../__generated__/NotificationsListFragment.graphql'
1214
import { NotificationsListQuery as NotificationsListQueryType } from '../../../../__generated__/NotificationsListQuery.graphql'
1315
import {
16+
NUMBER_OF_NOTIFICATIONS_TO_LOAD_NEXT,
1417
NotificationsListFragment,
1518
NotificationsListQuery,
1619
useNotificationsSubscription,
@@ -35,7 +38,7 @@ const NotificationsList: FC<NotificationsListProps> = ({
3538
})
3639

3740
// TODO: handle infinite scroll
38-
const { data, refetch } = usePaginationFragment<
41+
const { data, loadNext, isLoadingNext, hasNext, refetch } = usePaginationFragment<
3942
NotificationsListQueryType,
4043
NotificationsListFragment$key
4144
>(NotificationsListFragment, me)
@@ -68,8 +71,20 @@ const NotificationsList: FC<NotificationsListProps> = ({
6871
if (notifications.length === 0) return <EmptyState />
6972

7073
return (
71-
<View>
72-
<FlatList data={notifications} renderItem={({ item }) => renderNotificationItem(item)} />
74+
<View style={{ height: '100%', paddingBottom: 74 }}>
75+
<InfiniteScrollerView
76+
data={notifications}
77+
renderItem={({ item }: { item: NotificationsListFragment$data }) =>
78+
renderNotificationItem(item)
79+
}
80+
estimatedItemSize={134}
81+
onEndReached={() => {
82+
if (hasNext) {
83+
loadNext(NUMBER_OF_NOTIFICATIONS_TO_LOAD_NEXT)
84+
}
85+
}}
86+
isLoading={isLoadingNext}
87+
/>
7388
</View>
7489
)
7590
}

packages/components/modules/notifications/web/NotificationsList/constants.ts

-1
This file was deleted.

packages/components/modules/notifications/web/NotificationsList/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import { Virtuoso } from 'react-virtuoso'
1212
import { NotificationsListFragment$key } from '../../../../__generated__/NotificationsListFragment.graphql'
1313
import { NotificationsListQuery as NotificationsListQueryType } from '../../../../__generated__/NotificationsListQuery.graphql'
1414
import {
15+
NUMBER_OF_NOTIFICATIONS_TO_LOAD_NEXT,
1516
NotificationsListFragment,
1617
NotificationsListQuery,
1718
useNotificationsSubscription,
1819
} from '../../common'
1920
import DefaultEmptyState from './EmptyState'
2021
import MarkAllAsReadButton from './MarkAllAsReadButton'
2122
import DefaultNotificationItem from './NotificationItem'
22-
import { NUMBER_OF_NOTIFICATIONS_TO_LOAD_NEXT } from './constants'
2323
import { HeaderContainer } from './styled'
2424
import { NotificationsListProps } from './types'
2525

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { FC } from 'react'
2+
3+
import { FlashList } from '@shopify/flash-list'
4+
5+
import { LoadingScreen } from '../../displays'
6+
import View from '../View'
7+
import { styles } from './styles'
8+
import { InfiniteScrollerViewProps } from './types'
9+
10+
/**
11+
* InfiniteScrollerView Component
12+
*
13+
* @description
14+
* This is a **BaseApp** feature.
15+
*
16+
* If you believe your changes should be in the BaseApp, please read the **CONTRIBUTING.md** guide.
17+
*
18+
* The `InfiniteScrollerView` component provides an infinite scrolling view by wrapping Shopify's FlashList.
19+
* It handles a footer loading state based on the "isLoading" prop if no custom ListFooterComponent is provided.
20+
*
21+
* When ListFooterComponent is not passed, "isLoading" is required and will display a default loading indicator.
22+
* If a custom ListFooterComponent is provided, the "isLoading" prop is omitted.
23+
*
24+
* @param {InfiniteScrollerViewProps<T>} props - The props for configuring the infinite scrolling behavior.
25+
* @param {boolean} [props.isLoading] - Indicates if the list is currently loading more items (required if ListFooterComponent is not provided).
26+
* @param {number} [props.estimatedItemSize=200] - The estimated size per list item.
27+
* @param {React.ComponentType<any> | React.ReactNode} [props.ListFooterComponent] - A custom component to display as the list footer.
28+
* @param {...any} props - Additional properties are passed down to the underlying FlashList component, all of FlashList's props are supported.
29+
*
30+
* @example
31+
* // Using the default footer loading state:
32+
* <InfiniteScrollerView
33+
* data={data}
34+
* renderItem={({ item }) => <ItemComponent item={item} />}
35+
* estimatedItemSize={134}
36+
* onEndReached={() => {
37+
* if (hasNext) {
38+
* loadNext(5)
39+
* }
40+
* }}
41+
* isLoading={isLoadingNext}
42+
* />
43+
*
44+
* @example
45+
* // Using a custom footer component:
46+
* <InfiniteScrollerView
47+
* data={data}
48+
* renderItem={({ item }) => <ItemComponent item={item} />}
49+
* estimatedItemSize={134}
50+
* onEndReached={() => {
51+
* if (hasNext) {
52+
* loadNext(5)
53+
* }
54+
* }}
55+
* ListFooterComponent={<CustomFooter />}
56+
* />
57+
*
58+
* @returns {JSX.Element} A container wrapping a FlashList with infinite scrolling functionality.
59+
*/
60+
61+
// TODO: There is a ref error that will be fixed on the next version of flash-list we need to update to this new version to fix this issue https://github.com/Shopify/flash-list/issues/1559
62+
const InfiniteScrollerView: FC<InfiniteScrollerViewProps<any>> = ({
63+
isLoading,
64+
estimatedItemSize = 200,
65+
ListFooterComponent,
66+
...props
67+
}) => {
68+
const renderFooterLoadingState = () => {
69+
if (!isLoading) return <View style={{ paddingTop: 24 }} />
70+
71+
return <LoadingScreen size="small" />
72+
}
73+
74+
return (
75+
<View style={styles.container}>
76+
<FlashList
77+
estimatedItemSize={estimatedItemSize}
78+
ListFooterComponent={ListFooterComponent ?? renderFooterLoadingState}
79+
{...props}
80+
/>
81+
</View>
82+
)
83+
}
84+
85+
export default InfiniteScrollerView
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { StyleSheet } from 'react-native'
2+
3+
export const styles = StyleSheet.create({
4+
container: {
5+
height: '100%',
6+
},
7+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { FlashListProps } from '@shopify/flash-list'
2+
3+
type ListFooterComponent<TItem> = {
4+
ListFooterComponent: Pick<FlashListProps<TItem>, 'ListFooterComponent'>
5+
isLoading?: never
6+
}
7+
8+
type IsLoading = {
9+
isLoading: boolean
10+
ListFooterComponent?: never
11+
}
12+
13+
export type InfiniteScrollerViewProps<TItem> = FlashListProps<TItem> &
14+
Omit<FlashListProps<TItem>, 'ListFooterComponent'> &
15+
(ListFooterComponent<TItem> | IsLoading)

packages/design-system/components/native/views/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export { default as CollapsibleView } from './CollapsibleView'
22
export type * from './CollapsibleView/types'
3+
export { default as InfiniteScrollerView } from './InfiniteScrollerView'
4+
export type * from './InfiniteScrollerView/types'
35
export { default as PageViewWithHeader } from './PageViewWithHeader'
46
export type * from './PageViewWithHeader/types'
57
export { default as ParallaxScrollView } from './ParallaxScrollView'

packages/design-system/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"@mui/x-date-pickers": "catalog:material-ui",
7070
"@react-navigation/drawer": "catalog:react-native-core",
7171
"@react-navigation/native": "catalog:react-native-core",
72+
"@shopify/flash-list": "^1.7.5",
7273
"@storybook/react": "catalog:storybook",
7374
"color": "^4.2.3",
7475
"expo-router": "catalog:react-native-core",

0 commit comments

Comments
 (0)