This commit is contained in:
JUDIT GRESKOVITS 2017-01-06 12:51:48 +00:00
commit cbbc764f30
64 changed files with 14970 additions and 17 deletions

8
bin/on-changes Executable file
View File

@ -0,0 +1,8 @@
#! /usr/bin/env bash
#
# Prelude
#
set -euo pipefail
[[ $(git diff --name-only) == *"$1"* ]]

View File

@ -32,7 +32,7 @@ deployment:
branch: master branch: master
commands: commands:
- ./bin/docker-login - ./bin/docker-login
- make -C ui publish | sed '/NPM_TOKEN/d' - ./bin/on-changes && make -C ui publish | sed '/NPM_TOKEN/d'
- make -j2 build | sed '/NPM_TOKEN/d' - make -j2 build | sed '/NPM_TOKEN/d'
- make -j2 push | sed '/NPM_TOKEN/d' - make -j2 push | sed '/NPM_TOKEN/d'
- ./bin/deploy - ./bin/deploy

View File

@ -29,7 +29,7 @@ const plugins = [
{ {
register: understood, register: understood,
options: { options: {
'default': 'en-us', default: 'en-us',
localesDir: path.join(__dirname, '../static/locales') localesDir: path.join(__dirname, '../static/locales')
} }
} }
@ -39,7 +39,7 @@ const defaultHandler = (request, reply) => {
const locales = (request.locale || '').toLowerCase().split(/\-/); const locales = (request.locale || '').toLowerCase().split(/\-/);
reply(html({ reply(html({
locale: locales[1], locale: request.locale,
lang: locales[0] lang: locales[0]
})); }));
}; };

View File

@ -5,7 +5,12 @@
- [x] form values in redux store - [x] form values in redux store
- [x] clear / retain values in store - [x] clear / retain values in store
- [x] pre-populate form - [x] pre-populate form
- [x] validation field / form level - [x] validation on field / form level
- [x] multi page form - [x] multi page form
- [x] custom form fields - [x] custom form components
- [?] requires updates to existing ui components - [ ] requires updates to existing ui components as props to custom components are passed in the following format:
`"props": { "input": "value": "", "name": "", "onChange": "", "onFocus": "", ... }, "meta": { "valid": "", "error": "", ... }, "anyOtherPropsOnField": "", ... }`
- [ ] explore proxying props from Field to custom components from above shape to a flat form as expected by custom components
- [ ] consider creating component that handles logic and display of label and error which would be reused by form components to avoid code duplication for this functionality

View File

@ -0,0 +1,15 @@
{
"sourceMaps": "both",
"presets": [
"react",
"es2015"
],
"plugins": [
"react-hot-loader/babel",
"add-module-exports",
"syntax-async-functions",
["transform-object-rest-spread", {
"useBuiltIns": true
}]
]
}

View File

@ -0,0 +1,42 @@
{
"name": "react-infinite-spike",
"private": true,
"license": "private",
"scripts": {
"start": "webpack-dev-server --config webpack/index.js"
},
"dependencies": {
"build-array": "^1.0.0",
"delay": "^1.3.1",
"lodash.debounce": "^4.0.8",
"react": "^15.4.0",
"react-dom": "^15.4.0",
"react-fuzzy-filter": "^2.3.0",
"react-hot-loader": "^3.0.0-beta.6",
"react-infinite": "^0.10.0",
"react-redux": "^4.4.6",
"redux": "^3.6.0",
"redux-logger": "^2.7.4",
"redux-promise-middleware": "^4.1.0",
"redux-thunk": "^2.1.0"
},
"devDependencies": {
"babel-core": "^6.18.2",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.7",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-transform-object-rest-spread": "^6.19.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"css-loader": "^0.25.0",
"extract-text-webpack-plugin": "^2.0.0-beta.4",
"faker": "^3.1.0",
"postcss-loader": "^1.0.0",
"postcss-modules-values": "^1.2.2",
"postcss-nested": "^1.0.0",
"style-loader": "^0.13.1",
"webpack": "^2.1.0-beta.27",
"webpack-dev-server": "^1.16.2"
}
}

View File

@ -0,0 +1,5 @@
# react-fuzzy-filter
- https://github.com/jdlehman/react-fuzzy-filter
- Does not use API endpoint, instead an array of objects needs to be created locally

View File

@ -0,0 +1,91 @@
const buildArray = require('build-array');
const delay = require('delay');
const faker = require('faker');
const actions = {
'FETCH_FULFILLED': (state, action) => {
return {
...state,
fetching: false,
items: (state.items || []).concat(action.payload)
};
},
'FETCH_PENDING': (state, action) => {
return {
...state,
fetching: true
};
},
'FILTER_PENDING': (state, action) => {
return {
...state,
fetching: true
};
},
'FILTER_FULFILLED': (state, action) => {
return {
...state,
fetching: false,
filtered: action.payload.length !== state.items.length
? action.payload
: null
};
},
};
const fetch = () => (dispatch, getState) => {
const {
filtered
} = getState();
if (filtered) {
return;
}
return dispatch({
type: 'FETCH',
payload: delay(500).then(() => {
const {
items = []
} = getState();
return buildArray(100000).map((v, i) => {
const id = items.length + i;
return {
id,
title: `test ${id}`,
description: faker.lorem.sentence(),
image: faker.image.imageUrl(),
date: faker.date.recent()
};
});
})
});
};
const filter = (payload) => (dispatch, getState) => {
const regexp = new RegExp(payload);
return dispatch({
type: 'FILTER',
payload: delay(500).then(() => {
const {
items = []
} = getState();
return items.filter((item) => {
return regexp.test(item.title);
}).sort((a, b) => a.id - b.id);
})
});
};
module.exports = (state, action) => {
return actions[action.type]
? actions[action.type](state, action)
: state;
};
module.exports.fetch = fetch;
module.exports.filter = filter;

