diff --git a/package.json b/package.json index 1945ecdc..27f060bf 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "babel-plugin-transform-class-properties": "^6.24.1", "base64-url": "^2.2.0", "buble": "^0.19.3", + "classnames": "^2.2.6", "codemirror": "^5.48.2", "command-line-args": "^5.0.2", "command-line-usage": "^5.0.5", @@ -86,10 +87,8 @@ "prop-types": "^15.6.2", "query-string": "^6.1.0", "re-resizable": "^4.9.3", - "react": "^16.8.6", "react-codemirror2": "^6.0.0", "react-docgen-typescript": "^1.12.2", - "react-dom": "^16.8.6", "read-pkg-up": "^5.0.0", "scope-eval": "^1.0.0", "style-loader": "^0.23.0", @@ -106,15 +105,20 @@ "commitlint-config-seek": "^1.0.0", "cypress": "^3.4.0", "cz-conventional-changelog": "^2.1.0", - "eslint": "^5.8.0", - "eslint-config-seek": "^3.2.1", - "eslint-plugin-cypress": "^2.6.0", + "eslint": "^6.7.2", + "eslint-config-seek": "^4.3.2", "extract-text-webpack-plugin": "^3.0.2", "husky": "^1.1.3", "jest": "^23.6.0", "lint-staged": "^8.0.4", + "react": "^16.8.6", + "react-dom": "^16.8.6", "semantic-release": "^15.10.8", "serve": "^11.1.0", "start-server-and-test": "^1.7.11" + }, + "peerDependencies": { + "react": "^16.8", + "react-dom": "^16.8" } } diff --git a/src/Playroom/CodeEditor/CodeEditor.js b/src/Playroom/CodeEditor/CodeEditor.js new file mode 100644 index 00000000..ca6d3925 --- /dev/null +++ b/src/Playroom/CodeEditor/CodeEditor.js @@ -0,0 +1,156 @@ +import React, { useRef, useEffect } from 'react'; +import 'codemirror/lib/codemirror.css'; +import 'codemirror/theme/neo.css'; + +import { formatCode as format } from '../../utils/formatting'; + +import styles from './CodeEditor.less'; + +import { Controlled as ReactCodeMirror } from 'react-codemirror2'; +import 'codemirror/mode/jsx/jsx'; +import 'codemirror/addon/edit/closetag'; +import 'codemirror/addon/edit/closebrackets'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/addon/hint/xml-hint'; +import compileJsx from '../../utils/compileJsx'; + +const completeAfter = (cm, predicate) => { + const CodeMirror = cm.constructor; + if (!predicate || predicate()) { + setTimeout(() => { + if (!cm.state.completionActive) { + cm.showHint({ completeSingle: false }); + } + }, 100); + } + + return CodeMirror.Pass; +}; + +const completeIfAfterLt = cm => { + const CodeMirror = cm.constructor; + + return completeAfter(cm, () => { + const cur = cm.getCursor(); + // eslint-disable-next-line new-cap + return cm.getRange(CodeMirror.Pos(cur.line, cur.ch - 1), cur) === '<'; + }); +}; + +const completeIfInTag = cm => { + const CodeMirror = cm.constructor; + + return completeAfter(cm, () => { + const tok = cm.getTokenAt(cm.getCursor()); + if ( + tok.type === 'string' && + (!/['"]/.test(tok.string.charAt(tok.string.length - 1)) || + tok.string.length === 1) + ) { + return false; + } + const inner = CodeMirror.innerMode(cm.getMode(), tok.state).state; + return inner.tagName; + }); +}; + +const validateCode = (editorInstance, code) => { + editorInstance.clearGutter(styles.gutter); + + try { + compileJsx(code); + } catch (err) { + const errorMessage = err && (err.message || ''); + const matches = errorMessage.match(/\(([0-9]+):/); + const lineNumber = + matches && matches.length >= 2 && matches[1] && parseInt(matches[1], 10); + + if (lineNumber) { + const marker = document.createElement('div'); + marker.classList.add(styles.marker); + marker.setAttribute('title', err.message); + editorInstance.setGutterMarker(lineNumber - 1, styles.gutter, marker); + } + } +}; + +export const CodeEditor = ({ code, onChange, hints }) => { + const editorInstanceRef = useRef(null); + + useEffect( + () => { + const handleKeyDown = e => { + if ( + editorInstanceRef && + editorInstanceRef.current && + e.keyCode === 83 && + (navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey) + ) { + e.preventDefault(); + + const { formattedCode, line, ch } = format({ + code, + cursor: editorInstanceRef.current.getCursor() + }); + + onChange(formattedCode); + + setTimeout(() => { + editorInstanceRef.current.focus(); + editorInstanceRef.current.setCursor({ + line, + ch + }); + }); + } + }; + + window.addEventListener('keydown', handleKeyDown); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, + [code, onChange] + ); + + return ( + { + editorInstanceRef.current = editorInstance; + validateCode(editorInstance, code); + editorInstance.focus(); + editorInstance.setCursor(0, 0); + }} + value={code} + onBeforeChange={(editor, data, newCode) => { + onChange(newCode); + validateCode(editorInstanceRef.current, newCode); + }} + options={{ + mode: 'jsx', + autoCloseTags: true, + autoCloseBrackets: true, + theme: 'neo', + gutters: [styles.gutter], + hintOptions: { schemaInfo: hints }, + viewportMargin: 50, + extraKeys: { + Tab: cm => { + if (cm.somethingSelected()) { + cm.indentSelection('add'); + } else { + const indent = cm.getOption('indentUnit'); + const spaces = Array(indent + 1).join(' '); + cm.replaceSelection(spaces); + } + }, + "'<'": completeAfter, + "'/'": completeIfAfterLt, + "' '": completeIfInTag, + "'='": completeIfInTag + } + }} + /> + ); +}; diff --git a/src/Playroom/CodeEditor/CodeEditor.less b/src/Playroom/CodeEditor/CodeEditor.less new file mode 100644 index 00000000..61834464 --- /dev/null +++ b/src/Playroom/CodeEditor/CodeEditor.less @@ -0,0 +1,122 @@ +@import (reference) '../variables.less'; + +@editor-font-family: Source Code Pro, Firacode, Hasklig, Menlo, monospace; +@sandbox-editor-height: 30vh; +@gutter-size: 40px; + +.gutter { + @box-fade-size: 10px; + width: @gutter-size - @box-fade-size; + background: white; + position: absolute; + right: @box-fade-size; + box-shadow: 0 0 @box-fade-size 5px white; +} + +.marker { + width: @sandbox-marker-size; + height: @sandbox-marker-size; + border-radius: (@sandbox-marker-size / 2); + background-color: @sandbox-marker-color; + position: relative; + top: (@sandbox-marker-size / 2); + left: 10px; +} + +:global { + .react-codemirror2 { + height: 100%; + background-color: #fff; + } + + .CodeMirror { + height: 100%; + width: 100%; + font-family: @editor-font-family; + } + + .CodeMirror-gutters { + width: @gutter-size; + } + + .CodeMirror-gutter-elt { + position: relative; + } + + .CodeMirror pre, + .CodeMirror-linenumber { + font-size: 16px; + } + + .CodeMirror-line > span[role='presentation'] { + padding-right: (@dock-size + @dock-fade-distance + @gutter-size) !important; + } + + .CodeMirror-lines { + padding: 16px 0; + } + + .CodeMirror-hints { + position: absolute; + z-index: 10; + overflow: hidden; + list-style: none; + margin: 0; + padding: 0; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); + border-radius: 3px; + background: white; + font-size: 90%; + line-height: 150%; + font-family: @editor-font-family; + max-height: 20em; + overflow-y: auto; + } + + .CodeMirror-hint { + margin: 0; + padding: 4px 8px; + border-radius: 2px; + white-space: pre; + color: black; + cursor: pointer; + } + + li.CodeMirror-hint-active { + background: #08f; + color: white; + } + + .cm-s-neo { + &.CodeMirror { + background-color: #fff; + } + .CodeMirror-cursor { + background: black; + width: 2px; + } + .CodeMirror-gutters { + background: none; + border: none; + pointer-events: none; + } + .CodeMirror-linenumber { + color: white; + } + .cm-tag { + color: #040080; + } + .cm-attribute { + color: #005ad2; + } + .cm-string { + color: #00439c; + } + .cm-atom { + color: #00439c; + } + .cm-variable { + color: #827eff; + } + } +} diff --git a/src/Playroom/DockPosition/DockPosition.js b/src/Playroom/DockPosition/DockPosition.js new file mode 100644 index 00000000..29186d95 --- /dev/null +++ b/src/Playroom/DockPosition/DockPosition.js @@ -0,0 +1,64 @@ +import React from 'react'; +import EditorUndockedSvg from './icons/EditorUndockedSvg'; +import EditorLeftSvg from './icons/EditorLeftSvg'; +import EditorBottomSvg from './icons/EditorBottomSvg'; +import EditorRightSvg from './icons/EditorRightSvg'; + +import styles from './DockPosition.less'; + +export default ({ position, setPosition }) => { + return ( +
+
+
+ { + { + undocked: , + left: , + right: , + bottom: + }[position] + } +
+
+ {position !== 'undocked' && ( + + )} + {position !== 'left' && ( + + )} + {position !== 'right' && ( + + )} + {position !== 'bottom' && ( + + )} +
+
+
+ ); +}; diff --git a/src/Playroom/DockPosition/DockPosition.less b/src/Playroom/DockPosition/DockPosition.less new file mode 100644 index 00000000..55d30b77 --- /dev/null +++ b/src/Playroom/DockPosition/DockPosition.less @@ -0,0 +1,68 @@ +@import (reference) '../variables.less'; + +@dock-button-size: 36px; +@dock-horizontal-padding: (@dock-size - @dock-button-size) / 2; + +.root { + display: flex; + flex-direction: column-reverse; + align-items: center; + overflow: hidden; + position: relative; + padding: 20px @dock-horizontal-padding; + background: white; +} + +.container { + display: flex; + flex-direction: column-reverse; + align-items: center; +} + +.currentPosition { + height: @dock-button-size; + width: 24px; + display: flex; + align-items: center; + justify-content: center; + color: #a7a7a7; + z-index: 1; + + .container:hover & { + color: #5965e3; + } +} + +.buttons { + opacity: 0; + transition: opacity 0.25s ease, transform 0.25s ease; + transform: translateY(30%); + + .container:hover & { + opacity: 1; + transform: translateY(0); + } +} + +.button { + padding: 0; + border: 0; + outline: none; + background: transparent; + width: @dock-button-size; + height: @dock-button-size; + color: #a7a7a7; + cursor: pointer; + display: block; + align-items: center; + justify-content: center; + + &:hover { + color: #515151; + } + &:focus { + background: #e8e8e8; + outline: none; + border-radius: 100%; + } +} diff --git a/src/Playroom/DockPosition/icons/EditorBottomSvg.js b/src/Playroom/DockPosition/icons/EditorBottomSvg.js new file mode 100644 index 00000000..c2a8e911 --- /dev/null +++ b/src/Playroom/DockPosition/icons/EditorBottomSvg.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export default props => ( + + + +); diff --git a/src/Playroom/DockPosition/icons/EditorLeftSvg.js b/src/Playroom/DockPosition/icons/EditorLeftSvg.js new file mode 100644 index 00000000..f22fd6db --- /dev/null +++ b/src/Playroom/DockPosition/icons/EditorLeftSvg.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export default props => ( + + + +); diff --git a/src/Playroom/DockPosition/icons/EditorRightSvg.js b/src/Playroom/DockPosition/icons/EditorRightSvg.js new file mode 100644 index 00000000..59054c00 --- /dev/null +++ b/src/Playroom/DockPosition/icons/EditorRightSvg.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export default props => ( + + + +); diff --git a/src/Playroom/DockPosition/icons/EditorUndockedSvg.js b/src/Playroom/DockPosition/icons/EditorUndockedSvg.js new file mode 100644 index 00000000..9e1e9312 --- /dev/null +++ b/src/Playroom/DockPosition/icons/EditorUndockedSvg.js @@ -0,0 +1,15 @@ +import React from 'react'; + +export default props => ( + + + + +); diff --git a/src/Playroom/Frame.js b/src/Playroom/Frame.js index 38624ee6..b43408b8 100644 --- a/src/Playroom/Frame.js +++ b/src/Playroom/Frame.js @@ -1,62 +1,33 @@ import React, { Component } from 'react'; -import queryString from 'query-string'; +import getParamsFromQuery from '../utils/getParamsFromQuery'; import CatchErrors from './CatchErrors/CatchErrors'; import RenderCode from './RenderCode/RenderCode'; -const themesImport = require('./themes'); -const componentsImport = require('./components'); -const frameComponentImport = require('./frameComponent'); - -const getQueryParams = () => { - try { - const hash = window.location.hash.replace(/^#/, ''); - return queryString.parse(hash); - } catch (err) { - return {}; - } -}; - export default class Frame extends Component { constructor(props) { super(props); - const { themeName, code = '' } = getQueryParams(); + const { themeName, code = '' } = getParamsFromQuery(); this.state = { themeName, - themes: themesImport, - components: componentsImport, - FrameComponent: frameComponentImport, code }; } componentDidMount() { window.addEventListener('hashchange', () => { - const { themeName, code } = getQueryParams(); + const { themeName, code } = getParamsFromQuery(); if (themeName && code) { this.setState({ themeName, code }); } }); - - if (module.hot) { - module.hot.accept('./themes', () => { - this.setState({ themes: require('./themes') }); - }); - - module.hot.accept('./components', () => { - this.setState({ components: require('./components') }); - }); - - module.hot.accept('./frameComponent', () => { - this.setState({ FrameComponent: require('./frameComponent') }); - }); - } } render() { - const { FrameComponent, themes, themeName, components, code } = this.state; + const { themeName, code } = this.state; + const { themes, components, FrameComponent } = this.props; const resolvedThemeName = themeName === '__PLAYROOM__NO_THEME__' ? null : themeName; diff --git a/src/Playroom/Playroom.js b/src/Playroom/Playroom.js index d2a21e5e..27d553ea 100644 --- a/src/Playroom/Playroom.js +++ b/src/Playroom/Playroom.js @@ -1,381 +1,111 @@ -import React, { Component } from 'react'; -import parsePropTypes from 'parse-prop-types'; +import React from 'react'; +import classnames from 'classnames'; import flatMap from 'lodash/flatMap'; import debounce from 'lodash/debounce'; -import omit from 'lodash/omit'; -import { transform } from 'buble'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/theme/neo.css'; import Resizable from 're-resizable'; import Preview from './Preview/Preview'; import styles from './Playroom.less'; -import { store } from '../index'; import WindowPortal from './WindowPortal'; -import UndockSvg from '../assets/icons/NewWindowSvg'; -import { formatCode } from '../utils/formatting'; - -import { Controlled as ReactCodeMirror } from 'react-codemirror2'; -import 'codemirror/mode/jsx/jsx'; -import 'codemirror/addon/edit/closetag'; -import 'codemirror/addon/edit/closebrackets'; -import 'codemirror/addon/hint/show-hint'; -import 'codemirror/addon/hint/xml-hint'; - -const themesImport = require('./themes'); -const componentsImport = require('./components'); - -const compileJsx = code => - transform(`${code.trim() || ''}`).code; - -const resizableConfig = { - top: true, - right: false, +import componentsToHints from '../utils/componentsToHints'; +import { CodeEditor } from './CodeEditor/CodeEditor'; +import { useStore } from './useStore'; +import DockPosition from './DockPosition/DockPosition'; + +const resizableConfig = (position = 'bottom') => ({ + top: position === 'bottom', + right: position === 'left', bottom: false, - left: false, + left: position === 'right', topRight: false, bottomRight: false, bottomLeft: false, topLeft: false -}; - -const completeAfter = (cm, predicate) => { - const CodeMirror = cm.constructor; - if (!predicate || predicate()) { - setTimeout(() => { - if (!cm.state.completionActive) { - cm.showHint({ completeSingle: false }); - } - }, 100); +}); + +export default ({ themes, components, widths }) => { + const { + editorPosition, + setEditorPosition, + editorSize, + setEditorSize, + code, + setCode, + ready + } = useStore(); + + if (!ready) { + return null; } - return CodeMirror.Pass; -}; - -const completeIfAfterLt = cm => { - const CodeMirror = cm.constructor; - - return completeAfter(cm, () => { - const cur = cm.getCursor(); - // eslint-disable-next-line new-cap - return cm.getRange(CodeMirror.Pos(cur.line, cur.ch - 1), cur) === '<'; - }); -}; - -const completeIfInTag = cm => { - const CodeMirror = cm.constructor; - - return completeAfter(cm, () => { - const tok = cm.getTokenAt(cm.getCursor()); - if ( - tok.type === 'string' && - (!/['"]/.test(tok.string.charAt(tok.string.length - 1)) || - tok.string.length === 1) - ) { - return false; - } - const inner = CodeMirror.innerMode(cm.getMode(), tok.state).state; - return inner.tagName; - }); -}; - -export default class Playroom extends Component { - constructor(props) { - super(props); - - this.state = { - themes: themesImport, - components: componentsImport, - codeReady: false, - code: null, - renderCode: null, - height: 200, - editorUndocked: false - }; - } - - componentDidMount = () => { - if (module.hot) { - module.hot.accept('./themes', () => { - this.setState({ themes: require('./themes') }); - }); - - module.hot.accept('./components', () => { - this.setState({ components: require('./components') }); - }); - } - - Promise.all([this.props.getCode(), store.getItem('editorSize')]).then( - ([code, height]) => { - if (height) { - this.setState({ - height - }); - } - this.initialiseCode(code); - this.validateCode(code); - } - ); - - window.addEventListener('keydown', this.handleKeyPress); - }; - - componentWillUnmount() { - window.removeEventListener('keydown', this.handleKeyPress); - } - - storeCodeMirrorInstance = editorInstance => { - this.editorInstance = editorInstance; - }; - - setEditorUndocked = val => { - this.setState({ - editorUndocked: val - }); + const themeNames = Object.keys(themes); + const frames = flatMap(widths, width => + themeNames.map(theme => ({ theme, width })) + ); + + const codeEditor = ( + + ); + + const size = { + height: editorPosition === 'bottom' ? `${editorSize}px` : '100vh', // issue in ff & safari when not a string + width: /(left|right)/.test(editorPosition) ? `${editorSize}px` : '100vw' }; - - initialiseCode = code => { - let renderCode; - - try { - renderCode = compileJsx(code); - } catch (err) { - renderCode = ''; - } - - this.setState({ - codeReady: true, - code, - renderCode - }); - }; - - updateCode = code => { - this.props.updateCode(code); - this.validateCode(code); - }; - - validateCode = code => { - this.editorInstance.clearGutter(styles.gutter); - - try { - this.setState({ renderCode: compileJsx(code) }); - } catch (err) { - const errorMessage = err && (err.message || ''); - - const matches = errorMessage.match(/\(([0-9]+):/); - - const lineNumber = - matches && - matches.length >= 2 && - matches[1] && - parseInt(matches[1], 10); - - if (!lineNumber) { - return; - } - - const marker = document.createElement('div'); - marker.classList.add(styles.marker); - marker.setAttribute('title', err.message); - this.editorInstance.setGutterMarker( - lineNumber - 1, - styles.gutter, - marker - ); - } - }; - - handleKeyPress = e => { - if ( - e.keyCode === 83 && - (navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey) - ) { - e.preventDefault(); - - const { code } = this.state; - - const { formattedCode, line, ch } = formatCode({ - code, - cursor: this.editorInstance.getCursor() - }); - - this.setState({ code: formattedCode }); - this.updateCode(formattedCode); - this.editorInstance.setValue(formattedCode); - this.editorInstance.focus(); - this.editorInstance.setCursor({ - line, - ch - }); - } - }; - - updateHeight = (event, direction, ref) => { - this.setState({ - height: ref.offsetHeight - }); - store.setItem('editorSize', ref.offsetHeight); - }; - - updateCodeDebounced = debounce(this.updateCode, 500); - handleChange = (editor, data, code) => { - this.setState({ code }); - this.updateCodeDebounced(code); - }; - - handleResize = debounce(this.updateHeight, 200); - - handleUndockEditor = () => { - this.setEditorUndocked(true); - }; - - handleRedockEditor = () => { - this.setEditorUndocked(false); - }; - - render() { - const { staticTypes, widths } = this.props; - const { - themes, - components, - codeReady, - code, - renderCode, - height, - editorUndocked - } = this.state; - - const themeNames = Object.keys(themes); - const frames = flatMap(widths, width => - themeNames.map(theme => { - return { theme, width }; - }) - ); - - const componentNames = Object.keys(components).sort(); - const tags = Object.assign( - {}, - ...componentNames.map(componentName => { - const staticTypesForComponent = staticTypes[componentName]; - if ( - staticTypesForComponent && - Object.keys(staticTypesForComponent).length > 0 - ) { - return { - [componentName]: { - attrs: staticTypesForComponent - } - }; - } - - const parsedPropTypes = parsePropTypes(components[componentName]); - const filteredPropTypes = omit( - parsedPropTypes, - 'children', - 'className' - ); - const propNames = Object.keys(filteredPropTypes); - - return { - [componentName]: { - attrs: Object.assign( - {}, - ...propNames.map(propName => { - const propType = filteredPropTypes[propName].type; - - return { - [propName]: - propType.name === 'oneOf' - ? propType.value.filter(x => typeof x === 'string') - : null - }; - }) - ) - } - }; - }) - ); - - const codeMirrorEl = ( - { - if (cm.somethingSelected()) { - cm.indentSelection('add'); - } else { - const indent = cm.getOption('indentUnit'); - const spaces = Array(indent + 1).join(' '); - cm.replaceSelection(spaces); - } - }, - "'<'": completeAfter, - "'/'": completeIfAfterLt, - "' '": completeIfInTag, - "'='": completeIfInTag - } + const editorContainer = + editorPosition === 'undocked' ? ( + setEditorPosition()} + > + {codeEditor} + + ) : ( + { + debounce(currentSize => { + setEditorSize(currentSize, editorPosition); + }, 1)( + editorPosition === 'bottom' ? ref.offsetHeight : ref.offsetWidth + ); }} - /> - ); - - if (editorUndocked && codeReady) { - return ( -
-
- -
- -
{codeMirrorEl}
-
+ enable={resizableConfig(editorPosition)} + > +
+
- ); - } + {codeEditor} + + ); - return !codeReady ? null : ( -
-
- -
- -
- -
- {codeMirrorEl} -
+ return ( +
+
+
- ); - } -} + {editorContainer} +
+ ); +}; diff --git a/src/Playroom/Playroom.less b/src/Playroom/Playroom.less index 3521f6a4..a954e5c6 100644 --- a/src/Playroom/Playroom.less +++ b/src/Playroom/Playroom.less @@ -1,16 +1,4 @@ -@row-height: 6px; -@gutter-width: 40px; -@sandbox-background: #f2f2f2; -@sandbox-preview-background: white; -@sandbox-line-height: (@row-height * 5); -@sandbox-marker-size: 12px; -@sandbox-marker-color: blue; -@sandbox-editor-height: 30vh; -@sandbox-padding: 40px; -@sandbox-icon-width: 24px; -@sandbox-icon-height: 24px; -@sandbox-icon-color: #555; -@sandbox-icon-hover-color: #000; +@import (reference) './variables.less'; :global(body) { margin: 0; @@ -20,7 +8,6 @@ background: @sandbox-background; height: 100vh; } -@editor-font-family: Source Code Pro, Firacode, Hasklig, Menlo, monospace; .previewContainer { position: fixed; @@ -32,147 +19,47 @@ } .editorContainer { - position: fixed; + box-shadow: 0 0 8px rgba(18, 21, 26, 0.2); + position: fixed !important; // override internal inline style bottom: 0; - left: 0; - right: 0; z-index: 1; - max-height: 95vh; + overflow: hidden; } -.undockedEditorContainer { - height: 100vh; -} - -.toolbar { - background-color: rgba(100%, 100%, 100%, 0.8); - box-shadow: 0 0 10px rgba(100%, 100%, 100%, 0.8); - padding: @row-height * 2; - display: flex; - justify-content: flex-end; - position: absolute; - top: 0; +.editorContainer_isRight { right: 0; - z-index: 3; + top: 0; + max-width: 95vw; } -.toolbarIcon { - width: @sandbox-icon-width; - height: @sandbox-icon-height; - cursor: pointer; - * { - fill: @sandbox-icon-color; - } - &:hover * { - fill: @sandbox-icon-hover-color; - } +.editorContainer_isLeft { + left: 0; + top: 0; + max-width: 95vw; } -.gutter { - width: @sandbox-marker-size * 1.5; +.editorContainer_isBottom { + left: 0; + right: 0; + max-height: 95vh; } -.marker { - width: @sandbox-marker-size; - height: @sandbox-marker-size; - border-radius: (@sandbox-marker-size / 2); - background-color: @sandbox-marker-color; - position: relative; - top: (@sandbox-line-height - @sandbox-marker-size) / 2; - left: -20px; +.editorContainer_isUndocked { + width: 100vw; + height: 100vh; } -:global { - .react-codemirror2 { - height: 100%; - } - - .CodeMirror { - &, - & :global(*) { - box-sizing: border-box; - } - height: 100%; - padding: 0 16px; - font-family: @editor-font-family; - } - - .CodeMirror-gutters { - display: none; - } - - .CodeMirror pre, - .CodeMirror-linenumber { - @padding: 2px; - font-size: 18px; - padding: @padding 0; - line-height: @sandbox-line-height - (@padding * 2); - } - - .CodeMirror-lines { - padding: 16px 20px; - } - - .CodeMirror-linenumber { - padding-right: (@gutter-width * 2); - } - - .CodeMirror-hints { - position: absolute; - z-index: 10; - overflow: hidden; - list-style: none; - margin: 0; - padding: 0; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); - border-radius: 3px; - background: white; - font-size: 90%; - line-height: 150%; - font-family: @editor-font-family; - max-height: 20em; - overflow-y: auto; - } - - .CodeMirror-hint { - margin: 0; - padding: 4px 8px; - border-radius: 2px; - white-space: pre; - color: black; - cursor: pointer; - } - - li.CodeMirror-hint-active { - background: #08f; - color: white; - } +.dockPosition { + position: absolute; + top: 0; + bottom: 0; + right: 0; + z-index: 3; + display: flex; + box-shadow: 0 0 20px 10px white; - .cm-s-neo { - &.CodeMirror { - background-color: #fff; - } - .CodeMirror-cursor { - background: black; - width: 2px; - } - .CodeMirror-linenumber { - color: white; - } - .cm-tag { - color: #040080; - } - .cm-attribute { - color: #005ad2; - } - .cm-string { - color: #00439c; - } - .cm-atom { - color: #00439c; - } - .cm-variable { - color: #827eff; - } + .editorContainer_isLeft & { + // dont overlap resize hit area when left docked + right: 6px; } } diff --git a/src/Playroom/Preview/Preview.js b/src/Playroom/Preview/Preview.js index d80a16c9..939ea12b 100644 --- a/src/Playroom/Preview/Preview.js +++ b/src/Playroom/Preview/Preview.js @@ -2,6 +2,7 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import Iframe from './Iframe'; import styles from './Preview.less'; +import compileJsx from '../../utils/compileJsx'; export default class Preview extends Component { static propTypes = { @@ -20,6 +21,14 @@ export default class Preview extends Component { render() { const { code, frames } = this.props; + let renderCode = code; + + try { + renderCode = compileJsx(code); + } catch (e) { + renderCode = ''; + } + return (
{frames.map(frame => ( @@ -43,7 +52,7 @@ export default class Preview extends Component {