react-bootstrap
Version:
Bootstrap 3 components build with React
289 lines (243 loc) • 7.27 kB
JSX
var React = require('react');
var joinClasses = require('./utils/joinClasses');
var classSet = require('./utils/classSet');
var cloneWithProps = require('./utils/cloneWithProps');
var BootstrapMixin = require('./BootstrapMixin');
var ValidComponentChildren = require('./utils/ValidComponentChildren');
var Carousel = React.createClass({
mixins: [BootstrapMixin],
propTypes: {
slide: React.PropTypes.bool,
indicators: React.PropTypes.bool,
controls: React.PropTypes.bool,
pauseOnHover: React.PropTypes.bool,
wrap: React.PropTypes.bool,
onSelect: React.PropTypes.func,
onSlideEnd: React.PropTypes.func,
activeIndex: React.PropTypes.number,
defaultActiveIndex: React.PropTypes.number,
direction: React.PropTypes.oneOf(['prev', 'next'])
},
getDefaultProps: function () {
return {
slide: true,
interval: 5000,
pauseOnHover: true,
wrap: true,
indicators: true,
controls: true
};
},
getInitialState: function () {
return {
activeIndex: this.props.defaultActiveIndex == null ?
0 : this.props.defaultActiveIndex,
previousActiveIndex: null,
direction: null
};
},
getDirection: function (prevIndex, index) {
if (prevIndex === index) {
return null;
}
return prevIndex > index ?
'prev' : 'next';
},
componentWillReceiveProps: function (nextProps) {
var activeIndex = this.getActiveIndex();
if (nextProps.activeIndex != null && nextProps.activeIndex !== activeIndex) {
clearTimeout(this.timeout);
this.setState({
previousActiveIndex: activeIndex,
direction: nextProps.direction != null ?
nextProps.direction : this.getDirection(activeIndex, nextProps.activeIndex)
});
}
},
componentDidMount: function () {
this.waitForNext();
},
componentWillUnmount: function() {
clearTimeout(this.timeout);
},
next: function (e) {
if (e) {
e.preventDefault();
}
var index = this.getActiveIndex() + 1;
var count = ValidComponentChildren.numberOf(this.props.children);
if (index > count - 1) {
if (!this.props.wrap) {
return;
}
index = 0;
}
this.handleSelect(index, 'next');
},
prev: function (e) {
if (e) {
e.preventDefault();
}
var index = this.getActiveIndex() - 1;
if (index < 0) {
if (!this.props.wrap) {
return;
}
index = ValidComponentChildren.numberOf(this.props.children) - 1;
}
this.handleSelect(index, 'prev');
},
pause: function () {
this.isPaused = true;
clearTimeout(this.timeout);
},
play: function () {
this.isPaused = false;
this.waitForNext();
},
waitForNext: function () {
if (!this.isPaused && this.props.slide && this.props.interval &&
this.props.activeIndex == null) {
this.timeout = setTimeout(this.next, this.props.interval);
}
},
handleMouseOver: function () {
if (this.props.pauseOnHover) {
this.pause();
}
},
handleMouseOut: function () {
if (this.isPaused) {
this.play();
}
},
render: function () {
var classes = {
carousel: true,
slide: this.props.slide
};
return (
<div
{...this.props}
className={joinClasses(this.props.className, classSet(classes))}
onMouseOver={this.handleMouseOver}
onMouseOut={this.handleMouseOut}>
{this.props.indicators ? this.renderIndicators() : null}
<div className="carousel-inner" ref="inner">
{ValidComponentChildren.map(this.props.children, this.renderItem)}
</div>
{this.props.controls ? this.renderControls() : null}
</div>
);
},
renderPrev: function () {
return (
<a className="left carousel-control" href="#prev" key={0} onClick={this.prev}>
<span className="glyphicon glyphicon-chevron-left" />
</a>
);
},
renderNext: function () {
return (
<a className="right carousel-control" href="#next" key={1} onClick={this.next}>
<span className="glyphicon glyphicon-chevron-right"/>
</a>
);
},
renderControls: function () {
if (this.props.wrap) {
var activeIndex = this.getActiveIndex();
var count = ValidComponentChildren.numberOf(this.props.children);
return [
(activeIndex !== 0) ? this.renderPrev() : null,
(activeIndex !== count - 1) ? this.renderNext() : null
];
}
return [
this.renderPrev(),
this.renderNext()
];
},
renderIndicator: function (child, index) {
var className = (index === this.getActiveIndex()) ?
'active' : null;
return (
<li
key={index}
className={className}
onClick={this.handleSelect.bind(this, index, null)} />
);
},
renderIndicators: function () {
var indicators = [];
ValidComponentChildren
.forEach(this.props.children, function(child, index) {
indicators.push(
this.renderIndicator(child, index),
// Force whitespace between indicator elements, bootstrap
// requires this for correct spacing of elements.
' '
);
}, this);
return (
<ol className="carousel-indicators">
{indicators}
</ol>
);
},
getActiveIndex: function () {
return this.props.activeIndex != null ? this.props.activeIndex : this.state.activeIndex;
},
handleItemAnimateOutEnd: function () {
this.setState({
previousActiveIndex: null,
direction: null
}, function() {
this.waitForNext();
if (this.props.onSlideEnd) {
this.props.onSlideEnd();
}
});
},
renderItem: function (child, index) {
var activeIndex = this.getActiveIndex();
var isActive = (index === activeIndex);
var isPreviousActive = this.state.previousActiveIndex != null &&
this.state.previousActiveIndex === index && this.props.slide;
return cloneWithProps(
child,
{
active: isActive,
ref: child.ref,
key: child.key ? child.key : index,
index: index,
animateOut: isPreviousActive,
animateIn: isActive && this.state.previousActiveIndex != null && this.props.slide,
direction: this.state.direction,
onAnimateOutEnd: isPreviousActive ? this.handleItemAnimateOutEnd: null
}
);
},
handleSelect: function (index, direction) {
clearTimeout(this.timeout);
var previousActiveIndex = this.getActiveIndex();
direction = direction || this.getDirection(previousActiveIndex, index);
if (this.props.onSelect) {
this.props.onSelect(index, direction);
}
if (this.props.activeIndex == null && index !== previousActiveIndex) {
if (this.state.previousActiveIndex != null) {
// If currently animating don't activate the new index.
// TODO: look into queuing this canceled call and
// animating after the current animation has ended.
return;
}
this.setState({
activeIndex: index,
previousActiveIndex: previousActiveIndex,
direction: direction
});
}
}
});
module.exports = Carousel;