├── .all-contributorsrc ├── .babelrc ├── .coveralls.yml ├── .csscomb.json ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── Bug_Report.md │ ├── Feature_Request.md │ └── Question.md ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .nvmrc-olf ├── .travis.yml.depracted ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── __mocks__ └── fileMock.js ├── dev ├── README.md ├── index.html └── media │ ├── express-logo.svg │ ├── img01.jpeg │ ├── img02.jpeg │ ├── img03.jpeg │ ├── img04.jpeg │ ├── img05.jpeg │ ├── img06.jpeg │ ├── img07.jpeg │ ├── img08.jpeg │ ├── pdp-men.gif │ └── pdp-women.gif ├── package-lock.json ├── package.json ├── postbuild.js ├── rollup.config.cjs.js ├── rollup.config.dev.js ├── rollup.config.es.js ├── src ├── .eslintrc ├── App │ ├── App.jsx │ ├── README.md │ ├── __tests__ │ │ ├── .eslintrc │ │ └── App.test.jsx │ ├── examples │ │ ├── Example1 │ │ │ ├── Example1.jsx │ │ │ └── index.js │ │ ├── Example10 │ │ │ ├── CustomSpinner.jsx │ │ │ ├── CustomSpinner.scss │ │ │ ├── Example10.jsx │ │ │ └── index.js │ │ ├── Example11 │ │ │ ├── Example11.jsx │ │ │ └── index.js │ │ ├── Example12 │ │ │ ├── Example12.jsx │ │ │ └── index.js │ │ ├── Example13 │ │ │ ├── Example13.jsx │ │ │ └── index.js │ │ ├── Example14 │ │ │ ├── Example14.jsx │ │ │ └── index.js │ │ ├── Example2 │ │ │ ├── Example2.jsx │ │ │ └── index.js │ │ ├── Example3 │ │ │ ├── Example3.jsx │ │ │ └── index.js │ │ ├── Example4 │ │ │ ├── Example4.jsx │ │ │ └── index.js │ │ ├── Example5 │ │ │ ├── Example5.jsx │ │ │ └── index.js │ │ ├── Example6 │ │ │ ├── Example6.jsx │ │ │ ├── Example6.scss │ │ │ └── index.js │ │ ├── Example7 │ │ │ ├── Example7.jsx │ │ │ ├── Example7.scss │ │ │ ├── SlideComponent │ │ │ │ ├── SlideComponent.jsx │ │ │ │ ├── SliderComponent.css │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── redux │ │ │ │ ├── demo-dux.js │ │ │ │ └── demo-store.js │ │ ├── Example8 │ │ │ ├── Example8.jsx │ │ │ └── index.js │ │ ├── Example9 │ │ │ ├── Example9.jsx │ │ │ └── index.js │ │ └── index.js │ └── style.scss ├── ButtonBack │ ├── ButtonBack.jsx │ ├── ButtonBack.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── ButtonBack.test.jsx │ └── index.js ├── ButtonFirst │ ├── ButtonFirst.jsx │ ├── ButtonFirst.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── ButtonFirst.test.jsx │ └── index.js ├── ButtonLast │ ├── ButtonLast.jsx │ ├── ButtonLast.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── ButtonLast.test.jsx │ └── index.js ├── ButtonNext │ ├── ButtonNext.jsx │ ├── ButtonNext.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── ButtonNext.test.jsx │ └── index.js ├── ButtonPlay │ ├── ButtonPlay.jsx │ ├── ButtonPlay.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── ButtonPlay.test.jsx │ └── index.js ├── CarouselProvider │ ├── CarouselConsumer.jsx │ ├── CarouselProvider.jsx │ ├── __tests__ │ │ ├── .eslintrc │ │ └── CarouselProvider.test.jsx │ ├── context.js │ └── index.js ├── Dot │ ├── Dot.jsx │ ├── Dot.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── Dot.test.jsx │ └── index.js ├── DotGroup │ ├── DotGroup.jsx │ ├── DotGroup.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── DotGroup.test.jsx │ └── index.js ├── Image │ ├── Image.jsx │ ├── Image.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── Image.test.jsx │ └── index.js ├── ImageWithZoom │ ├── ImageWithZoom.jsx │ ├── ImageWithZoom.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── ImageWithZoom.test.jsx │ └── index.js ├── Slide │ ├── Slide.jsx │ ├── Slide.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── Slide.test.jsx │ └── index.js ├── Slider │ ├── GetScrollParent.js │ ├── Slider.jsx │ ├── Slider.scss │ ├── __tests__ │ │ ├── .eslintrc │ │ └── Slider.test.jsx │ └── index.js ├── Spinner │ ├── Spinner.jsx │ ├── Spinner.scss │ └── index.js ├── Store │ ├── Store.jsx │ ├── WithStore.jsx │ └── __tests__ │ │ ├── .eslintrc │ │ └── Store.test.jsx ├── __tests__ │ ├── .eslintrc │ └── general.test.jsx ├── app.js ├── helpers │ ├── component-config.js │ └── index.js └── index.js └── typings ├── carouselElements.d.ts └── index.d.ts /.babelrc: -------------------------------------------------------------------------------- 1 | // { 2 | // "presets": [ 3 | // ["@babel/preset-env", { "modules": false }], 4 | // "@babel/preset-react" 5 | // ], 6 | // "plugins": [ 7 | // "@babel/plugin-proposal-class-properties", 8 | // "@babel/plugin-proposal-object-rest-spread", 9 | // "@babel/plugin-transform-object-assign", 10 | // "@babel/plugin-external-helpers" 11 | // ], 12 | // "env": { 13 | // "test": { 14 | // "plugins": [ 15 | // ["@babel/plugin-transform-runtime", { "runtimeHelpers": true }], 16 | // "transform-es2015-modules-commonjs" 17 | // ] 18 | // } 19 | // } 20 | // } 21 | 22 | { 23 | "env": { 24 | "development": { 25 | "presets": [ 26 | [ 27 | "@babel/preset-env", 28 | { 29 | "targets": { 30 | "browsers": ["> 5%", "Explorer 11"] 31 | } 32 | } 33 | ], 34 | "@babel/preset-react" 35 | ], 36 | "plugins": [ 37 | "@babel/plugin-proposal-class-properties", 38 | "@babel/plugin-proposal-object-rest-spread", 39 | "@babel/plugin-transform-object-assign" 40 | // latest version of rollup-plugin-babel already applies external helpers for us. 41 | // "@babel/plugin-external-helpers" 42 | ] 43 | }, 44 | "production": { 45 | "presets": [ 46 | [ 47 | "@babel/preset-env", 48 | { 49 | "targets": { 50 | "browsers": ["> 5%", "Explorer 11"] 51 | } 52 | } 53 | ], 54 | "@babel/preset-react" 55 | ], 56 | "plugins": [ 57 | "@babel/plugin-proposal-class-properties", 58 | "@babel/plugin-proposal-object-rest-spread", 59 | "@babel/plugin-transform-object-assign" 60 | ] 61 | }, 62 | "test": { 63 | // difference is that we don't use external-helpers 64 | "presets": [ 65 | [ 66 | "@babel/preset-env", 67 | { 68 | "targets": { 69 | "browsers": ["> 5%", "Explorer 11"], 70 | "node": "current" 71 | } 72 | } 73 | ], 74 | "@babel/preset-react" 75 | ], 76 | "plugins": [ 77 | "@babel/plugin-proposal-class-properties", 78 | "@babel/plugin-proposal-object-rest-spread", 79 | "@babel/plugin-transform-object-assign", 80 | [ 81 | "@babel/plugin-transform-runtime", 82 | { 83 | "regenerator": true 84 | } 85 | ] 86 | ] 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 2018, 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "jsx": true, 11 | }, 12 | }, 13 | "plugins": [ 14 | "node" 15 | ], 16 | "extends": [ 17 | "eslint:recommended", 18 | "plugin:import/errors", 19 | "plugin:import/warnings" 20 | ], 21 | "rules": { 22 | "indent": ["error", 2], 23 | "import/no-named-as-default": ["error", 0], 24 | "import/no-named-as-default-member": ["error", 0] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_Report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: I think something is broken 😡 4 | --- 5 | 6 | 14 | 15 | - `pure-react-carousel` version: 16 | - `react` version: 17 | - `browser` used: 18 | - `node` version: 19 | 20 | ### Relevant code or config: 21 | 22 | ```js 23 | const your = code => 'here'; 24 | ``` 25 | 26 | ### What you did: 27 | 28 | 29 | 30 | ### What happened: 31 | 32 | 33 | 34 | ### Reproduction: 35 | 36 | 41 | 42 | ### Suggested solution: 43 | 44 | 45 | 46 | ### Can you help us fix this issue by submitting a pull request? 47 | 48 | 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_Request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💡 Feature Request 3 | about: I think something could be better 🤔 4 | --- 5 | 6 | 15 | 16 | **Describe the feature you'd like:** 17 | 18 | 19 | 20 | **Suggested implementation:** 21 | 22 | 23 | 24 | **Describe alternatives you've considered:** 25 | 26 | 27 | 28 | **Teachability, Documentation, Adoption, Migration Strategy:** 29 | 30 | 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ❓ Support Question 3 | about: I think I don't understand how to do something 🤨 4 | --- 5 | 6 | 7 | 8 | Issues on GitHub are intended to be related to problems and feature requests so we ask you not use 9 | issues to ask for support. 10 | 11 | --- 12 | 13 | ## ❓ Pure React Carousel Resources 14 | 15 | - Tutorial - https://github.com/express-labs/pure-react-carousel#-tutorial 16 | - Examples - https://express-labs.github.io/pure-react-carousel/ 17 | - Stack Overflow - https://stackoverflow.com/questions/tagged/pure-react-carousel 18 | 19 | **ISSUES WHICH ARE QUESTIONS WILL BE CLOSED** 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | **What**: 12 | 13 | 14 | 15 | **Why**: 16 | 17 | 18 | 19 | **How**: 20 | 21 | 22 | 23 | **Checklist**: 24 | 25 | 26 | 27 | 28 | 29 | - [ ] Documentation added/updated 30 | - [ ] Typescript definitions updated 31 | - [ ] Tests added and passing 32 | - [ ] Ready to be merged 33 | 34 | 35 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 10 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 3 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - work in progress 10 | - help wanted 11 | - under investigation 12 | - bug 13 | # Label to use when marking an issue as stale 14 | staleLabel: stale 15 | # Comment to post when marking an issue as stale. Set to `false` to disable 16 | markComment: > 17 | This issue has been automatically marked as stale because it has not had 18 | recent activity. It will be closed if no further activity occurs. Thank you 19 | for your contributions. 20 | # Comment to post when closing a stale issue. Set to `false` to disable 21 | closeComment: > 22 | This issue has been automatically closed because it has not had recent activity. 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: [ master ] 5 | workflow_dispatch: 6 | jobs: 7 | # This workflow contains a single job called "build" 8 | build: 9 | # The type of runner that the job will run on 10 | runs-on: ubuntu-latest 11 | 12 | # Steps represent a sequence of tasks that will be executed as part of the job 13 | steps: 14 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 15 | - uses: actions/checkout@v2 16 | 17 | - name: Setup Node 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: '10.16.1' 21 | 22 | - name: Install Dependencies, run build, lint, and tests 23 | run: | 24 | npm i 25 | npm run lint 26 | npm run test 27 | npm run build 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: [ master ] 5 | workflow_dispatch: 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v2 12 | with: 13 | node-version: 10.16.1 14 | - run: npm i 15 | - run: npm run test 16 | - run: npm run lint 17 | - run: npm run prebuild 18 | 19 | publish-npm: 20 | needs: build 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: actions/setup-node@v2 25 | with: 26 | node-version: 14.17 27 | registry-url: https://registry.npmjs.org/ 28 | - run: npm i 29 | - run: npx semantic-release 30 | env: 31 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | 34 | deploy: 35 | needs: publish-npm 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: actions/setup-node@v2 40 | with: 41 | node-version: 10.16.1 42 | registry-url: https://registry.npmjs.org/ 43 | - run: npm i 44 | - run: | 45 | git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/express-labs/pure-react-carousel.git 46 | npm run deploy -- -u "github-actions-bot " 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | #babel cache 7 | .babel-cache 8 | 9 | # dev server 10 | /dev/script 11 | /dev/style.css 12 | 13 | #ignore webpack generated stats file 14 | stats.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Dependency directory 34 | node_modules 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Stuff for vim users 43 | *.un~ 44 | *.swp 45 | 46 | # Sonar cache 47 | .sonar 48 | 49 | # dist folder 50 | /dist 51 | 52 | # Bamboo Artifacts 53 | eslint.json 54 | jest.json 55 | coverage.json 56 | 57 | /templates/dev.ejs 58 | .DS_Store 59 | .npmrc 60 | 61 | .idea/ 62 | .vscode/ 63 | -------------------------------------------------------------------------------- /.nvmrc-olf: -------------------------------------------------------------------------------- 1 | 10.16.1 2 | -------------------------------------------------------------------------------- /.travis.yml.depracted: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: npm 4 | notifications: 5 | email: false 6 | node_js: 7 | - 8 8 | - 10 9 | - 12 10 | install: 11 | - npm i 12 | # as requested by the React team :) 13 | # https://reactjs.org/blog/2019/10/22/react-release-channels.html#using-the-next-channel-for-integration-testing 14 | - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then npm update react@next react-dom@next; fi 15 | script: npm run lint && npm run test 16 | jobs: 17 | include: 18 | - stage: release 19 | if: branch = master AND type != pull_request 20 | node_js: 10 21 | script: npm run semantic-release 22 | after_success: npm run deploy 23 | -------------------------------------------------------------------------------- /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 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at brandonvcarroll@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Express 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 | -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /dev/README.md: -------------------------------------------------------------------------------- 1 | # "dev" Folder 2 | This folder contains a local dev environment for developers working on the react-carousel code base. 3 | -------------------------------------------------------------------------------- /dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Pure React Carousel - By Express Labs 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 |
Loading Dev App...
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /dev/media/express-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1D44A17D-1955-46C3-BA3D-573A8655263F 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /dev/media/img01.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/dev/media/img01.jpeg -------------------------------------------------------------------------------- /dev/media/img02.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/dev/media/img02.jpeg -------------------------------------------------------------------------------- /dev/media/img03.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/dev/media/img03.jpeg -------------------------------------------------------------------------------- /dev/media/img04.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/dev/media/img04.jpeg -------------------------------------------------------------------------------- /dev/media/img05.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/dev/media/img05.jpeg -------------------------------------------------------------------------------- /dev/media/img06.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/dev/media/img06.jpeg -------------------------------------------------------------------------------- /dev/media/img07.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/dev/media/img07.jpeg -------------------------------------------------------------------------------- /dev/media/img08.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/dev/media/img08.jpeg -------------------------------------------------------------------------------- /dev/media/pdp-men.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/dev/media/pdp-men.gif -------------------------------------------------------------------------------- /dev/media/pdp-women.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/dev/media/pdp-women.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pure-react-carousel", 3 | "version": "1.32.0", 4 | "description": "A highly impartial suite of React components that can be assembled by the consumer to create a responsive and aria compliant carousel with almost no limits on DOM structure or CSS styles.", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "jsnext:main": "dist/index.es.js", 8 | "files": [ 9 | "dist/**/*", 10 | "typings/**/*" 11 | ], 12 | "keywords": [ 13 | "react", 14 | "carousel", 15 | "aria", 16 | "responsive", 17 | "accessibility", 18 | "accessible" 19 | ], 20 | "scripts": { 21 | "commit": "git-cz", 22 | "commit:add": "git add .", 23 | "commit:all": "npm run commit:add && npm run commit", 24 | "test": "BABEL_ENV=test jest --no-cache", 25 | "test:coverage": "BABEL_ENV=test jest --coverage --no-cache", 26 | "test:watch": "BABEL_ENV=test jest --watch", 27 | "start": "check-node-version --node $(cat .nvmrc) && NODE_ENV=development rollup -w -c rollup.config.dev.js", 28 | "lint": "eslint . --ext .js --ext .jsx --ignore-path .gitignore", 29 | "lint:fix": "eslint ./src --fix --ext .js --ext .jsx --ignore-path .gitignore", 30 | "prebuild": "rimraf dist", 31 | "build": "check-node-version --node $(cat .nvmrc) && NODE_ENV=production rollup -c rollup.config.cjs.js && NODE_ENV=production rollup -c rollup.config.es.js", 32 | "build:commonjs": "check-node-version --node $(cat .nvmrc) && NODE_ENV=production rollup -c rollup.config.cjs.js", 33 | "build:es": "check-node-version --node $(cat .nvmrc) && NODE_ENV=production rollup -c rollup.config.es.js", 34 | "postbuild": "node postbuild.js", 35 | "build:watch": "check-node-version --node $(cat .nvmrc) && watch 'npm run build' src", 36 | "semantic-release": "semantic-release", 37 | "predeploy": "check-node-version --node $(cat .nvmrc) && rimraf dev/script && NODE_ENV=development rollup -c rollup.config.dev.js", 38 | "deploy": "check-node-version --node $(cat .nvmrc) && gh-pages -d dev", 39 | "true-level": "check-node-version --node $(cat .nvmrc) && rm -rf ./node_modules ./.babel-cache; npm install" 40 | }, 41 | "typings": "typings/index.d.ts", 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/express-labs/pure-react-carousel.git" 45 | }, 46 | "author": "Matthew Toledo", 47 | "license": "MIT", 48 | "bugs": { 49 | "url": "https://github.com/express-labs/pure-react-carousel" 50 | }, 51 | "homepage": "https://github.com/express-labs/pure-react-carousel#readme", 52 | "devDependencies": { 53 | "@babel/core": "^7.5.5", 54 | "@babel/plugin-external-helpers": "^7.2.0", 55 | "@babel/plugin-proposal-class-properties": "^7.5.5", 56 | "@babel/plugin-proposal-object-rest-spread": "^7.5.5", 57 | "@babel/plugin-transform-object-assign": "^7.2.0", 58 | "@babel/plugin-transform-runtime": "^7.5.5", 59 | "@babel/preset-env": "^7.5.5", 60 | "@babel/preset-react": "^7.0.0", 61 | "@types/react": "^16.7.6", 62 | "acorn": "^6.0.4", 63 | "babel-eslint": "^10.0.3", 64 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", 65 | "check-node-version": "^4.2.1", 66 | "clone": "^2.1.1", 67 | "commitizen": "^3.0.7", 68 | "cz-conventional-changelog": "^2.1.0", 69 | "enzyme": "^3.7.0", 70 | "enzyme-adapter-react-16": "^1.6.0", 71 | "escape-string-regexp": "^1.0.5", 72 | "eslint": "^5.8.0", 73 | "eslint-config-airbnb": "^17.1.0", 74 | "eslint-config-mrb3k-jest": "^1.3.0", 75 | "eslint-plugin-import": "^2.14.0", 76 | "eslint-plugin-jest": "^22.0.0", 77 | "eslint-plugin-jsx-a11y": "^6.1.2", 78 | "eslint-plugin-node": "^8.0.0", 79 | "eslint-plugin-react": "7.14.3", 80 | "gh-pages": "^2.0.1", 81 | "identity-obj-proxy": "^3.0.0", 82 | "jest": "^24.9.0", 83 | "node-sass": "^4.12.0", 84 | "object.omit": "^3.0.0", 85 | "raf": "^3.4.1", 86 | "react": "^16.9.0", 87 | "react-dom": "^16.9.0", 88 | "react-redux": "^5.1.0", 89 | "react-test-renderer": "^16.6.0", 90 | "redux": "^4.0.1", 91 | "regenerator-runtime": "^0.10.5", 92 | "replace-in-file": "^3.4.2", 93 | "rimraf": "^3.0.2", 94 | "rollup": "^1.20.3", 95 | "rollup-plugin-babel": "^4.3.3", 96 | "rollup-plugin-commonjs": "^10.1.0", 97 | "rollup-plugin-eslint": "^5.0.0", 98 | "rollup-plugin-img": "^1.1.0", 99 | "rollup-plugin-livereload": "^1.0.1", 100 | "rollup-plugin-multi-entry": "^2.1.0", 101 | "rollup-plugin-node-resolve": "^5.2.0", 102 | "rollup-plugin-postcss": "^2.0.3", 103 | "rollup-plugin-replace": "^2.2.0", 104 | "rollup-plugin-serve": "^1.0.1", 105 | "rollup-plugin-uglify": "^6.0.3", 106 | "rollup-plugin-uglify-es": "0.0.1", 107 | "rollup-watch": "^4.3.1", 108 | "semantic-release": "^19.0.5", 109 | "uglify-es": "^3.3.5", 110 | "waait": "^1.0.5", 111 | "watch": "^1.0.2" 112 | }, 113 | "peerDependencies": { 114 | "react": "15.x || 16.x || 17.x || 18.x || 19.x", 115 | "react-dom": "15.x || 16.x || 17.x || 18.x || 19.x" 116 | }, 117 | "dependencies": { 118 | "@babel/runtime": "^7.5.5", 119 | "deep-freeze": "0.0.1", 120 | "deepmerge": "^2.2.1", 121 | "equals": "^1.0.5", 122 | "prop-types": "^15.6.2" 123 | }, 124 | "jest": { 125 | "moduleNameMapper": { 126 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js", 127 | "\\.(css|scss)$": "identity-obj-proxy" 128 | }, 129 | "testPathIgnorePatterns": [ 130 | "/node_modules/", 131 | "/dev/", 132 | "/dist/" 133 | ], 134 | "testMatch": [ 135 | "**/__tests__/**.test.js?(x)" 136 | ], 137 | "collectCoverage": true, 138 | "coverageDirectory": "coverage", 139 | "collectCoverageFrom": [ 140 | "src/**/*.jsx" 141 | ], 142 | "coveragePathIgnorePatterns": [ 143 | "/node_modules/", 144 | "/dev/", 145 | "/dist/", 146 | "/src/App/examples/" 147 | ], 148 | "coverageReporters": [ 149 | "text", 150 | "json", 151 | "json-summary", 152 | "lcov" 153 | ], 154 | "coverageThreshold": { 155 | "global": { 156 | "branches": 100, 157 | "functions": 95, 158 | "lines": 95, 159 | "statements": 95 160 | } 161 | }, 162 | "setupFiles": [ 163 | "raf/polyfill" 164 | ], 165 | "testEnvironmentOptions": { 166 | "resources": "usable" 167 | } 168 | }, 169 | "release": { 170 | "branch": "master", 171 | "plugins": [ 172 | "@semantic-release/commit-analyzer", 173 | "@semantic-release/release-notes-generator", 174 | "@semantic-release/npm", 175 | "@semantic-release/github" 176 | ] 177 | }, 178 | "config": { 179 | "commitizen": { 180 | "path": "./node_modules/cz-conventional-changelog" 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /postbuild.js: -------------------------------------------------------------------------------- 1 | // This script corrects the path for sourceMappingURL. PostCSS spits out the wrong name. 2 | // This script is run automatically when you do `npm run build`. To run this script manually, 3 | // do `node postbuild.js` 4 | var replace = require('replace-in-file'); 5 | var escape = require('escape-string-regexp'); 6 | const baseDir = process.cwd(); 7 | 8 | 9 | replace({ 10 | files: [ 11 | 'dist/react-carousel.cjs.css', 12 | 'dist/react-carousel.es.css', 13 | 'dist/react-carousel.cjs.css.map', 14 | 'dist/react-carousel.es.css.map', 15 | ], 16 | from: [ 17 | /sourceMappingURL=index\.cjs\.css\.map/g, 18 | /sourceMappingURL=index\.es\.css\.map/g, 19 | new RegExp(escape(baseDir),'g'), 20 | new RegExp(escape(baseDir),'g'), 21 | ], 22 | to: [ 23 | 'sourceMappingURL=react-carousel.cjs.css.map', 24 | 'sourceMappingURL=react-carousel.es.css.map', 25 | '..', 26 | '..', 27 | ], 28 | }); 29 | -------------------------------------------------------------------------------- /rollup.config.cjs.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import { eslint } from 'rollup-plugin-eslint'; 4 | import path from 'path'; 5 | import postcss from 'rollup-plugin-postcss'; 6 | import replace from 'rollup-plugin-replace'; 7 | import resolve from 'rollup-plugin-node-resolve'; 8 | import { uglify } from 'rollup-plugin-uglify'; 9 | 10 | var pkg = require('./package.json'); 11 | var cache; 12 | 13 | export default { 14 | input: 'src/index.js', 15 | cache: cache, 16 | output: { 17 | file: 'dist/index.cjs.js', 18 | format: 'cjs', 19 | name: 'pureReactCarousel', 20 | sourcemap: true, 21 | sourcemapFile: path.resolve('dist/main.cjs.js'), 22 | }, 23 | // exclude peerDependencies from our bundle 24 | external: Object.keys(pkg.peerDependencies), 25 | plugins: [ 26 | postcss({ 27 | extensions: ['.css', '.scss'], 28 | extract: 'dist/react-carousel.cjs.css', 29 | minimize: true, 30 | modules: { 31 | // customize the name of the css classes that are created 32 | generateScopedName: '[local]___[hash:base64:5]', 33 | }, 34 | sourceMap: true, 35 | }), 36 | resolve({ 37 | browser: true, 38 | customResolveOptions: { 39 | moduleDirectory: 'node_modules' 40 | }, 41 | extensions: ['.js', '.jsx'], 42 | jsnext: true, 43 | main: true, 44 | module: true 45 | }), 46 | replace({ 'process.env.NODE_ENV': JSON.stringify('production') }), 47 | commonjs(), 48 | eslint({ 49 | exclude: [ 50 | '**/*.css', 51 | '**/*.scss', 52 | 'node_modules/**' 53 | ] 54 | }), 55 | babel({ 56 | exclude: [ 57 | 'node_modules/**' 58 | ], 59 | }), 60 | replace({ 61 | include: 'src/**', 62 | ENV: JSON.stringify(process.env.NODE_ENV || 'production') 63 | }), 64 | (process.env.NODE_ENV === 'production' && uglify()) 65 | ], 66 | } 67 | -------------------------------------------------------------------------------- /rollup.config.dev.js: -------------------------------------------------------------------------------- 1 | import babel from "rollup-plugin-babel"; 2 | import commonjs from "rollup-plugin-commonjs"; 3 | import { eslint } from "rollup-plugin-eslint"; 4 | import livereload from "rollup-plugin-livereload"; 5 | import image from "rollup-plugin-img"; 6 | import omit from "object.omit"; 7 | import path from "path"; 8 | import postcss from "rollup-plugin-postcss"; 9 | import replace from "rollup-plugin-replace"; 10 | import resolve from "rollup-plugin-node-resolve"; 11 | import serve from "rollup-plugin-serve"; 12 | 13 | var pkg = require("./package.json"); 14 | var cache; 15 | 16 | const plugins = [ 17 | image({ 18 | limit: 10000 19 | }), 20 | postcss({ 21 | extensions: [".css", ".scss"], 22 | extract: "dev/script/style.css", 23 | minimize: true, 24 | modules: { 25 | // customize the name of the css classes that are created 26 | generateScopedName: "[local]___[hash:base64:5]" 27 | }, 28 | sourceMap: "inline" // true, "inline" or false 29 | }), 30 | resolve({ 31 | browser: true, 32 | customResolveOptions: { 33 | moduleDirectory: "node_modules" 34 | }, 35 | extensions: [".js", ".jsx"], 36 | jsnext: true, 37 | main: true, 38 | module: true 39 | }), 40 | replace({ "process.env.NODE_ENV": JSON.stringify("development") }), 41 | commonjs({ 42 | include: ["node_modules/**"], 43 | exclude: ["node_modules/process-es6/**"], 44 | namedExports: { 45 | "node_modules/react/index.js": [ 46 | "Children", 47 | "Component", 48 | "Fragment", 49 | "PureComponent", 50 | "createElement", 51 | "lazy", 52 | "useState", 53 | "Suspense" 54 | ], 55 | "node_modules/react-dom/index.js": ["render"], 56 | "node_modules/react-is/index.js": ["isValidElementType"] 57 | } 58 | }), 59 | eslint({ 60 | exclude: ["**/*.scss", "**/*.css", "node_modules/**"] 61 | }), 62 | babel({ 63 | exclude: ["node_modules/**", "__tests__/**"] 64 | }), 65 | ]; 66 | 67 | if (process.env.npm_lifecycle_event === 'start') { 68 | plugins.push( 69 | serve({ 70 | open: true, 71 | verbose: true, 72 | contentBase: ["dev"], 73 | historyApiFallback: false, 74 | host: "localhost", 75 | port: 10001 76 | }), 77 | livereload() 78 | ) 79 | } 80 | 81 | export default { 82 | input: "src/app.js", 83 | cache: cache, 84 | output: { 85 | file: "dev/script/index.umd.js", 86 | format: "umd", 87 | name: "pureReactCarousel", 88 | sourcemap: true, 89 | sourcemapFile: path.resolve("dev/main.umd.js") 90 | }, 91 | // exclude peerDependencies from our bundle, except for react, react-dom, prop-types when dev'ing 92 | external: Object.keys(omit(pkg.peerDependencies, ["react", "react-dom"])), 93 | plugins 94 | }; 95 | -------------------------------------------------------------------------------- /rollup.config.es.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import { eslint } from 'rollup-plugin-eslint'; 4 | import path from 'path'; 5 | import postcss from 'rollup-plugin-postcss'; 6 | import replace from 'rollup-plugin-replace'; 7 | import resolve from 'rollup-plugin-node-resolve'; 8 | import uglify from 'rollup-plugin-uglify-es'; 9 | 10 | var pkg = require('./package.json'); 11 | var cache; 12 | 13 | export default { 14 | input: 'src/index.js', 15 | cache: cache, 16 | output: { 17 | file: 'dist/index.es.js', 18 | format: 'es', 19 | sourcemap: true, 20 | sourcemapFile: path.resolve('dist/main.es.js'), 21 | }, 22 | // exclude peerDependencies from our bundle 23 | external: Object.keys(pkg.peerDependencies), 24 | plugins: [ 25 | postcss({ 26 | extensions: ['.css', '.scss'], 27 | extract: 'dist/react-carousel.es.css', 28 | minimize: true, 29 | modules: { 30 | // customize the name of the css classes that are created 31 | generateScopedName: '[local]___[hash:base64:5]', 32 | }, 33 | sourceMap: true, 34 | }), 35 | resolve({ 36 | module: true, 37 | jsnext: true, 38 | main: true, 39 | browser: true, 40 | extensions: ['.js', '.jsx'], 41 | customResolveOptions: { 42 | moduleDirectory: 'node_modules' 43 | } 44 | }), 45 | replace({ 'process.env.NODE_ENV': JSON.stringify('production') }), 46 | commonjs(), 47 | eslint({ 48 | exclude: [ 49 | '**/*.css', 50 | '**/*.scss', 51 | 'node_modules/**' 52 | ] 53 | }), 54 | babel({ 55 | exclude: [ 56 | 'node_modules/**' 57 | ], 58 | }), 59 | replace({ 60 | include: 'src/**', 61 | ENV: JSON.stringify(process.env.NODE_ENV || 'production') 62 | }), 63 | (process.env.NODE_ENV === 'production' && uglify()) 64 | ], 65 | } 66 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "babel-eslint", 4 | "parserOptions": { 5 | "ecmaVersion": 2018, 6 | "ecmaFeatures": { 7 | "jsx": true 8 | }, 9 | }, 10 | "env": { 11 | "browser": true, 12 | "es6": true 13 | }, 14 | "globals": { 15 | "ENV": true, 16 | "react": true, 17 | "react-dom": true 18 | }, 19 | "extends": [ 20 | "airbnb" 21 | ], 22 | 23 | "plugins": [ 24 | "react", 25 | "jsx-a11y" 26 | ], 27 | "rules": { 28 | "import/no-extraneous-dependencies": ["error", { "peerDependencies": true }], 29 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 30 | "react/forbid-prop-types": "off", 31 | "react/destructuring-assignment": [0, "never"], 32 | "jsx-a11y/label-has-for": [ 2, { 33 | "components": [ "Label" ], 34 | "required": { 35 | "every": [ "nesting", "id" ] 36 | }, 37 | "allowChildren": true 38 | }], 39 | "jsx-a11y/label-has-associated-control": [ 2, { 40 | "labelAttributes": ["label"], 41 | "controlComponents": ["input", "select"], 42 | "depth": 3, 43 | }], 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/App/README.md: -------------------------------------------------------------------------------- 1 | # App 2 | The App component is for development only. Rollup Tree-shaking should exclude this folder and it's 3 | contents from the final product. 4 | -------------------------------------------------------------------------------- /src/App/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "mrb3k-jest" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/App/__tests__/App.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import App from '../App'; 5 | 6 | configure({ adapter: new Adapter() }); 7 | 8 | 9 | describe('', () => { 10 | it('should render the demo app', () => { 11 | const wrapper = shallow(); 12 | expect(wrapper.exists()).toBe(true); 13 | }); 14 | it('should call handleChange when a select is changed', () => { 15 | const wrapper = shallow(); 16 | expect(wrapper.state('value')).toBe('0'); 17 | wrapper.find('.select').simulate('change', { target: { value: '5' } }); 18 | wrapper.update(); 19 | expect(wrapper.state('value')).toBe('5'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/App/examples/Example1/Example1.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, ButtonFirst, ButtonLast, ButtonNext, 4 | CarouselProvider, DotGroup, ImageWithZoom, Slide, Slider, 5 | } from '../../..'; 6 | 7 | import s from '../../style.scss'; 8 | 9 | export default () => ( 10 | 18 |

Carousel (With Master Loading Spinner)

19 |

20 | This spinner will go away after all the images have loaded. You might want to use 21 | Chrome dev tools to throttle the network connection so you can see the spinner. 22 |

23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | First 44 | Back 45 | Next 46 | Last 47 | 48 |
49 | ); 50 | -------------------------------------------------------------------------------- /src/App/examples/Example1/index.js: -------------------------------------------------------------------------------- 1 | import Example1 from './Example1'; 2 | 3 | export default Example1; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example10/CustomSpinner.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import s from './CustomSpinner.scss'; 3 | 4 | export default class CustomSpinner extends React.Component { 5 | static meta = { 6 | VERSION: '0.0.0', 7 | } 8 | 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/App/examples/Example10/CustomSpinner.scss: -------------------------------------------------------------------------------- 1 | // thanks to Fabio Ottaviani 2 | // https://codepen.io/supah/pen/BjYLdW 3 | 4 | .spinner { 5 | animation: rotate 2s linear infinite; 6 | z-index: 2; 7 | position: absolute; 8 | top: 50%; 9 | left: 50%; 10 | margin: -25px 0 0 -25px; 11 | width: 50px; 12 | height: 50px; 13 | } 14 | 15 | .path { 16 | stroke: hsl(210, 70, 75); 17 | stroke-linecap: round; 18 | animation: dash 1.5s ease-in-out infinite; 19 | } 20 | 21 | @keyframes rotate { 22 | 100% { 23 | transform: rotate(360deg); 24 | } 25 | } 26 | 27 | @keyframes dash { 28 | 0% { 29 | stroke-dasharray: 1, 150; 30 | stroke-dashoffset: 0; 31 | } 32 | 50% { 33 | stroke-dasharray: 90, 150; 34 | stroke-dashoffset: -35; 35 | } 36 | 100% { 37 | stroke-dasharray: 90, 150; 38 | stroke-dashoffset: -124; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/App/examples/Example10/Example10.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, ButtonFirst, ButtonLast, ButtonNext, 4 | CarouselProvider, DotGroup, ImageWithZoom, Slide, Slider, 5 | } from '../../..'; 6 | 7 | import CustomSpinner from './CustomSpinner'; 8 | 9 | 10 | import s from '../../style.scss'; 11 | 12 | 13 | export default () => ( 14 | 22 |

Carousel (Custom Spinner)

23 |

24 | This uses a customized spinner. 25 |

26 | }> 27 | 28 | } /> 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | First 47 | Back 48 | Next 49 | Last 50 | 51 |
52 | ); 53 | -------------------------------------------------------------------------------- /src/App/examples/Example10/index.js: -------------------------------------------------------------------------------- 1 | import Example10 from './Example10'; 2 | 3 | export default Example10; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example11/Example11.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, 4 | ButtonFirst, 5 | ButtonLast, 6 | ButtonNext, 7 | CarouselProvider, 8 | DotGroup, 9 | Image, 10 | Slide, 11 | Slider, 12 | } from '../../..'; 13 | 14 | import s from '../../style.scss'; 15 | 16 | function eventLogger(ev) { 17 | // eslint-disable-next-line no-console 18 | console.log(ev.type, ev.target); 19 | } 20 | 21 | export default () => ( 22 | 28 |

Carousel with custom event handlers.

29 |

30 | Simple carousel with custom event handlers attached to the 31 | {' '} 32 | <Slider /> 33 | {' '} 34 | component's 35 | {' '} 36 | trayProps 37 | {' '} 38 | property. Open your browser developer tools and look at 39 | the console log, then manipulate the carousel. 40 |

41 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | First 130 | Back 131 | Next 132 | Last 133 | 134 |
135 | ); 136 | -------------------------------------------------------------------------------- /src/App/examples/Example11/index.js: -------------------------------------------------------------------------------- 1 | import Example11 from './Example11'; 2 | 3 | export default Example11; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example12/Example12.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, ButtonFirst, ButtonLast, ButtonNext, 4 | CarouselProvider, DotGroup, ImageWithZoom, Slide, Slider, 5 | } from '../../..'; 6 | 7 | import s from '../../style.scss'; 8 | 9 | export default () => ( 10 | 19 |

Infinite Carousel

20 |

21 | A carousel that returns to the first slide if the user clicks the Next 22 | button while on the last slide and returns to the last slide if the user 23 | clicks the Back button while on the first slide. Also behaves similarly 24 | with swiping left on the first image or right on the last image. 25 |

26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | First 53 | Back 54 | Next 55 | Last 56 | 57 |
58 | ); 59 | -------------------------------------------------------------------------------- /src/App/examples/Example12/index.js: -------------------------------------------------------------------------------- 1 | import Example12 from './Example12'; 2 | 3 | export default Example12; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example13/Example13.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, ButtonFirst, ButtonLast, ButtonNext, CarouselProvider, Slide, Slider, 4 | } from '../../..'; 5 | 6 | import s from '../../style.scss'; 7 | 8 | export default () => ( 9 | 17 |

With intrinsic axis dimension

18 |

19 | 20 | 21 |

This is a test slide to demonstrate, how this affects height

22 |

23 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor 24 | invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et 25 | accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata 26 | sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing 27 | elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, 28 | sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita 29 | kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. 30 |

31 | 32 | 33 |

This is a test slide to demonstrate, how this affects height

34 |

35 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor 36 | invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et 37 | accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata 38 | sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing 39 | elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, 40 | sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita 41 | kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. 42 |

43 |

This is a test slide to demonstrate, how this affects height

44 |

45 | lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor 46 | invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. at vero eos et 47 | accusam et justo duo dolores et ea rebum. stet clita kasd gubergren, no sea takimata 48 | sanctus est lorem ipsum dolor sit amet. lorem ipsum dolor sit amet, consetetur sadipscing 49 | elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, 50 | sed diam voluptua. at vero eos et accusam et justo duo dolores et ea rebum. stet clita 51 | kasd gubergren, no sea takimata sanctus est lorem ipsum dolor sit amet. 52 |

53 |
54 | 55 |

This is a test slide to demonstrate, how this affects height

56 |

57 | lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor 58 | invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. at vero eos et 59 | accusam et justo duo dolores et ea rebum. stet clita kasd gubergren, no sea takimata 60 | sanctus est lorem ipsum dolor sit amet. lorem ipsum dolor sit amet, consetetur sadipscing 61 | elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, 62 | sed diam voluptua. at vero eos et accusam et justo duo dolores et ea rebum. stet clita 63 | kasd gubergren, no sea takimata sanctus est lorem ipsum dolor sit amet. 64 |

65 |
66 | 67 |

This is a test slide to demonstrate, how this affects height

68 |

69 | lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor 70 | invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. at vero eos et 71 | accusam et justo duo dolores et ea rebum. stet clita kasd gubergren, no sea takimata 72 | sanctus est lorem ipsum dolor sit amet. lorem ipsum dolor sit amet, consetetur sadipscing 73 | elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, 74 | sed diam voluptua. at vero eos et accusam et justo duo dolores et ea rebum. stet clita 75 | kasd gubergren, no sea takimata sanctus est lorem ipsum dolor sit amet. 76 |

77 |
78 | 79 | First 80 | Back 81 | Next 82 | Last 83 |
84 | ); 85 | -------------------------------------------------------------------------------- /src/App/examples/Example13/index.js: -------------------------------------------------------------------------------- 1 | import Example13 from './Example13'; 2 | 3 | export default Example13; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example14/Example14.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 3 | { 4 | ButtonBack, 5 | ButtonFirst, 6 | ButtonLast, 7 | ButtonNext, 8 | CarouselProvider, 9 | Slide, 10 | Slider, 11 | ImageWithZoom, 12 | DotGroup, 13 | } from '../../..'; 14 | 15 | import s from '../../style.scss'; 16 | 17 | export default () => ( 18 |
19 | 26 |

RTL

27 |

28 | A carousel wrapped in an element with 29 | {' '} 30 | dir="rtl" 31 | , demonstrating support for use with right-to-left languages. 32 |

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | First 60 | Back 61 | Next 62 | Last 63 | 64 |
65 |
66 | ); 67 | -------------------------------------------------------------------------------- /src/App/examples/Example14/index.js: -------------------------------------------------------------------------------- 1 | import Example14 from './Example14'; 2 | 3 | export default Example14; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example2/Example2.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, 4 | ButtonFirst, 5 | ButtonLast, 6 | ButtonNext, 7 | CarouselProvider, 8 | DotGroup, 9 | ImageWithZoom, 10 | Slide, 11 | Slider, 12 | } from '../../..'; 13 | 14 | import s from '../../style.scss'; 15 | 16 | export default () => ( 17 | 25 |

Carousel (With Individual Spinners)

26 |

27 | Each ImageWithZoom component has it's own spinner. You might want to use Chrome 28 | dev tools to throttle the network connection so you can see the spinners. 29 |

30 |

31 | Also, 32 | we added the boolean prop dotNumbers to the DotGroup for displaying slide 33 | numbers. 34 |

35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | First 56 | Back 57 | Next 58 | Last 59 | 60 |
61 | ); 62 | -------------------------------------------------------------------------------- /src/App/examples/Example2/index.js: -------------------------------------------------------------------------------- 1 | import Example2 from './Example2'; 2 | 3 | export default Example2; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example3/Example3.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, 4 | ButtonFirst, 5 | ButtonLast, 6 | ButtonNext, 7 | CarouselProvider, 8 | DotGroup, 9 | ImageWithZoom, 10 | Slide, 11 | Slider, 12 | } from '../../..'; 13 | 14 | import s from '../../style.scss'; 15 | 16 | export default () => ( 17 | 24 |

Carousel (Just One Image)

25 |

Single image

26 | 27 | 28 | 29 | 30 | 31 | First 32 | Back 33 | Next 34 | Last 35 | 36 |
37 | ); 38 | -------------------------------------------------------------------------------- /src/App/examples/Example3/index.js: -------------------------------------------------------------------------------- 1 | import Example3 from './Example3'; 2 | 3 | export default Example3; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example4/Example4.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, 4 | ButtonFirst, 5 | ButtonLast, 6 | ButtonNext, 7 | CarouselProvider, 8 | DotGroup, 9 | ImageWithZoom, 10 | Slide, 11 | Slider, 12 | } from '../../..'; 13 | 14 | import s from '../../style.scss'; 15 | 16 | export default () => ( 17 | 25 |

Vertical Carousel (With Master Loading Spinner)

26 |

27 | This is a vertical carousel. 28 |

29 |

30 | TODO: make vertical carousels responsive based on image. It can be done but 31 | it's not part of the requirements for now. 32 |

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | First 54 | Back 55 | Next 56 | Last 57 | 58 |
59 | 60 | ); 61 | -------------------------------------------------------------------------------- /src/App/examples/Example4/index.js: -------------------------------------------------------------------------------- 1 | import Example4 from './Example4'; 2 | 3 | export default Example4; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example5/Example5.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, 4 | ButtonFirst, 5 | ButtonLast, 6 | ButtonNext, 7 | CarouselProvider, 8 | DotGroup, 9 | Image, 10 | Slide, 11 | Slider, 12 | } from '../../..'; 13 | 14 | import s from '../../style.scss'; 15 | 16 | export default () => ( 17 | 23 |

Horizontal Carousel (With Master Loading Spinner)

24 |

25 | Horizontal Carousel with regular, non-zooming images and dotNumbers on the Dot 26 | Group. 27 |

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | First 49 | Back 50 | Next 51 | Last 52 | 53 |
54 | ); 55 | -------------------------------------------------------------------------------- /src/App/examples/Example5/index.js: -------------------------------------------------------------------------------- 1 | import Example5 from './Example5'; 2 | 3 | export default Example5; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example6/Example6.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, 4 | ButtonNext, 5 | CarouselProvider, 6 | DotGroup, 7 | ImageWithZoom, 8 | Slide, 9 | Slider, 10 | } from '../../..'; 11 | 12 | import s from './Example6.scss'; 13 | 14 | export default () => ( 15 | 23 |

Simple Carousel with vertically aligned nav buttons

24 |

25 | Wrap the <Slider />, <ButtonBack />, <ButtonNext /> components in a div with 26 | relative positioning. Add absolute positioning to the buttons. 27 |

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | Back 50 | Next 51 |
52 | 53 |
54 | ); 55 | -------------------------------------------------------------------------------- /src/App/examples/Example6/Example6.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | max-width: 800px; 4 | margin: auto; 5 | } 6 | 7 | .buttonBack { 8 | position: absolute; 9 | top: 50%; 10 | left: 0; 11 | transform: translateY(-50%); 12 | } 13 | 14 | .buttonNext { 15 | position: absolute; 16 | top: 50%; 17 | right: 0; 18 | transform: translateY(-50%); 19 | } 20 | 21 | 22 | .dotGroup { 23 | text-align: center; 24 | } 25 | -------------------------------------------------------------------------------- /src/App/examples/Example6/index.js: -------------------------------------------------------------------------------- 1 | import Example6 from './Example6'; 2 | 3 | export default Example6; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example7/Example7.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, 4 | ButtonNext, 5 | CarouselProvider, 6 | DotGroup, 7 | Slide, 8 | Slider, 9 | } from '../../..'; 10 | import SlideComponent from './SlideComponent'; 11 | import s from './Example7.scss'; 12 | 13 | export default () => ( 14 | 21 |

Simple Carousel With React Redux

22 |

23 | The slides in this example use components that utilize React-Redux to track a counter value. 24 | This example demonstrates that the context used by Pure React Carousel won't interfere 25 | with components connected to React-Redux. 26 |

27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Back 49 | Next 50 |
51 | 52 |
53 | ); 54 | -------------------------------------------------------------------------------- /src/App/examples/Example7/Example7.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | max-width: 800px; 3 | } 4 | 5 | .slider { 6 | background-color: yellow; 7 | } 8 | 9 | .slide:nth-child(1) { background: darkred; color: white } 10 | .slide:nth-child(2) { background: limegreen; } 11 | .slide:nth-child(3) { background: cyan; } 12 | .slide:nth-child(4) { background: royalblue; color: white } 13 | .slide:nth-child(5) { background: orange; } 14 | -------------------------------------------------------------------------------- /src/App/examples/Example7/SlideComponent/SlideComponent.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import s from './SliderComponent.css'; 4 | 5 | 6 | export default class SlideComponent extends React.Component { 7 | static propTypes = { 8 | count: PropTypes.number.isRequired, 9 | setIncrement: PropTypes.func.isRequired, 10 | setDecrement: PropTypes.func.isRequired, 11 | children: PropTypes.node, 12 | }; 13 | 14 | static defaultProps = { 15 | children: null, 16 | } 17 | 18 | constructor() { 19 | super(); 20 | 21 | this.handleIncrement = this.handleIncrement.bind(this); 22 | this.handleDecrement = this.handleDecrement.bind(this); 23 | } 24 | 25 | handleIncrement() { 26 | this.props.setIncrement(); 27 | } 28 | 29 | handleDecrement() { 30 | this.props.setDecrement(); 31 | } 32 | 33 | render() { 34 | return ( 35 |
36 |
{this.props.children}
37 |
38 |

39 | Count: 40 | { this.props.count } 41 |

42 |

43 | 44 | 45 |

46 |
47 |
48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/App/examples/Example7/SlideComponent/SliderComponent.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | } 7 | -------------------------------------------------------------------------------- /src/App/examples/Example7/SlideComponent/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import SlideComponent from './SlideComponent'; 3 | import { setIncrement, setDecrement } from '../redux/demo-dux'; 4 | 5 | export default connect( 6 | state => ({ count: state.demoReducer.count }), 7 | { setIncrement, setDecrement }, 8 | )(SlideComponent); 9 | -------------------------------------------------------------------------------- /src/App/examples/Example7/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createStore, combineReducers } from 'redux'; 3 | import { Provider } from 'react-redux'; 4 | import demoReducer from './redux/demo-dux'; 5 | import Example7 from './Example7'; 6 | 7 | 8 | // create a redux store to store application state 9 | /* eslint-disable no-underscore-dangle */ 10 | const store = createStore( 11 | combineReducers({ demoReducer }), 12 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), 13 | ); 14 | /* eslint-enable */ 15 | 16 | export default () => ( 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/App/examples/Example7/redux/demo-dux.js: -------------------------------------------------------------------------------- 1 | // DUCKS module (Redux Reducer Bundles) 2 | // see: https://github.com/erikras/ducks-modular-redux 3 | 4 | // Action types 5 | // ------------ 6 | // 7 | // Declare action types as constants. The action types need to be unique strings. Since there will 8 | // most likely be more than one ocassion to clear a form, the best practice is to namespace the 9 | // constant values. Programatically, the "/" means nothing. It's just a convention we're using to 10 | // namespace the actions. 11 | const INCREMENT = 'demo/INCREMENT'; 12 | const DECREMENT = 'demo/DECREMENT'; 13 | 14 | // Initial State 15 | // ------------- 16 | const initialState = { 17 | count: 0, 18 | }; 19 | 20 | // Reducer 21 | // ------- 22 | export default function reducer(state = initialState, action = {}) { 23 | switch (action.type) { 24 | case INCREMENT: { 25 | let { count } = state; 26 | count += 1; 27 | return Object.assign({}, state, { count }); 28 | } 29 | case DECREMENT: { 30 | let { count } = state; 31 | count -= 1; 32 | return Object.assign({}, state, { count }); 33 | } 34 | default: 35 | return state; 36 | } 37 | } 38 | 39 | // Action Creators 40 | // --------------- 41 | export const setIncrement = () => ({ type: INCREMENT }); 42 | export const setDecrement = () => ({ type: DECREMENT }); 43 | -------------------------------------------------------------------------------- /src/App/examples/Example7/redux/demo-store.js: -------------------------------------------------------------------------------- 1 | // import { createStore } from 'redux'; 2 | // 3 | // export default rootReducer => createStore(rootReducer); 4 | -------------------------------------------------------------------------------- /src/App/examples/Example8/Example8.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ButtonBack, ButtonFirst, ButtonLast, ButtonNext, 4 | CarouselProvider, DotGroup, ImageWithZoom, Slide, Slider, 5 | } from '../../..'; 6 | 7 | import s from '../../style.scss'; 8 | 9 | export default () => ( 10 | 19 |

Carousel (lockOnWindowScroll set to TRUE)

20 |

21 | - When scrolling the browser, up and down, horizontal scrollbars will not scroll horizontally. 22 |

23 |

24 | - When scrolling the browser, left and right, vertical scrollbars will not scroll vertically. 25 |

26 |

27 | - This spinner will go away after all the images have loaded. 28 |
29 |

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | First 51 | Back 52 | Next 53 | Last 54 | 55 |
56 | ); 57 | -------------------------------------------------------------------------------- /src/App/examples/Example8/index.js: -------------------------------------------------------------------------------- 1 | import Example8 from './Example8'; 2 | 3 | export default Example8; 4 | -------------------------------------------------------------------------------- /src/App/examples/Example9/Example9.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { 3 | ButtonBack, 4 | ButtonFirst, 5 | ButtonLast, 6 | ButtonNext, 7 | ButtonPlay, 8 | CarouselProvider, 9 | DotGroup, 10 | ImageWithZoom, 11 | Slide, 12 | Slider, 13 | } from '../../..'; 14 | 15 | import s from '../../style.scss'; 16 | 17 | export default () => { 18 | const [slide, setSlide] = useState(0); 19 | 20 | return ( 21 | 22 |
23 | 26 | 46 |
47 | 57 |

Horizontal Carousel Auto Play

58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | First 80 | Back 81 | Next 82 | Last 83 | 84 |
85 |
86 | ); 87 | }; 88 | -------------------------------------------------------------------------------- /src/App/examples/Example9/index.js: -------------------------------------------------------------------------------- 1 | import Example9 from './Example9'; 2 | 3 | export default Example9; 4 | -------------------------------------------------------------------------------- /src/App/examples/index.js: -------------------------------------------------------------------------------- 1 | export { default as Example1 } from './Example1'; 2 | export { default as Example2 } from './Example2'; 3 | export { default as Example3 } from './Example3'; 4 | export { default as Example4 } from './Example4'; 5 | export { default as Example5 } from './Example5'; 6 | export { default as Example6 } from './Example6'; 7 | // export { default as Example7 } from './Example7'; 8 | export { default as Example8 } from './Example8'; 9 | export { default as Example9 } from './Example9'; 10 | export { default as Example10 } from './Example10'; 11 | export { default as Example11 } from './Example11'; 12 | export { default as Example12 } from './Example12'; 13 | export { default as Example13 } from './Example13'; 14 | export { default as Example14 } from './Example14'; 15 | -------------------------------------------------------------------------------- /src/App/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | All of the CSS below is strictly for the demo. none of the components rely on this for actual 3 | functionality. 4 | */ 5 | 6 | $dark: #373737; 7 | $light: #f4f4f4; 8 | $white: #fefefe; 9 | $highlight1: #c0b283; 10 | $highlight2: #dcd0c0; 11 | $font-size--large-headline--mobile: 4rem; 12 | $font-size--large-headline--desktop: 4.8rem; 13 | $font-size--large-text--mobile: 1.6rem; 14 | $font-size--large-text--desktop: 2rem; 15 | 16 | html { 17 | scroll-behavior: smooth; 18 | font-size: 10px; 19 | } 20 | 21 | body { 22 | margin: 0; 23 | font-family: 'Raleway', sans-serif; 24 | color: $dark; 25 | background-color: $light; 26 | } 27 | 28 | hr { 29 | border: 0; 30 | border-bottom: 1px dashed $highlight1; 31 | background: $highlight2; 32 | margin: 2rem 0; 33 | } 34 | 35 | h1, 36 | h2, 37 | h3, 38 | h4, 39 | h5, 40 | h6 { 41 | font-family: 'Montserrat', sans-serif; 42 | } 43 | 44 | p { 45 | font-size: 1.2rem; 46 | line-height: 1.5em; 47 | 48 | @media all and (min-width: 480px) { 49 | font-size: 1.3rem; 50 | } 51 | } 52 | 53 | .responsiveImg { 54 | max-width: 100%; 55 | } 56 | 57 | .heroHeadline { 58 | font-size: $font-size--large-headline--mobile; 59 | text-align: center; 60 | color: $highlight1; 61 | line-height: 4rem; 62 | 63 | @media all and (min-width: 768px) and (min-height: 570px) { 64 | margin-top: 0; 65 | font-size: $font-size--large-headline--desktop; 66 | line-height: 4.4rem; 67 | } 68 | } 69 | 70 | .heroText { 71 | font-size: $font-size--large-text--mobile; 72 | text-align: justify; 73 | 74 | &:last-child { 75 | margin-bottom: 0; 76 | } 77 | 78 | @media all and (min-width: 768px) and (min-height: 570px) { 79 | font-size: $font-size--large-text--desktop; 80 | } 81 | } 82 | 83 | .heroList { 84 | font-size: $font-size--large-text--mobile; 85 | line-height: 2rem; 86 | 87 | a { 88 | color: $highlight2; 89 | text-decoration: none; 90 | 91 | &:hover, 92 | &:active, 93 | &:visited { 94 | color: $highlight2; 95 | } 96 | } 97 | } 98 | 99 | .logo { 100 | margin: 2rem 0; 101 | } 102 | 103 | .logofill { 104 | fill: $dark; 105 | } 106 | 107 | .firstSlide { 108 | position: relative; 109 | color: $light; 110 | background-color: $dark; 111 | 112 | @media all and (min-height: 570px) { 113 | min-height: 80vh; 114 | } 115 | 116 | &CenterVert { 117 | @media all and (min-height: 570px) { 118 | position: absolute; 119 | top: 50%; 120 | transform: translateY(-50%); 121 | width: 100%; 122 | } 123 | } 124 | 125 | &CenterHoriz { 126 | max-width: 460px; 127 | margin: auto; 128 | padding: 20px; 129 | } 130 | } 131 | 132 | .secondSlide { 133 | max-width: 768px; 134 | margin: auto; 135 | padding: 20px; 136 | font-size: 1.2rem; 137 | } 138 | 139 | .examples { 140 | padding: 1rem; 141 | background-color: $white; 142 | 143 | &Demo { 144 | @extend .examples; 145 | background-color: $highlight2; 146 | h2, h3 { 147 | color: $white; 148 | } 149 | } 150 | 151 | &Inner { 152 | max-width: 100rem; 153 | margin: auto; 154 | 155 | &Center { 156 | @extend .examplesInner; 157 | text-align: center; 158 | } 159 | } 160 | } 161 | 162 | .example { 163 | .headline { 164 | text-align: left; 165 | } 166 | } 167 | 168 | .headline { 169 | text-align: center; 170 | color: $highlight1; 171 | } 172 | 173 | .select { 174 | width: 300px; 175 | } 176 | 177 | .slider { 178 | max-width: 800px; 179 | } 180 | 181 | .externalLink { 182 | vertical-align: baseline; 183 | fill: $highlight2; 184 | margin: 0 0.4rem; 185 | width: 1.4rem; 186 | height: 1.4rem; 187 | position: relative; 188 | top: 0.2rem; 189 | } 190 | 191 | .verticalSlider { 192 | width: 100%; 193 | max-width: 300px; 194 | } 195 | 196 | .express { 197 | text-align: center; 198 | padding: 2rem; 199 | } 200 | -------------------------------------------------------------------------------- /src/ButtonBack/ButtonBack.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { CarouselPropTypes, cn } from '../helpers'; 4 | import s from './ButtonBack.scss'; 5 | 6 | export default class ButtonBack extends React.Component { 7 | static propTypes = { 8 | carouselStore: PropTypes.object.isRequired, 9 | children: CarouselPropTypes.children.isRequired, 10 | className: PropTypes.string, 11 | currentSlide: PropTypes.number.isRequired, 12 | disabled: PropTypes.bool, 13 | onClick: PropTypes.func, 14 | step: PropTypes.number.isRequired, 15 | totalSlides: PropTypes.number.isRequired, 16 | visibleSlides: PropTypes.number.isRequired, 17 | infinite: PropTypes.bool, 18 | }; 19 | 20 | static defaultProps = { 21 | className: null, 22 | disabled: null, 23 | onClick: null, 24 | infinite: false, 25 | }; 26 | 27 | static setDisabled(disabled, currentSlide, infinite) { 28 | if (disabled !== null) return disabled; 29 | if (currentSlide === 0 && !infinite) return true; 30 | return false; 31 | } 32 | 33 | constructor(props) { 34 | super(props); 35 | this.handleOnClick = this.handleOnClick.bind(this); 36 | } 37 | 38 | handleOnClick(ev) { 39 | const { 40 | carouselStore, currentSlide, onClick, step, infinite, visibleSlides, totalSlides, 41 | } = this.props; 42 | 43 | const maxSlide = totalSlides - visibleSlides; 44 | 45 | let newCurrentSlide = Math.max( 46 | currentSlide - step, 47 | 0, 48 | ); 49 | 50 | if (infinite) { 51 | const isOnFirstSlide = currentSlide === 0; 52 | newCurrentSlide = isOnFirstSlide ? maxSlide : newCurrentSlide; 53 | } 54 | 55 | carouselStore.setStoreState( 56 | { 57 | currentSlide: newCurrentSlide, 58 | isPlaying: false, 59 | }, 60 | onClick !== null && onClick.call(this, ev), 61 | ); 62 | } 63 | 64 | render() { 65 | const { 66 | carouselStore, 67 | className, 68 | currentSlide, 69 | disabled, 70 | onClick, 71 | step, 72 | totalSlides, 73 | visibleSlides, 74 | infinite, 75 | ...props 76 | } = this.props; 77 | 78 | const newClassName = cn([s.buttonBack, 'carousel__back-button', className]); 79 | const isDisabled = ButtonBack.setDisabled( 80 | this.props.disabled, 81 | this.props.currentSlide, 82 | infinite, 83 | ); 84 | 85 | return ( 86 | 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/ButtonBack/ButtonBack.scss: -------------------------------------------------------------------------------- 1 | .buttonBack { 2 | cursor: pointer; 3 | } 4 | -------------------------------------------------------------------------------- /src/ButtonBack/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/ButtonBack/__tests__/ButtonBack.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount, configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import ButtonBack from '../ButtonBack'; 5 | 6 | import Store from '../../Store/Store'; 7 | import { CarouselProvider } from '../..'; 8 | import ButtonBackWithStore from '..'; 9 | 10 | configure({ adapter: new Adapter() }); 11 | 12 | 13 | describe('', () => { 14 | it('should render', () => { 15 | const wrapper = shallow( 16 | 17 | Hello 18 | , 19 | ); 20 | expect(wrapper.exists()).toBe(true); 21 | }); 22 | it('should be disabled if the currentSlide is 0', () => { 23 | const wrapper = shallow( 24 | 31 | Hello 32 | , 33 | ); 34 | expect(wrapper.prop('disabled')).toBe(true); 35 | }); 36 | it('should be disabled if the disabled prop is set manually, regardless of currentSlide', () => { 37 | const wrapper = shallow( 38 | 46 | Hello 47 | , 48 | ); 49 | expect(wrapper.prop('disabled')).toBe(true); 50 | }); 51 | it('should subract the value of step from currentSlide when clicked.', () => { 52 | const mockStore = new Store({ 53 | currentSlide: 4, 54 | step: 3, 55 | }); 56 | 57 | const wrapper = mount( 58 | 65 | Hello 66 | , 67 | ); 68 | wrapper.find('button').simulate('click'); 69 | expect(mockStore.getStoreState().currentSlide).toBe(1); 70 | }); 71 | it('should subract the value of step from currentSlide when clicked and infinite is true.', () => { 72 | const mockStore = new Store({ 73 | currentSlide: 4, 74 | step: 3, 75 | totalSlides: 10, 76 | visibleSlides: 3, 77 | }); 78 | 79 | const wrapper = mount( 80 | 88 | back 89 | , 90 | ); 91 | expect(wrapper.find('button').prop('disabled')).toBe(false); 92 | wrapper.find('button').simulate('click'); 93 | expect(mockStore.getStoreState().currentSlide).toBe(1); 94 | }); 95 | it('should set the current slide to last if clicked when on the first slide if infinite is true.', () => { 96 | const mockStore = new Store({ 97 | currentSlide: 0, 98 | step: 3, 99 | totalSlides: 10, 100 | visibleSlides: 3, 101 | }); 102 | 103 | const wrapper = mount( 104 | 112 | Back 113 | , 114 | ); 115 | expect(wrapper.find('button').prop('disabled')).toBe(false); 116 | wrapper.find('button').simulate('click'); 117 | expect(mockStore.getStoreState().currentSlide).toBe(7); 118 | }); 119 | it('should call an onClick function passed as a prop', () => { 120 | const mockStore = new Store({ 121 | currentSlide: 4, 122 | step: 3, 123 | }); 124 | 125 | const mockOnClick = jest.fn(); 126 | 127 | const wrapper = mount( 128 | 136 | Hello 137 | , 138 | ); 139 | wrapper.find('button').simulate('click'); 140 | expect(mockOnClick.mock.calls.length).toBe(1); 141 | }); 142 | xit('should disable the button and change the slide to 0 if currentSlide - step <= 0', () => { 143 | const wrapper = mount(( 144 | 151 | Hello 152 | 153 | )); 154 | 155 | expect(wrapper.instance().getStore().state.currentSlide).toBe(1); 156 | wrapper.find('button').simulate('click'); 157 | wrapper.update(); 158 | expect(wrapper.instance().getStore().state.currentSlide).toBe(0); 159 | expect(wrapper.find('button').prop('disabled')).toBe(true); 160 | }); 161 | it('should pass through any classess and append them to the list of classnames', () => { 162 | const wrapper = shallow( 163 | 171 | Hello 172 | , 173 | ); 174 | const classes = wrapper.find('button').prop('className').split(' '); 175 | expect(classes[0]).toEqual('buttonBack'); 176 | expect(classes[1]).toEqual('carousel__back-button'); 177 | expect(classes[2]).toEqual('bob'); 178 | }); 179 | it('should pass through any props not consumed by the component', () => { 180 | const wrapper = shallow( 181 | 189 | Hello 190 | , 191 | ); 192 | expect(wrapper.find('button').prop('foo')).toEqual('bar'); 193 | }); 194 | it('should pause autoplay when clicked', () => { 195 | const wrapper = mount( 196 | 204 | Hello 205 | , 206 | ); 207 | wrapper.find('button').simulate('click'); 208 | expect(wrapper.instance().getStore().state.isPlaying).toBe(false); 209 | }); 210 | }); 211 | -------------------------------------------------------------------------------- /src/ButtonBack/index.js: -------------------------------------------------------------------------------- 1 | import ButtonBack from './ButtonBack'; 2 | import WithStore from '../Store/WithStore'; 3 | 4 | export default WithStore(ButtonBack, state => ({ 5 | currentSlide: state.currentSlide, 6 | step: state.step, 7 | totalSlides: state.totalSlides, 8 | visibleSlides: state.visibleSlides, 9 | infinite: state.infinite, 10 | })); 11 | -------------------------------------------------------------------------------- /src/ButtonFirst/ButtonFirst.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import s from './ButtonFirst.scss'; 4 | import { CarouselPropTypes, cn } from '../helpers'; 5 | 6 | const ButtonFirst = class ButtonFirst extends React.Component { 7 | static propTypes = { 8 | carouselStore: PropTypes.object.isRequired, 9 | children: CarouselPropTypes.children.isRequired, 10 | className: PropTypes.string, 11 | currentSlide: PropTypes.number.isRequired, 12 | disabled: PropTypes.bool, 13 | onClick: PropTypes.func, 14 | totalSlides: PropTypes.number.isRequired, 15 | } 16 | 17 | static defaultProps = { 18 | className: null, 19 | disabled: null, 20 | onClick: null, 21 | } 22 | 23 | constructor() { 24 | super(); 25 | this.handleOnClick = this.handleOnClick.bind(this); 26 | } 27 | 28 | handleOnClick(ev) { 29 | const { carouselStore, onClick } = this.props; 30 | carouselStore.setStoreState({ 31 | currentSlide: 0, 32 | isPlaying: false, 33 | }, onClick !== null && onClick.call(this, ev)); 34 | } 35 | 36 | render() { 37 | const { 38 | carouselStore, 39 | className, 40 | currentSlide, 41 | disabled, 42 | onClick, 43 | totalSlides, 44 | ...props 45 | } = this.props; 46 | 47 | const newClassName = cn([ 48 | s.buttonFirst, 49 | 'carousel__first-button', 50 | className, 51 | ]); 52 | 53 | const newDisabled = disabled !== null ? disabled : currentSlide === 0; 54 | 55 | return ( 56 | 66 | ); 67 | } 68 | }; 69 | 70 | export default ButtonFirst; 71 | -------------------------------------------------------------------------------- /src/ButtonFirst/ButtonFirst.scss: -------------------------------------------------------------------------------- 1 | .buttonFirst { 2 | cursor: pointer; 3 | } 4 | -------------------------------------------------------------------------------- /src/ButtonFirst/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/ButtonFirst/__tests__/ButtonFirst.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount, configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import clone from 'clone'; 5 | import ButtonFirst from '../ButtonFirst'; 6 | import components from '../../helpers/component-config'; 7 | 8 | configure({ adapter: new Adapter() }); 9 | 10 | 11 | let props; 12 | 13 | describe('', () => { 14 | beforeEach(() => { 15 | props = clone(components.ButtonFirst.props); 16 | }); 17 | it('should render', () => { 18 | const wrapper = shallow(); 19 | expect(wrapper.exists()).toBe(true); 20 | }); 21 | it('should be disabled if the currentSlide is 0', () => { 22 | const newProps = Object.assign({}, props, { currentSlide: 0 }); 23 | const wrapper = shallow(); 24 | expect(wrapper.prop('disabled')).toBe(true); 25 | }); 26 | it('should be disabled if the disabled prop is set manually, regardless of currentSlide', () => { 27 | const wrapper = shallow(); 28 | expect(wrapper.prop('disabled')).toBe(true); 29 | }); 30 | it('should set the currentSlide to 0 when clicked', () => { 31 | const wrapper = mount(); 32 | wrapper.find('button').simulate('click'); 33 | wrapper.update(); 34 | expect(props.carouselStore.getStoreState().currentSlide).toBe(0); 35 | }); 36 | it('should call an onClick function passed as a prop', () => { 37 | const onClick = jest.fn(); 38 | const newProps = Object.assign({}, props, { onClick }); 39 | const wrapper = mount(); 40 | wrapper.find('button').simulate('click'); 41 | expect(onClick.mock.calls.length).toBe(1); 42 | }); 43 | it('should pause autoplay when clicked', () => { 44 | const wrapper = mount(); 45 | wrapper.find('button').simulate('click'); 46 | expect(props.carouselStore.getStoreState().isPlaying).toBe(false); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/ButtonFirst/index.js: -------------------------------------------------------------------------------- 1 | import ButtonFirst from './ButtonFirst'; 2 | import WithStore from '../Store/WithStore'; 3 | 4 | export default WithStore(ButtonFirst, state => ({ 5 | currentSlide: state.currentSlide, 6 | totalSlides: state.totalSlides, 7 | })); 8 | -------------------------------------------------------------------------------- /src/ButtonLast/ButtonLast.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import s from './ButtonLast.scss'; 4 | import { CarouselPropTypes, cn } from '../helpers'; 5 | 6 | const ButtonLast = class ButtonLast extends React.Component { 7 | static propTypes = { 8 | carouselStore: PropTypes.object.isRequired, 9 | children: CarouselPropTypes.children.isRequired, 10 | className: PropTypes.string, 11 | currentSlide: PropTypes.number.isRequired, 12 | disabled: PropTypes.bool, 13 | onClick: PropTypes.func, 14 | totalSlides: PropTypes.number.isRequired, 15 | visibleSlides: PropTypes.number.isRequired, 16 | }; 17 | 18 | static defaultProps = { 19 | className: null, 20 | disabled: null, 21 | onClick: null, 22 | }; 23 | 24 | constructor() { 25 | super(); 26 | this.handleOnClick = this.handleOnClick.bind(this); 27 | } 28 | 29 | handleOnClick(ev) { 30 | const { 31 | carouselStore, onClick, totalSlides, visibleSlides, 32 | } = this.props; 33 | carouselStore.setStoreState( 34 | { 35 | currentSlide: totalSlides - visibleSlides, 36 | isPlaying: false, 37 | }, 38 | onClick !== null && onClick.call(this, ev), 39 | ); 40 | } 41 | 42 | render() { 43 | const { 44 | carouselStore, 45 | className, 46 | currentSlide, 47 | disabled, 48 | onClick, 49 | totalSlides, 50 | visibleSlides, 51 | ...props 52 | } = this.props; 53 | 54 | const newClassName = cn([s.buttonLast, 'carousel__last-button', className]); 55 | 56 | const isDisabled = disabled !== null ? disabled : currentSlide >= totalSlides - visibleSlides; 57 | 58 | return ( 59 | 69 | ); 70 | } 71 | }; 72 | 73 | export default ButtonLast; 74 | -------------------------------------------------------------------------------- /src/ButtonLast/ButtonLast.scss: -------------------------------------------------------------------------------- 1 | .buttonLast { 2 | cursor: pointer; 3 | } 4 | -------------------------------------------------------------------------------- /src/ButtonLast/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/ButtonLast/__tests__/ButtonLast.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount, configure } from 'enzyme'; 3 | import clone from 'clone'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import ButtonLast from '../ButtonLast'; 6 | import Store from '../../Store/Store'; 7 | import components from '../../helpers/component-config'; 8 | 9 | configure({ adapter: new Adapter() }); 10 | 11 | 12 | let props; 13 | 14 | describe('', () => { 15 | beforeEach(() => { 16 | props = clone(components.ButtonLast.props); 17 | }); 18 | it('should render', () => { 19 | const wrapper = shallow(); 20 | expect(wrapper.exists()).toBe(true); 21 | }); 22 | it('should be disabled if the currentSlide is equal to totalSlides', () => { 23 | const newProps = Object.assign({}, props, { currentSlide: props.totalSlides }); 24 | const wrapper = shallow(); 25 | expect(wrapper.prop('disabled')).toBe(true); 26 | }); 27 | it('should be disabled if the disabled prop is set manually, regardless of currentSlide', () => { 28 | const wrapper = shallow(); 29 | expect(wrapper.prop('disabled')).toBe(true); 30 | }); 31 | it('should set the currentSlide to totalSlides - 1 when clicked', () => { 32 | const wrapper = mount(); 33 | wrapper.find('button').simulate('click'); 34 | wrapper.update(); 35 | expect(props.carouselStore.getStoreState().currentSlide).toBe(props.totalSlides - 1); 36 | }); 37 | it('should call an onClick function passed as a prop', () => { 38 | const onClick = jest.fn(); 39 | const newProps = Object.assign({}, props, { onClick }); 40 | const wrapper = mount(); 41 | wrapper.find('button').simulate('click'); 42 | wrapper.update(); 43 | expect(onClick.mock.calls.length).toBe(1); 44 | }); 45 | it('should set the button to disabled when (visibleSlides > 1) and (currentSlide > totalSlides - visibleSlides)', () => { 46 | const newProps = Object.assign({}, props, { 47 | currentSlide: 5, 48 | carouselStore: new Store({ 49 | currentSlide: 5, 50 | totalSlides: 7, 51 | visibleSlides: 2, 52 | }), 53 | totalSlides: 7, 54 | visibleSlides: 2, 55 | }); 56 | const wrapper = shallow(); 57 | expect(wrapper.prop('disabled')).toBe(true); 58 | }); 59 | it('should NOT set the button to disabled when (visibleSlides > 1) and (currentSlide <= totalSlides - visibleSlides)', () => { 60 | const newProps = Object.assign({}, props, { 61 | currentSlide: 4, 62 | carouselStore: new Store({ 63 | currentSlide: 4, 64 | totalSlides: 7, 65 | visibleSlides: 2, 66 | }), 67 | totalSlides: 7, 68 | visibleSlides: 2, 69 | }); 70 | const wrapper = shallow(); 71 | expect(wrapper.prop('disabled')).toBe(false); 72 | }); 73 | it('should pause autoplay when clicked', () => { 74 | const newProps = Object.assign({}, props, { 75 | carouselStore: new Store({ 76 | isPlaying: true, 77 | }), 78 | }); 79 | const wrapper = mount(); 80 | wrapper.find('button').simulate('click'); 81 | expect(newProps.carouselStore.getStoreState().isPlaying).toBe(false); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /src/ButtonLast/index.js: -------------------------------------------------------------------------------- 1 | import ButtonLast from './ButtonLast'; 2 | import WithStore from '../Store/WithStore'; 3 | 4 | export default WithStore(ButtonLast, state => ({ 5 | currentSlide: state.currentSlide, 6 | totalSlides: state.totalSlides, 7 | visibleSlides: state.visibleSlides, 8 | })); 9 | -------------------------------------------------------------------------------- /src/ButtonNext/ButtonNext.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { CarouselPropTypes, cn } from '../helpers'; 4 | import s from './ButtonNext.scss'; 5 | 6 | const ButtonNext = class ButtonNext extends React.PureComponent { 7 | static propTypes = { 8 | carouselStore: PropTypes.object.isRequired, 9 | children: CarouselPropTypes.children.isRequired, 10 | className: PropTypes.string, 11 | currentSlide: PropTypes.number.isRequired, 12 | disabled: PropTypes.bool, 13 | onClick: PropTypes.func, 14 | step: PropTypes.number.isRequired, 15 | totalSlides: PropTypes.number.isRequired, 16 | visibleSlides: PropTypes.number.isRequired, 17 | infinite: PropTypes.bool, 18 | }; 19 | 20 | static defaultProps = { 21 | className: null, 22 | disabled: null, 23 | onClick: null, 24 | infinite: false, 25 | }; 26 | 27 | static setDisabled(disabled, currentSlide, visibleSlides, totalSlides, infinite) { 28 | if (disabled !== null) return disabled; 29 | if (currentSlide >= totalSlides - visibleSlides && !infinite) return true; 30 | return false; 31 | } 32 | 33 | constructor(props) { 34 | super(props); 35 | this.handleOnClick = this.handleOnClick.bind(this); 36 | } 37 | 38 | handleOnClick(ev) { 39 | const { 40 | currentSlide, onClick, step, carouselStore, infinite, totalSlides, visibleSlides, 41 | } = this.props; 42 | 43 | const maxSlide = totalSlides - visibleSlides; 44 | const nextSlide = step + currentSlide; 45 | 46 | let newCurrentSlide = Math.min( 47 | nextSlide, 48 | maxSlide, 49 | ); 50 | 51 | if (infinite) { 52 | const isOnLastSlide = maxSlide === currentSlide; 53 | newCurrentSlide = isOnLastSlide ? 0 : newCurrentSlide; 54 | } 55 | 56 | carouselStore.setStoreState( 57 | { 58 | currentSlide: newCurrentSlide, 59 | isPlaying: false, 60 | }, 61 | onClick !== null && onClick.call(this, ev), 62 | ); 63 | } 64 | 65 | render() { 66 | const { 67 | carouselStore, 68 | className, 69 | currentSlide, 70 | disabled, 71 | onClick, 72 | step, 73 | totalSlides, 74 | visibleSlides, 75 | infinite, 76 | ...props 77 | } = this.props; 78 | 79 | const newClassName = cn([s.buttonNext, 'carousel__next-button', className]); 80 | const isDisabled = ButtonNext.setDisabled( 81 | disabled, 82 | currentSlide, 83 | visibleSlides, 84 | totalSlides, 85 | infinite, 86 | ); 87 | 88 | return ( 89 | 99 | ); 100 | } 101 | }; 102 | 103 | export default ButtonNext; 104 | -------------------------------------------------------------------------------- /src/ButtonNext/ButtonNext.scss: -------------------------------------------------------------------------------- 1 | .buttonNext { 2 | cursor: pointer; 3 | } 4 | -------------------------------------------------------------------------------- /src/ButtonNext/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/ButtonNext/__tests__/ButtonNext.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount, configure } from 'enzyme'; 3 | import clone from 'clone'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import components from '../../helpers/component-config'; 6 | import ButtonNext from '../ButtonNext'; 7 | import Store from '../../Store/Store'; 8 | import CarouselProvider from '../../CarouselProvider/CarouselProvider'; 9 | import ButtonNextWithStore from '..'; 10 | 11 | configure({ adapter: new Adapter() }); 12 | 13 | 14 | let props; 15 | 16 | describe('', () => { 17 | beforeEach(() => { 18 | props = clone(components.ButtonNext.props); 19 | }); 20 | it('should render', () => { 21 | const wrapper = shallow(); 22 | expect(wrapper.exists()).toBe(true); 23 | }); 24 | it('should be disabled if the currentSlide >= totalSlides - visibleSlides', () => { 25 | const newProps = Object.assign({}, props, { 26 | carouselStore: new Store({ 27 | currentSlide: 5, 28 | totalSlides: 7, 29 | visibleSlides: 2, 30 | }), 31 | }); 32 | const wrapper = shallow(); 33 | expect(wrapper.prop('disabled')).toBe(true); 34 | }); 35 | it('should NOT be disabled if the currentSlide < totalSlides - visibleSlides', () => { 36 | const carouselStore = new Store({ 37 | currentSlide: 4, 38 | totalSlides: 7, 39 | visibleSlides: 2, 40 | step: 1, 41 | }); 42 | 43 | const newProps = Object.assign({}, props, { 44 | currentSlide: 4, 45 | totalSlides: 7, 46 | visibleSlides: 2, 47 | step: 1, 48 | carouselStore, 49 | }); 50 | 51 | const wrapper = shallow(); 52 | expect(wrapper.prop('disabled')).toBe(false); 53 | }); 54 | it('should be disabled if the disabled prop is set manually, regardless of currentSlide', () => { 55 | const wrapper = shallow(); 56 | expect(wrapper.prop('disabled')).toBe(true); 57 | }); 58 | it('should add the value of step from currentSlide when clicked.', () => { 59 | const carouselStore = new Store({ 60 | currentSlide: 0, 61 | totalSlides: 7, 62 | visibleSlides: 2, 63 | step: 2, 64 | }); 65 | 66 | const newProps = Object.assign({}, props, { 67 | carouselStore, 68 | currentSlide: 0, 69 | totalSlides: 7, 70 | visibleSlides: 2, 71 | step: 2, 72 | }); 73 | 74 | const wrapper = mount(); 75 | wrapper.find('button').simulate('click'); 76 | wrapper.update(); 77 | expect(carouselStore.state.currentSlide).toBe(2); 78 | }); 79 | it('should add the value of step from currentSlide when clicked and infinite is true.', () => { 80 | const carouselStore = new Store({ 81 | currentSlide: 0, 82 | totalSlides: 7, 83 | visibleSlides: 2, 84 | step: 2, 85 | }); 86 | 87 | const newProps = Object.assign({}, props, { 88 | carouselStore, 89 | currentSlide: 0, 90 | totalSlides: 7, 91 | visibleSlides: 2, 92 | step: 2, 93 | }); 94 | 95 | const wrapper = mount(); 96 | wrapper.find('button').simulate('click'); 97 | wrapper.update(); 98 | expect(carouselStore.state.currentSlide).toBe(2); 99 | }); 100 | it('should set the current slide to first slide if clicked when on the last slide if infinite is true.', () => { 101 | const mockStore = new Store({ 102 | currentSlide: 7, 103 | step: 3, 104 | totalSlides: 10, 105 | }); 106 | 107 | const wrapper = mount( 108 | 116 | Next 117 | , 118 | ); 119 | expect(wrapper.find('button').prop('disabled')).toBe(false); 120 | wrapper.find('button').simulate('click'); 121 | expect(mockStore.getStoreState().currentSlide).toBe(0); 122 | }); 123 | it('should call an onClick function passed as a prop', () => { 124 | const onClick = jest.fn(); 125 | const newProps = Object.assign({}, props, { onClick, currentSlide: 0 }); 126 | const wrapper = mount(); 127 | wrapper.find('button').simulate('click'); 128 | wrapper.update(); 129 | expect(onClick.mock.calls.length).toBe(1); 130 | }); 131 | xit('should disable the button and change the slide to (totalSlides - visibleSlides) if ((currentSlide + step) >= (totalSlides + 1))', () => { 132 | const newProps = { 133 | currentSlide: 4, 134 | totalSlides: 7, 135 | visibleSlides: 2, 136 | step: 4, 137 | naturalSlideWidth: 100, 138 | naturalSlideHeight: 125, 139 | }; 140 | 141 | const wrapper = mount(( 142 | 143 | Next 144 | 145 | )); 146 | 147 | const instance = wrapper.instance(); 148 | expect(instance.carouselStore.state.currentSlide).toBe(4); 149 | wrapper.find('button').simulate('click'); 150 | wrapper.update(); 151 | expect(instance.carouselStore.state.currentSlide).toBe(newProps.totalSlides - newProps.visibleSlides); 152 | expect(wrapper.find('button').prop('disabled')).toBe(true); 153 | }); 154 | it('should pause autoplay when clicked', () => { 155 | const wrapper = mount( 156 | 164 | Hello 165 | , 166 | ); 167 | wrapper.find('button').simulate('click'); 168 | expect(wrapper.instance().getStore().state.isPlaying).toBe(false); 169 | }); 170 | }); 171 | -------------------------------------------------------------------------------- /src/ButtonNext/index.js: -------------------------------------------------------------------------------- 1 | import ButtonNext from './ButtonNext'; 2 | import WithStore from '../Store/WithStore'; 3 | 4 | export default WithStore(ButtonNext, state => ({ 5 | currentSlide: state.currentSlide, 6 | step: state.step, 7 | totalSlides: state.totalSlides, 8 | visibleSlides: state.visibleSlides, 9 | infinite: state.infinite, 10 | })); 11 | -------------------------------------------------------------------------------- /src/ButtonPlay/ButtonPlay.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { cn } from '../helpers'; 4 | import s from './ButtonPlay.scss'; 5 | 6 | const ButtonPlay = class ButtonPlay extends React.PureComponent { 7 | static propTypes = { 8 | carouselStore: PropTypes.object.isRequired, 9 | children: PropTypes.node, 10 | childrenPaused: PropTypes.node, 11 | childrenPlaying: PropTypes.node, 12 | className: PropTypes.string, 13 | isPlaying: PropTypes.bool.isRequired, 14 | onClick: PropTypes.func, 15 | }; 16 | 17 | static defaultProps = { 18 | children: null, 19 | childrenPaused: null, 20 | childrenPlaying: null, 21 | className: null, 22 | onClick: null, 23 | } 24 | 25 | constructor(props) { 26 | super(props); 27 | this.handleOnClick = this.handleOnClick.bind(this); 28 | } 29 | 30 | handleOnClick(ev) { 31 | const { onClick } = this.props; 32 | this.props.carouselStore.setStoreState({ 33 | isPlaying: !this.props.isPlaying, 34 | }, onClick !== null && onClick.call(this, ev)); 35 | } 36 | 37 | render() { 38 | const { 39 | carouselStore, 40 | children, 41 | childrenPaused, 42 | childrenPlaying, 43 | className, 44 | isPlaying, 45 | onClick, 46 | ...props 47 | } = this.props; 48 | 49 | const newClassName = cn([ 50 | s.buttonNext, 51 | 'carousel__play-button', 52 | className, 53 | ]); 54 | 55 | return ( 56 | 67 | ); 68 | } 69 | }; 70 | 71 | export default ButtonPlay; 72 | -------------------------------------------------------------------------------- /src/ButtonPlay/ButtonPlay.scss: -------------------------------------------------------------------------------- 1 | .buttonNext { 2 | cursor: pointer; 3 | } 4 | -------------------------------------------------------------------------------- /src/ButtonPlay/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/ButtonPlay/__tests__/ButtonPlay.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, configure } from 'enzyme'; 3 | import clone from 'clone'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import components from '../../helpers/component-config'; 6 | import ButtonPlay from '../ButtonPlay'; 7 | 8 | configure({ adapter: new Adapter() }); 9 | 10 | let props; 11 | 12 | describe('', () => { 13 | beforeEach(() => { 14 | props = clone(components.ButtonPlay.props); 15 | }); 16 | it('should render', () => { 17 | const wrapper = shallow(); 18 | expect(wrapper.exists()).toBe(true); 19 | }); 20 | it('should update isPlaying in the carousel store toggling it from false to true after one click and back again after another click.', () => { 21 | const wrapper = shallow(); 22 | wrapper.find('button').simulate('click'); 23 | expect(props.carouselStore.state.isPlaying).toBe(true); 24 | // manualy replicate WithStore HOC() 25 | wrapper.setProps({ isPlaying: props.carouselStore.state.isPlaying }); 26 | wrapper.find('button').simulate('click'); 27 | expect(props.carouselStore.state.isPlaying).toBe(false); 28 | }); 29 | it('should call any supplied onClick function on click', () => { 30 | const onClick = jest.fn(); 31 | const wrapper = shallow(); 32 | wrapper.find('button').simulate('click'); 33 | expect(onClick).toHaveBeenCalledTimes(1); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/ButtonPlay/index.js: -------------------------------------------------------------------------------- 1 | import ButtonPlay from './ButtonPlay'; 2 | import WithStore from '../Store/WithStore'; 3 | 4 | export default WithStore(ButtonPlay, state => ({ 5 | isPlaying: state.isPlaying, 6 | })); 7 | -------------------------------------------------------------------------------- /src/CarouselProvider/CarouselConsumer.jsx: -------------------------------------------------------------------------------- 1 | import Context from './context'; 2 | 3 | export default Context.Consumer; 4 | -------------------------------------------------------------------------------- /src/CarouselProvider/CarouselProvider.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Context from './context'; 4 | import Store from '../Store/Store'; 5 | import { 6 | CarouselPropTypes, slideSize, slideTraySize, cn, 7 | } from '../helpers'; 8 | 9 | const CarouselProvider = class CarouselProvider extends React.Component { 10 | static propTypes = { 11 | children: CarouselPropTypes.children.isRequired, 12 | className: PropTypes.string, 13 | currentSlide: PropTypes.number, 14 | disableAnimation: PropTypes.bool, 15 | disableKeyboard: PropTypes.bool, 16 | hasMasterSpinner: PropTypes.bool, 17 | interval: PropTypes.number, 18 | isPageScrollLocked: PropTypes.bool, 19 | isPlaying: PropTypes.bool, 20 | lockOnWindowScroll: PropTypes.bool, 21 | naturalSlideHeight: PropTypes.number.isRequired, 22 | naturalSlideWidth: PropTypes.number.isRequired, 23 | orientation: CarouselPropTypes.orientation, 24 | playDirection: CarouselPropTypes.direction, 25 | step: PropTypes.number, 26 | dragStep: PropTypes.number, 27 | tag: PropTypes.string, 28 | totalSlides: PropTypes.number.isRequired, 29 | touchEnabled: PropTypes.bool, 30 | dragEnabled: PropTypes.bool, 31 | visibleSlides: PropTypes.number, 32 | infinite: PropTypes.bool, 33 | isIntrinsicHeight: PropTypes.bool, 34 | }; 35 | 36 | static defaultProps = { 37 | className: null, 38 | currentSlide: 0, 39 | disableAnimation: false, 40 | disableKeyboard: false, 41 | hasMasterSpinner: false, 42 | interval: 5000, 43 | isPageScrollLocked: false, 44 | isPlaying: false, 45 | lockOnWindowScroll: false, 46 | orientation: 'horizontal', 47 | playDirection: 'forward', 48 | step: 1, 49 | dragStep: 1, 50 | tag: 'div', 51 | touchEnabled: true, 52 | dragEnabled: true, 53 | visibleSlides: 1, 54 | infinite: false, 55 | isIntrinsicHeight: false, 56 | }; 57 | 58 | constructor(props) { 59 | super(props); 60 | if (props.isIntrinsicHeight && props.orientation !== 'horizontal') { 61 | throw Error('isIntrinsicHeight can only be used in "horizontal" orientation. See Readme for more information.'); 62 | } 63 | const options = { 64 | currentSlide: props.currentSlide, 65 | disableAnimation: props.disableAnimation, 66 | disableKeyboard: props.disableKeyboard, 67 | hasMasterSpinner: props.hasMasterSpinner, 68 | imageErrorCount: 0, 69 | imageSuccessCount: 0, 70 | interval: props.interval, 71 | isPageScrollLocked: props.isPageScrollLocked, 72 | isPlaying: props.isPlaying, 73 | lockOnWindowScroll: props.lockOnWindowScroll, 74 | masterSpinnerThreshold: 0, 75 | naturalSlideHeight: props.naturalSlideHeight, 76 | naturalSlideWidth: props.naturalSlideWidth, 77 | orientation: props.orientation, 78 | playDirection: props.playDirection, 79 | privateUnDisableAnimation: false, 80 | slideSize: slideSize(props.totalSlides, props.visibleSlides), 81 | slideTraySize: slideTraySize(props.totalSlides, props.visibleSlides), 82 | step: props.step, 83 | dragStep: props.dragStep, 84 | totalSlides: props.totalSlides, 85 | touchEnabled: props.touchEnabled, 86 | dragEnabled: props.dragEnabled, 87 | visibleSlides: props.visibleSlides, 88 | infinite: props.infinite, 89 | isIntrinsicHeight: props.isIntrinsicHeight, 90 | }; 91 | this.carouselStore = new Store(options); 92 | } 93 | 94 | componentDidUpdate(prevProps) { 95 | const newStoreState = {}; 96 | 97 | [ 98 | 'currentSlide', // poorly named. This is only slide that shows on MOUNT. deprecating soon. 99 | 'disableAnimation', 100 | 'disableKeyboard', 101 | 'hasMasterSpinner', 102 | 'interval', 103 | 'isPlaying', 104 | 'naturalSlideHeight', 105 | 'naturalSlideWidth', 106 | 'lockOnWindowScroll', 107 | 'orientation', 108 | 'playDirection', 109 | 'step', 110 | 'dragStep', 111 | 'totalSlides', 112 | 'touchEnabled', 113 | 'dragEnabled', 114 | 'visibleSlides', 115 | ].forEach((propName) => { 116 | if (prevProps[propName] !== this.props[propName]) { 117 | newStoreState[propName] = this.props[propName]; 118 | } 119 | }); 120 | 121 | const isNewCurrentSlide = this.props.currentSlide !== prevProps.currentSlide; 122 | 123 | // currentSlide, a poorly named variable that determines which slide show when carousel is 124 | // mounted, has changed value. We want to temporarily disable the css transition and just 125 | // "jump" to the new "currentSlide" 126 | 127 | // Disable the css animation, set a private flag to re-enable the animation. 128 | if (isNewCurrentSlide && !this.props.disableAnimation) { 129 | newStoreState.disableAnimation = true; 130 | newStoreState.privateUnDisableAnimation = true; 131 | // Slider.jsx componentDidUpdate detects privateUnDisableAnimation to re-enable animation. 132 | } 133 | 134 | if ( 135 | this.props.totalSlides !== prevProps.totalSlides 136 | || this.props.visibleSlides !== prevProps.visibleSlides 137 | ) { 138 | newStoreState.slideSize = slideSize(this.props.totalSlides, this.props.visibleSlides); 139 | newStoreState.slideTraySize = slideTraySize(this.props.totalSlides, this.props.visibleSlides); 140 | } 141 | 142 | if (this.carouselStore.state.currentSlide >= this.props.totalSlides) { 143 | newStoreState.currentSlide = Math.max(this.props.totalSlides - 1, 0); 144 | } 145 | 146 | if (Object.keys(newStoreState).length > 0) { 147 | this.carouselStore.setStoreState(newStoreState); 148 | } 149 | } 150 | 151 | componentWillUnmount() { 152 | this.carouselStore.unsubscribeAllMasterSpinner(); 153 | } 154 | 155 | // Utility function for tests. 156 | // in jest + enzyme tests you can do wrapper.instance().getStore() 157 | // you can also just do... 158 | // wrapper.instance().carouselStore 159 | // I created this method to make it obvious that you have access to carouselStore. 160 | getStore() { 161 | return this.carouselStore; 162 | } 163 | 164 | render() { 165 | const { 166 | children, 167 | className, 168 | currentSlide, 169 | disableAnimation, 170 | disableKeyboard, 171 | hasMasterSpinner, 172 | interval, 173 | isPageScrollLocked, 174 | isPlaying, 175 | lockOnWindowScroll, 176 | naturalSlideHeight, 177 | naturalSlideWidth, 178 | orientation, 179 | playDirection, 180 | step, 181 | dragStep, 182 | tag: Tag, 183 | totalSlides, 184 | touchEnabled, 185 | dragEnabled, 186 | visibleSlides, 187 | infinite, 188 | isIntrinsicHeight, 189 | ...filteredProps 190 | } = this.props; 191 | 192 | const newClassName = cn(['carousel', this.props.className]); 193 | 194 | return ( 195 | 196 | 197 | {this.props.children} 198 | 199 | 200 | ); 201 | } 202 | }; 203 | 204 | export default CarouselProvider; 205 | -------------------------------------------------------------------------------- /src/CarouselProvider/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/CarouselProvider/__tests__/CarouselProvider.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount, configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import clone from 'clone'; 5 | import CarouselProvider from '..'; 6 | import components from '../../helpers/component-config'; 7 | import Slider from '../../Slider'; 8 | 9 | configure({ adapter: new Adapter() }); 10 | 11 | let props; 12 | 13 | jest.useFakeTimers(); 14 | 15 | describe('', () => { 16 | beforeEach(() => { 17 | props = clone(components.CarouselProvider.props); 18 | }); 19 | it('should render', () => { 20 | const wrapper = shallow( 21 | Hello, 22 | ); 23 | expect(wrapper.exists()).toBe(true); 24 | }); 25 | it('utility function getStoreState should return the state', () => { 26 | const wrapper = shallow( 27 | 28 | Hello 29 | , 30 | ); 31 | const instance = wrapper.instance(); 32 | expect(instance.getStore()).toBe(instance.carouselStore); 33 | }); 34 | it('should update the carouselStore values for slideSize and slideTraySize if totalSlides prop changes', () => { 35 | const wrapper = shallow(); 36 | const instance = wrapper.instance(); 37 | expect(instance.carouselStore.state.slideSize).toBe(25); 38 | expect(instance.carouselStore.state.slideTraySize).toBe(400); 39 | wrapper.setProps({ totalSlides: 2 }); 40 | expect(instance.carouselStore.state.slideSize).toBe(50); 41 | expect(instance.carouselStore.state.slideTraySize).toBe(200); 42 | }); 43 | it('should update the carouselStore values for slideSize and slideTraySize if visibleSlides prop changes', () => { 44 | const wrapper = shallow(); 45 | const instance = wrapper.instance(); 46 | expect(instance.carouselStore.state.slideSize).toBe(25); 47 | expect(instance.carouselStore.state.slideTraySize).toBe(400); 48 | wrapper.setProps({ visibleSlides: 2 }); 49 | expect(instance.carouselStore.state.slideSize).toBe(25); 50 | expect(instance.carouselStore.state.slideTraySize).toBe(200); 51 | }); 52 | it('should keep currentSlide within bounds of totalSlides', () => { 53 | const wrapper = shallow(); 54 | const instance = wrapper.instance(); 55 | wrapper.setProps({ totalSlides: 3 }); 56 | expect(instance.carouselStore.state.currentSlide).toEqual(2); 57 | }); 58 | it('should not update the carouselStore if some prop we do not track changes', () => { 59 | const wrapper = shallow(); 60 | const instance = wrapper.instance(); 61 | const start = clone(instance.carouselStore.state); 62 | wrapper.setProps({ 'data-foo': 2 }); 63 | const end = clone(instance.carouselStore.state); 64 | expect(start).toEqual(end); 65 | }); 66 | it('should not reset the currentSlide or disableAnimation values when unrelated props change', () => { 67 | const wrapper = shallow(); 68 | const instance = wrapper.instance(); 69 | instance.carouselStore.setStoreState({ currentSlide: 2 }); 70 | const start = clone(instance.carouselStore.state); 71 | wrapper.setProps({ naturalSlideWidth: 300 }); 72 | const end = clone(instance.carouselStore.state); 73 | expect(start.currentSlide).toEqual(end.currentSlide); 74 | expect(start.disableAnimation).toEqual(end.disableAnimation); 75 | }); 76 | it('should set disableAnimation to true and privateUnDisableAnimation to true if we updated currentSlide prop on CarouselProvider component', async () => { 77 | const wrapper = mount( 78 | Hello, 79 | ); 80 | wrapper.setProps({ currentSlide: 1 }); 81 | expect(wrapper.instance().getStore().state.disableAnimation).toBe(true); 82 | expect(wrapper.instance().getStore().state.privateUnDisableAnimation).toBe( 83 | true, 84 | ); 85 | }); 86 | it('The Slider component should reset disableAnimation to false and privateUnDisableAnimation to false if when Slider component is updated', async () => { 87 | const wrapper = mount( 88 | 89 | Hello 90 | , 91 | ); 92 | wrapper.setProps({ currentSlide: 1 }); 93 | expect(wrapper.instance().getStore().state.disableAnimation).toBe(false); 94 | expect(wrapper.instance().getStore().state.privateUnDisableAnimation).toBe( 95 | false, 96 | ); 97 | }); 98 | it('should correctly set store variable when using isIntrinsicHeight', async () => { 99 | const wrapper = mount( 100 | 101 | test 102 | , 103 | ); 104 | expect(wrapper.instance().getStore().state.isIntrinsicHeight).toBe(true); 105 | }); 106 | it('should throw an error, when trying to use isIntrinsicHeight in vertical orientation', async () => { 107 | expect(() => shallow( 108 | 109 | test 110 | , 111 | )).toThrow(Error); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /src/CarouselProvider/context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Context = React.createContext(); 4 | export default Context; 5 | -------------------------------------------------------------------------------- /src/CarouselProvider/index.js: -------------------------------------------------------------------------------- 1 | import CarouselProvider from './CarouselProvider'; 2 | import CarouselContext from './context'; 3 | import CarouselConsumer from './CarouselConsumer'; 4 | 5 | export { 6 | CarouselProvider, 7 | CarouselContext, 8 | CarouselConsumer, 9 | }; 10 | 11 | export default CarouselProvider; 12 | -------------------------------------------------------------------------------- /src/Dot/Dot.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { CarouselPropTypes, cn } from '../helpers'; 4 | import s from './Dot.scss'; 5 | 6 | const Dot = class Dot extends React.Component { 7 | static propTypes = { 8 | carouselStore: PropTypes.object.isRequired, 9 | children: CarouselPropTypes.children, 10 | className: PropTypes.string, 11 | currentSlide: PropTypes.number.isRequired, 12 | disabled: PropTypes.bool, 13 | onClick: PropTypes.func, 14 | selected: PropTypes.bool, 15 | slide: PropTypes.number.isRequired, 16 | totalSlides: PropTypes.number.isRequired, 17 | visibleSlides: PropTypes.number.isRequired, 18 | } 19 | 20 | static defaultProps = { 21 | children: null, 22 | className: null, 23 | disabled: null, 24 | onClick: null, 25 | selected: null, 26 | } 27 | 28 | constructor(props) { 29 | super(props); 30 | this.handleOnClick = this.handleOnClick.bind(this); 31 | } 32 | 33 | handleOnClick(ev) { 34 | const { 35 | carouselStore, onClick, slide, totalSlides, visibleSlides, 36 | } = this.props; 37 | const newSlide = slide >= totalSlides - visibleSlides ? totalSlides - visibleSlides : slide; 38 | 39 | carouselStore.setStoreState({ 40 | currentSlide: newSlide, 41 | isPlaying: false, 42 | }, onClick !== null && onClick.call(this, ev)); 43 | } 44 | 45 | render() { 46 | const { 47 | carouselStore, children, className, currentSlide, disabled, onClick, selected, slide, 48 | totalSlides, visibleSlides, ...props 49 | } = this.props; 50 | const defaultSelected = slide >= currentSlide && slide < (currentSlide + visibleSlides); 51 | const newSelected = typeof selected === 'boolean' ? selected : defaultSelected; 52 | const defaultDisabled = defaultSelected === true; 53 | const newDisabled = typeof disabled === 'boolean' ? disabled : defaultDisabled; 54 | 55 | const newClassName = cn([ 56 | s.dot, 57 | newSelected && s.dotSelected, 58 | 'carousel__dot', 59 | `carousel__dot--${slide}`, 60 | newSelected && 'carousel__dot--selected', 61 | className, 62 | ]); 63 | 64 | return ( 65 | 75 | ); 76 | } 77 | }; 78 | 79 | export default Dot; 80 | -------------------------------------------------------------------------------- /src/Dot/Dot.scss: -------------------------------------------------------------------------------- 1 | .dot { 2 | cursor: pointer; 3 | } 4 | -------------------------------------------------------------------------------- /src/Dot/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/Dot/__tests__/Dot.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount, configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import clone from 'clone'; 5 | import components from '../../helpers/component-config'; 6 | import Dot from '../Dot'; 7 | import Store from '../../Store/Store'; 8 | 9 | configure({ adapter: new Adapter() }); 10 | 11 | 12 | let props; 13 | 14 | describe('', () => { 15 | beforeEach(() => { 16 | props = clone(components.Dot.props); 17 | }); 18 | it('should render', () => { 19 | const wrapper = shallow(); 20 | expect(wrapper.exists()).toBe(true); 21 | }); 22 | it('should add the dotSelected class when selected', () => { 23 | const newProps = Object.assign({}, props, { selected: true }); 24 | const wrapper = shallow(); 25 | expect(wrapper.hasClass('dotSelected')).toBe(true); 26 | }); 27 | it('should add the carousel__dot--selected class when selected', () => { 28 | const newProps = Object.assign({}, props, { selected: true }); 29 | const wrapper = shallow(); 30 | expect(wrapper.hasClass('carousel__dot--selected')).toBe(true); 31 | }); 32 | it('should call any supplied onClick as a callback', () => { 33 | const onClick = jest.fn(); 34 | const newProps = Object.assign({}, props, { onClick }); 35 | const wrapper = mount(); 36 | expect(onClick.mock.calls.length).toBe(0); 37 | wrapper.find('button').simulate('click'); 38 | expect(onClick.mock.calls.length).toBe(1); 39 | }); 40 | it('should update carouselStore.state.currentSlide with the value of slide', () => { 41 | const onClick = jest.fn(); 42 | const carouselStore = new Store(Object.assign({}, props, { currentSlide: 0 })); 43 | const newProps = Object.assign({}, props, { onClick, carouselStore }); 44 | const wrapper = mount(); 45 | expect(carouselStore.state.currentSlide).toBe(0); 46 | wrapper.find('button').simulate('click'); 47 | expect(carouselStore.state.currentSlide).toEqual(props.slide); 48 | }); 49 | it('should keep the last slide pegged to the right of the viewport if visibleSlides > 1', () => { 50 | const wrapper = mount(); 51 | wrapper.find('button').simulate('click'); 52 | expect(props.carouselStore.getStoreState().currentSlide).toBe(8); 53 | }); 54 | it('should not override disabled if disabled prop is set to false manually', () => { 55 | const wrapper = mount(); 56 | expect(wrapper.find('button').prop('disabled')).toBe(false); 57 | }); 58 | it('should not override disabled if disabled prop is set to true manually', () => { 59 | const wrapper = mount(); 60 | expect(wrapper.find('button').prop('disabled')).toBe(true); 61 | }); 62 | it('should pause autoplay when clicked', () => { 63 | const wrapper = mount(); 64 | wrapper.find('button').simulate('click'); 65 | expect(props.carouselStore.getStoreState().isPlaying).toBe(false); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/Dot/index.js: -------------------------------------------------------------------------------- 1 | import Dot from './Dot'; 2 | import WithStore from '../Store/WithStore'; 3 | 4 | export default WithStore(Dot, state => ({ 5 | currentSlide: state.currentSlide, 6 | totalSlides: state.totalSlides, 7 | visibleSlides: state.visibleSlides, 8 | })); 9 | -------------------------------------------------------------------------------- /src/DotGroup/DotGroup.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Dot from '../Dot'; 4 | import { CarouselPropTypes, cn } from '../helpers'; 5 | import s from './DotGroup.scss'; 6 | 7 | const DotGroup = class DotGroup extends React.Component { 8 | static propTypes = { 9 | children: CarouselPropTypes.children, 10 | className: PropTypes.string, 11 | currentSlide: PropTypes.number.isRequired, 12 | carouselStore: PropTypes.object.isRequired, 13 | totalSlides: PropTypes.number.isRequired, 14 | visibleSlides: PropTypes.number.isRequired, 15 | dotNumbers: PropTypes.bool, 16 | disableActiveDots: PropTypes.bool, 17 | showAsSelectedForCurrentSlideOnly: PropTypes.bool, 18 | renderDots: PropTypes.func, 19 | } 20 | 21 | static defaultProps = { 22 | children: null, 23 | className: null, 24 | dotNumbers: false, 25 | disableActiveDots: true, 26 | showAsSelectedForCurrentSlideOnly: false, 27 | renderDots: null, 28 | } 29 | 30 | renderDots() { 31 | const { 32 | currentSlide, 33 | totalSlides, 34 | visibleSlides, 35 | disableActiveDots, 36 | showAsSelectedForCurrentSlideOnly, 37 | renderDots, 38 | } = this.props; 39 | 40 | if (renderDots) { 41 | const { renderDots: _, ...renderProps } = this.props; 42 | return renderDots(renderProps); 43 | } 44 | 45 | const dots = []; 46 | for (let i = 0; i < totalSlides; i += 1) { 47 | const multipleSelected = i >= currentSlide && i < (currentSlide + visibleSlides); 48 | const singleSelected = i === currentSlide; 49 | const selected = showAsSelectedForCurrentSlideOnly ? singleSelected : multipleSelected; 50 | const slide = i >= totalSlides - visibleSlides ? totalSlides - visibleSlides : i; 51 | dots.push( 52 | 58 | {this.props.dotNumbers && i + 1} 59 | , 60 | ); 61 | } 62 | return dots; 63 | } 64 | 65 | render() { 66 | const { 67 | carouselStore, 68 | children, 69 | className, 70 | currentSlide, 71 | dotNumbers, 72 | totalSlides, 73 | visibleSlides, 74 | disableActiveDots, 75 | showAsSelectedForCurrentSlideOnly, 76 | renderDots, 77 | ...props 78 | } = this.props; 79 | 80 | const newClassName = cn([ 81 | s.DotGroup, 82 | 'carousel__dot-group', 83 | className, 84 | ]); 85 | 86 | return ( 87 |
88 | {this.renderDots()} 89 | {children} 90 |
91 | ); 92 | } 93 | }; 94 | 95 | export default DotGroup; 96 | -------------------------------------------------------------------------------- /src/DotGroup/DotGroup.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express-labs/pure-react-carousel/1a10f5a5c29dd2731e2ed7ad8469f1b816289bb0/src/DotGroup/DotGroup.scss -------------------------------------------------------------------------------- /src/DotGroup/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/DotGroup/__tests__/DotGroup.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import clone from 'clone'; 5 | import components from '../../helpers/component-config'; 6 | import DotGroup from '../DotGroup'; 7 | 8 | configure({ adapter: new Adapter() }); 9 | 10 | let props; 11 | 12 | describe('', () => { 13 | beforeEach(() => { 14 | props = clone(components.DotGroup.props); 15 | }); 16 | it('should render', () => { 17 | const wrapper = shallow(); 18 | expect(wrapper.exists()).toBe(true); 19 | }); 20 | it('should render any children', () => { 21 | const wrapper = shallow(

Hello There

); 22 | expect(wrapper.find('h1').text()).toBe('Hello There'); 23 | }); 24 | it('should render dots with numbers if dotNumbers prop is true', () => { 25 | const wrapper = shallow(); 26 | expect(wrapper.find('span').at(0).text()).toEqual('1'); 27 | expect(wrapper.find('span').at(1).text()).toEqual('2'); 28 | expect(wrapper.find('span').at(2).text()).toEqual('3'); 29 | }); 30 | it('should NOT render dots with numbers if dotNumbers prop is not set', () => { 31 | const wrapper = shallow(); 32 | expect(wrapper.find('span').at(0).text()).toEqual(''); 33 | expect(wrapper.find('span').at(1).text()).toEqual(''); 34 | expect(wrapper.find('span').at(2).text()).toEqual(''); 35 | }); 36 | it('should render enabled active dots if disableActiveDots prop is false', () => { 37 | const wrapper = shallow(); 38 | expect(wrapper.children().at(0).prop('disabled')).toEqual(false); 39 | expect(wrapper.children().at(1).prop('disabled')).toEqual(false); 40 | expect(wrapper.children().at(2).prop('disabled')).toEqual(false); 41 | }); 42 | it('should render DISABLED dots if disableActiveDots prop is not set', () => { 43 | const wrapper = shallow(); 44 | expect(wrapper.children().at(0).prop('disabled')).toEqual(false); 45 | expect(wrapper.children().at(1).prop('disabled')).toEqual(true); 46 | expect(wrapper.children().at(2).prop('disabled')).toEqual(true); 47 | }); 48 | it('should render only current slide dot as selected if showAsSelectedForCurrentSlideOnly prop is true', () => { 49 | const wrapper = shallow(); 50 | expect(wrapper.children().at(0).prop('selected')).toEqual(false); 51 | expect(wrapper.children().at(1).prop('selected')).toEqual(true); 52 | expect(wrapper.children().at(2).prop('selected')).toEqual(false); 53 | }); 54 | it('should render all visible slides dot as selected if showAsSelectedForCurrentSlideOnly prop is not set', () => { 55 | const wrapper = shallow(); 56 | expect(wrapper.children().at(0).prop('selected')).toEqual(false); 57 | expect(wrapper.children().at(1).prop('selected')).toEqual(true); 58 | expect(wrapper.children().at(2).prop('selected')).toEqual(true); 59 | }); 60 | it('should render dots differently if renderDots is provided', () => { 61 | const renderDots = ({ totalSlides }) => { 62 | const dots = []; 63 | 64 | for (let i = 0; i < totalSlides; i += 1) { 65 | dots.push(); 66 | } 67 | return dots; 68 | }; 69 | 70 | const wrapper = shallow(); 71 | expect(wrapper.find('img').at(0).text()).toEqual(''); 72 | expect(wrapper.find('img').at(1).text()).toEqual(''); 73 | expect(wrapper.find('img').at(2).text()).toEqual(''); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /src/DotGroup/index.js: -------------------------------------------------------------------------------- 1 | import DotGroup from './DotGroup'; 2 | import WithStore from '../Store/WithStore'; 3 | 4 | export default WithStore(DotGroup, state => ({ 5 | currentSlide: state.currentSlide, 6 | totalSlides: state.totalSlides, 7 | visibleSlides: state.visibleSlides, 8 | })); 9 | -------------------------------------------------------------------------------- /src/Image/Image.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | CarouselPropTypes, cn, LOADING, SUCCESS, ERROR, 5 | } from '../helpers'; 6 | import s from './Image.scss'; 7 | 8 | class Image extends React.Component { 9 | static propTypes = { 10 | alt: PropTypes.string, 11 | carouselStore: PropTypes.object.isRequired, 12 | children: CarouselPropTypes.children, 13 | className: PropTypes.string, 14 | hasMasterSpinner: PropTypes.bool.isRequired, 15 | isBgImage: CarouselPropTypes.isBgImage, 16 | onError: PropTypes.func, 17 | onLoad: PropTypes.func, 18 | renderError: PropTypes.func, 19 | renderLoading: PropTypes.func, 20 | src: PropTypes.string.isRequired, 21 | style: PropTypes.object, 22 | tag: PropTypes.string, 23 | }; 24 | 25 | static defaultProps = { 26 | alt: '', 27 | children: null, 28 | className: null, 29 | isBgImage: false, 30 | onError: null, 31 | onLoad: null, 32 | renderError: null, 33 | renderLoading: null, 34 | style: null, 35 | tag: 'img', 36 | }; 37 | 38 | static subscribeMasterSpinner(props) { 39 | if (props.hasMasterSpinner) { 40 | props.carouselStore.subscribeMasterSpinner(props.src); 41 | } 42 | } 43 | 44 | static unsubscribeMasterSpinner(props) { 45 | if (props.hasMasterSpinner) { 46 | props.carouselStore.unsubscribeMasterSpinner(props.src); 47 | } 48 | } 49 | 50 | constructor(props) { 51 | super(props); 52 | this.state = { imageStatus: LOADING }; 53 | this.handleImageLoad = this.handleImageLoad.bind(this); 54 | this.handleImageError = this.handleImageError.bind(this); 55 | this.image = null; 56 | } 57 | 58 | componentDidMount() { 59 | Image.subscribeMasterSpinner(this.props); 60 | this.initImage(); 61 | } 62 | 63 | componentDidUpdate(prevProps) { 64 | if (prevProps.src !== this.props.src) { 65 | Image.unsubscribeMasterSpinner(prevProps); 66 | Image.subscribeMasterSpinner(this.props); 67 | this.initImage(); 68 | } 69 | } 70 | 71 | componentWillUnmount() { 72 | Image.unsubscribeMasterSpinner(this.props); 73 | this.image.removeEventListener('load', this.handleImageLoad); 74 | this.image.removeEventListener('error', this.handleImageError); 75 | this.image = null; 76 | } 77 | 78 | initImage() { 79 | this.setState({ imageStatus: LOADING }); 80 | this.image = document.createElement('img'); 81 | 82 | // set event listeners first 83 | this.image.addEventListener('load', this.handleImageLoad, false); 84 | this.image.addEventListener('error', this.handleImageError, false); 85 | 86 | // setting img.src initiates the image load. 87 | this.image.src = this.props.src; 88 | 89 | // Was the image cached? force the image through the load process again. 90 | // NOTE: figure out a way to test this. It might involve breaking initImage 91 | // up into some other methods. 92 | /* istanbul ignore if */ 93 | if (this.image.readyState || this.image.complete) { 94 | const { src } = this.image; 95 | this.image.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; 96 | this.image.src = src; 97 | } 98 | } 99 | 100 | handleImageLoad(ev) { 101 | this.setState({ imageStatus: SUCCESS }); 102 | if (this.props.hasMasterSpinner) this.props.carouselStore.masterSpinnerSuccess(this.props.src); 103 | if (this.props.onLoad) this.props.onLoad(ev); 104 | } 105 | 106 | handleImageError(ev) { 107 | this.setState({ imageStatus: ERROR }); 108 | if (this.props.hasMasterSpinner) this.props.carouselStore.masterSpinnerError(this.props.src); 109 | if (this.props.onError) this.props.onError(ev); 110 | } 111 | 112 | tempTag() { 113 | return this.props.tag === 'img' ? 'div' : this.props.tag; 114 | } 115 | 116 | customRender(propName) { 117 | if (typeof this.props[propName] === 'function') return this.props[propName](); 118 | return this.props.children; 119 | } 120 | 121 | renderLoading(filteredProps) { 122 | const Tag = this.tempTag(); 123 | 124 | const newClassName = cn([ 125 | s.image, 126 | s.imageLoading, 127 | 'carousel__image', 128 | this.props.isBgImage && 'carousel__image--with-background', 129 | 'carousel__image--loading', 130 | this.props.className, 131 | ]); 132 | 133 | return ( 134 | 135 | {this.customRender('renderLoading')} 136 | 137 | ); 138 | } 139 | 140 | renderError(filteredProps) { 141 | const Tag = this.tempTag(); 142 | 143 | const newClassName = cn([ 144 | s.image, 145 | s.imageError, 146 | 'carousel__image', 147 | this.props.isBgImage && 'carousel__image--with-background', 148 | 'carousel__image--error', 149 | this.props.className, 150 | ]); 151 | 152 | return ( 153 | 154 | {this.customRender('renderError')} 155 | 156 | ); 157 | } 158 | 159 | renderSuccess(filteredProps) { 160 | const { style, tag: Tag } = this.props; 161 | const newClassName = cn([ 162 | s.image, 163 | 'carousel__image', 164 | this.props.isBgImage && 'carousel__image--with-background', 165 | 'carousel__image--success', 166 | this.props.className, 167 | ]); 168 | 169 | let newStyle = Object.assign({}, style); 170 | 171 | let newFilteredProps = filteredProps; 172 | 173 | if (Tag !== 'img') { 174 | const { src, alt, ...tempFilteredProps } = filteredProps; 175 | newFilteredProps = tempFilteredProps; 176 | newStyle = Object.assign({}, style, { 177 | backgroundImage: `url("${src}")`, 178 | backgroundSize: 'cover', 179 | }); 180 | } 181 | 182 | return ( 183 | 184 | {this.props.children} 185 | 186 | ); 187 | } 188 | 189 | render() { 190 | const { 191 | carouselStore, 192 | children, 193 | className, 194 | hasMasterSpinner, 195 | isBgImage, 196 | onError, 197 | onLoad, 198 | renderError, 199 | renderLoading, 200 | style, 201 | tag, 202 | ...filteredProps 203 | } = this.props; 204 | 205 | switch (this.state.imageStatus) { 206 | case LOADING: 207 | return this.renderLoading(filteredProps); 208 | case SUCCESS: 209 | return this.renderSuccess(filteredProps); 210 | case ERROR: 211 | return this.renderError(filteredProps); 212 | default: 213 | throw new Error('unknown value for this.state.imageStatus'); 214 | } 215 | } 216 | } 217 | 218 | export default Image; 219 | -------------------------------------------------------------------------------- /src/Image/Image.scss: -------------------------------------------------------------------------------- 1 | .image { 2 | display: block; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/Image/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/Image/index.js: -------------------------------------------------------------------------------- 1 | import Image from './Image'; 2 | import WithStore from '../Store/WithStore'; 3 | 4 | export default WithStore(Image, state => ({ 5 | hasMasterSpinner: state.hasMasterSpinner, 6 | orientation: state.orientation, 7 | })); 8 | -------------------------------------------------------------------------------- /src/ImageWithZoom/ImageWithZoom.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | overflow: hidden; 4 | height: 100%; 5 | width: 100%; 6 | } 7 | 8 | .overlay { 9 | position: absolute; 10 | top: 0; 11 | left: 0; 12 | bottom: 0; 13 | right: 0; 14 | opacity: 0; 15 | cursor: zoom-in; 16 | transition: opacity 300ms, transform 300ms; 17 | } 18 | 19 | .hover { 20 | opacity: 1; 21 | } 22 | 23 | .zoom { 24 | opacity: 1; 25 | } 26 | 27 | .loading { 28 | opacity: 1; 29 | } 30 | 31 | .imageLoadingSpinnerContainer { 32 | position: absolute; 33 | top: 0; 34 | right: 0; 35 | bottom: 0; 36 | left: 0; 37 | background-color: #f4f4f4; 38 | } 39 | -------------------------------------------------------------------------------- /src/ImageWithZoom/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/ImageWithZoom/index.js: -------------------------------------------------------------------------------- 1 | import ImageWithZoom from './ImageWithZoom'; 2 | 3 | import WithStore from '../Store/WithStore'; 4 | 5 | export default WithStore(ImageWithZoom, () => ({})); 6 | -------------------------------------------------------------------------------- /src/Slide/Slide.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { CarouselPropTypes, cn, pct } from '../helpers'; 4 | import s from './Slide.scss'; 5 | 6 | const Slide = class Slide extends React.PureComponent { 7 | static propTypes = { 8 | ariaLabel: PropTypes.string, 9 | carouselStore: PropTypes.object, 10 | children: CarouselPropTypes.children, 11 | className: PropTypes.string, 12 | classNameHidden: PropTypes.string, 13 | classNameVisible: PropTypes.string, 14 | currentSlide: PropTypes.number.isRequired, 15 | index: PropTypes.number.isRequired, 16 | innerClassName: PropTypes.string, 17 | innerTag: PropTypes.string, 18 | naturalSlideHeight: PropTypes.number.isRequired, 19 | naturalSlideWidth: PropTypes.number.isRequired, 20 | onBlur: PropTypes.func, 21 | onFocus: PropTypes.func, 22 | orientation: CarouselPropTypes.orientation.isRequired, 23 | slideSize: PropTypes.number.isRequired, 24 | role: PropTypes.string, 25 | style: PropTypes.object, 26 | tag: PropTypes.string, 27 | totalSlides: PropTypes.number.isRequired, 28 | visibleSlides: PropTypes.number.isRequired, 29 | isIntrinsicHeight: PropTypes.bool, 30 | } 31 | 32 | static defaultProps = { 33 | ariaLabel: 'slide', 34 | carouselStore: null, 35 | children: null, 36 | className: null, 37 | classNameHidden: null, 38 | classNameVisible: null, 39 | innerClassName: null, 40 | innerTag: 'div', 41 | onBlur: null, 42 | onFocus: null, 43 | role: 'option', 44 | style: {}, 45 | tag: 'div', 46 | isIntrinsicHeight: false, 47 | } 48 | 49 | constructor(props) { 50 | super(props); 51 | this.handleOnFocus = this.handleOnFocus.bind(this); 52 | this.handleOnBlur = this.handleOnBlur.bind(this); 53 | this.state = { 54 | focused: false, 55 | }; 56 | } 57 | 58 | isVisible() { 59 | const { currentSlide, index, visibleSlides } = this.props; 60 | return index >= currentSlide && index < currentSlide + visibleSlides; 61 | } 62 | 63 | handleOnFocus(ev) { 64 | const { onFocus } = this.props; 65 | 66 | this.setState({ 67 | focused: true, 68 | }, () => { 69 | if (onFocus !== null) { onFocus.call(this, ev); } 70 | }); 71 | } 72 | 73 | handleOnBlur(ev) { 74 | const { onBlur } = this.props; 75 | 76 | this.setState({ 77 | focused: false, 78 | }, () => { 79 | if (onBlur !== null) { onBlur.call(this, ev); } 80 | }); 81 | } 82 | 83 | renderFocusRing() { 84 | if (this.state.focused) return
; 85 | return null; 86 | } 87 | 88 | render() { 89 | const { 90 | ariaLabel, 91 | carouselStore, 92 | children, 93 | className, 94 | classNameHidden, 95 | classNameVisible, 96 | currentSlide, 97 | index, 98 | innerClassName, 99 | innerTag: InnerTag, 100 | naturalSlideHeight, 101 | naturalSlideWidth, 102 | onBlur, 103 | onFocus, 104 | orientation, 105 | slideSize, 106 | style, 107 | tag: Tag, 108 | totalSlides, 109 | visibleSlides, 110 | isIntrinsicHeight, 111 | ...props 112 | } = this.props; 113 | 114 | const tempStyle = {}; 115 | 116 | if (orientation === 'horizontal') { 117 | tempStyle.width = pct(slideSize); 118 | tempStyle.paddingBottom = pct((naturalSlideHeight * 100) / (naturalSlideWidth * totalSlides)); 119 | } else { 120 | tempStyle.width = pct(100); 121 | tempStyle.paddingBottom = pct((naturalSlideHeight * 100) / naturalSlideWidth); 122 | } 123 | 124 | const innerStyle = {}; 125 | if (isIntrinsicHeight) { 126 | if (orientation === 'horizontal') { 127 | tempStyle.height = 'unset'; 128 | } else { 129 | tempStyle.width = 'unset'; 130 | } 131 | tempStyle.paddingBottom = 'unset'; 132 | innerStyle.position = 'unset'; 133 | } 134 | 135 | const newStyle = Object.assign({}, tempStyle, style); 136 | 137 | const isVisible = this.isVisible(); 138 | 139 | const newClassName = cn([ 140 | s.slide, 141 | orientation === 'horizontal' && s.slideHorizontal, 142 | 'carousel__slide', 143 | this.state.focused && 'carousel__slide--focused', 144 | isVisible && classNameVisible, 145 | isVisible && 'carousel__slide--visible', 146 | !isVisible && classNameHidden, 147 | !isVisible && 'carousel__slide--hidden', 148 | className, 149 | ]); 150 | 151 | const newInnerClassName = cn([ 152 | s.slideInner, 153 | 'carousel__inner-slide', 154 | innerClassName, 155 | ]); 156 | 157 | return ( 158 | { this.tagRef = el; }} 160 | aria-selected={this.isVisible()} 161 | aria-label={ariaLabel} 162 | role={this.props.role} 163 | onFocus={this.handleOnFocus} 164 | onBlur={this.handleOnBlur} 165 | className={newClassName} 166 | style={newStyle} 167 | {...props} 168 | > 169 | { this.innerTagRef = el; }} 171 | className={newInnerClassName} 172 | style={innerStyle} 173 | > 174 | {this.props.children} 175 | {this.renderFocusRing()} 176 | 177 | 178 | ); 179 | } 180 | }; 181 | 182 | export default Slide; 183 | -------------------------------------------------------------------------------- /src/Slide/Slide.scss: -------------------------------------------------------------------------------- 1 | .slide { 2 | position: relative; 3 | display: block; 4 | box-sizing: border-box; 5 | height: 0; 6 | margin: 0; 7 | list-style-type: none; 8 | 9 | &:focus { 10 | outline: none !important; 11 | } 12 | 13 | &Horizontal { 14 | float: left; 15 | 16 | [dir='rtl'] & { 17 | direction: rtl; 18 | transform: scaleX(-1); 19 | } 20 | } 21 | 22 | &Inner { 23 | position: absolute; 24 | top: 0; 25 | left: 0; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | } 30 | 31 | .focusRing { 32 | position: absolute; 33 | top: 5px; 34 | right: 5px; 35 | bottom: 5px; 36 | left: 5px; 37 | pointer-events: none; 38 | outline-width: 5px; 39 | outline-style: solid; 40 | outline-color: Highlight; 41 | 42 | @media (-webkit-min-device-pixel-ratio:0) { 43 | outline-style: auto; 44 | outline-color: -webkit-focus-ring-color; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Slide/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/Slide/__tests__/Slide.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import clone from 'clone'; 5 | import components from '../../helpers/component-config'; 6 | import Slide from '../Slide'; 7 | 8 | configure({ adapter: new Adapter() }); 9 | 10 | 11 | describe('', () => { 12 | let props; 13 | 14 | beforeEach(() => { 15 | props = clone(components.Slide.props); 16 | }); 17 | it('should render', () => { 18 | const wrapper = shallow(); 19 | expect(wrapper.exists()).toBe(true); 20 | }); 21 | it('should show an aria focus ring when focused', () => { 22 | const wrapper = shallow(); 23 | expect(wrapper.state('focused')).toBe(false); 24 | expect(wrapper.find('.focusRing').exists()).toBe(false); 25 | wrapper.find('.slide').simulate('focus'); 26 | wrapper.update(); 27 | expect(wrapper.state('focused')).toBe(true); 28 | expect(wrapper.find('.focusRing').exists()).toBe(true); 29 | }); 30 | it('should call any supplied onFocus when focused and pass it event data', () => { 31 | const onFocus = jest.fn(); 32 | const wrapper = shallow(); 33 | wrapper.find('.slide').simulate('focus', { data: 1 }); 34 | wrapper.update(); 35 | expect(onFocus).toHaveBeenCalledTimes(1); 36 | expect(onFocus.mock.calls[0][0]).toEqual({ data: 1 }); 37 | }); 38 | it('should remove the aria focus ring when blur after focus', () => { 39 | const wrapper = shallow(); 40 | expect(wrapper.state('focused')).toBe(false); 41 | expect(wrapper.find('.focusRing').exists()).toBe(false); 42 | wrapper.find('.slide').simulate('focus'); 43 | wrapper.update(); 44 | wrapper.find('.slide').simulate('blur'); 45 | wrapper.update(); 46 | expect(wrapper.state('focused')).toBe(false); 47 | expect(wrapper.find('.focusRing').exists()).toBe(false); 48 | }); 49 | it('should call any supplied onBlur when blurred and pass it event data', () => { 50 | const onBlur = jest.fn(); 51 | const wrapper = shallow(); 52 | wrapper.find('.slide').simulate('blur', { data: 1 }); 53 | wrapper.update(); 54 | expect(onBlur).toHaveBeenCalledTimes(1); 55 | expect(onBlur.mock.calls[0][0]).toEqual({ data: 1 }); 56 | }); 57 | it('should set the width to 100% when orientation is "vertical"', () => { 58 | const wrapper = shallow(); 59 | expect(wrapper.find('.slide').prop('style').width).toBe('100%'); 60 | }); 61 | 62 | it('should apply any supplied classes to hidden slides', () => { 63 | const wrapper = shallow(( 64 | 76 | )); 77 | expect(wrapper.find('.slide').hasClass('i-be-hidden')).toBe(true); 78 | expect(wrapper.find('.slide').hasClass('carousel__slide--hidden')).toBe(true); 79 | }); 80 | it('should apply any supplied classes to visible slides', () => { 81 | const wrapper = shallow(( 82 | 94 | )); 95 | expect(wrapper.find('.slide').hasClass('i-be-visible')).toBe(true); 96 | expect(wrapper.find('.slide').hasClass('carousel__slide--visible')).toBe(true); 97 | }); 98 | 99 | it('should correctly set styles, if isIntrinsicHeight is set', () => { 100 | // this is for testing only. 101 | 102 | const wrapper = shallow(); 103 | const slideStyle = wrapper.find('.slide').prop('style'); 104 | expect(slideStyle.paddingBottom).toBe('unset'); 105 | expect(slideStyle.height).toBe('unset'); 106 | 107 | const innerSlideStyle = wrapper.find('.carousel__inner-slide').prop('style'); 108 | expect(innerSlideStyle.position).toBe('unset'); 109 | }); 110 | it('should correctly set styles, in vertical mode if isIntrinsicHeight is set', () => { 111 | // this is for testing only. 112 | const wrapper = shallow(); 113 | const slideStyle = wrapper.find('.slide').prop('style'); 114 | expect(slideStyle.paddingBottom).toBe('unset'); 115 | expect(slideStyle.width).toBe('unset'); 116 | 117 | const innerSlideStyle = wrapper.find('.carousel__inner-slide').prop('style'); 118 | expect(innerSlideStyle.position).toBe('unset'); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /src/Slide/index.js: -------------------------------------------------------------------------------- 1 | import Slide from './Slide'; 2 | import WithStore from '../Store/WithStore'; 3 | 4 | export default WithStore(Slide, state => ({ 5 | currentSlide: state.currentSlide, 6 | naturalSlideHeight: state.naturalSlideHeight, 7 | naturalSlideWidth: state.naturalSlideWidth, 8 | orientation: state.orientation, 9 | slideSize: state.slideSize, 10 | totalSlides: state.totalSlides, 11 | visibleSlides: state.visibleSlides, 12 | isIntrinsicHeight: state.isIntrinsicHeight, 13 | })); 14 | -------------------------------------------------------------------------------- /src/Slider/GetScrollParent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * GetScrollParent 3 | * 4 | * Finds the first anscestor element (aka parent) with scroll. 5 | * Code is Based off of a snippet created by Ola Holmström 6 | * Original Source: https://github.com/olahol/scrollparent.js/blob/master/scrollparent.js#L13 7 | * 8 | * Usage: 9 | * const parent = new GetScrollParent(element); 10 | */ 11 | export default class GetScrollParent { 12 | static style(_node, prop) { 13 | return getComputedStyle(_node, null).getPropertyValue(prop); 14 | } 15 | 16 | static overflow(_node) { 17 | return ( 18 | GetScrollParent.style(_node, 'overflow') 19 | + GetScrollParent.style(_node, 'overflow-y') 20 | + GetScrollParent.style(_node, 'overflow-x') 21 | ); 22 | } 23 | 24 | static scroll(_node) { 25 | return /(auto|scroll)/.test(GetScrollParent.overflow(_node)); 26 | } 27 | 28 | static isNodeValid(_node) { 29 | return _node instanceof HTMLElement || _node instanceof SVGElement; 30 | } 31 | 32 | parents(_node, ps) { 33 | if (_node.parentNode === null) return ps; 34 | return this.parents(_node.parentNode, ps.concat([_node])); 35 | } 36 | 37 | 38 | scrollParent(_node) { 39 | const ps = this.parents(_node.parentNode, []); 40 | for (let i = 0; i < ps.length; i += 1) { 41 | if (GetScrollParent.scroll(ps[i])) { 42 | return ps[i]; 43 | } 44 | } 45 | 46 | return document.scrollingElement || document.documentElement; 47 | } 48 | 49 | getScrollParent(_node) { 50 | if (!GetScrollParent.isNodeValid(_node)) return null; 51 | return this.scrollParent(_node); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Slider/Slider.scss: -------------------------------------------------------------------------------- 1 | .horizontalSlider { 2 | position: relative; 3 | overflow: hidden; 4 | &:not(.touchDisabled) { 5 | touch-action: pan-y pinch-zoom; 6 | } 7 | 8 | [dir='rtl'] & { 9 | direction: ltr; 10 | transform: scaleX(-1); 11 | } 12 | 13 | &Tray { 14 | overflow: hidden; 15 | width: 100%; 16 | } 17 | } 18 | 19 | .verticalSlider { 20 | position: relative; 21 | overflow: hidden; 22 | 23 | &Tray { 24 | overflow: hidden; 25 | } 26 | } 27 | 28 | .verticalTray { 29 | float: left; 30 | } 31 | 32 | .verticalSlideTrayWrap { 33 | overflow: hidden; 34 | } 35 | 36 | .sliderTray { 37 | display: block; 38 | list-style: none; 39 | padding: 0; 40 | margin: 0; 41 | } 42 | 43 | .sliderAnimation { 44 | transition: transform 500ms; 45 | transition-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1.000); /* easeInOutCubic */ 46 | will-change: transform; 47 | } 48 | 49 | .masterSpinnerContainer { 50 | position: absolute; 51 | top: 0; 52 | right: 0; 53 | bottom: 0; 54 | left: 0; 55 | background-color: #f4f4f4; 56 | } 57 | -------------------------------------------------------------------------------- /src/Slider/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/Slider/index.js: -------------------------------------------------------------------------------- 1 | import Slider from './Slider'; 2 | import WithStore from '../Store/WithStore'; 3 | 4 | export default WithStore(Slider, state => ({ 5 | currentSlide: state.currentSlide, 6 | disableAnimation: state.disableAnimation, 7 | privateUnDisableAnimation: state.privateUnDisableAnimation, 8 | disableKeyboard: state.disableKeyboard, 9 | dragEnabled: state.dragEnabled, 10 | hasMasterSpinner: state.hasMasterSpinner, 11 | infinite: state.infinite, 12 | interval: state.interval, 13 | isPageScrollLocked: state.isPageScrollLocked, 14 | isPlaying: state.isPlaying, 15 | lockOnWindowScroll: state.lockOnWindowScroll, 16 | preventingVerticalScroll: state.preventingVerticalScroll, 17 | masterSpinnerFinished: state.masterSpinnerFinished, 18 | naturalSlideHeight: state.naturalSlideHeight, 19 | naturalSlideWidth: state.naturalSlideWidth, 20 | orientation: state.orientation, 21 | playDirection: state.playDirection, 22 | slideSize: state.slideSize, 23 | slideTraySize: state.slideTraySize, 24 | step: state.step, 25 | dragStep: state.dragStep, 26 | totalSlides: state.totalSlides, 27 | touchEnabled: state.touchEnabled, 28 | visibleSlides: state.visibleSlides, 29 | isIntrinsicHeight: state.isIntrinsicHeight, 30 | })); 31 | -------------------------------------------------------------------------------- /src/Spinner/Spinner.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { cn } from '../helpers'; 4 | import s from './Spinner.scss'; 5 | 6 | const Spinner = class Spinner extends React.PureComponent { 7 | static propTypes = { 8 | className: PropTypes.string, 9 | } 10 | 11 | static defaultProps = { 12 | className: null, 13 | } 14 | 15 | render() { 16 | const { className, ...filteredProps } = this.props; 17 | const newClassName = cn([s.spinner, 'carousel__spinner', className]); 18 | return ( 19 |
20 | ); 21 | } 22 | }; 23 | 24 | export default Spinner; 25 | -------------------------------------------------------------------------------- /src/Spinner/Spinner.scss: -------------------------------------------------------------------------------- 1 | .spinner { 2 | position: absolute; 3 | top: calc(50% - 15px); 4 | left: calc(50% - 15px); 5 | width: 30px; 6 | height: 30px; 7 | animation-name: spin; 8 | animation-duration: 1s; 9 | animation-timing-function: linear; 10 | animation-iteration-count: infinite; 11 | border-width: 4px; 12 | border-style: solid; 13 | border-top-color: black; 14 | border-right-color: darkgrey; 15 | border-bottom-color: darkgrey; 16 | border-left-color: darkgrey; 17 | border-radius: 30px; 18 | } 19 | 20 | @keyframes spin { 21 | from { 22 | transform: rotate(0deg); 23 | } 24 | 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Spinner/index.js: -------------------------------------------------------------------------------- 1 | import Spinner from './Spinner'; 2 | 3 | export default Spinner; 4 | -------------------------------------------------------------------------------- /src/Store/Store.jsx: -------------------------------------------------------------------------------- 1 | import deepMerge from 'deepmerge'; 2 | import deepFreeze from 'deep-freeze'; 3 | 4 | const DEFAULT_STATE = { 5 | masterSpinnerFinished: false, 6 | }; 7 | 8 | const Store = class Store { 9 | constructor(initialState) { 10 | this.state = deepFreeze(deepMerge(DEFAULT_STATE, initialState)); 11 | this.subscriptions = []; 12 | this.masterSpinnerSubscriptions = {}; 13 | this.setStoreState = this.setStoreState.bind(this); 14 | this.getStoreState = this.getStoreState.bind(this); 15 | this.subscribe = this.subscribe.bind(this); 16 | this.unsubscribe = this.unsubscribe.bind(this); 17 | this.updateSubscribers = this.updateSubscribers.bind(this); 18 | this.subscribeMasterSpinner = this.subscribeMasterSpinner.bind(this); 19 | this.unsubscribeMasterSpinner = this.unsubscribeMasterSpinner.bind(this); 20 | this.unsubscribeAllMasterSpinner = this.unsubscribeAllMasterSpinner.bind(this); 21 | this.masterSpinnerSuccess = this.masterSpinnerSuccess.bind(this); 22 | this.masterSpinnerError = this.masterSpinnerError.bind(this); 23 | } 24 | 25 | setStoreState(newState, cb) { 26 | this.state = deepFreeze(deepMerge(this.state, newState)); 27 | this.updateSubscribers(cb); 28 | } 29 | 30 | getStoreState() { 31 | return deepMerge({}, this.state); 32 | } 33 | 34 | subscribe(func) { 35 | this.subscriptions.push(func); 36 | } 37 | 38 | unsubscribe(func) { 39 | const index = this.subscriptions.indexOf(func); 40 | if (index !== -1) this.subscriptions.splice(index, 1); 41 | } 42 | 43 | updateSubscribers(cb) { 44 | this.subscriptions.forEach(func => func()); 45 | if (typeof cb === 'function') cb(this.getStoreState()); 46 | } 47 | 48 | subscribeMasterSpinner(src) { 49 | const index = Object.keys(this.masterSpinnerSubscriptions).indexOf(src); 50 | if (index === -1) { 51 | this.masterSpinnerSubscriptions[src] = { 52 | success: false, 53 | error: false, 54 | complete: false, 55 | }; 56 | } 57 | } 58 | 59 | unsubscribeMasterSpinner(src) { 60 | const index = Object.keys(this.masterSpinnerSubscriptions).indexOf(src); 61 | if (index === -1) { 62 | return false; 63 | } 64 | this.setMasterSpinnerFinished(); 65 | return delete this.masterSpinnerSubscriptions[src]; 66 | } 67 | 68 | unsubscribeAllMasterSpinner() { 69 | this.masterSpinnerSubscriptions = {}; 70 | this.setMasterSpinnerFinished(); 71 | } 72 | 73 | masterSpinnerSuccess(src) { 74 | this.masterSpinnerSubscriptions[src].success = true; 75 | this.masterSpinnerSubscriptions[src].complete = true; 76 | this.setMasterSpinnerFinished(); 77 | } 78 | 79 | masterSpinnerError(src) { 80 | this.masterSpinnerSubscriptions[src].error = true; 81 | this.masterSpinnerSubscriptions[src].complete = true; 82 | this.setMasterSpinnerFinished(); 83 | } 84 | 85 | setMasterSpinnerFinished() { 86 | this.setStoreState({ 87 | masterSpinnerFinished: this.isMasterSpinnerFinished(), 88 | }); 89 | } 90 | 91 | isMasterSpinnerFinished() { 92 | return Object.keys(this.masterSpinnerSubscriptions).filter( 93 | src => this.masterSpinnerSubscriptions[src].complete !== true, 94 | ).length === 0; 95 | } 96 | }; 97 | 98 | export default Store; 99 | -------------------------------------------------------------------------------- /src/Store/WithStore.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import equal from 'equals'; 3 | import deepMerge from 'deepmerge'; 4 | import { CarouselPropTypes } from '../helpers'; 5 | import { CarouselContext } from '../CarouselProvider'; 6 | 7 | export default function WithStore( 8 | WrappedComponent, 9 | /* istanbul ignore next */ mapStateToProps = () => ({}), 10 | ) { 11 | class Wrapper extends React.Component { 12 | static contextType = CarouselContext 13 | 14 | static propTypes = { 15 | children: CarouselPropTypes.children, 16 | }; 17 | 18 | static defaultProps = { 19 | children: null, 20 | }; 21 | 22 | constructor(props, context) { 23 | super(props, context); 24 | this.state = mapStateToProps({ ...context.state }); 25 | this.updateStateProps = this.updateStateProps.bind(this); 26 | } 27 | 28 | componentDidMount() { 29 | this.context.subscribe(this.updateStateProps); 30 | } 31 | 32 | shouldComponentUpdate(nextProps, nextState) { 33 | const test = !equal(nextState, this.state) || !equal(nextProps, this.props); 34 | return test; 35 | } 36 | 37 | componentWillUnmount() { 38 | this.context.unsubscribe(this.updateStateProps); 39 | } 40 | 41 | updateStateProps() { 42 | this.setState(mapStateToProps({ ...this.context.state })); 43 | } 44 | 45 | render() { 46 | const props = deepMerge(this.state, this.props); 47 | 48 | return ( 49 | { 51 | this.instance = el; 52 | }} // allows access to refs in wrapped components. 53 | {...props} 54 | carouselStore={{ 55 | getStoreState: this.context.getStoreState, 56 | masterSpinnerError: this.context.masterSpinnerError, 57 | masterSpinnerSuccess: this.context.masterSpinnerSuccess, 58 | setStoreState: this.context.setStoreState, 59 | subscribeMasterSpinner: this.context.subscribeMasterSpinner, 60 | unsubscribeAllMasterSpinner: this.context.unsubscribeAllMasterSpinner, 61 | unsubscribeMasterSpinner: this.context.unsubscribeMasterSpinner, 62 | }} 63 | > 64 | {this.props.children} 65 | 66 | ); 67 | } 68 | } 69 | 70 | return Wrapper; 71 | } 72 | -------------------------------------------------------------------------------- /src/Store/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "mrb3k-jest" 4 | } 5 | -------------------------------------------------------------------------------- /src/Store/__tests__/Store.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount, configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import Store from '../Store'; 5 | import CarouselProvider from '../../CarouselProvider'; 6 | import Slide from '../../Slide'; 7 | import components from '../../helpers/component-config'; 8 | 9 | configure({ adapter: new Adapter() }); 10 | 11 | 12 | describe('Store', () => { 13 | let carouselStore; 14 | beforeEach(() => { 15 | carouselStore = new Store({}); 16 | }); 17 | it('subscribe(func) should append a function to the list of suscriptions', () => { 18 | const func = () => null; 19 | expect(carouselStore.subscriptions.length).toBe(0); 20 | carouselStore.subscribe(func); 21 | expect(carouselStore.subscriptions.length).toBe(1); 22 | expect(carouselStore.subscriptions.indexOf(func)).toBe(0); 23 | }); 24 | it('unsubscribe(func) should remove a function from the list of subscription', () => { 25 | const func = () => null; 26 | carouselStore.subscriptions = [ 27 | func, 28 | ]; 29 | carouselStore.unsubscribe(func); 30 | expect(carouselStore.subscriptions.length).toBe(0); 31 | }); 32 | it('unsubscribe(func) should NOT remove any function from the list if func is not on the list', () => { 33 | const func = () => null; 34 | const notFunc = () => null; 35 | carouselStore.subscriptions = [ 36 | func, 37 | ]; 38 | carouselStore.unsubscribe(notFunc); 39 | expect(carouselStore.subscriptions.length).toBe(1); 40 | expect(carouselStore.subscriptions.indexOf(func)).toBe(0); 41 | }); 42 | it('updateSubscribers() should call each function on the list', () => { 43 | const func1 = jest.fn(); 44 | const func2 = jest.fn(); 45 | carouselStore.subscribe(func1); 46 | carouselStore.subscribe(func2); 47 | carouselStore.updateSubscribers(); 48 | expect(func1).toHaveBeenCalledTimes(1); 49 | expect(func2).toHaveBeenCalledTimes(1); 50 | }); 51 | it('updateSubscribers() should call any supplied callback after it dispatching updates', () => { 52 | const callback = jest.fn(); 53 | carouselStore.updateSubscribers(callback); 54 | expect(callback).toHaveBeenCalledTimes(1); 55 | }); 56 | it('subscribeMasterSpinner() append a src to the list of masterSpinnerSubscriptions', () => { 57 | expect(carouselStore.masterSpinnerSubscriptions).toEqual({}); 58 | carouselStore.subscribeMasterSpinner('/home/bob.jpg'); 59 | expect(carouselStore.masterSpinnerSubscriptions['/home/bob.jpg']).toEqual({ 60 | success: false, 61 | error: false, 62 | complete: false, 63 | }); 64 | }); 65 | it('masterSpinnerSuccess() should set masterSpinnerSubscriptions[src].success and masterSpinnerSubscriptions[src].complete to true', () => { 66 | expect(carouselStore.masterSpinnerSubscriptions).toEqual({}); 67 | carouselStore.subscribeMasterSpinner('/home/bob.jpg'); 68 | expect(carouselStore.masterSpinnerSubscriptions['/home/bob.jpg']).toEqual({ 69 | success: false, 70 | error: false, 71 | complete: false, 72 | }); 73 | carouselStore.masterSpinnerSuccess('/home/bob.jpg'); 74 | expect(carouselStore.masterSpinnerSubscriptions['/home/bob.jpg']).toEqual({ 75 | success: true, 76 | error: false, 77 | complete: true, 78 | }); 79 | }); 80 | it('masterSpinnerError() should set masterSpinnerSubscriptions[src].error and masterSpinnerSubscriptions[src].complete to true', () => { 81 | expect(carouselStore.masterSpinnerSubscriptions).toEqual({}); 82 | carouselStore.subscribeMasterSpinner('/home/bob.jpg'); 83 | expect(carouselStore.masterSpinnerSubscriptions['/home/bob.jpg']).toEqual({ 84 | success: false, 85 | error: false, 86 | complete: false, 87 | }); 88 | carouselStore.masterSpinnerError('/home/bob.jpg'); 89 | expect(carouselStore.masterSpinnerSubscriptions['/home/bob.jpg']).toEqual({ 90 | success: false, 91 | error: true, 92 | complete: true, 93 | }); 94 | }); 95 | it('subscribeMasterSpinner() should not append a duplicate listener for the same image src', () => { 96 | expect(carouselStore.masterSpinnerSubscriptions).toEqual({}); 97 | carouselStore.subscribeMasterSpinner('/home/bob.jpg'); 98 | carouselStore.subscribeMasterSpinner('/home/bob.jpg'); 99 | expect(carouselStore.masterSpinnerSubscriptions['/home/bob.jpg']).toEqual({ 100 | success: false, 101 | error: false, 102 | complete: false, 103 | }); 104 | }); 105 | it('unsubscribeMasterSpinner() should not remove anything but the supplied src', () => { 106 | expect(carouselStore.masterSpinnerSubscriptions).toEqual({}); 107 | carouselStore.subscribeMasterSpinner('/home/bob.jpg'); 108 | carouselStore.subscribeMasterSpinner('/home/poo.jpg'); 109 | expect(carouselStore.masterSpinnerSubscriptions).toEqual({ 110 | '/home/bob.jpg': { success: false, error: false, complete: false }, 111 | '/home/poo.jpg': { success: false, error: false, complete: false }, 112 | }); 113 | expect(carouselStore.unsubscribeMasterSpinner('/home/bob.jpg')).toBe(true); 114 | expect(carouselStore.unsubscribeMasterSpinner('/home/bob.jpg')).toBe(false); 115 | expect(carouselStore.masterSpinnerSubscriptions).toEqual({ 116 | '/home/poo.jpg': { success: false, error: false, complete: false }, 117 | }); 118 | }); 119 | it('isMasterSpinnerFinished() should return false if every image is not complete', () => { 120 | expect(carouselStore.masterSpinnerSubscriptions).toEqual({}); 121 | carouselStore.subscribeMasterSpinner('/home/bob.jpg'); 122 | carouselStore.subscribeMasterSpinner('/home/poo.jpg'); 123 | expect(carouselStore.masterSpinnerSubscriptions).toEqual({ 124 | '/home/bob.jpg': { success: false, error: false, complete: false }, 125 | '/home/poo.jpg': { success: false, error: false, complete: false }, 126 | }); 127 | expect(carouselStore.isMasterSpinnerFinished()).toBe(false); 128 | carouselStore.masterSpinnerSuccess('/home/bob.jpg'); 129 | expect(carouselStore.isMasterSpinnerFinished()).toBe(false); 130 | carouselStore.masterSpinnerError('/home/poo.jpg'); 131 | expect(carouselStore.isMasterSpinnerFinished()).toBe(true); 132 | }); 133 | }); 134 | 135 | describe('WithStore', () => { 136 | it('should remove itself from the subscribe list in the carouselStore when unmounted', () => { 137 | const { props } = components.CarouselProvider; 138 | const wrapper = mount(( 139 | 140 | 150 | Hello 151 | 152 | 153 | )); 154 | const instance = wrapper.instance(); 155 | const spy = jest.spyOn(instance.carouselStore, 'unsubscribe'); 156 | wrapper.unmount(); 157 | expect(spy).toHaveBeenCalledTimes(1); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /src/__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "mrb3k-jest" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/__tests__/general.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import components from '../helpers/component-config'; 5 | 6 | configure({ adapter: new Adapter() }); 7 | 8 | 9 | describe('All visual components should pass through props that are not consumed by that component.', () => { 10 | Object.keys(components).forEach((conf) => { 11 | it(`The ${conf} component should pass through propery "data-foo"`, () => { 12 | const { props, component: Component } = components[conf]; 13 | const wrapper = shallow(); 14 | expect(wrapper.at(0).prop('data-foo')).toBe('bar'); 15 | }); 16 | }); 17 | }); 18 | describe('All visual components should append any supplied className value to the end of className string', () => { 19 | Object.keys(components).forEach((conf) => { 20 | it(`The ${conf} component should append class "foo" to the end of the className string`, () => { 21 | const { props, component: Component } = components[conf]; 22 | const wrapper = shallow(); 23 | expect(wrapper.at(0).prop('className').split(' ').pop()).toBe('foo'); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | // This is a development app for local dev work on react-carousel 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import DevelopmentApp from './App/App'; 5 | 6 | ReactDOM.render(, document.getElementById('app')); 7 | -------------------------------------------------------------------------------- /src/helpers/component-config.js: -------------------------------------------------------------------------------- 1 | /* eslint {'import/no-named-as-default': 0, 'import/no-named-as-default-member': 0} */ 2 | import React from 'react'; 3 | 4 | import ButtonBack from '../ButtonBack/ButtonBack'; 5 | import ButtonFirst from '../ButtonFirst/ButtonFirst'; 6 | import ButtonLast from '../ButtonLast/ButtonLast'; 7 | import ButtonNext from '../ButtonNext/ButtonNext'; 8 | import ButtonPlay from '../ButtonPlay/ButtonPlay'; 9 | import CarouselProvider from '../CarouselProvider/CarouselProvider'; 10 | import Dot from '../Dot/Dot'; 11 | import DotGroup from '../DotGroup/DotGroup'; 12 | import Image from '../Image/Image'; 13 | import ImageWithZoom from '../ImageWithZoom/ImageWithZoom'; 14 | import Slide from '../Slide/Slide'; 15 | import Slider from '../Slider/Slider'; 16 | import Spinner from '../Spinner/Spinner'; 17 | import Store from '../Store/Store'; 18 | 19 | export default { 20 | ButtonBack: { 21 | component: ButtonBack, 22 | props: { 23 | children: 'hello', 24 | currentSlide: 1, 25 | step: 1, 26 | carouselStore: new Store({}), 27 | totalSlides: 3, 28 | visibleSlides: 2, 29 | }, 30 | }, 31 | ButtonFirst: { 32 | component: ButtonFirst, 33 | props: { 34 | children: 'hello', 35 | currentSlide: 1, 36 | carouselStore: new Store({ 37 | currentSlide: 1, 38 | totalSlides: 7, 39 | visibleSlides: 1, 40 | }), 41 | totalSlides: 7, 42 | }, 43 | }, 44 | ButtonLast: { 45 | component: ButtonLast, 46 | props: { 47 | children: 'hello', 48 | currentSlide: 1, 49 | carouselStore: new Store({ 50 | currentSlide: 1, 51 | totalSlides: 7, 52 | visibleSlides: 1, 53 | }), 54 | totalSlides: 7, 55 | visibleSlides: 1, 56 | }, 57 | }, 58 | ButtonNext: { 59 | component: ButtonNext, 60 | props: { 61 | children: 'hello', 62 | currentSlide: 1, 63 | step: 1, 64 | carouselStore: new Store({}), 65 | totalSlides: 3, 66 | visibleSlides: 2, 67 | }, 68 | }, 69 | ButtonPlay: { 70 | component: ButtonPlay, 71 | props: { 72 | carouselStore: new Store({ 73 | isPlaying: false, 74 | }), 75 | children: Sup?, 76 | childrenPause: Play, 77 | childrenPlay: Pause, 78 | isPlaying: false, 79 | }, 80 | }, 81 | CarouselProvider: { 82 | component: CarouselProvider, 83 | props: { 84 | children: 'hello', 85 | naturalSlideHeight: 125, 86 | naturalSlideWidth: 100, 87 | totalSlides: 1, 88 | }, 89 | }, 90 | Dot: { 91 | component: Dot, 92 | props: { 93 | children: 'hello', 94 | currentSlide: 0, 95 | slide: 2, 96 | carouselStore: new Store({ 97 | currentSlide: 0, 98 | totalSlides: 10, 99 | visibleSlides: 2, 100 | }), 101 | totalSlides: 10, 102 | visibleSlides: 2, 103 | }, 104 | }, 105 | DotGroup: { 106 | component: DotGroup, 107 | props: { 108 | currentSlide: 1, 109 | carouselStore: new Store({}), 110 | totalSlides: 3, 111 | visibleSlides: 2, 112 | }, 113 | }, 114 | Image: { 115 | component: Image, 116 | props: { 117 | hasMasterSpinner: false, 118 | orientation: 'horizontal', 119 | src: 'bob.jpg', 120 | carouselStore: new Store({}), 121 | }, 122 | }, 123 | ImageWithZoom: { 124 | component: ImageWithZoom, 125 | props: { 126 | carouselStore: new Store({}), 127 | src: 'bob.jpg', 128 | }, 129 | }, 130 | Slide: { 131 | component: Slide, 132 | props: { 133 | currentSlide: 1, 134 | index: 1, 135 | naturalSlideHeight: 400, 136 | naturalSlideWidth: 300, 137 | orientation: 'horizontal', 138 | slideSize: 25, 139 | totalSlides: 4, 140 | visibleSlides: 2, 141 | }, 142 | }, 143 | Slider: { 144 | component: Slider, 145 | props: { 146 | carouselStore: new Store({ 147 | currentSlide: 0, 148 | }), 149 | children: 'hello', 150 | currentSlide: 0, 151 | disableAnimation: false, 152 | disableKeyboard: false, 153 | dragEnabled: true, 154 | hasMasterSpinner: false, 155 | interval: 5000, 156 | infinite: false, 157 | isPageScrollLocked: false, 158 | isPlaying: false, 159 | lockOnWindowScroll: false, 160 | masterSpinnerFinished: false, 161 | naturalSlideHeight: 100, 162 | naturalSlideWidth: 100, 163 | orientation: 'horizontal', 164 | playDirection: 'forward', 165 | slideSize: 50, 166 | slideTraySize: 250, 167 | step: 2, 168 | totalSlides: 5, 169 | touchEnabled: true, 170 | visibleSlides: 2, 171 | }, 172 | }, 173 | Spinner: { 174 | component: Spinner, 175 | props: {}, 176 | }, 177 | }; 178 | -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | export function cn(a) { 4 | return a.map((b) => { 5 | if (b === false) return null; 6 | return b; 7 | }).join(' ').replace(/\s+/g, ' ').trim(); 8 | } 9 | 10 | export function randomHexColor() { 11 | // eslint-disable-next-line no-bitwise 12 | return `#${(Math.random() * 0xFFFFFF << 0).toString(16)}`; 13 | } 14 | 15 | export function slideUnit(visibleSlides = 1) { 16 | return 100 / visibleSlides; 17 | } 18 | 19 | export function slideSize(totalSlides, visibleSlides) { 20 | return ((100 / totalSlides) * visibleSlides) / visibleSlides; 21 | } 22 | 23 | export function slideTraySize(totalSlides, visibleSlides) { 24 | return (100 * totalSlides) / visibleSlides; 25 | } 26 | 27 | export function pct(num) { 28 | return `${num}%`; 29 | } 30 | 31 | export const LOADING = 'loading'; 32 | export const SUCCESS = 'success'; 33 | export const ERROR = 'error'; 34 | 35 | export const CarouselPropTypes = { 36 | children: PropTypes.oneOfType([ 37 | PropTypes.arrayOf(PropTypes.node), 38 | PropTypes.node, 39 | ]), 40 | direction: PropTypes.oneOf(['forward', 'backward']), 41 | height: (props, propName) => { 42 | const prop = props[propName]; 43 | if (props.orientation === 'vertical' && (prop === null || typeof prop !== 'number')) { 44 | return new Error(`Missing required property '${propName}' when orientation is vertical. You must supply a number representing the height in pixels`); 45 | } 46 | return null; 47 | }, 48 | orientation: PropTypes.oneOf(['horizontal', 'vertical']), 49 | isBgImage: (props, propName) => { 50 | const value = props[propName]; 51 | if (value === true && props.tag === 'img') { 52 | return new Error(`HTML img elements should not have a backgroundImage. Please use ${propName} for other block-level HTML tags, like div, a, section, etc...`); 53 | } 54 | return null; 55 | }, 56 | }; 57 | 58 | /** 59 | * Cap a value at a minimum value and a maximum value. 60 | * @param {number} min The smallest allowed value. 61 | * @param {number} max The largest allowed value. 62 | * @param {number} x A value. 63 | * @return {number} Either the original value, the minimum value, or the maximum value. 64 | */ 65 | export const boundedRange = ({ min, max, x }) => Math.min( 66 | max, 67 | Math.max(min, x), 68 | ); 69 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as ButtonBack } from './ButtonBack'; 2 | export { default as ButtonBackView } from './ButtonBack/ButtonBack'; 3 | export { default as ButtonFirst } from './ButtonFirst'; 4 | export { default as ButtonFirstView } from './ButtonFirst/ButtonFirst'; 5 | export { default as ButtonLast } from './ButtonLast'; 6 | export { default as ButtonLastView } from './ButtonLast/ButtonLast'; 7 | export { default as ButtonNext } from './ButtonNext'; 8 | export { default as ButtonNextView } from './ButtonNext/ButtonNext'; 9 | export { default as ButtonPlay } from './ButtonPlay'; 10 | export { default as ButtonPlayView } from './ButtonPlay/ButtonPlay'; 11 | export { default as CarouselProvider, CarouselContext } from './CarouselProvider'; 12 | export { default as Dot } from './Dot'; 13 | export { default as DotGroup } from './DotGroup'; 14 | export { default as DotGroupView } from './DotGroup/DotGroup'; 15 | export { default as DotView } from './Dot/Dot'; 16 | export { default as Image } from './Image'; 17 | export { default as ImageView } from './Image/Image'; 18 | export { default as ImageWithZoom } from './ImageWithZoom'; 19 | export { default as ImageWithZoomView } from './ImageWithZoom/ImageWithZoom'; 20 | export { default as Slide } from './Slide'; 21 | export { default as Slider } from './Slider'; 22 | export { default as SliderView } from './Slider/Slider'; 23 | export { default as SlideView } from './Slide/Slide'; 24 | export { default as Spinner } from './Spinner'; 25 | export { default as SpinnerView } from './Spinner/Spinner'; 26 | export { default as Store } from './Store/Store'; 27 | export { default as WithStore } from './Store/WithStore'; 28 | -------------------------------------------------------------------------------- /typings/carouselElements.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface SliderProps extends React.HTMLAttributes { 4 | readonly children: React.ReactNode 5 | readonly className?: string 6 | readonly classNameAnimation?: string 7 | readonly classNameTray?: string 8 | readonly classNameTrayWrap?: string 9 | readonly moveThreshold?: number, 10 | readonly preventVerticalScrollOnTouch?: boolean 11 | readonly horizontalPixelThreshold?: number 12 | readonly verticalPixelThreshold?: number, 13 | readonly onMasterSpinner?: () => void 14 | readonly style?: {} 15 | readonly spinner?: () => void 16 | readonly trayTag?: string 17 | readonly trayProps?: React.HTMLAttributes 18 | } 19 | type SliderInterface = React.ComponentType 20 | /** 21 | * A Slider is a viewport that masks slides. The Slider component must wrap one or more Slide components. 22 | */ 23 | declare const Slider: SliderInterface 24 | 25 | 26 | 27 | interface SlideProps extends React.HTMLAttributes { 28 | readonly className?: string 29 | readonly classNameHidden?: string 30 | readonly classNameVisible?: string 31 | readonly index: number 32 | readonly innerClassName?: string 33 | readonly innerTag?: string 34 | readonly onBlur?: () => void 35 | readonly onFocus?: () => void 36 | readonly tabIndex?: number 37 | readonly tag?: string 38 | readonly style?: {} 39 | } 40 | type SlideInterface = React.ComponentType 41 | /** 42 | * The Slide component is a container with an intrinsic ratio computed by the 43 | * CarouselProvider naturalSlideWidth and naturalSlideHeight properties. 44 | * By default, only one slide is visible in the Slider at a time. 45 | * You can change this by altering the visibleSlides property of the CarouselProvider. 46 | * Slide components also contain a div that acts as an aria compliant focus ring when 47 | * the Slide receives focus either by using a keyboard tab, mouse click, or touch. 48 | */ 49 | declare const Slide: SlideInterface 50 | 51 | 52 | 53 | interface ImageWithZoomProps { 54 | readonly alt?: string 55 | readonly bgImageProps?: object 56 | readonly bgImageTag?: string 57 | readonly className?: string 58 | readonly imageClassName?: string 59 | readonly overlayClassName?: string 60 | readonly onError?: () => void 61 | readonly onLoad?: () => void 62 | readonly src: string 63 | readonly srcZoomed?: string 64 | readonly tag?: string 65 | readonly isPinchZoomEnabled?: boolean 66 | } 67 | type ImageWithZoomInterface = React.ComponentType 68 | declare const ImageWithZoom: ImageWithZoomInterface 69 | 70 | 71 | 72 | interface ImageProps extends React.HTMLAttributes { 73 | readonly alt?: string 74 | readonly children?: React.ReactNode 75 | readonly className?: string 76 | readonly hasMasterSpinner: boolean 77 | readonly isBgImage?: boolean 78 | readonly onError?: () => void 79 | readonly onLoad?: () => void 80 | readonly renderError?: () => void 81 | readonly renderLoading?: () => void 82 | readonly src: string 83 | readonly style?: { 84 | readonly [key: string]: string 85 | } 86 | readonly tag?: string 87 | } 88 | type ImageInterface = React.ComponentClass 89 | declare const Image: ImageInterface 90 | 91 | 92 | interface RenderDotsProps { 93 | readonly currentSlide?: number, 94 | readonly totalSlides?: number, 95 | readonly visibleSlides?: number, 96 | readonly disableActiveDots?: boolean, 97 | readonly showAsSelectedForCurrentSlideOnly?: boolean, 98 | } 99 | 100 | type RenderDotsFunction = (props: RenderDotsProps) => void 101 | 102 | interface DotGroupProps extends React.HTMLAttributes { 103 | readonly children?: React.ReactNode 104 | readonly className?: string 105 | readonly dotNumbers?: boolean 106 | readonly currentSlide?: number, 107 | readonly totalSlides?: number, 108 | readonly visibleSlides?: number, 109 | readonly disableActiveDots?: boolean, 110 | readonly showAsSelectedForCurrentSlideOnly?: boolean, 111 | readonly renderDots?: RenderDotsFunction, 112 | } 113 | type DotGroupInterface = React.ComponentClass 114 | /** 115 | * A compound component that creates a bunch of Dot's automatically for you. 116 | */ 117 | declare const DotGroup: DotGroupInterface 118 | 119 | 120 | 121 | interface DotProps extends React.HTMLAttributes { 122 | readonly children?: React.ReactChild 123 | readonly className?: string 124 | readonly disabled?: boolean 125 | readonly onClick?: () => void 126 | readonly slide: number 127 | } 128 | type DotInterface = React.ComponentClass 129 | /** 130 | * A Dot component is a HTML button. Dots directly correlate to slides. Clicking on a dot causes it's correlating slide to scroll into the left-most visible slot of slider. The dots for currently visible slides cause are disabled. You can override the auto-disable feature by setting disabled to false (see table below) 131 | */ 132 | declare const Dot: DotInterface 133 | 134 | 135 | 136 | interface ButtonNextProps extends React.HTMLAttributes { 137 | readonly children: React.ReactChild 138 | readonly className?: string 139 | readonly disabled?: boolean 140 | readonly onClick?: (ev?: React.SyntheticEvent) => void 141 | } 142 | type ButtonNextInterface = React.ComponentClass 143 | /** 144 | * A button for moving the slider forwards. Forwards on a horizontal carousel means "move to the right". Backwards on a vertical carousel means "move to the bottom". The slider will traverse an amount of slides determined by the step property of CarouselProvider. 145 | */ 146 | declare const ButtonNext: ButtonNextInterface 147 | 148 | 149 | 150 | interface ButtonBackProps extends React.HTMLAttributes { 151 | readonly children: React.ReactChild 152 | readonly className?: string 153 | readonly disabled?: boolean 154 | readonly onClick?: (ev?: React.SyntheticEvent) => void 155 | } 156 | type ButtonBackInterface = React.ComponentClass 157 | /** 158 | * A button for moving the slider backwards. Backwards on a horizontal carousel means "move to the left". Backwards on a vertical carousel means "move to the top". The slider will traverse an amount of slides determined by the step property of CarouselProvider. 159 | */ 160 | declare const ButtonBack: ButtonBackInterface 161 | 162 | 163 | 164 | interface ButtonLastProps extends React.HTMLAttributes { 165 | readonly children: React.ReactChild 166 | readonly className?: string 167 | readonly disabled?: boolean 168 | readonly onClick?: (ev?: React.SyntheticEvent) => void 169 | } 170 | type ButtonLastInterface = React.ComponentClass 171 | /** 172 | * Moves the slider to the end of the slides (totalSlides - visibleSlides). 173 | */ 174 | declare const ButtonLast: ButtonLastInterface 175 | 176 | 177 | 178 | interface ButtonFirstProps extends React.HTMLAttributes { 179 | readonly children: React.ReactChild 180 | readonly className?: string 181 | readonly disabled?: boolean 182 | readonly onClick?: (ev?: React.SyntheticEvent) => void 183 | } 184 | type ButtonFirstInterface = React.ComponentClass 185 | /** 186 | * Moves the slider to the beginning of the slides. 187 | */ 188 | declare const ButtonFirst: ButtonLastInterface 189 | 190 | 191 | 192 | interface ButtonPlayProps extends React.HTMLAttributes { 193 | readonly childrenPaused?: React.ReactNode 194 | readonly childrenPlaying?: React.ReactNode 195 | readonly className?: string 196 | readonly disabled?: boolean 197 | readonly onClick?: (ev?: React.SyntheticEvent) => void 198 | } 199 | type ButtonPlayInterface = React.ComponentClass 200 | /** 201 | * Pressing this button causes the slides to automatically advance by CarouselProvider's step property after an interval determined by CarouselProvider's interval property. 202 | */ 203 | declare const ButtonPlay: ButtonPlayInterface 204 | 205 | export { 206 | ButtonBack, 207 | ButtonFirst, 208 | ButtonLast, 209 | ButtonNext, 210 | ButtonPlay, 211 | Dot, 212 | DotGroup, 213 | Image, 214 | ImageWithZoom, 215 | Slide, 216 | Slider, 217 | SliderProps, 218 | SlideProps, 219 | ImageWithZoomProps, 220 | ImageProps, 221 | DotGroupProps, 222 | DotProps, 223 | ButtonBackProps, 224 | ButtonNextProps, 225 | ButtonLastProps, 226 | ButtonFirstProps, 227 | ButtonPlayProps 228 | } 229 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for pure-react-carousel 1.12.0 2 | // Definitions by: Jedrzej Lewandowski 3 | // TypeScript Version: 2.7.2 4 | 5 | import React from 'react' 6 | import { 7 | ButtonBack, 8 | ButtonFirst, 9 | ButtonLast, 10 | ButtonNext, 11 | ButtonPlay, 12 | Dot, 13 | DotGroup, 14 | Image, 15 | ImageWithZoom, 16 | Slide, 17 | Slider, 18 | SliderProps, 19 | SlideProps, 20 | ImageWithZoomProps, 21 | ImageProps, 22 | DotGroupProps, 23 | DotProps, 24 | ButtonBackProps, 25 | ButtonNextProps, 26 | ButtonLastProps, 27 | ButtonFirstProps, 28 | ButtonPlayProps 29 | } from './carouselElements' 30 | 31 | interface CarouselState { 32 | readonly currentSlide: number 33 | readonly disableAnimation: boolean 34 | readonly disableKeyboard: boolean 35 | readonly hasMasterSpinner: boolean 36 | readonly imageErrorCount: number 37 | readonly imageSuccessCount: number 38 | readonly isPlaying: boolean 39 | readonly lockOnWindowScroll: boolean 40 | readonly preventVerticalScrollOnTouch: boolean 41 | readonly horizontalPixelThreshold: number 42 | readonly verticalPixelThreshold: number, 43 | readonly masterSpinnerFinished: boolean 44 | readonly masterSpinnerThreshold: number 45 | readonly naturalSlideHeight: number 46 | readonly naturalSlideWidth: number 47 | readonly orientation: 'horizontal' | 'vertical' 48 | readonly slideSize: number 49 | readonly slideTraySize: number 50 | readonly step: number 51 | readonly dragStep: number 52 | readonly totalSlides: number 53 | readonly touchEnabled: boolean 54 | readonly dragEnabled: boolean 55 | readonly visibleSlides: number 56 | readonly infinite: boolean 57 | readonly isIntrinsicHeight: boolean 58 | } 59 | 60 | interface CarouselStoreInterface { 61 | readonly state: CarouselState 62 | readonly setStoreState: (state: Partial) => void 63 | readonly getStoreState: () => CarouselState 64 | readonly subscribe: (func: () => void) => void 65 | readonly unsubscribe: (func: () => void) => void 66 | readonly updateSubscribers: (cb?: (state: CarouselState) => void) => void 67 | readonly subscribeMasterSpinner: (src: string) => void 68 | readonly unsubscribeMasterSpinner: (src: string) => false | object 69 | readonly unsubscribeAllMasterSpinner: () => void 70 | readonly masterSpinnerSuccess: (src: string) => void 71 | readonly masterSpinnerError: (src: string) => void 72 | readonly setMasterSpinnerFinished: () => void 73 | readonly isMasterSpinnerFinished: () => boolean 74 | } 75 | 76 | declare const CarouselContext: React.Context 77 | 78 | interface CarouselProviderProps { 79 | readonly children: React.ReactNode 80 | readonly className?: string 81 | readonly currentSlide?: CarouselState['currentSlide'] 82 | readonly disableAnimation?: CarouselState['disableAnimation'] 83 | readonly disableKeyboard?: CarouselState['disableKeyboard'] 84 | readonly hasMasterSpinner?: CarouselState['hasMasterSpinner'] 85 | readonly interval?: number 86 | readonly isPlaying?: CarouselState['isPlaying'] 87 | readonly lockOnWindowScroll?: CarouselState['lockOnWindowScroll'] 88 | readonly preventVerticalScrollOnTouch?: CarouselState['preventVerticalScrollOnTouch'] 89 | readonly horizontalPixelThreshold?: CarouselState['horizontalPixelThreshold'] 90 | readonly verticalPixelThreshold?: CarouselState['verticalPixelThreshold'], 91 | readonly naturalSlideHeight: CarouselState['naturalSlideHeight'] 92 | readonly naturalSlideWidth: CarouselState['naturalSlideWidth'] 93 | readonly playDirection?: 'forward' | 'backward' 94 | readonly orientation?: CarouselState['orientation'] 95 | readonly step?: CarouselState['step'] 96 | readonly dragStep?: CarouselState['dragStep'] 97 | readonly tag?: string 98 | readonly totalSlides: CarouselState['totalSlides'] 99 | readonly touchEnabled?: CarouselState['touchEnabled'] 100 | readonly dragEnabled?: CarouselState['dragEnabled'] 101 | readonly visibleSlides?: CarouselState['visibleSlides'] 102 | readonly infinite?: CarouselState['infinite'] 103 | readonly isIntrinsicHeight?: CarouselState['isIntrinsicHeight'] 104 | } 105 | 106 | type CarouselProviderInterface = React.ComponentType 107 | /** 108 | * CarouselProvider allows the other carousel components to communicate with each other. 109 | * 110 | * The only required properties are: 111 | * the totalSlides, naturalSlideWidth, and naturalSlideHeight. 112 | * 113 | * The naturalSlideWidth and naturalSlideHeight are used 114 | * to create an aspect ratio for each slide. 115 | * 116 | * Since the carousel is responsive by default, 117 | * it will stretch to fill in the width of it's parent container. 118 | * 119 | * The CarouselProvider must also have children. 120 | */ 121 | declare const CarouselProvider: CarouselProviderInterface 122 | 123 | export interface CarouselInjectedProps { 124 | readonly carouselStore: Pick< 125 | CarouselStoreInterface, 126 | | "getStoreState" 127 | | "masterSpinnerError" 128 | | "masterSpinnerSuccess" 129 | | "setStoreState" 130 | | "subscribeMasterSpinner" 131 | | "unsubscribeAllMasterSpinner" 132 | | "unsubscribeMasterSpinner" 133 | >; 134 | } 135 | 136 | type MapStateToProps = (state: CarouselState) => TStateProps 137 | 138 | interface WithStoreInterface { 139 | ( 140 | component: React.ComponentType, 141 | mapStateToProps?: MapStateToProps 142 | ): React.ComponentType 143 | } 144 | /** 145 | * Use this HOC to pass CarouselProvider state properties as props to a component. 146 | * Basically, Your custom component must be an descendant of . 147 | * It doesn't have to be a direct descendant, 148 | * it just needs to be between some the opening and closing CarouselProvider tags somewhere. 149 | * 150 | * 151 | * 152 | * 153 | * 154 | * WithStore has two arguments: 155 | * WithStore([component], [mapstateToProps]) 156 | * 157 | * The first argument is the component to wrap (ex: YourComponentHere) and it's required. 158 | * 159 | * The second argument is optional. 160 | * It is a "map state to props" function that you must create. 161 | * 162 | */ 163 | declare const WithStore: WithStoreInterface 164 | 165 | export { 166 | ButtonBack, 167 | ButtonFirst, 168 | ButtonLast, 169 | ButtonNext, 170 | ButtonPlay, 171 | CarouselStoreInterface, 172 | CarouselContext, 173 | CarouselProvider, 174 | CarouselProviderProps, 175 | Dot, 176 | DotGroup, 177 | Image, 178 | ImageWithZoom, 179 | Slide, 180 | Slider, 181 | WithStore, 182 | SliderProps, 183 | SlideProps, 184 | ImageWithZoomProps, 185 | ImageProps, 186 | DotGroupProps, 187 | DotProps, 188 | ButtonBackProps, 189 | ButtonNextProps, 190 | ButtonLastProps, 191 | ButtonFirstProps, 192 | ButtonPlayProps 193 | } 194 | --------------------------------------------------------------------------------