Add Playground component
parent
9fc08b6106
commit
1e201834a4
|
@ -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;
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
$unit: 1rem;
|
|
@ -0,0 +1,5 @@
|
|||
@mixin code-typography {
|
||||
font-family: 'source-code-pro', Menlo, Consolas, 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
|
@ -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 />;
|
|
@ -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 />;
|
|
@ -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 />;
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue