├── .eslintrc ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── docs ├── 3.x-document.md └── live-examples.md ├── package.json ├── src ├── ReactIdSwiper.custom.tsx ├── ReactIdSwiper.tsx ├── hooks.ts ├── index.ts ├── tests │ ├── ReactISwiper.custom.test.tsx │ ├── ReactIdSwiper.test.tsx │ ├── __snapshots__ │ │ ├── ReactISwiper.custom.test.tsx.snap │ │ └── ReactIdSwiper.test.tsx.snap │ ├── setup.ts │ └── utils.test.tsx ├── types.ts └── utils.ts ├── tsconfig.json ├── tsconfig.standalone.json ├── webpack.config.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:react/recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "prettier/@typescript-eslint", 8 | "plugin:prettier/recommended", 9 | "plugin:import/errors", 10 | "plugin:import/warnings", 11 | "plugin:import/typescript", 12 | "plugin:jest/recommended" 13 | ], 14 | "plugins": ["react", "@typescript-eslint", "prettier", "react-hooks"], 15 | "env": { 16 | "browser": true, 17 | "jest": true, 18 | "amd": true, 19 | "node": true, 20 | "es6": true, 21 | "jest/globals": true 22 | }, 23 | "parserOptions": { 24 | "ecmaVersion": 2018, 25 | "sourceType": "module", 26 | "ecmaFeatures": { 27 | "jsx": true 28 | } 29 | }, 30 | "rules": { 31 | "import/no-unresolved": "error", 32 | "import/named": "off", 33 | "import/no-named-as-default" : "off", 34 | "import/default": "off", 35 | "import/order": ["error", { 36 | "groups": ["external", "builtin", "internal", "parent", "sibling", "index" ] 37 | }], 38 | "@typescript-eslint/explicit-function-return-type": "off", 39 | "@typescript-eslint/no-explicit-any": ["off"], 40 | "@typescript-eslint/explicit-member-accessibility": "off", 41 | "@typescript-eslint/no-empty-interface": ["error", { 42 | "allowSingleExtends": true 43 | }], 44 | "@typescript-eslint/ban-types": ["error", { 45 | "types": { 46 | "{}": false, 47 | "object": false 48 | } 49 | }], 50 | "react/display-name": "off", 51 | "prettier/prettier": ["error", { 52 | "singleQuote": true, 53 | "printWidth": 100, 54 | "semi": true, 55 | "arrowParens" : "avoid", 56 | "trailingComma": "none" 57 | }] 58 | }, 59 | "overrides": [{ 60 | "files": ["**/*.tsx"], 61 | "rules": { 62 | "react/prop-types": "off" 63 | } 64 | }], 65 | "settings": { 66 | "react": { 67 | "version": "detect" 68 | }, 69 | "import/parsers": { 70 | "@typescript-eslint/parser": [".ts", ".tsx"] 71 | }, 72 | "import/resolver": { 73 | "typescript": {} 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js text 2 | *.html text 3 | *.json text 4 | *.png binary 5 | *.jpg binary 6 | 7 | # Make GitHub ignore vendor libraries while computing language stats. 8 | # See https://github.com/github/linguist#overrides. 9 | 10 | /src/* linguist-vendored=true 11 | /examples/app/* linguist-vendored=true 12 | 13 | # Explicitly specify language for non-standard extensions used under 14 | 15 | *.css_ linguist-language=CSS 16 | *.html_ linguist-language=HTML 17 | *.js_ linguist-language=JavaScript 18 | *.json_ linguist-language=JSON 19 | *.scss_ linguist-language=CSS 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | /lib 4 | /coverage 5 | .vscode -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | node_js: 7 | - 12 8 | install: 9 | - yarn install 10 | - npm install -g codecov 11 | script: 12 | - yarn test 13 | - yarn test:coverage 14 | - yarn lint:ts 15 | after_script: "cat ./coverage/lcov.info | $(npm bin)/codecov" 16 | branches: 17 | only: 18 | - master 19 | - /^greenkeeper/.*$/ 20 | notifications: 21 | email: 22 | recipients: 23 | - phucnguyenhoang1985@gmail.com 24 | on_success: change 25 | on_failure: always -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log for React-id-swiper 2 | 3 | ## [v4.0.0](https://github.com/kidjp85/react-id-swiper/compare/4.0.0...3.0.0) - Released on Jun 22th, 2020 4 | * Upgrade to support `Swiper` newest version 5 | * Remove `getSwiper` props 6 | * Allow to access `Swiper instance` from `ref` 7 | * Upgrade npm packages 8 | 9 | ## [v3.0.0](https://github.com/kidjp85/react-id-swiper/compare/3.0.0...2.4.0) - Released on Feb 29th, 2020 10 | * Upgrade to support `Swiper` version 5.3.0 11 | * Replace `createRef` with `useRef` for `swiper` node ref 12 | * Upgrade npm packages 13 | 14 | 15 | ## [v2.4.0](https://github.com/kidjp85/react-id-swiper/compare/2.4.0...2.3.2) - Released on Nov 7th, 2019 16 | * Upgrade to support `Swiper` version 5.x 17 | * Drop support for `Swiper`'s stylesheet 18 | * Upgrade npm packages 19 | 20 | ## [v2.3.2](https://github.com/kidjp85/react-id-swiper/compare/2.3.2...2.3.1) - Released on July 24th, 2019 21 | * Upgrade npm packages 22 | * Replace `tslint` with `eslint` 23 | 24 | ## [v2.3.1](https://github.com/kidjp85/react-id-swiper/compare/2.3.1...2.3.0) - Released on June 27th, 2019 25 | * Fix bug [276](https://github.com/kidjp85/react-id-swiper/issues/276) for rebuildOnUpdate and shouldSwiperUpdate not working properly. 26 | 27 | ## [v2.3.0](https://github.com/kidjp85/react-id-swiper/compare/2.3.0...2.1.2) - Released on June 27th, 2019 28 | * Use Swiper full build version as default instead of `react-id-swiper/lib/react-id-swiper.full` 29 | * Provide custom build version which allows to use `Swiper` class as props instead of importing directly from `swiper/dist/js/swiper.esm` 30 | * Stylesheet files are now can be loaded from `react-id-swiper/lib/styles` 31 | 32 | ## [v2.1.2](https://github.com/kidjp85/react-id-swiper/compare/2.1.2...2.1.1) - Released on April 16th, 2019 33 | * Fix rendering issue for styled-component element 34 | 35 | ## [v2.1.1](https://github.com/kidjp85/react-id-swiper/compare/2.1.1...2.1.0) - Released on Mar 26th, 2019 36 | * Add full version back to lib folder 37 | 38 | ## [v2.1.0](https://github.com/kidjp85/react-id-swiper/compare/2.1.0...2.0.0) - Released on Mar 15th, 2019 39 | * :bomb: **Great news** - Add new props **`modules`** that allows to import only necessary swiper modules. 40 | * Fix `activeSlideKey` does not work correctly when `loop: true` 41 | 42 | ## [v2.0.0](https://github.com/kidjp85/react-id-swiper/compare/2.0.0...2.0.0-beta) - Released on Mar 14th, 2019 43 | * Add tests 44 | * Update README 45 | 46 | ## [*v2.0.0-beta*](https://github.com/kidjp85/react-id-swiper/compare/2.0.0...1.6.9) - Released on Mar 13th, 2019 47 | * Rewrite completely new package with Typescript + React Hooks Apis 48 | * Use `swiper` as peer-dependencies 49 | * Add new prop `getSwiper` function that returns `Swiper` instance. 50 | * Drop custom build for lightweight version 51 | * Drop `swiper` from standalone umd build 52 | * Add swiper@4.5.0 stylesheet files 53 | 54 | ## [v1.6.9](https://github.com/kidjp85/react-id-swiper/compare/1.6.9...1.6.8) - Released on Feb 24th, 2019 55 | * Upgrade swiper@4.4.6 56 | * Update babel 7 57 | * [PR-227](https://github.com/kidjp85/react-id-swiper/pull/227) by [jpetitcolas](https://github.com/jpetitcolas) 58 | 59 | ## [v1.6.8](https://github.com/kidjp85/react-id-swiper/compare/1.6.8...1.6.7) - Released on Sep 20th, 2018 60 | * Upgrade swiper@4.4.1 61 | * Put `src/styles` folder back which only support `css`, `scss` 62 | * Update README 63 | 64 | ## [v1.6.7](https://github.com/kidjp85/react-id-swiper/compare/1.6.7...1.6.6) - Released on Sep 2nd, 2018 65 | * Upgrade swiper@4.3.5 66 | * Fix typo for README 67 | * Remove `src/styles` folder 68 | * Update test 69 | 70 | ## [v1.6.6](https://github.com/kidjp85/react-id-swiper/compare/1.6.6...1.6.5) - Released on June 17th, 2018 71 | * Upgrade swiper@4.3.3 for custom build 72 | * Remove support for parallax in lightweight version 73 | * Update test 74 | 75 | ## [v1.6.5](https://github.com/kidjp85/react-id-swiper/compare/1.6.5...1.6.4) - Released on June 14th, 2018 76 | * Upgrade swiper@4.3.3 77 | 78 | ## [v1.6.4](https://github.com/kidjp85/react-id-swiper/compare/1.6.4...1.6.3) - Released on May 18th, 2018 79 | * Provide lightweight version which drops those features below 80 | - Virtual 81 | - Keyboard 82 | - Mouse wheel 83 | - Zoom 84 | - Lazy load image 85 | - A11y 86 | - History 87 | - Hash-navigation 88 | - Effect-cube 89 | - Effect-flip 90 | - Effect-coverflow 91 | 92 | 93 | ## [v1.6.3](https://github.com/kidjp85/react-id-swiper/compare/1.6.3...1.6.2) - Released on May 3rd, 2018 94 | * Upgrade swiper@4.2.6 95 | * Deprecate these props: 96 | - renderCustomPrevButton 97 | - renderCustomNextButton 98 | - renderCustomScrollbar 99 | - renderCustomPagination 100 | - renderCustomParallax 101 | - prevButtonCustomizedClass, 102 | - nextButtonCustomizedClass, 103 | - paginationCustomizedClass, 104 | - scrollbarCustomizedClass 105 | * Add new render props: 106 | - renderPrevButton 107 | - renderNextButton 108 | - renderScrollbar 109 | - renderPagination 110 | - renderParallax 111 | * Clean code 112 | * Update unit test 113 | 114 | ## [v1.6.2](https://github.com/kidjp85/react-id-swiper/compare/1.6.2...1.6.1) - Released on April 1st, 2018 115 | * Upgrade swiper@4.2.2 116 | * Update unit test 117 | 118 | ## [v1.6.1](https://github.com/kidjp85/react-id-swiper/compare/1.6.1...1.5.8) - Released on February 17th, 2018 119 | * Upgrade swiper@4.1.6 120 | * Add unit test 121 | 122 | ## [v1.5.8](https://github.com/kidjp85/react-id-swiper/compare/1.5.8...1.5.7) - Released on January 24th, 2018 123 | * Upgrade swiper@4.1.0 124 | * Fix bug for UMD build 125 | 126 | ## [v1.5.7](https://github.com/kidjp85/react-id-swiper/compare/1.5.7...1.5.6) - Released on December 20th, 2017 127 | * Add umd version 128 | * Add new params 129 | - ContainerEl 130 | - WrapperEl 131 | - renderCustomPrevButton 132 | - renderCustomNextButton 133 | - renderCustomScrolbar 134 | - renderCustomPagination 135 | - renderCustomParallax 136 | 137 | ## [v1.5.6](https://github.com/kidjp85/react-id-swiper/compare/1.5.6...1.5.5) - Released on December 1st, 2017 138 | * Upgrade Swiper@4.0.7 139 | * Add jest. Expose rebuildSwiper instance method [PR 108](https://github.com/kidjp85/react-id-swiper/pull/108) by [timkindberg](https://github.com/timkindberg) 140 | 141 | ## [v1.5.5](https://github.com/kidjp85/react-id-swiper/compare/1.5.5...1.5.4) - Released on November 15, 2017 142 | * Upgrade Swiper@4.0.6 143 | * Add support for parallax -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nguyen Hoang Phuc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm Version](https://img.shields.io/npm/v/react-id-swiper.svg?style=flat-square)](https://npmjs.org/package/react-id-swiper) 2 | [![Coverage Status](https://img.shields.io/codecov/c/github/moroshko/react-autosuggest/master.svg?style=flat-square)](https://codecov.io/gh/kidjp85/react-id-swiper) 3 | [![Weekly download](https://img.shields.io/npm/dw/react-id-swiper.svg?style=flat-square)](https://npmjs.org/package/react-id-swiper) 4 | [![Total Downloads](https://img.shields.io/npm/dt/react-id-swiper.svg?style=flat-square)](https://npmjs.org/package/react-id-swiper) 5 | [![Build Status](https://travis-ci.org/kidjp85/react-id-swiper.svg?branch=master)](https://travis-ci.org/kidjp85/react-id-swiper) 6 | 7 | [![Package Quality](http://npm.packagequality.com/badge/react-id-swiper.png)](http://packagequality.com/#?package=react-id-swiper) 8 | 9 | react-id-swiper ( Newest version 4.0.0 ) 10 | ======================================== 11 | > A library to use [Swiper](http://www.idangero.us/swiper/get-started/) as a ReactJs component 12 | 13 | ![Demo](https://media.giphy.com/media/mByDrCTcJch4HVhmfi/giphy.gif) 14 | 15 | What is Swiper? 16 | =============== 17 | 18 | Swiper - is the free and most modern mobile touch slider with hardware accelerated transitions and amazing native behavior. 19 | 20 | It is intended to be used in mobile websites, mobile web apps, and mobile native/hybrid apps. Designed mostly for iOS, but also works great on latest Android, Windows Phone 8 and modern Desktop browsers. 21 | 22 | Swiper is not compatible with all platforms, it is a modern touch slider which is focused only on modern apps/platforms to bring the best experience and simplicity. Swiper does work well with [Gatsby](https://www.gatsbyjs.org/). 23 | 24 | # Props 25 | 26 | | Name | Type | Default value | Description | 27 | | ------------------ | -------- | ---------------- | -------------------------------------------------| 28 | | ContainerEl | String | 'div' | Element type for container | 29 | | containerClass | String | swiper-container | Swiper container class name | 30 | | WrapperEl | String | 'div' | Element type for wrapper | 31 | | wrapperClass | String | swiper-wrapper | Swiper wrapper class name | 32 | | slideClass | String | swiper-slide | Swiper slide class name | 33 | | shouldSwiperUpdate | Boolean | false | Update swiper when component is updated | 34 | | rebuildOnUpdate | Boolean | false | Rebuild swiper when component is updated | 35 | | noSwiping | Boolean | false | Disable swiping by condition | 36 | | activeSlideKey | String | null | Initial slide index | 37 | | renderPrevButton | function | | Render props function for prev button | 38 | | renderNextButton | function | | Render props function for next button | 39 | | renderScrollbar | function | | Render props function for scrollbar | 40 | | renderPagination | function | | Render props function for pagination | 41 | | renderParallax | function | | Render props function for parallax | 42 | 43 | **If you want to use Swiper custom build to reduce bundle size, you need to use extra props below.** 44 | 45 | # Custom build extra props 46 | 47 | | Name | Type | Default value | Description | 48 | | ------------------ | -------- | ---------------- | -------------------------------------------------| 49 | | Swiper | Class | | Swiper class | 50 | | modules | array | | Array of Swiper necessary modules | 51 | 52 | 53 | **NOTE:** 54 | 55 | * You can also use Swiper's original params too. Swiper API documentation [HERE](http://idangero.us/swiper/api/) 56 | * Find more info about Swiper custom build [HERE](https://idangero.us/swiper/api/#custom-build) 57 | * [<= 3.x documentation](docs/3.x-document.md) 58 | 59 | # Documentation 60 | 61 | - [Get Started](https://react-id-swiper.ashernguyen.site/doc/get-started) 62 | - [API](https://react-id-swiper.ashernguyen.site/doc/api) 63 | - [Custom Build](https://react-id-swiper.ashernguyen.site/doc/custom-build) 64 | - [Examples](https://react-id-swiper.ashernguyen.site/example/default) 65 | 66 | # Installation and setup 67 | 68 | - From version 2.0.0, it requires **React & ReactDOM ver >=16.8.0** to use [Hooks](https://reactjs.org/docs/hooks-intro.html) 69 | - From version 2.4.0, it requires **Swiper ver >= 5.0.0** 70 | 71 | ## Npm package 72 | 73 | > By npm 74 | 75 | ```bash 76 | npm install --save react-id-swiper@latest swiper@latest 77 | ``` 78 | 79 | > By Yarn 80 | 81 | ```bash 82 | yarn add react-id-swiper@latest swiper@latest 83 | ``` 84 | 85 | ## CDN 86 | 87 | ```html 88 | 89 | ``` 90 | 91 | ```html 92 | 93 | ``` 94 | 95 | # Styling 96 | 97 | **Swiper stylesheet file is required** 98 | 99 | Use Swiper stylesheet file from CDN 100 | 101 | ```html 102 | 103 | ``` 104 | 105 | ```html 106 | 107 | ``` 108 | 109 | **Or from Swiper package** 110 | 111 | You should import directly from `Swiper` package which supports css, scss and less 112 | 113 | > css 114 | 115 | ```javascript 116 | import 'swiper/css/swiper.css' 117 | ``` 118 | 119 | > scss 120 | 121 | ```javascript 122 | import 'swiper/swiper.scss' 123 | ``` 124 | 125 | > less 126 | 127 | ```javascript 128 | import 'swiper/swiper.less' 129 | ``` 130 | 131 | # Examples 132 | 133 | ## Live Examples 134 | 135 | [Codesandbox Live Examples](docs/live-examples.md) 136 | 137 | ## Default 138 | 139 | ```javascript 140 | import React from 'react'; 141 | import Swiper from 'react-id-swiper'; 142 | import 'swiper/css/swiper.css'; 143 | 144 | const SimpleSwiper = () => ( 145 | 146 |
Slide 1
147 |
Slide 2
148 |
Slide 3
149 |
Slide 4
150 |
Slide 5
151 |
152 | ) 153 | 154 | export default SimpleSwiper; 155 | ``` 156 | 157 | ## Using params 158 | 159 | ```javascript 160 | import React from 'react'; 161 | import Swiper from 'react-id-swiper'; 162 | 163 | const SimpleSwiperWithParams = () => { 164 | const params = { 165 | pagination: { 166 | el: '.swiper-pagination', 167 | type: 'bullets', 168 | clickable: true 169 | }, 170 | navigation: { 171 | nextEl: '.swiper-button-next', 172 | prevEl: '.swiper-button-prev' 173 | }, 174 | spaceBetween: 30 175 | } 176 | 177 | return( 178 | 179 |
Slide 1
180 |
Slide 2
181 |
Slide 3
182 |
Slide 4
183 |
Slide 5
184 |
185 | ) 186 | } 187 | 188 | export default SimpleSwiperWithParams; 189 | ``` 190 | 191 | ## Manipulating swiper from outside swiper component 192 | 193 | ```javascript 194 | import React, { useRef } from 'react'; 195 | import Swiper from 'react-id-swiper'; 196 | 197 | const ManipulatingSwiper = () => { 198 | const ref = useRef(null); 199 | 200 | const ref = useRef(null); 201 | 202 | const goNext = () => { 203 | if (ref.current !== null && ref.current.swiper !== null) { 204 | ref.current.swiper.slideNext(); 205 | } 206 | }; 207 | 208 | const goPrev = () => { 209 | if (ref.current !== null && ref.current.swiper !== null) { 210 | ref.current.swiper.slidePrev(); 211 | } 212 | }; 213 | 214 | return ( 215 |
216 | 217 |
Slide 1
218 |
Slide 2
219 |
Slide 3
220 |
Slide 4
221 |
Slide 5
222 |
223 | 224 | 225 |
226 | ); 227 | }; 228 | 229 | export default ManipulatingSwiper; 230 | ``` 231 | 232 | ## Custom build Swiper 233 | 234 | You can find the [WORKING DEMO REPO HERE](https://github.com/kidjp85/react-id-swiper-custom-build) 235 | 236 | ```javascript 237 | import React from 'react'; 238 | import ReactIdSwiperCustom from 'react-id-swiper/lib/ReactIdSwiper.custom'; 239 | import { Swiper, Navigation, Pagination } from 'swiper/js/swiper.esm'; 240 | 241 | const CustomBuildSwiper = () => { 242 | const params = { 243 | // Provide Swiper class as props 244 | Swiper, 245 | // Add modules you need 246 | modules: [Navigation, Pagination], 247 | pagination: { 248 | el: '.swiper-pagination', 249 | type: 'bullets', 250 | clickable: true 251 | }, 252 | navigation: { 253 | nextEl: '.swiper-button-next', 254 | prevEl: '.swiper-button-prev' 255 | }, 256 | spaceBetween: 30 257 | } 258 | 259 | return( 260 | 261 |
Slide 1
262 |
Slide 2
263 |
Slide 3
264 |
Slide 4
265 |
Slide 5
266 |
267 | ) 268 | } 269 | 270 | export default CustomBuildSwiper; 271 | ``` 272 | 273 | **NOTE**: 274 | 275 | * If you use Webpack & Babel you need to setup Babel loader config in `webpack.config.js` like below: 276 | 277 | ```javascript 278 | module: { 279 | rules: [ 280 | { 281 | exclude: [/node_modules\/(?!(swiper|dom7)\/).*/, /\.test\.js(x)?$/], 282 | test: /\.js(x)?$/, 283 | use: [{ loader: 'babel-loader' }], 284 | } 285 | ], 286 | } 287 | ``` 288 | 289 | ## Adding customized css classes 290 | 291 | ```javascript 292 | const params = { 293 | pagination: { 294 | el: '.swiper-pagination.customized-swiper-pagination', 295 | }, // Add your class name for pagination container 296 | navigation: { 297 | nextEl: '.swiper-button-next.customized-swiper-button-next', // Add your class name for next button 298 | prevEl: '.swiper-button-prev.customized-swiper-button-prev' // Add your class name for prev button 299 | }, 300 | containerClass: 'customized-swiper-container' // Replace swiper-container with customized-swiper-container 301 | } 302 | ``` 303 | 304 | ## Adding customized components 305 | 306 | For customized rendering to work, you have to use same classname with params el. 307 | 308 | ```javascript 309 | const params = { 310 | navigation: { 311 | nextEl: '.swiper-button-next', 312 | prevEl: '.swiper-button-prev' 313 | }, 314 | renderPrevButton: () => , 315 | renderNextButton: () => , 316 | } 317 | ``` 318 | 319 | ## Workable slides 320 | 321 | Each slide should be wrapped by HTML element 322 | 323 | > BAD CODE :thumbsdown: 324 | 325 | ```javascript 326 | 327 | Slide content 328 | 329 | ``` 330 | 331 | > GOOD CODE :thumbsup: 332 | 333 | ```javascript 334 | 335 | Slide content 336 | 337 | ``` 338 | 339 | # Bug report 340 | 341 | **Please use the prepared Codesanbox below to reproduce your issue. Thank you!!** 342 | 343 | [![Edit ReactIdSwiper - DEMO](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/p8j61y7j7?fontsize=14) 344 | 345 | 346 | # Authors 347 | 348 | * **Asher Nguyen** - *Initial work* - [Asher Nguyen](https://github.com/kidjp85) 349 | 350 | See also the list of [contributors](https://github.com/kidjp85/react-id-swiper/contributors) who participated in this project. 351 | 352 | 353 | # Buy me a coffee 354 | 355 | Buy Me A Coffee 356 | 357 | # License 358 | 359 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 360 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | 4 | const presets = ['@babel/preset-env']; 5 | const plugins = ['@babel/plugin-syntax-dynamic-import']; 6 | 7 | return { 8 | presets, 9 | plugins 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /docs/3.x-document.md: -------------------------------------------------------------------------------- 1 | [![npm Version](https://img.shields.io/npm/v/react-id-swiper.svg?style=flat-square)](https://npmjs.org/package/react-id-swiper) 2 | [![Coverage Status](https://img.shields.io/codecov/c/github/moroshko/react-autosuggest/master.svg?style=flat-square)](https://codecov.io/gh/kidjp85/react-id-swiper) 3 | [![Weekly download](https://img.shields.io/npm/dw/react-id-swiper.svg?style=flat-square)](https://npmjs.org/package/react-id-swiper) 4 | [![Total Downloads](https://img.shields.io/npm/dt/react-id-swiper.svg?style=flat-square)](https://npmjs.org/package/react-id-swiper) 5 | [![Build Status](https://travis-ci.org/kidjp85/react-id-swiper.svg?branch=master)](https://travis-ci.org/kidjp85/react-id-swiper) 6 | [![Greenkeeper badge](https://badges.greenkeeper.io/kidjp85/react-id-swiper.svg)](https://greenkeeper.io/) 7 | 8 | [![Package Quality](http://npm.packagequality.com/badge/react-id-swiper.png)](http://packagequality.com/#?package=react-id-swiper) 9 | 10 | react-id-swiper ( Newest version 3.0.0 ) 11 | ======================================== 12 | > A library to use [Swiper](http://www.idangero.us/swiper/get-started/) as a ReactJs component 13 | 14 | ![Demo](https://media.giphy.com/media/mByDrCTcJch4HVhmfi/giphy.gif) 15 | 16 | What is Swiper? 17 | =============== 18 | 19 | Swiper - is the free and most modern mobile touch slider with hardware accelerated transitions and amazing native behavior. 20 | 21 | It is intended to be used in mobile websites, mobile web apps, and mobile native/hybrid apps. Designed mostly for iOS, but also works great on latest Android, Windows Phone 8 and modern Desktop browsers. 22 | 23 | Swiper is not compatible with all platforms, it is a modern touch slider which is focused only on modern apps/platforms to bring the best experience and simplicity. Swiper does work well with [Gatsby](https://www.gatsbyjs.org/). 24 | 25 | # Props 26 | 27 | | Name | Type | Default value | Description | 28 | | ------------------ | -------- | ---------------- | -------------------------------------------------| 29 | | ContainerEl | String | 'div' | Element type for container | 30 | | containerClass | String | swiper-container | Swiper container class name | 31 | | WrapperEl | String | 'div' | Element type for wrapper | 32 | | wrapperClass | String | swiper-wrapper | Swiper wrapper class name | 33 | | slideClass | String | swiper-slide | Swiper slide class name | 34 | | shouldSwiperUpdate | Boolean | false | Update swiper when component is updated | 35 | | rebuildOnUpdate | Boolean | false | Rebuild swiper when component is updated | 36 | | noSwiping | Boolean | false | Disable swiping by condition | 37 | | activeSlideKey | String | null | Initial slide index | 38 | | renderPrevButton | function | | Render props function for prev button | 39 | | renderNextButton | function | | Render props function for next button | 40 | | renderScrollbar | function | | Render props function for scrollbar | 41 | | renderPagination | function | | Render props function for pagination | 42 | | renderParallax | function | | Render props function for parallax | 43 | | getSwiper | function | | Callback function that returns Swiper instance | 44 | 45 | **If you want to use Swiper custom build to reduce bundle size, you need to use extra props below.** 46 | 47 | # Custom build extra props 48 | 49 | | Name | Type | Default value | Description | 50 | | ------------------ | -------- | ---------------- | -------------------------------------------------| 51 | | Swiper | Class | | Swiper class | 52 | | modules | array | | Array of Swiper necessary modules | 53 | 54 | 55 | **NOTE:** 56 | 57 | * You can also use Swiper's original params too. Swiper API documentation [HERE](http://idangero.us/swiper/api/) 58 | * Find more info about Swiper custom build [HERE](https://idangero.us/swiper/api/#custom-build) 59 | 60 | # Documentation 61 | 62 | - [Get Started](https://react-id-swiper.ashernguyen.site/doc/get-started) 63 | - [API](https://react-id-swiper.ashernguyen.site/doc/api) 64 | - [Custom Build](https://react-id-swiper.ashernguyen.site/doc/custom-build) 65 | - [Examples](https://react-id-swiper.ashernguyen.site/example/default) 66 | 67 | # Installation and setup 68 | 69 | - From version 2.0.0, it requires **React & ReactDOM ver >=16.8.0** to use [Hooks](https://reactjs.org/docs/hooks-intro.html) 70 | - From version 2.4.0, it requires **Swiper ver >= 5.0.0** 71 | 72 | ## Npm package 73 | 74 | > By npm 75 | 76 | ```bash 77 | npm install --save react-id-swiper@latest swiper@latest 78 | ``` 79 | 80 | > By Yarn 81 | 82 | ```bash 83 | yarn add react-id-swiper@latest swiper@latest 84 | ``` 85 | 86 | ## CDN 87 | 88 | ```html 89 | 90 | ``` 91 | 92 | ```html 93 | 94 | ``` 95 | 96 | # Styling 97 | 98 | **Swiper stylesheet file is required** 99 | 100 | Use Swiper stylesheet file from CDN 101 | 102 | ```html 103 | 104 | ``` 105 | 106 | ```html 107 | 108 | ``` 109 | 110 | **For version <=2.3.2** 111 | 112 | You can import direct from `react-id-swiper/lib/styles/` (supporting css, scss) 113 | 114 | > css 115 | 116 | ```javascript 117 | import 'react-id-swiper/lib/styles/css/swiper.css' 118 | ``` 119 | 120 | > scss 121 | 122 | ```javascript 123 | import 'react-id-swiper/lib/styles/scss/swiper.scss' 124 | ``` 125 | 126 | **For version >=3.0.0** 127 | 128 | You should import directly from `Swiper` packages which supports css, scss and less 129 | 130 | > css 131 | 132 | ```javascript 133 | import 'swiper/css/swiper.css' 134 | ``` 135 | 136 | > scss 137 | 138 | ```javascript 139 | import 'swiper/swiper.scss' 140 | ``` 141 | 142 | > less 143 | 144 | ```javascript 145 | import 'swiper/swiper.less' 146 | ``` 147 | 148 | # Examples 149 | 150 | [Numerous live examples](https://react-id-swiper.ashernguyen.site/example/default): 151 | 152 | >Navigation, Pagination, Pagination / Dynamic Bullets, Progress Pagination, Fraction Pagination, Custom Pagination, Scrollbar, Vertical slider, Space Between Slides, Mutiple Slides Per View, Auto Slides Per View / Carousel Mode, Centered Slides, Centered Slides + Auto Slides Per View, Free Mode / No Fixed Positions, Scroll Container, Multiple Row Slides Layout, Nested Swipers, Grab Cursor, Loop Mode / Infinite Loop, Loop Mode With Multiple Slides Per Group, Fade Effect, 3D Cube Effect, 3D Coverflow Effect, 3D Flip Effect, Mousewheel-control, Auto Play, Thumbs Gallery With Two-way Control, RTL Layout, Parallax, Lazyload Image, Responsive Breakpoints, Manipulating component outside Swiper, Customized Component 153 | 154 | ## Default 155 | 156 | ```javascript 157 | import React from 'react'; 158 | import Swiper from 'react-id-swiper'; 159 | // Version <= 2.3.2 160 | import 'react-id-swiper/lib/styles/css/swiper.css'; 161 | // Version >= 2.4.0 162 | import 'swiper/css/swiper.css'; 163 | 164 | const SimpleSwiper = () => ( 165 | 166 |
Slide 1
167 |
Slide 2
168 |
Slide 3
169 |
Slide 4
170 |
Slide 5
171 |
172 | ) 173 | 174 | export default SimpleSwiper; 175 | ``` 176 | 177 | ## Using params 178 | 179 | ```javascript 180 | import React from 'react'; 181 | import Swiper from 'react-id-swiper'; 182 | 183 | const SimpleSwiperWithParams = () => { 184 | const params = { 185 | pagination: { 186 | el: '.swiper-pagination', 187 | type: 'bullets', 188 | clickable: true 189 | }, 190 | navigation: { 191 | nextEl: '.swiper-button-next', 192 | prevEl: '.swiper-button-prev' 193 | }, 194 | spaceBetween: 30 195 | } 196 | 197 | return( 198 | 199 |
Slide 1
200 |
Slide 2
201 |
Slide 3
202 |
Slide 4
203 |
Slide 5
204 |
205 | ) 206 | } 207 | 208 | export default SimpleSwiperWithParams; 209 | ``` 210 | 211 | ## Manipulating swiper from outside swiper component 212 | 213 | ```javascript 214 | import React, { useState } from 'react'; 215 | import Swiper from 'react-id-swiper'; 216 | 217 | const ManipulatingSwiper = () => { 218 | const [swiper, setSwiper] = useState(null); 219 | 220 | const goNext = () => { 221 | if (swiper !== null) { 222 | swiper.slideNext(); 223 | } 224 | }; 225 | 226 | const goPrev = () => { 227 | if (swiper !== null) { 228 | swiper.slidePrev(); 229 | } 230 | }; 231 | 232 | return ( 233 |
234 | 235 |
Slide 1
236 |
Slide 2
237 |
Slide 3
238 |
Slide 4
239 |
Slide 5
240 |
241 | 242 | 243 |
244 | ); 245 | }; 246 | 247 | export default ManipulatingSwiper; 248 | ``` 249 | 250 | ## Custom build Swiper 251 | 252 | You can find the [WORKING DEMO REPO HERE](https://github.com/kidjp85/react-id-swiper-custom-build) 253 | 254 | ```javascript 255 | import React from 'react'; 256 | import ReactIdSwiperCustom from 'react-id-swiper/lib/ReactIdSwiper.custom'; 257 | // For swiper version 4.x 258 | import { Swiper, Navigation, Pagination } from 'swiper/dist/js/swiper.esm'; 259 | // For swiper version 5.x 260 | import { Swiper, Navigation, Pagination } from 'swiper/js/swiper.esm'; 261 | 262 | const CustomBuildSwiper = () => { 263 | const params = { 264 | // Provide Swiper class as props 265 | Swiper, 266 | // Add modules you need 267 | modules: [Navigation, Pagination], 268 | pagination: { 269 | el: '.swiper-pagination', 270 | type: 'bullets', 271 | clickable: true 272 | }, 273 | navigation: { 274 | nextEl: '.swiper-button-next', 275 | prevEl: '.swiper-button-prev' 276 | }, 277 | spaceBetween: 30 278 | } 279 | 280 | return( 281 | 282 |
Slide 1
283 |
Slide 2
284 |
Slide 3
285 |
Slide 4
286 |
Slide 5
287 |
288 | ) 289 | } 290 | 291 | export default CustomBuildSwiper; 292 | ``` 293 | 294 | **NOTE**: 295 | * If you use Webpack & Babel you need to setup Babel loader config in `webpack.config.js` like below: 296 | 297 | ```javascript 298 | module: { 299 | rules: [ 300 | { 301 | exclude: [/node_modules\/(?!(swiper|dom7)\/).*/, /\.test\.js(x)?$/], 302 | test: /\.js(x)?$/, 303 | use: [{ loader: 'babel-loader' }], 304 | } 305 | ], 306 | } 307 | ``` 308 | 309 | ## Adding customized css classes 310 | 311 | ```javascript 312 | const params = { 313 | pagination: { 314 | el: '.swiper-pagination.customized-swiper-pagination', 315 | }, // Add your class name for pagination container 316 | navigation: { 317 | nextEl: '.swiper-button-next.customized-swiper-button-next', // Add your class name for next button 318 | prevEl: '.swiper-button-prev.customized-swiper-button-prev' // Add your class name for prev button 319 | }, 320 | containerClass: 'customized-swiper-container' // Replace swiper-container with customized-swiper-container 321 | } 322 | ``` 323 | 324 | ## Adding customized components 325 | 326 | For customized rendering to work, you have to use same classname with params el. 327 | 328 | ```javascript 329 | const params = { 330 | navigation: { 331 | nextEl: '.swiper-button-next', 332 | prevEl: '.swiper-button-prev' 333 | }, 334 | renderPrevButton: () => , 335 | renderNextButton: () => , 336 | } 337 | ``` 338 | 339 | ## Workable slides 340 | 341 | Each slide should be wrapped by HTML element 342 | 343 | > BAD CODE :thumbsdown: 344 | 345 | ```javascript 346 | 347 | Slide content 348 | 349 | ``` 350 | 351 | > GOOD CODE :thumbsup: 352 | 353 | ```javascript 354 | 355 | Slide content 356 | 357 | ``` 358 | 359 | # Bug report 360 | 361 | **Please use the prepared Codesanbox below to reproduce your issue. Thank you!!** 362 | 363 | [![Edit ReactIdSwiper - DEMO](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/p8j61y7j7?fontsize=14) 364 | 365 | 366 | # Authors 367 | 368 | * **Asher Nguyen** - *Initial work* - [Asher Nguyen](https://github.com/kidjp85) 369 | 370 | See also the list of [contributors](https://github.com/kidjp85/react-id-swiper/contributors) who participated in this project. 371 | 372 | # License 373 | 374 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 375 | -------------------------------------------------------------------------------- /docs/live-examples.md: -------------------------------------------------------------------------------- 1 | ## Live Examples 2 | 3 | You can view the live code examples in Code Sandbox. 4 | 5 | | Name | Link | 6 | | -------------------------------------------| ------------------------------------------------------------------------------------ | 7 | | Default | https://codesandbox.io/s/default-example-ys1vc | 8 | | Navigation | https://codesandbox.io/s/navigation-example-ksmh8 | 9 | | Pagination | https://codesandbox.io/s/pagination-example-b8ge | 10 | | Pagination Dynamic Bullets | https://codesandbox.io/s/pagination-dynamic-bullets-example-gposd | 11 | | Progress Pagination | https://codesandbox.io/s/progress-pagination-example-u6u6n | 12 | | Fraction Pagination | https://codesandbox.io/s/fraction-pagination-example-0sfty | 13 | | Custom Pagination | https://codesandbox.io/s/custom-pagination-example-jtf4k | 14 | | Scrollbar | https://codesandbox.io/s/scrollbar-example-tirwz | 15 | | Vertical Slider | https://codesandbox.io/s/vertical-slider-example-x3qf0 | 16 | | Space Between Slides | https://codesandbox.io/s/space-between-slides-example-p1ult | 17 | | Multiple Slides Per View | https://codesandbox.io/s/mutiple-slides-per-view-example-5z83y | 18 | | Auto Slides Per View Carousel Mode | https://codesandbox.io/s/auto-slides-per-view-carousel-mode-example-dsgmo | 19 | | Centered Slides | https://codesandbox.io/s/centered-slides-example-h4fmo | 20 | | Centered Slides Auto Slides Per View | https://codesandbox.io/s/centered-slides-auto-slides-per-view-example-o8qn6 | 21 | | Free Mode No Fixed Positions | https://codesandbox.io/s/free-mode-no-fixed-positions-example-43t9f | 22 | | Scroll Container | https://codesandbox.io/s/scroll-container-example-gvix8 | 23 | | Multiple Row Slides Layout | https://codesandbox.io/s/multiple-row-slides-layout-example-du250 | 24 | | Nested Swipers | https://codesandbox.io/s/nested-swipers-example-7xk37 | 25 | | Grab Cursor | https://codesandbox.io/s/grab-cursor-example-o0pwx | 26 | | Loop Mode Infinite Loop | https://codesandbox.io/s/loop-mode-infinite-loop-example-422q1 | 27 | | Loop Mode With Multiple Slides Per Group | https://codesandbox.io/s/loop-mode-with-multiple-slides-per-group-example-0it50 | 28 | | Fade Effect | https://codesandbox.io/s/fade-effect-example-75qts | 29 | | 3D Cube Effect | https://codesandbox.io/s/3d-cube-effect-example-wcxj5 | 30 | | 3D Coverflow Effect | https://codesandbox.io/s/3d-coverflow-effect-example-2jc7w | 31 | | 3D Flip Effect | https://codesandbox.io/s/3d-flip-effect-example-0k301 | 32 | | Mousewheel Control | https://codesandbox.io/s/mousewheel-control-example-evnhy | 33 | | Auto Play | https://codesandbox.io/s/auto-play-example-44urp | 34 | | Thumbs Gallery With Two-way Control | https://codesandbox.io/s/thumbs-gallery-with-two-way-control-example-htrhu | 35 | | RTL Layout | https://codesandbox.io/s/rtl-layout-example-bljf5 | 36 | | Parallax | https://codesandbox.io/s/parallax-example-rmxhe | 37 | | Lazyload Image | https://codesandbox.io/s/lazyload-image-rl1bk | 38 | | Responsive Breakpoints | https://codesandbox.io/s/responsive-breakpoints-example-t2rsy | 39 | | Manipulating component outside Swiper | https://codesandbox.io/s/manipulating-swiper-component-from-outside-example-5gv2r | 40 | | Customized Component | https://codesandbox.io/s/customized-component-example-nksc2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-id-swiper", 3 | "version": "4.0.0", 4 | "description": "ReactJs component for iDangerous Swiper", 5 | "main": "lib/index", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "lib/**/*" 9 | ], 10 | "scripts": { 11 | "build:cleanup": "rimraf lib", 12 | "build:lib": "yarn build:cleanup && tsc", 13 | "build:standalone": "cross-env BABEL_ENV=production webpack", 14 | "build": "yarn build:lib && yarn build:standalone", 15 | "lint:ts": "eslint 'src/**/*.ts?(x)'", 16 | "lint:ts:fix": "eslint --fix 'src/**/*.ts?(x)'", 17 | "test": "jest", 18 | "test:dev": "jest --watch --coverage", 19 | "test:coverage": "jest --coverage", 20 | "prepare": "yarn build", 21 | "preversion": "yarn lint:ts", 22 | "prepublishOnly": "yarn test && yarn lint:ts" 23 | }, 24 | "keywords": [ 25 | "iDangerous", 26 | "Swiper", 27 | "Reactjs" 28 | ], 29 | "engines": { 30 | "node": ">= 6.11.0" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git://github.com/kidjp85/react-id-swiper.git" 35 | }, 36 | "husky": { 37 | "hooks": { 38 | "pre-commit": "lint-staged" 39 | } 40 | }, 41 | "lint-staged": { 42 | "*.{ts,tsx}": [ 43 | "eslint", 44 | "git add" 45 | ] 46 | }, 47 | "bugs": { 48 | "url": "https://github.com/kidjp85/react-id-swiper/issues" 49 | }, 50 | "dependencies": { 51 | "object-assign": "^4.1.1" 52 | }, 53 | "peerDependencies": { 54 | "react": ">=16.8.0", 55 | "react-dom": ">=16.8.0", 56 | "swiper": ">=5.0.0" 57 | }, 58 | "devDependencies": { 59 | "@babel/core": "^7.10.4", 60 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 61 | "@babel/preset-env": "^7.10.4", 62 | "@babel/preset-react": "^7.10.4", 63 | "@types/enzyme": "^3.10.5", 64 | "@types/enzyme-adapter-react-16": "^1.0.6", 65 | "@types/jest": "^26.0.3", 66 | "@types/jsdom": "11.0.4", 67 | "@types/object-assign": "^4.0.30", 68 | "@types/react": "^16.9.41", 69 | "@types/react-dom": "^16.9.8", 70 | "@types/swiper": "^5.4.0", 71 | "@typescript-eslint/eslint-plugin": "^3.5.0", 72 | "@typescript-eslint/parser": "^3.5.0", 73 | "awesome-typescript-loader": "^5.2.1", 74 | "babel-jest": "^26.1.0", 75 | "babel-plugin-dynamic-import-node": "^2.3.3", 76 | "browser-resolve": "^1.11.3", 77 | "cross-env": "^7.0.2", 78 | "enzyme": "^3.11.0", 79 | "enzyme-adapter-react-16": "^1.15.2", 80 | "enzyme-to-json": "^3.5.0", 81 | "eslint": "^7.3.1", 82 | "eslint-config-prettier": "^6.11.0", 83 | "eslint-config-react": "^1.1.7", 84 | "eslint-import-resolver-typescript": "^2.0.0", 85 | "eslint-plugin-import": "^2.22.0", 86 | "eslint-plugin-jest": "^23.17.1", 87 | "eslint-plugin-prettier": "^3.1.4", 88 | "eslint-plugin-react": "^7.20.3", 89 | "eslint-plugin-react-hooks": "^4.0.5", 90 | "husky": "^4.2.5", 91 | "jest": "^26.1.0", 92 | "lint-staged": "^10.2.11", 93 | "prettier": "^2.0.5", 94 | "react": "^16.13.1", 95 | "react-dom": "^16.13.1", 96 | "rimraf": "^3.0.2", 97 | "swiper": "^5.4.5", 98 | "ts-jest": "^26.1.1", 99 | "ts-loader": "^7.0.5", 100 | "typescript": "^3.9.6", 101 | "uglifyjs-webpack-plugin": "^2.2.0", 102 | "webpack": "^4.43.0", 103 | "webpack-cli": "^3.3.12" 104 | }, 105 | "jest": { 106 | "resetMocks": true, 107 | "resetModules": true, 108 | "verbose": true, 109 | "collectCoverage": true, 110 | "browser": true, 111 | "snapshotSerializers": [ 112 | "enzyme-to-json/serializer" 113 | ], 114 | "transform": { 115 | "^.+\\.(js|jsx)$": "babel-jest", 116 | "^.+\\.(ts|tsx)$": "ts-jest" 117 | }, 118 | "moduleFileExtensions": [ 119 | "ts", 120 | "tsx", 121 | "js", 122 | "jsx" 123 | ], 124 | "roots": [ 125 | "/src/" 126 | ], 127 | "moduleDirectories": [ 128 | "node_modules", 129 | "/src" 130 | ], 131 | "transformIgnorePatterns": [ 132 | "node_modules/(?!(swiper|dom7)/)" 133 | ], 134 | "setupFiles": [ 135 | "/src/tests/setup.ts" 136 | ], 137 | "testMatch": [ 138 | "/src/tests/**/*.test.(ts|tsx|js)" 139 | ] 140 | }, 141 | "author": "Phuc Nguyen Hoang", 142 | "license": "MIT" 143 | } 144 | -------------------------------------------------------------------------------- /src/ReactIdSwiper.custom.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | forwardRef, 3 | Children, 4 | useEffect, 5 | useRef, 6 | cloneElement, 7 | isValidElement, 8 | useCallback, 9 | ReactElement 10 | } from 'react'; 11 | import objectAssign from 'object-assign'; 12 | import { ReactIdSwiperCustomProps, SwiperInstance, SwiperRefNode } from './types'; 13 | import { classNames, validateChildren, isReactElement, isModuleAvailable, setRef } from './utils'; 14 | import { useForkRef } from './hooks'; 15 | 16 | const ReactIdSwiperCustom = forwardRef( 17 | (props, externalRef) => { 18 | const { 19 | Swiper, 20 | activeSlideKey, 21 | ContainerEl, 22 | children, 23 | containerClass, 24 | navigation, 25 | noSwiping, 26 | pagination, 27 | parallax, 28 | parallaxEl, 29 | WrapperEl, 30 | wrapperClass, 31 | rebuildOnUpdate, 32 | renderScrollbar, 33 | renderPagination, 34 | renderPrevButton, 35 | renderNextButton, 36 | renderParallax, 37 | rtl, 38 | scrollbar, 39 | shouldSwiperUpdate, 40 | slideClass, 41 | loop, 42 | modules = [] 43 | } = props; 44 | 45 | // Initialize modules to use with swiper 46 | if (Swiper) { 47 | Swiper.use(modules); 48 | } 49 | 50 | // Define swiper instance ref 51 | const swiperInstanceRef = useRef(null); 52 | 53 | // Internal ref 54 | const swiperNodeRef = useRef(null); 55 | 56 | // Forked ref 57 | const ref = useForkRef(swiperNodeRef, externalRef); 58 | 59 | // Get current active slide key 60 | const getActiveSlideIndexFromProps = useCallback(() => { 61 | if (!activeSlideKey) { 62 | return null; 63 | } 64 | 65 | let activeSlideId = 0; 66 | 67 | // In loop mode first slide index should be 1 68 | let id = loop ? 1 : 0; 69 | 70 | Children.forEach(children, child => { 71 | if (isValidElement(child)) { 72 | if (child.key === activeSlideKey) { 73 | activeSlideId = id; 74 | } 75 | 76 | id += 1; 77 | } 78 | }); 79 | 80 | return activeSlideId; 81 | }, [activeSlideKey, children, loop]); 82 | 83 | // Destroy swiper 84 | const destroySwiper = useCallback(() => { 85 | if (swiperInstanceRef.current !== null) { 86 | swiperInstanceRef.current.destroy(true, true); 87 | 88 | setRef(swiperInstanceRef, null); 89 | } 90 | }, []); 91 | 92 | // Initialize swiper 93 | const buildSwiper = useCallback(() => { 94 | if (swiperNodeRef.current && swiperInstanceRef.current === null) { 95 | setRef(swiperInstanceRef, new Swiper(swiperNodeRef.current, objectAssign({}, props))); 96 | } 97 | }, [props]); 98 | 99 | // Render slides 100 | const renderContent = (e: ReactElement) => { 101 | if (!isReactElement(e)) { 102 | return null; 103 | } 104 | 105 | const slideClassNames = [slideClass, e.props.className]; 106 | 107 | if (noSwiping) { 108 | slideClassNames.push('swiper-no-swiping'); 109 | } 110 | 111 | return cloneElement(e, { 112 | ...e.props, 113 | className: slideClassNames.join(' ').trim() 114 | }); 115 | }; 116 | 117 | // Destroy Swiper instance when component is unmounted 118 | useEffect(() => { 119 | return () => destroySwiper(); 120 | }, [destroySwiper]); 121 | 122 | useEffect(() => { 123 | buildSwiper(); 124 | 125 | if (swiperInstanceRef.current !== null) { 126 | if (rebuildOnUpdate) { 127 | destroySwiper(); 128 | 129 | buildSwiper(); 130 | } else if (shouldSwiperUpdate) { 131 | swiperInstanceRef.current.update(); 132 | } 133 | 134 | const numSlides = swiperInstanceRef.current.slides.length; 135 | 136 | if (numSlides <= swiperInstanceRef.current.activeIndex) { 137 | const index = Math.max(numSlides - 1, 0); 138 | 139 | swiperInstanceRef.current.slideTo(index); 140 | } 141 | 142 | const slideToIndex = getActiveSlideIndexFromProps(); 143 | 144 | if (slideToIndex !== null) { 145 | swiperInstanceRef.current.slideTo(slideToIndex); 146 | } 147 | } 148 | }, [ 149 | destroySwiper, 150 | getActiveSlideIndexFromProps, 151 | rebuildOnUpdate, 152 | shouldSwiperUpdate, 153 | buildSwiper 154 | ]); 155 | 156 | // Check modules are loaded before rendering contents 157 | const shouldRenderParallax = isModuleAvailable(modules, 'parallax') && parallax && parallaxEl; 158 | const shouldRenderPagination = 159 | isModuleAvailable(modules, 'pagination') && pagination && pagination.el; 160 | const shouldRenderScrollbar = 161 | isModuleAvailable(modules, 'scrollbar') && scrollbar && scrollbar.el; 162 | const isNavigationModuleAvailable = isModuleAvailable(modules, 'navigation'); 163 | const shouldRenderNextButton = isNavigationModuleAvailable && navigation && navigation.nextEl; 164 | const shouldRenderPrevButton = isNavigationModuleAvailable && navigation && navigation.prevEl; 165 | 166 | // No render if wrapper elements are not provided or when modules is empty 167 | if (!Swiper || !children || !ContainerEl || !WrapperEl) { 168 | return null; 169 | } 170 | 171 | // Validate children props 172 | if (!validateChildren(children)) { 173 | console.warn('Children should be react element or an array of react element!!'); 174 | 175 | return null; 176 | } 177 | 178 | return ( 179 | 180 | {shouldRenderParallax && renderParallax && renderParallax(props)} 181 | {Children.map(children, renderContent)} 182 | {shouldRenderPagination && renderPagination && renderPagination(props)} 183 | {shouldRenderScrollbar && renderScrollbar && renderScrollbar(props)} 184 | {shouldRenderNextButton && renderNextButton && renderNextButton(props)} 185 | {shouldRenderPrevButton && renderPrevButton && renderPrevButton(props)} 186 | 187 | ); 188 | } 189 | ); 190 | 191 | // Default props 192 | ReactIdSwiperCustom.defaultProps = { 193 | containerClass: 'swiper-container', 194 | wrapperClass: 'swiper-wrapper', 195 | slideClass: 'swiper-slide', 196 | ContainerEl: 'div', 197 | WrapperEl: 'div', 198 | renderScrollbar: ({ scrollbar }) => 199 | scrollbar ?
: null, 200 | renderPagination: ({ pagination }) => 201 | pagination ?
: null, 202 | renderPrevButton: ({ navigation }) => 203 | navigation ?
: null, 204 | renderNextButton: ({ navigation }) => 205 | navigation ?
: null, 206 | renderParallax: ({ parallaxEl }) => 207 | parallaxEl ? ( 208 |
209 | ) : null, 210 | modules: [] 211 | }; 212 | 213 | ReactIdSwiperCustom.displayName = 'ReactIdSwiper'; 214 | 215 | export default ReactIdSwiperCustom; 216 | -------------------------------------------------------------------------------- /src/ReactIdSwiper.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | forwardRef, 3 | Children, 4 | useEffect, 5 | useRef, 6 | cloneElement, 7 | isValidElement, 8 | ReactElement, 9 | useCallback 10 | } from 'react'; 11 | import Swiper from 'swiper'; 12 | import objectAssign from 'object-assign'; 13 | import { ReactIdSwiperProps, SwiperInstance, SwiperRefNode } from './types'; 14 | import { classNames, validateChildren, isReactElement, setRef } from './utils'; 15 | import { useForkRef } from './hooks'; 16 | 17 | const ReactIdSwiper = forwardRef((props, externalRef) => { 18 | const { 19 | activeSlideKey, 20 | ContainerEl, 21 | children, 22 | containerClass, 23 | navigation, 24 | noSwiping, 25 | pagination, 26 | parallax, 27 | parallaxEl, 28 | WrapperEl, 29 | wrapperClass, 30 | rebuildOnUpdate, 31 | renderScrollbar, 32 | renderPagination, 33 | renderPrevButton, 34 | renderNextButton, 35 | renderParallax, 36 | rtl, 37 | scrollbar, 38 | shouldSwiperUpdate, 39 | slideClass, 40 | loop 41 | } = props; 42 | 43 | // Define swiper instance ref 44 | const swiperInstanceRef = useRef(null); 45 | 46 | // Internal ref 47 | const swiperNodeRef = useRef(null); 48 | 49 | // Forked ref 50 | const ref = useForkRef(swiperNodeRef, externalRef); 51 | 52 | // Get current active slide key 53 | const getActiveSlideIndexFromProps = useCallback(() => { 54 | if (!activeSlideKey) { 55 | return null; 56 | } 57 | 58 | let activeSlideId = 0; 59 | 60 | // In loop mode first slide index should be 1 61 | let id = loop ? 1 : 0; 62 | 63 | Children.forEach(children, child => { 64 | if (isValidElement(child)) { 65 | if (child.key === activeSlideKey) { 66 | activeSlideId = id; 67 | } 68 | 69 | id += 1; 70 | } 71 | }); 72 | 73 | return activeSlideId; 74 | }, [activeSlideKey, children, loop]); 75 | 76 | // Destroy swiper 77 | const destroySwiper = useCallback(() => { 78 | if (swiperInstanceRef.current !== null) { 79 | swiperInstanceRef.current.destroy(true, true); 80 | 81 | setRef(swiperInstanceRef, null); 82 | } 83 | }, []); 84 | 85 | // Initialize swiper 86 | const buildSwiper = useCallback(() => { 87 | if (swiperNodeRef.current && swiperInstanceRef.current === null) { 88 | setRef(swiperInstanceRef, new Swiper(swiperNodeRef.current, objectAssign({}, props))); 89 | } 90 | }, [props]); 91 | 92 | // Render slides 93 | const renderContent = (e: ReactElement) => { 94 | if (!isReactElement(e)) { 95 | return null; 96 | } 97 | 98 | const slideClassNames = [slideClass, e.props.className]; 99 | 100 | if (noSwiping) { 101 | slideClassNames.push('swiper-no-swiping'); 102 | } 103 | 104 | return cloneElement(e, { 105 | ...e.props, 106 | className: slideClassNames.join(' ').trim() 107 | }); 108 | }; 109 | 110 | // Destroy Swiper instance when component is unmounted 111 | useEffect(() => { 112 | return () => destroySwiper(); 113 | }, [destroySwiper]); 114 | 115 | useEffect(() => { 116 | buildSwiper(); 117 | 118 | if (swiperInstanceRef.current !== null) { 119 | if (rebuildOnUpdate) { 120 | destroySwiper(); 121 | 122 | buildSwiper(); 123 | } else if (shouldSwiperUpdate) { 124 | swiperInstanceRef.current.update(); 125 | } 126 | 127 | const numSlides = swiperInstanceRef.current.slides.length; 128 | 129 | if (numSlides <= swiperInstanceRef.current.activeIndex) { 130 | const index = Math.max(numSlides - 1, 0); 131 | 132 | swiperInstanceRef.current.slideTo(index); 133 | } 134 | 135 | const slideToIndex = getActiveSlideIndexFromProps(); 136 | 137 | if (slideToIndex !== null) { 138 | swiperInstanceRef.current.slideTo(slideToIndex); 139 | } 140 | } 141 | }, [ 142 | destroySwiper, 143 | getActiveSlideIndexFromProps, 144 | rebuildOnUpdate, 145 | shouldSwiperUpdate, 146 | buildSwiper 147 | ]); 148 | 149 | // No render if wrapper elements are not provided 150 | if (!children || !ContainerEl || !WrapperEl) { 151 | return null; 152 | } 153 | 154 | // Validate children props 155 | if (!validateChildren(children)) { 156 | if (process.env.NODE_ENV !== 'production') { 157 | console.warn('Children should be react element or an array of react element!!'); 158 | } 159 | 160 | return null; 161 | } 162 | 163 | return ( 164 | 165 | {parallax && parallaxEl && renderParallax && renderParallax(props)} 166 | {Children.map(children, renderContent)} 167 | {pagination && pagination.el && renderPagination && renderPagination(props)} 168 | {scrollbar && scrollbar.el && renderScrollbar && renderScrollbar(props)} 169 | {navigation && navigation.nextEl && renderNextButton && renderNextButton(props)} 170 | {navigation && navigation.prevEl && renderPrevButton && renderPrevButton(props)} 171 | 172 | ); 173 | }); 174 | 175 | // Default props 176 | ReactIdSwiper.defaultProps = { 177 | containerClass: 'swiper-container', 178 | wrapperClass: 'swiper-wrapper', 179 | slideClass: 'swiper-slide', 180 | ContainerEl: 'div', 181 | WrapperEl: 'div', 182 | renderScrollbar: ({ scrollbar }) => 183 | scrollbar ?
: null, 184 | renderPagination: ({ pagination }) => 185 | pagination ?
: null, 186 | renderPrevButton: ({ navigation }) => 187 | navigation ?
: null, 188 | renderNextButton: ({ navigation }) => 189 | navigation ?
: null, 190 | renderParallax: ({ parallaxEl }) => 191 | parallaxEl ? ( 192 |
193 | ) : null 194 | }; 195 | 196 | ReactIdSwiper.displayName = 'ReactIdSwiper'; 197 | 198 | export default ReactIdSwiper; 199 | -------------------------------------------------------------------------------- /src/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { setRef } from './utils'; 3 | import { UseForkRef } from './types'; 4 | 5 | export const useForkRef: UseForkRef = (refA, refB) => 6 | useMemo(() => { 7 | if (refA == null && refB == null) { 8 | return null; 9 | } 10 | 11 | return refValue => { 12 | setRef(refA, refValue); 13 | 14 | setRef(refB, refValue); 15 | }; 16 | }, [refA, refB]); 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import ReactIdSwiper from './ReactIdSwiper'; 2 | 3 | // Types 4 | export { 5 | ReactIdSwiperProps, 6 | ReactIdSwiperRenderProps, 7 | SelectableElement, 8 | SwiperInstance, 9 | WrappedElementType, 10 | ReactIdSwiperChildren, 11 | SwiperModuleName, 12 | SwiperRefNode 13 | } from './types'; 14 | 15 | // React-id-swiper 16 | export default ReactIdSwiper; 17 | -------------------------------------------------------------------------------- /src/tests/ReactISwiper.custom.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, mount } from 'enzyme'; 3 | import { Pagination, Navigation, Scrollbar, Parallax, Swiper } from 'swiper/js/swiper.esm'; 4 | import ReactIdSwiper from '../ReactIdSwiper.custom'; 5 | import { ReactIdSwiperCustomProps, WrappedElementType } from '../types'; 6 | 7 | const renderSwiper = (params?: ReactIdSwiperCustomProps) => 8 | render( 9 | 10 |
Slide 1
11 |
Slide 2
12 |
13 | ); 14 | 15 | const mountSwiper = (params?: ReactIdSwiperCustomProps) => 16 | mount( 17 | 18 |
Slide 1
19 |
Slide 2
20 |
21 | ); 22 | 23 | describe('ReactIdSwiperCustom', () => { 24 | describe('defaultProps', () => { 25 | const component = mountSwiper({ Swiper }); 26 | 27 | test('should have default props', () => { 28 | expect(component.prop('containerClass')).toEqual('swiper-container'); 29 | expect(component.prop('wrapperClass')).toEqual('swiper-wrapper'); 30 | expect(component.prop('slideClass')).toEqual('swiper-slide'); 31 | expect(component.prop('ContainerEl')).toEqual('div'); 32 | expect(component.prop('WrapperEl')).toEqual('div'); 33 | }); 34 | }); 35 | 36 | describe('rendering', () => { 37 | test('it should not render component with no child', () => { 38 | const wrapper = mount(); 39 | 40 | expect(wrapper.html()).toEqual(null); 41 | }); 42 | 43 | test('it should not render component with invalid children props', () => { 44 | const multipleChildren = mount( 45 | 46 |
Slide 1
47 | {'Slide 2' as any} 48 |
49 | ); 50 | 51 | const singleChildren = mount( 52 | {'Slide 2' as any} 53 | ); 54 | 55 | expect(multipleChildren.html()).toEqual(null); 56 | expect(singleChildren.html()).toEqual(null); 57 | }); 58 | }); 59 | 60 | describe('rendering snapshot', () => { 61 | // With default props 62 | describe('Default', () => { 63 | test('it should render default swiper', () => { 64 | expect(renderSwiper()).toMatchSnapshot(); 65 | }); 66 | }); 67 | 68 | // Render pagination 69 | describe('Pagination', () => { 70 | const params = { 71 | Swiper, 72 | modules: [Pagination], 73 | pagination: { 74 | el: '.swiper-pagination', 75 | clickable: true 76 | } 77 | }; 78 | 79 | test('it should render pagination', () => { 80 | expect(renderSwiper(params)).toMatchSnapshot(); 81 | }); 82 | 83 | test('it should render pagination with customized class name', () => { 84 | const customizedParams = { 85 | Swiper, 86 | pagination: { 87 | el: '.swiper-pagination.customized-swiper-pagination' 88 | }, 89 | containerClass: 'customized-swiper-container' // Replace swiper-container with customized-swiper-container 90 | }; 91 | 92 | expect(renderSwiper(customizedParams)).toMatchSnapshot(); 93 | }); 94 | 95 | test('it should render pagination with customized function', () => { 96 | const customizedParams = { 97 | ...params, 98 | renderCustomPagination: () => 99 | }; 100 | 101 | expect(renderSwiper(customizedParams)).toMatchSnapshot(); 102 | }); 103 | }); 104 | 105 | // Render navigation 106 | describe('Navigation', () => { 107 | const params = { 108 | Swiper, 109 | modules: [Navigation], 110 | navigation: { 111 | nextEl: '.swiper-button-next', 112 | prevEl: '.swiper-button-prev' 113 | } 114 | }; 115 | 116 | test('it should render navigation buttons', () => { 117 | expect(renderSwiper(params)).toMatchSnapshot(); 118 | }); 119 | 120 | test('it should render pagination with customized class name', () => { 121 | const customizedParams = { 122 | ...params, 123 | navigation: { 124 | nextEl: '.swiper-button-next.customized-swiper-button-next', 125 | prevEl: '.swiper-button-prev.customized-swiper-button-prev' 126 | } 127 | }; 128 | 129 | expect(renderSwiper(customizedParams)).toMatchSnapshot(); 130 | }); 131 | 132 | test('it should render pagination with customized function', () => { 133 | const customizedParams = { 134 | ...params, 135 | renderCustomNextButton: () => Customized next button, 136 | renderCustomPrevButton: () => Customized prev button 137 | }; 138 | 139 | expect(renderSwiper(customizedParams)).toMatchSnapshot(); 140 | }); 141 | }); 142 | 143 | describe('Parallax', () => { 144 | const params = { 145 | Swiper, 146 | modules: [Parallax], 147 | parallax: true, 148 | parallaxEl: { 149 | el: '.parallax-bg', 150 | value: '-23%' 151 | } 152 | }; 153 | 154 | test('it should render parallax', () => { 155 | expect(renderSwiper(params)).toMatchSnapshot(); 156 | }); 157 | }); 158 | 159 | describe('Scrollbar', () => { 160 | const params = { 161 | Swiper, 162 | modules: [Scrollbar], 163 | scrollbar: { 164 | el: '.swiper-scrollbar', 165 | hide: true 166 | } 167 | }; 168 | 169 | test('it should render scrollbar', () => { 170 | expect(renderSwiper(params)).toMatchSnapshot(); 171 | }); 172 | }); 173 | 174 | describe('No swiping', () => { 175 | const params = { 176 | Swiper, 177 | noSwiping: true 178 | }; 179 | 180 | test('it should render slide with swiper-no-swiping class name', () => { 181 | expect(renderSwiper(params)).toMatchSnapshot(); 182 | }); 183 | }); 184 | 185 | describe('RTL', () => { 186 | const params = { 187 | Swiper, 188 | rtl: 'rtl' 189 | }; 190 | 191 | test('it should render with rtl', () => { 192 | expect(renderSwiper(params)).toMatchSnapshot(); 193 | }); 194 | }); 195 | 196 | describe('Custom container & wrapper', () => { 197 | interface Params extends ReactIdSwiperCustomProps { 198 | ContainerEl: WrappedElementType; 199 | WrapperEl: WrappedElementType; 200 | } 201 | 202 | const params: Params = { 203 | Swiper, 204 | ContainerEl: 'div', 205 | WrapperEl: 'div' 206 | }; 207 | 208 | test('it should render with custom container & wrapper', () => { 209 | expect(renderSwiper(params)).toMatchSnapshot(); 210 | }); 211 | }); 212 | }); 213 | }); 214 | -------------------------------------------------------------------------------- /src/tests/ReactIdSwiper.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, mount } from 'enzyme'; 3 | import ReactIdSwiper from '../ReactIdSwiper'; 4 | import { ReactIdSwiperProps, WrappedElementType } from '../types'; 5 | 6 | const renderSwiper = (params?: ReactIdSwiperProps) => 7 | render( 8 | 9 |
Slide 1
10 |
Slide 2
11 |
12 | ); 13 | 14 | const mountSwiper = (params?: ReactIdSwiperProps) => 15 | mount( 16 | 17 |
Slide 1
18 |
Slide 2
19 |
20 | ); 21 | 22 | describe('ReactIdSwiper', () => { 23 | describe('defaultProps', () => { 24 | const component = mountSwiper(); 25 | 26 | test('should have default props', () => { 27 | expect(component.prop('containerClass')).toEqual('swiper-container'); 28 | expect(component.prop('wrapperClass')).toEqual('swiper-wrapper'); 29 | expect(component.prop('slideClass')).toEqual('swiper-slide'); 30 | expect(component.prop('ContainerEl')).toEqual('div'); 31 | expect(component.prop('WrapperEl')).toEqual('div'); 32 | }); 33 | }); 34 | 35 | describe('rendering', () => { 36 | test('it should not render component with no child', () => { 37 | const wrapper = mount(); 38 | 39 | expect(wrapper.html()).toEqual(null); 40 | }); 41 | 42 | test('it should not render component with invalid children props', () => { 43 | const multipleChildren = mount( 44 | 45 |
Slide 1
46 | {'Slide 2' as any} 47 |
48 | ); 49 | 50 | const singleChildren = mount({'Slide 2' as any}); 51 | 52 | expect(multipleChildren.html()).toEqual(null); 53 | expect(singleChildren.html()).toEqual(null); 54 | }); 55 | }); 56 | 57 | describe('rendering snapshot', () => { 58 | // With default props 59 | describe('Default', () => { 60 | test('it should render default swiper', () => { 61 | expect(renderSwiper()).toMatchSnapshot(); 62 | }); 63 | }); 64 | 65 | // Render pagination 66 | describe('Pagination', () => { 67 | const params = { 68 | pagination: { 69 | el: '.swiper-pagination', 70 | clickable: true 71 | } 72 | }; 73 | 74 | test('it should render pagination', () => { 75 | expect(renderSwiper(params)).toMatchSnapshot(); 76 | }); 77 | 78 | test('it should render pagination with customized class name', () => { 79 | const customizedParams = { 80 | pagination: { 81 | el: '.swiper-pagination.customized-swiper-pagination' 82 | }, 83 | containerClass: 'customized-swiper-container' // Replace swiper-container with customized-swiper-container 84 | }; 85 | 86 | expect(renderSwiper(customizedParams)).toMatchSnapshot(); 87 | }); 88 | 89 | test('it should render pagination with customized function', () => { 90 | const customizedParams = { 91 | ...params, 92 | renderCustomPagination: () => 93 | }; 94 | 95 | expect(renderSwiper(customizedParams)).toMatchSnapshot(); 96 | }); 97 | }); 98 | 99 | // Render navigation 100 | describe('Navigation', () => { 101 | const params = { 102 | navigation: { 103 | nextEl: '.swiper-button-next', 104 | prevEl: '.swiper-button-prev' 105 | } 106 | }; 107 | 108 | test('it should render navigation buttons', () => { 109 | expect(renderSwiper(params)).toMatchSnapshot(); 110 | }); 111 | 112 | test('it should render pagination with customized class name', () => { 113 | const customizedParams = { 114 | ...params, 115 | navigation: { 116 | nextEl: '.swiper-button-next.customized-swiper-button-next', 117 | prevEl: '.swiper-button-prev.customized-swiper-button-prev' 118 | } 119 | }; 120 | 121 | expect(renderSwiper(customizedParams)).toMatchSnapshot(); 122 | }); 123 | 124 | test('it should render pagination with customized function', () => { 125 | const customizedParams = { 126 | ...params, 127 | renderCustomNextButton: () => Customized next button, 128 | renderCustomPrevButton: () => Customized prev button 129 | }; 130 | 131 | expect(renderSwiper(customizedParams)).toMatchSnapshot(); 132 | }); 133 | }); 134 | 135 | describe('Parallax', () => { 136 | const params = { 137 | parallax: true, 138 | parallaxEl: { 139 | el: '.parallax-bg', 140 | value: '-23%' 141 | } 142 | }; 143 | 144 | test('it should render parallax', () => { 145 | expect(renderSwiper(params)).toMatchSnapshot(); 146 | }); 147 | }); 148 | 149 | describe('Scrollbar', () => { 150 | const params = { 151 | scrollbar: { 152 | el: '.swiper-scrollbar', 153 | hide: true 154 | } 155 | }; 156 | 157 | test('it should render scrollbar', () => { 158 | expect(renderSwiper(params)).toMatchSnapshot(); 159 | }); 160 | }); 161 | 162 | describe('No swiping', () => { 163 | const params = { 164 | noSwiping: true 165 | }; 166 | 167 | test('it should render slide with swiper-no-swiping class name', () => { 168 | expect(renderSwiper(params)).toMatchSnapshot(); 169 | }); 170 | }); 171 | 172 | describe('RTL', () => { 173 | const params = { 174 | rtl: 'rtl' 175 | }; 176 | 177 | test('it should render with rtl', () => { 178 | expect(renderSwiper(params)).toMatchSnapshot(); 179 | }); 180 | }); 181 | 182 | describe('Custom container & wrapper', () => { 183 | interface Params { 184 | ContainerEl: WrappedElementType; 185 | WrapperEl: WrappedElementType; 186 | } 187 | 188 | const params: Params = { 189 | ContainerEl: 'div', 190 | WrapperEl: 'div' 191 | }; 192 | 193 | test('it should render with custom container & wrapper', () => { 194 | expect(renderSwiper(params)).toMatchSnapshot(); 195 | }); 196 | }); 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /src/tests/__snapshots__/ReactISwiper.custom.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ReactIdSwiperCustom rendering snapshot Custom container & wrapper it should render with custom container & wrapper 1`] = ` 4 |
7 |
10 |
13 | Slide 1 14 |
15 |
18 | Slide 2 19 |
20 |
21 |
22 | `; 23 | 24 | exports[`ReactIdSwiperCustom rendering snapshot Default it should render default swiper 1`] = ` 25 |
28 |
31 |
34 | Slide 1 35 |
36 |
39 | Slide 2 40 |
41 |
42 |
43 | `; 44 | 45 | exports[`ReactIdSwiperCustom rendering snapshot Navigation it should render navigation buttons 1`] = ` 46 |
49 |
52 |
55 | Slide 1 56 |
57 |
60 | Slide 2 61 |
62 |
63 |
66 |
69 |
70 | `; 71 | 72 | exports[`ReactIdSwiperCustom rendering snapshot Navigation it should render pagination with customized class name 1`] = ` 73 |
76 |
79 |
82 | Slide 1 83 |
84 |
87 | Slide 2 88 |
89 |
90 |
93 |
96 |
97 | `; 98 | 99 | exports[`ReactIdSwiperCustom rendering snapshot Navigation it should render pagination with customized function 1`] = ` 100 |
103 |
106 |
109 | Slide 1 110 |
111 |
114 | Slide 2 115 |
116 |
117 |
120 |
123 |
124 | `; 125 | 126 | exports[`ReactIdSwiperCustom rendering snapshot No swiping it should render slide with swiper-no-swiping class name 1`] = ` 127 |
130 |
133 |
136 | Slide 1 137 |
138 |
141 | Slide 2 142 |
143 |
144 |
145 | `; 146 | 147 | exports[`ReactIdSwiperCustom rendering snapshot Pagination it should render pagination 1`] = ` 148 |
151 |
154 |
157 | Slide 1 158 |
159 |
162 | Slide 2 163 |
164 |
165 |
168 |
169 | `; 170 | 171 | exports[`ReactIdSwiperCustom rendering snapshot Pagination it should render pagination with customized class name 1`] = ` 172 |
175 |
178 |
181 | Slide 1 182 |
183 |
186 | Slide 2 187 |
188 |
189 |
190 | `; 191 | 192 | exports[`ReactIdSwiperCustom rendering snapshot Pagination it should render pagination with customized function 1`] = ` 193 |
196 |
199 |
202 | Slide 1 203 |
204 |
207 | Slide 2 208 |
209 |
210 |
213 |
214 | `; 215 | 216 | exports[`ReactIdSwiperCustom rendering snapshot Parallax it should render parallax 1`] = ` 217 |
220 |
224 |
227 |
230 | Slide 1 231 |
232 |
235 | Slide 2 236 |
237 |
238 |
239 | `; 240 | 241 | exports[`ReactIdSwiperCustom rendering snapshot RTL it should render with rtl 1`] = ` 242 |
246 |
249 |
252 | Slide 1 253 |
254 |
257 | Slide 2 258 |
259 |
260 |
261 | `; 262 | 263 | exports[`ReactIdSwiperCustom rendering snapshot Scrollbar it should render scrollbar 1`] = ` 264 |
267 |
270 |
273 | Slide 1 274 |
275 |
278 | Slide 2 279 |
280 |
281 |
284 |
285 | `; 286 | -------------------------------------------------------------------------------- /src/tests/__snapshots__/ReactIdSwiper.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ReactIdSwiper rendering snapshot Custom container & wrapper it should render with custom container & wrapper 1`] = ` 4 |
7 |
10 |
13 | Slide 1 14 |
15 |
18 | Slide 2 19 |
20 |
21 |
22 | `; 23 | 24 | exports[`ReactIdSwiper rendering snapshot Default it should render default swiper 1`] = ` 25 |
28 |
31 |
34 | Slide 1 35 |
36 |
39 | Slide 2 40 |
41 |
42 |
43 | `; 44 | 45 | exports[`ReactIdSwiper rendering snapshot Navigation it should render navigation buttons 1`] = ` 46 |
49 |
52 |
55 | Slide 1 56 |
57 |
60 | Slide 2 61 |
62 |
63 |
66 |
69 |
70 | `; 71 | 72 | exports[`ReactIdSwiper rendering snapshot Navigation it should render pagination with customized class name 1`] = ` 73 |
76 |
79 |
82 | Slide 1 83 |
84 |
87 | Slide 2 88 |
89 |
90 |
93 |
96 |
97 | `; 98 | 99 | exports[`ReactIdSwiper rendering snapshot Navigation it should render pagination with customized function 1`] = ` 100 |
103 |
106 |
109 | Slide 1 110 |
111 |
114 | Slide 2 115 |
116 |
117 |
120 |
123 |
124 | `; 125 | 126 | exports[`ReactIdSwiper rendering snapshot No swiping it should render slide with swiper-no-swiping class name 1`] = ` 127 |
130 |
133 |
136 | Slide 1 137 |
138 |
141 | Slide 2 142 |
143 |
144 |
145 | `; 146 | 147 | exports[`ReactIdSwiper rendering snapshot Pagination it should render pagination 1`] = ` 148 |
151 |
154 |
157 | Slide 1 158 |
159 |
162 | Slide 2 163 |
164 |
165 |
168 |
169 | `; 170 | 171 | exports[`ReactIdSwiper rendering snapshot Pagination it should render pagination with customized class name 1`] = ` 172 |
175 |
178 |
181 | Slide 1 182 |
183 |
186 | Slide 2 187 |
188 |
189 |
192 |
193 | `; 194 | 195 | exports[`ReactIdSwiper rendering snapshot Pagination it should render pagination with customized function 1`] = ` 196 |
199 |
202 |
205 | Slide 1 206 |
207 |
210 | Slide 2 211 |
212 |
213 |
216 |
217 | `; 218 | 219 | exports[`ReactIdSwiper rendering snapshot Parallax it should render parallax 1`] = ` 220 |
223 |
227 |
230 |
233 | Slide 1 234 |
235 |
238 | Slide 2 239 |
240 |
241 |
242 | `; 243 | 244 | exports[`ReactIdSwiper rendering snapshot RTL it should render with rtl 1`] = ` 245 |
249 |
252 |
255 | Slide 1 256 |
257 |
260 | Slide 2 261 |
262 |
263 |
264 | `; 265 | 266 | exports[`ReactIdSwiper rendering snapshot Scrollbar it should render scrollbar 1`] = ` 267 |
270 |
273 |
276 | Slide 1 277 |
278 |
281 | Slide 2 282 |
283 |
284 |
287 |
288 | `; 289 | -------------------------------------------------------------------------------- /src/tests/setup.ts: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /src/tests/utils.test.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react'; 2 | import { Pagination, Navigation, Scrollbar, Parallax } from 'swiper/js/swiper.esm'; 3 | import { mount } from 'enzyme'; 4 | import { classNames, validateChildren, isReactElement, isModuleAvailable } from '../utils'; 5 | 6 | describe('utils', () => { 7 | describe('classnames', () => { 8 | test('it should return empty string when input is undefined', () => { 9 | expect(classNames(undefined)).toEqual(''); 10 | }); 11 | 12 | test('it should return class string when input is string', () => { 13 | expect(classNames('.slide-container')).toEqual('slide-container'); 14 | }); 15 | 16 | test('it should return class string when input is html element', () => { 17 | const el = document.createElement('div'); 18 | el.className = 'slide-container'; 19 | 20 | expect(classNames(el)).toEqual('slide-container'); 21 | }); 22 | }); 23 | 24 | describe('validateChildren', () => { 25 | describe('when has more than 1 children prop', () => { 26 | test('it should return true if children props are valid', () => { 27 | const wrapper = mount( 28 |
29 |
Slide 1
30 |
Slide 2
31 |
32 | ); 33 | 34 | expect(validateChildren(wrapper.props().children)).toEqual(true); 35 | }); 36 | 37 | test('it should return false if children props are invalid', () => { 38 | const wrapper = mount( 39 |
40 |
Slide 1
41 | Slide 2 42 |
43 | ); 44 | 45 | expect(validateChildren(wrapper.props().children)).toEqual(false); 46 | }); 47 | }); 48 | 49 | describe('when has only 1 child prop', () => { 50 | test('it should return true if children props are valid', () => { 51 | const wrapper = mount( 52 |
53 |
Slide 1
54 |
55 | ); 56 | 57 | expect(validateChildren(wrapper.props().children)).toEqual(true); 58 | }); 59 | 60 | test('it should return false if children props are invalid', () => { 61 | const wrapper = mount(
Slide 2
); 62 | 63 | expect(validateChildren(wrapper.props().children)).toEqual(false); 64 | }); 65 | }); 66 | }); 67 | 68 | describe('isReactElement', () => { 69 | test('it should return true when element is react element', () => { 70 | const DOMTypeElement =
Hello world
; 71 | const CompositeTypeElement: FunctionComponent = () =>
Hello world
; 72 | 73 | expect(isReactElement(DOMTypeElement)).toEqual(true); 74 | expect(isReactElement()).toEqual(true); 75 | }); 76 | }); 77 | 78 | describe('isModuleAvailable', () => { 79 | const swiperModules = [Pagination, Navigation, Scrollbar, Parallax]; 80 | 81 | test('it should return true when module is available', () => { 82 | expect(isModuleAvailable(swiperModules, 'pagination')).toEqual(true); 83 | }); 84 | 85 | test('it should return false when module is unavailable', () => { 86 | expect(isModuleAvailable(swiperModules, 'virtual')).toEqual(false); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactElement, MutableRefObject } from 'react'; 2 | import Swiper, { 3 | SwiperOptions, 4 | SelectableElement as SwiperSelectableElement, 5 | SwiperModule 6 | } from 'swiper'; 7 | 8 | type Maybe = T | null; 9 | 10 | export type ReactIdSwiperRenderProps = (props: ReactIdSwiperProps) => Maybe; 11 | 12 | export type WrappedElementType = 'div' | 'section' | 'span'; 13 | 14 | export type ReactIdSwiperChildren = ReactElement | ReactElement[]; 15 | 16 | export type SwiperModules = (SwiperModule & { name: string })[]; 17 | 18 | export interface ReactIdSwiperProps extends SwiperOptions { 19 | ContainerEl?: WrappedElementType; 20 | WrapperEl?: WrappedElementType; 21 | containerClass?: string; 22 | wrapperClass?: string; 23 | slideClass?: string; 24 | rebuildOnUpdate?: boolean; 25 | shouldSwiperUpdate?: boolean; 26 | activeSlideKey?: string; 27 | renderScrollbar?: ReactIdSwiperRenderProps; 28 | renderPagination?: ReactIdSwiperRenderProps; 29 | renderPrevButton?: ReactIdSwiperRenderProps; 30 | renderNextButton?: ReactIdSwiperRenderProps; 31 | renderParallax?: ReactIdSwiperRenderProps; 32 | rtl?: string; 33 | children?: ReactIdSwiperChildren; 34 | parallaxEl?: { 35 | el: string; 36 | value: string; 37 | }; 38 | } 39 | 40 | export interface ReactIdSwiperCustomProps extends ReactIdSwiperProps { 41 | modules?: SwiperModules; 42 | Swiper: typeof Swiper; 43 | } 44 | 45 | export interface SwiperRefNode extends HTMLDivElement { 46 | swiper?: SwiperInstance; 47 | } 48 | 49 | export type SelectableElement = SwiperSelectableElement | undefined; 50 | 51 | export type SwiperInstance = Maybe; 52 | 53 | export type RefType = Maybe<((instance: Maybe) => void) | MutableRefObject>>; 54 | 55 | export type SetRef = (ref: RefType, value: V) => void; 56 | 57 | export type UseForkRef = (refA: RefType, refB: RefType) => Maybe<(v: T) => void>; 58 | 59 | export type SwiperModuleName = 60 | | 'navigation' 61 | | 'pagination' 62 | | 'scrollbar' 63 | | 'autoplay' 64 | | 'parallax' 65 | | 'lazy' 66 | | 'effect-fade' 67 | | 'effect-coverflow' 68 | | 'effect-flip' 69 | | 'effect-cube' 70 | | 'zoom' 71 | | 'keyboard' 72 | | 'mousewheel' 73 | | 'virtual' 74 | | 'hash-navigation' 75 | | 'history' 76 | | 'controller' 77 | | 'a11y'; 78 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { isValidElement, Children, ReactElement } from 'react'; 2 | import { 3 | SelectableElement, 4 | SwiperModules, 5 | SwiperModuleName, 6 | ReactIdSwiperChildren, 7 | SetRef 8 | } from './types'; 9 | 10 | export const classNames = (el: SelectableElement): string => { 11 | if (typeof el === 'string') { 12 | return el.split('.').join(' ').trim(); 13 | } else if (el instanceof HTMLElement) { 14 | return el.className; 15 | } 16 | 17 | return ''; 18 | }; 19 | 20 | export const validateChildren = (children: ReactIdSwiperChildren): boolean => { 21 | let isValid = true; 22 | 23 | if (Array.isArray(children)) { 24 | Children.forEach(children, (child: string | number | ReactElement) => { 25 | if (!isValidElement(child)) { 26 | isValid = false; 27 | } 28 | }); 29 | } else { 30 | isValid = isValidElement(children); 31 | } 32 | 33 | return isValid; 34 | }; 35 | 36 | export const isReactElement = (element: ReactElement): boolean => 37 | isValidElement(element) && 38 | (typeof element.type === 'string' || 39 | typeof element.type === 'function' || 40 | typeof element.type === 'object'); 41 | 42 | export const isModuleAvailable = ( 43 | modules: SwiperModules, 44 | moduleName: SwiperModuleName 45 | ): boolean => { 46 | let moduleAvailable = false; 47 | 48 | for (let i = 0; i < modules.length; i++) { 49 | if (modules[i].name === moduleName) { 50 | moduleAvailable = true; 51 | break; 52 | } 53 | } 54 | 55 | return moduleAvailable; 56 | }; 57 | 58 | export const setRef: SetRef = (ref, value) => { 59 | if (typeof ref === 'function') { 60 | ref(value); 61 | } else if (ref) { 62 | ref.current = value; 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "resolveJsonModule": true, 5 | "emitDecoratorMetadata":true, 6 | "experimentalDecorators":true, 7 | "lib": [ 8 | "es6", 9 | "dom", 10 | "es2015" 11 | ], 12 | "target": "es5", 13 | "module": "commonjs", 14 | "declaration": true, 15 | "outDir": "./lib", 16 | "strict": true, 17 | "jsx":"react", 18 | "moduleResolution":"node", 19 | "allowSyntheticDefaultImports":true, 20 | "noImplicitAny":true, 21 | "noImplicitThis":true, 22 | "noImplicitReturns":true, 23 | "strictNullChecks":true, 24 | "noUnusedLocals": true, 25 | "noUnusedParameters": true 26 | }, 27 | "include": ["src"], 28 | "exclude": ["node_modules", "**/tests/*"] 29 | } -------------------------------------------------------------------------------- /tsconfig.standalone.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false 5 | } 6 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const webpack = require('webpack'); 3 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 4 | 5 | const files = [ 6 | { 7 | outputName: 'react-id-swiper', 8 | entryName: 'ReactIdSwiper' 9 | }, 10 | { 11 | outputName: 'react-id-swiper.min', 12 | entryName: 'ReactIdSwiper', 13 | minimizer: true 14 | } 15 | ]; 16 | 17 | const PATHS = { 18 | src: resolve(__dirname, 'src'), 19 | output: resolve(__dirname, 'lib') 20 | }; 21 | 22 | module.exports = files.map(({ entryName, outputName, minimizer }) => ({ 23 | entry: `${PATHS.src}/${entryName}.tsx`, 24 | output: { 25 | path: PATHS.output, 26 | filename: `${outputName}.js`, 27 | libraryTarget: 'umd', 28 | library: 'ReactIdSwiper', 29 | auxiliaryComment: '' 30 | }, 31 | resolve: { 32 | extensions: ['.tsx', '.ts', '.js'], 33 | modules: ['./src', 'node_modules'] 34 | }, 35 | resolveLoader: { 36 | moduleExtensions: ['-loader'] 37 | }, 38 | module: { 39 | rules: [ 40 | { 41 | test: /\.ts(x)?$/, 42 | loader: 'awesome-typescript', 43 | include: [PATHS.src], 44 | options: { 45 | configFileName: 'tsconfig.standalone.json' 46 | } 47 | } 48 | ] 49 | }, 50 | externals: { 51 | react: 'React', 52 | swiper: 'Swiper' 53 | }, 54 | mode: 'production', 55 | optimization: { 56 | minimizer: minimizer 57 | ? [ 58 | new UglifyJsPlugin({ 59 | uglifyOptions: { 60 | output: { 61 | comments: false 62 | } 63 | } 64 | }) 65 | ] 66 | : [] 67 | }, 68 | plugins: [ 69 | new webpack.DefinePlugin({ 70 | 'process.env': { 71 | NODE_ENV: JSON.stringify('production') 72 | } 73 | }) 74 | ] 75 | })); 76 | --------------------------------------------------------------------------------