1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-31 00:16:47 +01:00

Merge pull request #155 from finn-no/feature/material_ui

Feature/material ui
This commit is contained in:
Ivar Conradi Østhus 2016-09-16 21:59:24 +02:00 committed by GitHub
commit 7b998e3aa3
24 changed files with 527 additions and 0 deletions

View File

@ -0,0 +1,8 @@
{
"presets": ["react", "es2015", "stage-2"],
"env": {
"development": {
"presets": ["react-hmre"]
}
}
}

View File

@ -0,0 +1,6 @@
{
"extends": [
"finn",
"finn/node"
]
}

View File

@ -0,0 +1 @@
dist

View File

@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<title>Unleash UI</title>
<link rel="stylesheet" href="/static/bundle.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet">
</head>
<body>
<div id='app'></div>
<script src="/static/bundle.js"></script>
</body>
</html>

View File

@ -0,0 +1,6 @@
'use strict';
const path = require('path');
module.exports = {
publicFolder: path.join(__dirname, 'dist'),
};

View File

@ -0,0 +1,9 @@
// preprocessor.js
'use strict';
const ReactTools = require('react-tools');
module.exports = {
process (src) {
return ReactTools.transform(src);
},
};

View File

@ -0,0 +1,78 @@
{
"name": "unleash-frontend-next",
"description": "unleash your features",
"version": "1.0.0",
"keywords": [
"unleash",
"feature toggle",
"feature",
"toggle"
],
"files": [
"public"
],
"repository": {
"type": "git",
"url": "ssh://git@github.com:finn-no/unleash.git"
},
"bugs": {
"url": "https://github.com/finn-no/unleash/issues"
},
"engines": {
"node": "6"
},
"scripts": {
"build": "webpack -p",
"start": "webpack-dev-server --config webpack.config.js --hot --progress --colors --port 3000",
"test": "echo 'no test'",
"test:ci": "npm run test",
"prepublish": "npm run build"
},
"main": "./index.js",
"dependencies": {
"immutability-helper": "^2.0.0",
"react": "^15.3.1",
"react-addons-css-transition-group": "^15.3.1",
"react-dom": "^15.3.1",
"react-redux": "^4.4.5",
"react-router": "^2.8.0",
"react-toolbox": "^1.2.1",
"redux": "^3.6.0"
},
"devDependencies": {
"babel-core": "^6.14.0",
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.11.1",
"babel-preset-react-hmre": "^1.1.1",
"babel-preset-stage-0": "^6.5.0",
"babel-preset-stage-2": "^6.13.0",
"css-loader": "^0.25.0",
"extract-text-webpack-plugin": "^1.0.1",
"node-sass": "~3.7.0",
"postcss-loader": "^0.13.0",
"redux-devtools": "^3.3.1",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.1",
"toolbox-loader": "0.0.3",
"webpack": "^1.13.2",
"webpack-dev-server": "^1.15.1"
},
"jest": {
"scriptPreprocessor": "<rootDir>/jest-preprocessor.js",
"modulePathIgnorePatterns": [
"<rootDir>/node_modules/npm"
],
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/reflux"
],
"moduleFileExtensions": [
"jsx",
"js"
]
},
"pre-commit": [
"lint"
]
}

View File

@ -0,0 +1,24 @@
{
"extends": [
"finn",
"finn-react"
],
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"rules": {
"react/sort-comp": "off"
},
"ecmaFeatures": {
"sourceType": "module"
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"experimentalObjectRestSpread": true
}
}
}

View File

@ -0,0 +1,37 @@
import React, { Component } from 'react';
import { Layout, Panel, NavDrawer, AppBar, IconButton } from 'react-toolbox';
import style from './style';
import Navigation from './Navigation';
export default class App extends Component {
constructor (props) {
super(props);
this.state = { drawerActive: false };
this.toggleDrawerActive = () => {
this.setState({ drawerActive: !this.state.drawerActive });
};
}
render () {
return (
<div className={style.container}>
<AppBar>
<IconButton icon="menu" onClick={ this.toggleDrawerActive }/>
</AppBar>
<Layout>
<NavDrawer active={this.state.drawerActive} permanentAt="sm" style={{ width: '200px' }}>
<Navigation />
</NavDrawer>
<Panel scrollY={false}>
<div style={{ padding: '1.8em' }}>
{this.props.children}
</div>
</Panel>
</Layout>
</div>
);
}
};

