From 1acb289201eccb6f6fdecafb66cc28c4307a3803 Mon Sep 17 00:00:00 2001 From: Vlad Bereznyuk Date: Sat, 7 Jul 2018 17:37:22 +0500 Subject: [PATCH 1/7] feature/carousel Added carousel component --- src/components/Plugins/Carousel/Carousel.js | 122 ++++++++++++++++++++ src/components/Plugins/Carousel/index.js | 4 + 2 files changed, 126 insertions(+) create mode 100644 src/components/Plugins/Carousel/Carousel.js create mode 100644 src/components/Plugins/Carousel/index.js diff --git a/src/components/Plugins/Carousel/Carousel.js b/src/components/Plugins/Carousel/Carousel.js new file mode 100644 index 0000000..7147900 --- /dev/null +++ b/src/components/Plugins/Carousel/Carousel.js @@ -0,0 +1,122 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +export default class Carousel extends Component { + static propTypes = { + title: PropTypes.string, + resizeDebounce: PropTypes.number, + duration: PropTypes.number, + perPage: PropTypes.number, + loop: PropTypes.bool, + children: PropTypes.oneOfType([ + PropTypes.element, + PropTypes.node + ]), + }; + + events = [ + 'onTouchStart', + 'onTouchEnd', + 'onTouchMove', + // 'onMouseDown', + // 'onMouseUp', + // 'onMouseLeave', + // 'onMouseMove', + ]; + + constructor(props) { + super(props); + this.config = Object.assign( + {}, + { + resizeDebounce: 250, + startIndex: 0, + duration: 200, + perPage: 1, + loop: false, + }, + props + ); + + this.events.forEach(handler => { + this[handler] = this[handler].bind(this); + }); + } + + componentDidMount() { + this.config.selector = this.selector; + this.currentSlide = this.config.startIndex; + + this.init(); + } + + setStyle(target, styles) { + Object.keys(styles).forEach((attribute) => { + target.style[attribute] = styles[attribute]; + }); + } + + setSelectorWidth() { + this.selectorWidth = this.selector.getBoundingClientRect().width; + } + + setInnerElements() { + // This one converts NodeList into Array + this.innerElements = [].slice.call(this.sliderFrame.children); + } + + resolveSlidesNumber() { + this.perPage = this.config.perPage; + } + + slideToCurrent() { + this.sliderFrame.style.transform = `translate3d(-${Math.round(this.currentSlide * (this.selectorWidth / this.perPage))}px, 0, 0)`; + //this.sliderFrame.style.transform = `translateX(-${(100 / this.innerElements.length) * this.perPage}%)`; + } + + init() { + this.setSelectorWidth(); + this.setInnerElements(); + this.resolveSlidesNumber(); + + // set width & transition to the outer div of elements + this.setStyle(this.sliderFrame, { + width: `${(this.selectorWidth / this.perPage) * this.innerElements.length}px`, + webkitTransition: `all ${this.config.duration}ms ${this.config.easing}`, + transition: `all ${this.config.duration}ms ${this.config.easing}`, + }); + + // set width to each slide based a number of slides + for (let i = 0; i < this.innerElements.length; i++) { + this.setStyle(this.innerElements[i], { + width: `${100 / this.innerElements.length}%` + }); + } + + this.slideToCurrent(); + + } + + onTouchStart(e) {}; + onTouchEnd(e) {}; + onTouchMove(e) {}; + + render() { + return ( +
(this.selector = selector)} + style={{ overflow: 'hidden' }} + {...this.events.reduce((props, event) => Object.assign({}, props, { [event]: this[event] }), {})} + > +
(this.sliderFrame = sliderFrame)}> + {React.Children.map(this.props.children, (children, index) => + React.cloneElement(children, { + key: index, + style: { float: 'left' }, + }) + )} +
+
+ ); + } +} diff --git a/src/components/Plugins/Carousel/index.js b/src/components/Plugins/Carousel/index.js new file mode 100644 index 0000000..5c22015 --- /dev/null +++ b/src/components/Plugins/Carousel/index.js @@ -0,0 +1,4 @@ +import React from 'react'; +import Carousel from './Carousel'; + +export default Carousel; From 6ff02411cf8a1c23cb4231a0d4ba8c26ab0f361e Mon Sep 17 00:00:00 2001 From: Vlad Bereznyuk Date: Sat, 7 Jul 2018 22:03:24 +0500 Subject: [PATCH 2/7] feature/carousel next prev buttons added --- src/components/Plugins/Carousel/Carousel.js | 41 ++++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/components/Plugins/Carousel/Carousel.js b/src/components/Plugins/Carousel/Carousel.js index 7147900..52c1b22 100644 --- a/src/components/Plugins/Carousel/Carousel.js +++ b/src/components/Plugins/Carousel/Carousel.js @@ -11,7 +11,9 @@ export default class Carousel extends Component { children: PropTypes.oneOfType([ PropTypes.element, PropTypes.node - ]), + ]), + hideNextButton: PropTypes.bool, + hidePrevButton: PropTypes.bool, }; events = [ @@ -38,9 +40,17 @@ export default class Carousel extends Component { props ); + this.state = { + hideNextButton: false, + hidePrevButton: false, + }; + this.events.forEach(handler => { this[handler] = this[handler].bind(this); }); + + this.next = this.next.bind(this); + this.prev = this.prev.bind(this); } componentDidMount() { @@ -52,7 +62,7 @@ export default class Carousel extends Component { setStyle(target, styles) { Object.keys(styles).forEach((attribute) => { - target.style[attribute] = styles[attribute]; + target.style[attribute] = styles[attribute]; }); } @@ -61,7 +71,6 @@ export default class Carousel extends Component { } setInnerElements() { - // This one converts NodeList into Array this.innerElements = [].slice.call(this.sliderFrame.children); } @@ -78,7 +87,7 @@ export default class Carousel extends Component { this.setSelectorWidth(); this.setInnerElements(); this.resolveSlidesNumber(); - + // set width & transition to the outer div of elements this.setStyle(this.sliderFrame, { width: `${(this.selectorWidth / this.perPage) * this.innerElements.length}px`, @@ -89,12 +98,29 @@ export default class Carousel extends Component { // set width to each slide based a number of slides for (let i = 0; i < this.innerElements.length; i++) { this.setStyle(this.innerElements[i], { - width: `${100 / this.innerElements.length}%` + width: `${100 / this.innerElements.length}%`, }); } this.slideToCurrent(); - + } + + next() { + if (this.currentSlide === this.innerElements.length - this.perPage && this.config.loop) { + this.currentSlide = 0; + } else { + this.currentSlide = Math.min(this.currentSlide + 1, this.innerElements.length - this.perPage); + } + this.slideToCurrent(); + } + + prev() { + if (this.currentSlide === 0 && this.config.loop) { + this.currentSlide = this.innerElements.length - this.perPage; + } else { + this.currentSlide = Math.max(this.currentSlide - 1, 0); + } + this.slideToCurrent(); } onTouchStart(e) {}; @@ -102,6 +128,7 @@ export default class Carousel extends Component { onTouchMove(e) {}; render() { + const { hideNextButton, hidePrevButton } = this.state; return (
(this.selector = selector)} @@ -116,6 +143,8 @@ export default class Carousel extends Component { }) )}
+ {!hideNextButton ? : null} + {!hidePrevButton ? : null} ); } From 0b86a0985c9e4738fb0a3064e858845e248fc0c8 Mon Sep 17 00:00:00 2001 From: Vlad Bereznyuk Date: Sun, 8 Jul 2018 12:56:32 +0500 Subject: [PATCH 3/7] feature/carousel slides per page --- src/components/Plugins/Carousel/Carousel.js | 22 ++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/Plugins/Carousel/Carousel.js b/src/components/Plugins/Carousel/Carousel.js index 52c1b22..0296b2c 100644 --- a/src/components/Plugins/Carousel/Carousel.js +++ b/src/components/Plugins/Carousel/Carousel.js @@ -60,6 +60,10 @@ export default class Carousel extends Component { this.init(); } + componentDidUpdate() { + this.init(); + } + setStyle(target, styles) { Object.keys(styles).forEach((attribute) => { target.style[attribute] = styles[attribute]; @@ -80,17 +84,21 @@ export default class Carousel extends Component { slideToCurrent() { this.sliderFrame.style.transform = `translate3d(-${Math.round(this.currentSlide * (this.selectorWidth / this.perPage))}px, 0, 0)`; - //this.sliderFrame.style.transform = `translateX(-${(100 / this.innerElements.length) * this.perPage}%)`; } init() { this.setSelectorWidth(); this.setInnerElements(); this.resolveSlidesNumber(); - + // set width & transition to the outer div of elements + const widthItem = this.selectorWidth / this.perPage; + const itemsToBuild = this.config.loop + ? this.innerElements.length + this.perPage * 2 + : this.innerElements.length; + this.setStyle(this.sliderFrame, { - width: `${(this.selectorWidth / this.perPage) * this.innerElements.length}px`, + width: `${widthItem * itemsToBuild}px`, webkitTransition: `all ${this.config.duration}ms ${this.config.easing}`, transition: `all ${this.config.duration}ms ${this.config.easing}`, }); @@ -98,7 +106,12 @@ export default class Carousel extends Component { // set width to each slide based a number of slides for (let i = 0; i < this.innerElements.length; i++) { this.setStyle(this.innerElements[i], { - width: `${100 / this.innerElements.length}%`, + width: `${ + this.config.loop + ? 100 / (this.innerElements.length + this.perPage * 2) + : 100 / this.innerElements.length + }%`, + float: 'left', }); } @@ -139,7 +152,6 @@ export default class Carousel extends Component { {React.Children.map(this.props.children, (children, index) => React.cloneElement(children, { key: index, - style: { float: 'left' }, }) )} From c1d485e041d89d7360e0c1ad1aa50ecde3838701 Mon Sep 17 00:00:00 2001 From: Vlad Bereznyuk Date: Sun, 8 Jul 2018 20:42:29 +0500 Subject: [PATCH 4/7] feature/carousel slides to show, slides to scroll added --- src/components/Plugins/Carousel/Carousel.js | 83 ++++++++++++--------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/src/components/Plugins/Carousel/Carousel.js b/src/components/Plugins/Carousel/Carousel.js index 0296b2c..6ac7612 100644 --- a/src/components/Plugins/Carousel/Carousel.js +++ b/src/components/Plugins/Carousel/Carousel.js @@ -6,12 +6,14 @@ export default class Carousel extends Component { title: PropTypes.string, resizeDebounce: PropTypes.number, duration: PropTypes.number, - perPage: PropTypes.number, + easing: PropTypes.string, + slidesToShow: PropTypes.number, loop: PropTypes.bool, children: PropTypes.oneOfType([ PropTypes.element, PropTypes.node ]), + slidesToScroll: PropTypes.number, hideNextButton: PropTypes.bool, hidePrevButton: PropTypes.bool, }; @@ -31,10 +33,11 @@ export default class Carousel extends Component { this.config = Object.assign( {}, { - resizeDebounce: 250, startIndex: 0, duration: 200, - perPage: 1, + easing: 'ease-out', + slidesToShow: 1, + slidesToScroll: 1, loop: false, }, props @@ -48,9 +51,6 @@ export default class Carousel extends Component { this.events.forEach(handler => { this[handler] = this[handler].bind(this); }); - - this.next = this.next.bind(this); - this.prev = this.prev.bind(this); } componentDidMount() { @@ -65,7 +65,7 @@ export default class Carousel extends Component { } setStyle(target, styles) { - Object.keys(styles).forEach((attribute) => { + Object.keys(styles).forEach(attribute => { target.style[attribute] = styles[attribute]; }); } @@ -79,59 +79,64 @@ export default class Carousel extends Component { } resolveSlidesNumber() { - this.perPage = this.config.perPage; + this.slidesToShow = this.config.slidesToShow; } slideToCurrent() { - this.sliderFrame.style.transform = `translate3d(-${Math.round(this.currentSlide * (this.selectorWidth / this.perPage))}px, 0, 0)`; + this.sliderFrame.style.transform = `translate3d(-${Math.round( + this.currentSlide * (this.selectorWidth / this.slidesToShow) + )}px, 0, 0)`; } init() { this.setSelectorWidth(); this.setInnerElements(); this.resolveSlidesNumber(); - // set width & transition to the outer div of elements - const widthItem = this.selectorWidth / this.perPage; + const widthItem = this.selectorWidth / this.slidesToShow; const itemsToBuild = this.config.loop - ? this.innerElements.length + this.perPage * 2 + ? this.innerElements.length + this.slidesToShow * 2 : this.innerElements.length; - - this.setStyle(this.sliderFrame, { - width: `${widthItem * itemsToBuild}px`, - webkitTransition: `all ${this.config.duration}ms ${this.config.easing}`, - transition: `all ${this.config.duration}ms ${this.config.easing}`, - }); - - // set width to each slide based a number of slides + this.sliderFrame.style.width = `${widthItem * itemsToBuild}px`; + this.enableTransition(); + // set width to each slide based on a number of slides + // and if loop is enabled or not for (let i = 0; i < this.innerElements.length; i++) { this.setStyle(this.innerElements[i], { - width: `${ - this.config.loop - ? 100 / (this.innerElements.length + this.perPage * 2) - : 100 / this.innerElements.length - }%`, + width: `${100 / itemsToBuild}%`, float: 'left', }); } - this.slideToCurrent(); } - next() { - if (this.currentSlide === this.innerElements.length - this.perPage && this.config.loop) { + disableTransition() { + this.sliderFrame.style.webkitTransition = `all 0ms ${this.config.easing}`; + this.sliderFrame.style.transition = `all 0ms ${this.config.easing}`; + } + + enableTransition() { + this.sliderFrame.style.webkitTransition = `all ${this.config.duration}ms ${this.config.easing}`; + this.sliderFrame.style.transition = `all ${this.config.duration}ms ${this.config.easing}`; + } + + next(slidesToScroll = 1) { + if (this.currentSlide === this.innerElements.length - this.slidesToShow && this.config.loop) { this.currentSlide = 0; } else { - this.currentSlide = Math.min(this.currentSlide + 1, this.innerElements.length - this.perPage); + this.currentSlide = Math.min( + this.currentSlide + slidesToScroll, + this.innerElements.length - this.slidesToShow + ); } this.slideToCurrent(); } - prev() { + prev(slidesToScroll = 1) { if (this.currentSlide === 0 && this.config.loop) { - this.currentSlide = this.innerElements.length - this.perPage; + this.currentSlide = this.innerElements.length - this.slidesToShow; } else { - this.currentSlide = Math.max(this.currentSlide - 1, 0); + this.currentSlide = Math.max(this.currentSlide - slidesToScroll, 0); } this.slideToCurrent(); } @@ -142,21 +147,25 @@ export default class Carousel extends Component { render() { const { hideNextButton, hidePrevButton } = this.state; + const { slidesToScroll } = this.config; return (
(this.selector = selector)} style={{ overflow: 'hidden' }} - {...this.events.reduce((props, event) => Object.assign({}, props, { [event]: this[event] }), {})} + {...this.events.reduce( + (props, event) => Object.assign({}, props, { [event]: this[event] }), + {} + )} >
(this.sliderFrame = sliderFrame)}> - {React.Children.map(this.props.children, (children, index) => - React.cloneElement(children, { + {React.Children.map(this.props.children, (child, index) => + React.cloneElement(child, { key: index, }) )}
- {!hideNextButton ? : null} - {!hidePrevButton ? : null} + {!hideNextButton ? : null} + {!hidePrevButton ? : null}
); } From b00a0b3e57a343d87337101bf35384ddf23c8534 Mon Sep 17 00:00:00 2001 From: Vlad Bereznyuk Date: Mon, 9 Jul 2018 13:15:28 +0500 Subject: [PATCH 5/7] feature/carousel moved carousel to shared folder --- src/components/{Plugins => Shared}/Carousel/Carousel.js | 0 src/components/{Plugins => Shared}/Carousel/index.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/components/{Plugins => Shared}/Carousel/Carousel.js (100%) rename src/components/{Plugins => Shared}/Carousel/index.js (100%) diff --git a/src/components/Plugins/Carousel/Carousel.js b/src/components/Shared/Carousel/Carousel.js similarity index 100% rename from src/components/Plugins/Carousel/Carousel.js rename to src/components/Shared/Carousel/Carousel.js diff --git a/src/components/Plugins/Carousel/index.js b/src/components/Shared/Carousel/index.js similarity index 100% rename from src/components/Plugins/Carousel/index.js rename to src/components/Shared/Carousel/index.js From 196f2da0b1ce6856756e633957eafd01985c301e Mon Sep 17 00:00:00 2001 From: Vlad Bereznyuk Date: Mon, 9 Jul 2018 18:37:14 +0500 Subject: [PATCH 6/7] feature/carousel resize added that supports slidesToShow object config --- src/components/Shared/Carousel/Carousel.js | 59 +++++++++++++++++++--- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/components/Shared/Carousel/Carousel.js b/src/components/Shared/Carousel/Carousel.js index 6ac7612..a521b43 100644 --- a/src/components/Shared/Carousel/Carousel.js +++ b/src/components/Shared/Carousel/Carousel.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import debounce from 'lodash/debounce'; export default class Carousel extends Component { static propTypes = { @@ -7,13 +8,16 @@ export default class Carousel extends Component { resizeDebounce: PropTypes.number, duration: PropTypes.number, easing: PropTypes.string, - slidesToShow: PropTypes.number, + slidesToShow: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.number + ]), + slidesToScroll: PropTypes.number, loop: PropTypes.bool, children: PropTypes.oneOfType([ PropTypes.element, PropTypes.node ]), - slidesToScroll: PropTypes.number, hideNextButton: PropTypes.bool, hidePrevButton: PropTypes.bool, }; @@ -39,6 +43,7 @@ export default class Carousel extends Component { slidesToShow: 1, slidesToScroll: 1, loop: false, + resizeDebounce: 250, }, props ); @@ -58,12 +63,25 @@ export default class Carousel extends Component { this.currentSlide = this.config.startIndex; this.init(); + + this.onResize = debounce(() => { + this.resize(); + this.slideToCurrent(); + // onresize follow slidesToShow value + this.config.slidesToScroll = this.slidesToShow; + }, this.config.resizeDebounce); + + window.addEventListener('resize', this.onResize); } componentDidUpdate() { this.init(); } + componentWillUnmount() { + window.removeEventListener('resize', this.onResize); + } + setStyle(target, styles) { Object.keys(styles).forEach(attribute => { target.style[attribute] = styles[attribute]; @@ -79,7 +97,33 @@ export default class Carousel extends Component { } resolveSlidesNumber() { - this.slidesToShow = this.config.slidesToShow; + const { slidesToShow } = this.config; + if (typeof slidesToShow === 'number') { + this.slidesToShow = slidesToShow; + } else if (typeof slidesToShow === 'object') { + this.slidesToShow = 1; + Object.keys(slidesToShow).forEach(viewport => { + if (window.innerWidth > viewport) { + this.slidesToShow = slidesToShow[viewport]; + } + }); + } + } + + resize() { + this.resolveSlidesNumber(); + this.selectorWidth = this.selector.getBoundingClientRect().width; + + this.setStyle(this.sliderFrame, { + width: `${(this.selectorWidth / this.slidesToShow) * this.innerElements.length}px`, + }); + + for (let i = 0; i < this.innerElements.length; i++) { + this.setStyle(this.innerElements[i], { + width: `${100 / this.innerElements.length}%`, + float: 'left', + }); + } } slideToCurrent() { @@ -107,6 +151,7 @@ export default class Carousel extends Component { float: 'left', }); } + this.slideToCurrent(); } @@ -122,6 +167,7 @@ export default class Carousel extends Component { next(slidesToScroll = 1) { if (this.currentSlide === this.innerElements.length - this.slidesToShow && this.config.loop) { + this.disableTransition(); this.currentSlide = 0; } else { this.currentSlide = Math.min( @@ -129,6 +175,7 @@ export default class Carousel extends Component { this.innerElements.length - this.slidesToShow ); } + this.slideToCurrent(); } @@ -138,6 +185,7 @@ export default class Carousel extends Component { } else { this.currentSlide = Math.max(this.currentSlide - slidesToScroll, 0); } + this.slideToCurrent(); } @@ -147,7 +195,6 @@ export default class Carousel extends Component { render() { const { hideNextButton, hidePrevButton } = this.state; - const { slidesToScroll } = this.config; return (
(this.selector = selector)} @@ -164,8 +211,8 @@ export default class Carousel extends Component { }) )}
- {!hideNextButton ? : null} - {!hidePrevButton ? : null} + {!hideNextButton ? : null} + {!hidePrevButton ? : null} ); } From b524144b4c1a4ded0273cacfbf7217dcb241e920 Mon Sep 17 00:00:00 2001 From: Vlad Bereznyuk Date: Mon, 9 Jul 2018 18:40:11 +0500 Subject: [PATCH 7/7] feature/carousel lodash debounce added to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b307fe5..9dadac2 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "axios": "^0.18.0", + "lodash": "^4.17.10", "prop-types": "^15.6.2", "react": "^16.4.1", "react-dom": "^16.4.1",