Add Playground component

old
Javi Velasco 2015-10-25 14:54:41 +01:00
parent 9fc08b6106
commit 1e201834a4
19 changed files with 579 additions and 10 deletions

View File

@ -1,8 +1,7 @@
@import url('http://fonts.googleapis.com/css?family=Roboto:300,400,500,700');
@import "~normalize.css";
@import "./base";
@import url(http://fonts.googleapis.com/css?family=Roboto:300,400,500,700);
//-- App
.app {
position: absolute;
top: 0;

View File

@ -22,8 +22,8 @@ export default {
IconMenu: require('./menu/icon_menu'),
Navigation: require('./navigation'),
ProgressBar: require('./progress_bar'),
RadioGroup: require('./radio/radio_button'),
RadioButton: require('./radio/radio_group'),
RadioGroup: require('./radio/radio_group'),
RadioButton: require('./radio/radio_button'),
Ripple: require('./ripple'),
Slider: require('./slider'),
Switch: require('./switch'),

View File

@ -1,13 +1,14 @@
/*eslint-disable no-unused-vars*/
import React from 'react';
import ReactDOM from 'react-dom';
import Button from 'react-toolbox/button';
import style from 'react-toolbox/commons';
import code from './examples/example.txt';
import Playground from './components/playground';
const App = () => {
return (
<app >
<Button label='Testing Toolbox' />
<app className={style.app}>
<Playground codeText={code} />
</app>
);
};

View File

@ -0,0 +1 @@
$unit: 1rem;

View File

@ -0,0 +1,5 @@
@mixin code-typography {
font-family: 'source-code-pro', Menlo, Consolas, 'Courier New', monospace;
font-size: 13px;
line-height: 1.4;
}

View File

@ -0,0 +1,18 @@
$editor-height: 25 * $unit;
$editor-padding: $unit;
div.CodeMirror {
height: $editor-height;
}
.CodeMirror-lines {
padding: $editor-padding 0;
}
pre.CodeMirror-line {
padding: 0 $editor-padding;
}
div.CodeMirror pre, div.CodeMirror-linenumber, code {
@include code-typography;
}

View File

@ -0,0 +1,173 @@
* {
color-profile: sRGB;
rendering-intent: auto;
}
.cm-s-solarized-light {
background-color: #f8f5ec;
color: #637c84;
}
.cm-s-solarized-light .emphasis {
font-weight: bold;
}
.cm-s-solarized-light .dotted {
border-bottom: 1px dotted #cb4b16;
}
.cm-s-solarized-light .CodeMirror-gutter {
background-color: #eee8d5;
border-right: 3px solid #eee8d5;
}
.cm-s-solarized-light .CodeMirror-gutter .CodeMirror-gutter-text {
color: #93a1a1;
}
.cm-s-solarized-light .CodeMirror-cursor {
border-left-color: #002b36 !important;
}
.cm-s-solarized-light .CodeMirror-matchingbracket {
color: #002b36;
background-color: #eee8d5;
box-shadow: 0 0 10px #eee8d5;
font-weight: bold;
}
.cm-s-solarized-light .CodeMirror-nonmatchingbracket {
color: #002b36;
background-color: #eee8d5;
box-shadow: 0 0 10px #eee8d5;
font-weight: bold;
color: #dc322f;
border-bottom: 1px dotted #cb4b16;
}
.cm-s-solarized-light span.cm-keyword {
color: #268bd2;
}
.cm-s-solarized-light span.cm-atom {
color: #2aa198;
}
.cm-s-solarized-light span.cm-number {
color: #586e75;
}
.cm-s-solarized-light span.cm-def {
color: #637c84;
}
.cm-s-solarized-light span.cm-variable {
color: #637c84;
}
.cm-s-solarized-light span.cm-variable-2 {
color: #b58900;
}
.cm-s-solarized-light span.cm-variable-3 {
color: #cb4b16;
}
.cm-s-solarized-light span.cm-comment {
color: #93a1a1;
}
.cm-s-solarized-light span.cm-property {
color: #637c84;
}
.cm-s-solarized-light span.cm-operator {
color: #657b83;
}
.cm-s-solarized-light span.cm-string {
color: #36958e;
}
.cm-s-solarized-light span.cm-error {
font-weight: bold;
border-bottom: 1px dotted #cb4b16;
}
.cm-s-solarized-light span.cm-bracket {
color: #cb4b16;
}
.cm-s-solarized-light span.cm-tag {
color: #657b83;
}
.cm-s-solarized-light span.cm-attribute {
color: #586e75;
font-weight: bold;
}
.cm-s-solarized-light span.cm-meta {
color: #268bd2;
}
.cm-s-solarized-dark {
background-color: #002b36;
color: #839496;
}
.cm-s-solarized-dark .emphasis {
font-weight: bold;
}
.cm-s-solarized-dark .dotted {
border-bottom: 1px dotted #cb4b16;
}
.cm-s-solarized-dark .CodeMirror-gutter {
background-color: #073642;
border-right: 3px solid #073642;
}
.cm-s-solarized-dark .CodeMirror-gutter .CodeMirror-gutter-text {
color: #586e75;
}
.cm-s-solarized-dark .CodeMirror-cursor {
border-left-color: #fdf6e3 !important;
}
.cm-s-solarized-dark .CodeMirror-matchingbracket {
color: #fdf6e3;
background-color: #073642;
box-shadow: 0 0 10px #073642;
font-weight: bold;
}
.cm-s-solarized-dark .CodeMirror-nonmatchingbracket {
color: #fdf6e3;
background-color: #073642;
box-shadow: 0 0 10px #073642;
font-weight: bold;
color: #dc322f;
border-bottom: 1px dotted #cb4b16;
}
.cm-s-solarized-dark span.cm-keyword {
color: #839496;
font-weight: bold;
}
.cm-s-solarized-dark span.cm-atom {
color: #2aa198;
}
.cm-s-solarized-dark span.cm-number {
color: #93a1a1;
}
.cm-s-solarized-dark span.cm-def {
color: #268bd2;
}
.cm-s-solarized-dark span.cm-variable {
color: #cb4b16;
}
.cm-s-solarized-dark span.cm-variable-2 {
color: #cb4b16;
}
.cm-s-solarized-dark span.cm-variable-3 {
color: #cb4b16;
}
.cm-s-solarized-dark span.cm-comment {
color: #586e75;
}
.cm-s-solarized-dark span.cm-property {
color: #b58900;
}
.cm-s-solarized-dark span.cm-operator {
color: #839496;
}
.cm-s-solarized-dark span.cm-string {
color: #6c71c4;
}
.cm-s-solarized-dark span.cm-error {
font-weight: bold;
border-bottom: 1px dotted #cb4b16;
}
.cm-s-solarized-dark span.cm-bracket {
color: #cb4b16;
}
.cm-s-solarized-dark span.cm-tag {
color: #839496;
}
.cm-s-solarized-dark span.cm-attribute {
color: #93a1a1;
font-weight: bold;
}
.cm-s-solarized-dark span.cm-meta {
color: #268bd2;
}

View File

@ -0,0 +1,61 @@
import React from 'react';
import style from './style';
import CodeMirror from 'codemirror';
import 'codemirror/mode/javascript/javascript';
class Editor extends React.Component {
static propTypes = {
className: React.PropTypes.string,
lineNumbers: React.PropTypes.bool,
onChange: React.PropTypes.func,
readOnly: React.PropTypes.bool,
tabSize: React.PropTypes.number,
theme: React.PropTypes.string
};
static defaultProps = {
className: '',
lineNumbers: false,
readOnly: false,
tabSize: 2,
theme: 'solarized-light'
};
componentDidMount () {
this.editor = CodeMirror.fromTextArea(this.refs.editor, {
mode: 'javascript',
lineNumbers: this.props.lineNumbers,
smartIndent: false,
tabSize: this.props.tabSize,
matchBrackets: true,
theme: this.props.theme,
readOnly: this.props.readOnly
});
this.editor.on('change', this.handleChange);
}
componentDidUpdate () {
if (this.props.readOnly) {
this.editor.setValue(this.props.codeText);
}
}
handleChange = () => {
if (!this.props.readOnly && this.props.onChange) {
this.props.onChange(this.editor.getValue());
}
};
render () {
let className = style.editor;
if (this.props.className) className += ` ${this.props.className}`;
return (
<div className={className}>
<textarea ref="editor" defaultValue={this.props.codeText} />
</div>
);
}
}
export default Editor;

View File

@ -0,0 +1,23 @@
@import "../globals";
@import "../mixins";
.editor {
position: relative;
:global {
@import "~codemirror/lib/codemirror";
@import "solarized";
@import "custom";
}
&:before {
display: block;
width: 100%;
padding: 3px 7px;
font-size: 12px;
font-weight: bold;
color: #c2c0bc;
content: "Try Component";
background: #f1ede4;
}
}

View File

@ -0,0 +1,42 @@
import React from 'react';
import Editor from '../editor';
import Preview from '../preview';
import style from './style';
class Playground extends React.Component {
static propTypes = {
codeText: React.PropTypes.string.isRequired,
layout: React.PropTypes.oneOf(['vertical', 'horizontal'])
};
static defaultProps = {
layout: 'horizontal'
};
state = {
code: this.props.codeText
};
handleCodeChange = (code) => {
this.setState({code});
};
render () {
const className = `${style.playground} ${style[this.props.layout]}`;
return (
<div className={className}>
<Editor
className={style.editor}
codeText={this.state.code}
onChange={this.handleCodeChange}
/>
<Preview
className={style.preview}
code={this.state.code}
/>
</div>
);
}
}
export default Playground;

View File

@ -0,0 +1,24 @@
@import "../globals";
$editor-width: 50%;
.playground {
display: flex;
padding: 2 * $unit;
}
.vertical {
flex-direction: column;
}
.horizontal {
flex-direction: row;
> .editor {
width: $editor-width;
}
> .preview {
width: 100% - $editor-width;
}
}

View File

@ -0,0 +1,99 @@
/*eslint-disable no-eval*/
import React from 'react';
import ReactDOM from 'react-dom';
import babel from 'babel-core/browser';
import style from './style';
import Toolbox from 'react-toolbox';
const ERROR_TIMEOUT = 500;
const Preview = React.createClass({
propTypes: {
className: React.PropTypes.string,
code: React.PropTypes.string.isRequired,
scope: React.PropTypes.object
},
getDefaultProps () {
return {
className: '',
scope: Object.assign({ React }, Toolbox)
};
},
getInitialState () {
return {
error: null
};
},
componentDidMount () {
this.executeCode();
},
componentDidUpdate (prevProps) {
clearTimeout(this.timeoutID);
if (this.props.code !== prevProps.code) {
this.executeCode();
}
},
setTimeout () {
clearTimeout(this.timeoutID);
this.timeoutID = setTimeout.apply(null, arguments);
},
compileCode () {
const code = `
(function (${Object.keys(this.props.scope).join(', ')}, mountNode) {
${this.props.code}
});`;
return babel.transform(code, {
optional: ['es7.classProperties']
}).code;
},
buildScope (mountNode) {
return Object.keys(this.props.scope).map((key) => {
return this.props.scope[key];
}).concat(mountNode);
},
executeCode () {
const mountNode = this.refs.mount;
const scope = this.buildScope(mountNode);
try {
ReactDOM.unmountComponentAtNode(mountNode);
} catch (e) {
console.error(e);
}
try {
const compiledCode = this.compileCode();
const Component = eval(compiledCode).apply(null, scope);
ReactDOM.render(Component, mountNode);
if (this.state.error) {
this.setState({error: null});
}
} catch (err) {
this.setTimeout(() => {
this.setState({error: err.toString()});
}, ERROR_TIMEOUT);
}
},
render () {
let className = style.preview;
if (this.props.className) className += ` ${this.props.className}`;
return (
<div className={className}>
{this.state.error !== null ? <span className={style.error}>{this.state.error}</span> : null}
<div ref="mount" className={style.content} />
</div>
);
}
});
export default Preview;

View File

@ -0,0 +1,24 @@
@import "../globals";
@import "../mixins";
$preview-error-background: #f2777a;
$preview-error-color: #fff;
.preview {
display: flex;
flex-direction: column;
}
.content {
flex-grow: 1;
padding: $unit;
}
.error {
@include code-typography;
padding: .5 * $unit;
color: $preview-error-color;
letter-spacing: -.05 * $unit;
background: $preview-error-background;
border-bottom: 1px solid darken($preview-error-background, 6);
}

View File

@ -0,0 +1,31 @@
class DialogTest extends React.Component {
state = {
title: 'Use Google\'s location service?',
actions: [
{ label: 'Disagree', type: 'flat', className: 'primary', onClick: this.onClose.bind(this) },
{ label: 'Agree', type: 'flat', className: 'primary', onClick: this.onClose.bind(this) }]
};
onClose () {
this.refs.dialog.hide();
}
onShow () {
this.refs.dialog.show();
}
render () {
return (
<section>
<h5>Dialog</h5>
<p>lorem ipsum...</p>
<Button kind='raised' label='Show Dialog' onClick={this.onShow.bind(this)} />
<Dialog ref='dialog' type='small' title={this.state.title} actions={this.state.actions}>
<p>Let Google help apps determine location. This means sending anonymous location data to Google, even when no apps are running.</p>
</Dialog>
</section>
);
}
}
return <DialogTest />;

View File

@ -0,0 +1,31 @@
class RadioGroupTest extends React.Component {
handleChange = (event, instance) => {
console.log('Changed!', { comic: instance.getValue()});
};
handleFocus = () => {
console.log('Focused V for a Vendetta');
};
handleBlur = () => {
console.log('Blurred Watchmen');
};
render () {
return (
<section>
<h5>Radio Button</h5>
<p style={{marginBottom: '10px'}}>Lorem ipsum...</p>
<RadioGroup ref='group' name='comic' value='vvendetta' onChange={this.handleChange}>
<RadioButton label='The Walking Dead' value='thewalkingdead'/>
<RadioButton label='From Hell' value='fromhell' disabled/>
<RadioButton label='V for a Vendetta' value='vvendetta' onFocus={this.handleFocus}/>
<RadioButton label='Watchmen' value='watchmen' onBlur={this.handleBlur}/>
</RadioGroup>
</section>
);
}
}
return <RadioGroupTest />;

View File

@ -0,0 +1,31 @@
class RadioGroupTest extends React.Component {
handleChange = (event, instance) => {
console.log('Changed!', { comic: instance.getValue()});
};
handleFocus = () => {
console.log('Focused V for a Vendetta');
};
handleBlur = () => {
console.log('Blurred Watchmen');
};
render () {
return (
<section>
<h5>Radio Button</h5>
<p style={{marginBottom: '10px'}}>Lorem ipsum...</p>
<RadioGroup ref='group' name='comic' value='vvendetta' onChange={this.handleChange}>
<RadioButton label='The Walking Dead' value='thewalkingdead'/>
<RadioButton label='From Hell' value='fromhell' disabled/>
<RadioButton label='V for a Vendetta' value='vvendetta' onFocus={this.handleFocus}/>
<RadioButton label='Watchmen' value='watchmen' onBlur={this.handleBlur}/>
</RadioGroup>
</section>
);
}
}
return <RadioGroupTest />;

View File

@ -7,6 +7,7 @@
"start": "node ./server"
},
"dependencies": {
"codemirror": "^5.8.0",
"react": "^0.14.0",
"react-dom": "^0.14.0"
},
@ -19,6 +20,7 @@
"css-loader": "^0.21.0",
"node-sass": "^3.3.3",
"postcss-loader": "^0.7.0",
"raw-loader": "^0.5.1",
"react-hot-loader": "^1.3.0",
"sass-loader": "^3.0.0",
"style-loader": "^0.13.0",

View File

@ -3,7 +3,7 @@ const WebpackDevServer = require('webpack-dev-server');
const config = require('./webpack.config');
const devServer = {
host: '0.0.0.0',
port: 3000,
port: 8080,
inline: true
};

View File

@ -1,7 +1,7 @@
const path = require('path');
const webpack = require('webpack');
const autoprefixer = require('autoprefixer');
const devServer = 'http://0.0.0.0:3000';
const devServer = 'http://0.0.0.0:8080';
module.exports = {
context: __dirname,
@ -9,7 +9,7 @@ module.exports = {
entry: [
'webpack-dev-server/client?' + devServer,
'webpack/hot/only-dev-server',
'./app/index.jsx'
'./app/app.jsx'
],
output: {
path: path.join(__dirname, 'build'),
@ -37,6 +37,10 @@ module.exports = {
}, {
test: /(\.scss|\.css)$/,
loader: 'style!css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss!sass'
}, {
test: /(\.txt)$/,
loader: 'raw-loader',
include: path.resolve(__dirname, './app/examples')
}
]
},