-
Notifications
You must be signed in to change notification settings - Fork 734
Reimplement Incubator.TouchableOpacity with reanimatedv2 #1380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
2fa85f0
Reimplement Incubator.TouchableOpacity with reanimatedv2
ethanshar 8513fa0
Update gesture-handler version
ethanshar c175304
Fix long press behavior and cancel event
ethanshar 48e4190
Remove commented code
ethanshar 87c3e97
Minor refactor with repeating code
ethanshar 7e2e792
Fix array deps for onPress callbacks
ethanshar 4d6a1b1
Fix deps array
ethanshar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import React from 'react'; | ||
import { LayoutChangeEvent } from 'react-native'; | ||
import { State } from 'react-native-gesture-handler'; | ||
import { ViewProps } from '../components/view'; | ||
export declare type TouchableOpacityProps = { | ||
/** | ||
* Background color | ||
*/ | ||
backgroundColor?: string; | ||
/** | ||
* Background color when actively pressing the touchable | ||
*/ | ||
feedbackColor?: string; | ||
/** | ||
* Opacity value when actively pressing the touchable | ||
*/ | ||
activeOpacity?: number; | ||
/** | ||
* Scale value when actively pressing the touchable | ||
*/ | ||
activeScale?: number; | ||
/** | ||
* Callback for when tapping the touchable | ||
*/ | ||
onPress?: (props: any) => void; | ||
/** | ||
* Callback for when long pressing the touchable | ||
*/ | ||
onLongPress?: (props: any) => void; | ||
/** | ||
* Pass controlled pressState to track gesture state changes | ||
*/ | ||
pressState?: State; | ||
/** | ||
* If true, disable all interactions for this component. | ||
*/ | ||
disabled?: boolean; | ||
/** | ||
* Pass custom style | ||
*/ | ||
style?: ViewProps['style']; | ||
/** | ||
* Custom value of any type to pass on to TouchableOpacity and receive back in onPress callback | ||
*/ | ||
customValue?: any; | ||
onLayout?: (event: LayoutChangeEvent) => void; | ||
testID?: string; | ||
}; | ||
declare const _default: React.ComponentClass<TouchableOpacityProps & { | ||
useCustomTheme?: boolean | undefined; | ||
}, any>; | ||
export default _default; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export { default as TabController } from './TabController'; | ||
export { default as TextField, TextFieldProps, FieldContextType } from './TextField'; | ||
export { default as TouchableOpacity, TouchableOpacityProps } from './TouchableOpacity'; | ||
export { default as TouchableOpacity2 } from './TouchableOpacity2'; | ||
export { default as WheelPicker, WheelPickerProps } from './WheelPicker'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import React, {PropsWithChildren, useCallback, useMemo} from 'react'; | ||
import {LayoutChangeEvent} from 'react-native'; | ||
import Reanimated, { | ||
useAnimatedGestureHandler, | ||
useAnimatedStyle, | ||
useSharedValue, | ||
withTiming, | ||
interpolate, | ||
interpolateColor, | ||
runOnJS | ||
} from 'react-native-reanimated'; | ||
import {TapGestureHandler, LongPressGestureHandler, State} from 'react-native-gesture-handler'; | ||
import {asBaseComponent, forwardRef, BaseComponentInjectedProps, ForwardRefInjectedProps} from '../commons/new'; | ||
import {ViewProps} from '../components/view'; | ||
|
||
export type TouchableOpacityProps = { | ||
/** | ||
* Background color | ||
*/ | ||
backgroundColor?: string; | ||
/** | ||
* Background color when actively pressing the touchable | ||
*/ | ||
feedbackColor?: string; | ||
/** | ||
* Opacity value when actively pressing the touchable | ||
*/ | ||
activeOpacity?: number; | ||
/** | ||
* Scale value when actively pressing the touchable | ||
*/ | ||
activeScale?: number; | ||
/** | ||
* Callback for when tapping the touchable | ||
*/ | ||
onPress?: (props: any) => void; | ||
/** | ||
* Callback for when long pressing the touchable | ||
*/ | ||
onLongPress?: (props: any) => void; | ||
/** | ||
* Pass controlled pressState to track gesture state changes | ||
*/ | ||
pressState?: State; | ||
/** | ||
* If true, disable all interactions for this component. | ||
*/ | ||
disabled?: boolean; | ||
/** | ||
* Pass custom style | ||
*/ | ||
style?: ViewProps['style']; | ||
/** | ||
* Custom value of any type to pass on to TouchableOpacity and receive back in onPress callback | ||
*/ | ||
customValue?: any; | ||
onLayout?: (event: LayoutChangeEvent) => void; | ||
testID?: string; | ||
}; | ||
|
||
type Props = PropsWithChildren<TouchableOpacityProps & BaseComponentInjectedProps & ForwardRefInjectedProps>; | ||
|
||
/** | ||
* @description: a Better, enhanced TouchableOpacity component | ||
* @modifiers: flex, margin, padding, background | ||
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/incubatorScreens/TouchableOpacityScreen.js | ||
*/ | ||
function TouchableOpacity(props: Props) { | ||
const { | ||
children, | ||
modifiers, | ||
style, | ||
disabled, | ||
forwardedRef, | ||
feedbackColor, | ||
activeOpacity = 0.2, | ||
activeScale = 1, | ||
...others | ||
} = props; | ||
const {borderRadius, paddings, margins, alignments, flexStyle} = modifiers; | ||
|
||
const isActive = useSharedValue(0); | ||
/* This flag is for fixing an issue with long press triggering twice | ||
TODO: Consider revisiting this issue to see if it still occurs */ | ||
const isLongPressed = useSharedValue(false); | ||
|
||
const backgroundColor = useMemo(() => { | ||
return props.backgroundColor || modifiers.backgroundColor; | ||
}, [props.backgroundColor, modifiers.backgroundColor]); | ||
|
||
const onPress = useCallback(() => { | ||
props.onPress?.(props); | ||
}, [props.onPress, props.customValue]); | ||
|
||
const onLongPress = useCallback(() => { | ||
props.onLongPress?.(props); | ||
}, [props.onLongPress, props.customValue]); | ||
|
||
const toggleActive = (value: number) => { | ||
'worklet'; | ||
isActive.value = withTiming(value, {duration: 200}); | ||
}; | ||
|
||
const tapGestureHandler = useAnimatedGestureHandler({ | ||
onStart: () => { | ||
toggleActive(1); | ||
}, | ||
onEnd: () => { | ||
toggleActive(0); | ||
|
||
runOnJS(onPress)(); | ||
}, | ||
onFail: () => { | ||
toggleActive(0); | ||
} | ||
}); | ||
|
||
const longPressGestureHandler = useAnimatedGestureHandler({ | ||
onActive: () => { | ||
if (!isLongPressed.value) { | ||
isLongPressed.value = true; | ||
toggleActive(0); | ||
runOnJS(onLongPress)(); | ||
} | ||
}, | ||
onFinish: () => { | ||
isLongPressed.value = false; | ||
} | ||
}); | ||
|
||
const animatedStyle = useAnimatedStyle(() => { | ||
const activeColor = feedbackColor || backgroundColor; | ||
const opacity = interpolate(isActive.value, [0, 1], [1, activeOpacity]); | ||
const scale = interpolate(isActive.value, [0, 1], [1, activeScale]); | ||
|
||
return { | ||
backgroundColor: interpolateColor(isActive.value, [0, 1], [backgroundColor, activeColor]), | ||
opacity, | ||
transform: [{scale}] | ||
}; | ||
}, [backgroundColor, feedbackColor]); | ||
|
||
return ( | ||
<TapGestureHandler | ||
// @ts-expect-error | ||
onGestureEvent={tapGestureHandler} | ||
shouldCancelWhenOutside | ||
enabled={!disabled} | ||
> | ||
<Reanimated.View> | ||
{/* @ts-expect-error */} | ||
<LongPressGestureHandler onGestureEvent={longPressGestureHandler} shouldCancelWhenOutside> | ||
<Reanimated.View | ||
{...others} | ||
ref={forwardedRef} | ||
style={[ | ||
borderRadius && {borderRadius}, | ||
flexStyle, | ||
paddings, | ||
margins, | ||
alignments, | ||
backgroundColor && {backgroundColor}, | ||
style, | ||
animatedStyle | ||
]} | ||
> | ||
{children} | ||
</Reanimated.View> | ||
</LongPressGestureHandler> | ||
</Reanimated.View> | ||
</TapGestureHandler> | ||
); | ||
} | ||
|
||
TouchableOpacity.displayName = 'Incubator.TouchableOpacity'; | ||
|
||
export default asBaseComponent<TouchableOpacityProps>(forwardRef<Props>(TouchableOpacity)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does the type of
props
here isany
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because of a behavior/feature we added in uilib to our touchable components.
We pass back the props the user originally passed to the component.
Why?
It solve a common issue when rendering multiple repeating items (like in the case of lists) and other use-cases..
For instance, You can have the following
renderItem
method for a FlatListYou see we have to pass an inline callback here (which is bad for performance) cause we need to pass on the specific index of the item, for each item.
With the feature we added, the user can do the following
Here we didn't have to pass an inline callback, we passed the callback by ref and another custom prop, the
index
.That's why
props
in the onPress callback type is any, cause the user can basically pass/accept anything here.Let me know if it's not clear, we can sync about It quickly..