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' + }, +});