Skip to content

Commit

Permalink
Merge pull request #14 from tsdmrfth/feature/rtl-support
Browse files Browse the repository at this point in the history
Feature/rtl support
  • Loading branch information
tsdmrfth authored Apr 11, 2021
2 parents cdd9357 + 071417b commit df16053
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 43 deletions.
44 changes: 28 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,33 +89,43 @@ const topics = [
}
]

export default function TopicsScreen() {
const { topicContainer, topicText, title } = styles
export default function App() {
const {
topicContainer,
topicText,
title,
container,
steveContainer
} = styles

const renderTopic = ({ item }) => {
const { emoji, text } = item
return (
<View style={topicContainer} >
<Text >
<View style={topicContainer}>
<Text>
{emoji}
</Text >
<Text style={topicText} >
</Text>
<Text style={topicText}>
{text}
</Text >
</View >
</Text>
</View>
)
}

return (
<View style={styles.container} >
<Text style={title} >
<View style={container}>
<Text style={title}>
{'TOPICS TO EXPLORE'}
</Text >
</Text>
<Steve
isRTL={false}
data={topics}
itemSpacing={10}
renderItem={renderTopic}
keyExtractor={item => item.text} />
</View >
itemStyle={{ margin: 5 }}
containerStyle={steveContainer}
keyExtractor={item => item.text}/>
</View>
)
}

Expand All @@ -137,7 +147,6 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
margin: 5,
backgroundColor: '#FFF'
},
topicText: {
Expand All @@ -151,7 +160,8 @@ const styles = StyleSheet.create({
marginBottom: 5,
marginLeft: 15,
fontWeight: '600'
}
},
steveContainer: { marginHorizontal: 5 }
})
```

Expand All @@ -162,7 +172,9 @@ const styles = StyleSheet.create({
| data | yes | Array | | An array of items to render
| renderItem | yes | Function | | Function that returns a component with given item and index. It is similar to FlatList's renderItem prop |
|keyExtractor| yes | Function| | Function that returns an unique key for each item in the array. Notice that it is a must to provide a unique key since it's used to make calculations||
|containerStyle|no|Style Object| | Style to use for root component |
|containerStyle|no|Style Object| | Style object for root component |
|isRTL|no|boolean|false|Whether the component is RTL layout|
|itemStyle|no|Style Object| |Style object for parent component of each child|

## Contributing

Expand Down
18 changes: 14 additions & 4 deletions example/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ const topics = [
]

export default function App() {
const { topicContainer, topicText, title } = styles
const {
topicContainer,
topicText,
title,
container,
steveContainer
} = styles

const renderTopic = ({ item }) => {
const { emoji, text } = item
Expand All @@ -79,13 +85,17 @@ export default function App() {
}

return (
<View style={styles.container}>
<View style={container}>
<Text style={title}>
{'TOPICS TO EXPLORE'}
</Text>
<Steve
isRTL={false}
data={topics}
itemSpacing={10}
renderItem={renderTopic}
itemStyle={{ margin: 5 }}
containerStyle={steveContainer}
keyExtractor={item => item.text}/>
</View>
)
Expand All @@ -109,7 +119,6 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
margin: 5,
backgroundColor: '#FFF'
},
topicText: {
Expand All @@ -123,5 +132,6 @@ const styles = StyleSheet.create({
marginBottom: 5,
marginLeft: 15,
fontWeight: '600'
}
},
steveContainer: { marginHorizontal: 5 }
})
4 changes: 3 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ declare module 'react-native-steve' {
export interface SteveProps<T> {
data: T[],
renderItem: ({item, index}: {item: T, index: number}) => JSX.Element,
keyExtractor: (item: T) => string,
keyExtractor: (item: T, index: number) => string,
containerStyle?: ViewStyle,
isRTL?: boolean,
itemStyle?: ViewStyle
}
const Steve: <T extends {}>(props: SteveProps<T>) => JSX.Element;

Expand Down
86 changes: 64 additions & 22 deletions src/Steve.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,37 @@ import React, { useRef, useState } from 'react'
import Animated, {
useAnimatedGestureHandler,
useSharedValue,
withDecay,
withSpring,
cancelAnimation,
useAnimatedStyle
useAnimatedStyle,
withDecay
} from 'react-native-reanimated'
import { Dimensions } from 'react-native'
import { PanGestureHandler } from 'react-native-gesture-handler'

const { width: screenWidth } = Dimensions.get('window')
const containerPaddingHorizontal = 10

export const Steve = ({ data, renderItem, keyExtractor, containerStyle }) => {
const getContainerHorizontalSpacing = style => {
const {
margin = 0,
marginHorizontal = 0,
marginLeft = 0,
marginRight = 0,
padding = 0,
paddingHorizontal = 0,
paddingLeft = 0,
paddingRight = 0
} = style
return 2 * (margin + marginHorizontal + padding + paddingHorizontal)
+ (marginLeft + marginRight + paddingLeft + paddingRight)
}

export const Steve = ({ data, renderItem, keyExtractor, containerStyle, isRTL, itemStyle }) => {
const itemLayoutsCache = useRef({})
const [itemLayouts, setItemLayouts] = useState({})
const containerHorizontalSpacing = getContainerHorizontalSpacing(containerStyle)
const translateX = useSharedValue(0)
const paddingHorizontal = containerStyle?.paddingHorizontal || containerPaddingHorizontal
const rtlStyle = isRTL ? { flexDirection: 'row-reverse' } : {}
const onGestureEvent = useAnimatedGestureHandler({
onStart: (event, context) => {
if (context.isDecayAnimationRunning) {
Expand All @@ -32,18 +47,21 @@ export const Steve = ({ data, renderItem, keyExtractor, containerStyle }) => {
context.offset = translateX.value
const { offset } = context
const { velocityX } = event
const maxLevelDifference = screenWidth - Math.max(...Object.values(itemLayouts.sumWidthOfLayer))
const maximumLayerWidth = Math.max(...Object.values(itemLayouts.sumWidthOfLayer))
const levelDifference = screenWidth - maximumLayerWidth - containerHorizontalSpacing
const leftBound = 0
const rightBound = maxLevelDifference - paddingHorizontal
const rightBound = isRTL ? -levelDifference : levelDifference
const firstCondition = isRTL ? (offset < leftBound) : (offset > leftBound)
const secondCondition = isRTL ? (offset > rightBound) : (offset < rightBound)

if (offset > leftBound) {
if (firstCondition) {
context.offset = leftBound
translateX.value = withSpring(leftBound, {
velocity: velocityX,
mass: 0.6,
stiffness: 90
})
} else if (offset < rightBound) {
} else if (secondCondition) {
context.offset = rightBound
translateX.value = withSpring(rightBound, {
velocity: velocityX,
Expand All @@ -52,10 +70,26 @@ export const Steve = ({ data, renderItem, keyExtractor, containerStyle }) => {
})
} else {
context.isDecayAnimationRunning = true
let clamp

if (isRTL) {
if (velocityX < 0) {
clamp = [0, translateX.value]
} else {
clamp = [translateX.value, rightBound]
}
} else {
if (velocityX < 0) {
clamp = [rightBound, translateX.value]
} else {
clamp = [translateX.value, 0]
}
}

translateX.value = withDecay(
{
velocity: velocityX,
clamp: velocityX < 0 ? [rightBound, translateX.value] : [translateX.value, 0]
clamp
},
() => {
context.isDecayAnimationRunning = false
Expand Down Expand Up @@ -103,7 +137,7 @@ export const Steve = ({ data, renderItem, keyExtractor, containerStyle }) => {

return (
<Animated.View
{...{ style }}
style={[style, itemStyle]}
onLayout={event => handleItemLayout(event, itemKey)}>
{renderItem({ item, index })}
</Animated.View>
Expand All @@ -121,9 +155,20 @@ export const Steve = ({ data, renderItem, keyExtractor, containerStyle }) => {
}

const finalizeLayoutSetUp = () => {
let spacingBetweenItems = 0
itemLayoutsCache.current = Object
.values(itemLayoutsCache.current)
.reduce((accumulator, current) => {
.reduce((accumulator, current, index) => {
if (index === 0) {
const firstIndex = isRTL ? 1 : 0
const secondIndex = isRTL ? 0 : 1
const firstKey = keyExtractor(data[firstIndex], firstIndex)
const secondKey = keyExtractor(data[secondIndex], secondIndex)
const firstItem = itemLayoutsCache.current[firstKey]
const secondItem = itemLayoutsCache.current[secondKey]
spacingBetweenItems = secondItem.layout.x - firstItem.layout.width - firstItem.layout.x
}

if (!accumulator.sumWidthOfLayer) {
accumulator.sumWidthOfLayer = {}
}
Expand All @@ -132,13 +177,7 @@ export const Steve = ({ data, renderItem, keyExtractor, containerStyle }) => {
accumulator.sumWidthOfLayer[current.layout.y] = 0
}

const tempMax = accumulator.sumWidthOfLayer[current.layout.y]
const maybeMax = current.layout.x + current.layout.width

if (maybeMax > tempMax) {
accumulator.sumWidthOfLayer[current.layout.y] = maybeMax
}

accumulator.sumWidthOfLayer[current.layout.y] += current.layout.width + spacingBetweenItems
return accumulator
}, itemLayoutsCache.current)
setItemLayouts(itemLayoutsCache.current)
Expand All @@ -159,9 +198,12 @@ const styles = {
container: {
flexWrap: 'wrap',
flexDirection: 'row',
width: screenWidth * 1.8,
paddingHorizontal: containerPaddingHorizontal
width: screenWidth * 1.8
}
}

Steve.displayName = 'Steve'
Steve.displayName = 'Steve'
Steve.defaultProps = {
containerStyle: {},
isRTL: false
}

0 comments on commit df16053

Please sign in to comment.