diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3116093f5..2cc97dac2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
+## v2.0.0
+
+* Items are now direct children of the component, which makes it easier to use (thanks [@Jonarod](https://github.com/Jonarod) !)
+* Props `items` and `renderItem` have been removed
+
## v1.6.1
-* Due to some touch events being buggy, rework methods so the children will receive touch events on Android.
+* Due to some touch events being buggy, rework methods so the children will receive touch events on Android
## v1.6.0
diff --git a/README.md b/README.md
index 38c17e7ee..baae772a1 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,9 @@ This app is going to be updated on a regular basis.
## Usage
+### Breaking change
+Since version 2.0.0, items are now **direct children of the component**. As a result, props `items` and `renderItem` have been removed.
+
```
$ npm install --save react-native-snap-carousel
```
@@ -34,20 +37,36 @@ $ npm install --save react-native-snap-carousel
```javascript
import Carousel from 'react-native-snap-carousel';
- _renderItem (data, index) {
- return (
- ...
- );
+ // Example with different children
+ render () {
+
+
+
+
+
}
+ // Example of appending the same component multiple times while looping through an array of data
render () {
+ const slides = this.state.entries.map((entry, index) => {
+ return (
+
+ { entry.title }
+
+ );
+ });
+
+ >
+ { slides }
+
}
```
@@ -57,27 +76,25 @@ import Carousel from 'react-native-snap-carousel';
Prop | Description | Type | Default
------ | ------ | ------ | ------
-items | Array of items to loop on | Array | Required
-sliderWidth | The width in pixels of your slider | Number | Required
-itemWidth | Width in pixels of your items | Number | Required
-renderItem | Function returning a react element. The entry data is the 1st parameter, its index is the 2nd | Function | Required
-shouldOptimizeUpdates | whether to implement a `shouldComponentUpdate` strategy to minimize updates | Boolean | `true`
-slideStyle | Style of each item's container | Number | Required
-swipeThreshold | Delta x when swiping to trigger the snap | Number | `20`
-animationFunc | Animated animation to use. Provide the name of the method | String | `Timing`
+**itemWidth** | Width in pixels of your items | Number | **Required**
+**sliderWidth** | The width in pixels of your slider | Number | **Required**
+animationFunc | Animated animation to use. Provide the name of the method | String | `timing`
animationOptions | Animation options to be merged with the default ones. Can be used w/ animationFunc | Object | `{ easing: Easing.elastic(1) }`
-firstItem | Index of the first item to display | Number | `0`
autoplay | Trigger autoplay on mount | Boolean | `false`
-autoplayInterval | Delay in ms until navigating to the next item | `3000`
autoplayDelay | Delay before enabling autoplay on startup & after releasing the touch | Number | `5000`
+autoplayInterval | Delay in ms until navigating to the next item | `3000`
+containerCustomStyle | Optional styles for Scrollview's global wrapper | ScrollView Style Object | `{}`
+contentContainerCustomStyle | Optional styles for Scrollview's items container | ScrollView Style Object | `{}`
+enableMomentum | See [momentum](#momentum) | Boolean | `false`
enableSnap | If enabled, releasing the touch will scroll to the center of the nearest/active item | Number | `true`
-enableMomentum | See [momentum](#momentum) | Boolean | `true`
-snapOnAndroid | Snapping on android is kinda choppy, especially when swiping quickly so you can disable it | Boolean | `false`
-containerCustomStyle | Optional styles for Scrollview's global wrapper | Number | `null`
-contentContainerCustomStyle | Optional styles for Scrollview's items container | Number | `null`
-inactiveSlideScale | Value of the 'scale' transform applied to inactive slides | Number | `0.9`
+firstItem | Index of the first item to display | Number | `0`
inactiveSlideOpacity | Value of the opacity effect applied to inactive slides | Number | `1`
-onSnapToItem(slideIndex, itemData) | Callback fired when navigating to an item | Function | `undefined`
+inactiveSlideScale | Value of the 'scale' transform applied to inactive slides | Number | `0.9`
+shouldOptimizeUpdates | whether to implement a `shouldComponentUpdate` strategy to minimize updates | Boolean | `true`
+slideStyle | Optional style for each item's container (the one whose scale and opacity are animated) | Animated View Style Object | {}
+snapOnAndroid | Snapping on android is kinda choppy, especially when swiping quickly so you can disable it | Boolean | `true`
+swipeThreshold | Delta x when swiping to trigger the snap | Number | `20`
+onSnapToItem(slideIndex) | Callback fired when navigating to an item | Function | `undefined`
## Methods
@@ -109,8 +126,9 @@ You can adjust this value to your needs thanks to [this prop](https://facebook.g
If you need some **extra horizontal margin** between slides (besides the one resulting from the scale effect), you should add it as `paddingHorizontal` on the slide container. Make sure to take this into account when calculating item's width.
```javascript
+const sliderWidth = Dimensions.get('window').width * 0.75;
const slideWidth = 250;
-const horizontalMargin = 40;
+const horizontalMargin = 20;
const itemWidth = slideWidth + horizontalMargin * 2;
const styles = Stylesheet.create({
@@ -121,16 +139,19 @@ const styles = Stylesheet.create({
};
+>
+ ...
+
```
## TODO
-- [ ] Add 'loop' mode
-- [ ] Add 'preload' mode
+- [ ] Known issue: updating children's length doesn't play well with autoplay
+- [ ] Implement 'loop' mode
+- [ ] Implement 'preload' mode
- [ ] Handle changing props on-the-fly
- [ ] Handle device orientation event
- [ ] Add vertical implementation
diff --git a/example/package.json b/example/package.json
index 356359b75..aa755dad0 100644
--- a/example/package.json
+++ b/example/package.json
@@ -1,6 +1,6 @@
{
"name": "example",
- "version": "0.0.1",
+ "version": "0.2.0",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
@@ -9,7 +9,7 @@
"dependencies": {
"react": "15.4.1",
"react-native": "0.38.0",
- "react-native-snap-carousel": "1.6.1"
+ "react-native-snap-carousel": "2.0.0"
},
"jest": {
"preset": "react-native"
diff --git a/example/src/components/SliderEntry.js b/example/src/components/SliderEntry.js
index de975c59f..bfd4ee936 100644
--- a/example/src/components/SliderEntry.js
+++ b/example/src/components/SliderEntry.js
@@ -7,27 +7,33 @@ export default class SliderEntry extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string,
- illustration: PropTypes.string
+ illustration: PropTypes.string,
+ even: PropTypes.bool
};
render () {
- const { title, subtitle, illustration } = this.props;
+ const { title, subtitle, illustration, even } = this.props;
+
+ const uppercaseTitle = title ? (
+ { title.toUpperCase() }
+ ) : false;
return (
{ alert(`You've clicked '${title}'`); }}
>
-
+
+
-
- { title.toUpperCase() }
- { subtitle }
+
+ { uppercaseTitle }
+ { subtitle }
);
diff --git a/example/src/index.js b/example/src/index.js
index e7a63194f..c9d90477d 100644
--- a/example/src/index.js
+++ b/example/src/index.js
@@ -3,73 +3,87 @@ import { View, ScrollView, Text, StatusBar } from 'react-native';
import Carousel from 'react-native-snap-carousel';
import { sliderWidth, itemWidth } from 'example/src/styles/SliderEntry.style';
import SliderEntry from 'example/src/components/SliderEntry';
-import mainStyles from 'example/src/styles/index.style';
-import sliderStyles from 'example/src/styles/Slider.style';
+import styles from 'example/src/styles/index.style';
import { ENTRIES1, ENTRIES2 } from 'example/src/static/entries';
export default class example extends Component {
- _renderItem (entry) {
- return (
-
- );
+ getSlides (entries) {
+ if (!entries) {
+ return false;
+ }
+
+ return entries.map((entry, index) => {
+ return (
+
+ );
+ });
}
get example1 () {
return (
+ >
+ { this.getSlides(ENTRIES1) }
+
);
}
get example2 () {
return (
+ >
+ { this.getSlides(ENTRIES2) }
+
);
}
render () {
return (
-
+
-
-
-
+
+
+
-
- Example 1
+
+ Example 1
+ Momentum | Scale | Opacity
{ this.example1 }
- Example 2 (no momentum)
+ Example 2
+ Autoplay | No momentum
{ this.example2 }
diff --git a/example/src/styles/Slider.style.js b/example/src/styles/Slider.style.js
deleted file mode 100644
index 6565ec769..000000000
--- a/example/src/styles/Slider.style.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { StyleSheet } from 'react-native';
-import { itemHorizontalMargin, itemWidth } from 'example/src/styles/SliderEntry.style';
-
-export default StyleSheet.create({
- slider: {
- marginBottom: 35
- },
- sliderContainer: {
- },
- slide: {
- flexDirection: 'column',
- width: itemWidth,
- paddingHorizontal: itemHorizontalMargin
- }
-});
diff --git a/example/src/styles/SliderEntry.style.js b/example/src/styles/SliderEntry.style.js
index 799bc1907..7e9333204 100644
--- a/example/src/styles/SliderEntry.style.js
+++ b/example/src/styles/SliderEntry.style.js
@@ -15,18 +15,24 @@ export const sliderWidth = viewportWidth;
export const itemHorizontalMargin = wp(2);
export const itemWidth = slideWidth + itemHorizontalMargin * 2;
-const entryBorderRadius = 6;
+const entryBorderRadius = 8;
export default StyleSheet.create({
slideInnerContainer: {
- height: slideHeight
+ width: itemWidth,
+ height: slideHeight,
+ paddingHorizontal: itemHorizontalMargin,
+ paddingBottom: 18 // needed for shadow
},
imageContainer: {
flex: 1,
- backgroundColor: colors.gray,
+ backgroundColor: 'white',
borderTopLeftRadius: entryBorderRadius,
borderTopRightRadius: entryBorderRadius
},
+ imageContainerEven: {
+ backgroundColor: colors.black
+ },
image: {
...StyleSheet.absoluteFillObject,
resizeMode: 'cover',
@@ -34,24 +40,46 @@ export default StyleSheet.create({
borderTopLeftRadius: entryBorderRadius,
borderTopRightRadius: entryBorderRadius
},
+ // image's border radius is buggy on ios; let's hack it!
+ radiusMask: {
+ position: 'absolute',
+ bottom: 0,
+ left: 0,
+ right: 0,
+ height: entryBorderRadius,
+ backgroundColor: 'white'
+ },
+ radiusMaskEven: {
+ backgroundColor: colors.black
+ },
textContainer: {
justifyContent: 'center',
- paddingVertical: 20,
+ paddingTop: 20 - entryBorderRadius,
+ paddingBottom: 20,
paddingHorizontal: 16,
backgroundColor: 'white',
borderBottomLeftRadius: entryBorderRadius,
borderBottomRightRadius: entryBorderRadius
},
+ textContainerEven: {
+ backgroundColor: colors.black
+ },
title: {
color: colors.black,
fontSize: 13,
fontWeight: 'bold',
letterSpacing: 0.5
},
+ titleEven: {
+ color: 'white'
+ },
subtitle: {
marginTop: 6,
color: colors.gray,
fontSize: 12,
fontStyle: 'italic'
+ },
+ subtitleEven: {
+ color: 'rgba(255, 255, 255, 0.7)'
}
});
diff --git a/example/src/styles/index.style.js b/example/src/styles/index.style.js
index a9464aa7a..449238370 100644
--- a/example/src/styles/index.style.js
+++ b/example/src/styles/index.style.js
@@ -29,10 +29,25 @@ export default StyleSheet.create({
paddingTop: 50
},
title: {
- marginVertical: 15,
+ marginTop: 15,
backgroundColor: 'transparent',
- color: 'rgba(255, 255, 255, 0.85)',
- fontSize: 24,
+ color: 'rgba(255, 255, 255, 0.9)',
+ fontSize: 22,
+ fontWeight: 'bold',
textAlign: 'center'
+ },
+ subtitle: {
+ marginTop: 5,
+ marginBottom: 15,
+ backgroundColor: 'transparent',
+ color: 'rgba(255, 255, 255, 0.75)',
+ fontSize: 16,
+ fontStyle: 'italic',
+ textAlign: 'center'
+ },
+ slider: {
+ marginBottom: 30
+ },
+ sliderContainer: {
}
});
diff --git a/index.js b/index.js
index 48754c2b0..646d2c813 100644
--- a/index.js
+++ b/index.js
@@ -7,95 +7,86 @@ export default class Carousel extends Component {
static propTypes = {
...ScrollView.propTypes,
/**
- * Supply items to loop on
- */
- items: PropTypes.array.isRequired,
+ * Width in pixels of your elements
+ */
+ itemWidth: PropTypes.number.isRequired,
/**
* Width in pixels of your slider according
* to your styles
*/
sliderWidth: PropTypes.number.isRequired,
/**
- * Width in pixels of your elements
- */
- itemWidth: PropTypes.number.isRequired,
+ * Animated animation to use. Provide the name
+ * of the method, defaults to timing
+ */
+ animationFunc: PropTypes.string,
/**
- * Function returning a react element. The entry
- * data is the 1st parameter, its index is the 2nd
- */
- renderItem: PropTypes.func.isRequired,
+ * Animation options to be merged with the
+ * default ones. Can be used w/ animationFunc
+ */
+ animationOptions: PropTypes.object,
/**
- * Style of each item's container
- */
- slideStyle: Animated.View.propTypes.style,
+ * Trigger autoplay
+ */
+ autoplay: PropTypes.bool,
/**
- * whether to implement a `shouldComponentUpdate`
- * strategy to minimize updates
- */
- shouldOptimizeUpdates: PropTypes.bool,
+ * Delay before enabling autoplay on startup and
+ * after releasing the touch
+ */
+ autoplayDelay: PropTypes.number,
+ /**
+ * Delay until navigating to the next item
+ */
+ autoplayInterval: PropTypes.number,
/**
* Global wrapper's style
*/
- containerCustomStyle: Animated.View.propTypes.style,
+ containerCustomStyle: ScrollView.propTypes.style,
/**
* Content container's style
*/
- contentContainerCustomStyle: Animated.View.propTypes.style,
- /**
- * Delta x when swiping to trigger the snap
- */
- swipeThreshold: PropTypes.number,
+ contentContainerCustomStyle: ScrollView.propTypes.style,
/**
- * Animated animation to use. Provide the name
- * of the method, defaults to timing
- */
- animationFunc: PropTypes.string,
- /**
- * Animation options to be merged with the
- * default ones. Can be used w/ animationFunc
- */
- animationOptions: PropTypes.object,
- /**
- * Scale factor of the inactive slides
- */
- inactiveSlideScale: PropTypes.number,
+ * If enabled, snapping will be triggered once
+ * the ScrollView stops moving, not when the
+ * user releases his finger
+ */
+ enableMomentum: PropTypes.bool,
/**
- * Opacity value of the inactive slides
- */
- inactiveSlideOpacity: PropTypes.number,
+ * If enabled, releasing the touch will scroll
+ * to the center of the nearest/active item
+ */
+ enableSnap: PropTypes.bool,
/**
- * Index of the first item to display
- */
+ * Index of the first item to display
+ */
firstItem: PropTypes.number,
/**
- * Trigger autoplay
- */
- autoplay: PropTypes.bool,
+ * Opacity value of the inactive slides
+ */
+ inactiveSlideOpacity: PropTypes.number,
/**
- * Delay until navigating to the next item
- */
- autoplayInterval: PropTypes.number,
+ * Scale factor of the inactive slides
+ */
+ inactiveSlideScale: PropTypes.number,
/**
- * Delay before enabling autoplay on startup and
- * after releasing the touch
+ * Style of each item's container
*/
- autoplayDelay: PropTypes.number,
+ slideStyle: Animated.View.propTypes.style,
/**
- * If enabled, releasing the touch will scroll
- * to the center of the nearest/active item
+ * whether to implement a `shouldComponentUpdate`
+ * strategy to minimize updates
*/
- enableSnap: PropTypes.bool,
- /**
- * If enabled, snapping will be triggered once
- * the ScrollView stops moving, not when the
- * user releases his finger
- */
- enableMomentum: PropTypes.bool,
+ shouldOptimizeUpdates: PropTypes.bool,
/**
* Snapping on android is kinda choppy, especially
* when swiping quickly so you can disable it
*/
snapOnAndroid: PropTypes.bool,
+ /**
+ * Delta x when swiping to trigger the snap
+ */
+ swipeThreshold: PropTypes.number,
/**
* Fired when snapping to an item
*/
@@ -103,24 +94,24 @@ export default class Carousel extends Component {
};
static defaultProps = {
- shouldOptimizeUpdates: true,
- autoplay: false,
- autoplayInterval: 3000,
- autoplayDelay: 5000,
- firstItem: 0,
- enableSnap: true,
- enableMomentum: true,
- snapOnAndroid: false,
- swipeThreshold: 20,
animationFunc: 'timing',
animationOptions: {
easing: Easing.elastic(1)
},
- slideStyle: {},
- containerCustomStyle: null,
- contentContainerCustomStyle: null,
+ autoplay: false,
+ autoplayDelay: 5000,
+ autoplayInterval: 3000,
+ containerCustomStyle: {},
+ contentContainerCustomStyle: {},
+ enableMomentum: false,
+ enableSnap: true,
+ firstItem: 0,
+ inactiveSlideOpacity: 1,
inactiveSlideScale: 0.9,
- inactiveSlideOpacity: 1
+ slideStyle: {},
+ shouldOptimizeUpdates: true,
+ snapOnAndroid: true,
+ swipeThreshold: 20
}
constructor (props) {
@@ -162,9 +153,10 @@ export default class Carousel extends Component {
}
componentWillReceiveProps (nextProps) {
- const { items, firstItem } = nextProps;
+ const { firstItem } = nextProps;
+ const { interpolators } = this.state;
- if (items.length !== this.props.items.length) {
+ if (interpolators.length !== nextProps.children.length) {
this._positions = [];
this._calcCardPositions(nextProps);
this._initInterpolators(nextProps);
@@ -189,9 +181,9 @@ export default class Carousel extends Component {
}
_calcCardPositions (props = this.props) {
- const { items, itemWidth } = props;
+ const { itemWidth } = props;
- items.forEach((item, index) => {
+ this.props.children.map((item, index) => {
this._positions[index] = {
start: index * itemWidth
};
@@ -200,10 +192,10 @@ export default class Carousel extends Component {
}
_initInterpolators (props = this.props) {
- const { items, firstItem } = props;
+ const { firstItem } = props;
let interpolators = [];
- items.forEach((item, index) => {
+ this.props.children.map((item, index) => {
interpolators.push(new Animated.Value(index === firstItem ? 1 : 0));
});
this.setState({ interpolators });
@@ -336,37 +328,6 @@ export default class Carousel extends Component {
}
}
- get items () {
- const { items, renderItem, slideStyle, inactiveSlideScale, inactiveSlideOpacity } = this.props;
- if (!this.state.interpolators || !this.state.interpolators.length) {
- return false;
- }
-
- return items.map((entry, index) => {
- const animatedValue = this.state.interpolators[index];
- return (
-
- { renderItem(entry, index) }
-
- );
- });
- }
-
get currentIndex () {
return this.state.activeItem;
}
@@ -410,7 +371,7 @@ export default class Carousel extends Component {
// Make sure the component hasn't been unmounted
if (this.refs.scrollview) {
this.refs.scrollview.scrollTo({x: snapX, y: 0, animated});
- this.props.onSnapToItem && fireCallback && this.props.onSnapToItem(index, this.props.items[index]);
+ this.props.onSnapToItem && fireCallback && this.props.onSnapToItem(index);
// iOS fix, check the note in the constructor
if (!initial && Platform.OS === 'ios') {
@@ -439,6 +400,42 @@ export default class Carousel extends Component {
this.snapToItem(newIndex, animated);
}
+ _children (children = this.props.children) {
+ return React.Children.map(children, (child) => child);
+ }
+
+ _childSlides () {
+ const { slideStyle, inactiveSlideScale, inactiveSlideOpacity } = this.props;
+
+ if (!this.state.interpolators || !this.state.interpolators.length) {
+ return false;
+ };
+
+ return this._children().map((child, index) => {
+ const animatedValue = this.state.interpolators[index];
+
+ if (!animatedValue) {
+ return false;
+ }
+
+ return (
+
+ { child }
+
+ );
+ });
+ }
+
render () {
const { sliderWidth, itemWidth, containerCustomStyle, contentContainerCustomStyle, enableMomentum } = this.props;
@@ -468,7 +465,7 @@ export default class Carousel extends Component {
scrollEventThrottle={50}
{...this.props}
>
- { this.items }
+ { this._childSlides() }
);
}
diff --git a/package.json b/package.json
index 769abf9ec..7a5d06946 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "react-native-snap-carousel",
- "version": "1.6.1",
- "description": "Simple carousel component with snapping effect on Android & iOS for React Native",
+ "version": "2.0.0",
+ "description": "Swiper component for React Native with previews and snapping effect. Compatible with Android & iOS.",
"main": "index.js",
"repository": {
"type": "git",