View File

@ -0,0 +1,18 @@
const Store = require('./store');
const ReactDOM = require('react-dom');
const React = require('react');
const render = () => {
const Root = require('./root');
ReactDOM.render(
<Root store={Store()} />,
document.getElementById('root')
);
};
render();
if (module.hot) {
module.hot.accept('./root', render);
}

View File

@ -0,0 +1,24 @@
const ReactRedux = require('react-redux');
const ReactHotLoader = require('react-hot-loader');
const React = require('react');
const Search = require('./search');
const {
AppContainer
} = ReactHotLoader;
const {
Provider
} = ReactRedux;
module.exports = ({
store
}) => {
return (
<AppContainer>
<Provider store={store}>
<Search />
</Provider>
</AppContainer>
);
};

View File

@ -0,0 +1,62 @@
const ReactRedux = require('react-redux');
const actions = require('./actions');
const React = require('react');
const users = require('../../users');
import fuzzyFilterFactory from 'react-fuzzy-filter';
// these components share state and can even live in different components
const {InputFilter, FilterResults} = fuzzyFilterFactory();
const {
connect
} = ReactRedux;
const {
fetch,
filter
} = actions;
const mapStateToProps = (state) => {
return state;
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
fetch: () => {
return dispatch(fetch());
},
filter: (payload) => {
return dispatch(filter(payload));
}
}
};
const Search = React.createClass({
renderItem: function(item, index) {
return(<div key={index}>{item.name}</div>);
},
render: function() {
const fuseConfig = {
keys: ['meta', 'tag']
};
return (
<div>
<InputFilter />
<div>Any amount of content between</div>
<FilterResults
items={users}
renderItem={this.renderItem}
fuseConfig={fuseConfig}
/>
</div>
);
}
})
module.exports = connect(
mapStateToProps,
mapDispatchToProps,
)(Search);

View File

@ -0,0 +1,19 @@
const createLogger = require('redux-logger');
const promiseMiddleware = require('redux-promise-middleware').default;
const thunk = require('redux-thunk').default;
const redux = require('redux');
const reducer = require('./actions');
const {
createStore,
compose,
applyMiddleware
} = redux;
module.exports = (state = Object.freeze({})) => {
return createStore(reducer, state, applyMiddleware(
createLogger(),
promiseMiddleware(),
thunk
));
};

View File

