From 8ec83112047bd99b5fa1cf1cfb9c716018e7e31e Mon Sep 17 00:00:00 2001 From: Moris Zen Date: Tue, 10 Jul 2018 01:14:49 +0200 Subject: [PATCH] Replicating InfinityCarousel and fixing outdated bugfixes, css changes --- package.json | 2 +- src/components/Carousel/InfiniteCarousel.css | 91 ++ .../Carousel/InfiniteCarouselArrow.js | 41 + .../Carousel/InfiniteCarouselDots.js | 36 + src/components/Carousel/helpers.js | 44 + src/components/Carousel/index.js | 1012 +++++++++++++++++ src/components/Technologies.js | 13 +- src/pages/services.js | 20 +- src/styles/index.css | 80 +- tailwind.config.js | 2 +- yarn.lock | 29 +- 11 files changed, 1283 insertions(+), 87 deletions(-) create mode 100644 src/components/Carousel/InfiniteCarousel.css create mode 100644 src/components/Carousel/InfiniteCarouselArrow.js create mode 100644 src/components/Carousel/InfiniteCarouselDots.js create mode 100644 src/components/Carousel/helpers.js create mode 100644 src/components/Carousel/index.js 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
    {dots}
+} + +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 ( +
  • + placeholder +
  • + ) + }) + + 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} +
    +
      + {formattedChildren} +
    +
    + {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 = () => (

    -
    +