├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── build.js ├── examples ├── App.js ├── bundle.js ├── devServer.js ├── index.html ├── index.js ├── styles.css └── webpack.config.js ├── package-lock.json ├── package.json └── src ├── OffCanvas.js ├── OffCanvasBody.js ├── OffCanvasMenu.js └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-0"], 3 | "env": { 4 | "development": { 5 | "plugins": [["react-transform", { 6 | "transforms": [{ 7 | "transform": "react-transform-hmr", 8 | "imports": ["react"], 9 | "locals": ["module"] 10 | }] 11 | }]] 12 | }, 13 | "production": { 14 | "plugins": ["transform-remove-console", "transform-remove-debugger", "transform-property-literals"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | insert_final_newline = true 9 | 10 | [*.{html,js,css,md}] 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "jsx": true 8 | }, 9 | }, 10 | "rules": { 11 | "semi": 2 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log.* 3 | npm-debug.log 4 | lib/ 5 | gh-pages/ 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | examples/ 3 | .babelrc 4 | .eslintrc 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Vu Tran 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 | # React Off-Canvas 2 | 3 | Off-canvas menus for React. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | $ npm install --save react-offcanvas 9 | ``` 10 | 11 | ## Usage 12 | 13 | ### Basic Usage 14 | 15 | ```jsx 16 | 23 | 24 | This is the canvas body. 25 | 26 | 27 | This is the canvas menu. 28 | 29 | 30 | ``` 31 | 32 | ### Within An Application 33 | 34 | You'll need to hook up your application to handle the state for toggling the menu. Check out the basic example below: 35 | 36 | ```js 37 | "use strict"; 38 | 39 | import React, { Component } from "react"; 40 | import { OffCanvas, OffCanvasMenu, OffCanvasBody } from "react-offcanvas"; 41 | 42 | export default class App extends Component { 43 | componentWillMount() { 44 | // sets the initial state 45 | this.setState({ 46 | isMenuOpened: false 47 | }); 48 | } 49 | 50 | render() { 51 | return ( 52 | 59 | 63 |

This is the main body container.

64 |

65 | 66 | Click here 67 | {" "} 68 | to toggle the menu. 69 |

70 |
71 | 72 |

Placeholder content.

