Skip to content

Commit

Permalink
add support for JS Objects
Browse files Browse the repository at this point in the history
add support for JS Objects (#1186)
add react-ace as code editor for object type (#1187)
  • Loading branch information
z4o4z committed Jul 12, 2017
1 parent 8c11961 commit 13768d6
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 75 deletions.
60 changes: 51 additions & 9 deletions addons/knobs/example/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ const stories = storiesOf('Example of Knobs', module);

stories.addDecorator(withKnobs);

stories.add('simple example', () => <button>{text('Label', 'Hello Button')}</button>);
stories.add('simple example', () =>
<button>
{text('Label', 'Hello Button')}
</button>
);

stories.add('with all knobs', () => {
const name = text('Name', 'Tom Cary');
Expand All @@ -34,8 +38,14 @@ stories.add('with all knobs', () => {

return (
<div style={style}>
I'm {name} and I was born on "{moment(dob).format('DD MMM YYYY')}"
I like: <ul>{passions.map((p, i) => <li key={i}>{p}</li>)}</ul>
I'm {name} and I was born on "{moment(dob).format('DD MMM YYYY')}" I like:{' '}
<ul>
{passions.map((p, i) =>
<li key={i}>
{p}
</li>
)}
</ul>
<p>My favorite number is {favoriteNumber}.</p>
<p>My most comfortable room temperature is {comfortTemp} degrees Fahrenheit.</p>
</div>
Expand All @@ -50,21 +60,31 @@ stories.add('dates Knob', () => {
return (
<ul style={{ listStyleType: 'none', listStyle: 'none', paddingLeft: '15px' }}>
<li>
<p><b>Javascript Date</b> default value, passes date value</p>
<p>
<b>Javascript Date</b> default value, passes date value
</p>
<blockquote>
<code>const myDob = date('My DOB', new Date('July 07 1993'));</code>
<pre>// I was born in: "{moment(myDob).format('DD MMM YYYY')}"</pre>
<pre>
// I was born in: "{moment(myDob).format('DD MMM YYYY')}"
</pre>
</blockquote>
</li>
<li>
<p><b>undefined</b> default value passes today's date</p>
<p>
<b>undefined</b> default value passes today's date
</p>
<blockquote>
<code>const today = date('today');</code>
<pre>// Today's date is: "{moment(today).format('DD MMM YYYY')}"</pre>
<pre>
// Today's date is: "{moment(today).format('DD MMM YYYY')}"
</pre>
</blockquote>
</li>
<li>
<p><b>null</b> default value passes null value</p>
<p>
<b>null</b> default value passes null value
</p>
<blockquote>
<code>const dob = date('DOB', null);</code>
<pre>
Expand All @@ -83,7 +103,29 @@ stories.add('dynamic knobs', () => {
<div>
{text('compulsary', 'I must be here')}
</div>
{showOptional === 'yes' ? <div>{text('optional', 'I can disapear')}</div> : null}
{showOptional === 'yes'
? <div>
{text('optional', 'I can disapear')}
</div>
: null}
</div>
);
});

stories.add('with object knobs', () => {
const data = object('Data', {
style: {
width: '400px',
height: '400px',
backgroundColor: '#000',
margin: '20px',
},
children: addonName => `This is ${addonName} addon`,
});

return (
<div style={data.style}>
{typeof data.children === 'function' ? data.children('knobs') : data.children}
</div>
);
});
Expand Down
2 changes: 2 additions & 0 deletions addons/knobs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
"dependencies": {
"@storybook/addons": "^3.1.6",
"babel-runtime": "^6.23.0",
"brace": "^0.10.0",
"deep-equal": "^1.0.1",
"global": "^4.3.2",
"insert-css": "^1.0.0",
"lodash.debounce": "^4.0.8",
"moment": "^2.18.1",
"prop-types": "^15.5.8",
"react-ace": "^5.1.0",
"react-color": "^2.11.4",
"react-datetime": "^2.8.10",
"react-textarea-autosize": "^4.3.0"
Expand Down
8 changes: 4 additions & 4 deletions addons/knobs/src/components/PropForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const stylesheet = {
},
};

export default class propForm extends React.Component {
export default class PropForm extends React.Component {
constructor() {
super();
this._onFieldChange = this.onFieldChange.bind(this);
Expand Down Expand Up @@ -54,13 +54,13 @@ export default class propForm extends React.Component {
}
}

propForm.displayName = 'propForm';
PropForm.displayName = 'PropForm';

propForm.defaultProps = {
PropForm.defaultProps = {
knobs: [],
};

propForm.propTypes = {
PropForm.propTypes = {
knobs: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string,
Expand Down
107 changes: 46 additions & 61 deletions addons/knobs/src/components/types/Object.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,65 @@
import PropTypes from 'prop-types';
import React from 'react';
import Textarea from 'react-textarea-autosize';
import deepEqual from 'deep-equal';

const styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
fontFamily: 'monospace',
};
import AceEditor from 'react-ace';

class ObjectType extends React.Component {
constructor(...args) {
super(...args);
this.state = {};
}
import 'brace';
import 'brace/mode/javascript';
import 'brace/theme/github';

getJSONString() {
const { json, jsonString } = this.state;
const { knob } = this.props;
const getSpaces = level => Array.from({ length: level * 2 }, () => ' ').join('');

function toString(obj, spaceLevel = 1) {
const string = [];

if (typeof obj === 'object' && obj !== null) {
const keys = Object.keys(obj);
string.push('{', '\n');

keys.forEach(prop =>
string.push(getSpaces(spaceLevel), prop, ': ', toString(obj[prop], spaceLevel + 1), ',', '\n')
);

// If there is an error in the JSON, we need to give that errored JSON.
if (this.failed) return jsonString;
string.push(getSpaces(spaceLevel - 1), '}');
} else if (Array.isArray(obj)) {
const keys = Object.keys(obj);
string.push('[');

// If the editor value and the knob value is the same, we need to return the
// editor value as it allow user to add new fields to the JSON.
if (deepEqual(json, knob.value)) return jsonString;
keys.forEach(prop => string.push(toString(obj[prop]), ','));

// If the knob's value is different from the editor, it seems like
// there's a outside change and we need to get that.
return JSON.stringify(knob.value, null, 2);
string.push(']');
} else if (typeof obj === 'function') {
string.push(obj.toString());
} else {
string.push(JSON.stringify(obj));
}

handleChange(e) {
return `${string.join('')}`;
}

class ObjectType extends React.Component {
handleChange = value => {
const { onChange } = this.props;
const newState = {
jsonString: e.target.value,
};

try {
newState.json = JSON.parse(e.target.value.trim());
onChange(newState.json);
this.failed = false;
onChange(value);
} catch (err) {
this.failed = true;
console.warn(err);
}

this.setState(newState);
}
};

render() {
const { knob } = this.props;
const jsonString = this.getJSONString();
const extraStyle = {};

if (this.failed) {
extraStyle.border = '1px solid #fadddd';
extraStyle.backgroundColor = '#fff5f5';
}
const objectString = knob.value;

return (
<Textarea
id={knob.name}
ref={c => {
this.input = c;
}}
style={{ ...styles, ...extraStyle }}
value={jsonString}
onChange={e => this.handleChange(e)}
<AceEditor
ref={ref => (this.ace = ref)}
mode="javascript"
name={knob.name}
value={objectString}
width="100%"
onChange={this.handleChange}
editorProps={{ $blockScrolling: true }}
/>
);
}
Expand All @@ -88,12 +73,12 @@ ObjectType.defaultProps = {
ObjectType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.object,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};

ObjectType.serialize = object => JSON.stringify(object);
ObjectType.deserialize = value => (value ? JSON.parse(value) : {});
ObjectType.deserialize = value => eval(`(${value})`); // eslint-disable-line no-eval
ObjectType.serialize = obj => `(${toString(obj)})`;

export default ObjectType;
5 changes: 4 additions & 1 deletion addons/knobs/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import addons from '@storybook/addons';
import KnobManager from './KnobManager';
import ObjectType from './components/types/Object';

const manager = new KnobManager();

Expand Down Expand Up @@ -39,7 +40,9 @@ export function color(name, value) {
}

export function object(name, value) {
return manager.knob(name, { type: 'object', value });
return ObjectType.deserialize(
manager.knob(name, { type: 'object', value: ObjectType.serialize(value) })
);
}

export function select(name, options, value) {
Expand Down

0 comments on commit 13768d6

Please sign in to comment.