diff --git a/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js b/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js new file mode 100644 index 000000000..3020047bf --- /dev/null +++ b/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule SwipeableFlatList + * @flow + * @format + */ +'use strict'; + +import type {Props as FlatListProps} from 'FlatList'; +import type {renderItemType} from 'VirtualizedList'; + +const PropTypes = require('prop-types'); +const React = require('React'); +const SwipeableRow = require('SwipeableRow'); +const FlatList = require('FlatList'); + +type SwipableListProps = { + /** + * To alert the user that swiping is possible, the first row can bounce + * on component mount. + */ + bounceFirstRowOnMount: boolean, + // Maximum distance to open to after a swipe + maxSwipeDistance: number | (Object => number), + // Callback method to render the view that will be unveiled on swipe + renderQuickActions: renderItemType, +}; + +type Props = SwipableListProps & FlatListProps; + +type State = { + openRowKey: ?string, +}; + +/** + * A container component that renders multiple SwipeableRow's in a FlatList + * implementation. This is designed to be a drop-in replacement for the + * standard React Native `FlatList`, so use it as if it were a FlatList, but + * with extra props, i.e. + * + * + * + * SwipeableRow can be used independently of this component, but the main + * benefit of using this component is + * + * - It ensures that at most 1 row is swiped open (auto closes others) + * - It can bounce the 1st row of the list so users know it's swipeable + * - Increase performance on iOS by locking list swiping when row swiping is occuring + * - More to come + */ + +class SwipeableFlatList extends React.Component, State> { + props: Props; + state: State; + + _flatListRef: ?FlatList = null; + _shouldBounceFirstRowOnMount: boolean = false; + + static propTypes = { + ...FlatList.propTypes, + + /** + * To alert the user that swiping is possible, the first row can bounce + * on component mount. + */ + bounceFirstRowOnMount: PropTypes.bool.isRequired, + + // Maximum distance to open to after a swipe + maxSwipeDistance: PropTypes.oneOfType([PropTypes.number, PropTypes.func]) + .isRequired, + + // Callback method to render the view that will be unveiled on swipe + renderQuickActions: PropTypes.func.isRequired, + }; + + static defaultProps = { + ...FlatList.defaultProps, + bounceFirstRowOnMount: true, + renderQuickActions: () => null, + }; + + constructor(props: Props, context: any): void { + super(props, context); + this.state = { + openRowKey: null, + }; + + this._shouldBounceFirstRowOnMount = this.props.bounceFirstRowOnMount; + } + + render(): React.Node { + return ( + { + this._flatListRef = ref; + }} + onScroll={this._onScroll} + renderItem={this._renderItem} + /> + ); + } + + _onScroll = (e): void => { + // Close any opens rows on ListView scroll + if (this.state.openRowKey) { + this.setState({ + openRowKey: null, + }); + } + + this.props.onScroll && this.props.onScroll(e); + }; + + _renderItem = (info: Object): ?React.Element => { + const slideoutView = this.props.renderQuickActions(info); + const key = this.props.keyExtractor(info.item, info.index); + + // If renderQuickActions is unspecified or returns falsey, don't allow swipe + if (!slideoutView) { + return this.props.renderItem(info); + } + + let shouldBounceOnMount = false; + if (this._shouldBounceFirstRowOnMount) { + this._shouldBounceFirstRowOnMount = false; + shouldBounceOnMount = true; + } + + return ( + this._onOpen(key)} + onClose={() => this._onClose(key)} + shouldBounceOnMount={shouldBounceOnMount} + onSwipeEnd={this._setListViewScrollable} + onSwipeStart={this._setListViewNotScrollable}> + {this.props.renderItem(info)} + + ); + }; + + // This enables rows having variable width slideoutView. + _getMaxSwipeDistance(info: Object): number { + if (typeof this.props.maxSwipeDistance === 'function') { + return this.props.maxSwipeDistance(info); + } + + return this.props.maxSwipeDistance; + } + + _setListViewScrollableTo(value: boolean) { + if (this._flatListRef) { + this._flatListRef.setNativeProps({ + scrollEnabled: value, + }); + } + } + + _setListViewScrollable = () => { + this._setListViewScrollableTo(true); + }; + + _setListViewNotScrollable = () => { + this._setListViewScrollableTo(false); + }; + + _onOpen(key: any): void { + this.setState({ + openRowKey: key, + }); + } + + _onClose(key: any): void { + this.setState({ + openRowKey: null, + }); + } +} + +module.exports = SwipeableFlatList; diff --git a/Libraries/Lists/FlatList.js b/Libraries/Lists/FlatList.js index ee8385cee..5f12dfd20 100644 --- a/Libraries/Lists/FlatList.js +++ b/Libraries/Lists/FlatList.js @@ -201,7 +201,7 @@ type OptionalProps = { */ viewabilityConfigCallbackPairs?: Array, }; -type Props = RequiredProps & +export type Props = RequiredProps & OptionalProps & VirtualizedListProps; @@ -209,7 +209,7 @@ const defaultProps = { ...VirtualizedList.defaultProps, numColumns: 1, }; -type DefaultProps = typeof defaultProps; +export type DefaultProps = typeof defaultProps; /** * A performant interface for rendering simple, flat lists, supporting the most handy features: diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index 78fe26709..d327ec30d 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -42,7 +42,7 @@ import type { type Item = any; -type renderItemType = (info: any) => ?React.Element; +export type renderItemType = (info: any) => ?React.Element; type ViewabilityHelperCallbackTuple = { viewabilityHelper: ViewabilityHelper, diff --git a/Libraries/react-native/react-native-implementation.js b/Libraries/react-native/react-native-implementation.js index 9019d15d1..88cd864c1 100644 --- a/Libraries/react-native/react-native-implementation.js +++ b/Libraries/react-native/react-native-implementation.js @@ -46,6 +46,7 @@ const ReactNative = { get Switch() { return require('Switch'); }, get RefreshControl() { return require('RefreshControl'); }, get StatusBar() { return require('StatusBar'); }, + get SwipeableFlatList() { return require('SwipeableFlatList'); }, get SwipeableListView() { return require('SwipeableListView'); }, get TabBarIOS() { return require('TabBarIOS'); }, get Text() { return require('Text'); }, diff --git a/RNTester/js/RNTesterList.android.js b/RNTester/js/RNTesterList.android.js index 3eecace85..5f5e4e6c8 100644 --- a/RNTester/js/RNTesterList.android.js +++ b/RNTester/js/RNTesterList.android.js @@ -85,6 +85,10 @@ const ComponentExamples: Array = [ key: 'StatusBarExample', module: require('./StatusBarExample'), }, + { + key: 'SwipeableFlatListExample', + module: require('./SwipeableFlatListExample') + }, { key: 'SwipeableListViewExample', module: require('./SwipeableListViewExample') diff --git a/RNTester/js/RNTesterList.ios.js b/RNTester/js/RNTesterList.ios.js index 63a43716a..a547363cb 100644 --- a/RNTester/js/RNTesterList.ios.js +++ b/RNTester/js/RNTesterList.ios.js @@ -153,6 +153,11 @@ const ComponentExamples: Array = [ module: require('./StatusBarExample'), supportsTVOS: false, }, + { + key: 'SwipeableFlatListExample', + module: require('./SwipeableFlatListExample'), + supportsTVOS: false, + }, { key: 'SwipeableListViewExample', module: require('./SwipeableListViewExample'), diff --git a/RNTester/js/SwipeableFlatListExample.js b/RNTester/js/SwipeableFlatListExample.js new file mode 100644 index 000000000..b34719383 --- /dev/null +++ b/RNTester/js/SwipeableFlatListExample.js @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule SwipeableFlatListExample + * @flow + * @format + */ +'use strict'; + +const React = require('react'); +const createReactClass = require('create-react-class'); +const ReactNative = require('react-native'); +const { + Image, + SwipeableFlatList, + TouchableHighlight, + StyleSheet, + Text, + View, + Alert, +} = ReactNative; + +const RNTesterPage = require('./RNTesterPage'); + +const data = [ + { + key: 'like', + icon: require('./Thumbnails/like.png'), + data: 'Like!', + }, + { + key: 'heart', + icon: require('./Thumbnails/heart.png'), + data: 'Heart!', + }, + { + key: 'party', + icon: require('./Thumbnails/party.png'), + data: 'Party!', + }, +]; + +const SwipeableFlatListExample = createReactClass({ + displayName: 'SwipeableFlatListExample', + statics: { + title: '', + description: 'Performant, scrollable, swipeable list of data.', + }, + + render: function() { + return ( + '} + noSpacer={true} + noScroll={true}> + + + ); + }, + + _renderItem: function({item}): ?React.Element { + return ( + + + + + {item.data} + + + + ); + }, + + _renderQuickActions: function({item}: Object): ?React.Element { + return ( + + { + Alert.alert( + 'Tips', + 'You could do something with this edit action!', + ); + }}> + Edit + + { + Alert.alert( + 'Tips', + 'You could do something with this remove action!', + ); + }}> + Remove + + + ); + }, +}); + +var styles = StyleSheet.create({ + row: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + padding: 10, + backgroundColor: '#F6F6F6', + }, + rowIcon: { + width: 64, + height: 64, + marginRight: 20, + }, + rowData: { + flex: 1, + }, + rowDataText: { + fontSize: 24, + }, + actionsContainer: { + flex: 1, + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + }, + actionButton: { + padding: 10, + width: 80, + backgroundColor: '#999999', + }, + actionButtonDestructive: { + backgroundColor: '#FF0000', + }, + actionButtonText: { + textAlign: 'center', + }, +}); + +module.exports = SwipeableFlatListExample;