73 | 85 |
86 |
87 | ); 88 | } 89 | 90 | handleClick() { 91 | // toggles the menu opened state 92 | this.setState({ isMenuOpened: !this.state.isMenuOpened }); 93 | } 94 | } 95 | ``` 96 | 97 | ## Components 98 | 99 | ### `OffCanvas` 100 | 101 | This is the main component you will be using to compose your body and menu. 102 | 103 | | Prop | Type | Default | Description | 104 | | :------------------- | :------- | :------ | :--------------------------------------------------- | 105 | | `width` | `number` | 250 | The width of the menu and off-canvas transition. | 106 | | `transitionDuration` | `number` | 250 | The time in ms for the transition. | 107 | | `isMenuOpened` | `bool` | false | Is the menu opened or not. | 108 | | `position` | `string` | "left" | Position the menu to the "left" or "right". | 109 | | `effect` | `string` | "push" | Choose menu effect. "push", "overlay" or "parallax". | 110 | 111 | ### `OffCanvasBody` 112 | 113 | | Prop | Type | Default | Description | 114 | | :------------------- | :------- | :------ | :----------------------------------------------- | 115 | | `width` | `number` | 250 | The width of the menu and off-canvas transition. | 116 | | `transitionDuration` | `number` | 250 | The time in ms for the transition. | 117 | | `isMenuOpened` | `bool` | false | Is the menu opened or not. | 118 | | `position` | `string` | "left" | Position the menu to the "left" or "right". | 119 | | `className` | `string` | | Custom className for the element. | 120 | | `style` | `object` | | CSS style object for the element. | 121 | 122 | ### `OffCanvasMenu` 123 | 124 | | Prop | Type | Default | Description | 125 | | :------------------- | :------- | :------ | :----------------------------------------------- | 126 | | `width` | `number` | 250 | The width of the menu and off-canvas transition. | 127 | | `transitionDuration` | `number` | 250 | The time in ms for the transition. | 128 | | `isMenuOpened` | `bool` | false | Is the menu opened or not. | 129 | | `position` | `string` | "left" | Position the menu to the "left" or "right". | 130 | | `className` | `string` | | Custom className for the element. | 131 | | `style` | `object` | | CSS style object for the element. | 132 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compiles and builds for the gh-pages branch into the gh-pages folder. 3 | */ 4 | 5 | import fs from 'fs'; 6 | import path from 'path'; 7 | import webpack from 'webpack'; 8 | import config from './examples/webpack.config'; 9 | 10 | const ghpConfig = Object.assign({}, config); 11 | ghpConfig.output.path = path.resolve(__dirname, 'gh-pages'); 12 | ghpConfig.devtool = null; 13 | 14 | config.entry.shift(); // remove eventsource-polyfill 15 | config.entry.shift(); // remove hmr entry 16 | config.plugins.shift(); // remove hmr plugin 17 | 18 | // create a webpack compiler 19 | const compiler = webpack(ghpConfig); 20 | 21 | // build! 22 | compiler.run((err, stats) => { 23 | if (err) { 24 | console.error(err); 25 | } 26 | }); 27 | 28 | // copies the index.html to the dist folder 29 | const ghpPath = path.resolve(__dirname, 'gh-pages'); 30 | const indexFile = path.resolve(__dirname, 'examples', 'index.html'); 31 | const outIndexFile = path.resolve(ghpPath, 'index.html'); 32 | try { 33 | fs.mkdir(ghpPath, (err) => { 34 | if (err) { 35 | console.error(err); 36 | } 37 | fs.createReadStream(indexFile).pipe(fs.createWriteStream(outIndexFile)); 38 | }); 39 | } catch (copyErr) { 40 | if (copyErr.code === 'EEXIST') { 41 | fs.createReadStream(indexFile).pipe(fs.createWriteStream(outIndexFile)); 42 | } else { 43 | console.error(copyErr); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/App.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React, { Component } from "react"; 4 | import { OffCanvas, OffCanvasMenu, OffCanvasBody } from "react-offcanvas"; 5 | 6 | import styles from "./styles.css"; 7 | 8 | export default class App extends Component { 9 | componentWillMount() { 10 | // sets the initial state 11 | this.setState({ 12 | isMenuOpened: false 13 | }); 14 | } 15 | 16 | render() { 17 | return ( 18 | 25 | 29 |

This is the main body container.

30 |

31 | 32 | Click here 33 | {" "} 34 | to toggle the menu. 35 |

36 |
37 | 38 |

Placeholder content.

39 | 51 |
52 |
53 | ); 54 | } 55 | 56 | handleClick() { 57 | // toggles the menu opened state 58 | this.setState({ isMenuOpened: !this.state.isMenuOpened }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/devServer.js: -------------------------------------------------------------------------------- 1 | // https://github.com/gaearon/react-transform-boilerplate/blob/master/devServer.js 2 | 3 | var path = require('path'); 4 | var express = require('express'); 5 | var webpack = require('webpack'); 6 | var config = require('./webpack.config'); 7 | 8 | var app = express(); 9 | var compiler = webpack(config); 10 | 11 | app.use(require('webpack-dev-middleware')(compiler, { 12 | noInfo: true, 13 | publicPath: config.output.publicPath 14 | })); 15 | 16 | app.use(require('webpack-hot-middleware')(compiler)); 17 | 18 | app.get('*', function(req, res) { 19 | res.sendFile(path.join(__dirname, 'index.html')); 20 | }); 21 | 22 | app.listen(3000, 'localhost', function(err) { 23 | if (err) { 24 | console.log(err); 25 | return; 26 | } 27 | 28 | console.log('Listening at http://localhost:3000'); 29 | }); 30 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Off-Canvas 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import App from './App'; 6 | 7 | ReactDOM.render( 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /examples/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0px; 7 | padding: 0px; 8 | } 9 | 10 | p { 11 | margin-top: 0px; 12 | } 13 | 14 | .bodyClass { 15 | background-color: #fff; 16 | padding: 100px; 17 | } 18 | 19 | .menuClass { 20 | background-color: #999; 21 | padding: 15px; 22 | } 23 | -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | // fork of https://github.com/gaearon/react-transform-boilerplate/blob/master/webpack.config.dev.js 2 | 3 | var path = require('path'); 4 | var webpack = require('webpack'); 5 | 6 | module.exports = { 7 | devtool: 'cheap-module-eval-source-map', 8 | entry : [ 9 | 'eventsource-polyfill', // necessary for hot reloading with IE 10 | 'webpack-hot-middleware/client', 11 | path.join(__dirname, 'index.js'), 12 | ], 13 | output: { 14 | publicPath: '/', 15 | path: __dirname, 16 | filename: 'bundle.js', 17 | }, 18 | module: { 19 | loaders: [ 20 | { 21 | test: /\.css$/, 22 | loaders: ['style', 'css?modules'], 23 | }, 24 | { 25 | test: /\.js$/, 26 | exclude: /node_modules/, 27 | loaders: ['babel'], 28 | }, 29 | ], 30 | }, 31 | plugins: [ 32 | new webpack.HotModuleReplacementPlugin(), 33 | new webpack.NoErrorsPlugin(), 34 | ], 35 | resolve: { 36 | alias: { 37 | "react-offcanvas": path.resolve(__dirname, '..', 'src'), 38 | }, 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-offcanvas", 3 | "version": "0.4.0", 4 | "description": "Off-canvas menus for React.", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib", 8 | "LICENSE", 9 | "README.md" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:vutran/react-offcanvas.git" 14 | }, 15 | "scripts": { 16 | "clean": "rimraf lib", 17 | "lint": "eslint ./src", 18 | "compile": "babel ./src -d ./lib", 19 | "build-ghp": "cross-env NODE_ENV=production babel-node build.js", 20 | "build": "cross-env NODE_ENV=production npm run clean && npm run compile", 21 | "webpack": "webpack --config ./examples/webpack.config.js", 22 | "devServer": "node ./examples/devServer.js", 23 | "dev": "npm run webpack && npm run devServer", 24 | "prepublish": "npm run build" 25 | }, 26 | "author": { 27 | "name": "Vu Tran", 28 | "email": "vu.tran@thermofisher.com" 29 | }, 30 | "contributors": [ 31 | { 32 | "name": "Vu Tran", 33 | "email": "vu.tran@thermofisher.com", 34 | "url": "https://github.com/vutran/" 35 | } 36 | ], 37 | "license": "MIT", 38 | "devDependencies": { 39 | "babel-cli": "^6.6.5", 40 | "babel-core": "^6.6.5", 41 | "babel-eslint": "^5.0.0", 42 | "babel-loader": "^6.2.4", 43 | "babel-plugin-react-transform": "^2.0.2", 44 | "babel-plugin-transform-property-literals": "^6.5.0", 45 | "babel-plugin-transform-remove-console": "^6.5.0", 46 | "babel-plugin-transform-remove-debugger": "^6.5.0", 47 | "babel-preset-es2015": "^6.6.0", 48 | "babel-preset-react": "^6.5.0", 49 | "babel-preset-stage-0": "^6.5.0", 50 | "cross-env": "^5.2.0", 51 | "css-loader": "^0.23.1", 52 | "eslint": "^2.3.0", 53 | "eventsource-polyfill": "^0.9.6", 54 | "express": "^4.13.4", 55 | "react-dom": "^0.14.7", 56 | "react-transform-hmr": "^1.0.4", 57 | "rimraf": "^2.5.2", 58 | "style-loader": "^0.13.0", 59 | "webpack": "^1.12.14", 60 | "webpack-dev-middleware": "^1.5.1", 61 | "webpack-hot-middleware": "^2.9.0" 62 | }, 63 | "dependencies": { 64 | "react": "^0.14.7" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/OffCanvas.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React from "react"; 4 | 5 | let OffCanvas = ({ 6 | width = 250, 7 | transitionDuration = 250, 8 | isMenuOpened = false, 9 | position = "left", 10 | effect = "push", 11 | children 12 | }) => { 13 | // transfer the props to the children 14 | const transferProps = element => { 15 | return React.cloneElement(element, { 16 | width, 17 | transitionDuration, 18 | isMenuOpened, 19 | position, 20 | effect 21 | }); 22 | }; 23 | 24 | // transfer the props from OffCanvas to the child 25 | let offCanvasChildren = React.Children.map(children, transferProps); 26 | 27 | return
{offCanvasChildren}
; 28 | }; 29 | 30 | export default OffCanvas; 31 | -------------------------------------------------------------------------------- /src/OffCanvasBody.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React, { PropTypes } from "react"; 4 | 5 | let OffCanvasBody = ({ 6 | width = 250, 7 | transitionDuration = 250, 8 | isMenuOpened = false, 9 | position = "left", 10 | effect = "push", 11 | children, 12 | className, 13 | style 14 | }) => { 15 | // closed state style 16 | let translateX = position === "left" ? 0 : 0; 17 | let closedStyle = { 18 | transitionDuration: transitionDuration + "ms", 19 | transform: "translate(" + translateX + "px, 0px)", 20 | backfaceVisibility: "hidden" 21 | }; 22 | 23 | // open state style 24 | let translateOpenX = position === "left" ? width : -1 * width; 25 | translateOpenX = effect === "parallax" ? translateOpenX / 2 : translateOpenX; 26 | translateOpenX = effect === "overlay" ? 0 : translateOpenX; 27 | 28 | let openStyle = { 29 | transform: "translate(" + translateOpenX + "px, 0px)" 30 | }; 31 | 32 | // create current state styles 33 | let currStyle = Object.assign({}, closedStyle); 34 | if (isMenuOpened) { 35 | currStyle = Object.assign({}, currStyle, openStyle); 36 | } 37 | 38 | return ( 39 |
40 | {children} 41 |
42 | ); 43 | }; 44 | 45 | OffCanvasBody.propTypes = { 46 | width: PropTypes.number, 47 | transitionDuration: PropTypes.number, 48 | isMenuOpened: PropTypes.bool, 49 | position: PropTypes.oneOf(["left", "right"]), 50 | effect: PropTypes.oneOf(["push", "parallax", "overlay"]), 51 | style: PropTypes.object 52 | }; 53 | 54 | export default OffCanvasBody; 55 | -------------------------------------------------------------------------------- /src/OffCanvasMenu.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import React, { PropTypes } from "react"; 4 | 5 | let OffCanvasMenu = ({ 6 | width = 250, 7 | transitionDuration = 250, 8 | isMenuOpened = false, 9 | position = "left", 10 | effect = "push", 11 | children, 12 | className, 13 | style 14 | }) => { 15 | 16 | // closed state style 17 | let left = position === "left" ? -1 * width + "px" : "auto"; 18 | let right = position === "left" ? "auto" : -1 * width + "px"; 19 | let translateX = position === "left" ? -1 * width : 0; 20 | let closedStyle = { 21 | width: width + "px", 22 | position: "fixed", 23 | top: "0px", 24 | left: left, 25 | right: right, 26 | transform: "translate(" + translateX + "px, 0px)", 27 | transitionDuration: transitionDuration + "ms", 28 | backfaceVisibility: "hidden" 29 | }; 30 | 31 | // open state style 32 | let translateOpenX = position === "left" ? width : -1 * width; 33 | let openStyle = { 34 | transform: "translate(" + translateOpenX + "px, 0px)" 35 | }; 36 | 37 | // create current state styles 38 | let currStyle = Object.assign({}, closedStyle); 39 | if (isMenuOpened) { 40 | currStyle = Object.assign({}, currStyle, openStyle); 41 | } 42 | 43 | return ( 44 |
45 | {children} 46 |
47 | ); 48 | }; 49 | 50 | OffCanvasMenu.propTypes = { 51 | width: PropTypes.number, 52 | transitionDuration: PropTypes.number, 53 | isMenuOpened: PropTypes.bool, 54 | position: PropTypes.oneOf(["left", "right"]), 55 | effect: PropTypes.oneOf(["push", "parallax", "overlay"]), 56 | style: PropTypes.object 57 | }; 58 | 59 | export default OffCanvasMenu; 60 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export OffCanvas from './OffCanvas'; 4 | export OffCanvasBody from './OffCanvasBody'; 5 | export OffCanvasMenu from './OffCanvasMenu'; 6 | --------------------------------------------------------------------------------