View File

@ -0,0 +1,25 @@
import React, { Component } from 'react';
import { Link } from 'react-router';
import { ListSubHeader, List, ListItem, ListDivider } from 'react-toolbox';
export default class UnleashNav extends Component {
render () {
return (
<List selectable ripple>
<Link to="/"><ListItem selectable className="active" caption="Feature Toggles" /></Link>
<Link to="/strategies"><ListItem selectable caption="Strategies" /></Link>
<Link to="/logs"><ListItem selectable caption="Log" /></Link>
<Link to="/archive"><ListItem selectable caption="Archive" /></Link>
<ListDivider />
<ListSubHeader Resources/>
<Link to="/archive"><ListItem selectable caption="Documentation" /></Link>
<a href="https://github.com/finn-no/unleash/" target="_blank"><ListItem selectable caption="GitHub" /></a>
<ListDivider />
<ListItem selectable={false} ripple="false">
<p>A product by <a href="https://finn.no" target="_blank">FINN.no</a></p>
</ListItem>
</List>
);
}
};

View File

@ -0,0 +1,11 @@
let nextFeatureId = 0;
export const addFeatureToggle = (featureName) => ({
type: 'ADD_FEATURE_TOGGLE',
id: nextFeatureId++,
featureName,
});
export const toggleFeature = (id) => ({
type: 'TOGGLE_FEATURE_TOGGLE',
id,
});

View File

@ -0,0 +1,31 @@
import React from 'react';
import { connect } from 'react-redux';
import { addFeatureToggle } from '../../action';
let AddFeatureToggle = ({ dispatch }) => {
let input;
return (
<div>
<form onSubmit={e => {
e.preventDefault();
if (!input.value.trim()) {
return;
}
dispatch(addFeatureToggle(input.value));
input.value = '';
}}>
<input ref={node => {
input = node;
}} />
<button type="submit">
Add Feature Toggle.
</button>
</form>
</div>
);
};
AddFeatureToggle = connect()(AddFeatureToggle);
export default AddFeatureToggle;

View File

@ -0,0 +1,16 @@
import React, { PropTypes } from 'react';
const Feature = ({ onClick, featureName, enabled }) => (
<li>
{featureName} is {enabled ? 'enabled ' : 'disabled '}
<a onClick={onClick} href="#">toggle</a>
</li>
);
Feature.propTypes = {
onClick: PropTypes.func.isRequired,
enabled: PropTypes.bool.isRequired,
featureName: PropTypes.string.isRequired,
};
export default Feature;

View File

@ -0,0 +1,21 @@
import React, { PropTypes } from 'react';
import Feature from './Feature';
const FeatureList = ({ features, onFeatureClick }) => (
<ul>
{features.map(featureToggle =>
<Feature
key={featureToggle.id}
{...featureToggle}
onClick={() => onFeatureClick(featureToggle.id)}
/>
)}
</ul>
);
FeatureList.propTypes = {
onFeatureClick: PropTypes.func.isRequired,
features: PropTypes.array.isRequired,
};
export default FeatureList;

View File

@ -0,0 +1,21 @@
import { connect } from 'react-redux';
import { toggleFeature } from '../../action';
import FeatureList from './FeatureList';
const mapStateToProps = (state) => ({
features: state.features,
});
const mapDispatchToProps = (dispatch) => ({
onFeatureClick: (id) => {
dispatch(toggleFeature(id));
},
});
const FeatureListContainer = connect(
mapStateToProps,
mapDispatchToProps
)(FeatureList);
export default FeatureListContainer;

View File

@ -0,0 +1,27 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import store from './store';
import App from './App';
import Features from './page/features';
import Strategies from './page/strategies';
import Logs from './page/logs';
import Archive from './page/archive';
const unleashStore = createStore(store);
ReactDOM.render(
<Provider store={unleashStore}>
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Features} />
<Route path="/strategies" component={Strategies} />
<Route path="/logs" component={Logs} />
<Route path="/archive" component={Archive} />
</Route>
</Router>
</Provider>, document.getElementById('app'));

View File

