├── .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 |
--------------------------------------------------------------------------------