Skip to content

Instantly share code, notes, and snippets.

@bvaughn
Created October 12, 2017 17:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bvaughn/c65d556693fed3dbc6ab2025efac2c2b to your computer and use it in GitHub Desktop.
Save bvaughn/c65d556693fed3dbc6ab2025efac2c2b to your computer and use it in GitHub Desktop.
CodeEditor with JSX toggle (example)
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* @emails react-core
*/
'use strict';
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import Remarkable from 'remarkable';
// TODO: switch back to the upstream version of react-live
// once https://github.com/FormidableLabs/react-live/issues/37 is fixed.
import {LiveProvider, LiveEditor} from '@gaearon/react-live';
import {colors, media} from 'theme';
import MetaTitle from 'templates/components/MetaTitle';
// eslint-disable-next-line no-undef
const compileES5 = code =>
Babel.transform(code, {presets: ['es2015', 'react']}).code;
// eslint-disable-next-line no-undef
const compileES6 = code => Babel.transform(code, {presets: ['react']}).code;
class CodeEditor extends Component {
constructor(props, context) {
super(props, context);
this.state = {
showJSX: true,
...this._updateState(props.code),
};
}
componentDidMount() {
// Initial render() will always be a no-op,
// Because the mountNode ref won't exist yet.
this._render();
}
componentDidUpdate(prevProps, prevState) {
if (prevState.compiledES5 !== this.state.compiledES5) {
this._render();
}
}
render() {
const {children, code} = this.props;
const {compiledES6, error, showJSX} = this.state;
return (
<LiveProvider code={showJSX ? code : compiledES6} mountStylesheet={false}>
<div
css={{
[media.greaterThan('xlarge')]: {
display: 'flex',
flexDirection: 'row',
},
[media.lessThan('large')]: {
display: 'block',
},
}}>
{children && (
<div
css={{
flex: '0 0 33%',
[media.lessThan('xlarge')]: {
marginBottom: 20,
},
'& h3': {
color: colors.dark,
maxWidth: '11em',
paddingTop: 0,
},
'& p': {
marginTop: 15,
marginRight: 40,
lineHeight: 1.7,
[media.greaterThan('xlarge')]: {
marginTop: 25,
},
},
}}>
{children}
</div>
)}
<div
css={{
[media.greaterThan('medium')]: {
flex: '0 0 67%',
display: 'flex',
alignItems: 'stretch',
flexDirection: 'row',
},
[media.lessThan('small')]: {
display: 'block',
},
}}>
<div
css={{
flex: '0 0 70%',
overflow: 'hidden',
borderRadius: '10px 0 0 10px',
[media.lessThan('medium')]: {
borderRadius: '10px 10px 0 0',
},
}}>
<div
css={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0px 10px',
background: colors.darker,
color: colors.white,
}}>
<MetaTitle onDark={true}>Live Editor</MetaTitle>
<label
css={{
fontSize: 14,
}}>
<input
checked={this.state.showJSX}
onChange={event =>
this.setState({showJSX: event.target.checked})}
type="checkbox"
/>{' '}
JSX?
</label>
</div>
<div
css={{
height: '100%',
width: '100%',
borderRadius: '0',
maxHeight: '340px !important',
marginTop: '0 !important',
marginLeft: '0 !important',
paddingLeft: '0 !important',
marginRight: '0 !important',
paddingRight: '0 !important',
marginBottom: '0 !important',
paddingBottom: '20px !important',
[media.lessThan('medium')]: {
marginBottom: '0 !important',
},
'& pre.prism-code[contenteditable]': {
outline: 0,
overflow: 'auto',
marginRight: '0 !important',
marginBottom: '0 !important',
},
}}
className="gatsby-highlight">
<LiveEditor onChange={this._onChange} />
</div>
</div>
{error && (
<div
css={{
flex: '0 0 30%',
overflow: 'hidden',
border: `1px solid ${colors.error}`,
borderRadius: '0 10px 10px 0',
fontSize: 12,
lineHeight: 1.5,
[media.lessThan('medium')]: {
borderRadius: '0 0 10px 10px',
},
}}>
<div
css={{
padding: '0px 10px',
background: colors.error,
color: colors.white,
}}>
<MetaTitle
cssProps={{
color: colors.white,
}}>
Error
</MetaTitle>
</div>
<pre
css={{
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
color: colors.error,
padding: 10,
}}>
{error.message}
</pre>
</div>
)}
{!error && (
<div
css={{
flex: '0 0 30%',
overflow: 'hidden',
border: `1px solid ${colors.divider}`,
borderRadius: '0 10px 10px 0',
[media.lessThan('medium')]: {
borderRadius: '0 0 10px 10px',
},
}}>
<div
css={{
padding: '0 10px',
backgroundColor: colors.divider,
}}>
<MetaTitle>Result</MetaTitle>
</div>
<div
css={{
padding: 10,
maxHeight: '340px !important',
overflow: 'auto',
'& input': {
width: '100%',
display: 'block',
border: '1px solid #ccc', // TODO
padding: 5,
},
'& button': {
marginTop: 10,
padding: '5px 10px',
},
'& textarea': {
width: '100%',
marginTop: 10,
height: 60,
padding: 5,
},
}}
ref={this._setMountRef}
/>
</div>
)}
</div>
</div>
</LiveProvider>
);
}
_render() {
if (!this._mountNode) {
return;
}
const {compiledES5} = this.state;
try {
// Example code requires React, ReactDOM, and Remarkable to be within scope.
// It also requires a "mountNode" variable for ReactDOM.render()
// eslint-disable-next-line no-new-func
new Function('React', 'ReactDOM', 'Remarkable', 'mountNode', compiledES5)(
React,
ReactDOM,
Remarkable,
this._mountNode,
);
} catch (error) {
console.error(error);
this.setState({
compiledES5: null,
error,
});
}
}
_setMountRef = ref => {
this._mountNode = ref;
};
_updateState(code) {
try {
return {
compiledES5: compileES5(code),
compiledES6: compileES6(code),
error: null,
};
} catch (error) {
console.error(error);
return {
compiledES5: null,
compiledES6: null,
error,
};
}
}
_onChange = code => {
this.setState(this._updateState(code));
};
}
export default CodeEditor;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment