Files
react-native/packages/rn-tester/js/examples/Pressable/PressableExample.js
T
Nishan Bende 0823f299e5 Foreground ripple support in Pressable (#31632)
Summary:
This PR aims to enable support for foreground ripple in Pressable. This makes it possible to show ripple on top of custom child components like Image as shown in the below example.

## Changelog

<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->

[Android] [Added] - Support for foreground ripple in Pressable

Pull Request resolved: https://github.com/facebook/react-native/pull/31632

Test Plan:
- Pass property useForeground: true in android_ripple config to verify the changes.

https://user-images.githubusercontent.com/23293248/120111371-4cecbf00-c18f-11eb-8acb-d10718d5483c.mov

Reviewed By: kacieb

Differential Revision: D28926493

Pulled By: yungsters

fbshipit-source-id: 12a6ba71a7dc6ed60fbaeb651f015cace38e03b1
2021-06-07 17:07:58 -07:00

501 lines
13 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import * as React from 'react';
import {
Animated,
Image,
Pressable,
StyleSheet,
Text,
Platform,
View,
} from 'react-native';
const {useEffect, useRef, useState} = React;
const forceTouchAvailable =
(Platform.OS === 'ios' && Platform.constants.forceTouchAvailable) || false;
function ContentPress() {
const [timesPressed, setTimesPressed] = useState(0);
let textLog = '';
if (timesPressed > 1) {
textLog = timesPressed + 'x onPress';
} else if (timesPressed > 0) {
textLog = 'onPress';
}
return (
<>
<View style={styles.row}>
<Pressable
onPress={() => {
setTimesPressed(current => current + 1);
}}>
{({pressed}) => (
<Text style={styles.text}>{pressed ? 'Pressed!' : 'Press Me'}</Text>
)}
</Pressable>
</View>
<View style={styles.logBox}>
<Text testID="pressable_press_console">{textLog}</Text>
</View>
</>
);
}
function TextOnPressBox() {
const [timesPressed, setTimesPressed] = useState(0);
let textLog = '';
if (timesPressed > 1) {
textLog = timesPressed + 'x text onPress';
} else if (timesPressed > 0) {
textLog = 'text onPress';
}
return (
<>
<Text
style={styles.textBlock}
testID="tappable_text"
onPress={() => {
setTimesPressed(prev => prev + 1);
}}>
Text has built-in onPress handling
</Text>
<View style={styles.logBox}>
<Text testID="tappable_text_console">{textLog}</Text>
</View>
</>
);
}
function PressableFeedbackEvents() {
const [eventLog, setEventLog] = useState([]);
function appendEvent(eventName) {
const limit = 6;
setEventLog(current => {
return [eventName].concat(current.slice(0, limit - 1));
});
}
return (
<View testID="pressable_feedback_events">
<View style={[styles.row, styles.centered]}>
<Pressable
style={styles.wrapper}
testID="pressable_feedback_events_button"
accessibilityLabel="pressable feedback events"
accessibilityRole="button"
onPress={() => appendEvent('press')}
onPressIn={() => appendEvent('pressIn')}
onPressOut={() => appendEvent('pressOut')}
onLongPress={() => appendEvent('longPress')}>
<Text style={styles.button}>Press Me</Text>
</Pressable>
</View>
<View
testID="pressable_feedback_events_console"
style={styles.eventLogBox}>
{eventLog.map((e, ii) => (
<Text key={ii}>{e}</Text>
))}
</View>
</View>
);
}
function PressableDelayEvents() {
const [eventLog, setEventLog] = useState([]);
function appendEvent(eventName) {
const limit = 6;
const newEventLog = eventLog.slice(0, limit - 1);
newEventLog.unshift(eventName);
setEventLog(newEventLog);
}
return (
<View testID="pressable_delay_events">
<View style={[styles.row, styles.centered]}>
<Pressable
style={styles.wrapper}
testID="pressable_delay_events_button"
onPress={() => appendEvent('press')}
onPressIn={() => appendEvent('pressIn')}
onPressOut={() => appendEvent('pressOut')}
delayLongPress={800}
onLongPress={() => appendEvent('longPress - 800ms delay')}>
<Text style={styles.button}>Press Me</Text>
</Pressable>
</View>
<View style={styles.eventLogBox} testID="pressable_delay_events_console">
{eventLog.map((e, ii) => (
<Text key={ii}>{e}</Text>
))}
</View>
</View>
);
}
function ForceTouchExample() {
const [force, setForce] = useState(0);
const consoleText = forceTouchAvailable
? 'Force: ' + force.toFixed(3)
: '3D Touch is not available on this device';
return (
<View testID="pressable_3dtouch_event">
<View style={styles.forceTouchBox} testID="pressable_3dtouch_output">
<Text>{consoleText}</Text>
</View>
<View style={[styles.row, styles.centered]}>
<View
style={styles.wrapper}
testID="pressable_3dtouch_button"
onStartShouldSetResponder={() => true}
onResponderMove={event => setForce(event.nativeEvent?.force || 1)}
onResponderRelease={event => setForce(0)}>
<Text style={styles.button}>Press Me</Text>
</View>
</View>
</View>
);
}
function PressableHitSlop() {
const [timesPressed, setTimesPressed] = useState(0);
let log = '';
if (timesPressed > 1) {
log = timesPressed + 'x onPress';
} else if (timesPressed > 0) {
log = 'onPress';
}
return (
<View testID="pressable_hit_slop">
<View style={[styles.row, styles.centered]}>
<Pressable
onPress={() => setTimesPressed(num => num + 1)}
style={styles.hitSlopWrapper}
hitSlop={{top: 30, bottom: 30, left: 60, right: 60}}
testID="pressable_hit_slop_button">
<Text style={styles.hitSlopButton}>Press Outside This View</Text>
</Pressable>
</View>
<View style={styles.logBox}>
<Text>{log}</Text>
</View>
</View>
);
}
function PressableNativeMethods() {
const [status, setStatus] = useState<?boolean>(null);
const ref = useRef(null);
useEffect(() => {
setStatus(ref.current != null && typeof ref.current.measure === 'function');
}, []);
return (
<>
<View style={[styles.row, styles.block]}>
<Pressable ref={ref}>
<View />
</Pressable>
<Text>
{status == null
? 'Missing Ref!'
: status === true
? 'Native Methods Exist'
: 'Native Methods Missing!'}
</Text>
</View>
</>
);
}
function PressableDisabled() {
return (
<>
<Pressable disabled={true} style={[styles.row, styles.block]}>
<Text style={styles.disabledButton}>Disabled Pressable</Text>
</Pressable>
<Pressable
disabled={false}
style={({pressed}) => [
{opacity: pressed ? 0.5 : 1},
styles.row,
styles.block,
]}>
<Text style={styles.button}>Enabled Pressable</Text>
</Pressable>
</>
);
}
const styles = StyleSheet.create({
row: {
justifyContent: 'center',
flexDirection: 'row',
},
centered: {
justifyContent: 'center',
},
text: {
fontSize: 16,
},
block: {
padding: 10,
},
button: {
color: '#007AFF',
},
disabledButton: {
color: '#007AFF',
opacity: 0.5,
},
hitSlopButton: {
color: 'white',
},
wrapper: {
borderRadius: 8,
},
wrapperCustom: {
borderRadius: 8,
padding: 6,
},
hitSlopWrapper: {
backgroundColor: 'red',
marginVertical: 30,
},
logBox: {
padding: 20,
margin: 10,
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#f0f0f0',
backgroundColor: '#f9f9f9',
},
eventLogBox: {
padding: 10,
margin: 10,
height: 120,
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#f0f0f0',
backgroundColor: '#f9f9f9',
},
forceTouchBox: {
padding: 10,
margin: 10,
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#f0f0f0',
backgroundColor: '#f9f9f9',
alignItems: 'center',
},
textBlock: {
fontWeight: '500',
color: 'blue',
},
image: {
height: 100,
width: 100,
},
});
exports.displayName = (undefined: ?string);
exports.description = 'Component for making views pressable.';
exports.title = 'Pressable';
exports.category = 'UI';
exports.documentationURL = 'https://reactnative.dev/docs/pressable';
exports.examples = [
{
title: 'Change content based on Press',
render(): React.Node {
return <ContentPress />;
},
},
{
title: 'Change style based on Press',
render(): React.Node {
return (
<View style={styles.row}>
<Pressable
style={({pressed}) => [
{
backgroundColor: pressed ? 'rgb(210, 230, 255)' : 'white',
},
styles.wrapperCustom,
]}>
<Text style={styles.text}>Press Me</Text>
</Pressable>
</View>
);
},
},
{
title: 'Pressable feedback events',
description: ('<Pressable> components accept onPress, onPressIn, ' +
'onPressOut, and onLongPress as props.': string),
render: function(): React.Node {
return <PressableFeedbackEvents />;
},
},
{
title: 'Pressable with Ripple and Animated child',
description: ('Pressable can have an AnimatedComponent as a direct child.': string),
platform: 'android',
render: function(): React.Node {
const mScale = new Animated.Value(1);
Animated.timing(mScale, {
toValue: 0.3,
duration: 1000,
useNativeDriver: false,
}).start();
const style = {
backgroundColor: 'rgb(180, 64, 119)',
width: 200,
height: 100,
transform: [{scale: mScale}],
};
return (
<View style={styles.row}>
<Pressable android_ripple={{color: 'green'}}>
<Animated.View style={style} />
</Pressable>
</View>
);
},
},
{
title: 'Pressable with custom Ripple',
description: ("Pressable can specify ripple's radius, color and borderless params": string),
platform: 'android',
render: function(): React.Node {
const nativeFeedbackButton = {
textAlign: 'center',
margin: 10,
};
return (
<View>
<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>
<Pressable android_ripple={{borderless: false}}>
<View style={styles.block}>
<Text style={[styles.button, nativeFeedbackButton]}>
with border, default color and radius
</Text>
</View>
</Pressable>
<View style={{alignItems: 'center'}}>
<Pressable
android_ripple={{
borderless: false,
foreground: true,
}}>
<Image
source={{
uri: 'https://www.facebook.com/ads/pics/successstories.png',
}}
style={styles.image}
/>
</Pressable>
<Text>use foreground</Text>
</View>
</View>
);
},
},
{
title: '<Text onPress={fn}> with highlight',
render: function(): React.Node {
return <TextOnPressBox />;
},
},
{
title: 'Pressable delay for events',
description: ('<Pressable> also accept delayPressIn, ' +
'delayPressOut, and delayLongPress as props. These props impact the ' +
'timing of feedback events.': string),
render: function(): React.Node {
return <PressableDelayEvents />;
},
},
{
title: '3D Touch / Force Touch',
description:
'iPhone 8 and 8 plus support 3D touch, which adds a force property to touches',
render: function(): React.Node {
return <ForceTouchExample />;
},
platform: 'ios',
},
{
title: 'Pressable Hit Slop',
description: ('<Pressable> components accept hitSlop prop which extends the touch area ' +
'without changing the view bounds.': string),
render: function(): React.Node {
return <PressableHitSlop />;
},
},
{
title: 'Pressable Native Methods',
description: ('<Pressable> components expose native methods like `measure`.': string),
render: function(): React.Node {
return <PressableNativeMethods />;
},
},
{
title: 'Disabled Pressable',
description: ('<Pressable> components accept disabled prop which prevents ' +
'any interaction with component': string),
render: function(): React.Node {
return <PressableDisabled />;
},
},
];