/** * 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 */ 'use strict'; const React = require('react'); const ReactNative = require('react-native'); const { Animated, Image, StyleSheet, Text, TouchableHighlight, TouchableOpacity, Platform, TouchableNativeFeedback, TouchableWithoutFeedback, View, } = ReactNative; const NativeModules = require('../../Libraries/BatchedBridge/NativeModules'); const forceTouchAvailable = (NativeModules.PlatformConstants && NativeModules.PlatformConstants.forceTouchAvailable) || false; class TouchableHighlightBox extends React.Component<{}, $FlowFixMeState> { state = { timesPressed: 0, }; touchableOnPress = () => { this.setState({ timesPressed: this.state.timesPressed + 1, }); }; render() { let textLog = ''; if (this.state.timesPressed > 1) { textLog = this.state.timesPressed + 'x TouchableHighlight onPress'; } else if (this.state.timesPressed > 0) { textLog = 'TouchableHighlight onPress'; } return ( <View> <View style={styles.row}> <TouchableHighlight style={styles.wrapper} testID="touchable_highlight_image_button" onPress={this.touchableOnPress}> <Image source={heartImage} style={styles.image} /> </TouchableHighlight> <TouchableHighlight style={styles.wrapper} testID="touchable_highlight_text_button" activeOpacity={1} tvParallaxProperties={{ pressMagnification: 1.3, pressDuration: 0.6, }} underlayColor="rgb(210, 230, 255)" onPress={this.touchableOnPress}> <View style={styles.wrapperCustom}> <Text style={styles.text}>Tap Here For Custom Highlight!</Text> </View> </TouchableHighlight> </View> <View style={styles.logBox}> <Text testID="touchable_highlight_console">{textLog}</Text> </View> </View> ); } } class TouchableWithoutFeedbackBox extends React.Component<{}, $FlowFixMeState> { state = { timesPressed: 0, }; textOnPress = () => { this.setState({ timesPressed: this.state.timesPressed + 1, }); }; render() { let textLog = ''; if (this.state.timesPressed > 1) { textLog = this.state.timesPressed + 'x TouchableWithoutFeedback onPress'; } else if (this.state.timesPressed > 0) { textLog = 'TouchableWithoutFeedback onPress'; } return ( <View> <TouchableWithoutFeedback onPress={this.textOnPress} testID="touchable_without_feedback_button"> <View style={styles.wrapperCustom}> <Text style={styles.text}>Tap Here For No Feedback!</Text> </View> </TouchableWithoutFeedback> <View style={styles.logBox}> <Text testID="touchable_without_feedback_console">{textLog}</Text> </View> </View> ); } } class TextOnPressBox extends React.Component<{}, $FlowFixMeState> { state = { timesPressed: 0, }; textOnPress = () => { this.setState({ timesPressed: this.state.timesPressed + 1, }); }; render() { let textLog = ''; if (this.state.timesPressed > 1) { textLog = this.state.timesPressed + 'x text onPress'; } else if (this.state.timesPressed > 0) { textLog = 'text onPress'; } return ( <View> <Text style={styles.textBlock} testID="tappable_text" onPress={this.textOnPress}> Text has built-in onPress handling </Text> <View style={styles.logBox}> <Text testID="tappable_text_console">{textLog}</Text> </View> </View> ); } } class TouchableFeedbackEvents extends React.Component<{}, $FlowFixMeState> { state = { eventLog: [], }; render() { return ( <View testID="touchable_feedback_events"> <View style={[styles.row, styles.centered]}> <TouchableOpacity style={styles.wrapper} testID="touchable_feedback_events_button" accessibilityLabel="touchable feedback events" accessibilityRole="button" onPress={() => this._appendEvent('press')} onPressIn={() => this._appendEvent('pressIn')} onPressOut={() => this._appendEvent('pressOut')} onLongPress={() => this._appendEvent('longPress')}> <Text style={styles.button}>Press Me</Text> </TouchableOpacity> </View> <View testID="touchable_feedback_events_console" style={styles.eventLogBox}> {this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)} </View> </View> ); } _appendEvent = eventName => { const limit = 6; const eventLog = this.state.eventLog.slice(0, limit - 1); eventLog.unshift(eventName); this.setState({eventLog}); }; } class TouchableDelayEvents extends React.Component<{}, $FlowFixMeState> { state = { eventLog: [], }; render() { return ( <View testID="touchable_delay_events"> <View style={[styles.row, styles.centered]}> <TouchableOpacity style={styles.wrapper} testID="touchable_delay_events_button" onPress={() => this._appendEvent('press')} delayPressIn={400} onPressIn={() => this._appendEvent('pressIn - 400ms delay')} delayPressOut={1000} onPressOut={() => this._appendEvent('pressOut - 1000ms delay')} delayLongPress={800} onLongPress={() => this._appendEvent('longPress - 800ms delay')}> <Text style={styles.button}>Press Me</Text> </TouchableOpacity> </View> <View style={styles.eventLogBox} testID="touchable_delay_events_console"> {this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)} </View> </View> ); } _appendEvent = eventName => { const limit = 6; const eventLog = this.state.eventLog.slice(0, limit - 1); eventLog.unshift(eventName); this.setState({eventLog}); }; } class ForceTouchExample extends React.Component<{}, $FlowFixMeState> { state = { force: 0, }; _renderConsoleText = () => { return forceTouchAvailable ? 'Force: ' + this.state.force.toFixed(3) : '3D Touch is not available on this device'; }; render() { return ( <View testID="touchable_3dtouch_event"> <View style={styles.forceTouchBox} testID="touchable_3dtouch_output"> <Text>{this._renderConsoleText()}</Text> </View> <View style={[styles.row, styles.centered]}> <View style={styles.wrapper} testID="touchable_3dtouch_button" onStartShouldSetResponder={() => true} onResponderMove={event => this.setState({force: event.nativeEvent.force}) } onResponderRelease={event => this.setState({force: 0})}> <Text style={styles.button}>Press Me</Text> </View> </View> </View> ); } } class TouchableHitSlop extends React.Component<{}, $FlowFixMeState> { state = { timesPressed: 0, }; onPress = () => { this.setState({ timesPressed: this.state.timesPressed + 1, }); }; render() { let log = ''; if (this.state.timesPressed > 1) { log = this.state.timesPressed + 'x onPress'; } else if (this.state.timesPressed > 0) { log = 'onPress'; } return ( <View testID="touchable_hit_slop"> <View style={[styles.row, styles.centered]}> <TouchableOpacity onPress={this.onPress} style={styles.hitSlopWrapper} hitSlop={{top: 30, bottom: 30, left: 60, right: 60}} testID="touchable_hit_slop_button"> <Text style={styles.hitSlopButton}>Press Outside This View</Text> </TouchableOpacity> </View> <View style={styles.logBox}> <Text>{log}</Text> </View> </View> ); } } class TouchableDisabled extends React.Component<{}> { render() { return ( <View> <TouchableOpacity disabled={true} style={[styles.row, styles.block]}> <Text style={styles.disabledButton}>Disabled TouchableOpacity</Text> </TouchableOpacity> <TouchableOpacity disabled={false} style={[styles.row, styles.block]}> <Text style={styles.button}>Enabled TouchableOpacity</Text> </TouchableOpacity> <TouchableHighlight activeOpacity={1} disabled={true} underlayColor="rgb(210, 230, 255)" style={[styles.row, styles.block]} onPress={() => console.log('custom THW text - highlight')}> <Text style={styles.disabledButton}>Disabled TouchableHighlight</Text> </TouchableHighlight> <TouchableHighlight activeOpacity={1} underlayColor="rgb(210, 230, 255)" style={[styles.row, styles.block]} onPress={() => console.log('custom THW text - highlight')}> <Text style={styles.button}>Enabled TouchableHighlight</Text> </TouchableHighlight> <TouchableWithoutFeedback onPress={() => console.log('TWOF has been clicked')} disabled={true}> <View style={styles.wrapperCustom}> <Text style={[ styles.button, styles.nativeFeedbackButton, styles.disabledButton, ]}> Disabled TouchableWithoutFeedback </Text> </View> </TouchableWithoutFeedback> <TouchableWithoutFeedback onPress={() => console.log('TWOF has been clicked')} disabled={false}> <View style={styles.wrapperCustom}> <Text style={[styles.button, styles.nativeFeedbackButton]}> Enabled TouchableWithoutFeedback </Text> </View> </TouchableWithoutFeedback> {Platform.OS === 'android' && ( <TouchableNativeFeedback style={[styles.row, styles.block]} onPress={() => console.log('custom TNF has been clicked')} background={TouchableNativeFeedback.SelectableBackground()}> <View> <Text style={[styles.button, styles.nativeFeedbackButton]}> Enabled TouchableNativeFeedback </Text> </View> </TouchableNativeFeedback> )} {Platform.OS === 'android' && ( <TouchableNativeFeedback disabled={true} style={[styles.row, styles.block]} onPress={() => console.log('custom TNF has been clicked')} background={TouchableNativeFeedback.SelectableBackground()}> <View> <Text style={[styles.disabledButton, styles.nativeFeedbackButton]}> Disabled TouchableNativeFeedback </Text> </View> </TouchableNativeFeedback> )} </View> ); } } const heartImage = { uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small', }; const styles = StyleSheet.create({ row: { justifyContent: 'center', flexDirection: 'row', }, centered: { justifyContent: 'center', }, image: { width: 50, height: 50, }, text: { fontSize: 16, }, block: { padding: 10, }, button: { color: '#007AFF', }, disabledButton: { color: '#007AFF', opacity: 0.5, }, nativeFeedbackButton: { textAlign: 'center', margin: 10, }, 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', }, }); exports.displayName = (undefined: ?string); exports.description = 'Touchable and onPress examples.'; exports.title = '<Touchable*> and onPress'; exports.examples = [ { title: '<TouchableHighlight>', description: 'TouchableHighlight works by adding an extra view with a ' + 'black background under the single child view. This works best when the ' + 'child view is fully opaque, although it can be made to work as a simple ' + 'background color change as well with the activeOpacity and ' + 'underlayColor props.', render: function() { return <TouchableHighlightBox />; }, }, { title: '<TouchableWithoutFeedback>', render: function() { return <TouchableWithoutFeedbackBox />; }, }, { title: 'TouchableNativeFeedback with Animated child', description: 'TouchableNativeFeedback can have an AnimatedComponent as a' + 'direct child.', platform: 'android', render: function() { const mScale = new Animated.Value(1); Animated.timing(mScale, {toValue: 0.3, duration: 1000}).start(); const style = { backgroundColor: 'rgb(180, 64, 119)', width: 200, height: 100, transform: [{scale: mScale}], }; return ( <View> <View style={styles.row}> <TouchableNativeFeedback> <Animated.View style={style} /> </TouchableNativeFeedback> </View> </View> ); }, }, { title: '<Text onPress={fn}> with highlight', render: function(): React.Element<any> { return <TextOnPressBox />; }, }, { title: 'Touchable feedback events', description: '<Touchable*> components accept onPress, onPressIn, ' + 'onPressOut, and onLongPress as props.', render: function(): React.Element<any> { return <TouchableFeedbackEvents />; }, }, { title: 'Touchable delay for events', description: '<Touchable*> components also accept delayPressIn, ' + 'delayPressOut, and delayLongPress as props. These props impact the ' + 'timing of feedback events.', render: function(): React.Element<any> { return <TouchableDelayEvents />; }, }, { title: '3D Touch / Force Touch', description: 'iPhone 6s and 6s plus support 3D touch, which adds a force property to touches', render: function(): React.Element<any> { return <ForceTouchExample />; }, platform: 'ios', }, { title: 'Touchable Hit Slop', description: '<Touchable*> components accept hitSlop prop which extends the touch area ' + 'without changing the view bounds.', render: function(): React.Element<any> { return <TouchableHitSlop />; }, }, { title: 'Disabled Touchable*', description: '<Touchable*> components accept disabled prop which prevents ' + 'any interaction with component', render: function(): React.Element<any> { return <TouchableDisabled />; }, }, ];