├── .babelrc
├── .eslintrc
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── docs.md
│ └── feature_request.md
└── stale.yml
├── .gitignore
├── .npmrc
├── .yarnrc
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── analytics.js
├── demoApp
└── src
│ ├── DemoApp.js
│ └── index.js
├── doczrc.js
├── gatsby-node.js
├── libName.js
├── logo.png
├── package.json
├── rollup.config.demo.js
├── rollup.config.js
├── src
├── .eslintrc
├── docs
│ ├── components
│ │ ├── Button.js
│ │ ├── Description.js
│ │ ├── Flex.js
│ │ ├── Item.js
│ │ ├── ItemContainer.js
│ │ ├── ItemWithLink.js
│ │ ├── Poster.js
│ │ ├── SimpleItem.js
│ │ ├── Square.js
│ │ ├── Title.js
│ │ ├── breakPoints.js
│ │ └── itemsCollection.js
│ ├── mdx
│ │ ├── autoTabIndexVisibleItems.mdx
│ │ ├── breakpoints.mdx
│ │ ├── easing.mdx
│ │ ├── enableAutoPlay.mdx
│ │ ├── focusOnSelect.mdx
│ │ ├── goTo.mdx
│ │ ├── index.mdx
│ │ ├── initialFirstItem.mdx
│ │ ├── isRTL.mdx
│ │ ├── itemPadding.mdx
│ │ ├── itemPosition.mdx
│ │ ├── itemsToScroll.mdx
│ │ ├── itemsToShow.mdx
│ │ ├── onChange.mdx
│ │ ├── onNextEnd.mdx
│ │ ├── onNextStart.mdx
│ │ ├── onPrevEnd.mdx
│ │ ├── onPrevStart.mdx
│ │ ├── onResize.mdx
│ │ ├── outerSpacing.mdx
│ │ ├── renderArrow.mdx
│ │ ├── renderPagination.mdx
│ │ ├── showEmptySlots.mdx
│ │ ├── slideNext.mdx
│ │ ├── styling.mdx
│ │ └── verticalMode.mdx
│ └── styles
│ │ └── styling.css
├── react-elastic-carousel
│ ├── actions
│ │ ├── consts.js
│ │ └── itemsActions.js
│ ├── components
│ │ ├── Arrow.js
│ │ ├── Carousel.js
│ │ ├── ItemWrapperContainer.js
│ │ ├── Pagination
│ │ │ ├── Dot.js
│ │ │ ├── Pagination.js
│ │ │ ├── __test__
│ │ │ │ └── Dot.js
│ │ │ └── index.js
│ │ ├── Track.js
│ │ ├── __tests__
│ │ │ └── Carousel.test.js
│ │ └── styled
│ │ │ ├── Button.js
│ │ │ ├── CarouselWrapper.js
│ │ │ ├── ItemWrapper.js
│ │ │ ├── Slider.js
│ │ │ ├── SliderContainer.js
│ │ │ ├── StyledCarousel.js
│ │ │ └── index.js
│ ├── consts.js
│ ├── index.d.ts
│ ├── index.js
│ ├── reducers
│ │ ├── __tests__
│ │ │ └── items.test.js
│ │ └── items.js
│ └── utils
│ │ ├── __tests__
│ │ └── helpers.test.js
│ │ └── helpers.js
└── setupTests.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "modules": false
5 | }],
6 | "@babel/preset-react"
7 | ],
8 | "plugins": ["@babel/plugin-proposal-class-properties"]
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "standard",
5 | "standard-react",
6 | "plugin:prettier/recommended"
7 | ],
8 | "env": {
9 | "es6": true
10 | },
11 | "plugins": [
12 | "react",
13 | "prettier"
14 | ],
15 | "parserOptions": {
16 | "sourceType": "module"
17 | },
18 | "rules": {
19 | // don't force es6 functions to include space before paren
20 | "space-before-function-paren": 0,
21 |
22 | // allow specifying true explicitly for boolean props
23 | "react/jsx-boolean-value": 0
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Sandbox link**
24 | Please provide a link to a [codesandbox](https://codesandbox.io/) with the reproduced bug
25 |
26 | **Screenshots**
27 | If applicable, add screenshots to help explain your problem.
28 |
29 | **Desktop (please complete the following information):**
30 | - OS: [e.g. iOS]
31 | - Browser [e.g. chrome, safari]
32 | - Version [e.g. 22]
33 |
34 | **Smartphone (please complete the following information):**
35 | - Device: [e.g. iPhone6]
36 | - OS: [e.g. iOS8.1]
37 | - Browser [e.g. stock browser, safari]
38 | - Version [e.g. 22]
39 |
40 | **Additional context**
41 | Add any other context about the problem here.
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/docs.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | - bug
10 | - block-stale
11 | # Label to use when marking an issue as stale
12 | staleLabel: stale
13 | # Comment to post when marking an issue as stale. Set to `false` to disable
14 | markComment: >
15 | This issue has been automatically marked as stale because it has not had
16 | recent activity. It will be closed if no further activity occurs. Thank you
17 | for your contributions.
18 | # Comment to post when closing a stale issue. Set to `false` to disable
19 | closeComment: false
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # See https://help.github.com/ignore-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # builds
8 | build
9 | dist
10 | demo
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | # docz
21 | .docz/cache/
22 |
23 | # test-related files
24 | coverage
25 |
26 |
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 |
31 | .vscode
32 | .editorconfig
33 | .docz
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | strict-ssl false
2 | registry "https://registry.npmjs.org/"
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | We use [Semantic Versioning](http://semver.org/).
4 | Each release can be found in the Github [Releases page](https://github.com/sag1v/react-elastic-carousel/releases).
5 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at sagiv.bengiat@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Sagiv ben giat
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # react-elastic-carousel
3 |
4 | > A flexible and responsive carousel component for react
5 |
6 | [](https://www.npmjs.com/package/react-elastic-carousel)  
7 |
8 | ## Why do we need yet another carousel component
9 |
10 | - **Element resize support (true responsiveness)**
11 | Most of the carousel components are responsive to the viewport size, but this is not a real responsive support as we can have an element with a `width:500px` on a `1200px` screen, most carousel component will "think" we are on a `1200px` mode because they "watch" the view-port's size and not the wrapping element's size.
12 | This is the reason why `react-eleastic-carousel` is using the [resize-observer](https://developers.google.com/web/updates/2016/10/resizeobserver) which gives us a true responsive support, not matter on what screen size we are.
13 |
14 | - **RTL (right-to-left) support**
15 | Supporting right-to-left languages requires a full support for right-to-left rendering and animations which is not supported in most of the carousel components out there. also, right-to-left support is [important and should be a standard for most applications](https://www.youtube.com/watch?v=A_3BfONFRUc).
16 |
17 | ## [Live Demos & Docs](https://sag1v.github.io/react-elastic-carousel/)
18 |
19 | ## Install
20 |
21 | ```bash
22 | npm install --save react-elastic-carousel
23 | ```
24 |
25 | or
26 |
27 | ```bash
28 | yarn add react-elastic-carousel
29 | ```
30 |
31 | ### Note
32 |
33 | `react-elastic-carousel` is using [styled-components](https://github.com/styled-components/styled-components) for styling, this means that you should install it as well:
34 |
35 | ```bash
36 | npm install --save styled-components
37 | ```
38 |
39 | ## Usage
40 |
41 | ```jsx
42 | import React, { Component } from 'react';
43 | import Carousel from 'react-elastic-carousel';
44 |
45 | class App extends Component {
46 | state = {
47 | items: [
48 | {id: 1, title: 'item #1'},
49 | {id: 2, title: 'item #2'},
50 | {id: 3, title: 'item #3'},
51 | {id: 4, title: 'item #4'},
52 | {id: 5, title: 'item #5'}
53 | ]
54 | }
55 |
56 | render () {
57 | const { items } = this.state;
58 | return (
59 |
60 | {items.map(item => {item.title}
)}
61 |
62 | )
63 | }
64 | }
65 | ```
66 |
67 | ## Playground
68 |
69 | [](https://codesandbox.io/s/21o46mkwnr)
70 |
71 | ## Development
72 |
73 | ```console
74 | git clone https://github.com/sag1v/react-elastic-carousel.git
75 | cd react-elastic-carousel
76 | yarn
77 | ```
78 |
79 | ### To run the docs site run
80 |
81 | ```console
82 | yarn start
83 | ```
84 |
85 | ### to run a demo Application run
86 |
87 | ```console
88 | yarn demo
89 | ```
90 |
91 | The application is running at http://localhost:8888
92 |
93 | ## License
94 |
95 | MIT © [sag1v](https://github.com/sag1v)
96 |
--------------------------------------------------------------------------------
/analytics.js:
--------------------------------------------------------------------------------
1 | {/* */}
2 | window.dataLayer = window.dataLayer || [];
3 | function gtag(){dataLayer.push(arguments);}
4 | gtag('js', new Date());
5 |
6 | gtag('config', 'UA-127217040-1');
7 |
--------------------------------------------------------------------------------
/demoApp/src/DemoApp.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from "react";
2 | import Carousel from "../../src/react-elastic-carousel/components/Carousel";
3 | import styled from "styled-components";
4 |
5 | const Item = styled.div`
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | color: #fff;
10 | background-color: green;
11 | width: 100%;
12 | height: 150px;
13 | margin: 15px;
14 | `;
15 |
16 | const Layout = styled.div`
17 | display: flex;
18 | flex-direction: column;
19 | align-items: center;
20 | height: 100vh;
21 | `;
22 |
23 | const ControlsLayout = styled.div`
24 | display: flex;
25 | flex-direction: column;
26 | margin: 25px;
27 | `;
28 |
29 | const StyledControlFields = styled.div`
30 | display: flex;
31 | margin: 5px;
32 | `;
33 |
34 | const breakPoints = [
35 | { width: 200, itemsToShow: 1 },
36 | { width: 600, itemsToShow: 2 },
37 | ];
38 | const toggle = (updater) => () => updater((o) => !o);
39 |
40 | const CheckBox = ({ label, onToggle, ...rest }) => {
41 | return (
42 |
43 | {label}
44 |
45 |
46 | );
47 | };
48 |
49 | const serverItems = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
50 |
51 | const DemoApp = () => {
52 | const [show, setShow] = useState(true);
53 | const [enableAutoPlay, setEnableAutoPlay] = useState(false);
54 | const [items, setItems] = useState([]);
55 | const [itemsToShow, setItemsToShow] = useState(3);
56 | const [showArrows, setShowArrows] = useState(true);
57 | const [pagination, setPagination] = useState(true);
58 | const [verticalMode, setVerticalMode] = useState(false);
59 | const carouselRef = useRef();
60 |
61 | const addItem = () => {
62 | setItems((currentItems) => [...currentItems, currentItems.length + 1]);
63 | };
64 |
65 | const removeItem = () => {
66 | setItems((currentItems) => currentItems.slice(0, currentItems.length - 1));
67 | };
68 |
69 | const updateItemsToShow = ({ target }) =>
70 | setItemsToShow(Number(target.value));
71 |
72 | const goTo = ({ target }) => carouselRef.current.goTo(Number(target.value));
73 |
74 | useEffect(() => {
75 | setTimeout(() => {
76 | setItems(serverItems);
77 | }, 2500);
78 | }, []);
79 |
80 | return (
81 |
82 |
83 |
84 | setShow((o) => !o)}>
85 | {`${show ? "Hide" : "Show"} Carousel`}
86 |
87 |
88 |
89 | Add Item
90 | Remove Item
91 |
92 |
93 | goTo
94 |
95 |
96 |
97 | itemsToShow
98 |
103 |
104 |
109 |
114 |
119 |
124 |
125 | {show && (
126 |
134 | {items.map((item) => (
135 | - {item}
136 | ))}
137 |
138 | )}
139 |
140 | );
141 | };
142 |
143 | export default DemoApp;
144 |
--------------------------------------------------------------------------------
/demoApp/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import DemoApp from "./DemoApp";
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById("app")
10 | );
11 |
--------------------------------------------------------------------------------
/doczrc.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import pkg from './package.json';
3 |
4 | const libName = pkg.name;
5 |
6 | export default {
7 | ignore: ['README.md', 'changelog.md', 'CODE_OF_CONDUCT.md', 'contributing.md', 'LICENSE.md'],
8 | //src: './src',
9 | dest: './demo',
10 | port: 8888,
11 | hashRouter: true,
12 | base: `/${libName}/`,
13 | menu: [
14 | "Getting started",
15 | {
16 | name: 'Examples',
17 | menu: [
18 | 'itemsToScroll',
19 | 'itemsToShow',
20 | 'breakPoints',
21 | 'verticalMode',
22 | 'initialActiveIndex',
23 | 'focusOnSelect',
24 | 'isRTL',
25 | 'enableAutoPlay',
26 | 'itemPadding',
27 | 'outerSpacing',
28 | 'showEmptySlots',
29 | 'itemPosition',
30 | 'easing',
31 | 'renderArrow',
32 | 'renderPagination',
33 | 'autoTabIndexVisibleItems',
34 | 'onChange',
35 | 'onNextStart',
36 | 'onNextEnd',
37 | 'onPrevStart',
38 | 'onPrevEnd',
39 | 'onResize',
40 | 'slideNext / slidePrev',
41 | 'goTo',
42 | 'Styling'
43 | ]
44 | }
45 | ],
46 | // htmlContext: {
47 | // head: {
48 | // scripts: [
49 | // {
50 | // async: 'async',
51 | // src: 'https://www.googletagmanager.com/gtag/js?id=UA-127217040-1'
52 | // }
53 | // ],
54 | // raw: ``,
60 | // links: [{
61 | // rel: 'stylesheet',
62 | // href: '//codemirror.net/theme/dracula.css'
63 | // }]
64 | // }
65 | // },
66 | // themeConfig: {
67 | // // logo: {
68 | // // src: "//image.ibb.co/iACdcK/carousel_logo.png",
69 | // // width: "50%"
70 | // // },
71 | // mode: 'light',
72 | // showPlaygroundEditor: true,
73 | // codemirrorTheme: 'dracula',
74 | // styles: {
75 | // body: {
76 | // fontFamily: "'Source Sans Pro', Helvetica, sans-serif",
77 | // fontSize: 16,
78 | // lineHeight: 1.6,
79 | // },
80 | // container: {
81 | // width: '100%',
82 | // padding: ['20px 40px'],
83 | // },
84 | // }
85 | // },
86 | }
87 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const pkg = require('./package.json');
3 |
4 | const libName = pkg.name;
5 |
6 | module.exports = {
7 | plugins: [
8 | {
9 | resolve: `gatsby-plugin-google-analytics`,
10 | options: {
11 | trackingId: "UA-127217040-1",
12 | head: true,
13 | },
14 | },
15 | ],
16 | onCreateWebpackConfig: ({
17 | stage,
18 | rules,
19 | loaders,
20 | plugins,
21 | actions,
22 | }) => {
23 | actions.setWebpackConfig({
24 | resolve: {
25 | modules: [path.resolve(__dirname, '../src'), 'node_modules'],
26 | alias: {
27 | [libName]: path.join(__dirname, `/src/${libName}/index.js`)
28 | }
29 | }
30 | })
31 | },
32 | }
33 |
--------------------------------------------------------------------------------
/libName.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json';
2 | export default pkg.name;
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sag1v/react-elastic-carousel/6f129b51bf39b36d31375e39c66a8f0c4b669076/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-elastic-carousel",
3 | "version": "0.11.5",
4 | "description": "A flexible and responsive carousel component for react",
5 | "author": "sag1v (Sagiv Ben Giat)",
6 | "license": "MIT",
7 | "repository": "sag1v/react-elastic-carousel",
8 | "main": "dist/index.js",
9 | "module": "dist/index.es.js",
10 | "types": "./dist/index.d.ts",
11 | "jsnext:main": "dist/index.es.js",
12 | "publishConfig": {
13 | "registry": "https://registry.npmjs.org"
14 | },
15 | "engines": {
16 | "node": ">=8",
17 | "npm": ">=5"
18 | },
19 | "scripts": {
20 | "start": "concurrently -r -k -s all \"docz dev\" \"yarn run lint:watch\"",
21 | "demo": "concurrently -r -k -s all \"rollup --config rollup.config.demo.js --watch\" \"yarn run lint:watch\"",
22 | "lint:fix": "eslint src/* --fix",
23 | "lint:watch": "esw --watch --fix src/*",
24 | "test": "cross-env CI=1 react-scripts test --env=jsdom",
25 | "test:watch": "react-scripts test --env=jsdom",
26 | "prebuild": "yarn run lint:fix",
27 | "build": "rollup -c",
28 | "build-doc": "docz build",
29 | "deploy-doc": "gh-pages -d demo"
30 | },
31 | "lint-staged": {
32 | "*.js": "eslint src/. --fix"
33 | },
34 | "dependencies": {
35 | "classnames": "^2.2.6",
36 | "react-only-when": "^1.0.2",
37 | "react-swipeable": "^5.5.1",
38 | "resize-observer-polyfill": "1.5.0"
39 | },
40 | "peerDependencies": {
41 | "prop-types": "^15.5.4",
42 | "react": "15 - 17",
43 | "react-dom": "15 - 17",
44 | "styled-components": "^5.1.0"
45 | },
46 | "devDependencies": {
47 | "@babel/core": "^7.3.4",
48 | "@babel/plugin-external-helpers": "^7.0.0",
49 | "@babel/plugin-proposal-class-properties": "^7.3.4",
50 | "@babel/plugin-proposal-decorators": "^7.0.0",
51 | "@babel/plugin-proposal-do-expressions": "^7.0.0",
52 | "@babel/plugin-proposal-export-default-from": "^7.0.0",
53 | "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
54 | "@babel/plugin-proposal-function-bind": "^7.0.0",
55 | "@babel/plugin-proposal-function-sent": "^7.0.0",
56 | "@babel/plugin-proposal-json-strings": "^7.0.0",
57 | "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
58 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
59 | "@babel/plugin-proposal-numeric-separator": "^7.0.0",
60 | "@babel/plugin-proposal-optional-chaining": "^7.0.0",
61 | "@babel/plugin-proposal-pipeline-operator": "^7.0.0",
62 | "@babel/plugin-proposal-throw-expressions": "^7.0.0",
63 | "@babel/plugin-syntax-dynamic-import": "^7.0.0",
64 | "@babel/plugin-syntax-import-meta": "^7.0.0",
65 | "@babel/preset-env": "^7.3.4",
66 | "@babel/preset-react": "^7.0.0",
67 | "@rollup/plugin-replace": "^2.3.4",
68 | "babel-eslint": "^9.0.0",
69 | "concurrently": "^4.1.0",
70 | "cross-env": "^5.1.4",
71 | "docz": "^2.3.1",
72 | "enzyme": "^3.6.0",
73 | "enzyme-adapter-react-16": "^1.5.0",
74 | "eslint": "5.12.0",
75 | "eslint-config-prettier": "^3.0.1",
76 | "eslint-config-standard": "^12.0.0",
77 | "eslint-config-standard-react": "^7.0.0",
78 | "eslint-plugin-import": "^2.13.0",
79 | "eslint-plugin-node": "^7.0.1",
80 | "eslint-plugin-prettier": "^2.6.2",
81 | "eslint-plugin-promise": "^4.0.0",
82 | "eslint-plugin-react": "^7.10.0",
83 | "eslint-plugin-standard": "^3.1.0",
84 | "eslint-watch": "^4.0.2",
85 | "gatsby-plugin-google-analytics": "^2.1.34",
86 | "gh-pages": "^2.2.0",
87 | "husky": "^4.3.0",
88 | "lint-staged": "^10.5.2",
89 | "prettier": "1.14.2",
90 | "prettier-eslint": "^8.8.2",
91 | "react": "^16.12.0",
92 | "react-dom": "^16.12.0",
93 | "react-resizable": "^1.7.5",
94 | "react-scripts": "^2.1.8",
95 | "react-test-renderer": "^16.5.2",
96 | "rollup": "^0.64.1",
97 | "rollup-plugin-alias": "^1.4.0",
98 | "rollup-plugin-auto-external": "^2.0.0",
99 | "rollup-plugin-babel": "^4.3.2",
100 | "rollup-plugin-commonjs": "^9.1.3",
101 | "rollup-plugin-copy": "^3.3.0",
102 | "rollup-plugin-livereload": "^2.0.0",
103 | "rollup-plugin-node-resolve": "^3.3.0",
104 | "rollup-plugin-postcss": "^1.6.2",
105 | "rollup-plugin-serve": "^1.1.0",
106 | "rollup-plugin-url": "^1.4.0",
107 | "styled-components": "^5.1.0"
108 | },
109 | "files": [
110 | "dist"
111 | ],
112 | "keywords": [
113 | "react",
114 | "react-carousel",
115 | "carousel",
116 | "responsive",
117 | "reactjs",
118 | "carrousel",
119 | "slides",
120 | "flexibale",
121 | "rtl",
122 | "right-to-left",
123 | "resize",
124 | "touch"
125 | ],
126 | "resolutions": {
127 | "ansi-styles": "^3.2.0"
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/rollup.config.demo.js:
--------------------------------------------------------------------------------
1 | import babel from "rollup-plugin-babel";
2 | import commonjs from "rollup-plugin-commonjs";
3 | import postcss from "rollup-plugin-postcss";
4 | import resolve from "rollup-plugin-node-resolve";
5 | import url from "rollup-plugin-url";
6 | import alias from "rollup-plugin-alias";
7 | import serve from "rollup-plugin-serve";
8 | import replace from "@rollup/plugin-replace";
9 | import livereload from 'rollup-plugin-livereload'
10 |
11 | import libName from "./libName";
12 |
13 | import * as ReactNamedExports from 'react';
14 | import * as ReactIsNamedExports from 'react-is';
15 |
16 | export default {
17 | input: `demoApp/src/index.js`,
18 | output: [
19 | {
20 | file: "demoApp/dist/bundle.js",
21 | format: "cjs",
22 | sourcemap: true,
23 | exports: "named",
24 | },
25 | ],
26 | plugins: [
27 | alias({
28 | "react-elastic-carousel": `src/${libName}/index.js`,
29 | }),
30 | //external(),
31 | postcss({
32 | modules: false,
33 | }),
34 | url(),
35 | babel({
36 | exclude: "node_modules/**",
37 | plugins: ["@babel/external-helpers"],
38 | }),
39 | resolve(),
40 | commonjs({
41 | include: "node_modules/**",
42 | namedExports: {
43 | "node_modules/react-is/index.js": Object.keys(ReactIsNamedExports),
44 | "node_modules/react/index.js": Object.keys(ReactNamedExports),
45 | },
46 | }),
47 | serve({
48 | open: true,
49 | contentBase: "demoApp/dist",
50 | }),
51 | livereload(),
52 | replace({
53 | "process.env.NODE_ENV": JSON.stringify("production"),
54 | }),
55 | ],
56 | };
57 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import copy from "rollup-plugin-copy";
4 | import external from "rollup-plugin-auto-external";
5 | import postcss from 'rollup-plugin-postcss';
6 | import resolve from 'rollup-plugin-node-resolve';
7 | import url from 'rollup-plugin-url';
8 | import alias from 'rollup-plugin-alias';
9 |
10 | import pkg from './package.json'
11 |
12 | import libName from './libName';
13 |
14 | export default {
15 | input: `src/${libName}/index.js`,
16 | output: [
17 | {
18 | file: pkg.main,
19 | format: 'cjs',
20 | sourcemap: true,
21 | exports: 'named'
22 | },
23 | {
24 | file: pkg.module,
25 | format: 'es',
26 | sourcemap: true
27 | }
28 | ],
29 | plugins: [
30 | alias({
31 | [libName]: `./src/${libName}/index.js`
32 | }),
33 | external(),
34 | postcss({
35 | modules: false
36 | }),
37 | url(),
38 | babel({
39 | exclude: 'node_modules/**',
40 | plugins: [ '@babel/external-helpers' ]
41 | }),
42 | resolve(),
43 | commonjs({
44 | include: 'node_modules/**',
45 | namedExports: {
46 | 'node_modules/react-is/index.js': ['isValidElementType']
47 | }
48 | }),
49 | copy({
50 | targets: [{ src: `src/${libName}/index.d.ts`, dest: "dist" }],
51 | })
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/docs/components/Button.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | export default styled.button`
3 | background: none;
4 | border: none;
5 | font-size: 2.5em;
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | cursor: pointer;
10 | user-select: none;
11 | &:disabled {
12 | cursor: not-allowed;
13 | }
14 | `;
15 |
--------------------------------------------------------------------------------
/src/docs/components/Description.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Description = styled.div`
4 | padding-top: 10px;
5 | `;
6 |
7 | export default Description;
8 |
--------------------------------------------------------------------------------
/src/docs/components/Flex.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Flex = styled.div`
4 | display: flex;
5 | flex-direction: ${({ direction }) => direction};
6 | `;
7 |
8 | export default Flex;
9 |
--------------------------------------------------------------------------------
/src/docs/components/Item.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Item = styled.div`
4 | transition: transform 200ms ease;
5 | box-sizing: border-box;
6 | width: 100%;
7 | max-width: 100%;
8 | padding: 15px;
9 | margin: 5px;
10 | border: 1px solid
11 | ${({ active }) => (active ? "rgba(0,0,0,.2)" : "rgba(0,0,0,.02)")};
12 | color: #fff;
13 | background-color: #673ab7;
14 | &:hover {
15 | cursor: pointer;
16 | }
17 | `;
18 |
19 | export default Item;
20 |
--------------------------------------------------------------------------------
/src/docs/components/ItemContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import Flex from "./Flex";
4 | import Item from "./Item";
5 | import Title from "./Title";
6 | import Poster from "./Poster";
7 | import Description from "./Description";
8 |
9 | class ItemContainer extends Component {
10 | onClick = () => {
11 | const { onClick, id } = this.props;
12 | onClick && onClick(id);
13 | };
14 | render() {
15 | const { title, img, description } = this.props;
16 | return (
17 | -
18 |
19 | {title}
20 |
21 | {img && }
22 | {description}
23 |
24 |
25 |
26 | );
27 | }
28 | }
29 |
30 | ItemContainer.propTypes = {
31 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
32 | title: PropTypes.string,
33 | img: PropTypes.string,
34 | description: PropTypes.string,
35 | onClick: PropTypes.func
36 | };
37 |
38 | export default ItemContainer;
39 |
--------------------------------------------------------------------------------
/src/docs/components/ItemWithLink.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import Item from "./SimpleItem";
4 |
5 | const ItemWithLink = ({ tabIndex, text }) => (
6 | -
7 |
8 | {text}
9 |
10 |
11 | );
12 |
13 | ItemWithLink.propTypes = {
14 | text: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
15 | tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
16 | };
17 |
18 | export default ItemWithLink;
19 |
--------------------------------------------------------------------------------
/src/docs/components/Poster.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Poster = styled.div.attrs(({ src }) => {
4 | return {
5 | style: {
6 | backgroundImage: `url(${src})`
7 | }
8 | };
9 | })`
10 | background-repeat: no-repeat;
11 | background-size: cover;
12 | background-position: center;
13 | height: 200px;
14 | `;
15 |
16 | export default Poster;
17 |
--------------------------------------------------------------------------------
/src/docs/components/SimpleItem.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import Item from "./Item";
3 |
4 | const SimpleItem = styled(Item)`
5 | font-family: Comic Sans MS, Comic Sans, cursive;
6 | font-weight: 100;
7 | display: flex;
8 | min-height: 100px;
9 | justify-content: center;
10 | align-items: center;
11 | font-size: 2.5em;
12 | max-width: ${({ maxWidth }) => maxWidth};
13 | `;
14 |
15 | export default SimpleItem;
16 |
--------------------------------------------------------------------------------
/src/docs/components/Square.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export default styled.div`
4 | cursor: pointer;
5 | transition: all 250ms ease-in;
6 | background-color: ${({ active }) => (active ? "#8bc34a" : "transparent")};
7 | color: ${({ active }) => (active ? "#fff" : "333")};
8 | transform: scale(${({ active }) => (!active ? 1.1 : 1)});
9 | box-shadow: 0 0 2px 1px #555;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | width: 20px;
14 | height: 20px;
15 | margin: 10px 10px;
16 | `;
17 |
--------------------------------------------------------------------------------
/src/docs/components/Title.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Title = styled.h3`
4 | margin: 0 0 5px 0;
5 | text-transform: uppercase;
6 | `;
7 |
8 | export default Title;
9 |
--------------------------------------------------------------------------------
/src/docs/components/breakPoints.js:
--------------------------------------------------------------------------------
1 | export default [
2 | { width: 1, itemsToShow: 1 },
3 | { width: 550, itemsToShow: 2, itemsToScroll: 2 },
4 | { width: 850, itemsToShow: 3 },
5 | { width: 1150, itemsToShow: 4, itemsToScroll: 2 },
6 | { width: 1450, itemsToShow: 5 },
7 | { width: 1750, itemsToShow: 6 }
8 | ];
9 |
--------------------------------------------------------------------------------
/src/docs/components/itemsCollection.js:
--------------------------------------------------------------------------------
1 | import { numberToArray } from "../../react-elastic-carousel/utils/helpers";
2 | export default (numOfItems = 12) =>
3 | numberToArray(numOfItems).map(id => ({
4 | id,
5 | title: `This is item #${id + 1}`,
6 | description: `this is the description for item ${id + 1}`,
7 | img: `https://picsum.photos/450/250?${id}`
8 | }));
9 |
--------------------------------------------------------------------------------
/src/docs/mdx/autoTabIndexVisibleItems.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: autoTabIndexVisibleItems
3 | route: /autoTabIndexVisibleItems
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import ItemWithLink from '../components/ItemWithLink';
10 |
11 | # autoTabIndexVisibleItems
12 | Automatically inject `tabIndex:0` to visible items and `tabIndex:-1` to none visible items.
13 |
14 | **This prop defaults to `true`.**
15 |
16 | In some cases you might want users to directly focus nested elements inside the Item,
17 | so you can grab that `tabIndex` and pass it to whatever nested element you wish.
18 |
19 | In this example we pass the injected `tabIndex` to the ` ` element:
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | ### link1
34 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
35 |
36 |
37 |
38 | ### link2
39 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
40 |
41 |
42 |
43 | ### link3
44 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
45 |
46 |
47 |
48 | ### link4
49 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
50 |
51 |
52 |
53 | ### link5
54 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
55 |
56 |
57 |
58 | ### link6
59 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
--------------------------------------------------------------------------------
/src/docs/mdx/breakpoints.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: breakPoints
3 | route: /breakPoints
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # breakPoints
12 | _This is the coolest feature and one of the main reasons that `react-elastic-carousel` was written._
13 |
14 | The `breakPoints` array, allows you to set different `props` for different sizes of the element, regardless to the window's size.
15 | This is due to the fact that `react-elastic-carousel` is listening to the element's `resize` event instead of the window.
16 |
17 | #### You can pass an array of objects that will signal `react-elastic-carousel` to change props like `itemsToShow` and `itemsToScroll` (and others) on different `width` values.
18 |
19 | **ANY** prop can be passed directly to React Elastic Carousel or via a the breakpoint object. Keep in mind that a prop passed via a breakpoint will override a direct prop.
20 |
21 | ## Example
22 | (_Resize the container to see the changes..._)
23 |
24 |
25 | {
26 | class App extends React.Component{
27 | constructor(props){
28 | super(props);
29 | this.breakPoints = [
30 | { width: 1, itemsToShow: 1 },
31 | { width: 550, itemsToShow: 2, itemsToScroll: 2, pagination: false },
32 | { width: 850, itemsToShow: 3 },
33 | { width: 1150, itemsToShow: 4, itemsToScroll: 2 },
34 | { width: 1450, itemsToShow: 5 },
35 | { width: 1750, itemsToShow: 6 }
36 | ];
37 | }
38 | render(){
39 | return(
40 |
41 | - 1
42 | - 2
43 | - 3
44 | - 4
45 | - 5
46 | - 6
47 |
48 | );
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/docs/mdx/easing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: easing
3 | route: /easing
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # easing
12 |
13 | #### You can pass an easing pattern for the animations
14 |
15 |
16 |
21 | - 1
22 | - 2
23 | - 3
24 | - 4
25 | - 5
26 | - 6
27 |
28 |
--------------------------------------------------------------------------------
/src/docs/mdx/enableAutoPlay.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: enableAutoPlay
3 | route: /enableAutoPlay
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # enableAutoPlay
12 | #### Toggle `enableAutoPlay` and set `autoPlaySpeed` as you wish
13 |
14 |
15 |
16 | - 1
17 | - 2
18 | - 3
19 | - 4
20 | - 5
21 | - 6
22 |
23 |
--------------------------------------------------------------------------------
/src/docs/mdx/focusOnSelect.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: focusOnSelect
3 | route: /focusOnSelect
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # focusOnSelect
12 | #### Sliding to an item on click
13 |
14 |
15 |
19 | - 1
20 | - 2
21 | - 3
22 | - 4
23 | - 5
24 | - 6
25 |
26 |
--------------------------------------------------------------------------------
/src/docs/mdx/goTo.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: goTo
3 | route: /goTo
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # goTo
12 | #### You can trigger the `goTo` method via a `ref` to the `Carousel`:
13 | ```
14 | this.carousel.goTo(someIndex)
15 | ```
16 |
17 |
18 | {
19 | class App extends React.Component {
20 | constructor(props){
21 | super(props);
22 | this.goto = this.goto.bind(this);
23 | }
24 | goto({target}){this.carousel.goTo(Number(target.value))}
25 | render() {
26 | return(
27 |
28 | Go to
29 |
30 |
31 | this.carousel = ref}>
32 | - 1
33 | - 2
34 | - 3
35 | - 4
36 | - 5
37 | - 6
38 |
39 |
40 | );
41 | }
42 | }
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/src/docs/mdx/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Getting started
3 | route: /
4 | ---
5 |
6 | import {Playground, Props } from 'docz';
7 | import Carousel from 'react-elastic-carousel';
8 | import Item from '../components/SimpleItem';
9 |
10 | # Getting started
11 |
12 | ## install
13 |
14 | ```bash
15 | npm install react-elastic-carousel
16 | ```
17 |
18 | ## Import
19 |
20 | ```js
21 | import Carousel from 'react-elastic-carousel'
22 | ```
23 |
24 | ## play
25 |
26 |
27 |
28 | - 1
29 | - 2
30 | - 3
31 | - 4
32 | - 5
33 | - 6
34 |
35 |
36 |
37 | ## Props
38 |
39 |
40 |
41 | ## Styling
42 |
43 | Almost every element in `react-elastic-carousel` has a css class with the `rec-` prefix (rec is short React Elastic Carousel).
44 | For example: `rec-arrow` for both arrow buttons or `rec-arrow-left` just for the left one.
45 |
46 | You can see a code example [here](/styling).
--------------------------------------------------------------------------------
/src/docs/mdx/initialFirstItem.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: initialActiveIndex
3 | route: /initialActiveIndex
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # initialActiveIndex
12 |
13 | #### The first active item on mount
14 |
15 |
16 |
17 | - 1
18 | - 2
19 | - 3
20 | - 4
21 | - 5
22 | - 6
23 |
24 |
25 |
26 | ---
27 | > If you are using the old prop `initialFirstItem`, then note that its deprecated and you should use `initialActiveIndex`.
28 |
--------------------------------------------------------------------------------
/src/docs/mdx/isRTL.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: isRTL
3 | route: /isRTL
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # isRTL
12 | #### Support for `right-to-left` layouts
13 |
14 |
15 |
16 | - 1
17 | - 2
18 | - 3
19 | - 4
20 | - 5
21 | - 6
22 |
23 |
--------------------------------------------------------------------------------
/src/docs/mdx/itemPadding.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: itemPadding
3 | route: /itemPadding
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # itemPadding
12 | #### Sets a `padding` to the wrapping element of each item
13 |
14 |
15 |
16 | - 1
17 | - 2
18 | - 3
19 | - 4
20 | - 5
21 | - 6
22 |
23 |
--------------------------------------------------------------------------------
/src/docs/mdx/itemPosition.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: itemPosition
3 | route: /itemPosition
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel, {consts} from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # itemPosition
12 | #### Sets the position of the item within the item's wrapper.
13 | _You should import the `consts` object to pass the desired `itemPosition`_
14 |
15 | ```
16 | import Carousel, { consts } from 'react-elastic-carousel';
17 | ```
18 |
19 |
20 |
21 | - 1
22 | - 3
23 | - 4
24 | - 5
25 |
26 |
--------------------------------------------------------------------------------
/src/docs/mdx/itemsToScroll.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: itemsToScroll
3 | route: /itemsToScroll
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # itemsToScroll
12 | #### Number of items to scroll
13 |
14 |
15 |
16 | - 1
17 | - 2
18 | - 3
19 | - 4
20 | - 5
21 | - 6
22 |
23 |
--------------------------------------------------------------------------------
/src/docs/mdx/itemsToShow.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: itemsToShow
3 | route: /itemsToShow
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # itemsToShow
12 | #### Number of visible items
13 |
14 |
15 |
16 | - 1
17 | - 2
18 | - 3
19 | - 4
20 | - 5
21 | - 6
22 |
23 |
--------------------------------------------------------------------------------
/src/docs/mdx/onChange.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: onChange
3 | route: /onChange
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # onChange
12 | #### `onChange` will pass the new __current__ `item-object` and __current__ `page-index`:
13 | ```
14 | (currentItem, pageIndex) => {/*...*/}
15 | ```
16 | - The `item-object` has the shape of:
17 | ```
18 | {
19 | item: {/* your item */},
20 | index: // the item's index number
21 | }
22 | ```
23 |
24 |
25 |
26 | alert(JSON.stringify({currentItem, pageIndex}))}
29 | >
30 | - 1
31 | - 2
32 | - 3
33 | - 4
34 | - 5
35 | - 6
36 |
37 |
--------------------------------------------------------------------------------
/src/docs/mdx/onNextEnd.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: onNextEnd
3 | route: /onNextEnd
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # onNextEnd
12 | #### `onNextEnd` will pass the new __current__ `item-object` and __current__ `page-index`:
13 | ```
14 | (currentItem, pageIndex) => {/*...*/}
15 | ```
16 | - The `item-object` has the shape of:
17 | ```
18 | {
19 | item: {/* your item */},
20 | index: // the item's index number
21 | }
22 | ```
23 |
24 |
25 |
26 | alert(JSON.stringify({currentItem, pageIndex}))}
29 | >
30 | - 1
31 | - 2
32 | - 3
33 | - 4
34 | - 5
35 | - 6
36 |
37 |
--------------------------------------------------------------------------------
/src/docs/mdx/onNextStart.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: onNextStart
3 | route: /onNextStart
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # onNextStart
12 | #### `onNextStart` will pass both the __current__ and __next__ `item-object`:
13 | ```
14 | (currentItem, nextItem) => {/*...*/}
15 | ```
16 | - The `item-object` has the shape of:
17 | ```
18 | {
19 | item: {/* your item */},
20 | index: // the item's index number
21 | }
22 | ```
23 |
24 |
25 |
26 | alert(JSON.stringify(currentItem) + `\n` + JSON.stringify(nextItem))}
29 | >
30 | - 1
31 | - 2
32 | - 3
33 | - 4
34 | - 5
35 | - 6
36 |
37 |
--------------------------------------------------------------------------------
/src/docs/mdx/onPrevEnd.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: onPrevEnd
3 | route: /onPrevEnd
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # onPrevEnd
12 | #### `onPrevEnd` will pass the new __current__ `item-object` and __current__ `page-index`:
13 | ```
14 | (currentItem, pageIndex) => {/*...*/}
15 | ```
16 | - The `item-object` has the shape of:
17 | ```
18 | {
19 | item: {/* your item */},
20 | index: // the item's index number
21 | }
22 | ```
23 |
24 |
25 |
26 | alert(JSON.stringify({currentItem, pageIndex}))}
30 | >
31 | - 1
32 | - 2
33 | - 3
34 | - 4
35 | - 5
36 | - 6
37 |
38 |
--------------------------------------------------------------------------------
/src/docs/mdx/onPrevStart.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: onPrevStart
3 | route: /onPrevStart
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # onPrevStart
12 | #### `onPrevStart` will pass both the __current__ and __next__ `item-object`:
13 | ```
14 | (currentItem, nextItem) => {/*...*/}
15 | ```
16 | - The `item-object` has the shape of:
17 | ```
18 | {
19 | item: {/* your item */},
20 | index: // the item's index number
21 | }
22 | ```
23 |
24 |
25 |
26 | alert(JSON.stringify(currentItem) + `\n` + JSON.stringify(nextItem))}
30 | >
31 | - 1
32 | - 2
33 | - 3
34 | - 4
35 | - 5
36 | - 6
37 |
38 |
--------------------------------------------------------------------------------
/src/docs/mdx/onResize.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: onResize
3 | route: /onResize
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 | import breakPoints from '../components/breakPoints';
11 |
12 | # onResize
13 | #### You can listen to the `onResize` event of the `Carousel`, this will get invoked on each `resize` event of the root element and will return the current `breakPoint` object being used:
14 |
15 | ```
16 | (currentBreakPoint) => {/*...*/}
17 | ```
18 |
19 |
20 | console.log(currentBreakPoint)}>
21 | - 1
22 | - 2
23 | - 3
24 | - 4
25 | - 5
26 | - 6
27 |
28 |
--------------------------------------------------------------------------------
/src/docs/mdx/outerSpacing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: outerSpacing
3 | route: /outerSpacing
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # outerSpacing
12 | #### Sets a margin to the beginning and the end of the slider
13 | #### (not compatible with `verticalMode` yet !)
14 |
15 |
16 |
17 | - 1
18 | - 2
19 | - 3
20 | - 4
21 | - 5
22 | - 6
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/docs/mdx/renderArrow.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: renderArrow
3 | route: /renderArrow
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel, {consts} from 'react-elastic-carousel';
9 | import Button from '../components/Button';
10 | import Item from '../components/SimpleItem';
11 |
12 | # renderArrow
13 |
14 | ### You can provide custom Arrow buttons.
15 |
16 | ## Signature
17 |
18 | - You can pass a function that returns a react element,
19 | This function is a [render prop](https://reactjs.org/docs/render-props.html) and its signature is:
20 | ```
21 | ({type, onClick, isEdge}) => {/* return your element here */}
22 | ```
23 |
24 | - `type` will be either `consts.PREV` or `consts.NEXT`, depends on the relevant arrow direction.
25 | - `onClick` Is the hook to the inner `onClick` of the `Carousel`. You can attach it wherever you want within the returned component.
26 | - `isEdge` Will be true when there are no more items to show for this direction.
27 |
28 | _You should import the `consts` object to check the arrow direction_
29 | ```
30 | import Carousel, { consts } from 'react-elastic-carousel';
31 | // ....
32 |
33 | type === consts.PREV
34 | ```
35 |
36 | ## Demo
37 |
38 |
39 | {
40 | class App extends React.Component{
41 | myArrow({type, onClick, isEdge}){
42 | const pointer = type === consts.PREV ? '👈' : '👉';
43 | return (
44 |
45 | {pointer}
46 |
47 | );
48 | }
49 | render(){
50 | return(
51 |
52 |
53 | - 1
54 | - 2
55 | - 3
56 | - 4
57 | - 5
58 | - 6
59 |
60 |
61 | );
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/docs/mdx/renderPagination.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: renderPagination
3 | route: /renderPagination
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel, {consts} from 'react-elastic-carousel';
9 | import Square from '../components/Square';
10 | import Flex from '../components/Flex';
11 | import Item from '../components/SimpleItem';
12 |
13 | # renderPagination
14 |
15 | ### You can provide custom pagination buttons.
16 |
17 | ## Signature
18 |
19 | - You can pass a function that returns a react element,
20 | This function is a [render prop](https://reactjs.org/docs/render-props.html) and its signature is:
21 | ```
22 | ({ pages, activePage, onClick }) => {/* return your element here */}
23 | ```
24 |
25 | - `pages` Array of page index.
26 | - `activePage` The current active page id.
27 | - `onClick` Is the hook to the inner `onClick` of the `Carousel`. You can attach it wherever you want within the returned component.
28 |
29 | ## Demo
30 |
31 |
32 | {
36 | return (
37 |
38 | {
39 | pages.map((page) => {
40 | const isActivePage = activePage === page;
41 | return (
42 | onClick(page)} active={isActivePage}/>
43 | )
44 | })}
45 |
46 | )
47 | }}
48 | >
49 | - 1
50 | - 2
51 | - 3
52 | - 4
53 | - 5
54 | - 6
55 | - 7
56 | - 8
57 | - 9
58 | - 10
59 |
60 |
--------------------------------------------------------------------------------
/src/docs/mdx/showEmptySlots.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: showEmptySlots
3 | route: /showEmptySlots
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # showEmptySlots
12 | #### Shows empty slots when there are less children than items to show (`children.length` < `itemsToShow`)
13 | #### (not compatible with `verticalMode` yet !)
14 |
15 |
16 |
17 | - 1
18 | - 2
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/docs/mdx/slideNext.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: slideNext / slidePrev
3 | route: /slideNext
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # slideNext / slidePrev
12 | #### You can trigger the `slideNext` or `slidePrev` methods via a `ref` to the `Carousel`:
13 |
14 | ```
15 | this.carousel.slideNext()
16 | this.carousel.slidePrev()
17 | ```
18 |
19 |
20 | {
21 | class App extends React.Component{
22 | render(){
23 | return(
24 |
25 | this.carousel.slidePrev()}>Prev
26 | this.carousel.slideNext()}>Next
27 |
28 | this.carousel = ref}>
29 | - 1
30 | - 2
31 | - 3
32 | - 4
33 | - 5
34 | - 6
35 |
36 |
37 | );
38 | }
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/docs/mdx/styling.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Styling
3 | route: /styling
4 | menu: Examples
5 | ---
6 |
7 | import {Playground, Props } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 | import '../styles/styling.css';
11 |
12 | # Styling
13 | #### Almost every element in `react-elastic-carousel` has a css class with the `rec-` prefix (rec is short React Elastic Carousel).
14 |
15 | ```css
16 | /* square buttons */
17 | .rec.rec-arrow {
18 | border-radius: 0;
19 | }
20 |
21 | /* round buttons on hover */
22 | .rec.rec-arrow:hover {
23 | border-radius: 50%;
24 | }
25 |
26 | /* hide disabled buttons */
27 | .rec.rec-arrow:disabled {
28 | visibility: hidden;
29 | }
30 |
31 | /* disable default outline on focused items */
32 | /* add custom outline on focused items */
33 | .rec-carousel-item:focus {
34 | outline: none;
35 | box-shadow: inset 0 0 1px 1px lightgrey;
36 | }
37 | ```
38 |
39 |
40 |
41 |
42 |
43 | - 1
44 | - 2
45 | - 3
46 | - 4
47 | - 5
48 | - 6
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/docs/mdx/verticalMode.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: verticalMode
3 | route: /verticalMode
4 | menu: Examples
5 | ---
6 |
7 | import {Playground } from 'docz';
8 | import Carousel from 'react-elastic-carousel';
9 | import Item from '../components/SimpleItem';
10 |
11 | # verticalMode
12 |
13 | #### Display and animate the Carousel in a vertical layout
14 |
15 |
16 |
17 | - 1
18 | - 2
19 | - 3
20 | - 4
21 | - 5
22 | - 6
23 | - 7
24 | - 8
25 |
26 |
--------------------------------------------------------------------------------
/src/docs/styles/styling.css:
--------------------------------------------------------------------------------
1 | /* square buttons */
2 | .styling-example .rec.rec-arrow {
3 | border-radius: 0;
4 | }
5 |
6 | /* round buttons on hover */
7 | .styling-example .rec.rec-arrow:hover {
8 | border-radius: 50%;
9 | }
10 | /* hide disabled buttons */
11 | .styling-example .rec.rec-arrow:disabled {
12 | visibility: hidden;
13 | }
14 | /* disable default outline on focused items */
15 | /* add custom outline on focused items */
16 | .styling-example .rec-carousel-item:focus {
17 | outline: none;
18 | box-shadow: inset 0 0 1px 1px lightgrey;
19 | }
20 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/actions/consts.js:
--------------------------------------------------------------------------------
1 | export const NEXT_ITEM = "NEXT_ITEM";
2 | export const PREV_ITEM = "PREV_ITEM";
3 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/actions/itemsActions.js:
--------------------------------------------------------------------------------
1 | import { NEXT_ITEM, PREV_ITEM } from "./consts";
2 |
3 | export const nextItemAction = (limit, itemsToScroll) => ({
4 | type: NEXT_ITEM,
5 | limit,
6 | itemsToScroll
7 | });
8 |
9 | export const prevItemAction = (limit, itemsToScroll) => ({
10 | type: PREV_ITEM,
11 | limit,
12 | itemsToScroll
13 | });
14 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/Arrow.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import cx from "classnames";
4 | import { cssPrefix } from "../utils/helpers";
5 | import { Button } from "./styled";
6 |
7 | const directionIcons = {
8 | left: "❮",
9 | right: "❯",
10 | up: "❮",
11 | down: "❯"
12 | };
13 |
14 | const arrowClassname = cssPrefix("arrow");
15 |
16 | const rotateStyle = direction => {
17 | let rotate = {};
18 | if (direction === Arrow.up || direction === Arrow.down) {
19 | rotate.transform = "rotate(90deg)";
20 | }
21 | return rotate;
22 | };
23 | const Arrow = ({ direction, onClick, icons, style, ...rest }) => {
24 | const arrows = { ...directionIcons, ...icons };
25 | const styleObj = {
26 | ...rotateStyle(direction),
27 | ...style
28 | };
29 | return (
30 |
37 | {arrows[direction]}
38 |
39 | );
40 | };
41 |
42 | Arrow.left = "left";
43 | Arrow.right = "right";
44 | Arrow.up = "up";
45 | Arrow.down = "down";
46 |
47 | Arrow.propTypes = {
48 | direction: PropTypes.oneOf(["left", "right", "up", "down"]).isRequired,
49 | icons: PropTypes.object,
50 | style: PropTypes.object,
51 | onClick: PropTypes.func
52 | };
53 |
54 | export default Arrow;
55 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/Carousel.js:
--------------------------------------------------------------------------------
1 | import React, { Children } from "react";
2 | import PropTypes from "prop-types";
3 | import ResizeObserver from "resize-observer-polyfill";
4 | import Only from "react-only-when";
5 | import Track from "./Track";
6 | import Arrow from "./Arrow";
7 | import consts from "../consts";
8 | import { activeIndexReducer } from "../reducers/items";
9 | import { nextItemAction, prevItemAction } from "../actions/itemsActions";
10 | import {
11 | SliderContainer,
12 | Slider,
13 | StyledCarousel,
14 | CarouselWrapper
15 | } from "./styled";
16 | import { pipe, noop, cssPrefix, numberToArray } from "../utils/helpers";
17 | import { Pagination } from "./Pagination";
18 |
19 | class Carousel extends React.Component {
20 | isComponentMounted = false;
21 | state = {
22 | rootHeight: 0,
23 | childHeight: 0,
24 | sliderPosition: 0,
25 | swipedSliderPosition: 0,
26 | isSwiping: false,
27 | transitioning: false,
28 | transitionMs: this.props.transitionMs,
29 | activeIndex: this.props.initialActiveIndex || this.props.initialFirstItem, // support deprecated initialFirstItem
30 | pages: [],
31 | activePage: 0,
32 | sliderContainerWidth: 0
33 | };
34 |
35 | componentDidMount() {
36 | this.isComponentMounted = true;
37 | this.initResizeObserver();
38 | this.updateActivePage();
39 | this.setPages();
40 | }
41 |
42 | componentDidUpdate(prevProps, prevState) {
43 | const {
44 | enableAutoPlay,
45 | children,
46 | itemsToShow,
47 | itemsToScroll,
48 | breakPoints
49 | } = this.props;
50 | const { activeIndex, sliderContainerWidth } = this.state;
51 | const nextItem = this.getNextItemIndex(activeIndex, false);
52 | const currentChildrenLength = Children.toArray(children).length;
53 | const prevChildrenLength = Children.toArray(prevProps.children).length;
54 | // update pages (for pagination)
55 | if (
56 | prevChildrenLength !== currentChildrenLength ||
57 | prevProps.itemsToShow !== itemsToShow ||
58 | prevProps.itemsToScroll !== itemsToScroll ||
59 | prevProps.breakPoints !== breakPoints ||
60 | sliderContainerWidth !== prevState.sliderContainerWidth
61 | ) {
62 | // we mimic a container resize to recalculate item width when itemsToShow are updated
63 | this.onContainerResize({ contentRect: { width: sliderContainerWidth } });
64 | this.setPages();
65 | this.updateActivePage();
66 | }
67 |
68 | // autoplay update
69 | if (activeIndex === nextItem) {
70 | this.removeAutoPlay();
71 | } else if (enableAutoPlay && !this.autoPlayIntervalId) {
72 | this.setAutoPlay();
73 | } else if (!enableAutoPlay && this.autoPlayIntervalId) {
74 | this.removeAutoPlay();
75 | }
76 |
77 | if (prevChildrenLength !== currentChildrenLength) {
78 | const {
79 | itemsToShow: calculatedItemsToShow
80 | } = this.getDerivedPropsFromBreakPoint();
81 | // number of items is reduced (we don't care if number of items is increased)
82 | // we need to check if our current index is not out of boundaries
83 | // we need to include itemsToShow so we can fill up the slots
84 | const lastIndex = currentChildrenLength - 1;
85 | const isOutOfRange = activeIndex + calculatedItemsToShow > lastIndex;
86 | if (isOutOfRange) {
87 | // we are out of boundaries, go "back" to last item of the list (respect itemsToShow)
88 | this.goTo(Math.max(0, currentChildrenLength - calculatedItemsToShow));
89 | }
90 | }
91 | }
92 |
93 | componentWillUnmount() {
94 | this.isComponentMounted = false;
95 | this.removeAutoPlay();
96 | this.unSubscribeObserver();
97 | }
98 |
99 | setRef = name => ref => (this[name] = ref);
100 |
101 | initResizeObserver = () => {
102 | this.ro = new ResizeObserver((entries, observer) => {
103 | for (const entry of entries) {
104 | if (entry.target === this.sliderContainer) {
105 | // we are using rAF because it fixes the infinite refresh with gatsby (ssr?).
106 | // TBH, I'm not sure i fully understand why.
107 | // see https://github.com/sag1v/react-elastic-carousel/issues/107
108 | window.requestAnimationFrame(() => {
109 | this.onContainerResize(entry);
110 | });
111 | }
112 | if (entry.target === this.slider) {
113 | // we are using rAF because it fixes the infinite refresh with gatsby (ssr?).
114 | // TBH, I'm not sure i fully understand why
115 | // see https://github.com/sag1v/react-elastic-carousel/issues/107
116 | window.requestAnimationFrame(() => {
117 | this.onSliderResize(entry);
118 | });
119 | }
120 | }
121 | });
122 |
123 | this.ro.observe(this.sliderContainer);
124 | this.ro.observe(this.slider);
125 | };
126 |
127 | unSubscribeObserver = () => this.ro.disconnect();
128 |
129 | setAutoPlay = () => {
130 | const { autoPlaySpeed } = this.getDerivedPropsFromBreakPoint();
131 | this.autoPlayIntervalId = setInterval(() => {
132 | if (this.isComponentMounted) {
133 | const { transitioning } = this.state;
134 | if (!transitioning) {
135 | this.slideNext();
136 | }
137 | }
138 | }, autoPlaySpeed);
139 | };
140 |
141 | removeAutoPlay = () => {
142 | if (this.autoPlayIntervalId) {
143 | clearInterval(this.autoPlayIntervalId);
144 | this.autoPlayIntervalId = null;
145 | }
146 | };
147 |
148 | setPages = () => {
149 | const numOfPages = this.getNumOfPages();
150 | const pages = numberToArray(numOfPages);
151 | this.setState({ pages });
152 | };
153 |
154 | onSliderTransitionEnd = fn => {
155 | this.slider.addEventListener("transitionend", fn);
156 | };
157 |
158 | removeSliderTransitionHook = fn => {
159 | this.slider.removeEventListener("transitionend", fn);
160 | };
161 |
162 | getDerivedPropsFromBreakPoint = () => {
163 | const { breakPoints, ...restOfProps } = this.props;
164 | const { sliderContainerWidth } = this.state;
165 |
166 | // default breakpoint from individual props
167 | let currentBreakPoint;
168 | // if breakpoints were added as props override the individual props
169 | if (breakPoints && breakPoints.length > 0) {
170 | currentBreakPoint = breakPoints
171 | .slice() // no mutations
172 | .reverse() // so we can find last match
173 | .find(bp => bp.width <= sliderContainerWidth);
174 | if (!currentBreakPoint) {
175 | /* in case we don't have a lower width than sliderContainerWidth
176 | * this mostly happens in initilization when sliderContainerWidth is 0
177 | */
178 | currentBreakPoint = breakPoints[0];
179 | }
180 | }
181 | // merge direct props with current breakpoint Props
182 | return { ...restOfProps, ...currentBreakPoint };
183 | };
184 |
185 | updateSliderPosition = () => {
186 | this.setState(state => {
187 | const {
188 | children,
189 | verticalMode,
190 | itemsToShow,
191 | transitionMs
192 | } = this.getDerivedPropsFromBreakPoint();
193 | const { childHeight, activeIndex } = state;
194 |
195 | const childWidth = this.calculateChildWidth();
196 | const totalItems = Children.toArray(children).length;
197 | const hiddenSlots = totalItems - itemsToShow;
198 | let moveBy = activeIndex * -1;
199 | const emptySlots = itemsToShow - (totalItems - activeIndex);
200 | if (emptySlots > 0 && hiddenSlots > 0) {
201 | moveBy = emptySlots + activeIndex * -1;
202 | }
203 | let sliderPosition = (verticalMode ? childHeight : childWidth) * moveBy;
204 | const newActiveIndex =
205 | emptySlots > 0 ? activeIndex - emptySlots : activeIndex;
206 | // go back from 0ms to whatever set by the user
207 | // We were at 0ms because we wanted to disable animation on resize
208 | // see https://github.com/sag1v/react-elastic-carousel/issues/94
209 | window.requestAnimationFrame(() => {
210 | if (this.isComponentMounted) {
211 | this.setState({ transitionMs });
212 | }
213 | });
214 | return {
215 | sliderPosition,
216 | activeIndex: newActiveIndex < 0 ? 0 : newActiveIndex
217 | };
218 | });
219 | };
220 |
221 | onSliderResize = sliderNode => {
222 | if (!this.isComponentMounted) {
223 | return;
224 | }
225 |
226 | const {
227 | verticalMode,
228 | children,
229 | itemsToShow
230 | } = this.getDerivedPropsFromBreakPoint();
231 | const { height: sliderHeight } = sliderNode.contentRect;
232 | const nextState = {};
233 | const childrenLength = Children.toArray(children).length;
234 | if (verticalMode) {
235 | const childHeight = sliderHeight / childrenLength;
236 | // We use Math.min because we don't want to make the child smaller
237 | // if the number of children is smaller than itemsToShow.
238 | // (Because we do not want "empty slots")
239 | nextState.rootHeight =
240 | childHeight * Math.min(childrenLength, itemsToShow);
241 | nextState.childHeight = childHeight;
242 | } else {
243 | nextState.rootHeight = sliderHeight;
244 | }
245 | this.setState(nextState);
246 | };
247 |
248 | calculateChildWidth = () => {
249 | const { sliderContainerWidth } = this.state;
250 | const {
251 | verticalMode,
252 | itemsToShow,
253 | showEmptySlots,
254 | children
255 | } = this.getDerivedPropsFromBreakPoint();
256 |
257 | /* based on slider container's width, get num of items to show
258 | * and calculate child's width (and update it in state)
259 | */
260 | const childrenLength = Children.toArray(children).length || 1;
261 |
262 | let childWidth = 0;
263 | if (verticalMode) {
264 | childWidth = sliderContainerWidth;
265 | } else {
266 | // When "showEmptySlots" is false
267 | // We use Math.min because we don't want to make the child smaller
268 | // if the number of children is smaller than itemsToShow.
269 | // (Because we do not want "empty slots")
270 | childWidth =
271 | sliderContainerWidth /
272 | (showEmptySlots ? itemsToShow : Math.min(childrenLength, itemsToShow));
273 | }
274 | return childWidth;
275 | };
276 |
277 | onContainerResize = sliderContainerNode => {
278 | const { width: newSliderContainerWidth } = sliderContainerNode.contentRect;
279 | // update slider container width
280 | // disable animation on resize see https://github.com/sag1v/react-elastic-carousel/issues/94
281 | const {
282 | outerSpacing,
283 | verticalMode: initialVerticalMode
284 | } = this.getDerivedPropsFromBreakPoint();
285 | const containerWidth =
286 | newSliderContainerWidth - (initialVerticalMode ? 0 : outerSpacing * 2);
287 |
288 | if (
289 | !this.isComponentMounted ||
290 | this.state.sliderContainerWidth === newSliderContainerWidth
291 | ) {
292 | // prevent infinite loop
293 | return;
294 | }
295 | this.setState(
296 | { sliderContainerWidth: containerWidth, transitionMs: 0 },
297 | () => {
298 | // we must get these props inside setState (get future props because its async)
299 | const {
300 | onResize,
301 | itemsToShow,
302 | children
303 | } = this.getDerivedPropsFromBreakPoint();
304 |
305 | const childrenLength = Children.toArray(children).length || 1;
306 |
307 | this.setState(
308 | currentState => {
309 | // We might need to change the selected index when the size of the container changes
310 | // we are making sure the selected index is not out of boundaries and respecting itemsToShow
311 | // This usually happens with breakpoints. see https://github.com/sag1v/react-elastic-carousel/issues/122
312 | let activeIndex = currentState.activeIndex;
313 | // we take the lowest, in case itemsToShow is greater than childrenLength
314 | const maxItemsToShow = Math.min(childrenLength, itemsToShow);
315 | const endLimit = childrenLength - maxItemsToShow;
316 | if (activeIndex > endLimit) {
317 | activeIndex = endLimit;
318 | }
319 |
320 | return { activeIndex };
321 | },
322 | () => {
323 | /* Based on all of the above new data:
324 | * update slider position
325 | * get the new current breakpoint
326 | * pass the current breakpoint to the consumer's callback
327 | */
328 | this.updateSliderPosition();
329 | const currentBreakPoint = this.getDerivedPropsFromBreakPoint();
330 | onResize(currentBreakPoint);
331 | }
332 | );
333 | }
334 | );
335 | };
336 |
337 | tiltMovement = (position, distance = 20, duration = 150) => {
338 | this.setState(state => {
339 | return {
340 | isSwiping: true,
341 | swipedSliderPosition: position - distance
342 | };
343 | });
344 | setTimeout(() => {
345 | this.setState({
346 | isSwiping: false,
347 | swipedSliderPosition: 0
348 | });
349 | }, duration);
350 | };
351 |
352 | convertChildToCbObj = index => {
353 | const { children } = this.getDerivedPropsFromBreakPoint();
354 | // support decimal itemsToShow
355 | const roundedIdx = Math.round(index);
356 | const child = Children.toArray(children)[roundedIdx];
357 | return { item: child.props, index: roundedIdx };
358 | };
359 |
360 | getNextItemIndex = (currentIndex, getPrev) => {
361 | const {
362 | children,
363 | itemsToShow,
364 | itemsToScroll
365 | } = this.getDerivedPropsFromBreakPoint();
366 | const childrenLength = Children.toArray(children).length || 1;
367 | const notEnoughItemsToShow = itemsToShow > childrenLength;
368 | let limit = getPrev ? 0 : childrenLength - itemsToShow;
369 |
370 | if (notEnoughItemsToShow) {
371 | limit = 0; // basically don't move
372 | }
373 | const nextAction = getPrev
374 | ? prevItemAction(0, itemsToScroll)
375 | : nextItemAction(limit, itemsToScroll);
376 | const nextItem = activeIndexReducer(currentIndex, nextAction);
377 | return nextItem;
378 | };
379 |
380 | getNextItemObj = getPrev => {
381 | const { children } = this.getDerivedPropsFromBreakPoint();
382 | const { activeIndex } = this.state;
383 | const nextItemIndex = this.getNextItemIndex(activeIndex, getPrev);
384 | // support decimal itemsToShow
385 | const roundedIdx = Math.round(nextItemIndex);
386 | const asElement = Children.toArray(children)[roundedIdx];
387 | const asObj = { item: asElement.props, index: roundedIdx };
388 | return asObj;
389 | };
390 |
391 | resetSwipe = () => {
392 | this.setState({
393 | swipedSliderPosition: 0,
394 | transitioning: false,
395 | isSwiping: false
396 | });
397 | };
398 |
399 | onSwiping = data => {
400 | const { deltaX, absX, deltaY, absY, dir } = data;
401 |
402 | this.setState(state => {
403 | const { childHeight, activeIndex, sliderPosition } = state;
404 | const {
405 | itemsToShow,
406 | verticalMode,
407 | children,
408 | isRTL
409 | } = this.getDerivedPropsFromBreakPoint();
410 |
411 | const childWidth = this.calculateChildWidth();
412 |
413 | // determine how far can user swipe
414 | const childrenLength = Children.toArray(children).length || 1;
415 | const goingNext =
416 | (!verticalMode && dir === "Left" && !isRTL) ||
417 | (!verticalMode && dir === "Right" && isRTL) ||
418 | (verticalMode && dir === "Up");
419 | const goingBack =
420 | (!verticalMode && dir === "Right" && !isRTL) ||
421 | (!verticalMode && dir === "Left" && isRTL) ||
422 | (verticalMode && dir === "Down");
423 |
424 | const horizontalSwipe = dir === "Left" || dir === "Right";
425 | const verticalSwipe = dir === "Up" || dir === "Down";
426 | const horizontalMode = !verticalMode;
427 |
428 | let distanceSwipe = 0;
429 | const horizontalEdgeStoppage = childWidth / 2;
430 | const verticalEdgeStoppage = childHeight / 2;
431 |
432 | if (verticalMode) {
433 | if (verticalSwipe) {
434 | const trackSize = childrenLength * childHeight;
435 | if (goingNext) {
436 | distanceSwipe =
437 | trackSize -
438 | childHeight * activeIndex -
439 | itemsToShow * childHeight +
440 | verticalEdgeStoppage;
441 | } else if (goingBack) {
442 | distanceSwipe = childHeight * activeIndex + verticalEdgeStoppage;
443 | }
444 | }
445 | } else {
446 | if (horizontalSwipe) {
447 | const trackSize = childrenLength * childWidth;
448 | if (goingNext) {
449 | distanceSwipe =
450 | trackSize -
451 | childWidth * activeIndex -
452 | itemsToShow * childWidth +
453 | horizontalEdgeStoppage;
454 | } else if (goingBack) {
455 | distanceSwipe = childWidth * activeIndex + horizontalEdgeStoppage;
456 | }
457 | }
458 | }
459 |
460 | const shouldHorizontalSkipUpdate =
461 | (horizontalMode && verticalSwipe) ||
462 | (horizontalMode && horizontalSwipe && absX > distanceSwipe);
463 |
464 | const shouldVerticalSkipUpdate =
465 | (verticalMode && horizontalSwipe) ||
466 | (verticalMode && verticalSwipe && absY > distanceSwipe);
467 |
468 | if (shouldHorizontalSkipUpdate || shouldVerticalSkipUpdate) {
469 | // bail out of state update
470 | return;
471 | }
472 | let swipedSliderPosition;
473 | if (horizontalSwipe) {
474 | if (isRTL) {
475 | swipedSliderPosition = sliderPosition + deltaX;
476 | } else {
477 | swipedSliderPosition = sliderPosition - deltaX;
478 | }
479 | } else {
480 | swipedSliderPosition = sliderPosition - deltaY;
481 | }
482 | return {
483 | swipedSliderPosition,
484 | isSwiping: true,
485 | transitioning: true
486 | };
487 | });
488 | };
489 |
490 | onSwiped = data => {
491 | // we need to handle all scenarios:
492 | // 1. Horizontal mode - swipe left or right
493 | // 2. Horizontal mode with RTL - swipe left or right
494 | // 3. vertical mode - swipe up or down
495 |
496 | const { absX, absY, dir } = data;
497 | const { childHeight, activeIndex } = this.state;
498 | const {
499 | verticalMode,
500 | isRTL,
501 | itemsToScroll
502 | } = this.getDerivedPropsFromBreakPoint();
503 | const childWidth = this.calculateChildWidth();
504 |
505 | let func = this.resetSwipe;
506 | const minSwipeDistanceHorizontal = childWidth / 5;
507 | const minSwipeDistanceVertical = childHeight / 5;
508 | const swipedLeft = dir === "Left";
509 | const swipedRight = dir === "Right";
510 | const swipedUp = dir === "Up";
511 | const swipedDown = dir === "Down";
512 | const verticalGoSwipe =
513 | verticalMode &&
514 | (swipedUp || swipedDown) &&
515 | absY > minSwipeDistanceVertical;
516 |
517 | const horizontalGoSwipe =
518 | !verticalMode &&
519 | (swipedRight || swipedLeft) &&
520 | absX > minSwipeDistanceHorizontal;
521 |
522 | let goodToGo = false;
523 | if (verticalGoSwipe || horizontalGoSwipe) {
524 | goodToGo = true;
525 | }
526 |
527 | if (goodToGo) {
528 | // we should go to a different item
529 | // determine what method we need to invoke
530 |
531 | if (verticalMode) {
532 | // get number of slides from user's swiping
533 | const numberOfSlidesViaSwipe = Math.ceil(
534 | (absY - minSwipeDistanceVertical) / childHeight
535 | );
536 | // if user swipes more than itemsToScroll then we want to bypass itemsToScroll for a smoother scroll
537 | const numberOfSlidesTogo = Math.max(
538 | itemsToScroll,
539 | numberOfSlidesViaSwipe
540 | );
541 |
542 | const backSlidesToGo = activeIndex - numberOfSlidesTogo;
543 | const forwardSlideTtoGo = activeIndex + numberOfSlidesTogo;
544 |
545 | // up or down
546 | if (swipedDown) {
547 | // func = this.onPrevStart;
548 | func = () => this.goTo(backSlidesToGo);
549 | }
550 | if (swipedUp) {
551 | // func = this.onNextStart;
552 | func = () => this.goTo(forwardSlideTtoGo);
553 | }
554 | } else {
555 | // get number of slides from user's swiping
556 | const numberOfSlidesViaSwipe = Math.ceil(
557 | (absX - minSwipeDistanceHorizontal) / childWidth
558 | );
559 | // if user swipes more than itemsToScroll then we want to bypass itemsToScroll for a smoother scroll
560 | const numberOfSlidesTogo = Math.max(
561 | itemsToScroll,
562 | numberOfSlidesViaSwipe
563 | );
564 |
565 | const backSlidesToGo = activeIndex - numberOfSlidesTogo;
566 | const forwardSlideTtoGo = activeIndex + numberOfSlidesTogo;
567 |
568 | // horizontal mode
569 | if (isRTL) {
570 | // flip sides
571 | if (swipedLeft) {
572 | // func = this.onPrevStart;
573 | func = () => this.goTo(backSlidesToGo);
574 | }
575 | if (swipedRight) {
576 | // func = this.onNextStart;
577 | func = () => this.goTo(forwardSlideTtoGo);
578 | }
579 | } else {
580 | // normal behavior
581 | if (swipedLeft) {
582 | // func = this.onNextStart;
583 | func = () => this.goTo(forwardSlideTtoGo);
584 | }
585 | if (swipedRight) {
586 | // func = this.onPrevStart;
587 | func = () => this.goTo(backSlidesToGo);
588 | }
589 | }
590 | }
591 | }
592 | // we are not "tilting" on edges, so we need to reset isSwiping and transitioning.
593 | // otherwise we wont slide back to edge
594 | this.setState({ isSwiping: false, transitioning: false });
595 | func({ skipTilt: true });
596 | };
597 |
598 | onNextStart = options => {
599 | const { onNextStart } = this.getDerivedPropsFromBreakPoint();
600 | const { activeIndex } = this.state;
601 | const nextItemObj = this.getNextItemObj();
602 | const prevItemObj = this.convertChildToCbObj(activeIndex);
603 | onNextStart(prevItemObj, nextItemObj);
604 | this.slideNext(options);
605 | };
606 |
607 | onPrevStart = options => {
608 | const { onPrevStart } = this.getDerivedPropsFromBreakPoint();
609 | const { activeIndex } = this.state;
610 | const nextItemObj = this.getNextItemObj(true);
611 | const prevItemObj = this.convertChildToCbObj(activeIndex);
612 | onPrevStart(prevItemObj, nextItemObj);
613 | this.slidePrev(options);
614 | };
615 |
616 | slideNext = (options = {}) => {
617 | const { skipTilt } = options;
618 | const { enableTilt } = this.getDerivedPropsFromBreakPoint();
619 | const { activeIndex, sliderPosition } = this.state;
620 | const nextItem = this.getNextItemIndex(activeIndex, false);
621 | if (activeIndex !== nextItem) {
622 | this.goTo(nextItem);
623 | } else if (enableTilt && !skipTilt) {
624 | this.tiltMovement(sliderPosition, 20, 150);
625 | }
626 | };
627 |
628 | slidePrev = (options = {}) => {
629 | const { skipTilt } = options;
630 | const { activeIndex } = this.state;
631 | const { enableTilt } = this.getDerivedPropsFromBreakPoint();
632 | const prevItem = this.getNextItemIndex(activeIndex, true);
633 | if (activeIndex !== prevItem) {
634 | this.goTo(prevItem);
635 | } else if (enableTilt && !skipTilt) {
636 | this.tiltMovement(0, -20, 150);
637 | }
638 | };
639 |
640 | onNextEnd = () => {
641 | const { onNextEnd, onChange } = this.getDerivedPropsFromBreakPoint();
642 | const { activeIndex, activePage } = this.state;
643 | const nextItemObj = this.convertChildToCbObj(activeIndex);
644 | this.removeSliderTransitionHook(this.onNextEnd);
645 | this.setState({ transitioning: false });
646 | onChange && onChange(nextItemObj, activePage);
647 | onNextEnd(nextItemObj, activePage);
648 | };
649 |
650 | onPrevEnd = () => {
651 | const { onPrevEnd, onChange } = this.getDerivedPropsFromBreakPoint();
652 | const { activeIndex, activePage } = this.state;
653 | const nextItemObj = this.convertChildToCbObj(activeIndex);
654 | this.removeSliderTransitionHook(this.onPrevEnd);
655 | this.setState({ transitioning: false });
656 | onChange && onChange(nextItemObj, activePage);
657 | onPrevEnd(nextItemObj, activePage);
658 | };
659 |
660 | generatePositionUpdater = (
661 | direction,
662 | nextItemId,
663 | verticalMode,
664 | rest
665 | ) => state => {
666 | const { sliderPosition, childHeight, activeIndex } = state;
667 | const childWidth = this.calculateChildWidth();
668 |
669 | let newSliderPosition = 0;
670 | const childSize = verticalMode ? childHeight : childWidth;
671 | if (direction === consts.NEXT) {
672 | newSliderPosition =
673 | sliderPosition - childSize * (nextItemId - activeIndex);
674 | } else {
675 | newSliderPosition =
676 | sliderPosition + childSize * (activeIndex - nextItemId);
677 | }
678 |
679 | return {
680 | sliderPosition: newSliderPosition,
681 | activeIndex: nextItemId,
682 | swipedSliderPosition: 0,
683 | isSwiping: false,
684 | ...rest
685 | };
686 | };
687 |
688 | goTo = nextItemId => {
689 | const {
690 | children,
691 | verticalMode,
692 | itemsToShow
693 | } = this.getDerivedPropsFromBreakPoint();
694 | const { activeIndex } = this.state;
695 | const childrenLength = Children.toArray(children).length;
696 | let safeNextItemId = Math.max(0, nextItemId); // don't allow negative numbers
697 | const isPrev = activeIndex > safeNextItemId;
698 | const nextAvailableItem = this.getNextItemIndex(activeIndex, isPrev);
699 | const noChange = nextAvailableItem === activeIndex;
700 | const outOfBoundary = safeNextItemId + itemsToShow >= childrenLength;
701 | if (noChange) {
702 | return;
703 | }
704 | if (outOfBoundary) {
705 | // Either go to last index (respect itemsToShow) or 0 index if we can't fill the slider
706 | safeNextItemId = Math.max(0, childrenLength - itemsToShow);
707 | }
708 | let direction = consts.NEXT;
709 | let positionEndCb = this.onNextEnd;
710 | if (isPrev) {
711 | direction = consts.PREV;
712 | positionEndCb = this.onPrevEnd;
713 | }
714 | const stateUpdater = this.generatePositionUpdater(
715 | direction,
716 | safeNextItemId,
717 | verticalMode,
718 | {
719 | transitioning: true
720 | }
721 | );
722 | this.setState(stateUpdater, () => {
723 | // callback
724 | pipe(
725 | this.updateActivePage(),
726 | this.onSliderTransitionEnd(positionEndCb)
727 | );
728 | });
729 | };
730 |
731 | getNumOfPages = () => {
732 | const { children, itemsToShow } = this.getDerivedPropsFromBreakPoint();
733 | const childrenLength = Children.toArray(children).length;
734 | const safeItemsToShow = Math.max(itemsToShow, 1);
735 | const numOfPages = Math.ceil(childrenLength / safeItemsToShow);
736 | return numOfPages || 1;
737 | };
738 |
739 | updateActivePage = () => {
740 | this.setState(state => {
741 | const { itemsToShow, children } = this.getDerivedPropsFromBreakPoint();
742 | const { activeIndex, activePage } = state;
743 | const numOfPages = this.getNumOfPages();
744 | const childrenLength = Children.toArray(children).length;
745 | const inRangeItemsToShow = Math.min(childrenLength, itemsToShow);
746 | // watch out from 0 (so we wont divide by zero)
747 | const safeItemsToShow = Math.max(inRangeItemsToShow, 1);
748 | const newActivePage = Math.ceil(activeIndex / safeItemsToShow);
749 | const inRangeActivePageIndex = Math.min(numOfPages - 1, newActivePage);
750 | if (activePage !== inRangeActivePageIndex) {
751 | return { activePage: inRangeActivePageIndex };
752 | }
753 | });
754 | };
755 |
756 | onIndicatorClick = indicatorId => {
757 | const { itemsToShow } = this.getDerivedPropsFromBreakPoint();
758 | const gotoIndex = indicatorId * itemsToShow;
759 | this.setState({ activePage: indicatorId });
760 | this.goTo(gotoIndex);
761 | };
762 |
763 | render() {
764 | const {
765 | activePage,
766 | isSwiping,
767 | sliderPosition,
768 | swipedSliderPosition,
769 | rootHeight,
770 | pages,
771 | activeIndex,
772 | transitionMs
773 | } = this.state;
774 | const {
775 | className,
776 | style,
777 | itemsToShow,
778 | itemsToScroll,
779 | verticalMode,
780 | isRTL,
781 | easing,
782 | tiltEasing,
783 | children,
784 | focusOnSelect,
785 | autoTabIndexVisibleItems,
786 | itemPosition,
787 | itemPadding,
788 | outerSpacing,
789 | enableSwipe,
790 | enableMouseSwipe,
791 | pagination,
792 | showArrows,
793 | disableArrowsOnEnd,
794 | preventDefaultTouchmoveEvent,
795 | renderArrow,
796 | renderPagination
797 | } = this.getDerivedPropsFromBreakPoint();
798 |
799 | const childWidth = this.calculateChildWidth();
800 |
801 | const numOfPages = this.getNumOfPages();
802 |
803 | /** Determine if arrows should be disabled */
804 | const canSlidePrev =
805 | activeIndex !== this.getNextItemIndex(activeIndex, true);
806 | const canSlideNext =
807 | activeIndex !== this.getNextItemIndex(activeIndex, false);
808 | const disabledPrevArrow = !canSlidePrev && disableArrowsOnEnd;
809 | const disabledNextArrow = !canSlideNext && disableArrowsOnEnd;
810 |
811 | return (
812 |
817 |
821 |
822 | {renderArrow ? (
823 | renderArrow({
824 | type: consts.PREV,
825 | onClick: this.onPrevStart,
826 | isEdge: !canSlidePrev
827 | })
828 | ) : (
829 |
834 | )}
835 |
836 |
840 |
853 |
870 |
871 |
872 |
873 | {renderArrow ? (
874 | renderArrow({
875 | type: consts.NEXT,
876 | onClick: this.onNextStart,
877 | isEdge: !canSlideNext
878 | })
879 | ) : (
880 |
885 | )}
886 |
887 |
888 |
889 | {renderPagination ? (
890 | renderPagination({
891 | pages: pages,
892 | activePage,
893 | onClick: this.onIndicatorClick
894 | })
895 | ) : (
896 |
901 | )}
902 |
903 |
904 | );
905 | }
906 | }
907 |
908 | Carousel.defaultProps = {
909 | className: "",
910 | style: {},
911 | verticalMode: false,
912 | isRTL: false,
913 | initialFirstItem: 0,
914 | initialActiveIndex: 0,
915 | showArrows: true,
916 | showEmptySlots: false,
917 | disableArrowsOnEnd: true,
918 | pagination: true,
919 | easing: "ease",
920 | tiltEasing: "ease",
921 | transitionMs: 500,
922 | enableTilt: true,
923 | enableSwipe: true,
924 | enableMouseSwipe: true,
925 | preventDefaultTouchmoveEvent: false,
926 | focusOnSelect: false,
927 | autoTabIndexVisibleItems: true,
928 | itemsToShow: 1,
929 | itemsToScroll: 1,
930 | itemPosition: consts.CENTER,
931 | itemPadding: [0, 0, 0, 0],
932 | outerSpacing: 0,
933 | enableAutoPlay: false,
934 | autoPlaySpeed: 2000,
935 |
936 | // callbacks
937 | onChange: noop,
938 | onNextEnd: noop,
939 | onPrevEnd: noop,
940 | onNextStart: noop,
941 | onPrevStart: noop,
942 | onResize: noop
943 | };
944 |
945 | Carousel.propTypes = {
946 | /** Items to render */
947 | children: PropTypes.node.isRequired,
948 |
949 | /** The css class for the root element */
950 | className: PropTypes.string,
951 |
952 | /** The style object for the root element */
953 | style: PropTypes.object,
954 |
955 | /** Display the Carousel in a vertical layout */
956 | verticalMode: PropTypes.bool,
957 |
958 | /** Flip right to left */
959 | isRTL: PropTypes.bool,
960 |
961 | /** Show dots for paging */
962 | pagination: PropTypes.bool,
963 |
964 | /** Animation speed */
965 | transitionMs: PropTypes.number,
966 |
967 | /** transition easing pattern */
968 | easing: PropTypes.string,
969 |
970 | /** transition easing pattern for the tilt */
971 | tiltEasing: PropTypes.string,
972 |
973 | /** The "bump" animation when reaching the last item */
974 | enableTilt: PropTypes.bool,
975 |
976 | /** Number of visible items */
977 | itemsToShow: PropTypes.number,
978 |
979 | /** Number of items to scroll */
980 | itemsToScroll: PropTypes.number,
981 |
982 | /** Collection of objects with a width, itemsToShow and itemsToScroll */
983 | breakPoints: PropTypes.arrayOf(
984 | PropTypes.shape({
985 | width: PropTypes.number.isRequired,
986 | itemsToShow: PropTypes.number,
987 | itemsToScroll: PropTypes.number
988 | })
989 | ),
990 |
991 | /** The initial active index when the component mounts */
992 | initialActiveIndex: PropTypes.number,
993 |
994 | /** **DEPRECATED - use initialActiveIndex instead** The first items when the component mounts */
995 | initialFirstItem: PropTypes.number,
996 |
997 | /** Show the arrow buttons */
998 | showArrows: PropTypes.bool,
999 |
1000 | /** Show empty slots when children.length < itemsToShow (not compatible with verticalMode yet !) */
1001 | showEmptySlots: PropTypes.bool,
1002 |
1003 | /** Disables the arrow button when there are no more items */
1004 | disableArrowsOnEnd: PropTypes.bool,
1005 |
1006 | /** Go to item on click */
1007 | focusOnSelect: PropTypes.bool,
1008 |
1009 | /** Automatically inject `tabIndex:0` to visible items */
1010 | autoTabIndexVisibleItems: PropTypes.bool,
1011 |
1012 | /** A render prop for the arrow component
1013 | * - ({type, onClick}) => {type === 'prev' ? '<-' : '->'}
1014 | */
1015 | renderArrow: PropTypes.func,
1016 |
1017 | /** A render prop for the pagination component
1018 | * - ({ pages, activePage, onClick }) =>
1019 | */
1020 | renderPagination: PropTypes.func,
1021 |
1022 | /** Position the element relative to it's wrapper (use the consts object) - consts.START | consts.CENTER | consts.END */
1023 | itemPosition: PropTypes.oneOf([consts.START, consts.CENTER, consts.END]),
1024 |
1025 | /** A padding for each element */
1026 | itemPadding: PropTypes.array,
1027 |
1028 | /** A margin at the beginning and at the end of the carousel (not compatible with verticalMode yet !) */
1029 | outerSpacing: PropTypes.number,
1030 |
1031 | // swipe
1032 | /** Enable or disable swipe */
1033 | enableSwipe: PropTypes.bool,
1034 |
1035 | /** Enable or disable mouse swipe */
1036 | enableMouseSwipe: PropTypes.bool,
1037 |
1038 | /** Prevent page scroll on touchmove.
1039 | * Use this to stop the browser from scrolling while a user swipes.
1040 | * More details: https://github.com/FormidableLabs/react-swipeable#preventdefaulttouchmoveevent-details
1041 | */
1042 | preventDefaultTouchmoveEvent: PropTypes.bool,
1043 |
1044 | // auto play
1045 | /** Enable or disable auto play */
1046 | enableAutoPlay: PropTypes.bool,
1047 |
1048 | /** Set auto play speed (ms) */
1049 | autoPlaySpeed: PropTypes.number,
1050 |
1051 | // callbacks
1052 | /** A callback for the change of an item
1053 | * - onChange(currentItemObject, currentPageIndex) => {} */
1054 | onChange: PropTypes.func,
1055 |
1056 | /** A callback for the beginning of the next transition
1057 | * - onNextStart(prevItemObject, nextItemObject) => {} */
1058 | onNextStart: PropTypes.func,
1059 |
1060 | /** A callback for the beginning of the prev transition
1061 | * - onPrevStart(prevItemObject, nextItemObject) => {} */
1062 | onPrevStart: PropTypes.func,
1063 |
1064 | /** A callback for the end of the next transition
1065 | * - onNextEnd(nextItemObject, currentPageIndex) => {} */
1066 | onNextEnd: PropTypes.func,
1067 |
1068 | /** A callback for the end of the prev transition
1069 | * - onPrevEnd(nextItemObject, currentPageIndex) => {} */
1070 | onPrevEnd: PropTypes.func,
1071 |
1072 | /** A callback for the "slider-container" resize
1073 | * - onResize(currentBreakPoint) => {} */
1074 | onResize: PropTypes.func
1075 | };
1076 |
1077 | export default Carousel;
1078 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/ItemWrapperContainer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { ItemWrapper } from "./styled";
4 | import { noop } from "../utils/helpers";
5 |
6 | class ItemWrapperContainer extends React.Component {
7 | onClick = () => {
8 | const { onClick, id } = this.props;
9 | onClick(id);
10 | };
11 | render() {
12 | return ;
13 | }
14 | }
15 |
16 | ItemWrapperContainer.defaultProps = {
17 | onClick: noop
18 | };
19 |
20 | ItemWrapperContainer.propTypes = {
21 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
22 | onClick: PropTypes.func
23 | };
24 |
25 | export default ItemWrapperContainer;
26 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/Pagination/Dot.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import styled from "styled-components";
4 | import { cssPrefix } from "../../utils/helpers";
5 |
6 | const boxShadow = "0 0 1px 2px rgba(0, 0, 0, 0.5)";
7 | const activeBoxShadow = "0 0 1px 3px rgba(103,58,183,1)";
8 | const hoveredBoxShadow = "0 0 1px 3px rgba(103,58,183,.5)";
9 |
10 | const Dot = styled.button.attrs(({ type = "button" }) => ({ type }))`
11 | box-sizing: border-box;
12 | padding: 0;
13 | transition: all 250ms ease;
14 | border: none;
15 | margin: 5px;
16 | background-color: ${({ active }) =>
17 | active ? "rgba(103,58,183,.5)" : "transparent"};
18 | font-size: 1.3em;
19 | content: "";
20 | height: 10px;
21 | width: 10px;
22 | box-shadow: ${({ active }) => (active ? activeBoxShadow : boxShadow)};
23 | border-radius: 50%;
24 | outline: none;
25 | &:hover,
26 | &:focus {
27 | cursor: pointer;
28 | box-shadow: ${({ active }) =>
29 | active ? activeBoxShadow : hoveredBoxShadow};
30 | }
31 | `;
32 |
33 | class DotContainer extends React.Component {
34 | onClick = () => {
35 | const { onClick, id } = this.props;
36 | onClick(id);
37 | };
38 | render() {
39 | const { active } = this.props;
40 | return (
41 |
49 | );
50 | }
51 | }
52 |
53 | DotContainer.propTypes = {
54 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
55 | active: PropTypes.bool,
56 | onClick: PropTypes.func
57 | };
58 |
59 | export default DotContainer;
60 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/Pagination/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import styled from "styled-components";
4 | import Dot from "./Dot";
5 | import { numberToArray, cssPrefix, noop } from "../../utils/helpers";
6 |
7 | const Indicators = styled.div`
8 | display: flex;
9 | flex-wrap: wrap;
10 | margin-top: 15px;
11 | `;
12 |
13 | class Pagination extends React.Component {
14 | render() {
15 | const { numOfPages, activePage, onClick } = this.props;
16 | const pages = numberToArray(numOfPages);
17 | return (
18 |
19 | {pages.map((item, i) => (
20 |
21 | ))}
22 |
23 | );
24 | }
25 | }
26 |
27 | Pagination.defaultProps = {
28 | onClick: noop
29 | };
30 |
31 | Pagination.propTypes = {
32 | numOfPages: PropTypes.number.isRequired,
33 | activePage: PropTypes.number.isRequired,
34 | onClick: PropTypes.func
35 | };
36 |
37 | export default Pagination;
38 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/Pagination/__test__/Dot.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Dot from "../Dot";
3 | import { mount } from "enzyme";
4 |
5 | describe("Dot", () => {
6 | it("renders", () => {
7 | mount( );
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/Pagination/index.js:
--------------------------------------------------------------------------------
1 | export { default as Pagination } from "./Pagination";
2 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/Track.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Swipeable } from "react-swipeable";
4 | import { cssPrefix } from "../utils/helpers";
5 | import ItemWrapperContainer from "./ItemWrapperContainer";
6 |
7 | const Track = ({
8 | children,
9 | childWidth,
10 | autoTabIndexVisibleItems,
11 | enableSwipe,
12 | enableMouseSwipe,
13 | preventDefaultTouchmoveEvent,
14 | itemsToShow,
15 | itemsToScroll,
16 | currentItem,
17 | itemPosition,
18 | itemPadding,
19 | onSwiped,
20 | onSwiping,
21 | verticalMode,
22 | onItemClick
23 | }) => {
24 | const width = `${childWidth}px`;
25 | const paddingStyle = `${itemPadding.join("px ")}px`;
26 | const minVisibleItem = currentItem;
27 | const maxVisibleItem = currentItem + itemsToShow;
28 | const prevItem = minVisibleItem - itemsToScroll;
29 | const nextItem = maxVisibleItem + itemsToScroll;
30 |
31 | const originalChildren = React.Children.map(children, (child, idx) => {
32 | const isVisible = idx >= minVisibleItem && idx < maxVisibleItem;
33 | const isPrevItem = !isVisible && idx >= prevItem && idx < currentItem;
34 | const isNextItem = !isVisible && idx < nextItem && idx > currentItem;
35 | const itemClass = "carousel-item";
36 |
37 | const childToRender = autoTabIndexVisibleItems
38 | ? React.cloneElement(child, {
39 | tabIndex: isVisible ? 0 : -1
40 | })
41 | : child;
42 | return (
43 |
52 |
59 | {childToRender}
60 |
61 |
62 | );
63 | });
64 | const toRender = enableSwipe ? (
65 |
77 | {originalChildren}
78 |
79 | ) : (
80 | originalChildren
81 | );
82 | return toRender;
83 | };
84 |
85 | Track.propTypes = {
86 | children: PropTypes.array.isRequired,
87 | itemsToShow: PropTypes.number.isRequired,
88 | noAutoTabbedItems: PropTypes.bool,
89 | currentItem: PropTypes.number.isRequired,
90 | itemPosition: PropTypes.string,
91 | itemPadding: PropTypes.array,
92 | childWidth: PropTypes.number,
93 | verticalMode: PropTypes.bool,
94 | enableSwipe: PropTypes.bool,
95 | enableMouseSwipe: PropTypes.bool,
96 | preventDefaultTouchmoveEvent: PropTypes.bool,
97 | onSwiped: PropTypes.func,
98 | onSwiping: PropTypes.func,
99 | onItemClick: PropTypes.func
100 | };
101 |
102 | export default Track;
103 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/__tests__/Carousel.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { mount, shallow } from "enzyme";
3 | import Carousel from "../Carousel";
4 | import Slider from "../styled/Slider";
5 | import Pagination from "../Pagination/Pagination";
6 | import { numberToArray } from "../../utils/helpers";
7 |
8 | describe("Carousel - public API (props)", () => {
9 | const Items = numberToArray(5).map(i => (
10 |
11 | {i}
12 |
13 | ));
14 |
15 | it("renders without crashing", () => {
16 | shallow({Items} );
17 | });
18 |
19 | it("renders children", () => {
20 | const wrapper = mount({Items} );
21 | const children = wrapper.find(Carousel).find(".test-child");
22 | expect(children.length).toEqual(Items.length);
23 | });
24 |
25 | it("one child wont break on next", () => {
26 | const wrapper = mount({Items[0]} );
27 | const nextButton = wrapper.find(Carousel).find("button.rec-arrow-right");
28 | nextButton.simulate("click");
29 | });
30 |
31 | it("renders with className in root", () => {
32 | const testClassName = "test-root";
33 | const wrapper = mount(
34 | {Items}
35 | );
36 | const carousel = wrapper.first();
37 | expect(carousel.hasClass(testClassName));
38 | });
39 |
40 | it("renders with style in root", () => {
41 | const styleToRender = { position: "fixed" };
42 | const wrapper = mount({Items} );
43 | const carousel = wrapper.getDOMNode();
44 | expect(carousel.style.position).toEqual("fixed");
45 | });
46 |
47 | it("verticalMode", () => {
48 | const wrapper = shallow({Items} );
49 | const slider = wrapper.find(Slider);
50 | expect(slider.props().verticalMode).toEqual(true);
51 | });
52 |
53 | it("isRTL", () => {
54 | const wrapper = shallow({Items} );
55 | const slider = wrapper.find(Slider);
56 | expect(slider.props().isRTL).toEqual(true);
57 | });
58 |
59 | it("pagination", () => {
60 | const wrapper = shallow({Items} );
61 | const pagination = wrapper.find(Pagination);
62 | expect(pagination.exists()).toEqual(true);
63 | });
64 |
65 | it("renderPagination (renders custom pagination)", () => {
66 | const CustomPagination = () => test
;
67 | const renderPagination = () => ;
68 | const wrapper = shallow(
69 | {Items}
70 | );
71 |
72 | const customPagination = wrapper.find(CustomPagination);
73 | expect(customPagination.exists()).toEqual(true);
74 | });
75 |
76 | it("wont break with outerSpacing", () => {
77 | const wrapper = shallow({Items} );
78 |
79 | const carousel = wrapper.find(".rec-carousel");
80 | expect(carousel.exists()).toEqual(true);
81 | });
82 | });
83 |
84 | describe("Carousel - public CSS classnames", () => {
85 | const publicClasses = [
86 | "carousel-wrapper",
87 | "carousel",
88 | "slider-container",
89 | "slider",
90 | "carousel-item",
91 | "carousel-item-visible",
92 | "carousel-item-hidden",
93 | "carousel-item-prev",
94 | "carousel-item-next",
95 | "swipable",
96 | "dot",
97 | "dot_active",
98 | "pagination",
99 | "item-wrapper",
100 | "arrow"
101 | ];
102 | const prefix = "rec";
103 | const Items = numberToArray(5).map(i => (
104 |
105 | {i}
106 |
107 | ));
108 | const carousel = mount(
109 |
110 | {Items}
111 |
112 | );
113 | publicClasses.forEach(className => {
114 | const withPrefix = `${prefix}-${className}`;
115 | it(`renders ${withPrefix}`, () => {
116 | const withClass = carousel.find(`.${withPrefix}`);
117 | expect(withClass.exists()).toEqual(true);
118 | });
119 | });
120 | });
121 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/styled/Button.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export default styled.button.attrs(({ type = "button" }) => ({ type }))`
4 | box-sizing: border-box;
5 | transition: all 0.3s ease;
6 | font-size: 1.6em;
7 | background-color: rgba(103, 58, 183, 0.1);
8 | color: ${props => (props.disabled ? "#999" : "#333")};
9 | box-shadow: 0 0 2px 0px #333;
10 | border-radius: 50%;
11 | border: none;
12 | padding: 0;
13 | width: 50px;
14 | height: 50px;
15 | min-width: 50px;
16 | line-height: 50px;
17 | align-self: center;
18 | cursor: pointer;
19 | outline: none;
20 | &:hover:enabled,
21 | &:focus:enabled {
22 | color: #fff;
23 | background-color: rgba(103, 58, 183, 1);
24 | box-shadow: 0 0 2px 0 #333;
25 | }
26 | &:disabled {
27 | cursor: not-allowed;
28 | }
29 | `;
30 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/styled/CarouselWrapper.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export default styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | width: 100%;
8 | direction: ${({ isRTL }) => (isRTL ? "rtl" : "ltr")};
9 | `;
10 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/styled/ItemWrapper.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import PropTypes from "prop-types";
3 | import { cssPrefix } from "../../utils/helpers";
4 | import consts from "../../consts";
5 |
6 | const ItemWrapper = styled.div.attrs(({ style }) => ({
7 | style,
8 | className: cssPrefix("item-wrapper")
9 | }))`
10 | box-sizing: border-box;
11 | display: flex;
12 | overflow: hidden;
13 | user-select: none;
14 | justify-content: ${({ itemPosition }) => itemPosition};
15 | `;
16 |
17 | ItemWrapper.defaultProps = {
18 | style: {},
19 | itemPosition: consts.CENTER
20 | };
21 |
22 | ItemWrapper.propTypes = {
23 | children: PropTypes.element.isRequired,
24 | style: PropTypes.object,
25 | itemPosition: PropTypes.oneOf([consts.START, consts.CENTER, consts.END])
26 | };
27 |
28 | export default ItemWrapper;
29 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/styled/Slider.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const calcLeft = ({
4 | isRTL,
5 | verticalMode,
6 | isSwiping,
7 | swipedSliderPosition,
8 | sliderPosition
9 | }) => {
10 | if (verticalMode || isRTL) {
11 | return "auto";
12 | } else {
13 | return `${isSwiping ? swipedSliderPosition : sliderPosition}px`;
14 | }
15 | };
16 |
17 | const calcRight = ({
18 | isRTL,
19 | verticalMode,
20 | isSwiping,
21 | swipedSliderPosition,
22 | sliderPosition
23 | }) => {
24 | if (!verticalMode && isRTL) {
25 | return `${isSwiping ? swipedSliderPosition : sliderPosition}px`;
26 | } else {
27 | return "auto";
28 | }
29 | };
30 |
31 | const calcTop = ({
32 | verticalMode,
33 | isSwiping,
34 | swipedSliderPosition,
35 | sliderPosition
36 | }) => {
37 | if (!verticalMode) {
38 | return "auto";
39 | } else {
40 | return `${isSwiping ? swipedSliderPosition : sliderPosition}px`;
41 | }
42 | };
43 |
44 | const calcTransition = ({ isSwiping, transitionMs, easing, tiltEasing }) => {
45 | const duration = isSwiping ? 0 : transitionMs;
46 | const effectiveEasing = isSwiping ? tiltEasing : easing;
47 | return `all ${duration}ms ${effectiveEasing}`;
48 | };
49 |
50 | // We use attributes (style) to bypass multiple creation of classes (dynamic styling)
51 | export default styled.div.attrs(props => ({
52 | style: {
53 | transition: calcTransition(props),
54 | left: calcLeft(props),
55 | right: calcRight(props),
56 | top: calcTop(props)
57 | }
58 | }))`
59 | position: absolute;
60 | display: flex;
61 | flex-direction: ${({ verticalMode }) => (verticalMode ? "column" : "row")};
62 | ${({ verticalMode }) => (verticalMode ? "min-height: 100%;" : "")};
63 | ${({ verticalMode, outerSpacing }) =>
64 | verticalMode ? "" : `margin: 0 ${outerSpacing}px;`};
65 | `;
66 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/styled/SliderContainer.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export default styled.div`
4 | overflow: hidden;
5 | position: relative;
6 | width: 100%;
7 | margin: 0 10px;
8 | `;
9 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/styled/StyledCarousel.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export default styled.div.attrs(props => ({
4 | style: {
5 | height: props.size.height
6 | }
7 | }))`
8 | display: flex;
9 | flex-direction: row;
10 | width: 100%;
11 | `;
12 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/components/styled/index.js:
--------------------------------------------------------------------------------
1 | export { default as Button } from "./Button";
2 | export { default as ItemWrapper } from "./ItemWrapper";
3 | export { default as SliderContainer } from "./SliderContainer";
4 | export { default as Slider } from "./Slider";
5 | export { default as StyledCarousel } from "./StyledCarousel";
6 | export { default as CarouselWrapper } from "./CarouselWrapper";
7 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/consts.js:
--------------------------------------------------------------------------------
1 | export default {
2 | PREV: "PREV",
3 | NEXT: "NEXT",
4 | START: "flex-start",
5 | CENTER: "center",
6 | END: "flex-end"
7 | };
8 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/index.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint no-unused-vars: 0 */ // --> OFF
2 | /* eslint no-undef: 0 */ // --> OFF
3 | import * as React from "react";
4 |
5 | export type RenderArrowProps = {
6 | type: "PREV" | "NEXT";
7 | onClick: () => void;
8 | isEdge: boolean;
9 | };
10 |
11 | export type RenderPaginationProps = {
12 | pages: number[];
13 | activePage: number;
14 | // The onClick event that sets the state of the carousel and sends
15 | // it to a specific page.
16 | onClick: (indicatorId: string) => void;
17 | };
18 |
19 | export type ItemObject = {
20 | // Children's props
21 | object: any;
22 | index: number;
23 | };
24 |
25 | export type Breakpoint = {
26 | itemsToScroll: number;
27 | itemsToShow: number;
28 | };
29 |
30 | export interface ReactElasticCarouselProps {
31 | className?: string;
32 | // Defaults to 1
33 | itemsToShow?: number;
34 | // Defaults to false
35 | verticalMode?: boolean;
36 | // Defaults to false
37 | isRTL: boolean;
38 | // Defaults to true
39 | pagination?: boolean;
40 | // Defaults to 500
41 | transitionMs?: number;
42 | // Defaults to "ease"
43 | easing?: string;
44 | // Defaults to "ease"
45 | tiltEasing?: string;
46 | // Defaults to true.
47 | enableTilt?: boolean;
48 | // Defaults to 1
49 | itemsToScroll?: number;
50 | // Collection of objects with a width, itemsToShow and itemsToScroll
51 | breakPoints?: {
52 | width: number;
53 | itemsToShow?: number;
54 | itemsToScroll?: number;
55 | }[];
56 | // Defaults to 0
57 | initialActiveIndex?: number;
58 | // Defaults to 0
59 | initialFirstItem?: number;
60 | // Defaults to true
61 | showArrows?: boolean;
62 | // Defaults to false (not compatible with verticalMode yet !)
63 | showEmptySlots?: boolean;
64 | // Defaults to true
65 | disableArrowsOnEnd?: boolean;
66 | // Defaults to boolean
67 | focusOnSelect?: boolean;
68 | // Function to generate your own navigation arrows.
69 | renderArrow?: (props: RenderArrowProps) => void;
70 | // Function to generate your own pagination component.
71 | renderPagination?: (props: RenderPaginationProps) => JSX.Element;
72 | // Defaults to "CENTER"
73 | itemPosition?: "START" | "CENTER" | "END";
74 | // A padding for each element - Defaults to [0,0,0,0]
75 | itemPadding?: number[];
76 | // A margin at the beginning and at the end of the carousel - Defaults to 0 (not compatible with verticalMode yet !)
77 | outerSpacing?: number;
78 | // Enable or disable swipe - Defaults to true
79 | enableSwipe?: boolean;
80 | /** Enable or disable mouse swipe */
81 | enableMouseSwipe?: boolean;
82 | /** Prevent page scroll on touchmove.
83 | * Use this to stop the browser from scrolling while a user swipes.
84 | * More details: https://github.com/FormidableLabs/react-swipeable#preventdefaulttouchmoveevent-details
85 | */
86 | preventDefaultTouchmoveEvent?: boolean;
87 | // Enable or disable auto play - Defaults to true
88 | enableAutoPlay?: boolean;
89 | /** Set auto play speed (ms) - Defaults to 2000 */
90 | autoPlaySpeed?: number;
91 | // A callback for the change of an item
92 | onChange?: (currentItemObject: ItemObject, currentPageIndex: number) => void;
93 | // A callback for the beginning of the next transition
94 | onNextStart?: (
95 | prevItemObject: ItemObject,
96 | nextItemObject: ItemObject
97 | ) => void;
98 | // A callback for the beginning of the prev transition
99 | onPrevStart?: (
100 | prevItemObject: ItemObject,
101 | nextItemObject: ItemObject
102 | ) => void;
103 | // A callback for the end of the next transition
104 | onNextEnd?: (nextItemObject: ItemObject, currentPageIndex: number) => void;
105 | // A callback for the end of the prev transition
106 | onPrevEnd?: (nextItemObject: ItemObject, currentPageIndex: number) => void;
107 | // A callback for the "slider-container" resize
108 | onResize?: (currentBreakpoint: Breakpoint) => void;
109 | }
110 |
111 | declare class ReactElasticCarousel extends React.Component<
112 | ReactElasticCarouselProps
113 | > {}
114 |
115 | export default ReactElasticCarousel;
116 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./components/Carousel";
2 | export { default as consts } from "./consts";
3 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/reducers/__tests__/items.test.js:
--------------------------------------------------------------------------------
1 | import { activeIndexReducer } from "../items";
2 | import { nextItemAction, prevItemAction } from "../../actions/itemsActions";
3 |
4 | describe("activeIndexReducer", () => {
5 | it("returns default state", () => {
6 | const currentIndex = 5;
7 | const nextIndex = activeIndexReducer(currentIndex, {});
8 | expect(nextIndex).toEqual(currentIndex);
9 | });
10 |
11 | it("get +1 index", () => {
12 | const currentIndex = 3;
13 | const limit = 5;
14 | const itemsToScroll = 1;
15 | const action = nextItemAction(limit, itemsToScroll);
16 | const nextIndex = activeIndexReducer(currentIndex, action);
17 | expect(nextIndex).toEqual(currentIndex + 1);
18 | });
19 |
20 | it("get -1 index", () => {
21 | const currentIndex = 3;
22 | const limit = 0;
23 | const itemsToScroll = 1;
24 | const action = prevItemAction(limit, itemsToScroll);
25 | const nextIndex = activeIndexReducer(currentIndex, action);
26 | expect(nextIndex).toEqual(currentIndex - 1);
27 | });
28 |
29 | it("itemsToScroll", () => {
30 | const currentIndex = 3;
31 | const limit = 5;
32 | const itemsToScroll = 2;
33 | const action = nextItemAction(limit, itemsToScroll);
34 | const nextIndex = activeIndexReducer(currentIndex, action);
35 | expect(nextIndex).toEqual(limit);
36 | });
37 |
38 | it("get last or first index if out of boundries", () => {
39 | const currentIndex = 3;
40 | const itemsToScroll = 5;
41 |
42 | // next
43 | const topLimit = 5;
44 | const nextAction = nextItemAction(topLimit, itemsToScroll);
45 | const nextIndex = activeIndexReducer(currentIndex, nextAction);
46 |
47 | // prev
48 | const startLimit = 0;
49 | const prevAction = prevItemAction(startLimit, itemsToScroll);
50 | const prevIndex = activeIndexReducer(currentIndex, prevAction);
51 |
52 | // assert
53 | expect(nextIndex).toEqual(topLimit);
54 | expect(prevIndex).toEqual(startLimit);
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/reducers/items.js:
--------------------------------------------------------------------------------
1 | import { NEXT_ITEM, PREV_ITEM } from "../actions/consts";
2 |
3 | export const activeIndexReducer = (state, action) => {
4 | const { limit, itemsToScroll, type } = action;
5 | switch (type) {
6 | case NEXT_ITEM: {
7 | let optimisticNextItem = state + itemsToScroll;
8 | const nextItem = limit >= optimisticNextItem ? optimisticNextItem : limit;
9 | return nextItem;
10 | }
11 |
12 | case PREV_ITEM: {
13 | let optimisticPrevItem = state - itemsToScroll;
14 | const prevItem = optimisticPrevItem >= limit ? optimisticPrevItem : limit;
15 | return prevItem;
16 | }
17 |
18 | default:
19 | return state;
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/utils/__tests__/helpers.test.js:
--------------------------------------------------------------------------------
1 | import * as helpers from "../helpers";
2 |
3 | describe("helpers", () => {
4 | it("numberToArray", () => {
5 | const length = 5;
6 | const arr = helpers.numberToArray(length);
7 | expect(Array.isArray(arr)).toEqual(true);
8 | expect(arr.length).toEqual(length);
9 | });
10 |
11 | it("cssPrefix", () => {
12 | const css = helpers.cssPrefix("test");
13 | expect(css).toEqual("rec rec-test");
14 | });
15 |
16 | it("cssPrefix multi keys", () => {
17 | const css = helpers.cssPrefix("test", "test2");
18 | expect(css).toEqual("rec rec-test rec-test2");
19 | });
20 |
21 | it("pipe", () => {
22 | const inc = num => num + 1;
23 | const double = num => num * 2;
24 | const split = num => num / 2;
25 |
26 | const result = split(double(inc(2)));
27 | const piped = helpers.pipe(
28 | inc,
29 | double,
30 | split
31 | );
32 | expect(result).toEqual(piped(2));
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/src/react-elastic-carousel/utils/helpers.js:
--------------------------------------------------------------------------------
1 | export const noop = () => {};
2 |
3 | export const numberToArray = n => [...Array(n).keys()];
4 |
5 | export const cssPrefix = (...classNames) => {
6 | const prefix = "rec";
7 | const space = " ";
8 | let result = `${prefix}`; // initial it with global prefix;
9 |
10 | // in case of an array we add the class prefix per item;
11 | const chainedClasses = classNames.reduce((acc, current) => {
12 | if (current) {
13 | acc += `${space}${prefix}-${current}`; // we must keep spaces between class names
14 | }
15 | return acc;
16 | }, "");
17 | result += chainedClasses;
18 |
19 | return result;
20 | };
21 |
22 | export const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
23 |
24 | export const throttle = (func, limit) => {
25 | let lastFunc;
26 | let lastRan;
27 | return function() {
28 | const context = this;
29 | const args = arguments;
30 | if (!lastRan) {
31 | func.apply(context, args);
32 | lastRan = Date.now();
33 | } else {
34 | clearTimeout(lastFunc);
35 | lastFunc = setTimeout(function() {
36 | if (Date.now() - lastRan >= limit) {
37 | func.apply(context, args);
38 | lastRan = Date.now();
39 | }
40 | }, limit - (Date.now() - lastRan));
41 | }
42 | };
43 | };
44 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | import { configure } from "enzyme";
2 | import Adapter from "enzyme-adapter-react-16";
3 |
4 | configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------