diff --git a/Libraries/Experimental/SwipeableRow.js b/Libraries/Experimental/SwipeableRow/SwipeableRow.js similarity index 95% rename from Libraries/Experimental/SwipeableRow.js rename to Libraries/Experimental/SwipeableRow/SwipeableRow.js index 3f7a0350f..d480a4143 100644 --- a/Libraries/Experimental/SwipeableRow.js +++ b/Libraries/Experimental/SwipeableRow/SwipeableRow.js @@ -45,11 +45,13 @@ const SwipeableRow = React.createClass({ _previousLeft: CLOSED_LEFT_POSITION, propTypes: { + isOpen: PropTypes.bool, /** * Left position of the maximum open swipe. If unspecified, swipe will open * fully to the left */ maxSwipeDistance: PropTypes.number, + onOpen: PropTypes.func, /** * A ReactElement that is unveiled when the user swipes */ @@ -75,6 +77,7 @@ const SwipeableRow = React.createClass({ getDefaultProps(): Object { return { + isOpen: false, swipeThreshold: 50, }; }, @@ -93,6 +96,16 @@ const SwipeableRow = React.createClass({ }); }, + componentWillReceiveProps(nextProps: Object): void { + /** + * We do not need an "animateOpen(noCallback)" because this animation is + * handled internally by this component. + */ + if (this.props.isOpen && !nextProps.isOpen) { + this._animateClose(); + } + }, + render(): ReactElement { // The view hidden behind the main view const slideOutView = ( @@ -260,14 +273,12 @@ const SwipeableRow = React.createClass({ _animateTo(toValue: number): void { Animated.timing(this.state.currentLeft, {toValue: toValue}).start(() => { this._previousLeft = toValue; - - this.setState({ - currentLeft: new Animated.Value(this._previousLeft), - }); }); }, _animateOpen(): void { + this.props.onOpen && this.props.onOpen(); + const toValue = this.props.maxSwipeDistance ? -this.props.maxSwipeDistance : -this.state.scrollViewWidth; diff --git a/Libraries/Experimental/SwipeableRow/SwipeableRowListView.js b/Libraries/Experimental/SwipeableRow/SwipeableRowListView.js new file mode 100644 index 000000000..47336dd1b --- /dev/null +++ b/Libraries/Experimental/SwipeableRow/SwipeableRowListView.js @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2013-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. + * + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + * + * @providesModule SwipeableRowListView + * @flow + */ +'use strict'; + +const ListViewDataSource = require('ListViewDataSource'); +const React = require('React'); +const SwipeableRow = require('SwipeableRow'); +const View = require('View'); + +const {PropTypes} = React; + +/** + * A container component that renders multiple SwipeableRow's in a provided + * ListView implementation and allows a maximum of 1 SwipeableRow to be open at + * any given time. + */ +const SwipeableRowListView = React.createClass({ + propTypes: { + // Raw data blob for the ListView + dataBlob: PropTypes.object.isRequired, + /** + * Provided implementation of ListView that will be used to render + * SwipeableRow elements from dataBlob + */ + listView: PropTypes.func.isRequired, + maxSwipeDistance: PropTypes.number, + renderRow: PropTypes.func.isRequired, + rowIDs: PropTypes.array.isRequired, + sectionIDs: PropTypes.array.isRequired, + }, + + getInitialState(): Object { + const ds = new ListViewDataSource({ + getRowData: (data, sectionID, rowID) => data[rowID], + getSectionHeaderData: (data, sectionID) => data[sectionID], + rowHasChanged: (row1, row2) => row1 !== row2, + sectionHeaderHasChanged: (s1, s2) => s1 !== s2, + }); + + return { + dataSource: ds.cloneWithRowsAndSections( + this.props.dataBlob, + this.props.sectionIDs, + this.props.rowIDs, + ), + }; + }, + + render(): ReactElement { + const CustomListView = this.props.listView; + + return ( + + ); + }, + + _renderRow(rowData: Object, sectionID: string, rowID: string): ReactElement { + return ( + } + isOpen={rowData.isOpen} + maxSwipeDistance={this.props.maxSwipeDistance} + key={rowID} + onOpen={() => this._onRowOpen(rowID)}> + {this.props.renderRow(rowData, sectionID, rowID, this.state.dataSource)} + + ); + }, + + _onRowOpen(rowID: string): void { + // Need to recreate dataSource object and not just update existing + const blob = JSON.parse(JSON.stringify(this.props.dataBlob)); + blob[rowID].isOpen = true; + + this.setState({ + dataSource: this.state.dataSource.cloneWithRowsAndSections( + blob, + this.props.sectionIDs, + this.props.rowIDs, + ), + }); + }, +}); + +module.exports = SwipeableRowListView;