diff --git a/.eslintrc.json b/.eslintrc.json index e5f74dbb..8df87e5c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -44,4 +44,4 @@ } } ] -} +} \ No newline at end of file diff --git a/example/App.jsx b/example/App.jsx index 1139c353..80c2458f 100644 --- a/example/App.jsx +++ b/example/App.jsx @@ -19,6 +19,7 @@ class App extends React.Component { showPlayButton: true, showGalleryPlayButton: true, showNav: true, + slideVertically: false, isRTL: false, slideDuration: 450, slideInterval: 2000, @@ -201,6 +202,7 @@ class App extends React.Component { slideOnThumbnailOver={this.state.slideOnThumbnailOver} additionalClass="app-image-gallery" useWindowKeyDown={this.state.useWindowKeyDown} + slideVertically={this.state.slideVertically} />
@@ -333,6 +335,18 @@ class App extends React.Component { /> +
  • + + +
  • 2) { if (currentIndex === 0 && index === totalSlides) { // make the last slide the slide before the first // if it is RTL the base line should be reversed - translateX = -100 * (isRTL ? -1 : 1) + currentSlideOffset; + translateValue = -100 * (isRTL ? -1 : 1) + currentSlideOffset; } else if (currentIndex === totalSlides && index === 0) { // make the first slide the slide after the last // if it is RTL the base line should be reversed - translateX = 100 * (isRTL ? -1 : 1) + currentSlideOffset; + translateValue = 100 * (isRTL ? -1 : 1) + currentSlideOffset; } } // Special case when there are only 2 items with infinite on if (infinite && items.length === 2) { - translateX = this.getTranslateXForTwoSlide(index); + translateValue = this.getTranslateXForTwoSlide(index); } - let translate = `translate(${translateX}%, 0)`; + let translate = slideVertically + ? `translate(0, ${translateValue}%)` + : `translate(${translateValue}%, 0)`; if (useTranslate3D) { - translate = `translate3d(${translateX}%, 0, 0)`; + translate = slideVertically + ? `translate3d(0, ${translateValue}%, 0)` + : `translate3d(${translateValue}%, 0, 0)`; } // don't show some slides while transitioning to avoid background transitions @@ -781,16 +788,23 @@ class ImageGallery extends React.Component { } handleSwiping({ event, absX, dir }) { - const { disableSwipe, stopPropagation } = this.props; - const { galleryWidth, isTransitioning, swipingUpDown, swipingLeftRight } = - this.state; + const { disableSwipe, stopPropagation, swipingTransitionDuration } = + this.props; + const { + galleryWidth, + isTransitioning, + swipingUpDown, + swipingLeftRight, + } = this.state; + + const { slideVertically } = this.props; // if the initial swiping is up/down prevent moving the slides until swipe ends if ((dir === UP || dir === DOWN || swipingUpDown) && !swipingLeftRight) { if (!swipingUpDown) { this.setState({ swipingUpDown: true }); } - return; + if (!slideVertically) return; } if ((dir === LEFT || dir === RIGHT) && !swipingLeftRight) { @@ -799,15 +813,28 @@ class ImageGallery extends React.Component { if (disableSwipe) return; - const { swipingTransitionDuration } = this.props; if (stopPropagation) { event.preventDefault(); } if (!isTransitioning) { - const side = dir === RIGHT ? 1 : -1; + const isSwipeLeftOrRight = dir === LEFT || dir === RIGHT; + const isSwipeTopOrDown = dir === UP || dir === DOWN; + + if (isSwipeLeftOrRight && slideVertically) return; + if (isSwipeTopOrDown && !slideVertically) return; + + const sides = { + [LEFT]: -1, + [RIGHT]: 1, + [UP]: -1, + [DOWN]: 1, + }; + + const side = sides[dir]; let currentSlideOffset = (absX / galleryWidth) * 100; + if (Math.abs(currentSlideOffset) >= 100) { currentSlideOffset = 100; } @@ -953,6 +980,7 @@ class ImageGallery extends React.Component { handleOnSwiped({ event, dir, velocity }) { const { disableSwipe, stopPropagation, flickThreshold } = this.props; + const { slideVertically } = this.props; if (disableSwipe) return; @@ -961,17 +989,24 @@ class ImageGallery extends React.Component { this.resetSwipingDirection(); // if it is RTL the direction is reversed - const swipeDirection = (dir === LEFT ? 1 : -1) * (isRTL ? -1 : 1); + let swipeDirection = (dir === LEFT ? 1 : -1) * (isRTL ? -1 : 1); + if (slideVertically) swipeDirection = dir === UP ? 1 : -1; + const isSwipeUpOrDown = dir === UP || dir === DOWN; + const isSwipeLeftOrRight = dir === LEFT || dir === RIGHT; const isLeftRightFlick = velocity > flickThreshold && !isSwipeUpOrDown; - this.handleOnSwipedTo(swipeDirection, isLeftRightFlick); + const isTopDownFlick = velocity > flickThreshold && !isSwipeLeftOrRight; + + const isFlick = slideVertically ? isTopDownFlick : isLeftRightFlick; + + this.handleOnSwipedTo(swipeDirection, isFlick); } - handleOnSwipedTo(swipeDirection, isLeftRightFlick) { + handleOnSwipedTo(swipeDirection, isFlick) { const { currentIndex, isTransitioning } = this.state; let slideTo = currentIndex; - if ((this.sufficientSwipe() || isLeftRightFlick) && !isTransitioning) { + if ((this.sufficientSwipe() || isFlick) && !isTransitioning) { // slideto the next/prev slide slideTo += swipeDirection; } @@ -1437,8 +1472,14 @@ class ImageGallery extends React.Component { } render() { - const { currentIndex, isFullscreen, modalFullscreen, isPlaying } = - this.state; + const { + currentIndex, + isFullscreen, + modalFullscreen, + isPlaying, + } = this.state; + + const { slideVertically } = this.props; const { additionalClass, @@ -1451,6 +1492,8 @@ class ImageGallery extends React.Component { renderCustomControls, renderLeftNav, renderRightNav, + renderTopNav, + renderBottomNav, showBullets, showFullscreenButton, showIndex, @@ -1475,8 +1518,12 @@ class ImageGallery extends React.Component { {showNav && ( - {renderLeftNav(this.slideLeft, !this.canSlideLeft())} - {renderRightNav(this.slideRight, !this.canSlideRight())} + {slideVertically + ? renderTopNav(this.slideLeft, !this.canSlideLeft()) + : renderLeftNav(this.slideLeft, !this.canSlideLeft())} + {slideVertically + ? renderBottomNav(this.slideRight, !this.canSlideRight()) + : renderRightNav(this.slideRight, !this.canSlideRight())} )} -
    {slides}
    +
    + {slides} +
    ) : ( @@ -1649,6 +1702,8 @@ ImageGallery.propTypes = { renderCustomControls: func, renderLeftNav: func, renderRightNav: func, + renderTopNav: func, + renderBottomNav: func, renderPlayPauseButton: func, renderFullscreenButton: func, renderItem: func, @@ -1658,6 +1713,7 @@ ImageGallery.propTypes = { useTranslate3D: bool, isRTL: bool, useWindowKeyDown: bool, + slideVertically: bool, }; ImageGallery.defaultProps = { @@ -1709,12 +1765,19 @@ ImageGallery.defaultProps = { slideInterval: 3000, slideOnThumbnailOver: false, swipeThreshold: 30, + slideVertically: false, renderLeftNav: (onClick, disabled) => ( ), renderRightNav: (onClick, disabled) => ( ), + renderTopNav: (onClick, disabled) => ( + + ), + renderBottomNav: (onClick, disabled) => ( + + ), renderPlayPauseButton: (onClick, isPlaying) => ( ), diff --git a/src/components/SVG.jsx b/src/components/SVG.jsx index 58aa9bcb..9cc59c4f 100644 --- a/src/components/SVG.jsx +++ b/src/components/SVG.jsx @@ -3,6 +3,8 @@ import { number, oneOf, string } from "prop-types"; const left = ; const right = ; +const top = ; +const bottom = ; const maximize = ( ); @@ -20,6 +22,8 @@ const pause = ( const iconMapper = { left, right, + top, + bottom, maximize, minimize, play, @@ -52,8 +56,16 @@ const SVG = (props) => { SVG.propTypes = { strokeWidth: number, viewBox: string, - icon: oneOf(["left", "right", "maximize", "minimize", "play", "pause"]) - .isRequired, + icon: oneOf([ + "left", + "right", + "top", + "bottom", + "maximize", + "minimize", + "play", + "pause", + ]).isRequired, }; export default SVG; diff --git a/src/components/controls/BottomNav.jsx b/src/components/controls/BottomNav.jsx new file mode 100644 index 00000000..e9482910 --- /dev/null +++ b/src/components/controls/BottomNav.jsx @@ -0,0 +1,26 @@ +import React from "react"; +import { bool, func } from "prop-types"; +import SVG from "src/components/SVG"; + +const BottomNav = React.memo(({ disabled, onClick }) => { + return ( + + ); +}); + +BottomNav.displayName = "BottomNav"; + +BottomNav.propTypes = { + disabled: bool.isRequired, + onClick: func.isRequired, +}; + +export default BottomNav; diff --git a/src/components/controls/TopNav.jsx b/src/components/controls/TopNav.jsx new file mode 100644 index 00000000..c8142d2b --- /dev/null +++ b/src/components/controls/TopNav.jsx @@ -0,0 +1,26 @@ +import React from "react"; +import { bool, func } from "prop-types"; +import SVG from "src/components/SVG"; + +const TopNav = React.memo(({ disabled, onClick }) => { + return ( + + ); +}); + +TopNav.displayName = "TopNav"; + +TopNav.propTypes = { + disabled: bool.isRequired, + onClick: func.isRequired, +}; + +export default TopNav; diff --git a/styles/scss/image-gallery.scss b/styles/scss/image-gallery.scss index 445c334a..3eaf8180 100644 --- a/styles/scss/image-gallery.scss +++ b/styles/scss/image-gallery.scss @@ -86,6 +86,46 @@ $ig-shadow: 0 2px 2px lighten($ig-black, 10%); left: 0; } +.image-gallery-top-nav, +.image-gallery-bottom-nav { + padding: 10px 10px; + left: 50%; + transform: translateX(-50%); + + .image-gallery-svg { + height: 120px; + width: 60px; + } + + @media (max-width: $ig-small-screen) { + .image-gallery-svg { + height: 72px; + width: 36px; + } + } + + @media (max-width: $ig-xsmall-screen) { + .image-gallery-svg { + height: 48px; + width: 24px; + } + } + + &[disabled] { + cursor: disabled; + opacity: .6; + pointer-events: none; + } +} + +.image-gallery-top-nav { + top: 0; +} + +.image-gallery-bottom-nav { + bottom: 0; +} + .image-gallery-left-nav, .image-gallery-right-nav { padding: 50px 10px;