@ -0,0 +1,7 @@
*
!.gitignore
!.gitkeep
!index.html
js/*
!js/.gitkeep

View File

@ -0,0 +1,10 @@
<!doctype html>
<html lang='en-US'>
<head>
<title>Infinite List</title>
</head>
<body>
<div id='root'></div>
<script src='main.js'></script>
</body>
</html>

View File

@ -0,0 +1,56 @@
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const plugins = {
'no-errors-plugin': new webpack.NoErrorsPlugin(),
};
exports.config = {
context: path.join(__dirname, '../'),
output: {
path: path.join(__dirname, '../static'),
publicPath: '/',
filename: '[name].js'
},
plugins: [
new webpack.NoErrorsPlugin(),
new ExtractTextPlugin({
filename: 'css/[name].css',
allChunks: true
}),
new webpack.LoaderOptionsPlugin({
options: {
postcss: {}
}
})
],
module: {
loaders: [{
test: /js?$/,
exclude: /node_modules/,
include: [
path.join(__dirname, '../src')
],
loader: 'babel-loader'
}, {
test: /\.css?$/,
exclude: /node_modules/,
include: [
path.join(__dirname, '../src'),
path.join(__dirname, '../docs')
],
loader: ExtractTextPlugin.extract({
fallbackLoader: 'style-loader',
loader: [
'css-loader?',
'modules&importLoaders=1&',
'localIdentName=[name]__[local]___[hash:base64:5]!',
'postcss-loader'
].join('')
})
}]
}
};
exports.plugins = plugins;

View File

@ -0,0 +1,29 @@
const base = require('./base.js');
const webpack = require('webpack');
const path = require('path');
const devServer = {
contentBase: [
path.join(__dirname, '../static/')
],
hot: true,
compress: true,
lazy: false,
historyApiFallback: {
index: './index.html'
}
};
module.exports = Object.assign(base.config, {
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./src/index.js'
],
plugins: base.config.plugins.concat([
new webpack.HotModuleReplacementPlugin()
]),
devtool: 'source-map',
devServer
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
{
"sourceMaps": "both",
"presets": [
"react",
"es2015"
],
"plugins": [
"react-hot-loader/babel",
"add-module-exports",
"syntax-async-functions",
["transform-object-rest-spread", {
"useBuiltIns": true
}]
]
}

View File

@ -0,0 +1,39 @@
{
"name": "react-infinite-spike",
"private": true,
"license": "private",
"scripts": {
"start": "webpack-dev-server --config webpack/index.js"
},
"dependencies": {
"axios": "^0.15.3",
"build-array": "^1.0.0",
"delay": "^1.3.1",
"lodash.debounce": "^4.0.8",
"react": "^15.4.0",
"react-dom": "^15.4.0",
"react-hot-loader": "^3.0.0-beta.6",
"react-infinite": "^0.10.0",
"react-redux": "^4.4.6",
"react-select": "^1.0.0-rc.2",
"redux": "^3.6.0",
"redux-logger": "^2.7.4",
"redux-promise-middleware": "^4.1.0",
"redux-thunk": "^2.1.0"
},
"devDependencies": {
"babel-core": "^6.18.2",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.7",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-transform-object-rest-spread": "^6.19.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"extract-text-webpack-plugin": "^2.0.0-beta.4",
"faker": "^3.1.0",
"style-loader": "^0.13.1",
"webpack": "^2.1.0-beta.27",
"webpack-dev-server": "^1.16.2"
}
}

View File

@ -0,0 +1,7 @@
# react-select
- http://jedwatson.github.io/react-select/
- Has a seperate stylesheet that needs to be included
- Allows for async option

View File

@ -0,0 +1,17 @@
const ReactDOM = require('react-dom');
const React = require('react');
const render = () => {
const Root = require('./root');
ReactDOM.render(
<Root />,
document.getElementById('root')
);
};
render();
if (module.hot) {
module.hot.accept('./root', render);
}

View File

@ -0,0 +1,27 @@
const ReactRedux = require('react-redux');
const ReactHotLoader = require('react-hot-loader');
const React = require('react');
const Search = require('./search');
const SearchAsync = require('./search-async')
const {
AppContainer
} = ReactHotLoader;
const {
Provider
} = ReactRedux;
module.exports = ({
store
}) => {
return (
<AppContainer>
<div>
<Search multi />
<Search />
<SearchAsync />
</div>
</AppContainer>
);
};

View File

@ -0,0 +1,56 @@
const React = require('react');
const Select = require('react-select');
const SelectAsync = Select.Async
const SearchAsync = React.createClass({
getInitialState: function() {
return {
selectValue: ''
}
},
updateValue: function(newValue) {
console.log('State changed to ' + newValue);
this.setState({
selectValue: newValue
});
},
options: function(input, callback) {
setTimeout(function() {
callback(null, {
options: [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'three', label: 'Three' },
{ value: 'four', label: 'Four' },
{ value: 'five', label: 'Five' },
{ value: 'six', label: 'Six' },
],
// CAREFUL! Only set this to true when there are no more options,
// or more specific queries will not be sent to the server.
complete: true
}, 10000);
});
},
render: function () {
return (
<div>
<h1> Async Search </h1>
<SelectAsync
ref="stateSelectAsync"
loadOptions={this.options}
value={this.state.selectValue}
onChange={this.updateValue}
multi={true}
/>
</div>
)
}
})
module.exports = SearchAsync

View File

@ -0,0 +1,49 @@
const React = require('react');
const Select = require('react-select');
const Search = React.createClass({
getInitialState: function() {
return {
selectValue: ''
}
},
updateValue: function(newValue) {
console.log('State changed to ' + newValue);
this.setState({
selectValue: newValue
});
},
render: function () {
var options = [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'three', label: 'Three' },
{ value: 'four', label: 'Four' },
{ value: 'five', label: 'Five' },
{ value: 'six', label: 'Six' },
];
return (
<div>
<h1> {this.props.multi ? 'Multi' : 'Single'} Search </h1>
<Select
ref="stateSelect"
className="is-open"
autofocus
options={options}
name="selected-state"
value={this.state.selectValue}
onChange={this.updateValue}
multi={this.props.multi}
/>
</div>
)
}
})
module.exports = Search

View File

@ -0,0 +1,7 @@
*
!.gitignore
!.gitkeep
!index.html
js/*
!js/.gitkeep

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang='en-US'>
<head>
<title>React Select</title>
</head>
<body>
<div id='root'></div>
<script src='main.js'></script>
<link rel="stylesheet" href="https://unpkg.com/react-select/dist/react-select.css">
</body>
</html>

View File

@ -0,0 +1,56 @@
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const plugins = {
'no-errors-plugin': new webpack.NoErrorsPlugin(),
};
exports.config = {
context: path.join(__dirname, '../'),
output: {
path: path.join(__dirname, '../static'),
publicPath: '/',
filename: '[name].js'
},
plugins: [
new webpack.NoErrorsPlugin(),
new ExtractTextPlugin({
filename: 'css/[name].css',
allChunks: true
}),
new webpack.LoaderOptionsPlugin({
options: {
postcss: {}
}
})
],
module: {
loaders: [{
test: /js?$/,
exclude: /node_modules/,
include: [
path.join(__dirname, '../src')
],
loader: 'babel-loader'
}, {
test: /\.css?$/,
exclude: /node_modules/,
include: [
path.join(__dirname, '../src'),
path.join(__dirname, '../docs')
],
loader: ExtractTextPlugin.extract({
fallbackLoader: 'style-loader',
loader: [
'css-loader?',
'modules&importLoaders=1&',
'localIdentName=[name]__[local]___[hash:base64:5]!',
'postcss-loader'
].join('')
})
}]
}
};
exports.plugins = plugins;

View File

@ -0,0 +1,29 @@
const base = require('./base.js');
const webpack = require('webpack');
const path = require('path');
const devServer = {
contentBase: [
path.join(__dirname, '../static/')
],
hot: true,
compress: true,
lazy: false,
historyApiFallback: {
index: './index.html'
}
};
module.exports = Object.assign(base.config, {
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./src/index.js'
],
plugins: base.config.plugins.concat([
new webpack.HotModuleReplacementPlugin()
]),
devtool: 'source-map',
devServer
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
{
"sourceMaps": "both",
"presets": [
"react",
"es2015"
],
"plugins": [
"react-hot-loader/babel",
"add-module-exports",
"syntax-async-functions",
["transform-object-rest-spread", {
"useBuiltIns": true
}]
]
}

View File

@ -0,0 +1,41 @@
{
"name": "react-infinite-spike",
"private": true,
"license": "private",
"scripts": {
"start": "webpack-dev-server --config webpack/index.js"
},
"dependencies": {
"axios": "^0.15.3",
"build-array": "^1.0.0",
"delay": "^1.3.1",
"lodash.debounce": "^4.0.8",
"react": "^15.4.0",
"react-addons-css-transition-group": "^15.4.1",
"react-addons-shallow-compare": "^15.4.1",
"react-dom": "^15.4.0",
"react-hot-loader": "^3.0.0-beta.6",
"react-infinite": "^0.10.0",
"react-redux": "^4.4.6",
"react-selectize": "^2.1.0",
"redux": "^3.6.0",
"redux-logger": "^2.7.4",
"redux-promise-middleware": "^4.1.0",
"redux-thunk": "^2.1.0"
},
"devDependencies": {
"babel-core": "^6.18.2",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.7",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-transform-object-rest-spread": "^6.19.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"extract-text-webpack-plugin": "^2.0.0-beta.4",
"faker": "^3.1.0",
"style-loader": "^0.13.1",
"webpack": "^2.1.0-beta.27",
"webpack-dev-server": "^1.16.2"
}
}

View File

@ -0,0 +1,5 @@
# react-fuzzy-filter
- https://github.com/jdlehman/react-fuzzy-filter
- Does not use API endpoint, instead an array of objects needs to be created locally

View File

@ -0,0 +1,44 @@
const buildArray = require('build-array');
const delay = require('delay');
const faker = require('faker');
const actions = {
'FETCH': (state, action) => {
return {
...state,
items: (state.items || []).concat(action.payload)
};
}
};
const fetch = () => (dispatch, getState) => {
return dispatch({
type: 'FETCH',
payload: () => {
// debugger
const {
items = []
} = getState();
return buildArray(200).map((v, i) => {
const id = items.length + i;
return {
id,
name: faker.name.firstName(),
meta: `${faker.name.firstName()}|${faker.random.number()}`,
key: faker.image.imageUrl()
};
});
}
});
};
module.exports = (state, action) => {
return actions[action.type]
? actions[action.type](state, action)
: state;
};
module.exports.fetch = fetch;

View File

@ -0,0 +1,18 @@
const Store = require('./store');
const ReactDOM = require('react-dom');
const React = require('react');
const render = () => {
const Root = require('./root');
ReactDOM.render(
<Root store={Store()} />,
document.getElementById('root')
);
};
render();
if (module.hot) {
module.hot.accept('./root', render);
}

View File

@ -0,0 +1,24 @@
const ReactRedux = require('react-redux');
const ReactHotLoader = require('react-hot-loader');
const React = require('react');
const Search = require('./search');
const {
AppContainer
} = ReactHotLoader;
const {
Provider
} = ReactRedux;
module.exports = ({
store
}) => {
return (
<AppContainer>
<Provider store={store}>
<Search />
</Provider>
</AppContainer>
);
};

View File

@ -0,0 +1,28 @@
const actions = require('./actions');
const React = require('react');
const axios = require('axios');
const users = require('../../users');
const ReactSelectize = require("react-selectize");
const SimpleSelect = ReactSelectize.SimpleSelect;
const Search = () => {
let _options = ["apple", "mango", "grapes", "melon", "strawberry"];
_options = _options.map(function(fruit){
return {label: fruit, value: fruit}
});
return (
<SimpleSelect
options={_options}
placeholder="Select a fruit"
theme="material"
/>
);
}
module.exports = Search;

View File

@ -0,0 +1,19 @@
const createLogger = require('redux-logger');
const promiseMiddleware = require('redux-promise-middleware').default;
const thunk = require('redux-thunk').default;
const redux = require('redux');
const reducer = require('./actions');
const {
createStore,
compose,
applyMiddleware
} = redux;
module.exports = (state = Object.freeze({})) => {
return createStore(reducer, state, applyMiddleware(
createLogger(),
promiseMiddleware(),
thunk
));
};

View File

@ -0,0 +1,7 @@
*
!.gitignore
!.gitkeep
!index.html
js/*
!js/.gitkeep

View File

@ -0,0 +1,10 @@
<!doctype html>
<html lang='en-US'>
<head>
<title>Infinite List</title>
</head>
<body>
<div id='root'></div>
<script src='main.js'></script>
</body>
</html>

View File

@ -0,0 +1,56 @@
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const plugins = {
'no-errors-plugin': new webpack.NoErrorsPlugin(),
};
exports.config = {
context: path.join(__dirname, '../'),
output: {
path: path.join(__dirname, '../static'),
publicPath: '/',
filename: '[name].js'
},
plugins: [
new webpack.NoErrorsPlugin(),
new ExtractTextPlugin({
filename: 'css/[name].css',
allChunks: true
}),
new webpack.LoaderOptionsPlugin({
options: {
postcss: {}
}
})
],
module: {
loaders: [{
test: /js?$/,
exclude: /node_modules/,
include: [
path.join(__dirname, '../src')
],
loader: 'babel-loader'
}, {
test: /\.css?$/,
exclude: /node_modules/,
include: [
path.join(__dirname, '../src'),
path.join(__dirname, '../docs')
],
loader: ExtractTextPlugin.extract({
fallbackLoader: 'style-loader',
loader: [
'css-loader?',
'modules&importLoaders=1&',
'localIdentName=[name]__[local]___[hash:base64:5]!',
'postcss-loader'
].join('')
})
}]
}
};
exports.plugins = plugins;

View File

@ -0,0 +1,29 @@
const base = require('./base.js');
const webpack = require('webpack');
const path = require('path');
const devServer = {
contentBase: [
path.join(__dirname, '../static/')
],
hot: true,
compress: true,
lazy: false,
historyApiFallback: {
index: './index.html'
}
};
module.exports = Object.assign(base.config, {
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./src/index.js'
],
plugins: base.config.plugins.concat([
new webpack.HotModuleReplacementPlugin()
]),
devtool: 'source-map',
devServer
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
{
"sourceMaps": "both",
"presets": [
"react",
"es2015"
],
"plugins": [
"react-hot-loader/babel",
"add-module-exports",
"syntax-async-functions",
["transform-object-rest-spread", {
"useBuiltIns": true
}]
]
}

View File

@ -0,0 +1,41 @@
{
"name": "react-infinite-spike",
"private": true,
"license": "private",
"scripts": {
"start": "webpack-dev-server --config webpack/index.js"
},
"dependencies": {
"axios": "^0.15.3",
"build-array": "^1.0.0",
"delay": "^1.3.1",
"lodash.debounce": "^4.0.8",
"react": "^15.4.0",
"react-addons-css-transition-group": "^15.4.1",
"react-addons-shallow-compare": "^15.4.1",
"react-dom": "^15.4.0",
"react-hot-loader": "^3.0.0-beta.6",
"react-infinite": "^0.10.0",
"react-redux": "^4.4.6",
"react-selectize": "^2.1.0",
"redux": "^3.6.0",
"redux-logger": "^2.7.4",
"redux-promise-middleware": "^4.1.0",
"redux-thunk": "^2.1.0"
},
"devDependencies": {
"babel-core": "^6.18.2",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.7",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-transform-object-rest-spread": "^6.19.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"extract-text-webpack-plugin": "^2.0.0-beta.4",
"faker": "^3.1.0",
"style-loader": "^0.13.1",
"webpack": "^2.1.0-beta.27",
"webpack-dev-server": "^1.16.2"
}
}

View File

@ -0,0 +1,7 @@
# react-fuzzy-filter
- https://github.com/jdlehman/react-fuzzy-filter
- Does not use API endpoint, instead an array of objects needs to be created locally
- Hard to style - or didn't come easily straight out of the box
- Reqiures adding `react-addons-css-transition-group` and `react-addons-shallow-compare`

View File

@ -0,0 +1,44 @@
const buildArray = require('build-array');
const delay = require('delay');
const faker = require('faker');
const actions = {
'FETCH': (state, action) => {
return {
...state,
items: (state.items || []).concat(action.payload)
};
}
};
const fetch = () => (dispatch, getState) => {
return dispatch({
type: 'FETCH',
payload: () => {
// debugger
const {
items = []
} = getState();
return buildArray(200).map((v, i) => {
const id = items.length + i;
return {
id,
name: faker.name.firstName(),
meta: `${faker.name.firstName()}|${faker.random.number()}`,
key: faker.image.imageUrl()
};
});
}
});
};
module.exports = (state, action) => {
return actions[action.type]
? actions[action.type](state, action)
: state;
};
module.exports.fetch = fetch;

View File

@ -0,0 +1,18 @@
const Store = require('./store');
const ReactDOM = require('react-dom');
const React = require('react');
const render = () => {
const Root = require('./root');
ReactDOM.render(
<Root store={Store()} />,
document.getElementById('root')
);
};
render();
if (module.hot) {
module.hot.accept('./root', render);
}

View File

@ -0,0 +1,23 @@
const ReactRedux = require('react-redux');
const ReactHotLoader = require('react-hot-loader');
const React = require('react');
const Search = require('./search');
const {
AppContainer
} = ReactHotLoader;
const {
Provider
} = ReactRedux;
module.exports = ({
store
}) => {
return (
<AppContainer>
<Provider store={store}>
<Search />
</Provider>
</AppContainer>
);
};

View File

@ -0,0 +1,90 @@
const ReactRedux = require('react-redux');
const actions = require('./actions');
const React = require('react');
const axios = require('axios');
const users = require('../../users');
var ReactSelectize = require("react-selectize");
var MultiSelect = ReactSelectize.MultiSelect;
const {
connect
} = ReactRedux;
const {
fetch
} = actions;
const mapStateToProps = (state) => {
return state;
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
fetch: () => {
return dispatch(fetch());
}
}
};
const SearchMulti = React.createClass({
getInitialState: function(){
return {
countries: [],
country: undefined
};
},
componentWillMount: function(){
var self = this;
this.req = axios.get("http://restverse.com/countries").then(function(countries){
self.setState({countries: countries.data}, function(){
self.refs.select.highlightFirstSelectableOption();
});
});
},
render: function(){
var self = this;
const _onValueChange = (country) => {
self.setState({country: country});
}
const _renderNoResultsFound = () => {
return (
<div className = "no-results-found">
{!!self.req ? "loading countries ..." : "No results found"}
</div>
)
}
return (
<div>
<h1>Multiple Options</h1>
<MultiSelect
ref = "select"
placeholder = "Select a country"
options = {this.state.countries}
value = {this.state.country}
onValueChange = {_onValueChange}
renderNoResultsFound = {_renderNoResultsFound}
/>
{ !!self.state.country ? (
<div style = {{margin: 8}}>
<span>you selected: </span>
<span style = {{fontWeight: "bold"}}>{self.state.country.label}</span>
</div>
) : null }
</div>
)
}
})
module.exports = connect(
mapStateToProps,
mapDispatchToProps,
)(SearchMulti);

View File

@ -0,0 +1,90 @@
const ReactRedux = require('react-redux');
const actions = require('./actions');
const React = require('react');
const axios = require('axios');
const users = require('../../users');
var ReactSelectize = require("react-selectize");
var SimpleSelect = ReactSelectize.SimpleSelect;
const {
connect
} = ReactRedux;
const {
fetch
} = actions;
const mapStateToProps = (state) => {
return state;
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
fetch: () => {
return dispatch(fetch());
}
}
};
const Search = React.createClass({
getInitialState: function(){
return {
countries: [],
country: undefined
};
},
componentWillMount: function(){
var self = this;
this.req = axios.get("http://restverse.com/countries").then(function(countries){
self.setState({countries: countries.data}, function(){
self.refs.select.highlightFirstSelectableOption();
});
});
},
render: function(){
var self = this;
const _onValueChange = (country) => {
self.setState({country: country});
}
const _renderNoResultsFound = () => {
return (
<div className = "no-results-found">
{!!self.req ? "loading countries ..." : "No results found"}
</div>
)
}
return (
<div>
<h1>Single Select</h1>
<SimpleSelect
ref = "select"
placeholder = "Select a country"
options = {this.state.countries}
value = {this.state.country}
onValueChange = {_onValueChange}
renderNoResultsFound = {_renderNoResultsFound}
/>
{ !!self.state.country ? (
<div style = {{margin: 8}}>
<span>you selected: </span>
<span style = {{fontWeight: "bold"}}>{self.state.country.label}</span>
</div>
) : null }
</div>
)
}
})
module.exports = connect(
mapStateToProps,
mapDispatchToProps,
)(Search);

View File

@ -0,0 +1,15 @@
const React = require('react');
const SearchSingle = require('./search-single');
const SearchMulti = require('./search-multi')
const Search = () => {
return (
<div>
<SearchSingle />
<SearchMulti />
</div>
)
}
module.exports = Search

View File

@ -0,0 +1,19 @@
const createLogger = require('redux-logger');
const promiseMiddleware = require('redux-promise-middleware').default;
const thunk = require('redux-thunk').default;
const redux = require('redux');
const reducer = require('./actions');
const {
createStore,
compose,
applyMiddleware
} = redux;
module.exports = (state = Object.freeze({})) => {
return createStore(reducer, state, applyMiddleware(
createLogger(),
promiseMiddleware(),
thunk
));
};

View File

@ -0,0 +1,7 @@
*
!.gitignore
!.gitkeep
!index.html
js/*
!js/.gitkeep

View File

@ -0,0 +1,10 @@
<!doctype html>
<html lang='en-US'>
<head>
<title>Multi Select</title>
</head>
<body>
<div id='root'></div>
<script src='main.js'></script>
</body>
</html>

View File

@ -0,0 +1,56 @@
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const plugins = {
'no-errors-plugin': new webpack.NoErrorsPlugin(),
};
exports.config = {
context: path.join(__dirname, '../'),
output: {
path: path.join(__dirname, '../static'),
publicPath: '/',
filename: '[name].js'
},
plugins: [
new webpack.NoErrorsPlugin(),
new ExtractTextPlugin({
filename: 'css/[name].css',
allChunks: true
}),
new webpack.LoaderOptionsPlugin({
options: {
postcss: {}
}
})
],
module: {
loaders: [{
test: /js?$/,
exclude: /node_modules/,
include: [
path.join(__dirname, '../src')
],
loader: 'babel-loader'
}, {
test: /\.css?$/,
exclude: /node_modules/,
include: [
path.join(__dirname, '../src'),
path.join(__dirname, '../docs')
],
loader: ExtractTextPlugin.extract({
fallbackLoader: 'style-loader',
loader: [
'css-loader?',
'modules&importLoaders=1&',
'localIdentName=[name]__[local]___[hash:base64:5]!',
'postcss-loader'
].join('')
})
}]
}
};
exports.plugins = plugins;

View File

@ -0,0 +1,29 @@
const base = require('./base.js');
const webpack = require('webpack');
const path = require('path');
const devServer = {
contentBase: [
path.join(__dirname, '../static/')
],
hot: true,
compress: true,
lazy: false,
historyApiFallback: {
index: './index.html'
}
};
module.exports = Object.assign(base.config, {
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./src/index.js'
],
plugins: base.config.plugins.concat([
new webpack.HotModuleReplacementPlugin()
]),
devtool: 'source-map',
devServer
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
module.exports = [
{
"name": "Franks Howe",
"integer": 0,
"meta": "Franks Howe|0",
"tag": "ea"
},
{
"name": "Lucas Barlow",
"integer": 1,
"meta": "Lucas Barlow|1",
"tag": "dolore"
},
{
"name": "Black Cote",
"integer": 3,
"meta": "Black Cote|3",
"tag": "eu"
},
{
"name": "Francisca Pickett",
"integer": 10,
"meta": "Francisca Pickett|10",
"tag": "ipsum"
},
{
"name": "Debbie Morris",
"integer": 2,
"meta": "Debbie Morris|2",
"tag": "consectetur"
},
{
"name": "Olga Mclean",
"integer": 7,
"meta": "Olga Mclean|7",
"tag": "laboris"
},
{
"name": "Wendy Cabrera",
"integer": 8,
"meta": "Wendy Cabrera|8",
"tag": "nisi"
},
{
"name": "Nell Duncan",
"integer": 3,
"meta": "Nell Duncan|3",
"tag": "qui"
},
{
"name": "Mendoza Wiggins",
"integer": 4,
"meta": "Mendoza Wiggins|4",
"tag": "Lorem"
},
{
"name": "Edith Franklin",
"integer": 3,
"meta": "Edith Franklin|3",
"tag": "exercitation"
}
]

1
ui/.storybook/head.html Normal file
View File

@ -0,0 +1 @@
<link href="https://fonts.googleapis.com/css?family=Libre+Franklin:400,600" rel="stylesheet">

View File

@ -4,7 +4,6 @@ ARG CIRCLE_BUILD_NUM
ENV NPM_TOKEN ${NPM_TOKEN} ENV NPM_TOKEN ${NPM_TOKEN}
ENV CIRCLE_BUILD_NUM ${CIRCLE_BUILD_NUM} ENV CIRCLE_BUILD_NUM ${CIRCLE_BUILD_NUM}
RUN echo -e "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc RUN echo -e "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
RUN make install
RUN NODE_ENV=production make compile RUN NODE_ENV=production make compile
RUN npm version 0.0.1-${CIRCLE_BUILD_NUM} RUN npm version 0.0.1-${CIRCLE_BUILD_NUM}
RUN npm publish --tag alpha --access public RUN npm publish --tag alpha --access public

View File

@ -28,7 +28,7 @@ install-docs:
.PHONY: install .PHONY: install
install: install-embed-markdown-loader install: install-embed-markdown-loader
yarn install --prefer-offline NODE_ENV=development yarn install --prefer-offline
.PHONY: start .PHONY: start
start: start:

View File

@ -1,4 +1,14 @@
const React = require('react'); const React = require('react');
const Styled = require('styled-components');
const constants = require('../../shared/constants');
const {
colors
} = constants;
const {
default: styled
} = Styled;
const buildArray = require('build-array'); const buildArray = require('build-array');
const Chart = require('chart.js'); const Chart = require('chart.js');
@ -6,6 +16,62 @@ const whisker = require('chartjs-chart-box-plot');
whisker(Chart); whisker(Chart);
const StyledDiv = styled.div`
height: 127px;
width: 158px;
background-color: ${colors.miniBackground};
border: solid 1px ${colors.borderSecondary};
&::before {
position: absolute;
z-index: 1;
width: 9px;
height: 127px;
background-image:
linear-gradient(to right, rgba(0, 0, 0, 0.1), rgba(216, 216, 216, 0));
content: '';
}
`;
const Devider = styled.div`
width: 158px;
height: 1px;
background-color: ${colors.seperator}
`;
const TextMetric = styled.div`
height: 38px;
padding: 8px 12px;
`;
const InnerTextBox = styled.div`
width: 136px;
height: 36px;
font-family: 'Libre Franklin', sans-serif;
font-size: 12px;
font-weight: normal;
font-style: normal;
font-stretch: normal;
line-height: 18px;
text-align: right;
color: ${colors.regular};
& p {
margin: 0;
}
& h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
line-height: 1.29;
color: ${colors.semibold};
}
`;
const StyledCanvas = styled.canvas`
`;
class MiniMetric extends React.Component { class MiniMetric extends React.Component {
componentDidMount() { componentDidMount() {
const { const {
@ -22,13 +88,16 @@ class MiniMetric extends React.Component {
this._chart = new Chart(this._refs.component, { this._chart = new Chart(this._refs.component, {
type: 'whisker', type: 'whisker',
responsive: true, responsive: true,
maintainAspectRatio: true,
options: { options: {
scales: { scales: {
xAxes: [{ xAxes: [{
display: false,
barPercentage: 1.0, barPercentage: 1.0,
categoryPercentage: 1.0 categoryPercentage: 1.0
}], }],
yAxes: [{ yAxes: [{
display: false,
ticks: { ticks: {
min: min, min: min,
max: max max: max
@ -36,7 +105,7 @@ class MiniMetric extends React.Component {
}] }]
}, },
legend: { legend: {
display: true display: false
} }
}, },
data: { data: {
@ -63,12 +132,25 @@ class MiniMetric extends React.Component {
}; };
} }
render() { render() {
const {
name,
} = this.props;
return ( return (
<canvas <StyledDiv>
height='400' <TextMetric>
ref={this.ref('component')} <InnerTextBox>
width='400' <h3>{name}: 54%</h3>
/> <p>(1280/3000 MB)</p>
</InnerTextBox>
</TextMetric>
<Devider />
<StyledCanvas
height='72'
innerRef={this.ref('component')}
width='157'
/>
</StyledDiv>
); );
} }
} }
@ -78,6 +160,7 @@ MiniMetric.propTypes = {
labels: React.PropTypes.number, labels: React.PropTypes.number,
max: React.PropTypes.number, max: React.PropTypes.number,
min: React.PropTypes.number, min: React.PropTypes.number,
name: React.PropTypes.string,
}; };
module.exports = MiniMetric; module.exports = MiniMetric;

View File

@ -1,3 +1,8 @@
const fonts = {
semibold: '#464646',
regular: '#646464'
};
const brandPrimary = { const brandPrimary = {
brandPrimary: '#3B46CC', brandPrimary: '#3B46CC',
brandPrimaryDark: '#1838C0', brandPrimaryDark: '#1838C0',
@ -32,11 +37,18 @@ const notifications = {
warningLight: '#FFFAED', warningLight: '#FFFAED',
}; };
const metrics = {
miniBackground: '#F3F4F9',
seperator: '#D9DEF3'
};
const colors = { const colors = {
...brandPrimary, ...brandPrimary,
...brandSecondary, ...brandSecondary,
...brandInactive, ...brandInactive,
...notifications ...notifications,
...metrics,
...fonts
}; };
module.exports = colors; module.exports = colors;

View File

@ -306,7 +306,139 @@ storiesOf('Widget', module)
</Widget> </Widget>
)); ));
const colors = {
perc: 'rgba(54, 74, 205, 0.2)',
alt: 'rgba(245, 93, 93, 0.2)'
};
storiesOf('Metrics', module) storiesOf('Metrics', module)
.add('mini', () => ( .add('Mini Metric', () => (
<MiniMetric /> <MiniMetric
datasets={[{
backgroundColor: colors['perc'],
altBackgroundColor: colors['alt'],
data: [
{
firstQuartile: 15,
thirdQuartile: 15,
median: 15,
max: 15,
min: 15,
},
{
firstQuartile: 26,
thirdQuartile: 26,
median: 26,
max: 26,
min: 26,
},
{
firstQuartile: 17,
thirdQuartile: 17,
median: 17,
max: 17,
min: 17,
},
{
firstQuartile: 15,
thirdQuartile: 25,
median: 19,
max: 19,
min: 20,
},
{
firstQuartile: 19,
thirdQuartile: 25,
median: 21,
max: 20,
min: 25,
},
{
firstQuartile: 24,
thirdQuartile: 30,
median: 25,
max: 26,
min: 27,
},
{
firstQuartile: 28,
thirdQuartile: 34,
median: 30,
max: 30,
min: 30,
},
{
firstQuartile: 30,
thirdQuartile: 45,
median: 35,
max: 40,
min: 40,
},
{
firstQuartile: 20,
thirdQuartile: 55,
median: 45,
max: 44,
min: 44,
},
{
firstQuartile: 55,
thirdQuartile: 55,
median: 55,
max: 55,
min: 55,
},
{
firstQuartile: 57,
thirdQuartile: 56,
median: 57,
max: 58,
min: 57,
},
{
firstQuartile: 57,
thirdQuartile: 56,
median: 56,
max: 56,
min: 56,
},
{
firstQuartile: 60,
thirdQuartile: 56,
median: 60,
max: 60,
min: 60,
},
{
firstQuartile: 57,
thirdQuartile: 57,
median: 57,
max: 57,
min: 57,
},
{
firstQuartile: 57,
thirdQuartile: 55,
median: 55,
max: 55,
min: 55,
},
{
firstQuartile: 20,
thirdQuartile: 45,
median: 45,
max: 45,
min: 45,
},
{
firstQuartile: 15,
thirdQuartile: 40,
median: 30,
max: 49,
min: 30,
},
]
}]}
labels={17}
name='Memory'
/>
)); ));