Skip to content
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

add ripple config object to Pressable #28156

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions Libraries/Components/Pressable/Pressable.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

import * as React from 'react';
import {useMemo, useState, useRef, useImperativeHandle} from 'react';
import useAndroidRippleForView from './useAndroidRippleForView';
import useAndroidRippleForView, {
type RippleConfig,
} from './useAndroidRippleForView';
import type {
AccessibilityActionEvent,
AccessibilityActionInfo,
Expand Down Expand Up @@ -122,7 +124,7 @@ type Props = $ReadOnly<{|
/**
* Enables the Android ripple effect and configures its color.
*/
android_rippleColor?: ?ColorValue,
android_ripple?: ?RippleConfig,

/**
* Used only for documentation or testing (e.g. snapshot testing).
Expand All @@ -138,7 +140,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
const {
accessible,
android_disableSound,
android_rippleColor,
android_ripple,
children,
delayLongPress,
disabled,
Expand All @@ -156,7 +158,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
const viewRef = useRef<React.ElementRef<typeof View> | null>(null);
useImperativeHandle(forwardedRef, () => viewRef.current);

const android_ripple = useAndroidRippleForView(android_rippleColor, viewRef);
const android_rippleConfig = useAndroidRippleForView(android_ripple, viewRef);

const [pressed, setPressed] = usePressState(testOnly_pressed === true);

Expand All @@ -172,18 +174,18 @@ function Pressable(props: Props, forwardedRef): React.Node {
onLongPress,
onPress,
onPressIn(event: PressEvent): void {
if (android_ripple != null) {
android_ripple.onPressIn(event);
if (android_rippleConfig != null) {
android_rippleConfig.onPressIn(event);
}
setPressed(true);
if (onPressIn != null) {
onPressIn(event);
}
},
onPressMove: android_ripple?.onPressMove,
onPressMove: android_rippleConfig?.onPressMove,
onPressOut(event: PressEvent): void {
if (android_ripple != null) {
android_ripple.onPressOut(event);
if (android_rippleConfig != null) {
android_rippleConfig.onPressOut(event);
}
setPressed(false);
if (onPressOut != null) {
Expand All @@ -193,7 +195,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
}),
[
android_disableSound,
android_ripple,
android_rippleConfig,
delayLongPress,
disabled,
hitSlop,
Expand All @@ -211,7 +213,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
<View
{...restProps}
{...eventHandlers}
{...android_ripple?.viewProps}
{...android_rippleConfig?.viewProps}
accessible={accessible !== false}
focusable={focusable !== false}
hitSlop={hitSlop}
Expand All @@ -227,10 +229,10 @@ function usePressState(forcePressed: boolean): [boolean, (boolean) => void] {
return [pressed || forcePressed, setPressed];
}

const MemodPressable = React.memo(React.forwardRef(Pressable));
MemodPressable.displayName = 'Pressable';
const MemoedPressable = React.memo(React.forwardRef(Pressable));
MemoedPressable.displayName = 'Pressable';

export default (MemodPressable: React.AbstractComponent<
export default (MemoedPressable: React.AbstractComponent<
Props,
React.ElementRef<typeof View>,
>);
22 changes: 16 additions & 6 deletions Libraries/Components/Pressable/useAndroidRippleForView.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,21 @@ type NativeBackgroundProp = $ReadOnly<{|
type: 'RippleAndroid',
color: ?number,
borderless: boolean,
rippleRadius: ?number,
|}>;

export type RippleConfig = {|
color?: ?ColorValue,
borderless?: ?boolean,
radius?: ?number,
|};

/**
* Provides the event handlers and props for configuring the ripple effect on
* supported versions of Android.
*/
export default function useAndroidRippleForView(
rippleColor: ?ColorValue,
rippleConfig: ?RippleConfig,
viewRef: {|current: null | React.ElementRef<typeof View>|},
): ?$ReadOnly<{|
onPressIn: (event: PressEvent) => void,
Expand All @@ -39,25 +46,28 @@ export default function useAndroidRippleForView(
nativeBackgroundAndroid: NativeBackgroundProp,
|}>,
|}> {
const {color, borderless, radius} = rippleConfig != null ? rippleConfig : {};

return useMemo(() => {
vonovak marked this conversation as resolved.
Show resolved Hide resolved
if (
Platform.OS === 'android' &&
Platform.Version >= 21 &&
rippleColor != null
(color != null || borderless != null || radius != null)
vonovak marked this conversation as resolved.
Show resolved Hide resolved
) {
const processedColor = processColor(rippleColor);
const processedColor = processColor(color);
invariant(
processedColor == null || typeof processedColor === 'number',
'Unexpected color given for Ripple color',
);

return {
viewProps: {
// Consider supporting `nativeForegroundAndroid` and `borderless`.
// Consider supporting `nativeForegroundAndroid`
nativeBackgroundAndroid: {
type: 'RippleAndroid',
color: processedColor,
borderless: false,
borderless: borderless === true,
rippleRadius: radius,
},
},
onPressIn(event: PressEvent): void {
Expand Down Expand Up @@ -90,5 +100,5 @@ export default function useAndroidRippleForView(
};
}
return null;
}, [rippleColor, viewRef]);
vonovak marked this conversation as resolved.
Show resolved Hide resolved
}, [color, borderless, radius, viewRef]);
}
1 change: 1 addition & 0 deletions Libraries/Components/View/ViewPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ type AndroidDrawableRipple = $ReadOnly<{|
type: 'RippleAndroid',
color?: ?number,
borderless?: ?boolean,
rippleRadius?: ?number,
|}>;

type AndroidDrawable = AndroidDrawableThemeAttr | AndroidDrawableRipple;
Expand Down
45 changes: 44 additions & 1 deletion RNTester/js/examples/Pressable/PressableExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,13 +369,56 @@ exports.examples = [
};
return (
<View style={styles.row}>
<Pressable android_rippleColor="green">
<Pressable android_ripple={{color: 'green'}}>
<Animated.View style={style} />
</Pressable>
</View>
);
},
},
{
title: 'Pressable with custom Ripple',
description: ("Pressable can specify ripple's radius and borderless params": string),
platform: 'android',
render: function(): React.Node {
const nativeFeedbackButton = {
textAlign: 'center',
margin: 10,
};
return (
<View
style={[
styles.row,
{justifyContent: 'space-around', alignItems: 'center'},
]}>
<Pressable
android_ripple={{color: 'orange', borderless: true, radius: 30}}>
<View>
<Text style={[styles.button, nativeFeedbackButton]}>
radius 30
</Text>
</View>
</Pressable>

<Pressable android_ripple={{borderless: true, radius: 150}}>
<View>
<Text style={[styles.button, nativeFeedbackButton]}>
radius 150
</Text>
</View>
</Pressable>

<Pressable android_ripple={{borderless: false, radius: 70}}>
<View style={styles.block}>
<Text style={[styles.button, nativeFeedbackButton]}>
radius 70, with border
</Text>
</View>
</Pressable>
</View>
);
},
},
{
title: '<Text onPress={fn}> with highlight',
render: function(): React.Node {
Expand Down
20 changes: 11 additions & 9 deletions RNTester/js/examples/Touchable/TouchableExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -452,10 +452,12 @@ function CustomRippleRadius() {

<TouchableNativeFeedback
onPress={() => console.log('custom TNF has been clicked')}
background={TouchableNativeFeedback.SelectableBackgroundBorderless(50)}>
background={TouchableNativeFeedback.SelectableBackgroundBorderless(
150,
)}>
<View>
<Text style={[styles.button, styles.nativeFeedbackButton]}>
radius 50
radius 150
</Text>
</View>
</TouchableNativeFeedback>
Expand Down Expand Up @@ -647,18 +649,18 @@ exports.examples = [
},
},
{
title: 'Disabled Touchable*',
description: ('<Touchable*> components accept disabled prop which prevents ' +
'any interaction with component': string),
title: 'Custom Ripple Radius (Android-only)',
description: ('Ripple radius on TouchableNativeFeedback can be controlled': string),
render: function(): React.Element<any> {
return <TouchableDisabled />;
return <CustomRippleRadius />;
},
},
{
title: 'Custom Ripple Radius (Android-only)',
description: ('Ripple radius on TouchableNativeFeedback can be controlled': string),
title: 'Disabled Touchable*',
description: ('<Touchable*> components accept disabled prop which prevents ' +
'any interaction with component': string),
render: function(): React.Element<any> {
return <CustomRippleRadius />;
return <TouchableDisabled />;
},
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private static RippleDrawable getRippleDrawable(
Context context, ReadableMap drawableDescriptionDict) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
throw new JSApplicationIllegalArgumentException(
"Ripple drawable is not available on " + "android API <21");
"Ripple drawable is not available on android API <21");
}
int color = getColor(context, drawableDescriptionDict);
Drawable mask = getMask(drawableDescriptionDict);
Expand Down Expand Up @@ -101,7 +101,7 @@ private static int getColor(Context context, ReadableMap drawableDescriptionDict
return context.getResources().getColor(sResolveOutValue.resourceId);
} else {
throw new JSApplicationIllegalArgumentException(
"Attribute colorControlHighlight " + "couldn't be resolved into a drawable");
"Attribute colorControlHighlight couldn't be resolved into a drawable");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ public void setBackground(Drawable drawable) {

public void setTranslucentBackgroundDrawable(@Nullable Drawable background) {
// it's required to call setBackground to null, as in some of the cases we may set new
// background to be a layer drawable that contains a drawable that has been previously setup
// background to be a layer drawable that contains a drawable that has been setup
// as a background previously. This will not work correctly as the drawable callback logic is
// messed up in AOSP
updateBackgroundDrawable(null);
Expand Down