├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── docs └── demo.gif ├── examples ├── bower-brunch │ ├── app │ │ ├── assets │ │ │ └── index.html │ │ ├── scripts │ │ │ ├── components │ │ │ │ ├── App.js │ │ │ │ └── Gist.js │ │ │ └── index.js │ │ └── styles │ │ │ └── main.css │ ├── bower.json │ ├── brunch-config.coffee │ └── package.json └── npm-webpack │ ├── index.html │ ├── package.json │ ├── server.js │ ├── src │ ├── components │ │ ├── App.js │ │ └── Gist.js │ ├── index.js │ └── main.css │ └── webpack.config.js ├── lib ├── SwipeViews.js ├── react-swipe-views.css ├── react-swipe-views.js ├── react-swipe-views.min.css └── react-swipe-views.min.js ├── package.json └── src ├── SwipeViews.css └── SwipeViews.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "ecmaFeatures": { 4 | "jsx": true, 5 | "modules": true 6 | }, 7 | "env": { 8 | "browser": true, 9 | "node": true, 10 | "es6": true 11 | }, 12 | "parser": "babel-eslint", 13 | "extends": "defaults/configurations/airbnb", 14 | "plugins": [ 15 | "react" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project generated files # 2 | ########################### 3 | public 4 | 5 | # NPM generated files # 6 | ####################### 7 | logs 8 | *.log 9 | node_modules 10 | bower_components 11 | 12 | # OS generated files # 13 | ###################### 14 | .DS_Store 15 | .DS_Store? 16 | ._* 17 | .Spotlight-V100 18 | .Trashes 19 | Icon? 20 | ehthumbs.db 21 | Thumbs.db 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dan Abramov 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 | >## WARNING DEPRECATION NOTICE 2 | 3 | >This library is not maintained anymore. Do not use it. 4 | 5 | >It has not been updated since React 0.14 and breaks with versions of React Router higher than 0.13. 6 | 7 | >Check out [react-swipeable-views](https://github.com/oliviertassinari/react-swipeable-views) instead. 8 | 9 | # React Swipe Views [![deprecated](https://img.shields.io/badge/status-deprecated-red.svg)](http://shields.io/) 10 | 11 | A React component for binded Tabs and Swipeable Views 12 | 13 | See [Swipe Views](http://developer.android.com/design/patterns/swipe-views.html) on the Android Design Patterns website for inspiration. 14 | 15 | Works with optional [React Router](https://github.com/rackt/react-router) (v0.13.x only), as a pure component or as a standalone component. 16 | 17 | ## Demo 18 | 19 | [Live example](http://damusnet.github.io/react-swipe-views/) 20 | 21 | ![Demo](docs/demo.gif) 22 | 23 | ## Quick Example 24 | 25 | ```jsx 26 | 'use strict'; 27 | 28 | import React from 'react'; 29 | import SwipeViews from 'react-swipe-views'; 30 | 31 | export default class App extends React.Component { 32 | render() { 33 | return ( 34 | 35 |
36 | Page 1 37 |
38 |
39 | Page 2 40 |
41 |
42 | Page 3 43 |
44 |
45 | ); 46 | } 47 | } 48 | ``` 49 | 50 | ## Install 51 | 52 | This component is available as an npm module or a bower component: 53 | 54 | ``` 55 | npm install react-swipe-views --save 56 | ``` 57 | 58 | or 59 | 60 | ``` 61 | bower install react-swipe-views --save 62 | ``` 63 | 64 | ## Examples 65 | 66 | There are two example projects in the [/examples](examples) folder. One is using the npm module and webpack as a build tool with react-router, the other uses bower and brunch as a pure component. 67 | 68 | ## TODO List 69 | 70 | - Allow for content to be inserted in the header 71 | - Write tests 72 | 73 | ## Thanks 74 | 75 | - [David Bruant](https://twitter.com/davidbruant) for making me believe in JavaScript 76 | - [React](http://facebook.github.io/react/) for being awesome 77 | - [Babel](http://babeljs.io/) for removing so much pain from transpiling/compiling/bundling 78 | - [Dan Abramov](https://github.com/gaearon) for all the useful ressources, in this case [React Hot Boilerplate](https://github.com/gaearon/react-hot-boilerplate) 79 | - [Hassan Hayat](https://github.com/TheSeamau5)'s [Swipe Pages WebComponent](https://github.com/TheSeamau5/swipe-pages) for inspiration 80 | - [Ferran Negre](https://github.com/ferrannp) for helping me debug 81 | 82 | ## License 83 | 84 | MIT -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-swipe-views", 3 | "version": "0.0.12", 4 | "description": "React Component for binded Tabs and Swipeable Views", 5 | "main": [ 6 | "./lib/react-swipe-views.js", 7 | "./lib/react-swipe-views.css" 8 | ], 9 | "moduleType": "umd", 10 | "dependencies": { 11 | "react": "~0.13.1" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "reactjs", 16 | "swipe", 17 | "views", 18 | "tabs" 19 | ], 20 | "author": "Damien Varron (https://github.com/damusnet)", 21 | "license": "MIT", 22 | "homepage": "https://github.com/damusnet/react-swipe-views", 23 | "ignore": [ 24 | "**/.*", 25 | "node_modules", 26 | "examples" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damusnet/react-swipe-views/bfcd5084bf84eadf277cc5423441b968079c82dc/docs/demo.gif -------------------------------------------------------------------------------- /examples/bower-brunch/app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Swipe Views - Bower/Brunch Example 7 | 8 | 9 | 10 | 11 | 12 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/bower-brunch/app/scripts/components/App.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Gist from './Gist'; 4 | 5 | export default class App extends React.Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | index: 0 11 | }; 12 | } 13 | 14 | _onIndexChange(selectedIndex) { 15 | this.setState({ 16 | index: selectedIndex 17 | }); 18 | } 19 | 20 | render() { 21 | return ( 22 | 23 |
24 |

A React component for binded Tabs and Swipeable Views

25 |

See 26 | Swipe Views on the Android Design Patterns website to understand the desired effect.

27 |

This demo is best viewed on a touch enabled device (real or emulated).

28 |

29 |
30 |
31 | 32 |
33 |
34 | 42 |

{ 43 | e.preventDefault(); 44 | this.setState({ 45 | index: 0 46 | }); 47 | }}>

48 |
49 |
50 | ); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /examples/bower-brunch/app/scripts/components/Gist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function loadGist(element, gistId) { 4 | var callbackName = 'gist_callback'; 5 | window[callbackName] = function (gistData) { 6 | delete window[callbackName]; 7 | var html = ''; 8 | html += gistData.div; 9 | element.innerHTML = html; 10 | }; 11 | var script = document.createElement('script'); 12 | script.setAttribute('src', 'https://gist.github.com/' + gistId + '.json?callback=' + callbackName); 13 | document.body.appendChild(script); 14 | } 15 | 16 | export default class Gist extends React.Component { 17 | 18 | componentDidMount() { 19 | var element = React.findDOMNode(this.refs.gist); 20 | loadGist(element, 'e9a28f91a3e4e4e6d9d3'); 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 | Online Gist Example 27 |
28 | ); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /examples/bower-brunch/app/scripts/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import App from './components/App'; 4 | 5 | React.initializeTouchEvents(true); 6 | 7 | React.render( 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /examples/bower-brunch/app/styles/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | overflow: hidden; 6 | font-family: Roboto, sans-serif; 7 | } 8 | 9 | section { 10 | height: 100%; 11 | } 12 | 13 | .SwipeViewsInk { 14 | background-color: #FF6950; 15 | } 16 | 17 | .SwipeViewsTabs .active, h1 { 18 | color: #FF6950; 19 | font-weight: 500; 20 | } 21 | 22 | .SwipeView { 23 | padding: 10px; 24 | } 25 | 26 | .SwipeViewsTab, .SwipeViewsTab a { 27 | font-size: 13px; 28 | color: #CCCCCC; 29 | } 30 | 31 | a { 32 | color: #FF6950; 33 | text-decoration: none; 34 | } 35 | 36 | #thanks li { 37 | margin-bottom: 2em; 38 | } 39 | 40 | button { 41 | margin-top: 20px; 42 | color: white; 43 | background-color: #FF6950; 44 | border: none; 45 | border-radius: 5px; 46 | width: 100px; 47 | height: 50px; 48 | } -------------------------------------------------------------------------------- /examples/bower-brunch/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-swipe-views-bower-brunch-example", 3 | "version": "0.0.12", 4 | "moduleType": [ 5 | "globals" 6 | ], 7 | "author": "Damien Varron (http://github.com/damusnet)", 8 | "license": "MIT", 9 | "homepage": "https://github.com/damusnet/react-swipe-views", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "tests" 16 | ], 17 | "dependencies": { 18 | "react": "~0.14.0", 19 | "react-swipe-views": "~0.0.12", 20 | "babel-polyfill": "~0.0.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/bower-brunch/brunch-config.coffee: -------------------------------------------------------------------------------- 1 | module.exports = config: 2 | files: 3 | javascripts: joinTo: 'js/bundle.js' 4 | stylesheets: joinTo: 'css/bundle.css' 5 | server: 6 | run: yes 7 | -------------------------------------------------------------------------------- /examples/bower-brunch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-swipe-views-bower-brunch-example", 3 | "version": "0.0.12", 4 | "description": "An implementation example of react-swipe-views built with Bower and Brunch", 5 | "main": "app/scripts/main.js", 6 | "scripts": { 7 | "install": "bower install", 8 | "start": "npm install && brunch watch", 9 | "build": "brunch build" 10 | }, 11 | "devDependencies": { 12 | "babel-brunch": "^4.0.0", 13 | "bower": "^1.5.3", 14 | "browser-sync-brunch": "0.0.9", 15 | "brunch": "^1.7.20", 16 | "css-brunch": "^1.7.0", 17 | "javascript-brunch": "^1.7.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/npm-webpack/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Swipe Views - NPM/Webpack Example 7 | 8 | 9 | 10 | 11 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/npm-webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-swipe-views-npm-webpack-example", 3 | "version": "0.0.12", 4 | "description": "An implementation example of react-swipe-views built with NPM and Webpack", 5 | "scripts": { 6 | "start": "npm install && node server.js" 7 | }, 8 | "devDependencies": { 9 | "babel-core": "^4.7.4", 10 | "babel-loader": "^4.1.0", 11 | "react-hot-loader": "^1.2.2", 12 | "webpack": "^1.7.2", 13 | "webpack-dev-server": "^1.7.0" 14 | }, 15 | "dependencies": { 16 | "react": "^0.13.0", 17 | "react-router": "^0.13.2", 18 | "react-swipe-views": "~0.0.12", 19 | "style-loader": "~0.9.0", 20 | "css-loader": "~0.9.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/npm-webpack/server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | historyApiFallback: true 9 | }).listen(3000, '0.0.0.0', function (err, result) { 10 | if (err) { 11 | console.log(err); 12 | } 13 | 14 | console.log('Listening at localhost:3000'); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/npm-webpack/src/components/App.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Link } from 'react-router'; 5 | import SwipeViews from 'react-swipe-views'; 6 | import Gist from './Gist.js'; 7 | 8 | export default class App extends React.Component { 9 | 10 | render() { 11 | return ( 12 | 13 |
Intro}> 14 |

A React component for binded Tabs and Swipeable Views

15 |

See 16 | Swipe Views on the Android Design Patterns website to understand the desired effect.

17 |

This demo is best viewed on a touch enabled device (real or emulated).

18 |

19 |
20 |
Code}> 21 | 22 |
23 |
Thanks}> 24 | 32 |

33 |
34 |
35 | ); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /examples/npm-webpack/src/components/Gist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | 5 | function loadGist(element, gistId) { 6 | var callbackName = 'gist_callback'; 7 | window[callbackName] = function (gistData) { 8 | delete window[callbackName]; 9 | var html = ''; 10 | html += gistData.div; 11 | element.innerHTML = html; 12 | }; 13 | var script = document.createElement('script'); 14 | script.setAttribute('src', 'https://gist.github.com/' + gistId + '.json?callback=' + callbackName); 15 | document.body.appendChild(script); 16 | } 17 | 18 | export default class Gist extends React.Component { 19 | 20 | componentDidMount() { 21 | var element = React.findDOMNode(this.refs.gist); 22 | loadGist(element, 'e9a28f91a3e4e4e6d9d3'); 23 | } 24 | 25 | render() { 26 | return ( 27 |
28 | Online Gist Example 29 |
30 | ); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /examples/npm-webpack/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { default as Router, Route, DefaultRoute, Redirect } from 'react-router'; 5 | import App from './components/App'; 6 | 7 | require('../../../lib/react-swipe-views.css'); 8 | require('./main.css'); 9 | require('babel-core/polyfill'); 10 | 11 | React.initializeTouchEvents(true); 12 | 13 | const routes = ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | Router.run(routes, Router.HistoryLocation, (Handler) => { 23 | React.render( 24 | , 25 | document.getElementById('root') 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /examples/npm-webpack/src/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | overflow: hidden; 6 | font-family: Roboto, sans-serif; 7 | } 8 | 9 | section { 10 | height: 100%; 11 | } 12 | 13 | .SwipeViewsInk { 14 | background-color: #13747d; 15 | } 16 | 17 | .SwipeViewsTabs .active, h1 { 18 | color: #13747d; 19 | font-weight: 500; 20 | } 21 | 22 | .SwipeView { 23 | padding: 10px; 24 | } 25 | 26 | .SwipeViewsTab, .SwipeViewsTab a { 27 | font-size: 13px; 28 | color: #CCCCCC; 29 | } 30 | 31 | a { 32 | color: #13747d; 33 | text-decoration: none; 34 | } 35 | 36 | #thanks li { 37 | margin-bottom: 2em; 38 | } 39 | 40 | button { 41 | margin-top: 20px; 42 | color: white; 43 | background-color: #13747d; 44 | border: none; 45 | border-radius: 5px; 46 | width: 100px; 47 | height: 50px; 48 | } -------------------------------------------------------------------------------- /examples/npm-webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'eval', 6 | resolve: { fallback: path.join(__dirname, "node_modules") }, 7 | resolveLoader: { fallback: path.join(__dirname, "node_modules") }, 8 | entry: [ 9 | 'webpack-dev-server/client?http://localhost:3000', 10 | 'webpack/hot/only-dev-server', 11 | './src/index' 12 | ], 13 | output: { 14 | path: path.join(__dirname, 'build'), 15 | filename: 'bundle.js', 16 | publicPath: '/src/' 17 | }, 18 | plugins: [ 19 | new webpack.HotModuleReplacementPlugin(), 20 | new webpack.NoErrorsPlugin() 21 | ], 22 | resolve: { 23 | extensions: ['', '.js', '.jsx'] 24 | }, 25 | module: { 26 | loaders: [ 27 | { 28 | test: /\.jsx?$/, 29 | loaders: ['react-hot', 'babel'], 30 | include: path.join(__dirname, 'src') 31 | }, 32 | { 33 | test: /\.css$/, 34 | loader: "style-loader!css-loader" 35 | }, 36 | { 37 | test: /\.less$/, 38 | loader: "style!css!less" 39 | } 40 | ] 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /lib/SwipeViews.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 12 | 13 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 14 | 15 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 16 | 17 | var _react = require('react'); 18 | 19 | var _react2 = _interopRequireDefault(_react); 20 | 21 | Number.isInteger = Number.isInteger || function (value) { 22 | return typeof value === "number" && isFinite(value) && Math.floor(value) === value; 23 | }; 24 | 25 | var SwipeViews = (function (_React$Component) { 26 | _inherits(SwipeViews, _React$Component); 27 | 28 | function SwipeViews(props) { 29 | _classCallCheck(this, SwipeViews); 30 | 31 | _get(Object.getPrototypeOf(SwipeViews.prototype), 'constructor', this).call(this, props); 32 | var selectedIndex = this.props.selectedIndex || 0; 33 | var pageWidthPerCent = 100 / this.props.children.length; 34 | var translation = selectedIndex * pageWidthPerCent; 35 | this.state = { 36 | selectedIndex: selectedIndex, 37 | pageWidthPerCent: pageWidthPerCent, 38 | translation: translation, 39 | clientX: null, 40 | animate: true, 41 | pageWidth: window.innerWidth 42 | }; 43 | } 44 | 45 | _createClass(SwipeViews, [{ 46 | key: 'componentDidMount', 47 | value: function componentDidMount() { 48 | this._selectIndex(); 49 | } 50 | }, { 51 | key: 'componentWillReceiveProps', 52 | value: function componentWillReceiveProps(nextProps) { 53 | this._selectIndex(parseInt(nextProps.selectedIndex, 10)); 54 | } 55 | }, { 56 | key: 'render', 57 | value: function render() { 58 | var _this = this; 59 | 60 | var swipeViewsInkStyle = { 61 | width: this.state.pageWidthPerCent + '%', 62 | marginLeft: this.state.translation + '%', 63 | transitionProperty: this.state.animate ? 'all' : 'none' 64 | }; 65 | var swipeViewsStyle = { 66 | transform: 'translateX(-' + this.state.translation + '%)', 67 | WebkitTransform: 'translateX(-' + this.state.translation + '%)', 68 | transitionProperty: this.state.animate ? 'all' : 'none', 69 | WebkitTransitionProperty: this.state.animate ? 'all' : 'none', 70 | width: this.props.children.length * 100 + '%' 71 | }; 72 | 73 | return _react2['default'].createElement( 74 | 'div', 75 | { className: 'SwipeViewsContainer' }, 76 | _react2['default'].createElement( 77 | 'header', 78 | { className: 'SwipeViewsHeader' }, 79 | _react2['default'].createElement( 80 | 'div', 81 | { className: 'SwipeViewsTabs' }, 82 | _react2['default'].createElement( 83 | 'ul', 84 | null, 85 | this.props.children.map(function (child, index) { 86 | var className = index === _this.state.selectedIndex ? 'active' : ''; 87 | return _react2['default'].createElement( 88 | 'li', 89 | { 90 | key: index, 91 | className: 'SwipeViewsTab ' + className, 92 | onClick: _this._handleClick.bind(_this, index) 93 | }, 94 | child.props.title 95 | ); 96 | }) 97 | ), 98 | _react2['default'].createElement('div', { className: 'SwipeViewsInk', style: swipeViewsInkStyle }) 99 | ) 100 | ), 101 | _react2['default'].createElement( 102 | 'div', 103 | { 104 | className: 'SwipeViews', 105 | style: swipeViewsStyle, 106 | onTouchMove: this._handleTouchMove.bind(this), 107 | onTouchEnd: this._handleTouchEnd.bind(this) 108 | }, 109 | this.props.children.map(function (child, index) { 110 | return _react2['default'].createElement( 111 | 'div', 112 | { 113 | className: 'SwipeView', 114 | key: index, 115 | style: { width: _this.state.pageWidthPerCent + '%' }, 116 | onScroll: _this._handleScroll.bind(_this) 117 | }, 118 | child.props.children 119 | ); 120 | }) 121 | ) 122 | ); 123 | } 124 | }, { 125 | key: '_selectIndex', 126 | value: function _selectIndex(selectedIndex) { 127 | var _this2 = this; 128 | 129 | if (Number.isInteger(selectedIndex)) { 130 | var translation = selectedIndex * this.state.pageWidthPerCent; 131 | return this.setState({ 132 | selectedIndex: selectedIndex, 133 | translation: translation, 134 | clientX: null, 135 | animate: true 136 | }); 137 | } 138 | if (!this.context.router) { 139 | return null; 140 | } 141 | this.props.children.map(function (child, index) { 142 | var to = child.props.title.props.to; 143 | var isActive = _this2.context.router.isActive(to); 144 | if (isActive) { 145 | var translation = index * _this2.state.pageWidthPerCent; 146 | return _this2.setState({ 147 | selectedIndex: index, 148 | translation: translation, 149 | clientX: null, 150 | animate: true 151 | }); 152 | } 153 | }); 154 | } 155 | }, { 156 | key: '_transitionTo', 157 | value: function _transitionTo(selectedIndex) { 158 | if (this.props.onIndexChange) { 159 | this.props.onIndexChange(selectedIndex); 160 | } 161 | if (!this.context.router) { 162 | return null; 163 | } 164 | var child = this.props.children[selectedIndex]; 165 | var to = child.props.title.props.to; 166 | if (!this.context.router.isActive(to)) { 167 | this.context.router.transitionTo(to); 168 | } 169 | } 170 | }, { 171 | key: '_handleTouchMove', 172 | value: function _handleTouchMove(event) { 173 | var clientX = event.changedTouches[0].clientX; 174 | var dx = clientX - this.state.clientX; 175 | var dxPerCent = dx / (this.state.pageWidth * this.props.children.length) * 100; 176 | var translation = this.state.translation - dxPerCent; 177 | var maxTranslation = this.state.pageWidthPerCent * (this.props.children.length - 1); 178 | var selectedIndex = this.state.selectedIndex; 179 | var previousTranslation = selectedIndex * this.state.pageWidthPerCent; 180 | var tippingPoint = this.state.pageWidthPerCent * 0.3; 181 | 182 | if (!this.state.clientX) { 183 | return this.setState({ 184 | clientX: clientX 185 | }); 186 | } 187 | 188 | if (translation < 0) { 189 | translation = 0; 190 | } else if (translation > maxTranslation) { 191 | translation = maxTranslation; 192 | } 193 | 194 | if (dx > 0 && translation < previousTranslation - tippingPoint) { 195 | selectedIndex -= 1; 196 | } else if (dx < 0 && translation > previousTranslation + tippingPoint) { 197 | selectedIndex += 1; 198 | } 199 | 200 | this.setState({ 201 | selectedIndex: selectedIndex, 202 | translation: translation, 203 | clientX: clientX, 204 | animate: false 205 | }); 206 | } 207 | }, { 208 | key: '_handleClick', 209 | value: function _handleClick(selectedIndex, event) { 210 | var translation = selectedIndex * this.state.pageWidthPerCent; 211 | this.setState({ 212 | selectedIndex: selectedIndex, 213 | translation: translation, 214 | clientX: null, 215 | animate: true 216 | }); 217 | if (event.target.localName === 'li') { 218 | this._transitionTo(selectedIndex); 219 | } 220 | } 221 | }, { 222 | key: '_handleTouchEnd', 223 | value: function _handleTouchEnd() { 224 | var selectedIndex = this.state.selectedIndex; 225 | var translation = selectedIndex * this.state.pageWidthPerCent; 226 | this.setState({ 227 | selectedIndex: selectedIndex, 228 | translation: translation, 229 | clientX: null, 230 | animate: true 231 | }, this._transitionTo(selectedIndex)); 232 | } 233 | }, { 234 | key: '_handleScroll', 235 | value: function _handleScroll() { 236 | var selectedIndex = this.state.selectedIndex; 237 | var translation = selectedIndex * this.state.pageWidthPerCent; 238 | this.setState({ 239 | selectedIndex: selectedIndex, 240 | translation: translation, 241 | clientX: null, 242 | animate: true 243 | }); 244 | } 245 | }]); 246 | 247 | return SwipeViews; 248 | })(_react2['default'].Component); 249 | 250 | exports['default'] = SwipeViews; 251 | 252 | SwipeViews.contextTypes = { 253 | router: _react2['default'].PropTypes.func 254 | }; 255 | 256 | SwipeViews.propTypes = { 257 | children: _react2['default'].PropTypes.array.isRequired, 258 | selectedIndex: _react2['default'].PropTypes.number, 259 | onIndexChange: _react2['default'].PropTypes.func 260 | }; 261 | module.exports = exports['default']; 262 | -------------------------------------------------------------------------------- /lib/react-swipe-views.css: -------------------------------------------------------------------------------- 1 | .SwipeViewsContainer { 2 | height: 100%; 3 | display: -webkit-box; 4 | display: -webkit-flex; 5 | display: -ms-flexbox; 6 | display: flex; 7 | -webkit-flex-flow: column nowrap; 8 | -ms-flex-flow: column nowrap; 9 | flex-flow: column nowrap; 10 | overflow: hidden; 11 | } 12 | 13 | .SwipeViewsHeader { 14 | min-height: 40px; 15 | border-bottom: #F0F0F0 4px solid; 16 | display: -webkit-box; 17 | display: -webkit-flex; 18 | display: -ms-flexbox; 19 | display: flex; 20 | -webkit-flex-flow: row nowrap; 21 | -ms-flex-flow: row nowrap; 22 | flex-flow: row nowrap; 23 | } 24 | 25 | .SwipeViewsTabs { 26 | -webkit-box-flex: 1; 27 | -webkit-flex: 1; 28 | -ms-flex: 1; 29 | flex: 1; 30 | min-height: 100%; 31 | display: -webkit-box; 32 | display: -webkit-flex; 33 | display: -ms-flexbox; 34 | display: flex; 35 | -webkit-flex-flow: column nowrap; 36 | -ms-flex-flow: column nowrap; 37 | flex-flow: column nowrap; 38 | margin-bottom: -4px; 39 | } 40 | 41 | .SwipeViewsTabs > ul { 42 | -webkit-box-flex: 1; 43 | -webkit-flex: 1; 44 | -ms-flex: 1; 45 | flex: 1; 46 | display: -webkit-box; 47 | display: -webkit-flex; 48 | display: -ms-flexbox; 49 | display: flex; 50 | -webkit-flex-flow: row nowrap; 51 | -ms-flex-flow: row nowrap; 52 | flex-flow: row nowrap; 53 | margin: 0; 54 | padding: 0; 55 | } 56 | 57 | .SwipeViewsTab { 58 | display: inline; 59 | padding-top: 15px; 60 | -webkit-box-flex: 1; 61 | -webkit-flex: 1; 62 | -ms-flex: 1; 63 | flex: 1; 64 | text-align: center; 65 | cursor: pointer; 66 | } 67 | 68 | .SwipeViewsTab > a { 69 | text-decoration: none; 70 | color: black; 71 | width: 100%; 72 | height: 100%; 73 | display: block; 74 | } 75 | 76 | .SwipeViewsTabs .active { 77 | color: #FF6950; 78 | } 79 | 80 | .SwipeViewsInk { 81 | -webkit-transition-duration: 0.2s; 82 | transition-duration: 0.2s; 83 | -webkit-transition-timing-function: ease-in-out; 84 | transition-timing-function: ease-in-out; 85 | height: 4px; 86 | background-color: #FF6950; 87 | border-radius: 5px; 88 | } 89 | 90 | .SwipeViews { 91 | -webkit-transition-duration: 0.2s; 92 | transition-duration: 0.2s; 93 | -webkit-transition-timing-function: ease-in-out; 94 | transition-timing-function: ease-in-out; 95 | height: 100%; 96 | display: -webkit-box; 97 | display: -webkit-flex; 98 | display: -ms-flexbox; 99 | display: flex; 100 | -webkit-flex-flow: row nowrap; 101 | -ms-flex-flow: row nowrap; 102 | flex-flow: row nowrap; 103 | overflow-x: hidden; 104 | } 105 | 106 | .SwipeView { 107 | overflow-y: scroll; 108 | } 109 | -------------------------------------------------------------------------------- /lib/react-swipe-views.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['exports', 'module', 'react'], factory); 4 | } else if (typeof exports !== 'undefined' && typeof module !== 'undefined') { 5 | factory(exports, module, require('react')); 6 | } else { 7 | var mod = { 8 | exports: {} 9 | }; 10 | factory(mod.exports, mod, global.React); 11 | global.SwipeViews = mod.exports; 12 | } 13 | })(this, function (exports, module, _react) { 14 | 'use strict'; 15 | 16 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 17 | 18 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 19 | 20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 21 | 22 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 23 | 24 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 25 | 26 | var _React = _interopRequireDefault(_react); 27 | 28 | Number.isInteger = Number.isInteger || function (value) { 29 | return typeof value === "number" && isFinite(value) && Math.floor(value) === value; 30 | }; 31 | 32 | var SwipeViews = (function (_React$Component) { 33 | _inherits(SwipeViews, _React$Component); 34 | 35 | function SwipeViews(props) { 36 | _classCallCheck(this, SwipeViews); 37 | 38 | _get(Object.getPrototypeOf(SwipeViews.prototype), 'constructor', this).call(this, props); 39 | var selectedIndex = this.props.selectedIndex || 0; 40 | var pageWidthPerCent = 100 / this.props.children.length; 41 | var translation = selectedIndex * pageWidthPerCent; 42 | this.state = { 43 | selectedIndex: selectedIndex, 44 | pageWidthPerCent: pageWidthPerCent, 45 | translation: translation, 46 | clientX: null, 47 | animate: true, 48 | pageWidth: window.innerWidth 49 | }; 50 | } 51 | 52 | _createClass(SwipeViews, [{ 53 | key: 'componentDidMount', 54 | value: function componentDidMount() { 55 | this._selectIndex(); 56 | } 57 | }, { 58 | key: 'componentWillReceiveProps', 59 | value: function componentWillReceiveProps(nextProps) { 60 | this._selectIndex(parseInt(nextProps.selectedIndex, 10)); 61 | } 62 | }, { 63 | key: 'render', 64 | value: function render() { 65 | var _this = this; 66 | 67 | var swipeViewsInkStyle = { 68 | width: this.state.pageWidthPerCent + '%', 69 | marginLeft: this.state.translation + '%', 70 | transitionProperty: this.state.animate ? 'all' : 'none' 71 | }; 72 | var swipeViewsStyle = { 73 | transform: 'translateX(-' + this.state.translation + '%)', 74 | WebkitTransform: 'translateX(-' + this.state.translation + '%)', 75 | transitionProperty: this.state.animate ? 'all' : 'none', 76 | WebkitTransitionProperty: this.state.animate ? 'all' : 'none', 77 | width: this.props.children.length * 100 + '%' 78 | }; 79 | 80 | return _React['default'].createElement( 81 | 'div', 82 | { className: 'SwipeViewsContainer' }, 83 | _React['default'].createElement( 84 | 'header', 85 | { className: 'SwipeViewsHeader' }, 86 | _React['default'].createElement( 87 | 'div', 88 | { className: 'SwipeViewsTabs' }, 89 | _React['default'].createElement( 90 | 'ul', 91 | null, 92 | this.props.children.map(function (child, index) { 93 | var className = index === _this.state.selectedIndex ? 'active' : ''; 94 | return _React['default'].createElement( 95 | 'li', 96 | { 97 | key: index, 98 | className: 'SwipeViewsTab ' + className, 99 | onClick: _this._handleClick.bind(_this, index) 100 | }, 101 | child.props.title 102 | ); 103 | }) 104 | ), 105 | _React['default'].createElement('div', { className: 'SwipeViewsInk', style: swipeViewsInkStyle }) 106 | ) 107 | ), 108 | _React['default'].createElement( 109 | 'div', 110 | { 111 | className: 'SwipeViews', 112 | style: swipeViewsStyle, 113 | onTouchMove: this._handleTouchMove.bind(this), 114 | onTouchEnd: this._handleTouchEnd.bind(this) 115 | }, 116 | this.props.children.map(function (child, index) { 117 | return _React['default'].createElement( 118 | 'div', 119 | { 120 | className: 'SwipeView', 121 | key: index, 122 | style: { width: _this.state.pageWidthPerCent + '%' }, 123 | onScroll: _this._handleScroll.bind(_this) 124 | }, 125 | child.props.children 126 | ); 127 | }) 128 | ) 129 | ); 130 | } 131 | }, { 132 | key: '_selectIndex', 133 | value: function _selectIndex(selectedIndex) { 134 | var _this2 = this; 135 | 136 | if (Number.isInteger(selectedIndex)) { 137 | var translation = selectedIndex * this.state.pageWidthPerCent; 138 | return this.setState({ 139 | selectedIndex: selectedIndex, 140 | translation: translation, 141 | clientX: null, 142 | animate: true 143 | }); 144 | } 145 | if (!this.context.router) { 146 | return null; 147 | } 148 | this.props.children.map(function (child, index) { 149 | var to = child.props.title.props.to; 150 | var isActive = _this2.context.router.isActive(to); 151 | if (isActive) { 152 | var translation = index * _this2.state.pageWidthPerCent; 153 | return _this2.setState({ 154 | selectedIndex: index, 155 | translation: translation, 156 | clientX: null, 157 | animate: true 158 | }); 159 | } 160 | }); 161 | } 162 | }, { 163 | key: '_transitionTo', 164 | value: function _transitionTo(selectedIndex) { 165 | if (this.props.onIndexChange) { 166 | this.props.onIndexChange(selectedIndex); 167 | } 168 | if (!this.context.router) { 169 | return null; 170 | } 171 | var child = this.props.children[selectedIndex]; 172 | var to = child.props.title.props.to; 173 | if (!this.context.router.isActive(to)) { 174 | this.context.router.transitionTo(to); 175 | } 176 | } 177 | }, { 178 | key: '_handleTouchMove', 179 | value: function _handleTouchMove(event) { 180 | var clientX = event.changedTouches[0].clientX; 181 | var dx = clientX - this.state.clientX; 182 | var dxPerCent = dx / (this.state.pageWidth * this.props.children.length) * 100; 183 | var translation = this.state.translation - dxPerCent; 184 | var maxTranslation = this.state.pageWidthPerCent * (this.props.children.length - 1); 185 | var selectedIndex = this.state.selectedIndex; 186 | var previousTranslation = selectedIndex * this.state.pageWidthPerCent; 187 | var tippingPoint = this.state.pageWidthPerCent * 0.3; 188 | 189 | if (!this.state.clientX) { 190 | return this.setState({ 191 | clientX: clientX 192 | }); 193 | } 194 | 195 | if (translation < 0) { 196 | translation = 0; 197 | } else if (translation > maxTranslation) { 198 | translation = maxTranslation; 199 | } 200 | 201 | if (dx > 0 && translation < previousTranslation - tippingPoint) { 202 | selectedIndex -= 1; 203 | } else if (dx < 0 && translation > previousTranslation + tippingPoint) { 204 | selectedIndex += 1; 205 | } 206 | 207 | this.setState({ 208 | selectedIndex: selectedIndex, 209 | translation: translation, 210 | clientX: clientX, 211 | animate: false 212 | }); 213 | } 214 | }, { 215 | key: '_handleClick', 216 | value: function _handleClick(selectedIndex, event) { 217 | var translation = selectedIndex * this.state.pageWidthPerCent; 218 | this.setState({ 219 | selectedIndex: selectedIndex, 220 | translation: translation, 221 | clientX: null, 222 | animate: true 223 | }); 224 | if (event.target.localName === 'li') { 225 | this._transitionTo(selectedIndex); 226 | } 227 | } 228 | }, { 229 | key: '_handleTouchEnd', 230 | value: function _handleTouchEnd() { 231 | var selectedIndex = this.state.selectedIndex; 232 | var translation = selectedIndex * this.state.pageWidthPerCent; 233 | this.setState({ 234 | selectedIndex: selectedIndex, 235 | translation: translation, 236 | clientX: null, 237 | animate: true 238 | }, this._transitionTo(selectedIndex)); 239 | } 240 | }, { 241 | key: '_handleScroll', 242 | value: function _handleScroll() { 243 | var selectedIndex = this.state.selectedIndex; 244 | var translation = selectedIndex * this.state.pageWidthPerCent; 245 | this.setState({ 246 | selectedIndex: selectedIndex, 247 | translation: translation, 248 | clientX: null, 249 | animate: true 250 | }); 251 | } 252 | }]); 253 | 254 | return SwipeViews; 255 | })(_React['default'].Component); 256 | 257 | module.exports = SwipeViews; 258 | 259 | SwipeViews.contextTypes = { 260 | router: _React['default'].PropTypes.func 261 | }; 262 | 263 | SwipeViews.propTypes = { 264 | children: _React['default'].PropTypes.array.isRequired, 265 | selectedIndex: _React['default'].PropTypes.number, 266 | onIndexChange: _React['default'].PropTypes.func 267 | }; 268 | }); 269 | -------------------------------------------------------------------------------- /lib/react-swipe-views.min.css: -------------------------------------------------------------------------------- 1 | .SwipeViewsContainer{height:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap;overflow:hidden}.SwipeViewsHeader,.SwipeViewsTabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox}.SwipeViewsHeader{min-height:40px;border-bottom:#F0F0F0 4px solid;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap}.SwipeViewsTabs{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;min-height:100%;display:flex;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap;margin-bottom:-4px}.SwipeViewsTabs>ul{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;margin:0;padding:0}.SwipeViewsTab{display:inline;padding-top:15px;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;text-align:center;cursor:pointer}.SwipeViewsTab>a{text-decoration:none;color:#000;width:100%;height:100%;display:block}.SwipeViewsTabs .active{color:#FF6950}.SwipeViewsInk{-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transition-timing-function:ease-in-out;transition-timing-function:ease-in-out;height:4px;background-color:#FF6950;border-radius:5px}.SwipeViews{-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transition-timing-function:ease-in-out;transition-timing-function:ease-in-out;height:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;overflow-x:hidden}.SwipeView{overflow-y:scroll} -------------------------------------------------------------------------------- /lib/react-swipe-views.min.js: -------------------------------------------------------------------------------- 1 | (function(global,factory){if(typeof define==="function"&&define.amd){define(["exports","module","react"],factory)}else if(typeof exports!=="undefined"&&typeof module!=="undefined"){factory(exports,module,require("react"))}else{var mod={exports:{}};factory(mod.exports,mod,global.React);global.SwipeViews=mod.exports}})(this,function(exports,module,_react){"use strict";var _createClass=function(){function defineProperties(target,props){for(var i=0;imaxTranslation){translation=maxTranslation}if(dx>0&&translationpreviousTranslation+tippingPoint){selectedIndex+=1}this.setState({selectedIndex:selectedIndex,translation:translation,clientX:clientX,animate:false})}},{key:"_handleClick",value:function _handleClick(selectedIndex,event){var translation=selectedIndex*this.state.pageWidthPerCent;this.setState({selectedIndex:selectedIndex,translation:translation,clientX:null,animate:true});if(event.target.localName==="li"){this._transitionTo(selectedIndex)}}},{key:"_handleTouchEnd",value:function _handleTouchEnd(){var selectedIndex=this.state.selectedIndex;var translation=selectedIndex*this.state.pageWidthPerCent;this.setState({selectedIndex:selectedIndex,translation:translation,clientX:null,animate:true},this._transitionTo(selectedIndex))}},{key:"_handleScroll",value:function _handleScroll(){var selectedIndex=this.state.selectedIndex;var translation=selectedIndex*this.state.pageWidthPerCent;this.setState({selectedIndex:selectedIndex,translation:translation,clientX:null,animate:true})}}]);return SwipeViews}(_React["default"].Component);module.exports=SwipeViews;SwipeViews.contextTypes={router:_React["default"].PropTypes.func};SwipeViews.propTypes={children:_React["default"].PropTypes.array.isRequired,selectedIndex:_React["default"].PropTypes.number,onIndexChange:_React["default"].PropTypes.func}}); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-swipe-views", 3 | "version": "0.0.12", 4 | "description": "React Component for binded Tabs and Swipeable Views", 5 | "main": "./lib/SwipeViews.js", 6 | "scripts": { 7 | "start": "npm install && npm run build && npm run watch", 8 | "watch": "parallelshell 'npm run watch:js' 'npm run watch:css'", 9 | "watch:js": "onchange './src/SwipeViews.js' -- npm run build:js", 10 | "watch:css": "onchange './src/SwipeViews.css' -- npm run build:css", 11 | "build": "npm run build:js && npm run build:css", 12 | "build:js": "npm run build:cjs && npm run build:umd && npm run min:js", 13 | "build:cjs": "babel ./src/SwipeViews.js -o ./lib/SwipeViews.js -v", 14 | "build:umd": "babel ./src/SwipeViews.js --modules umd -o ./lib/react-swipe-views.js -v", 15 | "build:css": "postcss --use autoprefixer ./src/SwipeViews.css -o ./lib/react-swipe-views.css && npm run min:css", 16 | "min:js": "uglifyjs ./lib/react-swipe-views.js -o ./lib/react-swipe-views.min.js", 17 | "min:css": "cleancss ./lib/react-swipe-views.css -o ./lib/react-swipe-views.min.css" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/damusnet/react-swipe-views.git" 22 | }, 23 | "keywords": [ 24 | "react", 25 | "reactjs", 26 | "react-component", 27 | "swipe", 28 | "views", 29 | "tabs" 30 | ], 31 | "author": "Damien Varron (https://github.com/damusnet)", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/damusnet/react-swipe-views/issues" 35 | }, 36 | "homepage": "https://github.com/damusnet/react-swipe-views", 37 | "peerDependencies": { 38 | "react": "^0.13.0 || ^0.14.0" 39 | }, 40 | "devDependencies": { 41 | "autoprefixer": "^6.0.3", 42 | "babel": "~5.8.23", 43 | "babel-eslint": "^4.1.3", 44 | "clean-css": "~3.4.5", 45 | "eslint": "^1.4.3", 46 | "eslint-config-defaults": "^6.0.0", 47 | "eslint-plugin-react": "^3.3.2", 48 | "onchange": "~2.0.0", 49 | "parallelshell": "~2.0.0", 50 | "postcss-cli": "^2.2.0", 51 | "uglify-js": "~2.5.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/SwipeViews.css: -------------------------------------------------------------------------------- 1 | .SwipeViewsContainer { 2 | height: 100%; 3 | display: flex; 4 | flex-flow: column nowrap; 5 | overflow: hidden; 6 | } 7 | 8 | .SwipeViewsHeader { 9 | min-height: 40px; 10 | border-bottom: #F0F0F0 4px solid; 11 | display: flex; 12 | flex-flow: row nowrap; 13 | } 14 | 15 | .SwipeViewsTabs { 16 | flex: 1; 17 | min-height: 100%; 18 | display: flex; 19 | flex-flow: column nowrap; 20 | margin-bottom: -4px; 21 | } 22 | 23 | .SwipeViewsTabs > ul { 24 | flex: 1; 25 | display: flex; 26 | flex-flow: row nowrap; 27 | margin: 0; 28 | padding: 0; 29 | } 30 | 31 | .SwipeViewsTab { 32 | display: inline; 33 | padding-top: 15px; 34 | flex: 1; 35 | text-align: center; 36 | cursor: pointer; 37 | } 38 | 39 | .SwipeViewsTab > a { 40 | text-decoration: none; 41 | color: black; 42 | width: 100%; 43 | height: 100%; 44 | display: block; 45 | } 46 | 47 | .SwipeViewsTabs .active { 48 | color: #FF6950; 49 | } 50 | 51 | .SwipeViewsInk { 52 | transition-duration: 0.2s; 53 | transition-timing-function: ease-in-out; 54 | height: 4px; 55 | background-color: #FF6950; 56 | border-radius: 5px; 57 | } 58 | 59 | .SwipeViews { 60 | transition-duration: 0.2s; 61 | transition-timing-function: ease-in-out; 62 | height: 100%; 63 | display: flex; 64 | flex-flow: row nowrap; 65 | overflow-x: hidden; 66 | } 67 | 68 | .SwipeView { 69 | overflow-y: scroll; 70 | } 71 | -------------------------------------------------------------------------------- /src/SwipeViews.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | Number.isInteger = Number.isInteger || function(value) { 4 | return typeof value === "number" && 5 | isFinite(value) && 6 | Math.floor(value) === value; 7 | }; 8 | 9 | export default class SwipeViews extends React.Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | const selectedIndex = this.props.selectedIndex || 0; 14 | const numChildren = React.Children.count(this.props.children) 15 | const pageWidthPerCent = 100 / numChildren; 16 | const translation = selectedIndex * pageWidthPerCent; 17 | this.state = { 18 | selectedIndex, 19 | pageWidthPerCent, 20 | translation, 21 | clientX: null, 22 | animate: true, 23 | pageWidth: window.innerWidth, 24 | }; 25 | } 26 | 27 | componentDidMount() { 28 | this._selectIndex(); 29 | } 30 | 31 | componentWillReceiveProps(nextProps) { 32 | this._selectIndex(parseInt(nextProps.selectedIndex, 10)); 33 | } 34 | 35 | render() { 36 | const swipeViewsInkStyle = { 37 | width: this.state.pageWidthPerCent + '%', 38 | marginLeft: this.state.translation + '%', 39 | transitionProperty: this.state.animate ? 'all' : 'none', 40 | }; 41 | const swipeViewsStyle = { 42 | transform: 'translateX(-' + this.state.translation + '%)', 43 | WebkitTransform: 'translateX(-' + this.state.translation + '%)', 44 | transitionProperty: this.state.animate ? 'all' : 'none', 45 | WebkitTransitionProperty: this.state.animate ? 'all' : 'none', 46 | width: React.Children.count(this.props.children) * 100 + '%', 47 | }; 48 | 49 | return ( 50 |
51 |
52 |
53 |
    54 | {React.Children.map(this.props.children, (child, index) => { 55 | const className = (index === this.state.selectedIndex ? 'active' : ''); 56 | return ( 57 |
  • 62 | {child.props.title} 63 |
  • 64 | ); 65 | })} 66 |
67 |
68 |
69 |
70 |
76 | {React.Children.map(this.props.children, (child, index) => { 77 | return ( 78 |
84 | {child.props.children} 85 |
86 | ); 87 | })} 88 |
89 |
90 | ); 91 | } 92 | 93 | _selectIndex(selectedIndex) { 94 | if (Number.isInteger(selectedIndex)) { 95 | const translation = selectedIndex * this.state.pageWidthPerCent; 96 | return this.setState({ 97 | selectedIndex, 98 | translation, 99 | clientX: null, 100 | animate: true, 101 | }); 102 | } 103 | if (!this.context.router) { 104 | return null; 105 | } 106 | React.Children.map(this.props.children, (child, index) => { 107 | const to = child.props.title.props.to; 108 | const isActive = this.context.router.isActive(to); 109 | if (isActive) { 110 | const translation = index * this.state.pageWidthPerCent; 111 | return this.setState({ 112 | selectedIndex: index, 113 | translation, 114 | clientX: null, 115 | animate: true, 116 | }); 117 | } 118 | }); 119 | } 120 | 121 | _transitionTo(selectedIndex) { 122 | if (this.props.onIndexChange) { 123 | this.props.onIndexChange(selectedIndex); 124 | } 125 | if (!this.context.router) { 126 | return null; 127 | } 128 | const child = React.Children.map(this.props.children, child => child)[selectedIndex]; 129 | const to = child.props.title.props.to; 130 | if (!this.context.router.isActive(to)) { 131 | this.context.router.transitionTo(to); 132 | } 133 | } 134 | 135 | _handleTouchMove(event) { 136 | const clientX = event.changedTouches[0].clientX; 137 | const dx = (clientX - this.state.clientX); 138 | const numChildren = React.Children.count(this.props.children) 139 | const dxPerCent = dx / (this.state.pageWidth * numChildren) * 100; 140 | let translation = this.state.translation - dxPerCent; 141 | const maxTranslation = this.state.pageWidthPerCent * (numChildren - 1); 142 | let selectedIndex = this.state.selectedIndex; 143 | const previousTranslation = selectedIndex * this.state.pageWidthPerCent; 144 | const tippingPoint = this.state.pageWidthPerCent * 0.3; 145 | 146 | if (!this.state.clientX) { 147 | return this.setState({ 148 | clientX, 149 | }); 150 | } 151 | 152 | if (translation < 0) { 153 | translation = 0; 154 | } else if (translation > maxTranslation) { 155 | translation = maxTranslation; 156 | } 157 | 158 | if (dx > 0 && translation < previousTranslation - tippingPoint) { 159 | selectedIndex -= 1; 160 | } else if (dx < 0 && translation > previousTranslation + tippingPoint) { 161 | selectedIndex += 1; 162 | } 163 | 164 | this.setState({ 165 | selectedIndex, 166 | translation, 167 | clientX, 168 | animate: false, 169 | }); 170 | } 171 | 172 | _handleClick(selectedIndex, event) { 173 | const translation = selectedIndex * this.state.pageWidthPerCent; 174 | this.setState({ 175 | selectedIndex, 176 | translation, 177 | clientX: null, 178 | animate: true, 179 | }); 180 | if (event.target.localName === 'li') { 181 | this._transitionTo(selectedIndex); 182 | } 183 | } 184 | 185 | _handleTouchEnd() { 186 | const selectedIndex = this.state.selectedIndex; 187 | const translation = selectedIndex * this.state.pageWidthPerCent; 188 | this.setState({ 189 | selectedIndex, 190 | translation, 191 | clientX: null, 192 | animate: true, 193 | }, this._transitionTo(selectedIndex)); 194 | } 195 | 196 | _handleScroll() { 197 | const selectedIndex = this.state.selectedIndex; 198 | const translation = selectedIndex * this.state.pageWidthPerCent; 199 | this.setState({ 200 | selectedIndex, 201 | translation, 202 | clientX: null, 203 | animate: true, 204 | }); 205 | } 206 | 207 | } 208 | 209 | SwipeViews.contextTypes = { 210 | router: React.PropTypes.func, 211 | }; 212 | 213 | SwipeViews.propTypes = { 214 | children: React.PropTypes.array.isRequired, 215 | selectedIndex: React.PropTypes.number, 216 | onIndexChange: React.PropTypes.func, 217 | }; 218 | --------------------------------------------------------------------------------