@ -0,0 +1,28 @@
import React, { Component } from 'react';
import { Card, CardTitle, CardText } from 'react-toolbox';
export default class Archive extends Component {
render () {
return (
<Card>
<CardTitle>Archived Feture Toggles</CardTitle>
<CardText>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam pharetra finibus
ullamcorper. Proin laoreet faucibus venenatis. Aenean quis leo finibus, maximus
nisi finibus, fringilla ex. Nam mollis congue orci eu consectetur. Aliquam a
massa quis tortor vestibulum lacinia. Phasellus nisi velit, mattis vel nulla
a, rhoncus porta dui. Vestibulum viverra augue in pellentesque tincidunt.
Aliquam rhoncus nunc ipsum, sed vehicula leo dictum in. Phasellus accumsan
elis sem, in ullamcorper nisi accumsan vitae. Nullam vitae consectetur mi,
sed vulputate augue. In quis augue tellus. Duis convallis cursus elit, in
interdum nisl pulvinar viverra. Sed vel ornare sapien, eu consectetur metus.
Vivamus at porta nisl. Nullam in aliquam nisl.
</p>
</CardText>
</Card>
);
}
};

View File

@ -0,0 +1,17 @@
import React, { Component } from 'react';
import FeatureListContainer from '../../component/feature/FeatureListContainer';
import AddFeatureToggle from '../../component/feature/AddFeatureToggle';
export default class Features extends Component {
render () {
return (
<div>
<h1>Feature Toggles</h1>
<AddFeatureToggle />
<FeatureListContainer />
</div>
);
}
};

View File

@ -0,0 +1,11 @@
import React, { Component } from 'react';
export default class Strategies extends Component {
render () {
return (
<div>
<h1>Strategies</h1>
</div>
);
}
};

View File

@ -0,0 +1,39 @@
const feature = (state = {}, action) => {
switch (action.type) {
case 'ADD_FEATURE_TOGGLE':
return {
id: action.id,
featureName: action.featureName,
enabled: false,
};
case 'TOGGLE_FEATURE_TOGGLE':
if (state.id !== action.id) {
return state;
}
return Object.assign({}, state, {
enabled: !state.enabled,
});
default:
return state;
}
};
const features = (state = [{ id: 1, featureName: 'test', enabled: true }], action) => {
switch (action.type) {
case 'ADD_FEATURE_TOGGLE':
return [
...state,
feature(undefined, action),
];
case 'TOGGLE_FEATURE_TOGGLE':
return state.map(t =>
feature(t, action)
);
default:
return state;
}
};
export default features;

View File

@ -0,0 +1,8 @@
import { combineReducers } from 'redux';
import features from './features';
const unleashStore = combineReducers({
features,
});
export default unleashStore;

View File

@ -0,0 +1,8 @@
.container {
height: auto;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}

View File

@ -0,0 +1,22 @@
@import "~react-toolbox/lib/colors";
@import "~react-toolbox/lib/globals";
@import "~react-toolbox/lib/mixins";
@import "~react-toolbox/lib/commons";
$color-primary:$palette-blue-400;
$color-primary-dark: $palette-blue-700;
$appbar-height-m-portrait: 5.6 * $unit !default;
$appbar-height-m-landscape: 4.8 * $unit !default;
$navigation-drawer-desktop-width: 3 * $standard-increment-desktop !default;
$navigation-drawer-max-desktop-width: 40 * $unit !default;
// Mobile:
// Width = Screen width 56 dp
// Maximum width: 320dp
$navigation-drawer-mobile-width: 5 * $standard-increment-mobile !default;
// sass doesn't like use of variable here: calc(100% - $standard-increment-mobile);
$navigation-drawer-max-mobile-width: calc(100% - 5.6rem) !default;

View File

@ -0,0 +1,60 @@
// docs: http://webpack.github.io/docs/configuration.html
'use strict';
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./src/index',
],
resolve: {
root: [path.join(__dirname, 'src')],
extensions: ['', '.scss', '.css', '.js', '.jsx', '.json'],
modulesDirectories: ['web_modules', 'node_modules'],
},
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/static/',
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: ['babel'],
include: path.join(__dirname, 'src'),
},
{
test: /(\.scss|\.css)$/,
loader: ExtractTextPlugin.extract('style',
'css?sourceMap&modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss!sass'),
},
],
},
plugins: [
new ExtractTextPlugin('bundle.css', { allChunks: true }),
new webpack.HotModuleReplacementPlugin(),
],
sassLoader: {
data: '@import "theme/_config.scss";',
includePaths: [path.resolve(__dirname, './src')],
},
devtool: 'source-map',
externals: {
// stuff not in node_modules can be resolved here.
},
};