Skip to content

Commit

Permalink
feat: Autocomplete via propTypes, auto close tags, quotes and braces (#4
Browse files Browse the repository at this point in the history
)
  • Loading branch information
markdalgleish committed Nov 13, 2018
1 parent 4fb1175 commit bd88b91
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 4 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -69,6 +69,7 @@
"lodash": "^4.17.11",
"mini-css-extract-plugin": "^0.4.3",
"opn": "^5.4.0",
"parse-prop-types": "^0.3.0",
"prop-types": "^15.6.2",
"query-string": "^6.1.0",
"react": "^16.5.2",
Expand Down
99 changes: 96 additions & 3 deletions src/Playroom/Playroom.js
@@ -1,23 +1,69 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import parsePropTypes from 'parse-prop-types';
import flatMap from 'lodash/flatMap';
import debounce from 'lodash/debounce';
import omit from 'lodash/omit';
import { Parser } from 'acorn-jsx';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/neo.css';
import Preview from './Preview/Preview';
import styles from './Playroom.less';

// CodeMirror blows up in a Node context, so only execute it in the browser
const CodeMirror =
const ReactCodeMirror =
typeof window === 'undefined'
? null
: (() => {
const lib = require('react-codemirror');
require('codemirror/mode/jsx/jsx');
require('codemirror/addon/edit/closetag');
require('codemirror/addon/edit/closebrackets');
require('codemirror/addon/hint/show-hint');
require('codemirror/addon/hint/xml-hint');

return lib;
})();

const completeAfter = (cm, predicate) => {
const CodeMirror = cm.constructor;
const cur = cm.getCursor();
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();
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);
Expand Down Expand Up @@ -98,6 +144,39 @@ export default class Playroom extends Component {
})
);

const componentNames = Object.keys(components).sort();
const tags = Object.assign(
{},
...componentNames.map(componentName => {
const { propTypes = {} } = components[componentName];
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
};
})
)
}
};
})
);

return !codeReady ? null : (
<div>
<div className={styles.previewContainer}>
Expand All @@ -110,14 +189,28 @@ export default class Playroom extends Component {
/>
</div>
<div className={styles.editorContainer}>
<CodeMirror
<ReactCodeMirror
ref={this.storeCodeMirrorRef}
value={code}
onChange={this.handleChange}
options={{
mode: 'jsx',
autoCloseTags: true,
autoCloseBrackets: true,
theme: 'neo',
gutters: [styles.gutter]
gutters: [styles.gutter],
hintOptions: { schemaInfo: tags },
extraKeys: {
Tab: cm => {
const indent = cm.getOption('indentUnit');
const spaces = Array(indent + 1).join(' ');
cm.replaceSelection(spaces);
},
"'<'": completeAfter,
"'/'": completeIfAfterLt,
"' '": completeIfInTag,
"'='": completeIfInTag
}
}}
/>
</div>
Expand Down
34 changes: 33 additions & 1 deletion src/Playroom/Playroom.less
Expand Up @@ -7,6 +7,7 @@
@sandbox-marker-color: blue;
@sandbox-editor-height: 30vh;
@sandbox-padding: 40px;
@editor-font-family: Source Code Pro, Firacode, Hasklig, Menlo, monospace;

.previewContainer {
position: fixed;
Expand Down Expand Up @@ -52,7 +53,7 @@
}
height: 100%;
padding: 0 16px;
font-family: Source Code Pro, Firacode, Hasklig, Menlo, monospace;
font-family: @editor-font-family;
}

.CodeMirror-gutters {
Expand All @@ -75,6 +76,37 @@
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;
}

.cm-s-neo {
&.CodeMirror {
background-color: #fff;
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Expand Up @@ -8516,6 +8516,11 @@ parse-passwd@^1.0.0:
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=

parse-prop-types@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/parse-prop-types/-/parse-prop-types-0.3.0.tgz#c0d3ccff4e3baabed6ec498974a657314a8f18d8"
integrity sha512-HAvQ2sGc2Y+OLWddBG+KKlxU/F931Vq0GPSqKVaRJb6bUVdkIYxk63mJ/ZwX1VTjZ0h34qrCXE3tmSVUHB1zxQ==

parseurl@~1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
Expand Down

0 comments on commit bd88b91

Please sign in to comment.