10 |
11 |
12 | )
13 |
--------------------------------------------------------------------------------
/demo/Server.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import React from 'react';
3 | import ReactDom from 'react-dom';
4 | import express from 'express';
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import webpackConfig from '../webpack/webpack.config.demo.prod.js';
8 | import { renderToString } from 'react-dom/server'
9 | import { match, RoutingContext } from 'react-router'
10 | import routes from './Routes'
11 |
12 | require.extensions['.txt'] = function (module, filename) {
13 | module.exports = fs.readFileSync(filename, 'utf8');
14 | };
15 |
16 | const development = process.env.NODE_ENV !== 'production';
17 | let app = express();
18 |
19 | if (development) {
20 |
21 | webpackConfig.output.path = '/';
22 | webpackConfig.output.publicPath = undefined;
23 |
24 | app = app
25 | .use(function renderApp(req, res) {
26 |
27 | match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
28 | if (error) {
29 | res.status(500).send(error.message)
30 | } else if (redirectLocation) {
31 | res.redirect(302, redirectLocation.pathname + redirectLocation.search)
32 | } else if (renderProps) {
33 |
34 | let wrap = require('../demo/pages/BasePage.txt')
35 | .replace(/\$\{cssBundlePath\}/g, '')
36 | .replace(/\$\{jsBundlePath\}/g, 'http://localhost:8082/assets/bundle.js');
37 | res.status(200).send(wrap);
38 |
39 | } else {
40 | res.status(404).send('Not found')
41 | }
42 | });
43 |
44 | });
45 | } else {
46 | app = app
47 | .use('/react-vnav', express.static(path.join(__dirname, '../demo-built')));
48 | }
49 |
50 | app
51 | .listen(4000, function () {
52 | console.log('Server started at http://localhost:4000/react-vnav/demo.html');
53 | });
54 |
--------------------------------------------------------------------------------
/demo/less/styles.less:
--------------------------------------------------------------------------------
1 | @import '../../node_modules/bootstrap/less/bootstrap';
2 | @import '../../node_modules/font-awesome/less/font-awesome';
3 |
4 | @import '../../src/less/vnav.less';
--------------------------------------------------------------------------------
/demo/pages/BasePage.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | React-vnav - A library for creating vertical main menus
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demo/pages/ClientLoader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Router from 'react-router';
3 | import Routes from '../Routes.js';
4 |
5 | const ClientLoader = React.createClass({
6 |
7 | render: function () {
8 |
9 | let browserInitScriptObj = {
10 | __html:`console.log('fuuuu');`
11 | };
12 |
13 | return (
14 |
19 | );
20 | }
21 | });
22 |
23 | export default ClientLoader;
24 |
--------------------------------------------------------------------------------
/demo/pages/Demo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import VNav from '../../src/components/VNav';
3 | import VNavItem from '../../src/components/VNavItem';
4 | import NotificationSystem from 'react-notification-system';
5 |
6 | const Demo = React.createClass({
7 |
8 | _notificationSystem: null,
9 |
10 | handleItemClick: function (node) {
11 | this._notificationSystem.addNotification({
12 | title: `You clicked on ${node.display}`,
13 | message: JSON.stringify(node),
14 | level: 'success'
15 | });
16 | },
17 |
18 | componentDidMount: function () {
19 | this._notificationSystem = this.refs.notificationSystem;
20 | },
21 |
22 | render: function () {
23 |
24 | var data = {
25 | contacts: {
26 | display: "Contacts",
27 | icon: "user",
28 | nodes: {
29 | newContact: {
30 | display: "New contact",
31 | icon: "user-plus"
32 | },
33 | search: {
34 | display: "Search contacts",
35 | icon: "search"
36 | }
37 | }
38 | },
39 | customization: {
40 | display: "Customization",
41 | icon: "wrench",
42 | nodes: {
43 | entities: {
44 | display: "Entities",
45 | icon: "database",
46 | nodes: {
47 | new: {
48 | display: "New entity",
49 | icon: "plus",
50 | route: {
51 | name: "new",
52 | params: {
53 | entity: "entity"
54 | }
55 | }
56 | },
57 | search: {
58 | display: "Search entities",
59 | icon: "search"
60 | }
61 | }
62 | }
63 | }
64 | }
65 | };
66 |
67 | return
68 |
73 |
74 |
;
75 | }
76 | });
77 |
78 | export default Demo;
79 |
--------------------------------------------------------------------------------
/demo/pages/Root.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Router from 'react-router';
3 |
4 | const Root = React.createClass({
5 | render() {
6 | return
7 | {this.props.children}
8 |
;
9 | }
10 | });
11 |
12 |
13 | export default Root;
14 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Sat Jun 20 2015 11:42:43 GMT-0300 (E. South America Standard Time)
3 | require('babel-core/register');
4 |
5 | var webpackConfig = require('./webpack/webpack.config.test.js');
6 | webpackConfig.devtool = 'inline-source-map';
7 | webpackConfig.watch = true;
8 |
9 | module.exports = function (config) {
10 | config.set({
11 |
12 | // base path that will be used to resolve all patterns (eg. files, exclude)
13 | basePath: '',
14 |
15 | // frameworks to use
16 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
17 | frameworks: [
18 | 'mocha',
19 | 'sinon-chai'
20 | ],
21 |
22 | // list of files / patterns to load in the browser
23 | files: [
24 | 'test/index.js'
25 | ],
26 |
27 | // preprocess matching files before serving them to the browser
28 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
29 | preprocessors: {
30 | 'test/index.js': ['webpack', 'sourcemap']
31 | },
32 |
33 | webpack: webpackConfig,
34 |
35 | // this is so that the tests will stop printing information about the files being packed
36 | webpackMiddleware: {
37 | noInfo: true
38 | },
39 |
40 | // test results reporter to use
41 | // possible values: 'dots', 'progress'
42 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
43 | reporters: ['progress'],
44 |
45 |
46 | // web server port
47 | port: 9876,
48 |
49 |
50 | // enable / disable colors in the output (reporters and logs)
51 | colors: true,
52 |
53 |
54 | // level of logging
55 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
56 | logLevel: config.LOG_INFO,
57 |
58 |
59 | // enable / disable watching file and executing tests whenever any file changes
60 | autoWatch: true,
61 |
62 |
63 | // start these browsers
64 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
65 | browsers: ['Chrome'],
66 |
67 |
68 | // Continuous Integration mode
69 | // if true, Karma captures browsers, runs the tests and exits
70 | singleRun: false
71 | });
72 | };
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-vnav",
3 | "version": "0.1.8",
4 | "description": "A React menu component compatible with react-router",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "build-demo": "babel-node ./tools/build-demo.js",
8 | "build-lib": "babel-node ./tools/build-lib.js",
9 | "start-demo": "babel-node ./demo/Server.js",
10 | "wpds": "webpack-dev-server --config ./webpack.config.demo.dev.js",
11 | "test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS",
12 | "test-chrome": "./node_modules/.bin/karma start --single-run --browsers Chrome"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/gearz-lab/react-vnav.git"
17 | },
18 | "keywords": [
19 | "react",
20 | "navigation",
21 | "react-router",
22 | "menu"
23 | ],
24 | "author": "Andre Pena",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/gearz-lab/react-vnav/issues"
28 | },
29 | "homepage": "https://github.com/gearz-lab/react-vnav#readme",
30 | "devDependencies": {
31 | "babel": "^5.8.23",
32 | "babel-core": "^5.8.25",
33 | "babel-eslint": "^4.1.3",
34 | "babel-loader": "^5.3.2",
35 | "bootstrap": "^3.3.5",
36 | "chai": "^3.4.1",
37 | "child-process-promise": "^1.1.0",
38 | "css-loader": "^0.19.0",
39 | "es5-shim": "^4.3.1",
40 | "extract-text-webpack-plugin": "^0.9.1",
41 | "file-loader": "^0.8.4",
42 | "fs-extra-promise": "^0.3.1",
43 | "json-loader": "^0.5.3",
44 | "karma": "~0.13.10",
45 | "karma-chrome-launcher": "~0.2.1",
46 | "karma-cli": "0.1.1",
47 | "karma-coverage": "^0.5.2",
48 | "karma-coveralls": "^1.1.2",
49 | "karma-firefox-launcher": "~0.1.6",
50 | "karma-mocha": "~0.2.0",
51 | "karma-mocha-reporter": "^1.1.1",
52 | "karma-phantomjs-launcher": "^0.2.1",
53 | "karma-sinon-chai": "^1.1.0",
54 | "karma-sourcemap-loader": "^0.3.5",
55 | "karma-webpack": "^1.7.0",
56 | "less": "^2.5.3",
57 | "less-loader": "^2.2.1",
58 | "mocha": "^2.3.4",
59 | "phantomjs": "^1.9.19",
60 | "raw-loader": "^0.5.1",
61 | "react-hot-loader": "^1.3.0",
62 | "react-notification-system": "^0.2.6",
63 | "rimraf-promise": "^2.0.0",
64 | "style-loader": "^0.12.3",
65 | "webpack-dev-server": "^1.12.1"
66 | },
67 | "dependencies": {
68 | "babel-runtime": "^5.8.25",
69 | "clone": "^1.0.2",
70 | "deep-freeze": "0.0.1",
71 | "express": "^4.13.3",
72 | "font-awesome": "^4.4.0",
73 | "history": "^1.13.1",
74 | "react": "^0.14.3",
75 | "react-addons-update": "^0.14.3",
76 | "react-dom": "^0.14.3",
77 | "react-router": "^1.0.0",
78 | "react-select": "^1.0.0-beta5",
79 | "underscore": "^1.8.3",
80 | "webpack": "^1.12.8"
81 | }
82 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | React-vnav
2 | ===
3 |
4 | `React-vnav` is a [React](https://facebook.github.io/react/) library for building vertical main menus.
5 |
6 | 
7 |
8 | React-vnav is under active development. APIs might change. Features:
9 |
10 | - Search
11 | - Collapse
12 | - Custom search bar buttons
13 |
14 | Features that will probably be introduced until version 1.0:
15 |
16 | - Search highlight
17 | - Keyboard navigation
18 | - Better integration with react-router. Today, routing must be done manually listening for node click events
19 | - Ability to mark active menu item automatically based on react-router
20 | - Drag and drop
21 | - Built in button for creating folders
22 | - Remove dependency on bootstrap and font-awesome LESS files
23 |
24 | Demo
25 | ---
26 |
27 | You can check the [online demo here](http://gearz-lab.github.io/react-vnav/demo.html).
28 |
29 | Installing
30 | ---
31 |
32 | For now, `react-vnav` is only supports `npm`.
33 |
34 | Install:
35 |
36 | npm install react-vnav --save
37 |
38 | Using
39 | ---
40 |
41 | import VNav from 'react-vnav';
42 | ...
43 | var menuData = {
44 | contacts: {
45 | display: "Contacts",
46 | icon: "user",
47 | nodes: {
48 | newContact: {
49 | display: "New contact",
50 | icon: "user-plus"
51 | },
52 | search: {
53 | display: "Search contacts",
54 | icon: "search"
55 | }
56 | }
57 | }
58 | };
59 | ...
60 | handleItemClick: function (node) {
61 | console.log(node);
62 | },
63 | ...
64 |
65 |
66 | Check the [demo source code](https://github.com/gearz-lab/react-vnav/blob/master/demo/pages/Demo.js) for a complete working example;
67 |
68 | Props
69 | ---
70 |
71 | Prop | Type | Description
72 | --- | --- | ---
73 | nodes | Object | A JavaScript that describes the nodes. Each property in the object represents a node. Nodes can have whatever properties you want. Special properties are: `display`: The display text. `icon`: The name of the font-awesome icon to use. `nodes`: The children nodes.
74 | clearSearchText | string | The text to be displayed next to the "clear search" button
75 | onItemClick | function | Function that will be called when an item is clicked
76 | searchBarButtons | array | Array of buttons that will be placed next to the search input. Each item of the array is a JavaScript object. Each object has the following properties: `icon`: The font-awesome icon. `onClick`: The function that will be called when the button is clicked.
77 |
78 | Adding the styles
79 | ---
80 |
81 | Currently `react-vnav` requires the `bootstrap` and the `font-awesome` less files to be included.
82 |
83 | @import 'bootstrap/less/bootstrap';
84 | @import 'font-awesome/less/font-awesome';
85 | @import 'react-vnav/lib/less/vnav.less';
86 |
87 | Running the demo locally
88 | ---
89 |
90 | **In development mode**
91 |
92 | - Open 2 console windows. Make sure you your `NODE_ENV` is `development` on both consoles. On Windows, you should type `SET NODE_ENV=development`. On OSX and Linux, this should be `export NODE_ENV=development`.
93 | - On the first one, type `npm run wpds`. On the second, type `npm run start-demo`.
94 | - The demo is available at: `http://localhost:4000/react-vnav/demo.html`.
95 |
96 | **In production mode**
97 |
98 | - Open 1 console window
99 | - Type `npm run build-demo`. Now there should be a folder called `demo-built`.
100 | - Type `npm run start-demo`.
101 | - The demo is available at: `http://localhost:4000/react-vnav/demo.html`.
102 |
103 | Contributing
104 | ---
105 |
106 | **Pull-requests are really really welcome**.
107 |
108 | I'll be more than glad to invite frequent contributors to join the organization.
109 | If you need help understanding the project, please post an issue and I'll do my best to reply and make sure you understand everything
110 | you need.
111 |
112 | In order to make a pull request:
113 |
114 | 1. Fork it.
115 | 2. Create your feature-branch git checkout -b your-new-feature-branch
116 | 3. Commit your change git commit -am 'Add new feature'
117 | 4. Push to the branch git push origin your-new-feature-branch
118 | 5. Create new Pull Request with master branch
119 |
120 | License
121 | ---
122 | `React-vnav` is [MIT](https://github.com/gearz-lab/react-vnav/blob/master/LICENSE) licensed.
123 |
--------------------------------------------------------------------------------
/src/components/Icon.js:
--------------------------------------------------------------------------------
1 | var React = require("react");
2 |
3 | var Icon = React.createClass({
4 | /**
5 | * ReactJS rendering function.
6 | * @returns {XML}
7 | */
8 | render: function () {
9 | return
10 | }
11 | });
12 |
13 | export default Icon;
--------------------------------------------------------------------------------
/src/components/VNav.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from 'react-dom';
3 | import update from 'react-addons-update';
4 | import VNavItem from './VNavItem';
5 | import Icon from './Icon';
6 | import menuHelper from '../lib/menuHelper.js';
7 | import clone from 'clone';
8 |
9 | var VNav = React.createClass({
10 |
11 | PropTypes: {
12 | clearSearchText: React.PropTypes.string,
13 | nodes: React.PropTypes.object.isRequired,
14 | onItemClick: React.PropTypes.func,
15 | searchBarButtons: React.PropTypes.array
16 | },
17 |
18 | getDefaultProps: function () {
19 | return {
20 | clearSearchText: "Clear search"
21 | }
22 | },
23 |
24 |
25 | getInitialState: function () {
26 | return {
27 | search: null
28 | }
29 | },
30 |
31 | handleTextChange: function (event) {
32 | this.setState({search: event.target.value});
33 | },
34 |
35 | handlerClearSearch: function (event) {
36 | this.setState({search: null}, function () {
37 | ReactDOM.findDOMNode(this.refs.searchInput).focus();
38 | });
39 | },
40 |
41 | /**
42 | * ReactJS rendering function.
43 | * @returns {XML}
44 | */
45 | render: function () {
46 |
47 | let nodesClone = clone(this.props.nodes);
48 | let nodes = this.state.search ? menuHelper.filterNodes(nodesClone, this.state.search) : nodesClone;
49 |
50 | let searchClearer = this.state.search ?
51 | {this.props.clearSearchText}
52 |
53 |
54 |
55 |
: null;
56 |
57 | let input = ;
59 |
60 | let inputGroup = this.props.searchBarButtons ?
61 |
62 | {input}
63 | {this.props.searchBarButtons.map((b, i) => {
64 | return b.onClick(b.id, e)}>
65 |
66 |
67 | })}
68 |
69 | : input;
70 |
71 | return
72 |
73 | { inputGroup }
74 |
75 |
76 | {searchClearer}
77 |
78 |
79 | {menuHelper.createVNavItemsFromNodes(nodes, this.props.onItemClick)}
80 |
81 |
;
82 | }
83 | });
84 |
85 | export default VNav;
--------------------------------------------------------------------------------
/src/components/VNavItem.js:
--------------------------------------------------------------------------------
1 | var React = require("react");
2 | import Icon from './Icon';
3 | import menuHelper from '../lib/menuHelper';
4 | import update from 'react-addons-update';
5 |
6 | var VNavItem = React.createClass({
7 |
8 | PropTypes: {
9 | node: React.PropTypes.object.isRequired,
10 | onClick: React.PropTypes.func
11 | },
12 |
13 | getInitialState: function () {
14 | return {
15 | collapsed: false
16 | }
17 | },
18 |
19 | handleOnClick: function () {
20 | if (this.props.node.nodes) {
21 | this.setState({collapsed: !this.state.collapsed});
22 | }
23 | if (this.props.onClick) {
24 | this.props.onClick(this.props.node);
25 | }
26 | },
27 |
28 | /**
29 | * ReactJS rendering function.
30 | * @returns {XML}
31 | */
32 | render: function () {
33 |
34 | let childrenWrapper = null;
35 | if (this.props.node.nodes && this.state.collapsed === false) {
36 | childrenWrapper =
37 | {menuHelper.createVNavItemsFromNodes(this.props.node.nodes, this.props.onClick)}
38 |
;
39 | }
40 |
41 | let vNavIconTextClass = this.props.node.icon ? "vnav-item-text with-icon" : "vnav-item-text";
42 | let plusWrapper = this.props.node.nodes ?
43 |
44 | : null;
45 |
46 | return
47 |
48 | {this.props.node.icon ? : null }
49 | {this.props.node.display}
50 | {plusWrapper}
51 |
52 | {childrenWrapper}
53 |
54 | }
55 | });
56 |
57 | export default VNavItem;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import VNav from './components/VNav';
2 | export default VNav;
--------------------------------------------------------------------------------
/src/less/vnav.less:
--------------------------------------------------------------------------------
1 | @vnav-background-color: @gray-lighter;
2 | @vnav-item-background-color: white;
3 | @vnav-color: @gray;
4 | @vnav-border-color: @table-border-color;
5 |
6 | .vnav {
7 | background-color: @vnav-background-color;
8 | border-width: 1px;
9 | border-style: solid;
10 | border-color: @vnav-border-color;
11 | border-radius: @border-radius-base;
12 | }
13 |
14 | .vnav-children-wrapper {
15 | padding-left: 10px;
16 | }
17 |
18 | .search-wrapper {
19 | padding: 10px;
20 | }
21 |
22 | .search-clearer {
23 | background-color: @alert-warning-bg;
24 | border-color: @alert-warning-border;
25 | border-width: 1px;
26 | border-style: solid;
27 | color: @state-warning-text;
28 | padding: @padding-base-vertical @padding-large-horizontal;
29 | }
30 |
31 | .search-clearer i.fa {
32 | margin-top: 3px;
33 | float: right;
34 | cursor: pointer;
35 | }
36 |
37 | .search-wrapper .input-group-addon {
38 | border-left: 0;
39 | cursor: pointer;
40 |
41 | // no-select
42 | -webkit-touch-callout: none;
43 | -webkit-user-select: none;
44 | -khtml-user-select: none;
45 | -moz-user-select: none;
46 | -ms-user-select: none;
47 | user-select: none;
48 | }
49 |
50 | .vnav-item {
51 | background-color: @vnav-item-background-color;
52 | padding: @padding-base-vertical @padding-large-horizontal;
53 | border-width: 1px 0 0 1px;
54 | border-style: solid;
55 | border-color: @vnav-border-color;
56 | cursor: pointer;
57 |
58 | // no-select
59 | -webkit-touch-callout: none;
60 | -webkit-user-select: none;
61 | -khtml-user-select: none;
62 | -moz-user-select: none;
63 | -ms-user-select: none;
64 | user-select: none;
65 | }
66 |
67 | // first vnav-items hierarchically
68 | .children-wrapper > .vnav-item-wrapper > .vnav-item {
69 | border-width: 1px 0 0 0;
70 | }
71 |
72 | .vnav-item-text.with-icon {
73 | margin-left: 10px;
74 | }
75 |
76 | .vnav-icon {
77 | }
78 |
79 | .vnav-item .plus-wrapper {
80 | margin-top: 3px;
81 | float: right;
82 | }
--------------------------------------------------------------------------------
/src/lib/menuHelper.js:
--------------------------------------------------------------------------------
1 | import VNavItem from '../components/VNavItem';
2 | import _ from 'underscore';
3 | import React from 'react';
4 |
5 | class MenuHelper {
6 |
7 | /**
8 | * Creates VNavItems from an Object containing nodes (each property in the nodes object represents a node)
9 | * @param nodes
10 | * @param onItemClick
11 | * @returns {*}
12 | */
13 | createVNavItemsFromNodes(nodes, onItemClick) {
14 | if (!nodes) {
15 | return null;
16 | }
17 | return Object.keys(nodes).map((n, i) => this.createVNavItemFromNode(nodes[n], i, onItemClick));
18 | }
19 |
20 | /**
21 | * Creates a VNavItem from a JSON node
22 | * @param node
23 | * @param key
24 | * @param onItemClick
25 | * @returns {XML}
26 | */
27 | createVNavItemFromNode(node, key, onItemClick) {
28 | return
29 | }
30 |
31 | /**
32 | * Filter the given nodes. "nodes" is an object in which each property is a node
33 | * @param nodes
34 | * @param filterString
35 | */
36 | filterNodes(nodes, filterString) {
37 | let result = {};
38 | for (let node in nodes) {
39 | let filteredNode = this.filterNode(nodes[node], filterString);
40 | if (filteredNode) {
41 | result[node] = filteredNode;
42 | }
43 | }
44 | return result;
45 | }
46 |
47 | /**
48 | * Filters the given node and returns it.
49 | * @param node
50 | * @param filterString
51 | */
52 | filterNode(node, filterString) {
53 | if (node.display.match(new RegExp(filterString, 'i'))) {
54 | return node;
55 | }
56 | else if (node.nodes) {
57 | let filteredChildren = this.filterNodes(node.nodes, filterString);
58 | if (Object.keys(filteredChildren).length) {
59 | node.nodes = filteredChildren;
60 | return node;
61 | }
62 | }
63 | return null;
64 | }
65 | }
66 |
67 | export default new MenuHelper();
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import 'es5-shim';
2 |
3 | beforeEach(function() {
4 | sinon.stub(console, 'warn');
5 | });
6 |
7 | afterEach(function() {
8 | if (typeof console.warn.restore === 'function') {
9 | assert(!console.warn.called, () => {
10 | return `${console.warn.getCall(0).args[0]} \nIn '${this.currentTest.fullTitle()}'`;
11 | });
12 | console.warn.restore();
13 | }
14 | });
15 |
16 | describe('Process environment for tests', function () {
17 | it('Should be development for React console warnings', function () {
18 | assert.equal(process.env.NODE_ENV, 'development');
19 | });
20 | });
21 |
22 | const testsContext = require.context('.', true, /Spec$/);
23 | testsContext.keys().forEach(testsContext);
24 |
--------------------------------------------------------------------------------
/test/menuHelperSpec.js:
--------------------------------------------------------------------------------
1 | import menuHelper from '../src/lib/menuHelper';
2 |
3 | describe('menuHelper', function() {
4 | it('should search', function() {
5 | var data = {
6 | customization: {
7 | display: "Customization",
8 | nodes: {
9 | entities: {
10 | display: "Entities",
11 | nodes: {
12 | new: {
13 | display: "New",
14 | route: {
15 | name: "new",
16 | params: {
17 | entity: "entity"
18 | }
19 | }
20 | },
21 | search: {
22 | display: "search",
23 | icon: "search"
24 | }
25 | }
26 | }
27 | }
28 | }
29 | };
30 |
31 | let filtered = menuHelper.filterNodes(data, 'fuckl');
32 | console.log(filtered);
33 | assert.ok(1);
34 | })
35 | });
--------------------------------------------------------------------------------
/tools/build-demo.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import colors from 'colors';
3 | import React from 'react';
4 | import path from 'path';
5 | import rimraf from 'rimraf-promise';
6 | import fsep from 'fs-extra-promise';
7 | import { exec } from 'child-process-promise';
8 | import routes from '../demo/Routes.js';
9 | import { renderToString } from 'react-dom/server'
10 | import { match, RoutingContext } from 'react-router'
11 |
12 | require.extensions['.txt'] = function (module, filename) {
13 | module.exports = fs.readFileSync(filename, 'utf8');
14 | };
15 |
16 | const repoRoot = path.resolve(__dirname, '../');
17 | const demoBuilt = path.join(repoRoot, 'demo-built');
18 |
19 | const licenseSrc = path.join(repoRoot, 'LICENSE');
20 | const licenseDest = path.join(demoBuilt, 'LICENSE');
21 |
22 | console.log('building demo'.green);
23 | if(process.env.NODE_ENV !== 'production') {
24 | console.log(`build-docs can only run in production. Current NODE_ENV: ${process.env.NODE_ENV}`.red);
25 | process.exit();
26 | }
27 |
28 | rimraf(demoBuilt)
29 | .then(() => fsep.mkdir(demoBuilt))
30 | .then(() => {
31 | console.log('writing static page files...');
32 |
33 | let wrap = require('../demo/pages/BasePage.txt')
34 | .replace(/\$\{cssBundlePath\}/g, 'assets/main.css')
35 | .replace(/\$\{jsBundlePath\}/g, 'assets/bundle.js');
36 |
37 | let demoHtmlPath = path.join(demoBuilt, 'demo.html');
38 | return fsep.writeFile(demoHtmlPath, wrap);
39 |
40 | }).catch(e=> console.log(e))
41 | .then(() => {
42 | console.log('running webpack on webpack.config.demo.prod.js...');
43 | return exec(`webpack --config webpack.config.demo.prod.js`);
44 | })
45 | .then(() => fsep.copyAsync(licenseSrc, licenseDest))
46 | .then(() => console.log('demo built'.green));
47 |
--------------------------------------------------------------------------------
/tools/build-dist.js:
--------------------------------------------------------------------------------
1 | var rimraf = require('rimraf-promise');
2 | var colors = require('colors');
3 | var exec = require('child-process-promise').exec;
4 |
5 | console.log('building dist'.green);
6 | rimraf('./dist').then(function (error) {
7 | var webpackCli = 'webpack --config webpack.config.dist.js';
8 | return exec(webpackCli).fail(function (error) {
9 | console.log(colors.red(error))
10 | });
11 | }).then(() => console.log('dist built'.green));
12 |
13 |
--------------------------------------------------------------------------------
/tools/build-lib.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | var fsep = require('fs-extra-promise');
3 | var rimraf = require('rimraf-promise');
4 | var colors = require('colors');
5 | var exec = require('child-process-promise').exec;
6 |
7 | console.log('building lib'.green);
8 |
9 | const repoRoot = path.resolve(__dirname, '../');
10 | const lib = path.join(repoRoot, 'lib');
11 | const lessSrc = path.join(repoRoot, '/src/less');
12 | const lessDest = path.join(lib, '/less');
13 |
14 | rimraf(lib)
15 | .then(function (error) {
16 | let babelCli = 'babel --optional es7.objectRestSpread ./src --out-dir ./lib';
17 | return exec(babelCli).fail(function (error) {
18 | console.log(colors.red(error))
19 | });
20 | })
21 | .then(() => fsep.copyAsync(lessSrc, lessDest))
22 | .then(() => console.log('lib built'.green));
23 |
--------------------------------------------------------------------------------
/webpack.config.demo.dev.js:
--------------------------------------------------------------------------------
1 | /* eslint no-var: 0 */
2 | require('babel-core/register');
3 | var config = require('./webpack/webpack.config.demo.dev.js');
4 | module.exports = config;
5 |
--------------------------------------------------------------------------------
/webpack.config.demo.prod.js:
--------------------------------------------------------------------------------
1 | /* eslint no-var: 0 */
2 | require('babel-core/register');
3 | var config = require('./webpack/webpack.config.demo.prod.js');
4 | module.exports = config;
5 |
--------------------------------------------------------------------------------
/webpack.config.dist.js:
--------------------------------------------------------------------------------
1 | /* eslint no-var: 0 */
2 | require('babel-core/register');
3 | var config = require('./webpack/webpack.config.dist');
4 | module.exports = config;
5 |
--------------------------------------------------------------------------------
/webpack/webpack.config.demo.dev.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 |
3 | export default {
4 | entry: [
5 | 'webpack-dev-server/client?http://localhost:8082',
6 | 'webpack/hot/only-dev-server',
7 | './demo/Client.js'
8 | ],
9 |
10 | output: {
11 | filename: 'bundle.js',
12 | path: './demo-built/assets',
13 | publicPath: 'http://localhost:8082/assets/'
14 | },
15 |
16 | externals: undefined,
17 |
18 | resolve: {
19 | extensions: ['', '.js', '.json']
20 | },
21 |
22 | module: {
23 | loaders: [
24 | {test: /\.js/, loaders: ['react-hot', 'babel-loader'], exclude: /node_modules/ },
25 | {test: /\.jsx/, loaders: ['react-hot', 'babel-loader'], exclude: /node_modules/ },
26 | {test: /\.css/, loader: 'style-loader!css-loader'},
27 | {test: /\.less$/, loader: 'style!css!less'},
28 | {test: /\.json$/, loader: 'json'},
29 | {test: /\.jpe?g$|\.gif$|\.png$|\.ico$/, loader: 'file?name=[name].[ext]'},
30 | {test: /\.eot|\.ttf|\.svg|\.woff2?/, loader: 'file?name=[name].[ext]'},
31 | {test: /\.txt/, loader: 'raw'}
32 | ]
33 | },
34 |
35 | plugins: [
36 | new webpack.HotModuleReplacementPlugin(),
37 | new webpack.NoErrorsPlugin(),
38 | new webpack.DefinePlugin({
39 | 'process.env': {
40 | NODE_ENV: JSON.stringify('development'),
41 | APP_ENV: JSON.stringify('browser')
42 | }
43 | })
44 | ],
45 |
46 | // the configuration above does not apply to the webpack-dev-server...
47 | // webpack-dev-server is configured below
48 | devServer: {
49 | contentBase: "./demo-built",
50 | hot: true,
51 | noInfo: true,
52 | headers: { 'Access-Control-Allow-Origin': '*' },
53 | port: 8082
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/webpack/webpack.config.demo.prod.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import ExtractTextPlugin from 'extract-text-webpack-plugin';
3 |
4 | export default {
5 | entry: [
6 | './demo/Client.js'
7 | ],
8 |
9 | output: {
10 | filename: 'bundle.js',
11 | path: './demo-built/assets',
12 | publicPath: '/react-vnav/assets/'
13 | },
14 |
15 | externals: undefined,
16 |
17 | resolve: {
18 | extensions: ['', '.js', '.json', 'txt']
19 | },
20 |
21 | module: {
22 | loaders: [
23 | {test: /\.js/, loaders: ['babel'], exclude: /node_modules/ },
24 | {test: /\.jsx/, loaders: ['babel'], exclude: /node_modules/ },
25 | {test: /\.css/, loader: ExtractTextPlugin.extract("style-loader", "css-loader")},
26 | {test: /\.less$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader")},
27 | {test: /\.json$/, loader: 'json'},
28 | {test: /\.jpe?g$|\.gif$|\.png$|\.ico$/, loader: 'file?name=[name].[ext]'},
29 | {test: /\.eot|\.ttf|\.svg|\.woff2?/, loader: 'file?name=[name].[ext]'},
30 | {test: /\.txt/, loader: 'raw'}
31 | ]
32 | },
33 |
34 | plugins: [
35 | new ExtractTextPlugin('[name].css'),
36 | new webpack.optimize.UglifyJsPlugin({minimize: true}),
37 | new webpack.DefinePlugin({
38 | 'process.env': {
39 | NODE_ENV: JSON.stringify('production'),
40 | APP_ENV: JSON.stringify('browser')
41 | }
42 | })
43 | ]
44 | };
45 |
--------------------------------------------------------------------------------
/webpack/webpack.config.dist.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import ExtractTextPlugin from 'extract-text-webpack-plugin';
3 |
4 | export default {
5 | entry: [
6 | './src/index.js'
7 | ],
8 |
9 | output: {
10 | filename: '[name].js',
11 | path: './dist',
12 | library: 'react-metaform',
13 | libraryTarget: 'umd'
14 | },
15 |
16 | externals: [
17 | {
18 | 'react': {
19 | root: 'React',
20 | commonjs2: 'react',
21 | commonjs: 'react',
22 | amd: 'react'
23 | }
24 | }
25 | ],
26 |
27 | resolve: {
28 | extensions: ['', '.js', '.json', 'txt']
29 | },
30 |
31 | module: {
32 | loaders: [
33 | {test: /\.js/, loaders: ['babel'], exclude: /node_modules/ },
34 | {test: /\.jsx/, loaders: ['babel'], exclude: /node_modules/ },
35 | {test: /\.css/, loader: ExtractTextPlugin.extract("style-loader", "css-loader")},
36 | {test: /\.less$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader")},
37 | {test: /\.json$/, loader: 'json'},
38 | {test: /\.jpe?g$|\.gif$|\.png$|\.ico$/, loader: 'file?name=[name].[ext]'},
39 | {test: /\.eot|\.ttf|\.svg|\.woff2?/, loader: 'file?name=[name].[ext]'},
40 | {test: /\.txt/, loader: 'raw'}
41 | ]
42 | },
43 |
44 | plugins: [
45 | new ExtractTextPlugin('[name].css'),
46 | new webpack.optimize.UglifyJsPlugin({minimize: true}),
47 | new webpack.DefinePlugin({
48 | 'process.env': {
49 | NODE_ENV: "'production'",
50 | APP_ENV: JSON.stringify('browser')
51 | }
52 | })
53 | ]
54 | };
55 |
--------------------------------------------------------------------------------
/webpack/webpack.config.test.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 |
3 | export default {
4 |
5 | devtool: 'inline-source-map',
6 |
7 | module: {
8 | loaders: [
9 | {test: /\.js/, loaders: ['react-hot', 'babel-loader'], exclude: /node_modules/ },
10 | {test: /\.jsx/, loaders: ['react-hot', 'babel-loader'], exclude: /node_modules/ },
11 | {test: /\.css/, loader: 'style-loader!css-loader'},
12 | {test: /\.less$/, loader: 'style!css!less'},
13 | {test: /\.json$/, loader: 'json'},
14 | {test: /\.jpe?g$|\.gif$|\.png$|\.ico$/, loader: 'file?name=[name].[ext]'},
15 | {test: /\.eot|\.ttf|\.svg|\.woff2?/, loader: 'file?name=[name].[ext]'}
16 | ]
17 | },
18 |
19 | plugins: [
20 | new webpack.DefinePlugin({
21 | 'process.env': {
22 | 'NODE_ENV': "'development'",
23 | APP_ENV: JSON.stringify('browser')
24 | }
25 | })
26 | ]
27 |
28 | };
29 |
--------------------------------------------------------------------------------