Compare commits

...

32 Commits

Author SHA1 Message Date
Marcos Rodriguez Vélez
e4e44a01a9
Update Row.js 2021-08-26 00:31:38 -04:00
Surya Sagi
46e39a30ae
Merge pull request #1 from surya-moven/master
CHORE: Remove deprecated lifecycle methods
2020-04-02 12:25:46 -04:00
surya-moven
0c08b861ae CHORE: Remove deprecated lifecycle methods 2020-04-02 12:23:36 -04:00
Zach
60336ebe26
release 0.0.24 (#178) 2020-02-07 00:13:08 +03:00
Attila Gazso
20b1208da3 Added keyboardShouldPersistTaps property (#154) 2019-11-26 21:47:02 +03:00
Zach
cd3b89c4d0 Expose more props for added flexability (#157)
* accept onScroll prop

* snap

* added scrollEnabled update prop

* fix paging

* fix propTypes
2019-11-26 21:42:58 +03:00
Tareq Dayya
a1ec06e9ae added following props to ScrollView: (#177)
- nestedScrollEnabled
- disableIntervalMomentum
2019-11-26 21:36:58 +03:00
Timur Gibadullin
8b3b9ef625 Release 0.0.23 2019-04-11 03:14:30 +03:00
Paul
d706ec915e Return the new list order on release row (#117)
* add the current order to _onReleaseRow callback

* add the current order to _onReleaseRow callback
2019-04-11 03:11:14 +03:00
jpstrikesback
575abd8383 Use ViewPropTypes in row (#103) 2019-04-11 03:08:30 +03:00
Jordan Cleigh
f49f45228e Adds conditional rendering (#139) 2019-04-11 03:05:46 +03:00
AshishKapoor
477c767e01 Create LICENSE (#147) 2019-04-11 03:04:35 +03:00
Timur Gibadullin
759a2eac3e Release 0.0.22 2018-04-14 08:11:03 +03:00
Junyuan Xue
d494d2f76a Adding hide scroll indicator props (#84)
* adding hide scroll indicator props

* proptypes and readme
2018-04-14 08:09:13 +03:00
Timur Gibadullin
8883239f22 Release 0.0.21 2018-03-01 00:43:57 +03:00
Jamie Curtis
2a834de1d7 Add renderHeader which acts the same as renderFooter but at the top (#91)
* Add renderHeader which acts the same as renderFooter but at the top

* Keep trailing comma

* Revert undefined check fix for separate PR
2018-02-28 18:12:45 +03:00
Jamie Curtis
7d995f50f2 Prevent crash by checking for undefined rowLayouts[key] (#93) 2018-02-28 18:11:50 +03:00
Jamie Curtis
4198d23318 Add innerContainerStyle that styles the sortable section and not the header and footer (#92)
ok, thanks for contribution, will release it soon
2018-02-28 17:01:31 +03:00
Timur Gibadullin
0896a6c4ac Release 0.0.20 2018-01-18 21:18:50 +03:00
Erivelton Elias
fe84e0bd54 remove warning that data need be an object [data allow object and array] (#82)
* Update SortableList.js

* Update data accept PropTypes
2018-01-17 20:16:34 +03:00
Timur Gibadullin
cc03b8e2ce Add badges to the readme 2017-11-17 01:50:24 +03:00
Timur Gibadullin
7f897abffc Release 0.0.19 2017-11-17 01:33:18 +03:00
Brad Bumbalough
4318d84e51 Only set pan responder on relevant gestures
- This should allow for child horizontal pan responders (ie swiping).
2017-11-17 00:56:13 +03:00
Timur Gibadullin
250e35f01f Code-style fixes 2017-11-17 00:55:19 +03:00
Brad Bumbalough
83133563da Expose toggleRowActive to renderItem component
- Use `manuallyActivateRows={true}` to have the `toggleRowActive` prop exposed to rendered rows.
- The out of box timer with pan responder will not activate rows.
2017-11-17 00:34:59 +03:00
Timur Gibadullin
ebbf4e5933 Update examples 2017-11-17 00:02:08 +03:00
Timur Gibadullin
97e82800b1 Release 0.0.18 2017-10-17 17:58:39 +03:00
David
af8e828c48 [Props] update deprecated View.propTypes to ViewPropTypes (#71) 2017-10-17 17:49:59 +03:00
Timur Gibadullin
f8260e26dc Release 0.0.17 2017-09-30 13:06:22 +03:00
jlo1
c1a69931ca Stretch row so child items fill container list (#67) 2017-09-30 13:05:11 +03:00
Timur Gibadullin
c3a11701aa Tiny refactoring 2017-09-15 11:37:15 +03:00
Timur Gibadullin
54ed60f74f Fix activation/deactivation conditions in Row 2017-09-15 11:33:01 +03:00
36 changed files with 2817 additions and 2140 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.DS_Store
*.log
node_modules
*.idea

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Tim Gibadullin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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 NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS 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.

View File

@ -1,5 +1,8 @@
## Sortable list view for react-native
![GitHub license](https://img.shields.io/badge/license-MIT-green.svg)
[![npm](https://img.shields.io/npm/v/react-native-sortable-list.svg?style=flat)](https://npmjs.com/package/react-native-sortable-list)
### Content
- [Demo](#demo)
- [Installation](#installation)
@ -26,16 +29,28 @@ npm i react-native-sortable-list --save
- **order?** (Array) an array of keys from data, the order of keys from the array will be used to initial rows order
- **style?** (Object, Array)
- **contentContainerStyle?** (Object, Array) these styles will be applied to the inner scroll view content container
- **innerContainerStyle?** (Object, Array) these styles will be applied to the inner scroll view content container, excluding the header and footer
- **horizontal?** (boolean) when true, the SortableList's children are arranged horizontally in a row instead of vertically in a column. The default value is false.
- **showsVerticalScrollIndicator** (boolean) when false, the vertical scroll indicator will not be visible. The default value is true.
- **showsHorizontalScrollIndicator** (boolean) when false, the horizontal scroll indicator will not be visible. The default value is true.
- **sortingEnabled?** (boolean) when false, rows are not sortable. The default value is true.
- **scrollEnabled?** (boolean) when false, the content does not scrollable. The default value is true.
- **keyboardShouldPersistTaps** (string)<br />
Determines when the keyboard should stay visible after a tap.
- 'never' (the default), tapping outside of the focused text input when the keyboard is up dismisses the keyboard. When this happens, children won't receive the tap.
- 'always', the keyboard will not dismiss automatically, and the scroll view will not catch taps, but children of the scroll view can catch taps.
- 'handled', the keyboard will not dismiss automatically when the tap was handled by a children, (or captured by an ancestor.<br/>
- **manuallyActivateRows?** (bool) whether you intend to use the `toggleRowActive` method to activate a row or use the out of box solution.
- **autoscrollAreaSize?** (number) determines the height for vertical list and the width for horizontal list of the area at the begining and the end of the list that will trigger autoscrolling. Defaults to 60.<br />
- **rowActivationTime?** (number) determines time delay in ms before pressed row becomes active. Defaults to 200 ms.<br />
- **refreshControl?** (element)<br />
A RefreshControl that works the same way as a ScrollView's refreshControl.
- **renderRow** (function)<br />
`({key, index, data, disabled, active}) => renderable`<br />
Takes a row key, row index, data entry from the data source and its statuses disabled, active and should return a renderable component to be rendered as the row.<br />
Takes a row key, row index, data entry from the data source and its statuses disabled, active and should return a renderable component to be rendered as the row. The child component will receive a method called `toggleRowActive` (only if `manuallyActivateRows={true}`) to manually activate the row. Useful if you have multiple touch responders in your view.<br />
- **renderHeader?** (function)<br />
`() => renderable`<br />
Renders returned component at the top of the list.
- **renderFooter?** (function)<br />
`() => renderable`<br />
Renders returned component at the bottom of the list.
@ -46,8 +61,8 @@ Called when rows were reordered, takes an array of rows keys of the next rows or
`(key) => void`<br />
Called when a row was activated (user long tapped).
- **onReleaseRow?** (function)<br />
`(key) => void`<br />
Called when the active row was released.
`(key, currentOrder) => void`<br />
Called when the active row was released. Returns the key and the new list order.
- **onPressRow?** (function)<br />
`(key) => void`<br />
Called when a row was pressed.

View File

@ -0,0 +1,48 @@
[ignore]
; We fork some components by platform
.*/*[.]android.js
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
.*/Libraries/react-native/React.js
; Ignore polyfills
.*/Libraries/polyfills/.*
[include]
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow/
[options]
emoji=true
module.system=haste
munge_underscores=true
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
unsafe.enable_getters_and_setters=true
[version]
^0.56.0

View File

@ -46,8 +46,8 @@ buck-out/
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
# https://docs.fastlane.tools/best-practices/source-control/
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots

226
examples/Basic/App.js Normal file
View File

@ -0,0 +1,226 @@
/**
* Sample React Native App
* httpss://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
Animated,
Easing,
StyleSheet,
Text,
Image,
View,
Dimensions,
Platform,
} from 'react-native';
import SortableList from 'react-native-sortable-list';
const window = Dimensions.get('window');
const data = {
0: {
image: 'https://placekitten.com/200/240',
text: 'Chloe',
},
1: {
image: 'https://placekitten.com/200/201',
text: 'Jasper',
},
2: {
image: 'https://placekitten.com/200/202',
text: 'Pepper',
},
3: {
image: 'https://placekitten.com/200/203',
text: 'Oscar',
},
4: {
image: 'https://placekitten.com/200/204',
text: 'Dusty',
},
5: {
image: 'https://placekitten.com/200/205',
text: 'Spooky',
},
6: {
image: 'https://placekitten.com/200/210',
text: 'Kiki',
},
7: {
image: 'https://placekitten.com/200/215',
text: 'Smokey',
},
8: {
image: 'https://placekitten.com/200/220',
text: 'Gizmo',
},
9: {
image: 'https://placekitten.com/220/239',
text: 'Kitty',
},
};
export default class Basic extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>React Native Sortable List</Text>
<SortableList
style={styles.list}
contentContainerStyle={styles.contentContainer}
data={data}
renderRow={this._renderRow} />
</View>
);
}
_renderRow = ({data, active}) => {
return <Row data={data} active={active} />
}
}
class Row extends Component {
constructor(props) {
super(props);
this._active = new Animated.Value(0);
this._style = {
...Platform.select({
ios: {
transform: [{
scale: this._active.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.1],
}),
}],
shadowRadius: this._active.interpolate({
inputRange: [0, 1],
outputRange: [2, 10],
}),
},
android: {
transform: [{
scale: this._active.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.07],
}),
}],
elevation: this._active.interpolate({
inputRange: [0, 1],
outputRange: [2, 6],
}),
},
})
};
}
componentWillReceiveProps(nextProps) {
if (this.props.active !== nextProps.active) {
Animated.timing(this._active, {
duration: 300,
easing: Easing.bounce,
toValue: Number(nextProps.active),
}).start();
}
}
render() {
const {data, active} = this.props;
return (
<Animated.View style={[
styles.row,
this._style,
]}>
<Image source={{uri: data.image}} style={styles.image} />
<Text style={styles.text}>{data.text}</Text>
</Animated.View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#eee',
...Platform.select({
ios: {
paddingTop: 20,
},
}),
},
title: {
fontSize: 20,
paddingVertical: 20,
color: '#999999',
},
list: {
flex: 1,
},
contentContainer: {
width: window.width,
...Platform.select({
ios: {
paddingHorizontal: 30,
},
android: {
paddingHorizontal: 0,
}
})
},
row: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
padding: 16,
height: 80,
flex: 1,
marginTop: 7,
marginBottom: 12,
borderRadius: 4,
...Platform.select({
ios: {
width: window.width - 30 * 2,
shadowColor: 'rgba(0,0,0,0.2)',
shadowOpacity: 1,
shadowOffset: {height: 2, width: 2},
shadowRadius: 2,
},
android: {
width: window.width - 30 * 2,
elevation: 0,
marginHorizontal: 30,
},
})
},
image: {
width: 50,
height: 50,
marginRight: 30,
borderRadius: 25,
},
text: {
fontSize: 24,
color: '#222222',
},
});

View File

@ -1,12 +1,12 @@
import 'react-native';
import React from 'react';
import Index from '../index.ios.js';
import App from '../App';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
it('renders correctly', () => {
const tree = renderer.create(
<Index />
<App />
);
});

View File

@ -1,12 +0,0 @@
import 'react-native';
import React from 'react';
import Index from '../index.android.js';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
it('renders correctly', () => {
const tree = renderer.create(
<Index />
);
});

View File

@ -72,6 +72,10 @@ import com.android.build.OutputFile
* ]
*/
project.ext.react = [
entryFile: "index.js"
]
apply from: "../../node_modules/react-native/react.gradle"
/**

View File

@ -25,6 +25,11 @@ public class MainApplication extends Application implements ReactApplication {
new MainReactPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override

View File

@ -1 +0,0 @@
import './index.js';

View File

@ -1 +0,0 @@
import './index.js';

View File

@ -1,229 +1,4 @@
/**
* Sample React Native App
* httpss://github.com/facebook/react-native
* @flow
*/
import { AppRegistry } from 'react-native';
import App from './App';
import React, { Component } from 'react';
import {
Animated,
Easing,
AppRegistry,
StyleSheet,
Text,
Image,
View,
Dimensions,
Platform,
} from 'react-native';
import SortableList from 'react-native-sortable-list';
const window = Dimensions.get('window');
const data = {
0: {
image: 'https://placekitten.com/200/240',
text: 'Chloe',
},
1: {
image: 'https://placekitten.com/200/201',
text: 'Jasper',
},
2: {
image: 'https://placekitten.com/200/202',
text: 'Pepper',
},
3: {
image: 'https://placekitten.com/200/203',
text: 'Oscar',
},
4: {
image: 'https://placekitten.com/200/204',
text: 'Dusty',
},
5: {
image: 'https://placekitten.com/200/205',
text: 'Spooky',
},
6: {
image: 'https://placekitten.com/200/210',
text: 'Kiki',
},
7: {
image: 'https://placekitten.com/200/215',
text: 'Smokey',
},
8: {
image: 'https://placekitten.com/200/220',
text: 'Gizmo',
},
9: {
image: 'https://placekitten.com/220/239',
text: 'Kitty',
},
};
class Basic extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>React Native Sortable List</Text>
<SortableList
style={styles.list}
contentContainerStyle={styles.contentContainer}
data={data}
renderRow={this._renderRow} />
</View>
);
}
_renderRow = ({data, active}) => {
return <Row data={data} active={active} />
}
}
class Row extends Component {
constructor(props) {
super(props);
this._active = new Animated.Value(0);
this._style = {
...Platform.select({
ios: {
transform: [{
scale: this._active.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.1],
}),
}],
shadowRadius: this._active.interpolate({
inputRange: [0, 1],
outputRange: [2, 10],
}),
},
android: {
transform: [{
scale: this._active.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.07],
}),
}],
elevation: this._active.interpolate({
inputRange: [0, 1],
outputRange: [2, 6],
}),
},
})
};
}
componentWillReceiveProps(nextProps) {
if (this.props.active !== nextProps.active) {
Animated.timing(this._active, {
duration: 300,
easing: Easing.bounce,
toValue: Number(nextProps.active),
}).start();
}
}
render() {
const {data, active} = this.props;
return (
<Animated.View style={[
styles.row,
this._style,
]}>
<Image source={{uri: data.image}} style={styles.image} />
<Text style={styles.text}>{data.text}</Text>
</Animated.View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#eee',
...Platform.select({
ios: {
paddingTop: 20,
},
}),
},
title: {
fontSize: 20,
paddingVertical: 20,
color: '#999999',
},
list: {
flex: 1,
},
contentContainer: {
width: window.width,
...Platform.select({
ios: {
paddingHorizontal: 30,
},
android: {
paddingHorizontal: 0,
}
})
},
row: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
padding: 16,
height: 80,
flex: 1,
marginTop: 7,
marginBottom: 12,
borderRadius: 4,
...Platform.select({
ios: {
width: window.width - 30 * 2,
shadowColor: 'rgba(0,0,0,0.2)',
shadowOpacity: 1,
shadowOffset: {height: 2, width: 2},
shadowRadius: 2,
},
android: {
width: window.width - 30 * 2,
elevation: 0,
marginHorizontal: 30,
},
})
},
image: {
width: 50,
height: 50,
marginRight: 30,
borderRadius: 25,
},
text: {
fontSize: 24,
color: '#222222',
},
});
AppRegistry.registerComponent('Basic', () => Basic);
AppRegistry.registerComponent('Basic', () => App);

View File

@ -36,6 +36,7 @@
2DCD954D1E0B4F2C00145EB5 /* BasicTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* BasicTests.m */; };
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -228,6 +229,13 @@
remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText;
};
ADBDB9261DFEBF0700ED6528 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 358F4ED71D1E81A9004DF814;
remoteInfo = RCTBlob;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
@ -255,6 +263,7 @@
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = "<group>"; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTBlob.xcodeproj; path = "../node_modules/react-native/Libraries/Blob/RCTBlob.xcodeproj"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -270,6 +279,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */,
@ -411,6 +422,7 @@
3DAD3EAB1DF850E9000B6D8A /* libcxxreact.a */,
3DAD3EAD1DF850E9000B6D8A /* libjschelpers.a */,
3DAD3EAF1DF850E9000B6D8A /* libjschelpers.a */,
3DAD3EA31DF850E9000B6D8A /* libReact-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@ -439,6 +451,7 @@
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */,
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */,
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */,
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */,
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */,
@ -471,6 +484,7 @@
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
@ -483,6 +497,14 @@
name = Products;
sourceTree = "<group>";
};
ADBDB9201DFEBF0600ED6528 /* Products */ = {
isa = PBXGroup;
children = (
ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -602,6 +624,10 @@
ProductGroup = 5E91572E1DD0AC6500FF2AA8 /* Products */;
ProjectRef = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */;
},
{
ProductGroup = ADBDB9201DFEBF0600ED6528 /* Products */;
ProjectRef = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */;
},
{
ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */;
ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
@ -748,10 +774,10 @@
remoteRef = 3DAD3E981DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3DAD3EA31DF850E9000B6D8A /* libReact.a */ = {
3DAD3EA31DF850E9000B6D8A /* libReact-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libReact.a;
path = "libReact-tvOS.a";
remoteRef = 3DAD3EA21DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
@ -825,6 +851,13 @@
remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTBlob.a;
remoteRef = ADBDB9261DFEBF0700ED6528 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */

View File

@ -18,7 +18,7 @@
{
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"Basic"

View File

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -3,11 +3,12 @@
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "16.0.0-alpha.12",
"react-native": "0.46.3",
"react-native-sortable-list": "../.."
"react": "16.0.0",
"react-native": "0.50.3",
"react-native-sortable-list": "0.0.17"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
[ignore]
; We fork some components by platform
.*/*[.]android.js
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
.*/Libraries/react-native/React.js
; Ignore polyfills
.*/Libraries/polyfills/.*
[include]
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow/
[options]
emoji=true
module.system=haste
munge_underscores=true
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
unsafe.enable_getters_and_setters=true
[version]
^0.56.0

View File

@ -46,8 +46,8 @@ buck-out/
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
# https://docs.fastlane.tools/best-practices/source-control/
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots

223
examples/Horizontal/App.js Normal file
View File

@ -0,0 +1,223 @@
/**
* Sample React Native App
* httpss://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
Animated,
Easing,
StyleSheet,
Text,
Image,
View,
Dimensions,
Platform,
} from 'react-native';
import SortableList from 'react-native-sortable-list';
const window = Dimensions.get('window');
const data = {
0: {
image: 'https://placekitten.com/200/240',
text: 'Chloe',
},
1: {
image: 'https://placekitten.com/200/201',
text: 'Jasper',
},
2: {
image: 'https://placekitten.com/200/202',
text: 'Pepper',
},
3: {
image: 'https://placekitten.com/200/203',
text: 'Oscar',
},
4: {
image: 'https://placekitten.com/200/204',
text: 'Dusty',
},
5: {
image: 'https://placekitten.com/200/205',
text: 'Spooky',
},
6: {
image: 'https://placekitten.com/200/210',
text: 'Kiki',
},
7: {
image: 'https://placekitten.com/200/215',
text: 'Smokey',
},
8: {
image: 'https://placekitten.com/200/220',
text: 'Gizmo',
},
9: {
image: 'https://placekitten.com/220/239',
text: 'Kitty',
},
};
export default class Horizontal extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>React Native Sortable List</Text>
<SortableList
horizontal
style={styles.list}
contentContainerStyle={styles.contentContainer}
data={data}
renderRow={this._renderRow} />
</View>
);
}
_renderRow = ({data, active}) => {
return <Row data={data} active={active} />
}
}
class Row extends Component {
constructor(props) {
super(props);
this._active = new Animated.Value(0);
this._style = {
...Platform.select({
ios: {
transform: [{
scale: this._active.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.1],
}),
}],
shadowRadius: this._active.interpolate({
inputRange: [0, 1],
outputRange: [2, 10],
}),
},
android: {
transform: [{
scale: this._active.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.07],
}),
}],
elevation: this._active.interpolate({
inputRange: [0, 1],
outputRange: [2, 6],
}),
},
})
};
}
componentWillReceiveProps(nextProps) {
if (this.props.active !== nextProps.active) {
Animated.timing(this._active, {
duration: 300,
easing: Easing.bounce,
toValue: Number(nextProps.active),
}).start();
}
}
render() {
const {data, active} = this.props;
return (
<Animated.View style={[
styles.row,
this._style,
]}>
<Image source={{uri: data.image}} style={styles.image} />
<Text style={styles.text}>{data.text}</Text>
</Animated.View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#eee',
...Platform.select({
ios: {
paddingTop: 20,
},
}),
},
title: {
fontSize: 20,
paddingVertical: 20,
color: '#999999',
},
list: {
height: 210,
width: window.width,
},
contentContainer: {
...Platform.select({
ios: {
paddingVertical: 30,
},
android: {
paddingVertical: 0,
}
})
},
row: {
flexDirection: 'column',
alignItems: 'center',
backgroundColor: '#fff',
padding: 16,
width: 110,
height: 150,
marginHorizontal: 10,
borderRadius: 4,
...Platform.select({
ios: {
shadowColor: 'rgba(0,0,0,0.2)',
shadowOpacity: 1,
shadowOffset: {height: 2, width: 2},
shadowRadius: 2,
},
android: {
elevation: 0,
marginHorizontal: 30,
},
})
},
image: {
width: 50,
height: 50,
marginBottom: 15,
borderRadius: 25,
},
text: {
fontSize: 18,
color: '#222222',
},
});

View File

@ -1,12 +1,12 @@
import 'react-native';
import React from 'react';
import Index from '../index.ios.js';
import App from '../App';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
it('renders correctly', () => {
const tree = renderer.create(
<Index />
<App />
);
});

View File

@ -1,12 +0,0 @@
import 'react-native';
import React from 'react';
import Index from '../index.android.js';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
it('renders correctly', () => {
const tree = renderer.create(
<Index />
);
});

View File

@ -72,6 +72,10 @@ import com.android.build.OutputFile
* ]
*/
project.ext.react = [
entryFile: "index.js"
]
apply from: "../../node_modules/react-native/react.gradle"
/**

View File

@ -25,6 +25,11 @@ public class MainApplication extends Application implements ReactApplication {
new MainReactPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override

View File

@ -1 +0,0 @@
import './index.js';

View File

@ -1 +0,0 @@
import './index.js';

View File

@ -1,226 +1,4 @@
/**
* Sample React Native App
* httpss://github.com/facebook/react-native
* @flow
*/
import { AppRegistry } from 'react-native';
import App from './App';
import React, { Component } from 'react';
import {
Animated,
Easing,
AppRegistry,
StyleSheet,
Text,
Image,
View,
Dimensions,
Platform,
} from 'react-native';
import SortableList from 'react-native-sortable-list';
const window = Dimensions.get('window');
const data = {
0: {
image: 'https://placekitten.com/200/240',
text: 'Chloe',
},
1: {
image: 'https://placekitten.com/200/201',
text: 'Jasper',
},
2: {
image: 'https://placekitten.com/200/202',
text: 'Pepper',
},
3: {
image: 'https://placekitten.com/200/203',
text: 'Oscar',
},
4: {
image: 'https://placekitten.com/200/204',
text: 'Dusty',
},
5: {
image: 'https://placekitten.com/200/205',
text: 'Spooky',
},
6: {
image: 'https://placekitten.com/200/210',
text: 'Kiki',
},
7: {
image: 'https://placekitten.com/200/215',
text: 'Smokey',
},
8: {
image: 'https://placekitten.com/200/220',
text: 'Gizmo',
},
9: {
image: 'https://placekitten.com/220/239',
text: 'Kitty',
},
};
class Horizontal extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>React Native Sortable List</Text>
<SortableList
horizontal
style={styles.list}
contentContainerStyle={styles.contentContainer}
data={data}
renderRow={this._renderRow} />
</View>
);
}
_renderRow = ({data, active}) => {
return <Row data={data} active={active} />
}
}
class Row extends Component {
constructor(props) {
super(props);
this._active = new Animated.Value(0);
this._style = {
...Platform.select({
ios: {
transform: [{
scale: this._active.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.1],
}),
}],
shadowRadius: this._active.interpolate({
inputRange: [0, 1],
outputRange: [2, 10],
}),
},
android: {
transform: [{
scale: this._active.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.07],
}),
}],
elevation: this._active.interpolate({
inputRange: [0, 1],
outputRange: [2, 6],
}),
},
})
};
}
componentWillReceiveProps(nextProps) {
if (this.props.active !== nextProps.active) {
Animated.timing(this._active, {
duration: 300,
easing: Easing.bounce,
toValue: Number(nextProps.active),
}).start();
}
}
render() {
const {data, active} = this.props;
return (
<Animated.View style={[
styles.row,
this._style,
]}>
<Image source={{uri: data.image}} style={styles.image} />
<Text style={styles.text}>{data.text}</Text>
</Animated.View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#eee',
...Platform.select({
ios: {
paddingTop: 20,
},
}),
},
title: {
fontSize: 20,
paddingVertical: 20,
color: '#999999',
},
list: {
height: 210,
width: window.width,
},
contentContainer: {
...Platform.select({
ios: {
paddingVertical: 30,
},
android: {
paddingVertical: 0,
}
})
},
row: {
flexDirection: 'column',
alignItems: 'center',
backgroundColor: '#fff',
padding: 16,
width: 110,
height: 150,
marginHorizontal: 10,
borderRadius: 4,
...Platform.select({
ios: {
shadowColor: 'rgba(0,0,0,0.2)',
shadowOpacity: 1,
shadowOffset: {height: 2, width: 2},
shadowRadius: 2,
},
android: {
elevation: 0,
marginHorizontal: 30,
},
})
},
image: {
width: 50,
height: 50,
marginBottom: 15,
borderRadius: 25,
},
text: {
fontSize: 18,
color: '#222222',
},
});
AppRegistry.registerComponent('Horizontal', () => Horizontal);
AppRegistry.registerComponent('Horizontal', () => App);

View File

@ -36,6 +36,7 @@
2DCD954D1E0B4F2C00145EB5 /* HorizontalTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* HorizontalTests.m */; };
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -228,6 +229,13 @@
remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText;
};
ADBDB9261DFEBF0700ED6528 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 358F4ED71D1E81A9004DF814;
remoteInfo = RCTBlob;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
@ -255,6 +263,7 @@
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = "<group>"; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTBlob.xcodeproj; path = "../node_modules/react-native/Libraries/Blob/RCTBlob.xcodeproj"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -270,6 +279,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */,
@ -411,6 +422,7 @@
3DAD3EAB1DF850E9000B6D8A /* libcxxreact.a */,
3DAD3EAD1DF850E9000B6D8A /* libjschelpers.a */,
3DAD3EAF1DF850E9000B6D8A /* libjschelpers.a */,
3DAD3EA31DF850E9000B6D8A /* libReact-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@ -439,6 +451,7 @@
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */,
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */,
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */,
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */,
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */,
@ -471,6 +484,7 @@
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
@ -483,6 +497,14 @@
name = Products;
sourceTree = "<group>";
};
ADBDB9201DFEBF0600ED6528 /* Products */ = {
isa = PBXGroup;
children = (
ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -602,6 +624,10 @@
ProductGroup = 5E91572E1DD0AC6500FF2AA8 /* Products */;
ProjectRef = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */;
},
{
ProductGroup = ADBDB9201DFEBF0600ED6528 /* Products */;
ProjectRef = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */;
},
{
ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */;
ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
@ -748,10 +774,10 @@
remoteRef = 3DAD3E981DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3DAD3EA31DF850E9000B6D8A /* libReact.a */ = {
3DAD3EA31DF850E9000B6D8A /* libReact-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libReact.a;
path = "libReact-tvOS.a";
remoteRef = 3DAD3EA21DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
@ -825,6 +851,13 @@
remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTBlob.a;
remoteRef = ADBDB9261DFEBF0700ED6528 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */

View File

@ -18,7 +18,7 @@
{
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"Horizontal"

View File

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -1,13 +1,14 @@
{
"name": "Horizontal",
"name": "Horizontal",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "16.0.0-alpha.12",
"react-native": "0.46.3",
"react-native-sortable-list": "file:../.."
"react": "16.0.0",
"react-native": "0.50.3",
"react-native-sortable-list": "0.0.17"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "react-native-sortable-list",
"version": "0.0.16",
"version": "0.0.24",
"description": "React Native Sortable List component",
"main": "index.js",
"repository": {

View File

@ -1,6 +1,6 @@
import React, {Component} from 'react';
import React, {Component, cloneElement} from 'react';
import PropTypes from 'prop-types';
import {Animated, PanResponder, StyleSheet} from 'react-native';
import {Animated, PanResponder, StyleSheet, ViewPropTypes} from 'react-native';
import {shallowEqual} from './utils';
export default class Row extends Component {
@ -9,11 +9,12 @@ export default class Row extends Component {
animated: PropTypes.bool,
disabled: PropTypes.bool,
horizontal: PropTypes.bool,
style: Animated.View.propTypes.style,
style: ViewPropTypes.style,
location: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number,
}),
manuallyActivateRows: PropTypes.bool,
activationTime: PropTypes.number,
// Will be called on long press.
@ -45,7 +46,14 @@ export default class Row extends Component {
_panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => !this._isDisabled(),
onMoveShouldSetPanResponder: () => !this._isDisabled(),
onMoveShouldSetPanResponder: (e, gestureState) => {
if (this._isDisabled()) return false;
const vy = Math.abs(gestureState.vy)
const vx = Math.abs(gestureState.vx)
return this._active && (this.props.horizontal ? vx > vy : vy > vx);
},
onShouldBlockNativeResponder: () => {
// Returns whether this component should block native components from becoming the JS
@ -56,16 +64,19 @@ export default class Row extends Component {
onPanResponderGrant: (e, gestureState) => {
e.persist();
this._wasLongPress = false;
this._target = e.nativeEvent.target;
this._prevGestureState = {
...gestureState,
moveX: gestureState.x0,
moveY: gestureState.y0,
};
if (this.props.manuallyActivateRows) return;
this._longPressTimer = setTimeout(() => {
this._wasLongPress = true;
this._target = e.nativeEvent.target;
this._prevGestureState = {
...gestureState,
moveX: gestureState.x0,
moveY: gestureState.y0,
};
if (this._active) return;
this._toggleActive(e, gestureState);
}, this.props.activationTime);
},
@ -93,13 +104,13 @@ export default class Row extends Component {
},
onPanResponderRelease: (e, gestureState) => {
if (this._wasLongPress) {
if (this._active) {
this._toggleActive(e, gestureState);
} else if (this._isTouchInsideElement(e)) {
} else {
this._cancelLongPress();
if (this.props.onPress) {
if (this._isTouchInsideElement(e) && this.props.onPress) {
this.props.onPress();
}
}
@ -131,10 +142,11 @@ export default class Row extends Component {
},
});
componentWillReceiveProps(nextProps) {
if (!this._active && !shallowEqual(this._location, nextProps.location)) {
const animated = !this._active && nextProps.animated;
this._relocate(nextProps.location, animated);
componentDidUpdate() {
const {animated, location} = this.props;
if (!this._active && !shallowEqual(this._location, location)) {
this._relocate(location, !this._active && animated);
}
}
@ -153,14 +165,23 @@ export default class Row extends Component {
}
render() {
const {children, style} = this.props;
const {children, style, horizontal} = this.props;
const rowStyle = [
style, styles.container, this._animatedLocation.getLayout(),
horizontal ? styles.horizontalContainer : styles.verticalContainer,
];
return (
<Animated.View
{...this._panResponder.panHandlers}
style={[style, styles.container, this._animatedLocation.getLayout()]}
style={rowStyle}
onLayout={this._onLayout}>
{children}
{this.props.manuallyActivateRows && children
? cloneElement(children, {
toggleRowActive: this._toggleActive,
})
: children
}
</Animated.View>
);
}
@ -177,6 +198,7 @@ export default class Row extends Component {
Animated.timing(this._animatedLocation, {
toValue: nextLocation,
duration: 300,
useNativeDriver: false,
}).start(() => {
this._isAnimationRunning = false;
});
@ -185,7 +207,7 @@ export default class Row extends Component {
}
}
_toggleActive(e, gestureState) {
_toggleActive = (e, gestureState) => {
const callback = this._active ? this.props.onRelease : this.props.onActivate;
this._active = !this._active;
@ -193,7 +215,7 @@ export default class Row extends Component {
if (callback) {
callback(e, gestureState, this._location);
}
}
};
_mapGestureToMove(prevGestureState, gestureState) {
return this.props.horizontal
@ -231,4 +253,12 @@ const styles = StyleSheet.create({
container: {
position: 'absolute',
},
horizontalContainer: {
top: 0,
bottom: 0,
},
verticalContainer: {
left: 0,
right: 0,
},
});

View File

@ -1,6 +1,6 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {ScrollView, View, StyleSheet, Platform, RefreshControl} from 'react-native';
import {ScrollView, View, StyleSheet, Platform, RefreshControl, ViewPropTypes} from 'react-native';
import {shallowEqual, swapArrayElements} from './utils';
import Row from './Row';
@ -15,29 +15,51 @@ uniqueRowKey.id = 0
export default class SortableList extends Component {
static propTypes = {
data: PropTypes.object.isRequired,
data: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
order: PropTypes.arrayOf(PropTypes.any),
style: View.propTypes.style,
contentContainerStyle: View.propTypes.style,
style: ViewPropTypes.style,
contentContainerStyle: ViewPropTypes.style,
innerContainerStyle: ViewPropTypes.style,
sortingEnabled: PropTypes.bool,
scrollEnabled: PropTypes.bool,
horizontal: PropTypes.bool,
showsVerticalScrollIndicator: PropTypes.bool,
showsHorizontalScrollIndicator: PropTypes.bool,
refreshControl: PropTypes.element,
autoscrollAreaSize: PropTypes.number,
snapToAlignment: PropTypes.string,
rowActivationTime: PropTypes.number,
manuallyActivateRows: PropTypes.bool,
keyboardShouldPersistTaps: PropTypes.oneOf(['never', 'always', 'handled']),
scrollEventThrottle: PropTypes.number,
decelerationRate: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
pagingEnabled: PropTypes.bool,
nestedScrollEnabled: PropTypes.bool,
disableIntervalMomentum: PropTypes.bool,
renderRow: PropTypes.func.isRequired,
renderHeader: PropTypes.func,
renderFooter: PropTypes.func,
onChangeOrder: PropTypes.func,
onActivateRow: PropTypes.func,
onReleaseRow: PropTypes.func,
onScroll: PropTypes.func,
};
static defaultProps = {
sortingEnabled: true,
scrollEnabled: true,
keyboardShouldPersistTaps: 'never',
autoscrollAreaSize: 60,
snapToAlignment: 'start',
manuallyActivateRows: false,
showsVerticalScrollIndicator: true,
showsHorizontalScrollIndicator: true,
scrollEventThrottle: 2,
decelerationRate: 'normal',
pagingEnabled: false,
onScroll: () => {}
}
/**
@ -66,27 +88,32 @@ export default class SortableList extends Component {
scrollEnabled: this.props.scrollEnabled
};
componentWillMount() {
componentDidMount() {
this.state.order.forEach((key) => {
this._rowsLayouts[key] = new Promise((resolve) => {
this._resolveRowLayout[key] = resolve;
});
});
if (this.props.renderHeader && !this.props.horizontal) {
this._headerLayout = new Promise((resolve) => {
this._resolveHeaderLayout = resolve;
});
}
if (this.props.renderFooter && !this.props.horizontal) {
this._footerLayout = new Promise((resolve) => {
this._resolveFooterLayout = resolve;
});
}
}
componentDidMount() {
this._onUpdateLayouts();
}
componentWillReceiveProps(nextProps) {
const {data, order} = this.state;
let {data: nextData, order: nextOrder} = nextProps;
componentDidUpdate(prevProps, prevState) {
const {data, order, scrollEnabled} = this.state;
let {data: nextData, order: nextOrder} = this.props;
const {data: prevData} = prevState;
if (data && nextData && !shallowEqual(data, nextData)) {
nextOrder = nextOrder || Object.keys(nextData)
@ -97,26 +124,32 @@ export default class SortableList extends Component {
this._resolveRowLayout[key] = resolve;
});
});
this.setState({
animated: false,
data: nextData,
containerLayout: null,
rowsLayouts: null,
order: nextOrder
});
if (Object.keys(nextData).length > Object.keys(data).length) {
this.setState({
animated: false,
data: nextData,
containerLayout: null,
rowsLayouts: null,
order: nextOrder
});
} else {
this.setState({
data: nextData,
order: nextOrder
});
}
} else if (order && nextOrder && !shallowEqual(order, nextOrder)) {
this.setState({order: nextOrder});
}
}
componentDidUpdate(prevProps, prevState) {
const {data} = this.state;
const {data: prevData} = prevState;
if (data && prevData && !shallowEqual(data, prevData)) {
this._onUpdateLayouts();
}
if (prevProps.scrollEnabled !== scrollEnabled) {
this.setState({scrollEnabled: prevProps.scrollEnabled})
}
}
scrollBy({dx = 0, dy = 0, animated = false}) {
@ -171,18 +204,30 @@ export default class SortableList extends Component {
}
render() {
const {contentContainerStyle, horizontal, style} = this.props;
let {
contentContainerStyle,
innerContainerStyle,
horizontal,
style,
showsVerticalScrollIndicator,
showsHorizontalScrollIndicator,
snapToAlignment,
scrollEventThrottle,
decelerationRate,
pagingEnabled,
nestedScrollEnabled,
disableIntervalMomentum,
keyboardShouldPersistTaps,
} = this.props;
const {animated, contentHeight, contentWidth, scrollEnabled} = this.state;
const containerStyle = StyleSheet.flatten([style, {opacity: Number(animated)}])
const innerContainerStyle = [styles.rowsContainer];
innerContainerStyle = [
styles.rowsContainer,
horizontal ? {width: contentWidth} : {height: contentHeight},
innerContainerStyle
];
let {refreshControl} = this.props;
if (horizontal) {
innerContainerStyle.push({width: contentWidth});
} else {
innerContainerStyle.push({height: contentHeight});
}
if (refreshControl && refreshControl.type === RefreshControl) {
refreshControl = React.cloneElement(this.props.refreshControl, {
enabled: scrollEnabled, // fix for Android
@ -192,13 +237,23 @@ export default class SortableList extends Component {
return (
<View style={containerStyle} ref={this._onRefContainer}>
<ScrollView
nestedScrollEnabled={nestedScrollEnabled}
disableIntervalMomentum={disableIntervalMomentum}
refreshControl={refreshControl}
ref={this._onRefScrollView}
horizontal={horizontal}
contentContainerStyle={contentContainerStyle}
scrollEventThrottle={2}
scrollEventThrottle={scrollEventThrottle}
pagingEnabled={pagingEnabled}
decelerationRate={decelerationRate}
scrollEnabled={scrollEnabled}
onScroll={this._onScroll}>
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
showsHorizontalScrollIndicator={showsHorizontalScrollIndicator}
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
snapToAlignment={snapToAlignment}
onScroll={this._onScroll}
>
{this._renderHeader()}
<View style={innerContainerStyle}>
{this._renderRows()}
</View>
@ -212,15 +267,6 @@ export default class SortableList extends Component {
const {horizontal, rowActivationTime, sortingEnabled, renderRow} = this.props;
const {animated, order, data, activeRowKey, releasedRowKey, rowsLayouts} = this.state;
let rowHeight = 0;
let rowWidth = 0;
if (rowsLayouts) {
Object.keys(rowsLayouts).forEach((key) => {
rowHeight = Math.max(rowHeight, rowsLayouts[key].height);
rowWidth = Math.max(rowWidth, rowsLayouts[key].width);
});
}
let nextX = 0;
let nextY = 0;
@ -231,13 +277,11 @@ export default class SortableList extends Component {
if (rowsLayouts) {
if (horizontal) {
style.height = rowHeight;
location.x = nextX;
nextX += rowsLayouts[key].width;
nextX += rowsLayouts[key] ? rowsLayouts[key].width : 0;
} else {
style.width = rowWidth;
location.y = nextY;
nextY += rowsLayouts[key].height;
nextY += rowsLayouts[key] ? rowsLayouts[key].height : 0;
}
}
@ -262,7 +306,8 @@ export default class SortableList extends Component {
onActivate={this._onActivateRow.bind(this, key, index)}
onPress={this._onPressRow.bind(this, key)}
onRelease={this._onReleaseRow.bind(this, key)}
onMove={this._onMoveRow}>
onMove={this._onMoveRow}
manuallyActivateRows={this.props.manuallyActivateRows}>
{renderRow({
key,
data: data[key],
@ -275,6 +320,20 @@ export default class SortableList extends Component {
});
}
_renderHeader() {
if (!this.props.renderHeader || this.props.horizontal) {
return null;
}
const {headerLayout} = this.state;
return (
<View onLayout={!headerLayout ? this._onLayoutHeader : null}>
{this.props.renderHeader()}
</View>
);
}
_renderFooter() {
if (!this.props.renderFooter || this.props.horizontal) {
return null;
@ -290,8 +349,8 @@ export default class SortableList extends Component {
}
_onUpdateLayouts() {
Promise.all([this._footerLayout, ...Object.values(this._rowsLayouts)])
.then(([footerLayout, ...rowsLayouts]) => {
Promise.all([this._headerLayout, this._footerLayout, ...Object.values(this._rowsLayouts)])
.then(([headerLayout, footerLayout, ...rowsLayouts]) => {
// Can get correct containers layout only after rowss layouts.
this._container.measure((x, y, width, height, pageX, pageY) => {
const rowsLayoutsByKey = {};
@ -307,6 +366,7 @@ export default class SortableList extends Component {
this.setState({
containerLayout: {x, y, width, height, pageX, pageY},
rowsLayouts: rowsLayoutsByKey,
headerLayout,
footerLayout,
contentHeight,
contentWidth,
@ -533,6 +593,10 @@ export default class SortableList extends Component {
this._resolveRowLayout[rowKey]({rowKey, layout});
}
_onLayoutHeader = ({nativeEvent: {layout}}) => {
this._resolveHeaderLayout(layout);
};
_onLayoutFooter = ({nativeEvent: {layout}}) => {
this._resolveFooterLayout(layout);
};
@ -568,7 +632,7 @@ export default class SortableList extends Component {
}));
if (this.props.onReleaseRow) {
this.props.onReleaseRow(rowKey);
this.props.onReleaseRow(rowKey, this.state.order);
}
};
@ -590,8 +654,9 @@ export default class SortableList extends Component {
}
};
_onScroll = ({nativeEvent: {contentOffset}}) => {
this._contentOffset = contentOffset;
_onScroll = (e) => {
this._contentOffset = e.nativeEvent.contentOffset;
this.props.onScroll(e)
};
_onRefContainer = (component) => {