diff --git a/.storybook/config.js b/.storybook/config.js
index 21edff7e..1977c69a 100644
--- a/.storybook/config.js
+++ b/.storybook/config.js
@@ -2,7 +2,6 @@ import { configure } from '@kadira/storybook';
import '../src/index.css';
function loadStories() {
- require('../src/containers/stories/');
require('../src/components/stories/');
}
diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js
index 08191f16..e62e19d4 100644
--- a/.storybook/webpack.config.js
+++ b/.storybook/webpack.config.js
@@ -1,34 +1 @@
-/* global module, __dirname, require */
-var webpack = require("webpack");
-const path = require("path");
-
-module.exports = {
- module: {
- loaders: [
- {
- test: /\.((png)|(eot)|(woff)|(woff2)|(ttf)|(svg)|(gif))(\?v=\d+\.\d+\.\d+)?$/,
- loader: 'url-loader?limit=100000'
- },
- { test: /\.json$/, loader: 'json-loader' },
- {
- test: /\.css$/,
- loader: 'style!css?modules&importLoaders=1!postcss'
- },
- {
- loader: 'babel',
- test: /\.js?$/,
- exclude: /(node_modules|bower_components)/,
- query: {
- cacheDirectory: true,
- presets: ['react', 'es2015'],
- plugins: ['transform-class-properties', 'transform-object-assign', 'transform-object-rest-spread']
- }
- }
- ]
- },
-
- postcss: [
- require("postcss-import")({addDependencyTo: webpack}),
- require("postcss-cssnext")()
- ],
-};
+module.exports = require('../webpack.base.js');
diff --git a/package.json b/package.json
index 775b7ed5..897a67e3 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"description": "Netlify CMS lets content editors work on structured content stored in git",
"main": "index.js",
"scripts": {
- "start": "webpack-dev-server --config webpack.config.js",
+ "start": "webpack-dev-server --config webpack.dev.js",
"test": "NODE_ENV=test mocha --recursive --compilers js:babel-register --require ./test/setup.js",
"test:watch": "npm test -- --watch",
"build": "webpack --config webpack.config.js",
@@ -53,6 +53,7 @@
"lint-staged": "^3.0.2",
"mocha": "^2.4.5",
"moment": "^2.11.2",
+ "node-sass": "^3.10.0",
"normalizr": "^2.0.0",
"postcss-cssnext": "^2.7.0",
"postcss-import": "^8.1.2",
@@ -69,10 +70,12 @@
"react-router-redux": "^4.0.5",
"redux": "^3.3.1",
"redux-thunk": "^1.0.3",
+ "sass-loader": "^4.0.2",
"style-loader": "^0.13.0",
"url-loader": "^0.5.7",
"webpack": "^1.13.2",
"webpack-dev-server": "^1.15.1",
+ "webpack-merge": "^0.14.1",
"webpack-postcss-tools": "^1.1.1",
"whatwg-fetch": "^1.0.0"
},
@@ -80,16 +83,20 @@
"bricks.js": "^1.7.0",
"dateformat": "^1.0.12",
"fuzzy": "^0.1.1",
+ "immutability-helper": "^2.0.0",
"js-base64": "^2.1.9",
"json-loader": "^0.5.4",
"localforage": "^1.4.2",
"lodash": "^4.13.1",
"markup-it": "git+https://github.com/cassiozen/markup-it.git",
+ "material-design-icons": "^3.0.1",
+ "normalize.css": "^4.2.0",
"pluralize": "^3.0.0",
"prismjs": "^1.5.1",
"react-addons-css-transition-group": "^15.3.1",
"react-datetime": "^2.6.0",
"react-portal": "^2.2.1",
+ "react-toolbox": "^1.2.1",
"react-simple-dnd": "^0.1.2",
"selection-position": "^1.0.0",
"semaphore": "^1.0.5",
diff --git a/src/actions/findbar.js b/src/actions/findbar.js
index 68db0399..dc1ea0cb 100644
--- a/src/actions/findbar.js
+++ b/src/actions/findbar.js
@@ -1,5 +1,5 @@
import history from '../routing/history';
-import { SEARCH } from '../containers/FindBar';
+import { SEARCH } from '../components/UI/FindBar/FindBar';
export const RUN_COMMAND = 'RUN_COMMAND';
export const SHOW_COLLECTION = 'SHOW_COLLECTION';
@@ -10,14 +10,22 @@ export function run(commandName, payload) {
return { type: RUN_COMMAND, command: commandName, payload };
}
+export function navigateToCollection(collectionName) {
+ return runCommand(SHOW_COLLECTION, { collectionName });
+}
+
+export function createNewEntryInCollection(collectionName) {
+ return runCommand(CREATE_COLLECTION, { collectionName });
+}
+
export function runCommand(commandName, payload) {
- return (dispatch, getState) => {
+ return dispatch => {
switch (commandName) {
case SHOW_COLLECTION:
history.push(`/collections/${payload.collectionName}`);
break;
case CREATE_COLLECTION:
- window.alert(`Create a new ${payload.collectionName} - not supported yet`);
+ history.push(`/collections/${payload.collectionName}/entries/new`);
break;
case HELP:
window.alert('Find Bar Help (PLACEHOLDER)\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit.');
diff --git a/src/components/UI/AppHeader/AppHeader.css b/src/components/UI/AppHeader/AppHeader.css
new file mode 100644
index 00000000..c1406f5d
--- /dev/null
+++ b/src/components/UI/AppHeader/AppHeader.css
@@ -0,0 +1,17 @@
+:root {
+ --foregroundColor: #fff;
+ --backgroundColor: #272e30;
+ --textFieldBorderColor: #e7e7e7;
+ --highlightFGColor: #fff;
+ --highlightBGColor: #3ab7a5;
+}
+
+.appBar {
+ background-color: var(--backgroundColor);
+}
+
+.createBtn {
+ position: fixed;
+ right: 2rem;
+ top: 3.5rem;
+}
diff --git a/src/components/UI/AppHeader/AppHeader.js b/src/components/UI/AppHeader/AppHeader.js
new file mode 100644
index 00000000..73759a46
--- /dev/null
+++ b/src/components/UI/AppHeader/AppHeader.js
@@ -0,0 +1,90 @@
+import React from 'react';
+import pluralize from 'pluralize';
+import { IndexLink } from 'react-router';
+import { Menu, MenuItem, Button, IconButton } from 'react-toolbox';
+import AppBar from 'react-toolbox/lib/app_bar';
+import FindBar from '../FindBar/FindBar';
+import styles from './AppHeader.css';
+
+export default class AppHeader extends React.Component {
+
+ state = {
+ createMenuActive: false
+ }
+
+ handleCreatePostClick = collectionName => {
+ const { onCreateEntryClick } = this.props;
+ if (onCreateEntryClick) {
+ onCreateEntryClick(collectionName);
+ }
+ }
+
+ handleCreateButtonClick = () => {
+ this.setState({
+ createMenuActive: true
+ });
+ }
+
+ handleCreateMenuHide = () => {
+ this.setState({
+ createMenuActive: false
+ });
+ }
+
+ render() {
+ const {
+ collections,
+ commands,
+ defaultCommands,
+ runCommand,
+ toggleNavDrawer
+ } = this.props;
+ const { createMenuActive } = this.state;
+
+ return (
+
+
+
+ Dashboard
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/containers/FindBar.css b/src/components/UI/FindBar/FindBar.css
similarity index 95%
rename from src/containers/FindBar.css
rename to src/components/UI/FindBar/FindBar.css
index be0abe88..ebc75482 100644
--- a/src/containers/FindBar.css
+++ b/src/components/UI/FindBar/FindBar.css
@@ -7,15 +7,14 @@
}
.root {
+ flex: 1;
position: relative;
background-color: var(--backgroundColor);
- padding: 1px 0;
- margin: 4px auto;
+ padding: 5px;
}
.inputArea {
display: table;
- width: calc(100% - 10px);
- margin: 5px;
+ width: 100%;
color: var(--foregroundColor);
background-color: #fff;
border: 1px solid var(--textFieldBorderColor);
diff --git a/src/containers/FindBar.js b/src/components/UI/FindBar/FindBar.js
similarity index 96%
rename from src/containers/FindBar.js
rename to src/components/UI/FindBar/FindBar.js
index f228b824..3f03c86f 100644
--- a/src/containers/FindBar.js
+++ b/src/components/UI/FindBar/FindBar.js
@@ -1,9 +1,7 @@
import React, { Component, PropTypes } from 'react';
import fuzzy from 'fuzzy';
import _ from 'lodash';
-import { runCommand } from '../actions/findbar';
-import { connect } from 'react-redux';
-import { Icon } from '../components/UI';
+import { Icon } from '../index';
import styles from './FindBar.css';
export const SEARCH = 'SEARCH';
@@ -102,13 +100,15 @@ class FindBar extends Component {
const paramName = command && command.param ? command.param.name : null;
const enteredParamValue = command && command.param && match[1] ? match[1].trim() : null;
+ console.log(this.props.runCommand);
+
if (command.search) {
this.setState({
activeScope: SEARCH,
placeholder: ''
});
- enteredParamValue && this.props.dispatch(runCommand(SEARCH, { searchTerm: enteredParamValue }));
+ enteredParamValue && this.props.runCommand(SEARCH, { searchTerm: enteredParamValue });
} else if (command.param && !enteredParamValue) {
// Partial Match
// Command was partially matched: It requires a param, but param wasn't entered
@@ -133,7 +133,7 @@ class FindBar extends Component {
if (paramName) {
payload[paramName] = enteredParamValue;
}
- this.props.dispatch(runCommand(command.type, payload));
+ this.props.runCommand(command.type, payload);
}
}
@@ -373,8 +373,7 @@ FindBar.propTypes = {
pattern: PropTypes.string.isRequired
})).isRequired,
defaultCommands: PropTypes.arrayOf(PropTypes.string),
- dispatch: PropTypes.func.isRequired,
+ runCommand: PropTypes.func.isRequired,
};
-export { FindBar };
-export default connect()(FindBar);
+export default FindBar;
diff --git a/src/components/UI/index.js b/src/components/UI/index.js
index 87b473a5..f99eba1c 100644
--- a/src/components/UI/index.js
+++ b/src/components/UI/index.js
@@ -2,3 +2,4 @@ export { default as Card } from './card/Card';
export { default as Loader } from './loader/Loader';
export { default as Icon } from './icon/Icon';
export { default as Toast } from './toast/Toast';
+export { default as AppHeader } from './AppHeader/AppHeader';
diff --git a/src/containers/stories/FindBar.js b/src/components/stories/FindBar.js
similarity index 91%
rename from src/containers/stories/FindBar.js
rename to src/components/stories/FindBar.js
index e8a73ec9..812d2f8c 100644
--- a/src/containers/stories/FindBar.js
+++ b/src/components/stories/FindBar.js
@@ -1,7 +1,7 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
-import { FindBar } from '../FindBar';
+import FindBar from '../UI/FindBar/FindBar';
const CREATE_COLLECTION = 'CREATE_COLLECTION';
const CREATE_POST = 'CREATE_POST';
@@ -30,15 +30,13 @@ const style = {
margin: 20
};
-const dispatch = action('DISPATCH');
-
storiesOf('FindBar', module)
.add('Default View', () => (
f(dispatch)}
+ runCommand={action}
/>
));
diff --git a/src/components/stories/index.js b/src/components/stories/index.js
index 21f91079..aef4bed3 100644
--- a/src/components/stories/index.js
+++ b/src/components/stories/index.js
@@ -1,3 +1,4 @@
import './Card';
import './Icon';
import './Toast';
+import './FindBar';
diff --git a/src/containers/App.css b/src/containers/App.css
index 0e438444..8e02b0cd 100644
--- a/src/containers/App.css
+++ b/src/containers/App.css
@@ -1,3 +1,10 @@
+.layout .navDrawer .drawerContent {
+ padding-top: 54px;
+}
+.nav {
+ display: block;
+ padding: 1rem;
+}
.main {
padding-top: 54px;
}
diff --git a/src/containers/App.js b/src/containers/App.js
index 0cfec9fa..b394fa0c 100644
--- a/src/containers/App.js
+++ b/src/containers/App.js
@@ -1,15 +1,27 @@
import React from 'react';
+import pluralize from 'pluralize';
import { connect } from 'react-redux';
+import { Layout, Panel, NavDrawer, Navigation, Link } from 'react-toolbox';
import { loadConfig } from '../actions/config';
import { loginUser } from '../actions/auth';
import { currentBackend } from '../backends/backend';
-import { Loader } from '../components/UI';
-import { SHOW_COLLECTION, CREATE_COLLECTION, HELP } from '../actions/findbar';
-import FindBar from './FindBar';
+import {
+ SHOW_COLLECTION,
+ CREATE_COLLECTION,
+ HELP,
+ runCommand,
+ navigateToCollection,
+ createNewEntryInCollection
+} from '../actions/findbar';
+import { AppHeader, Loader } from '../components/UI/index';
import styles from './App.css';
-import pluralize from 'pluralize';
class App extends React.Component {
+
+ state = {
+ navDrawerIsVisible: false
+ }
+
componentDidMount() {
this.props.dispatch(loadConfig());
}
@@ -62,7 +74,7 @@ class App extends React.Component {
id: `show_${collection.get('name')}`,
pattern: `Show ${pluralize(collection.get('label'))}`,
type: SHOW_COLLECTION,
- payload: { collectionName:collection.get('name') }
+ payload: { collectionName: collection.get('name') }
});
if (defaultCommands.length < 5) defaultCommands.push(`show_${collection.get('name')}`);
@@ -72,7 +84,7 @@ class App extends React.Component {
id: `create_${collection.get('name')}`,
pattern: `Create new ${pluralize(collection.get('label'), 1)}(:itemName as ${pluralize(collection.get('label'), 1)} Name)`,
type: CREATE_COLLECTION,
- payload: { collectionName:collection.get('name') }
+ payload: { collectionName: collection.get('name') }
});
}
});
@@ -83,8 +95,23 @@ class App extends React.Component {
return { commands, defaultCommands };
}
+ toggleNavDrawer = () => {
+ this.setState({
+ navDrawerIsVisible: !this.state.navDrawerIsVisible
+ });
+ }
+
render() {
- const { user, config, children } = this.props;
+ const { navDrawerIsVisible } = this.state;
+ const {
+ user,
+ config,
+ children,
+ collections,
+ runCommand,
+ navigateToCollection,
+ createNewEntryInCollection
+ } = this.props;
if (config === null) {
return null;
@@ -105,19 +132,42 @@ class App extends React.Component {
const { commands, defaultCommands } = this.generateFindBarCommands();
return (
-
-
-
-
+
+
+
+
+
+
+
+ {children}
-
-
- {children}
-
-
+
+
);
}
}
@@ -129,4 +179,19 @@ function mapStateToProps(state) {
return { auth, config, collections, user };
}
-export default connect(mapStateToProps)(App);
+function mapDispatchToProps(dispatch) {
+ return {
+ dispatch,
+ runCommand: (type, payload) => {
+ dispatch(runCommand(type, payload));
+ },
+ navigateToCollection: (collection) => {
+ dispatch(navigateToCollection(collection));
+ },
+ createNewEntryInCollection: (collectionName) => {
+ dispatch(createNewEntryInCollection(collectionName));
+ }
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(App);
diff --git a/src/containers/stories/index.js b/src/containers/stories/index.js
deleted file mode 100644
index 5091c8c9..00000000
--- a/src/containers/stories/index.js
+++ /dev/null
@@ -1 +0,0 @@
-import './FindBar';
diff --git a/src/index.css b/src/index.css
index 91c45f0d..296bdf5a 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,3 +1,5 @@
+@import "material-icons.css";
+
html {
box-sizing: border-box;
-ms-text-size-adjust: 100%;
@@ -18,16 +20,6 @@ body {
margin: 0;
}
-header {
- background-color: #272e30;
- box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.22);
- height: 54px;
- border-bottom: 2px solid #3ab7a5;
- position: fixed;
- width: 100%;
- z-index: 999;
-}
-
:global #root, :global #root > * {
height: 100%;
}
@@ -44,19 +36,6 @@ h1 {
font-size: 25px;
}
-header input {
- margin-bottom: 0;
-}
-
-button {
- border: 1px solid #3ab7a5;
- padding: 3px 20px;
- font-size: 12px;
- line-height: 18px;
- background-color: #fff;
- cursor: pointer;
-}
-
:global {
& .cms-widget {
border-bottom: 1px solid #e8eae8;
diff --git a/src/index.js b/src/index.js
index ae127d1e..e7c8ffe1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,9 +1,10 @@
import React from 'react';
import { render } from 'react-dom';
-import { AppContainer } from 'react-hot-loader'
-import Root from './root'
+import { AppContainer } from 'react-hot-loader';
+import Root from './root';
import registry from './lib/registry';
import 'file?name=index.html!../example/index.html';
+import 'react-toolbox/lib/commons.scss';
import './index.css';
// Create mount element dynamically
diff --git a/src/material-icons.css b/src/material-icons.css
new file mode 100644
index 00000000..a2807ad5
--- /dev/null
+++ b/src/material-icons.css
@@ -0,0 +1,35 @@
+@font-face {
+ font-family: 'Material Icons';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Material Icons'),
+ local('MaterialIcons-Regular'),
+ url('material-design-icons/iconfont/MaterialIcons-Regular.woff2') format('woff2'),
+ url('material-design-icons/iconfont/MaterialIcons-Regular.woff') format('woff'),
+ url('material-design-icons/iconfont/MaterialIcons-Regular.ttf') format('truetype');
+}
+
+:global .material-icons {
+ font-family: 'Material Icons';
+ font-weight: normal;
+ font-style: normal;
+ font-size: 24px; /* Preferred icon size */
+ display: inline-block;
+ line-height: 1;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: normal;
+ white-space: nowrap;
+ direction: ltr;
+
+ /* Support for all WebKit browsers. */
+ -webkit-font-smoothing: antialiased;
+ /* Support for Safari and Chrome. */
+ text-rendering: optimizeLegibility;
+
+ /* Support for Firefox. */
+ -moz-osx-font-smoothing: grayscale;
+
+ /* Support for IE. */
+ font-feature-settings: 'liga';
+}
diff --git a/webpack.base.js b/webpack.base.js
new file mode 100644
index 00000000..80a24194
--- /dev/null
+++ b/webpack.base.js
@@ -0,0 +1,45 @@
+const webpack = require('webpack');
+
+module.exports = {
+ module: {
+ loaders: [
+ {
+ test: /\.((png)|(eot)|(woff)|(woff2)|(ttf)|(svg)|(gif))(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'url-loader?limit=100000'
+ },
+ {
+ test: /\.json$/,
+ loader: 'json-loader'
+ },
+ {
+ test: /\.scss$/,
+ loader: 'style!css?modules!sass',
+ },
+ {
+ test: /\.css$/,
+ loader: 'style!css?modules&importLoaders=1&&localIdentName=cms__[name]__[local]!postcss',
+ },
+ {
+ loader: 'babel',
+ test: /\.js?$/,
+ exclude: /(node_modules|bower_components)/,
+ query: {
+ cacheDirectory: true,
+ presets: ['react', 'es2015'],
+ plugins: [
+ 'transform-class-properties',
+ 'transform-object-assign',
+ 'transform-object-rest-spread',
+ 'lodash',
+ 'react-hot-loader/babel'
+ ]
+ }
+ }
+ ]
+ },
+
+ postcss: [
+ require('postcss-import')({ addDependencyTo: webpack }),
+ require('postcss-cssnext')
+ ],
+};
diff --git a/webpack.config.js b/webpack.config.js
deleted file mode 100644
index 94222a49..00000000
--- a/webpack.config.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/* global module, __dirname, require */
-var webpack = require('webpack');
-var path = require('path');
-
-const HOST = 'localhost';
-const PORT = '8080';
-
-module.exports = {
- module: {
- loaders: [
- {
- test: /\.((png)|(eot)|(woff)|(woff2)|(ttf)|(svg)|(gif))(\?v=\d+\.\d+\.\d+)?$/,
- loader: 'url-loader?limit=100000'
- },
- { test: /\.json$/, loader: 'json-loader' },
- {
- test: /\.css$/,
- loader: 'style!css?modules&importLoaders=1&&localIdentName=cms__[name]__[local]!postcss',
- },
- {
- loader: 'babel',
- test: /\.js?$/,
- exclude: /(node_modules|bower_components)/,
- query: {
- cacheDirectory: true,
- presets: ['react', 'es2015'],
- plugins: [
- 'transform-class-properties',
- 'transform-object-assign',
- 'transform-object-rest-spread',
- 'lodash',
- 'react-hot-loader/babel'
- ]
- }
- }
- ]
- },
-
- postcss: [
- require('postcss-import')({ addDependencyTo: webpack }),
- require('postcss-cssnext')
- ],
-
- plugins: [
- new webpack.optimize.OccurenceOrderPlugin(),
- new webpack.HotModuleReplacementPlugin(),
- new webpack.NoErrorsPlugin(),
- new webpack.ProvidePlugin({
- 'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
- })
- ],
-
- context: path.join(__dirname, 'src'),
- entry: {
- cms: [
- 'webpack/hot/dev-server',
- `webpack-dev-server/client?http://${HOST}:${PORT}/`,
- 'react-hot-loader/patch',
- './index'
- ],
- },
- output: {
- path: path.join(__dirname, 'dist'),
- filename: '[name].js',
- publicPath: `http://${HOST}:${PORT}/`,
- },
- externals: [/^vendor\/.+\.js$/],
- devServer: {
- hot: true,
- contentBase: 'example/',
- historyApiFallback: true,
- devTool: 'cheap-module-source-map'
- },
-};
diff --git a/webpack.dev.js b/webpack.dev.js
new file mode 100644
index 00000000..894dfef0
--- /dev/null
+++ b/webpack.dev.js
@@ -0,0 +1,37 @@
+/* global module, __dirname, require */
+const path = require('path');
+const webpack = require('webpack');
+const merge = require('webpack-merge');
+const HOST = 'localhost';
+const PORT = '8080';
+
+module.exports = merge.smart(require('./webpack.base.js'), {
+ entry: {
+ cms: [
+ 'webpack/hot/dev-server',
+ `webpack-dev-server/client?http://${HOST}:${PORT}/`,
+ 'react-hot-loader/patch',
+ './index'
+ ],
+ },
+ output: {
+ path: path.join(__dirname, 'dist'),
+ filename: '[name].js',
+ publicPath: `http://${HOST}:${PORT}/`,
+ },
+ context: path.join(__dirname, 'src'),
+ plugins: [
+ new webpack.optimize.OccurenceOrderPlugin(),
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoErrorsPlugin(),
+ new webpack.ProvidePlugin({
+ 'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
+ })
+ ],
+ devServer: {
+ hot: true,
+ contentBase: 'example/',
+ historyApiFallback: true,
+ devTool: 'cheap-module-source-map'
+ },
+});