Skip to content

Commit

Permalink
Merge pull request #353 from gkartalis/gkartalis/masonry-flashlist-su…
Browse files Browse the repository at this point in the history
…pport

feat: masonry flashlist support
  • Loading branch information
andreialecu committed Aug 1, 2023
2 parents ea2acf3 + 0934be0 commit 9e7d3d4
Show file tree
Hide file tree
Showing 13 changed files with 477 additions and 30 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
- [Tabs.Container](#tabscontainer)
- [Tabs.Lazy](#tabslazy)
- [Tabs.FlatList](#tabsflatlist)
- [Tabs.FlashList](#tabsflashlist)
- [Tabs.FlashList](#tabsflatlist)
- [Tabs.MasonryFlashList](#tabsmasonryflatlist)
- [Tabs.SectionList](#tabssectionlist)
- [Tabs.ScrollView](#tabsscrollview)
- [Ref](#ref)
Expand Down Expand Up @@ -242,6 +243,10 @@ Use like a regular FlatList.

Use like a regular FlashList.

### Tabs.MasonryFlashList

Use like a regular MasonryFlashList.

### Tabs.ScrollView

Use like a regular ScrollView.
Expand Down
1 change: 1 addition & 0 deletions documentation/README_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [Tabs.Container](#tabscontainer)
- [Tabs.Lazy](#tabslazy)
- [Tabs.FlatList](#tabsflatlist)
- [Tabs.MasonryFlashList](#tabsmasonryflatlist)
- [Tabs.FlashList](#tabsflatlist)
- [Tabs.SectionList](#tabssectionlist)
- [Tabs.ScrollView](#tabsscrollview)
Expand Down
8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ PODS:
- React-Core
- RNGestureHandler (2.9.0):
- React-Core
- RNReanimated (2.14.4):
- RNReanimated (3.4.1):
- DoubleConversion
- FBLazyVector
- FBReactNativeSpec
Expand Down Expand Up @@ -411,7 +411,7 @@ DEPENDENCIES:
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "RNFlashList (from `../../node_modules/@shopify/flash-list`)"
- "RNFlashList (from `../node_modules/@shopify/flash-list`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
Expand Down Expand Up @@ -504,7 +504,7 @@ EXTERNAL SOURCES:
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
RNFlashList:
:path: "../../node_modules/@shopify/flash-list"
:path: "../node_modules/@shopify/flash-list"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNReanimated:
Expand Down Expand Up @@ -557,7 +557,7 @@ SPEC CHECKSUMS:
ReactCommon: e1067159764444e5db7c14e294d5cd79fb159c59
RNFlashList: 7fbca4fc075484a9426f1610d648dbea2de94eb0
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
RNReanimated: cc5e3aa479cb9170bcccf8204291a6950a3be128
RNReanimated: f13888574906e326894b4978b8c670c3a4063aec
Yoga: ba09b6b11e6139e3df8229238aa794205ca6a02a

PODFILE CHECKSUM: 4a1d4df08804ad081db13b6bea02bebbb098497a
Expand Down
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"react-native": "0.71.6",
"react-native-gesture-handler": "~2.9.0",
"react-native-pager-view": "6.2.0",
"react-native-reanimated": "~2.14.4",
"react-native-reanimated": "^3.4.1",
"react-native-web": "~0.18.11",
"use-debounce": "^5.2.0",
"use-deep-compare": "^1.1.0"
Expand Down
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import FlashList from './FlashList'
import HeaderOverscrollExample from './HeaderOverscroll'
import Lazy from './Lazy'
import LazyNoFade from './LazyNoFade'
import MasonryFlashList from './MasonryFlashList'
import MinHeaderHeight from './MinHeaderHeight'
import OnTabChange from './OnTabChange'
import QuickStartDemo from './QuickStartDemo'
Expand All @@ -45,6 +46,7 @@ const EXAMPLE_COMPONENTS: ExampleComponentType[] = [
RevealHeaderOnScrollSnap,
Lazy,
LazyNoFade,
MasonryFlashList,
ScrollableTabs,
CenteredEmptyList,
ScrollOnHeader,
Expand Down
84 changes: 84 additions & 0 deletions example/src/MasonryFlashList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react'
import { StyleSheet, View } from 'react-native'
import { useHeaderMeasurements } from 'react-native-collapsible-tab-view'
import Animated, {
interpolate,
useAnimatedStyle,
useDerivedValue,
} from 'react-native-reanimated'

import { useCurrentTabScrollY } from '../../src/hooks'
import ExampleComponent from './Shared/ExampleComponentMasonryFlashList'
import ReText from './Shared/ReText'
import { ExampleComponentType } from './types'

const title = 'MasonryFlashList'

const MIN_HEADER_HEIGHT = 48

export const Header = () => {
const { top, height } = useHeaderMeasurements()
const scrollY = useCurrentTabScrollY()

const scrollYText = useDerivedValue(
() => `Scroll Y is: ${scrollY.value.toFixed(2)}`
)

const stylez = useAnimatedStyle(() => {
return {
transform: [
{
translateY: interpolate(
top.value,
[0, -(height.value || 0 - MIN_HEADER_HEIGHT)],
[0, (height.value || 0 - MIN_HEADER_HEIGHT) / 2]
),
},
],
}
})

return (
<View style={[styles.root]}>
<Animated.View style={[styles.container, stylez]}>
<ReText style={styles.text} text={scrollYText} />
</Animated.View>
</View>
)
}

const Example: ExampleComponentType = () => {
return (
<ExampleComponent
allowHeaderOverscroll
renderHeader={() => <Header />}
minHeaderHeight={MIN_HEADER_HEIGHT}
/>
)
}

const styles = StyleSheet.create({
root: {
backgroundColor: '#2196f3',
justifyContent: 'center',
alignItems: 'center',
padding: 16,
height: 250,
},
container: {
height: MIN_HEADER_HEIGHT,
justifyContent: 'center',
alignItems: 'center',
width: '100%',
},
text: {
position: 'absolute',
color: 'white',
fontSize: 24,
textAlign: 'center',
},
})

Example.title = title

export default Example
47 changes: 47 additions & 0 deletions example/src/Shared/ExampleComponentMasonryFlashList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react'
import {
Tabs,
CollapsibleRef,
CollapsibleProps,
} from 'react-native-collapsible-tab-view'

import Albums from './Albums'
import Article from './Article'
import ContactsFlashList from './ContactsFlashList'
import ExampleMasonry from './ExampleMasonry'
import { HEADER_HEIGHT } from './Header'

type Props = {
emptyContacts?: boolean
hideArticleTab?: boolean
} & Partial<CollapsibleProps>

const Example = React.forwardRef<CollapsibleRef, Props>(
({ emptyContacts, ...props }, ref) => {
return (
<Tabs.Container ref={ref} headerHeight={HEADER_HEIGHT} {...props}>
{props.hideArticleTab ? (
<Tabs.Tab name="article" label="Article">
<Article />
</Tabs.Tab>
) : null}
<Tabs.Tab name="albums" label="Albums">
<Albums />
</Tabs.Tab>
<Tabs.Tab name="contacts" label="MasonryFlashList">
{/*
TODO: masonry has the same issue unfortunatelly when not having a lot of elements
// see: https://github.com/PedroBern/react-native-collapsible-tab-view/issues/335
<ExampleMasonry emptyList={emptyContacts} limit={5} />
*/}
<ExampleMasonry emptyList={emptyContacts} />
</Tabs.Tab>
<Tabs.Tab name="ordered" label="FlashList">
<ContactsFlashList emptyContacts={emptyContacts} />
</Tabs.Tab>
</Tabs.Container>
)
}
)

export default Example
153 changes: 153 additions & 0 deletions example/src/Shared/ExampleMasonry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import * as React from 'react'
import {
Alert,
Platform,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native'
import * as Tabs from 'react-native-collapsible-tab-view'
import Animated, {
interpolate,
useAnimatedStyle,
useDerivedValue,
} from 'react-native-reanimated'

import { useRefresh } from './useRefresh'

type Item = { id: string; aspectRatio: number; color: string }

export function getItems(count = 20) {
return Array.from({ length: count }, (v, k) => {
const r = Math.random() + 0.5
const t = Date.now()
return {
id: `${t}-${k}-${r}`,
aspectRatio: r,
color: `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
Math.random() * 256
)}, ${Math.floor(Math.random() * 256)})`,
}
})
}

export async function asyncGetItems(count = 20, delay = 1000) {
await new Promise<void>((resolve) => {
setTimeout(() => {
resolve()
}, delay)
})

return getItems(count)
}

interface MasonryItemProps {
item: Item
index: number
}

const MasonryItem: React.FC<MasonryItemProps> = ({ item, index }) => {
return (
<TouchableOpacity
style={{
backgroundColor: item.color,
aspectRatio: item.aspectRatio,
...styles.item,
}}
onPress={() => Alert.alert(item.id)}
>
<Text>
{index} - {item.aspectRatio}
</Text>
</TouchableOpacity>
)
}

const ItemSeparator = () => <View style={styles.separator} />

const ListEmptyComponent = () => {
const { top, height } = Tabs.useHeaderMeasurements()
const translateY = useDerivedValue(() => {
return interpolate(
-top.value,
[0, height.value || 0],
[-(height.value || 0) / 2, 0]
)
}, [height])

const stylez = useAnimatedStyle(() => {
return {
transform: [
{
translateY: translateY.value,
},
],
}
})

return (
<Animated.View style={[styles.listEmpty, stylez]}>
<Text>Centered Empty List!</Text>
</Animated.View>
)
}

const ExampleMasonry: React.FC<{
emptyList?: boolean
nestedScrollEnabled?: boolean
limit?: number
}> = ({ emptyList, nestedScrollEnabled, limit }) => {
const [isRefreshing, startRefreshing] = useRefresh()
const [refreshing, setRefreshing] = React.useState(false)
const [data, setData] = React.useState<Item[]>([])

const refresh = React.useCallback(async () => {
if (refreshing) {
return
}
setRefreshing(true)
const res = await asyncGetItems()
setData(res)
setRefreshing(false)
}, [refreshing])

React.useEffect(() => {
refresh()
}, [])

return (
<Tabs.MasonryFlashList
data={emptyList ? [] : limit ? data.slice(0, limit) : data}
numColumns={2}
estimatedItemSize={60}
keyExtractor={(_, i) => String(i)}
renderItem={MasonryItem}
ItemSeparatorComponent={ItemSeparator}
ListEmptyComponent={ListEmptyComponent}
// see https://github.com/software-mansion/react-native-reanimated/issues/1703
onRefresh={Platform.OS === 'ios' ? startRefreshing : undefined}
refreshing={Platform.OS === 'ios' ? isRefreshing : undefined}
nestedScrollEnabled={nestedScrollEnabled}
/>
)
}

export default ExampleMasonry

const styles = StyleSheet.create({
item: {
borderRadius: 10,
},
listEmpty: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
borderColor: 'black',
borderWidth: 10,
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: 'rgba(0, 0, 0, .08)',
},
})

0 comments on commit 9e7d3d4

Please sign in to comment.