├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── circle.yml ├── dev-server.js ├── dev.env ├── package.json ├── readme.MD ├── src ├── components │ └── error-message.js ├── init │ ├── index.html │ ├── main.js │ └── router.js ├── layout │ ├── app.js │ ├── components │ │ ├── gmap.js │ │ ├── index.js │ │ ├── page-top.js │ │ ├── search-bar.js │ │ └── sidebar.js │ └── login.js ├── lib │ └── event-bus.js └── page │ ├── about.js │ ├── button-demo.js │ ├── input-demo.js │ ├── modal-demo.js │ ├── not-found.js │ ├── notifications-demo.js │ ├── progress-bars.js │ ├── table-demo.js │ ├── tabs-demo.js │ └── welcome.js ├── test ├── page-test.js └── setup.js ├── webpack.config.dev.js └── webpack.config.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "env": { 4 | "development": { 5 | "presets": ["react-hmre"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.js] 13 | indent_size = 2 14 | 15 | [*.jsx] 16 | indent_size = 2 17 | 18 | [*.json] 19 | indent_size = 2 20 | 21 | [*.scss] 22 | indent_size = 2 23 | 24 | [*.sql] 25 | indent_size = 2 26 | 27 | [*.yml] 28 | indent_size = 2 29 | 30 | [Makefile] 31 | indent_style = tab 32 | 33 | [secrets/*] 34 | insert_final_newline = false 35 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | dist/** 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "babel-eslint", // https://github.com/babel/babel-eslint 4 | "env": { // http://eslint.org/docs/user-guide/configuring.html#specifying-environments 5 | "browser": true, // browser global variables 6 | "node": true, // Node.js global variables and Node.js-specific rules 7 | "es6": true 8 | }, 9 | "plugins": [ 10 | "react", 11 | ], 12 | { 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "arrowFunctions": true, 16 | "blockBindings": true, 17 | "classes": true, 18 | "defaultParams": true, 19 | "destructuring": true, 20 | "forOf": true, 21 | "generators": false, 22 | "modules": true, 23 | "objectLiteralComputedProperties": true, 24 | "objectLiteralDuplicateProperties": false, 25 | "objectLiteralShorthandMethods": true, 26 | "objectLiteralShorthandProperties": true, 27 | "spread": true, 28 | "superInFunctions": true, 29 | "templateStrings": true, 30 | "jsx": true 31 | }, 32 | } 33 | }, 34 | "rules": { 35 | /** 36 | * Strict mode 37 | */ 38 | // babel inserts "use strict"; for us 39 | "strict": [2, "never"], // http://eslint.org/docs/rules/strict 40 | 41 | /** 42 | * ES6 43 | */ 44 | "no-var": 2, // http://eslint.org/docs/rules/no-var 45 | "prefer-const": 0, // http://eslint.org/docs/rules/prefer-const 46 | 47 | /** 48 | * Variables 49 | */ 50 | "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow 51 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names 52 | "no-unused-vars": [ 2, {"args": "none"} ], 53 | "no-use-before-define": 0, // http://eslint.org/docs/rules/no-use-before-define 54 | 55 | /** 56 | * Possible errors 57 | */ 58 | "comma-dangle": [2, "always-multiline"], // http://eslint.org/docs/rules/comma-dangle 59 | "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign 60 | "no-console": 1, // http://eslint.org/docs/rules/no-console 61 | "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger 62 | "no-alert": 1, // http://eslint.org/docs/rules/no-alert 63 | "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition 64 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys 65 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case 66 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty 67 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign 68 | "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast 69 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi 70 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign 71 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations 72 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp 73 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace 74 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls 75 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays 76 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable 77 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan 78 | "block-scoped-var": 0, // http://eslint.org/docs/rules/block-scoped-var 79 | 80 | /** 81 | * Best practices 82 | */ 83 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return 84 | "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly 85 | "default-case": 2, // http://eslint.org/docs/rules/default-case 86 | "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation 87 | "allowKeywords": true 88 | }], 89 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq 90 | "guard-for-in": 0, // http://eslint.org/docs/rules/guard-for-in 91 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller 92 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return 93 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null 94 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval 95 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native 96 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind 97 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough 98 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal 99 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval 100 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks 101 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func 102 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str 103 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign 104 | "no-new": 2, // http://eslint.org/docs/rules/no-new 105 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func 106 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers 107 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal 108 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape 109 | "no-param-reassign": 0, // http://eslint.org/docs/rules/no-param-reassign 110 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto 111 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare 112 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign 113 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url 114 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare 115 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences 116 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal 117 | "no-with": 2, // http://eslint.org/docs/rules/no-with 118 | "radix": 2, // http://eslint.org/docs/rules/radix 119 | "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top 120 | "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife 121 | "yoda": 2, // http://eslint.org/docs/rules/yoda 122 | 123 | /** 124 | * Style 125 | */ 126 | "indent": [2, 2], // http://eslint.org/docs/rules/indent 127 | "brace-style": [2, // http://eslint.org/docs/rules/brace-style 128 | "1tbs", { 129 | "allowSingleLine": true 130 | }], 131 | "quotes": [ 132 | 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes 133 | ], 134 | "camelcase": [2, { // http://eslint.org/docs/rules/camelcase 135 | "properties": "never" 136 | }], 137 | "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing 138 | "before": false, 139 | "after": true 140 | }], 141 | "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style 142 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last 143 | "func-names": 0, // http://eslint.org/docs/rules/func-names 144 | "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing 145 | "beforeColon": false, 146 | "afterColon": true 147 | }], 148 | "new-cap": [2, { // http://eslint.org/docs/rules/new-cap 149 | "newIsCap": true 150 | }], 151 | "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines 152 | "max": 2 153 | }], 154 | "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary 155 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object 156 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func 157 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces 158 | "no-extra-parens": [2, "functions"], // http://eslint.org/docs/rules/no-extra-parens 159 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle 160 | "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var 161 | "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks 162 | "semi": [2, "always"], // http://eslint.org/docs/rules/semi 163 | "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing 164 | "before": false, 165 | "after": true 166 | }], 167 | "keyword-spacing": [2, {"before": true, "after": true, "overrides": {}}], // http://eslint.org/docs/rules/keyword-spacing 168 | "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks 169 | "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren 170 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops 171 | "spaced-comment": [0, "always", { // http://eslint.org/docs/rules/spaced-comment 172 | "exceptions": ["*"], 173 | "markers": ["*"] 174 | }], 175 | 176 | // react 177 | "react/jsx-indent": [ 2, 2 ], 178 | "react/jsx-indent-props": [ 2, 2 ], 179 | "react/jsx-no-duplicate-props": [ 2 ], 180 | "react/jsx-no-undef": 2, 181 | "react/jsx-sort-prop-types": 0, 182 | "react/jsx-sort-props": 0, 183 | "react/jsx-uses-react": [ 2 ], 184 | "react/jsx-uses-vars": 2, 185 | "react/no-did-mount-set-state": [ 2 ], 186 | "react/no-did-update-set-state": [ 2 ], 187 | "react/no-multi-comp": 2, // TODO 188 | "react/no-unknown-property": 2, 189 | "react/prop-types": [ 2, { "ignore" : [ "children" ]} ], 190 | "react/react-in-jsx-scope": 2, // TODO 191 | "react/self-closing-comp": 2, 192 | "react/wrap-multilines": [ 2 ], 193 | "react/sort-comp": [2, { 194 | "order": [ 195 | "displayName", 196 | "statics", 197 | "contexts", 198 | "mixins", 199 | "props", 200 | "constructor", 201 | "state", 202 | "lifecycle", 203 | "listeners", 204 | "everything-else", 205 | "renderers" 206 | ], 207 | "groups": { 208 | "contexts": [ 209 | "contextTypes", 210 | "childContextTypes", 211 | "getChildContext", 212 | ], 213 | "lifecycle": [ 214 | "componentWillMount", 215 | "componentDidMount", 216 | "componentWillReceiveProps", 217 | "shouldComponentUpdate", 218 | "componentWillUpdate", 219 | "componentDidUpdate", 220 | "componentWillUnmount", 221 | ], 222 | "listeners": [ 223 | "/^on.+$/", 224 | ], 225 | "props": [ 226 | "propTypes", 227 | "defaultProps", 228 | "getDefaultProps", 229 | ], 230 | "renderers": [ 231 | "/^render.+$/", 232 | "render", 233 | ], 234 | "state": [ 235 | "state", 236 | "getInitialState", 237 | ] 238 | } 239 | }], 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | npm-debug.log 3 | node_modules 4 | dist 5 | prod.go 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [v1.0.2] 2 | > July 28th, 2016 3 | 4 | - EditableSelect demo 5 | - Add Gmap Component/demo 6 | - Add blue-text demo 7 | - Add React Autosuggest Search Bar 8 | - Add additional icons on sidebar 9 | - Neaten up Breadcrumbs (use render functions instead) 10 | - Update to latest version of Blur v0.10.0 11 | 12 | ## [v1.0.1] 13 | > June 15th, 2016 14 | 15 | - Add Alert Bar demo 16 | - Add Breadcrumbs 17 | 18 | ## [v1.0.0] 19 | > May 23rd, 2016 20 | 21 | - Initial Demo 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Original work Copyright (c) 2016 Akvemus GSC 4 | Modified work Copyright (c) 2016 Consolidated Knowledge 5 | 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | WEBPACK = node_modules/webpack/bin/webpack.js 2 | ESLINT = node_modules/.bin/eslint 3 | 4 | .PHONY: clean build client lint test ghPages 5 | 6 | build: 7 | make clean 8 | NODE_ENV=production $(WEBPACK) \ 9 | --config webpack.config.prod.js \ 10 | --verbose \ 11 | --display-chunks \ 12 | --bail 13 | 14 | clean: 15 | -rm -rf ./dist 16 | 17 | client: 18 | nf run node dev-server.js 19 | 20 | lint: 21 | $(ESLINT) --ext .js --ext .jsx . 22 | 23 | test: 24 | NODE_PATH=. NODE_ENV=test npm run test 25 | 26 | ## Used to create the gh-pages branch, can be deleted for forked projects 27 | ghPages: 28 | rm -rf ../react-webpack-skeleton-ghpages/* 29 | make clean 30 | NODE_ENV=production APP_NAME="React Webpack Skeleton" BASE_URL="http://knledg.github.io/react-webpack-skeleton/" $(WEBPACK) \ 31 | --config webpack.config.prod.js \ 32 | --verbose \ 33 | --display-chunks \ 34 | --bail 35 | cp -R dist/* ../react-webpack-skeleton-ghpages 36 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: v6.2.1 4 | -------------------------------------------------------------------------------- /dev-server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 'use strict'; 3 | 4 | const _ = require('lodash'); 5 | const path = require('path'); 6 | const express = require('express'); 7 | const webpack = require('webpack'); 8 | const config = require('./webpack.config.dev'); 9 | 10 | const app = express(); 11 | const compiler = webpack(config); 12 | const devMiddleware = require('webpack-dev-middleware')(compiler, { 13 | noInfo: true, 14 | publicPath: config.output.publicPath, 15 | }); 16 | 17 | app.use(devMiddleware); 18 | app.use(require('webpack-hot-middleware')(compiler)); 19 | 20 | app.use(function(req, res, next) { 21 | const reqPath = req.url; 22 | // find the file that the browser is looking for 23 | const file = _.last(reqPath.split('/')); 24 | if (['index.html'].indexOf(file) !== -1) { 25 | res.end(devMiddleware.fileSystem.readFileSync(path.join(config.output.path, file))); 26 | } else if (file.indexOf('.') === -1) { 27 | // if the url does not have an extension, assume they've navigated to something like /home and want index.html 28 | res.end(devMiddleware.fileSystem.readFileSync(path.join(config.output.path, 'index.html'))); 29 | } else { 30 | next(); 31 | } 32 | }); 33 | 34 | /* eslint-disable no-console */ 35 | app.listen(process.env.WEBPACK_PORT, 'localhost', function(err) { 36 | if (err) { 37 | console.log(err); 38 | return; 39 | } 40 | 41 | console.log('Listening at http://localhost:' + process.env.WEBPACK_PORT); 42 | }); 43 | /* eslint-enable no-console */ 44 | -------------------------------------------------------------------------------- /dev.env: -------------------------------------------------------------------------------- 1 | NODE_PATH=. 2 | NODE_ENV=development 3 | WEBPACK_PORT=8011 4 | BASE_URL="http://localhost:8011/" 5 | TZ=UTC 6 | APP_NAME="React Webpack Skeleton - DEVELOP" 7 | 8 | # Set up Auth0 account for free or get from another developer on your team 9 | AUTH0_DOMAIN= 10 | AUTH0_PUB_KEY= -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-webpack-skeleton", 3 | "version": "1.0.2", 4 | "description": "React Webpack Skeleton", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "rimraf dist", 8 | "build:webpack": "NODE_ENV=production webpack --config webpack.config.prod.js", 9 | "build": "npm run clean && npm run build:webpack", 10 | "dev": "node dev-server.js", 11 | "lint": "node_modules/.bin/eslint --ext .js .", 12 | "test": "NODE_ENV=test NODE_PATH=. ./node_modules/.bin/mocha test/setup.js test --recursive", 13 | "webdriver:update": "./node_modules/.bin/webdriver-manager update", 14 | "webdriver:start": "./node_modules/.bin/webdriver-manager start", 15 | "pree2e": "npm run webdriver:update -- --standalone" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+ssh://git@github.com/knledg/react-webpack-skeleton.git" 20 | }, 21 | "author": "Consolidated Knowledge", 22 | "license": "MIT", 23 | "homepage": "https://github.com/knledg/react-webpack-skeleton#readme", 24 | "dependencies": { 25 | "axios": "^0.9.1", 26 | "classnames": "^2.2.5", 27 | "lodash": "^4.7.0", 28 | "moment": "^2.12.0", 29 | "react": "^0.14.8", 30 | "react-autosuggest": "^3.7.4", 31 | "react-blur-admin": "^0.10.0", 32 | "react-dom": "^0.14.8", 33 | "react-flex-proto": "^1.0.0", 34 | "react-gl-maps": "git+https://github.com/knledg/react-gl-maps.git", 35 | "react-router": "^2.0.1", 36 | "react-tap-event-plugin": "^0.2.2" 37 | }, 38 | "devDependencies": { 39 | "babel-core": "^6.7.4", 40 | "babel-eslint": "^6.0.2", 41 | "babel-loader": "^6.2.4", 42 | "babel-plugin-react-transform": "^2.0.2", 43 | "babel-polyfill": "^6.7.4", 44 | "babel-preset-es2015": "^6.6.0", 45 | "babel-preset-react": "^6.5.0", 46 | "babel-preset-react-hmre": "^1.1.1", 47 | "babel-preset-stage-0": "^6.5.0", 48 | "babel-register": "^6.11.6", 49 | "babel-runtime": "^6.6.1", 50 | "css-loader": "^0.23.1", 51 | "eslint": "^2.6.0", 52 | "eslint-plugin-react": "^4.2.3", 53 | "expect": "^1.16.0", 54 | "express": "^4.13.4", 55 | "extract-text-webpack-plugin": "^1.0.1", 56 | "file-loader": "^0.8.5", 57 | "html-loader": "^0.4.3", 58 | "html-webpack-plugin": "^2.15.0", 59 | "jsdom": "^9.4.1", 60 | "mocha": "^2.4.5", 61 | "node-sass": "^3.4.2", 62 | "postcss-loader": "^0.8.2", 63 | "raw-loader": "^0.5.1", 64 | "react-addons-test-utils": "^0.14.8", 65 | "react-transform-catch-errors": "^1.0.2", 66 | "react-transform-hmr": "^1.0.4", 67 | "redbox-react": "^1.2.2", 68 | "rimraf": "^2.5.2", 69 | "sass-loader": "^3.2.0", 70 | "style-loader": "^0.13.1", 71 | "url-loader": "^0.5.7", 72 | "webpack": "^1.12.14", 73 | "webpack-dev-middleware": "^1.6.1", 74 | "webpack-dev-server": "^1.14.1", 75 | "webpack-hot-middleware": "^2.10.0" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /readme.MD: -------------------------------------------------------------------------------- 1 | # React Webpack Skeleton 2 | 3 | [Demo](http://knledg.github.io/react-webpack-skeleton/) 4 | 5 | ## Build Status 6 | 7 | [![CircleCI](https://circleci.com/gh/knledg/react-webpack-skeleton/tree/master.svg?style=svg)](https://circleci.com/gh/knledg/react-webpack-skeleton/tree/master) 8 | 9 | ## Setup 10 | 11 | - `git clone https://github.com/knledg/react-webpack-skeleton.git your-project` 12 | - `cd your-project` 13 | - `rm -rf .git` 14 | - `git init && git remote add origin ` 15 | - `npm install` 16 | - `cp dev.env .env` 17 | - `npm i -g foreman` (if you don't have it globally installed already) 18 | - `make client` 19 | 20 | ## Features 21 | 22 | - Button demo 23 | - Input demo (text/select/switch/radio/checkbox/textarea) 24 | - Progress bar demo 25 | - Not Found page 26 | - Table demo 27 | - Tabs demo 28 | - Welcome page (featuring different panels) 29 | - Modal demo 30 | - Notification demo 31 | - Maps 32 | 33 | ## In progress of completion 34 | 35 | - Accordions (in progress) 36 | - Mail (waiting to be merged) 37 | - Slider 38 | - Profile (waiting to be merged) 39 | 40 | ## Todo 41 | 42 | - Settings demos 43 | - Timeline 44 | - Tree View 45 | - Fix routing on gh-pages 46 | 47 | ## Themes 48 | 49 | - Blur Theme (Copyright (c) 2016 Akvemus GSC) 50 | 51 | ## Auth0 52 | 53 | If you don't have an Auth0 account, create a new one, add the required .env vars. 54 | 55 | Make sure in your Settings tab on Auth0's site to set callback url's to `http://localhost:8011/login` and 56 | allowed origins to `http://localhost:8011` 57 | 58 | -------------------------------------------------------------------------------- /src/components/error-message.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export class ErrorMessage extends React.Component { 4 | static propTypes = { 5 | error: React.PropTypes.node.isRequired, 6 | } 7 | 8 | render() { 9 | return ( 10 |
11 | {this.props.error} 12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/init/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Webpack Skeleton 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/init/main.js: -------------------------------------------------------------------------------- 1 | /* global window, document */ 2 | 3 | import { render } from 'react-dom'; 4 | 5 | import { AppRouter } from 'src/init/router'; 6 | 7 | import 'react-flex-proto/styles/flex.css'; 8 | import 'react-blur-admin/dist/assets/styles/react-blur-admin.min.css'; 9 | 10 | // Initializing touch events 11 | import injectTapEventPlugin from 'react-tap-event-plugin'; 12 | injectTapEventPlugin(); 13 | 14 | /* eslint-disable max-statements */ 15 | function init() { 16 | render(AppRouter, document.getElementById('react-app')); 17 | } 18 | /* eslint-enable max-statements */ 19 | 20 | document.addEventListener('DOMContentLoaded', function() { 21 | try { 22 | window.localStorage.test = 'You appear to be unable to write to localStorage'; 23 | } catch (e) { 24 | document.body.innerHTML = ` 25 |
26 |

Your browser is not able to write to local storage.

27 |

If you are using private mode please disable it.

28 |

Otherwise your browser is not supported or you have local storage turned off in your browser preferences.

29 |
30 | `; 31 | } 32 | init(); 33 | }); 34 | -------------------------------------------------------------------------------- /src/init/router.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Router, Route, Redirect, browserHistory } from 'react-router'; 3 | 4 | import AppLayout from 'src/layout/app'; 5 | import Login from 'src/layout/login'; 6 | 7 | /* Demos */ 8 | import { Welcome } from 'src/page/welcome'; 9 | import { About } from 'src/page/about'; 10 | import { ProgressBars } from 'src/page/progress-bars'; 11 | import { TableDemo } from 'src/page/table-demo'; 12 | import { ButtonDemo } from 'src/page/button-demo'; 13 | import { ModalDemo } from 'src/page/modal-demo'; 14 | import { TabsDemo } from 'src/page/tabs-demo'; 15 | import { InputDemo } from 'src/page/input-demo'; 16 | import { NotificationsDemo } from 'src/page/notifications-demo'; 17 | /* End Demos */ 18 | 19 | import { NotFound } from 'src/page/not-found'; 20 | 21 | // Redirect is got GH pages and can be deleted for forked projects 22 | const redirect = ; 23 | 24 | export const AppRouter = ( 25 | 26 | {redirect} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ); 42 | -------------------------------------------------------------------------------- /src/layout/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter } from 'react-router'; 3 | 4 | // Components 5 | import { Sidebar, PageTop } from 'src/layout/components'; 6 | import { Notifications } from 'react-blur-admin'; 7 | 8 | // Lib 9 | import eventBus from 'src/lib/event-bus'; 10 | 11 | class AppLayout extends React.Component { 12 | static propTypes = { 13 | router: React.PropTypes.object.isRequired, 14 | location: React.PropTypes.shape({ 15 | pathname: React.PropTypes.string.isRequired, 16 | query: React.PropTypes.object.isRequired, 17 | }), 18 | } 19 | 20 | state = { 21 | idToken: null, // Token indicating user is logged in 22 | user: null, // Full user for that logged in user, if exists 23 | } 24 | 25 | componentWillMount() { 26 | if (process.env.AUTH0_PUB_KEY) { 27 | this.lock = new Auth0Lock(process.env.AUTH0_PUB_KEY, process.env.AUTH0_DOMAIN); 28 | this.setState({idToken: this.getIdToken()}); // Must come after this.lock init 29 | } 30 | 31 | eventBus.on('logout', () => this.onLogout()); 32 | } 33 | 34 | componentDidMount() { 35 | if (! this.state.idToken && process.env.AUTH0_PUB_KEY) { 36 | return this.redirectToLogin(); 37 | } 38 | return this.setUser(); 39 | } 40 | 41 | onLogout() { 42 | localStorage.removeItem('userToken'); 43 | this.setState({ idToken: null, user: null }); 44 | return this.redirectToLogin(); 45 | } 46 | 47 | redirectToLogin() { 48 | this.props.router.push({ 49 | pathname: '/login', 50 | query: { redirectUri: encodeURIComponent(this.props.location.pathname) }, 51 | }); 52 | } 53 | 54 | setUser() { 55 | if (! this.state.idToken) { 56 | return null; 57 | } 58 | 59 | return this.lock.getProfile(this.state.idToken, (err, user) => { 60 | return err ? this.onLogout() : this.setState({user}); 61 | }); 62 | } 63 | 64 | getIdToken() { 65 | let idToken = localStorage.getItem('userToken'); 66 | const authHash = this.lock.parseHash(window.location.hash); 67 | if (!idToken && authHash) { 68 | if (authHash.id_token) { 69 | idToken = authHash.id_token; 70 | localStorage.setItem('userToken', authHash.id_token); 71 | } 72 | if (authHash.error) { 73 | return this.onLogout(); 74 | } 75 | } 76 | return idToken; 77 | } 78 | 79 | render() { 80 | // @todo main - menu-collapsed 81 | return ( 82 |
83 |
84 | 85 | 86 | 87 |
88 |
89 | {React.cloneElement(this.props.children, _.assign({}, this.props, { user: this.state.user }))} 90 |
91 |
92 | 93 |
94 |
Created with
95 |
96 |
React Webpack Skeleton
97 |
    98 |
  • 99 |
  • 100 |
  • 101 |
  • 102 |
103 |
104 |
105 | 106 | 107 |
108 | 109 |
110 | ); 111 | } 112 | } 113 | 114 | export default withRouter(AppLayout); 115 | 116 | -------------------------------------------------------------------------------- /src/layout/components/gmap.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import React from 'react'; 3 | import {GoogleMapLoader, GoogleMap, Marker} from 'react-gl-maps'; 4 | 5 | export class GMap extends React.Component { 6 | 7 | static propTypes = { 8 | onMarkerClick: React.PropTypes.func, 9 | markers: React.PropTypes.arrayOf( 10 | React.PropTypes.shape({ 11 | label: React.PropTypes.string, 12 | position: React.PropTypes.shape({ 13 | lat: React.PropTypes.number.isRequired, 14 | lng: React.PropTypes.number.isRequired, 15 | }).isRequired, 16 | }), 17 | ), 18 | } 19 | 20 | static defaultProps = { 21 | onMarkerClick: _.noop, 22 | } 23 | 24 | static testMarkers = [ 25 | { 26 | label: 'A', 27 | position: { 28 | lat: 42.4072, 29 | lng: -71.3824, 30 | }, 31 | }, 32 | { 33 | label: 'B', 34 | position: { 35 | lat: 31.9686, 36 | lng: -99.9018, 37 | }, 38 | }, 39 | { 40 | label: 'C', 41 | position: { 42 | lat: 41.4925, 43 | lng: -99.9018, 44 | }, 45 | }, 46 | { 47 | label: 'D', 48 | position: { 49 | lat: 36.7783, 50 | lng: -119.4179, 51 | }, 52 | }, 53 | ] 54 | 55 | getMarkerIcon(attr) { 56 | const svg = this.generateIcon(attr.label); 57 | return { 58 | url: 'data:image/svg+xml;base64,' + window.btoa(svg), 59 | scaledSize: new google.maps.Size(125, 60), 60 | anchor: new google.maps.Point(15, 15), 61 | }; 62 | } 63 | 64 | getFillColor() { 65 | return '#2B7EBB'; 66 | } 67 | 68 | generateIcon(label, opts = {}) { 69 | const fillColor = this.getFillColor(); 70 | _.defaults(opts, { 71 | fontSize: '30px', 72 | fontColor: 'white', 73 | strokeColor: '#ffffff', 74 | fillColor, 75 | circleOpacity: '0.8', 76 | }); 77 | 78 | return ` 79 | 80 | 81 | 88 | 89 | 90 | ${label} 91 | 92 | `; 93 | } 94 | 95 | 96 | renderContainer() { 97 | return
; 98 | } 99 | 100 | renderMarkers() { 101 | return this.constructor.testMarkers.map((attr, index) => { 102 | const position = attr.position; 103 | const icon = this.getMarkerIcon(attr); 104 | 105 | return ( 106 | 111 | ); 112 | }); 113 | } 114 | 115 | renderMap() { 116 | return ( 117 | 189 | {this.renderMarkers()} 190 | 191 | ); 192 | } 193 | 194 | render() { 195 | return ( 196 | 199 | ); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export * from './gmap'; 2 | export * from './page-top'; 3 | export * from './sidebar'; 4 | -------------------------------------------------------------------------------- /src/layout/components/page-top.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import moment from 'moment'; 4 | import { noop } from 'lodash'; 5 | import Person from 'react-blur-admin/dist/assets/img/person.svg'; 6 | 7 | import {SearchBar} from 'src/layout/components/search-bar'; 8 | 9 | // Lib 10 | import eventBus from 'src/lib/event-bus'; 11 | import {MessagesAlert, MessagesAlertContainer, NotificationsAlert, NotificationAlert} from 'react-blur-admin'; 12 | import {Row, Col} from 'react-flex-proto'; 13 | 14 | export class PageTop extends React.Component { 15 | 16 | static propTypes = { 17 | user: React.PropTypes.object, 18 | location: React.PropTypes.shape({ 19 | pathname: React.PropTypes.string.isRequired, 20 | query: React.PropTypes.object.isRequired, 21 | }), 22 | } 23 | 24 | constructor(props) { 25 | super(props); 26 | this.state = { 27 | notifications: [{ 28 | user: { 29 | name: 'Ashley', 30 | picture: Person, 31 | }, 32 | subject: 'This is a notification alert', 33 | timeStamp: '02/13/95 9:00', 34 | relativeTime: moment('02/13/95').fromNow(), 35 | }, 36 | { 37 | user: { 38 | name: 'Nick', 39 | picture: Person, 40 | }, 41 | subject: 'This is a notification alert', 42 | timeStamp: '07/13/16 12:00', 43 | relativeTime: moment('07/13/16 12:00').fromNow(), 44 | }, 45 | { 46 | user: { 47 | name: 'Matt', 48 | picture: Person, 49 | }, 50 | subject: 'This is a notification alert', 51 | timeStamp: '04/20/15 9:00', 52 | relativeTime: moment('04/20/15 9:00').fromNow(), 53 | }, 54 | { 55 | user: { 56 | name: 'Jon', 57 | picture: Person, 58 | }, 59 | subject: 'This is a notification alert', 60 | timeStamp: '07/19/16 8:00', 61 | relativeTime: moment('07/19/16 8:00').fromNow(), 62 | }, 63 | { 64 | user: { 65 | name: 'Jacob', 66 | picture: Person, 67 | }, 68 | subject: 'This is a notification alert', 69 | timeStamp: '05/23/16 2:00', 70 | relativeTime: moment('05/23/16 2:00').fromNow(), 71 | }, 72 | { 73 | user: { 74 | name: 'Jason', 75 | picture: Person, 76 | }, 77 | subject: 'This is a notification alert', 78 | timeStamp: '05/01/16 4:00', 79 | relativeTime: moment('05/01/16 4:00').fromNow(), 80 | }], 81 | messages: [{ 82 | user: { 83 | name: 'Ashley', 84 | picture: Person, 85 | }, 86 | subject: 'This is a message alert', 87 | timeStamp: '02/13/95 9:00', 88 | relativeTime: moment('02/13/16').fromNow(), 89 | }, 90 | { 91 | user: { 92 | name: 'Nick', 93 | picture: Person, 94 | }, 95 | subject: 'This is a message alert', 96 | timeStamp: '07/13/16 12:00', 97 | relativeTime: moment('07/13/16 12:00').fromNow(), 98 | }], 99 | }; 100 | } 101 | 102 | state = { 103 | isMenuOpen: false, 104 | appName: process.env.APP_NAME, 105 | } 106 | 107 | componentWillMount() { 108 | 109 | } 110 | 111 | onToggleMenu() { 112 | this.setState({ isMenuOpen: ! this.state.isMenuOpen }); 113 | } 114 | 115 | onLogout() { 116 | eventBus.emit('logout'); 117 | } 118 | 119 | renderLogo() { 120 | return ( 121 | {this.state.appName} 122 | ); 123 | } 124 | 125 | renderHamburgerMenu() { 126 | return null; 127 | 128 | // @todo 129 | // return ( 130 | // 131 | // ); 132 | } 133 | 134 | renderSearch() { 135 | return ( 136 |
137 | 138 |
139 | ); 140 | } 141 | 142 | renderMessages() { 143 | let message = _.assign({}, this.state.messages); 144 | return _.map(message, (messages, index) => { 145 | return ( 146 | 147 | ); 148 | }); 149 | } 150 | 151 | renderNotifications() { 152 | let notifications = _.assign({}, this.state.notifications); 153 | return _.map(notifications, (notification, index) => { 154 | return ( 155 | 156 | ); 157 | }); 158 | } 159 | 160 | renderUserSection() { 161 | return ( 162 |
163 |
164 | 165 | 166 | 167 | 177 |
178 | 179 | 180 | 181 | {this.renderMessages()} 182 | 183 | 188 | {this.renderNotifications()} 189 | 190 | 191 | 192 |
193 | ); 194 | } 195 | 196 | render() { 197 | // dropdown - .open 198 | // @todo msg-center 199 | // onClick startSearch 200 | // import message cente 201 | return ( 202 |
203 | {this.renderLogo()} 204 | {this.renderHamburgerMenu()} 205 | {this.renderSearch()} 206 | {this.renderUserSection()} 207 |
208 | ); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/layout/components/search-bar.js: -------------------------------------------------------------------------------- 1 | import Autosuggest from 'react-autosuggest'; 2 | import React from 'react'; 3 | import { Row, Col } from 'react-flex-proto'; 4 | 5 | const suggestionExamples = [ 6 | { 7 | text: 'Tabs', 8 | }, 9 | { 10 | text: 'Buttons', 11 | }, 12 | { 13 | text: 'Progress Bars', 14 | }, 15 | { 16 | text: 'Tables', 17 | }, 18 | { 19 | text: 'About', 20 | }, 21 | { 22 | text: 'Inputs', 23 | }, 24 | { 25 | text: 'Notifications', 26 | }, 27 | { 28 | text: 'Home', 29 | }, 30 | { 31 | text: 'Panels', 32 | }, 33 | { 34 | text: 'Modals', 35 | }, 36 | ]; 37 | 38 | export class SearchBar extends React.Component { 39 | constructor() { 40 | super(); 41 | 42 | this.state = { 43 | value: '', 44 | suggestions: this.getSuggestions(''), 45 | }; 46 | 47 | this.onChange = this.onChange.bind(this); 48 | this.renderSuggestion = this.renderSuggestion.bind(this); 49 | this.onSuggestionsUpdateRequested = this.onSuggestionsUpdateRequested.bind(this); 50 | } 51 | 52 | onChange(event, { newValue }) { 53 | this.setState({ 54 | value: newValue, 55 | }); 56 | } 57 | 58 | onSuggestionsUpdateRequested({ value }) { 59 | this.setState({loading: true, suggestions: [{type: 'loading'}]}); 60 | 61 | setTimeout(function() { 62 | this.setState({ 63 | suggestions: this.getSuggestions(value), 64 | loading: false, 65 | }); 66 | }.bind(this), 200); 67 | } 68 | 69 | getSuggestions(value) { 70 | const inputValue = value.trim().toLowerCase(); 71 | const inputLength = inputValue.length; 72 | 73 | let suggestions = suggestionExamples.filter(lang => 74 | lang.text.toLowerCase().slice(0, inputLength) === inputValue 75 | ); 76 | 77 | return suggestions.length ? suggestions : [{type: 'no-results'}]; 78 | } 79 | 80 | getSuggestionValue(suggestion) { // when suggestion selected, this function tells 81 | return suggestion.text; // what should be the value of the input 82 | } 83 | 84 | renderSuggestion(suggestion) { 85 | if (this.state.loading) { 86 | return ( 87 |
88 | Loading... 89 |
90 | ); 91 | } 92 | 93 | if (suggestion.type === 'no-results') { 94 | return ( 95 |
96 | No results found, you may need to be more specific! 97 |
98 | ); 99 | } 100 | 101 | return ( 102 | {suggestion.text} 103 | ); 104 | } 105 | 106 | render() { 107 | const { value, suggestions } = this.state; 108 | const inputProps = { 109 | placeholder: 'Search for...', 110 | value, 111 | onChange: this.onChange, 112 | }; 113 | 114 | return ( 115 | 116 | 117 | 118 | 119 | 120 | 125 | 126 | 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/layout/components/sidebar.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import React from 'react'; 3 | import { Link } from 'react-router'; 4 | 5 | export class Sidebar extends React.Component { 6 | 7 | static propTypes = { 8 | location: React.PropTypes.shape({ 9 | pathname: React.PropTypes.string.isRequired, 10 | query: React.PropTypes.object.isRequired, 11 | }), 12 | } 13 | 14 | state = { 15 | navItems: [ 16 | { pathname: '/', label: 'Home', icon: 'home' }, 17 | { pathname: '/about', label: 'About', icon: 'info' }, 18 | { pathname: '/table-demo', label: 'Tables', icon: 'table' }, 19 | { pathname: '/button-demo', label: 'Buttons', icon: 'dot-circle-o' }, 20 | { pathname: '/progress-bars', label: 'Progress Bars', icon: 'spinner'}, 21 | { pathname: '/modal-demo', label: 'Modals', icon: 'clipboard' }, 22 | { pathname: '/tabs-demo', label: 'Tabs', icon: 'list-ul' }, 23 | { pathname: '/input-demo', label: 'Inputs', icon: 'check-square' }, 24 | { pathname: '/notifications-demo', label: 'Notifications', icon: 'exclamation' }, 25 | ], 26 | } 27 | 28 | isSelected(navItem) { 29 | return this.props.location.pathname === navItem.pathname ? 'selected' : ''; 30 | } 31 | 32 | renderLinks() { 33 | return _.map(this.state.navItems, (navItem) => { 34 | return ( 35 |
  • 36 | 37 | 38 | {navItem.label} 39 | 40 |
  • 41 | ); 42 | }); 43 | } 44 | 45 | render() { 46 | // navitems selected, with-sub-menu 47 | // links - hover 48 | /* 49 | 67 | */ 68 | /* 69 |
    71 | */ 72 | 73 | // ul - slimscroll="{height: '{{menuHeight}}px'}" slimscroll-watch="menuHeight" 74 | return ( 75 | 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/layout/login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter } from 'react-router'; 3 | 4 | class Login extends React.Component { 5 | static propTypes = { 6 | router: React.PropTypes.object.isRequired, 7 | location: React.PropTypes.shape({ 8 | pathname: React.PropTypes.string.isRequired, 9 | query: React.PropTypes.object.isRequired, 10 | }), 11 | } 12 | 13 | state = { 14 | idToken: null, 15 | } 16 | 17 | componentWillMount() { 18 | this.lock = new Auth0Lock(process.env.AUTH0_PUB_KEY, process.env.AUTH0_DOMAIN); 19 | this.setIdToken(); 20 | } 21 | 22 | setIdToken() { 23 | const idToken = this.getIdToken(); 24 | if (idToken) { 25 | this.setState({idToken}); 26 | 27 | if (this.props.location.query && this.props.location.query.redirectUri) { 28 | this.props.router.replace(decodeURIComponent(this.props.location.query.redirectUri)); 29 | } else { 30 | this.props.router.replace('/'); 31 | } 32 | } 33 | } 34 | 35 | getIdToken() { 36 | let idToken = localStorage.getItem('userToken'); 37 | const authHash = this.lock.parseHash(window.location.hash); 38 | if (!idToken && authHash) { 39 | if (authHash.id_token) { 40 | idToken = authHash.id_token; 41 | localStorage.setItem('userToken', authHash.id_token); 42 | } 43 | if (authHash.error) { 44 | /* eslint-disable no-console */ 45 | console.error('Error fetching user hash: ', authHash.error); 46 | /* eslint-enable no-console */ 47 | } 48 | } 49 | return idToken; 50 | } 51 | 52 | showLock() { 53 | this.lock.show(); 54 | } 55 | 56 | render() { 57 | if (this.state.idToken) { 58 | return null; 59 | } 60 | 61 | this.showLock(); 62 | return
    ; 63 | } 64 | } 65 | 66 | export default withRouter(Login); 67 | -------------------------------------------------------------------------------- /src/lib/event-bus.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events'; 2 | import _ from 'lodash'; 3 | 4 | class EventBus extends EventEmitter { 5 | 6 | // this.emit('message', payload) if available by default 7 | 8 | addNotification(type, text, options) { 9 | const props = _.extend(options, { 10 | type, 11 | }); 12 | 13 | this.emit('notification', {text, props}); 14 | } 15 | } 16 | 17 | const eventBus = new EventBus(); 18 | export default eventBus; 19 | -------------------------------------------------------------------------------- /src/page/about.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Page, Panel, Breadcrumbs } from 'react-blur-admin'; 4 | import { Link } from 'react-router'; 5 | 6 | export class About extends React.Component { 7 | 8 | renderBreadcrumbs() { 9 | return ( 10 | 11 | 12 | Home 13 | 14 | About 15 | 16 | ); 17 | } 18 | 19 | render() { 20 | return ( 21 | 22 | 23 | Lorem Ipsum 24 | 25 | 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/page/button-demo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Page, Panel, Button, Breadcrumbs } from 'react-blur-admin'; 4 | import { Link } from 'react-router'; 5 | import {Row, Col} from 'react-flex-proto'; 6 | 7 | export class ButtonDemo extends React.Component { 8 | 9 | renderBreadcrumbs() { 10 | return ( 11 | 12 | 13 | Home 14 | 15 | Buttons 16 | 17 | ); 18 | } 19 | 20 | render() { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 |