diff --git a/package.json b/package.json
index ca8beb4..7967590 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"gatsby-source-wordpress": "^2.0.93",
"gatsby-transformer-sharp": "^1.6.27",
"react-helmet": "^5.2.0",
- "react-leaf-carousel": "^1.1.1",
+ "react-responsive-mixin": "^0.4.0",
"slideout": "^1.0.1"
},
"keywords": [
diff --git a/src/components/Carousel/InfiniteCarousel.css b/src/components/Carousel/InfiniteCarousel.css
new file mode 100644
index 0000000..b8e707b
--- /dev/null
+++ b/src/components/Carousel/InfiniteCarousel.css
@@ -0,0 +1,91 @@
+.InfiniteCarousel {
+ position: relative;
+}
+
+.InfiniteCarouselFrame {
+ width: 100%;
+ overflow: hidden;
+}
+
+.InfiniteCarouselScrollTrack {
+ overflow-x: scroll;
+ overflow-y: hidden;
+ white-space: nowrap;
+ -webkit-overflow-scrolling: touch;
+ overflow: -moz-scrollbars-none;
+ -webkit-box-sizing: border-box;
+}
+
+.InfiniteCarouselScrollTrack::-webkit-scrollbar {
+ display: none;
+}
+
+.InfiniteCarouselSlide img {
+ width: 100%;
+}
+
+.InfiniteCarouselDots {
+ position: absolute;
+ left: 50%;
+ bottom: 0;
+ padding: 0;
+ transform: translateX(-50%);
+}
+
+.InfiniteCarouselDot {
+ display: inline-block;
+ list-style: none;
+ margin: 0 5px;
+ border: 0;
+ background: none;
+ cursor: pointer;
+}
+
+.InfiniteCarouselDotIcon {
+ display: block;
+ background-color: #e5e5e5;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+}
+
+.InfiniteCarouselDotActiveIcon {
+ background-color: #48799a;
+}
+
+.InfiniteCarouselArrow {
+ display: block;
+ background: none;
+ border: none;
+ position: absolute;
+ top: 50%;
+ z-index: 2;
+ outline: none;
+ transform: translateY(-50%);
+ cursor: pointer;
+}
+
+.InfiniteCarouselArrowPrev {
+ left: 15px;
+ right: auto;
+}
+
+.InfiniteCarouselArrowNext {
+ left: auto;
+ right: 15px;
+}
+
+.InfiniteCarouselArrowIcon {
+ display: inline-block;
+ padding: 10px;
+ border: solid #e5e5e5;
+ border-width: 0 5px 5px 0;
+}
+
+.InfiniteCarouselArrowNextIcon {
+ transform: rotate(-45deg);
+}
+
+.InfiniteCarouselArrowPrevIcon {
+ transform: rotate(135deg);
+}
diff --git a/src/components/Carousel/InfiniteCarouselArrow.js b/src/components/Carousel/InfiniteCarouselArrow.js
new file mode 100644
index 0000000..8b5d534
--- /dev/null
+++ b/src/components/Carousel/InfiniteCarouselArrow.js
@@ -0,0 +1,41 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+function InfiniteCarouselArrow({ next, onClick, styles }) {
+ const arrowClassName = styles.InfiniteCarouselArrow
+ let typeClassName
+ if (next) {
+ typeClassName = styles.InfiniteCarouselArrowNext
+ } else {
+ typeClassName = styles.InfiniteCarouselArrowPrev
+ }
+
+ const iconClassName = styles.InfiniteCarouselArrowIcon
+ let iconTypeClassName
+ if (next) {
+ iconTypeClassName = styles.InfiniteCarouselArrowNextIcon
+ } else {
+ iconTypeClassName = styles.InfiniteCarouselArrowPrevIcon
+ }
+
+ const className = `${arrowClassName} ${typeClassName}`
+ const classNameIcon = `${iconClassName} ${iconTypeClassName}`
+
+ return (
+
+
+
+ )
+}
+
+InfiniteCarouselArrow.propTypes = {
+ next: PropTypes.bool,
+ onClick: PropTypes.func.isRequired,
+ styles: PropTypes.object.isRequired,
+}
+
+InfiniteCarouselArrow.defaultProps = {
+ next: true,
+}
+
+export default InfiniteCarouselArrow
diff --git a/src/components/Carousel/InfiniteCarouselDots.js b/src/components/Carousel/InfiniteCarouselDots.js
new file mode 100644
index 0000000..5a51c94
--- /dev/null
+++ b/src/components/Carousel/InfiniteCarouselDots.js
@@ -0,0 +1,36 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+function InfiniteCarouselDots({ numberOfDots, activePage, onClick, styles }) {
+ const dots = []
+ const className = styles.InfiniteCarouselDots
+ const dotClassName = styles.InfiniteCarouselDot
+ const dotIconClassName = styles.InfiniteCarouselDotIcon
+ const activeClass = styles.InfiniteCarouselDotActiveIcon
+ let classNameIcon
+
+ for (let i = 0; i < numberOfDots; i += 1) {
+ classNameIcon = `${dotIconClassName} ${i === activePage ? activeClass : ''}`
+ dots.push(
+
+
+
+ ) // eslint-disable-line react/jsx-closing-tag-location
+ }
+
+ return
+}
+
+InfiniteCarouselDots.propTypes = {
+ numberOfDots: PropTypes.number.isRequired,
+ activePage: PropTypes.number.isRequired,
+ onClick: PropTypes.func.isRequired,
+ styles: PropTypes.object.isRequired,
+}
+
+export default InfiniteCarouselDots
diff --git a/src/components/Carousel/helpers.js b/src/components/Carousel/helpers.js
new file mode 100644
index 0000000..7e39128
--- /dev/null
+++ b/src/components/Carousel/helpers.js
@@ -0,0 +1,44 @@
+export function getElementWidth(elem) {
+ return elem.getBoundingClientRect().width || elem.offsetWidth || 0
+}
+
+export function getElementHeight(elem) {
+ return elem.getBoundingClientRect().height || elem.offsetHeight || 0
+}
+
+export function getSwipeDirection(x1, x2, y1, y2) {
+ const xDist = x1 - x2
+ const yDist = y1 - y2
+
+ let swipeAngle = Math.round((Math.atan2(yDist, xDist) * 180) / Math.PI)
+
+ if (swipeAngle < 0) {
+ swipeAngle = 360 - Math.abs(swipeAngle)
+ }
+ if (swipeAngle <= 45 && swipeAngle >= 0) {
+ return 1
+ }
+ if (swipeAngle <= 360 && swipeAngle >= 315) {
+ return 1
+ }
+ if (swipeAngle >= 135 && swipeAngle <= 225) {
+ return -1
+ }
+ return 0
+}
+
+export function isTouchDevice() {
+ return 'ontouchstart' in document.documentElement
+}
+
+export function sortNumber(a, b) {
+ return a - b
+}
+
+export function getScreenWidth() {
+ return (
+ window.innerWidth ||
+ document.documentElement.clientWidth ||
+ document.body.clientWidth
+ )
+}
diff --git a/src/components/Carousel/index.js b/src/components/Carousel/index.js
new file mode 100644
index 0000000..2c6c2a0
--- /dev/null
+++ b/src/components/Carousel/index.js
@@ -0,0 +1,1012 @@
+import React, { Component, Children } from 'react'
+import PropTypes from 'prop-types'
+import { media } from 'react-responsive-mixin'
+import {
+ getElementWidth,
+ getSwipeDirection,
+ isTouchDevice,
+ sortNumber,
+ getScreenWidth,
+} from './helpers'
+import InfiniteCarouselArrow from './InfiniteCarouselArrow'
+import InfiniteCarouselDots from './InfiniteCarouselDots'
+import styles from './InfiniteCarousel.css'
+
+class InfiniteCarousel extends Component {
+ static propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]),
+ arrows: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
+ dots: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
+ lazyLoad: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
+ swipe: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
+ draggable: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
+ animationDuration: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
+ slidesToShow: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
+ slidesToScroll: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
+ slidesSpacing: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
+ autoCycle: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
+ cycleInterval: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
+ pauseOnHover: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
+ responsive: PropTypes.bool,
+ breakpoints: PropTypes.arrayOf(PropTypes.object),
+ placeholderImageSrc: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
+ nextArrow: PropTypes.element, // eslint-disable-line react/no-unused-prop-types
+ prevArrow: PropTypes.element, // eslint-disable-line react/no-unused-prop-types
+ scrollOnDevice: PropTypes.bool,
+ showSides: PropTypes.bool,
+ sidesOpacity: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
+ sideSize: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
+ incrementalSides: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
+ }
+ static defaultProps = {
+ children: [],
+ arrows: true,
+ dots: false,
+ lazyLoad: false,
+ swipe: true,
+ draggable: false,
+ animationDuration: 500,
+ slidesToShow: 1,
+ slidesToScroll: 1,
+ slidesSpacing: 10,
+ autoCycle: false,
+ cycleInterval: 5000,
+ pauseOnHover: true,
+ responsive: true,
+ breakpoints: [],
+ placeholderImageSrc: '',
+ nextArrow: null,
+ prevArrow: null,
+ scrollOnDevice: false,
+ showSides: false,
+ sidesOpacity: 1,
+ sideSize: 0.5,
+ incrementalSides: false,
+ }
+
+ constructor(props) {
+ super(props)
+
+ // initial state
+ this.state = {
+ currentIndex: 0,
+ activePage: 0,
+ children: [],
+ lazyLoadedList: [],
+ visibleSlideList: [],
+ childrenCount: 0,
+ slidesCount: 0,
+ slidesWidth: 1,
+ slidePages: 1,
+ singlePage: true,
+ settings: {},
+ autoCycleTimer: null,
+ dragging: false,
+ touchObject: {
+ startX: 0,
+ startY: 0,
+ endX: 0,
+ endY: 0,
+ length: 0,
+ direction: -1,
+ },
+ scrollOnDeviceProps: {
+ arrows: false,
+ dots: false,
+ lazyLoad: false,
+ autoCycle: false,
+ },
+ lowerBreakpoint: undefined,
+ higherBreakpoint: undefined,
+ }
+ }
+
+ componentWillMount() {
+ this.init()
+ }
+
+ componentDidMount() {
+ this.setDimensions()
+
+ if (!window) {
+ return
+ }
+
+ if (window.addEventListener) {
+ window.addEventListener('resize', this.onWindowResized)
+ } else {
+ window.attachEvent('onresize', this.onWindowResized)
+ }
+
+ if (this.state.settings.autoCycle) {
+ this.playAutoCycle()
+ }
+ }
+
+ componentWillUnmount() {
+ if (window.addEventListener) {
+ window.removeEventListener('resize', this.onWindowResized)
+ } else {
+ window.detachEvent('onresize', this.onWindowResized)
+ }
+ if (this.state.autoCycleTimer) {
+ clearInterval(this.state.autoCycleTimer)
+ }
+ }
+
+ setupBreakpointSettings = breakpointsSettings => {
+ const breakpoints = breakpointsSettings.map(element => element.breakpoint)
+ const settings = {}
+ breakpointsSettings.forEach(element => {
+ settings[element.breakpoint] = element.settings
+ })
+ if (breakpoints.length > 0) {
+ breakpoints.sort(sortNumber)
+ // Register responsive media queries in settings
+ breakpoints.forEach((element, index) => {
+ let lowerBreakpoint
+ let higherBreakpoint
+ if (index === 0) {
+ lowerBreakpoint = 0
+ higherBreakpoint = element - 1
+ } else {
+ lowerBreakpoint = breakpoints[index - 1]
+ higherBreakpoint = element - 1
+ }
+
+ // assign breakpoints properties
+ const query = { minWidth: lowerBreakpoint, maxWidth: higherBreakpoint }
+
+ media(query, () => {
+ const scrollOnDevice = this.props.scrollOnDevice && isTouchDevice()
+ const scrollOnDeviceProps = scrollOnDevice
+ ? this.state.scrollOnDeviceProps
+ : {}
+ const newSettings = Object.assign(
+ {},
+ this.defaultProps,
+ this.props,
+ settings[element],
+ scrollOnDeviceProps
+ )
+ const children = this.getChildrenList(
+ this.props.children,
+ newSettings.slidesToShow
+ )
+ this.setState(
+ {
+ settings: newSettings,
+ children,
+ lowerBreakpoint,
+ higherBreakpoint,
+ },
+ this.setDimensions
+ )
+ })
+ })
+
+ // Resize from small to large
+ breakpoints.reverse()
+ const query = { minWidth: breakpoints[0] }
+ media(query, () => {
+ const scrollOnDevice = this.props.scrollOnDevice && isTouchDevice()
+ const scrollOnDeviceProps = scrollOnDevice
+ ? this.state.scrollOnDeviceProps
+ : {}
+ const newSettings = Object.assign(
+ {},
+ this.defaultProps,
+ this.props,
+ scrollOnDeviceProps
+ )
+ const children = this.getChildrenList(
+ this.props.children,
+ newSettings.slidesToShow
+ )
+ this.setState(
+ {
+ settings: newSettings,
+ children,
+ lowerBreakpoint: undefined,
+ higherBreakpoint: undefined,
+ },
+ this.setDimensions
+ )
+ })
+ }
+ }
+
+ getSideSize = (lowerBreakpoint, higherBreakpoint, currentScreenWidth) => {
+ const { incrementalSides } = this.state.settings
+
+ if (
+ lowerBreakpoint !== undefined &&
+ higherBreakpoint !== undefined &&
+ incrementalSides
+ ) {
+ const maxPoint = higherBreakpoint - lowerBreakpoint
+ const currentPoint = currentScreenWidth - lowerBreakpoint
+ const sideSizePercetange = (currentPoint * 50) / maxPoint
+
+ return sideSizePercetange / 100
+ }
+
+ return this.state.settings.sideSize
+ }
+
+ setDimensions = () => {
+ const { settings, lowerBreakpoint, higherBreakpoint } = this.state
+ const scrollOnDevice = this.props.scrollOnDevice && isTouchDevice()
+ const currentScreenWidth = getScreenWidth()
+ const sideSize = this.getSideSize(
+ lowerBreakpoint,
+ higherBreakpoint,
+ currentScreenWidth
+ )
+ const childrenCount = Children.count(this.props.children)
+ const slidesCount = scrollOnDevice
+ ? childrenCount
+ : Children.count(this.state.children)
+ const frameWidth = getElementWidth(this.frame)
+ const { showSides } = this.props
+ const slidesToShow = showSides
+ ? settings.slidesToShow + sideSize * 2
+ : settings.slidesToShow
+ const slidesWidth = frameWidth / slidesToShow - settings.slidesSpacing * 2
+ const childrenLength = this.props.children.length
+ const activePage = Math.ceil(
+ this.state.currentIndex / settings.slidesToShow
+ )
+ const countPages = Math.ceil(childrenLength / settings.slidesToShow)
+ const slidePages = childrenLength > settings.slidesToShow ? countPages : 1
+ const singlePage = slidePages <= 1
+
+ let lazyLoadedList
+ let visibleSlideList
+ if (singlePage || scrollOnDevice) {
+ lazyLoadedList = this.state.children.map((child, index) => index)
+ visibleSlideList = this.state.children.map((child, index) => index)
+ } else {
+ lazyLoadedList = this.getLazyLoadedIndexes(
+ this.props.children,
+ this.state.currentIndex
+ )
+ visibleSlideList = this.getVisibleIndexes(
+ this.props.children,
+ this.state.currentIndex
+ )
+ }
+
+ this.setState({
+ activePage,
+ childrenCount,
+ slidesCount,
+ slidesWidth,
+ slidePages,
+ singlePage,
+ lazyLoadedList,
+ visibleSlideList,
+ sideSize,
+ })
+ }
+
+ getVisibleIndexes = (children, currentIndex) => {
+ const visibleIndexes = []
+ let start
+ let limit
+ const { settings } = this.state
+ const showSidesSlide = settings.showSides ? 1 : 0
+
+ start = children.length + settings.slidesToShow + showSidesSlide
+ if (currentIndex === 0) {
+ limit = start + settings.slidesToShow - 1
+ for (let index = start; index <= limit; index += 1) {
+ visibleIndexes.push(index)
+ }
+ }
+
+ start = 0 + showSidesSlide
+ const isAtLastPage =
+ currentIndex === children.length - settings.slidesToShow
+
+ if (isAtLastPage) {
+ limit = start + settings.slidesToShow - 1
+ for (let index = start; index <= limit; index += 1) {
+ visibleIndexes.push(index)
+ }
+ }
+
+ start = currentIndex + this.state.settings.slidesToShow + showSidesSlide
+ limit = start + (this.state.settings.slidesToShow - 1)
+ for (let index = start; index <= limit; index += 1) {
+ visibleIndexes.push(index)
+ }
+
+ return visibleIndexes
+ }
+
+ getLazyLoadedIndexes = (children, currentIndex) => {
+ const { lazyLoadedList } = this.state
+ let start
+ let limit
+ const { settings } = this.state
+ const showSidesSlide = settings.showSides ? 1 : 0
+
+ start = children.length + settings.slidesToShow + showSidesSlide
+ if (currentIndex === 0 && this.state.lazyLoadedList.indexOf(start) < 0) {
+ limit = start + settings.slidesToShow + showSidesSlide - 1
+ for (let index = start; index <= limit; index += 1) {
+ lazyLoadedList.push(index)
+ }
+ }
+
+ start = 0
+ const isAtLastPage =
+ currentIndex === children.length - settings.slidesToShow
+ const notLazyLoaded = lazyLoadedList.indexOf(start) < 0
+
+ if (isAtLastPage && notLazyLoaded) {
+ limit = start + settings.slidesToShow + showSidesSlide - 1
+ for (let index = start; index <= limit; index += 1) {
+ lazyLoadedList.push(index)
+ }
+ }
+
+ start = currentIndex + settings.slidesToShow + showSidesSlide
+ limit = start + (settings.slidesToShow - 1)
+
+ if (this.state.settings.showSides) {
+ start -= 1
+ limit += 1
+ }
+
+ for (let index = start; index <= limit; index += 1) {
+ if (this.state.lazyLoadedList.indexOf(index) < 0) {
+ lazyLoadedList.push(index)
+ }
+ }
+
+ return lazyLoadedList
+ }
+
+ getChildrenList = (children, slidesToShow) => {
+ if (!Array.isArray(children)) {
+ return [children]
+ }
+
+ if (this.props.scrollOnDevice && isTouchDevice()) {
+ return children
+ }
+
+ if (children.length > slidesToShow && this.props.showSides) {
+ return [
+ ...children.slice(children.length - slidesToShow - 1, children.length),
+ ...children,
+ ...children.slice(0, slidesToShow + 1),
+ ]
+ }
+
+ if (children.length > slidesToShow) {
+ return [
+ ...children.slice(children.length - slidesToShow, children.length),
+ ...children,
+ ...children.slice(0, slidesToShow),
+ ]
+ }
+
+ return children
+ }
+
+ getTargetIndex = (index, slidesToScroll) => {
+ let targetIndex = index
+ const childrenReminder = this.state.childrenCount % slidesToScroll
+ if (index < 0) {
+ if (this.state.currentIndex === 0) {
+ targetIndex = this.state.childrenCount - slidesToScroll
+ } else {
+ targetIndex = 0
+ }
+ } else if (index >= this.state.childrenCount) {
+ if (childrenReminder !== 0) {
+ targetIndex = 0
+ } else {
+ targetIndex = index - this.state.childrenCount
+ }
+ } else if (
+ childrenReminder !== 0 &&
+ index === this.state.childrenCount - childrenReminder
+ ) {
+ targetIndex = index - (slidesToScroll - childrenReminder)
+ } else {
+ targetIndex = index
+ }
+
+ return targetIndex
+ }
+
+ handleTrack = (targetIndex, currentIndex) => {
+ const { settings } = this.state
+ const activePage = Math.ceil(currentIndex / settings.slidesToShow)
+ const lazyLoadedList = this.getLazyLoadedIndexes(
+ this.props.children,
+ currentIndex
+ )
+ const visibleSlideList = this.getVisibleIndexes(
+ this.props.children,
+ currentIndex
+ )
+
+ const callback = () => {
+ setTimeout(() => {
+ this.setState({
+ currentIndex,
+ animating: false,
+ dragging: false,
+ })
+ }, settings.animationDuration)
+ }
+
+ const stopAnimation = () => {
+ setTimeout(() => {
+ this.setState({
+ animating: false,
+ dragging: false,
+ })
+ }, settings.animationDuration)
+ }
+
+ if (targetIndex < 0) {
+ this.setState(
+ {
+ currentIndex: targetIndex,
+ activePage,
+ animating: true,
+ lazyLoadedList,
+ visibleSlideList,
+ touchObject: {
+ startX: 0,
+ startY: 0,
+ endX: 0,
+ endY: 0,
+ length: 0,
+ direction: -1,
+ },
+ },
+ callback
+ )
+ } else if (targetIndex >= this.props.children.length) {
+ this.setState(
+ {
+ currentIndex: targetIndex,
+ activePage,
+ animating: true,
+ lazyLoadedList,
+ visibleSlideList,
+ touchObject: {
+ startX: 0,
+ startY: 0,
+ endX: 0,
+ endY: 0,
+ length: 0,
+ direction: -1,
+ },
+ },
+ callback
+ )
+ } else {
+ this.setState(
+ {
+ currentIndex,
+ activePage,
+ animating: true,
+ lazyLoadedList,
+ visibleSlideList,
+ dragging: false,
+ touchObject: {
+ startX: 0,
+ startY: 0,
+ endX: 0,
+ endY: 0,
+ length: 0,
+ direction: -1,
+ },
+ },
+ stopAnimation
+ )
+ }
+ }
+
+ moveToNext = event => {
+ event.preventDefault()
+ if (this.state.animating) {
+ return
+ }
+ if (this.state.settings.autoCycle && this.state.autoCycleTimer) {
+ clearInterval(this.state.autoCycleTimer)
+ this.setState({
+ autoCycleTimer: null,
+ })
+ }
+ const { settings } = this.state
+ const targetIndex = this.state.currentIndex + settings.slidesToScroll
+ const currentIndex = this.getTargetIndex(
+ targetIndex,
+ settings.slidesToScroll
+ )
+ this.handleTrack(targetIndex, currentIndex)
+ if (this.state.settings.autoCycle) {
+ this.playAutoCycle()
+ }
+ }
+
+ moveToPrevious = event => {
+ event.preventDefault()
+ if (this.state.animating) {
+ return
+ }
+ if (this.state.settings.autoCycle && this.state.autoCycleTimer) {
+ clearInterval(this.state.autoCycleTimer)
+ this.setState({
+ autoCycleTimer: null,
+ })
+ }
+ const { settings } = this.state
+ let targetIndex = this.state.currentIndex - settings.slidesToScroll
+ const currentIndex = this.getTargetIndex(
+ targetIndex,
+ settings.slidesToScroll
+ )
+ if (targetIndex < 0 && this.state.currentIndex !== 0) {
+ targetIndex = 0
+ }
+ this.handleTrack(targetIndex, currentIndex)
+ if (this.state.settings.autoCycle) {
+ this.playAutoCycle()
+ }
+ }
+
+ onDotClick = event => {
+ event.preventDefault()
+ if (this.state.animating) {
+ return
+ }
+ if (this.state.settings.autoCycle && this.state.autoCycleTimer) {
+ clearInterval(this.state.autoCycleTimer)
+ this.setState({
+ autoCycleTimer: null,
+ })
+ }
+ const { settings } = this.state
+ const { slidesToShow } = settings
+ const targetIndex = event.target.parentElement.getAttribute('data-index')
+ const currentIndex = this.getTargetIndex(
+ targetIndex * slidesToShow,
+ slidesToShow
+ )
+ this.handleTrack(targetIndex * slidesToShow, currentIndex)
+ if (this.state.settings.autoCycle) {
+ this.playAutoCycle()
+ }
+ }
+
+ onWindowResized = () => {
+ this.setDimensions()
+ }
+
+ autoCycle = () => {
+ const { settings } = this.state
+ const targetIndex = this.state.currentIndex + settings.slidesToScroll
+ const currentIndex = this.getTargetIndex(
+ targetIndex,
+ settings.slidesToScroll
+ )
+ this.handleTrack(targetIndex, currentIndex)
+ }
+
+ playAutoCycle = () => {
+ if (this.state.settings.autoCycle) {
+ const autoCycleTimer = setInterval(
+ this.autoCycle,
+ this.state.settings.cycleInterval
+ )
+ this.setState({
+ autoCycleTimer,
+ })
+ }
+ }
+
+ pauseAutoCycle = () => {
+ if (this.state.autoCycleTimer) {
+ clearInterval(this.state.autoCycleTimer)
+ this.setState({
+ autoCycleTimer: null,
+ })
+ }
+ }
+
+ onMouseEnter = () => {
+ if (this.state.settings.autoCycle && this.state.settings.pauseOnHover) {
+ this.pauseAutoCycle()
+ }
+ }
+
+ onMouseOver = () => {
+ if (this.state.settings.autoCycle && this.state.settings.pauseOnHover) {
+ this.pauseAutoCycle()
+ }
+ }
+
+ onMouseLeave = () => {
+ if (this.state.settings.autoCycle && this.state.settings.pauseOnHover) {
+ this.playAutoCycle()
+ }
+ }
+
+ onSwipeStart = e => {
+ if (
+ this.state.settings.swipe === false ||
+ ('ontouchend' in document && this.state.settings.swipe === false)
+ ) {
+ return
+ } else if (
+ this.state.settings.draggable === false &&
+ e.type.indexOf('mouse') !== -1
+ ) {
+ return
+ }
+
+ const startX = e.touches !== undefined ? e.touches[0].pageX : e.clientX
+ const startY = e.touches !== undefined ? e.touches[0].pageY : e.clientY
+
+ this.setState({
+ dragging: true,
+ touchObject: {
+ startX,
+ startY,
+ },
+ })
+ }
+
+ onSwipeMove = e => {
+ if (!this.state.dragging) {
+ e.preventDefault()
+ return
+ }
+ if (this.state.animating) {
+ return
+ }
+ const curX = e.touches !== undefined ? e.touches[0].pageX : e.clientX
+ const curY = e.touches !== undefined ? e.touches[0].pageY : e.clientY
+ const { touchObject } = this.state
+ const direction = getSwipeDirection(
+ touchObject.startX,
+ curX,
+ touchObject.startY,
+ curY
+ )
+
+ if (direction !== 0) {
+ e.preventDefault()
+ }
+
+ const swipeLength = Math.round(Math.sqrt((curX - touchObject.startX) ** 2))
+
+ this.setState({
+ touchObject: {
+ startX: touchObject.startX,
+ startY: touchObject.startY,
+ endX: curX,
+ endY: curY,
+ length: swipeLength,
+ direction,
+ },
+ })
+ }
+
+ onSwipeEnd = () => {
+ const swipeLength = this.state.touchObject.length
+ if (swipeLength !== 0 && swipeLength > this.state.slidesWidth / 2) {
+ if (this.state.settings.autoCycle && this.state.autoCycleTimer) {
+ clearInterval(this.state.autoCycleTimer)
+ this.setState({
+ autoCycleTimer: null,
+ })
+ }
+
+ const { settings } = this.state
+ let targetIndex
+ let currentIndex
+ if (this.state.touchObject.direction === 1) {
+ // Next
+ targetIndex = this.state.currentIndex + settings.slidesToScroll
+ currentIndex = this.getTargetIndex(targetIndex, settings.slidesToScroll)
+ } else {
+ // Previous
+ targetIndex = this.state.currentIndex - settings.slidesToScroll
+ currentIndex = this.getTargetIndex(targetIndex, settings.slidesToScroll)
+ if (targetIndex < 0 && this.state.currentIndex !== 0) {
+ targetIndex = 0
+ }
+ }
+ this.handleTrack(targetIndex, currentIndex)
+
+ if (this.state.settings.autoCycle) {
+ this.playAutoCycle()
+ }
+ } else {
+ const callback = () => {
+ setTimeout(() => {
+ this.setState({
+ animating: false,
+ dragging: false,
+ touchObject: {
+ startX: 0,
+ startY: 0,
+ endX: 0,
+ endY: 0,
+ length: 0,
+ direction: -1,
+ },
+ })
+ }, this.state.settings.animationDuration)
+ }
+
+ this.setState(
+ {
+ animating: true,
+ touchObject: {
+ direction: this.state.touchObject.direction * -1,
+ },
+ },
+ callback
+ )
+ }
+ }
+
+ getTrackStyles = () => {
+ const { settings } = this.state
+ const { touchObject } = this.state
+ let trackWidth = this.state.slidesWidth + settings.slidesSpacing * 2
+ trackWidth *= this.state.slidesCount + settings.slidesToShow * 2
+ const totalSlideWidth = this.state.slidesWidth + settings.slidesSpacing * 2
+ const showSidesSlide = settings.showSides ? 1 : 0
+ const initialTrackPostion =
+ totalSlideWidth * (settings.slidesToShow + showSidesSlide)
+ const transition = this.state.animating
+ ? `transform ${settings.animationDuration}ms ease`
+ : ''
+ const hasTouchOffset = settings.swipe && touchObject.length
+ const touchOffset = hasTouchOffset
+ ? touchObject.length * touchObject.direction
+ : 0
+ const slidePosition = totalSlideWidth * this.state.currentIndex
+ let trackPosition = initialTrackPostion + slidePosition + touchOffset
+ const sideWidth = totalSlideWidth * this.state.sideSize
+
+ if (settings.showSides) {
+ trackPosition -= sideWidth
+ }
+
+ return {
+ position: 'relative',
+ display: 'block',
+ width: !this.state.singlePage ? trackWidth : '100%',
+ height: 'auto',
+ padding: 0,
+ transition,
+ transform: !this.state.singlePage
+ ? `translate(${-trackPosition}px, 0px)`
+ : 'none',
+ boxSizing: 'border-box',
+ MozBoxSizing: 'border-box',
+ marginLeft:
+ this.state.singlePage && settings.showSides ? `${sideWidth}px` : '0px',
+ }
+ }
+
+ getScrollTrackStyles = {
+ clear: 'both',
+ position: 'relative',
+ display: 'block',
+ width: '100%',
+ height: 'auto',
+ padding: 0,
+ boxSizing: 'border-box',
+ MozBoxSizing: 'border-box',
+ }
+
+ getSlideStyles = isVisible => {
+ const { slidesWidth } = this.state
+ const isScrollTouch = this.props.scrollOnDevice && isTouchDevice()
+ const float = isScrollTouch ? 'none' : 'left'
+ const display = 'inline-block'
+ const opacity = isVisible ? '1' : this.state.settings.sidesOpacity
+
+ return {
+ position: 'relative',
+ float,
+ display,
+ width: slidesWidth,
+ height: 'auto',
+ margin: `0 ${this.state.settings.slidesSpacing}px`,
+ opacity,
+ }
+ }
+
+ getFormatedChildren = (children, lazyLoadedList, visibleSlideList) =>
+ React.Children.map(children, (child, index) => {
+ const { settings } = this.state
+ const isVisible = visibleSlideList.indexOf(index) >= 0
+
+ if (!settings.lazyLoad || lazyLoadedList.indexOf(index) >= 0) {
+ return (
+
+ {child}
+
+ )
+ }
+ return (
+
+
+
+ )
+ })
+
+ init = () => {
+ const children = this.getChildrenList(
+ this.props.children,
+ this.props.slidesToShow
+ )
+ let settings
+ if (this.props.scrollOnDevice && isTouchDevice()) {
+ settings = Object.assign(
+ {},
+ this.defaultProps,
+ this.props,
+ this.state.scrollOnDeviceProps
+ )
+ } else {
+ settings = Object.assign({}, this.defaultProps, this.props)
+ }
+
+ this.setState({
+ children,
+ settings,
+ })
+
+ if (this.props.responsive) {
+ this.setupBreakpointSettings(this.props.breakpoints)
+ }
+ }
+
+ storeFrameRef = f => {
+ if (f !== null) {
+ this.frame = f
+ }
+ }
+
+ render() {
+ const scrollOnDevice = this.props.scrollOnDevice && isTouchDevice()
+ const { settings } = this.state
+ let prevArrow
+ let nextArrow
+ let dots
+
+ if (settings.arrows && !this.state.singlePage && !scrollOnDevice) {
+ if (settings.prevArrow == null) {
+ prevArrow = (
+
+ )
+ } else {
+ const prevArrowProps = {
+ onClick: this.moveToPrevious,
+ }
+ prevArrow = React.cloneElement(settings.prevArrow, prevArrowProps)
+ }
+
+ if (settings.nextArrow == null) {
+ nextArrow = (
+
+ )
+ } else {
+ const nextArrowProps = {
+ onClick: this.moveToNext,
+ }
+ nextArrow = React.cloneElement(settings.nextArrow, nextArrowProps)
+ }
+ }
+
+ if (settings.dots && !this.state.singlePage && !scrollOnDevice) {
+ dots = (
+
+ )
+ }
+
+ const { children, lazyLoadedList, visibleSlideList } = this.state
+ const formattedChildren = this.getFormatedChildren(
+ children,
+ lazyLoadedList,
+ visibleSlideList
+ )
+ let trackStyles
+ let trackClassName
+
+ if (this.props.scrollOnDevice && isTouchDevice()) {
+ trackStyles = Object.assign({}, this.getScrollTrackStyles)
+ trackClassName = 'InfiniteCarouselScrollTrack'
+ } else {
+ trackStyles = this.getTrackStyles()
+ trackClassName = ''
+ }
+
+ const disableSwipeEvents = this.props.scrollOnDevice && isTouchDevice()
+
+ return (
+
+ {prevArrow}
+
+ {nextArrow}
+ {dots}
+
+ )
+ }
+}
+
+export default InfiniteCarousel
diff --git a/src/components/Technologies.js b/src/components/Technologies.js
index 7f0a332..380743e 100644
--- a/src/components/Technologies.js
+++ b/src/components/Technologies.js
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { Component } from 'react'
import Link from 'gatsby-link'
import iconReact from '../images/react.svg'
@@ -17,10 +17,9 @@ import iconPython from '../images/python.svg'
import iconRuby from '../images/ruby.svg'
import iconRails from '../images/rails.svg'
import iconRedis from '../images/redis.svg'
+import Carousel from './Carousel'
-import Carousel from 'react-leaf-carousel'
-
-const Technologies = ({ ...props }) => (
+const Technologies = () => (
(
},
},
]}
- dots={false}
+ dots={true}
autoCycle={true}
- arrows={false}
+ arrows={true}
showSides={true}
pauseOnHover={false}
cycleInterval={2000}
- showSides={false}
+ showSides={true}
sidesOpacity={0.5}
sideSize={0.1}
slidesToScroll={2}
diff --git a/src/pages/services.js b/src/pages/services.js
index 75139b8..e2d0ae8 100644
--- a/src/pages/services.js
+++ b/src/pages/services.js
@@ -22,9 +22,9 @@ const ServicesPage = () => (
text="We have experiences with a wide variety of industries and are always keeping track of emerging technologies so that we can deliver forward-thinking solutions. Our developers are handpicked with care to ensure that we always deliver top-notch software quality. With our agile development process we can ensure high productivity and know how to balance between cost, time and quality."
/>
-
-
Modern Web Development
-
+
+
Modern Web Development
+
We offer full-cycle web development services for the connected
world. Our talented developers work with popular languages and are
always up to speed with cutting edge trends in web development. We
@@ -33,9 +33,9 @@ const ServicesPage = () => (
-
-
Mobile App Development
-
+
+
Mobile App Development
+
We have what it takes to develop competitive iOS and Android
applications using both native languages and hybrid solutions.
Solutions for iOS are built with Swift & Objective-C and Android
@@ -44,9 +44,9 @@ const ServicesPage = () => (
-
-
UX & UI Design
-
+
+
UX & UI Design
+
Our approach is simple: focus on how users might use the product in
the best way possible. We offer UX and interface design for all
screens and devices. The solutions are usually shaped through
@@ -54,7 +54,7 @@ const ServicesPage = () => (
-