diff --git a/client/Components/ComponentParams/ParamSelector/ParamSelectorJSX/index.js b/client/Components/ComponentParams/ParamSelector/ParamSelectorJSX/index.js
index 2687adc..75374d4 100644
--- a/client/Components/ComponentParams/ParamSelector/ParamSelectorJSX/index.js
+++ b/client/Components/ComponentParams/ParamSelector/ParamSelectorJSX/index.js
@@ -2,11 +2,9 @@ import React from 'react';
import _ from 'underscore';
import TextField from '../../../UI/TextField';
-import Tooltip from '../../../UI/Tooltip';
+import ErrorIndicator from '../../../UI/ErrorIndicator';
import jsxToString from '../../../../services/jsx-to-string';
-import './index.scss';
-
const DEBOUNCE_AMOUNT = 500;
const START_WITH_TAG_REGEX = /^\s*;
@@ -98,28 +96,17 @@ export default class ParamSelectorJSX extends React.Component {
onChange(null, compiledValue);
}
- /**
- * Create an error tooltip to show the latest compilation error in a tooltip
- */
- renderErrorTooltip() {
- return (
-
-
{this.state.error.toString()}}>
-
-
-
- );
- }
-
render() {
return (
-
- {this.state.error ? this.renderErrorTooltip() : null}
-
-
+
+
+ {this.state.error ? this.renderErrorTooltip() : null}
+
+
+
);
}
}
\ No newline at end of file
diff --git a/client/Components/ComponentParams/ParamSelector/ParamSelectorObject/index.js b/client/Components/ComponentParams/ParamSelector/ParamSelectorObject/index.js
new file mode 100644
index 0000000..87accbf
--- /dev/null
+++ b/client/Components/ComponentParams/ParamSelector/ParamSelectorObject/index.js
@@ -0,0 +1,75 @@
+import React from 'react';
+import _ from 'underscore';
+
+import TextField from '../../../UI/TextField';
+import ErrorIndicator from '../../../UI/ErrorIndicator';
+
+const DEBOUNCE_AMOUNT = 300;
+
+/**
+ * @description
+ * open input for object values. will try to eval value and fallback to undefined.
+ *
+ * @param {function} onChange
+ */
+export default class ParamSelectorObject extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {error: null};
+ this.onChange = this.onChange.bind(this);
+ this.reportChangeBounced = _.debounce(val => this.reportChange(val), DEBOUNCE_AMOUNT);
+ }
+
+ /**
+ * A bit of a hack - it puts initial Object to the TextField but free it right after
+ * (by setting it immediately after to undefined)
+ */
+ componentDidMount() {
+ let value = JSON.stringify(this.props.selectedValue);
+ if (value) {
+ this.setState(
+ state => _.extend({}, state, {value}),
+ () => this.setState(state => _.extend({}, state, {value: undefined}))
+ );
+ }
+ }
+
+ /**
+ * Will update the new value from the event.
+ * sadly the event cannot be sent to the parent onChange because we debounce the onChange
+ * @param {event} e
+ */
+ onChange(e) {
+ const newValue = e.target.value;
+ this.reportChangeBounced(newValue);
+ }
+
+ /**
+ * report to parent onChange, this function is debounced to be reportChangeBounced
+ * @param {string} val
+ */
+ reportChange(val) {
+ const {name, compiler = _.noop, onChange = _.noop} = this.props;
+ let obj, error = null;
+ try {
+ obj = compiler(`Object(${val})`);
+ } catch (e) {
+ error = e;
+ }
+ this.setState(state => _.extend({}, state, {error}));
+ onChange(null, obj);
+ }
+
+ render() {
+ return (
+
+
+
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/client/Components/ComponentParams/ParamSelector/index.js b/client/Components/ComponentParams/ParamSelector/index.js
index 6189f80..96b814f 100644
--- a/client/Components/ComponentParams/ParamSelector/index.js
+++ b/client/Components/ComponentParams/ParamSelector/index.js
@@ -7,12 +7,14 @@ import ParamSelectorVariant from './ParamSelectorVariant';
import ParamSelectorJSON from './ParamSelectorJSON';
import ParamSelectorFunction from './ParamSelectorFunction';
import ParamSelectorJSX from './ParamSelectorJSX';
+import ParamSelectorObject from './ParamSelectorObject';
const paramTypeToComponent = {
'bool': ParamSelectorBoolean,
'variant': ParamSelectorVariant,
'function' : ParamSelectorFunction,
'node': ParamSelectorJSX,
+ 'object': ParamSelectorObject,
'default': ParamSelectorJSON,
};
diff --git a/client/Components/UI/ErrorIndicator/index.js b/client/Components/UI/ErrorIndicator/index.js
new file mode 100644
index 0000000..d758695
--- /dev/null
+++ b/client/Components/UI/ErrorIndicator/index.js
@@ -0,0 +1,35 @@
+import React from 'react';
+
+import Tooltip from '../Tooltip';
+import './index.scss';
+
+/**
+ * @name
+ * ErrorIndicator
+ *
+ * @module
+ * Content
+ *
+ * @description
+ * Show an error tooltip on a container
+ *
+ * @example
+ *
+ * My error-full content
+ *
+ *
+ * @param {node} children
+ * @param {String|Error} [error]
+ */
+export default function ErrorIndicator ({children, error}) {
+ return (
+
+ {error ? (
+
{error.toString && error.toString()}}>
+
+
+
) : null}
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/client/Components/ComponentParams/ParamSelector/ParamSelectorJSX/index.scss b/client/Components/UI/ErrorIndicator/index.scss
similarity index 88%
rename from client/Components/ComponentParams/ParamSelector/ParamSelectorJSX/index.scss
rename to client/Components/UI/ErrorIndicator/index.scss
index f6d0074..765d13f 100644
--- a/client/Components/ComponentParams/ParamSelector/ParamSelectorJSX/index.scss
+++ b/client/Components/UI/ErrorIndicator/index.scss
@@ -1,4 +1,4 @@
-.library-_-jsx-selector-wrapper {
+.library-_-tooltip-error-indicator-wrapper {
position: relative;
pre {
white-space: pre-wrap;
diff --git a/client/components.js b/client/components.js
index 665f82b..586a160 100644
--- a/client/components.js
+++ b/client/components.js
@@ -1,5 +1,6 @@
import Card from './Components/UI/Card';
import CodeCard from './Components/UI/CodeCard';
+import ErrorIndicator from './Components/UI/ErrorIndicator';
import Menu from './Components/UI/Menu';
import RaisedButton from './Components/UI/RaisedButton';
import MenuItem from './Components/UI/MenuItem';
@@ -12,6 +13,7 @@ import Modal from './Components/UI/Modal';
export default {
Card,
CodeCard,
+ ErrorIndicator,
Menu,
MenuItem,
RaisedButton,
diff --git a/client/documentation.js b/client/documentation.js
index 1b38df8..c765378 100644
--- a/client/documentation.js
+++ b/client/documentation.js
@@ -1 +1 @@
-export default {"Card":"/**\n * @name \n * Card\n * \n * @module\n * Cards\n * \n * @description\n * Card container with border and box-shadow\n * Wrap children for showing them in a nice frame\n *\n * @example\n * // card with children\n * \n * Card content
\n *
\n * \n * \n *\n * @param {String} [className] Append class name to container\n * @param {node} [children] Children elements\n */","CodeCard":"/**\n * @name\n * CodeCard\n * \n * @module\n * Cards\n * \n * @description\n * CodeCard with border and inset box-shadow\n * to show code examples with \"pre\" tag\n *\n * @example\n * \n * console.log('You are awesome!');\n * \n *\n * @param {string} [className] additional className to the element\n * @param {node} children the code that will be formatted\n *\n */","Collapsible":"/**\n * @name\n * Collapsible\n * \n * @module\n * Content\n *\n * @description\n * An element that can be closed or opened by clicking on the title\n *\n * @example\n * // Example with some style minimalism\n * \n * This is the content\n * \n *\n * @param {node} title clickable string/element to open/close the content\n * @param {node} children inner content of the Collapsible\n * @param {boolean} [isOpen=true] to control the Collapsible from the outside by passing a boolean value\n * @param {function} [onChange] callback for change of the content state\n * @param {object} [style] outer style of the element\n * @param {object} [titleStyle] the style of the title only\n * @param {object} [contentStyle] the title of the content only\n */","Menu":"/**\n * @name\n * Menu \n * \n * @module\n * Menus\n * \n * @description\n * Menu to select items\n * Wrap MenuItem components with the prop \"value\"\n * and control the chosen value by the Menu prop \"value\"\n * \n * @example\n * // Choose a color\n * \n * \n * @param {string|number} [value] current selected value of MenuItem inside the menu\n * @param {function} [onChange] callback for changes in selected value\n * @param {node} children MenuItems to be selected\n */","MenuItem":"/**\n * @name \n * MenuItem\n * \n * @module\n * Menus\n * \n * @description\n * Menu Item to be used in Menu component\n * as a child. fill props.value with this item value.\n * \n * @example \n * \n *\n * @param {string|number} value this item value in the menu\n * @param {node} children\n */","Modal":"/**\n * @name\n * Modal\n * \n * @module\n * Content\n * \n * @description\n * Change isOpen to open/close the modal\n * \n * @example\n * \n * inner modal content\n * \n * \n * @param {boolean} [isOpen] change to open/close the modal\n * @param {string} title the modal title\n * @param {node} children the modal content\n * @param {function} onChange will execute the callback on change with the new value\n */","RaisedButton":"/**\n * @name \n * RaisedButton\n * \n * @module\n * Inputs\n * \n * @description\n * A styled button for clicking and hovering\n * \n * @example\n * console.log('RaisedButton was clicked, event: ', e)}>\n * Raised Button Text\n * \n * \n * @param {function} [onClick] callback for the tooltip was clicked\n * @param {node} children the clickable element that will open the tooltip\n */","Separator":"/**\n * @name\n * Separator\n * \n * @module\n * General\n * \n * @description\n * A generic separator to be used between components\n *\n * @example\n * \n */","TextField":"/**\n * @name\n * TextField\n * \n * @module\n * Inputs\n * \n * @description\n * Text Field with adjustable height\n * It will lengthen the textarea up to 5 rows based on the content in it\n * use props.value to control the component.\n * Try writing in it!\n * \n * @example\n * // Without value\n * console.log('TextField was changed with value: ', e.target.value)}/>\n * \n * @example\n * // With controlled value, change only from the parent\n * console.log('TextField was changed with value: ', e.target.value)}/>\n * \n * @param {string} [placeholder] value as a placeholder until user input another value\n * @param {string} [value] current value of the text field\n * @param {function} onChange\n */","Tooltip":"/**\n * @name\n * Tooltip\n * \n * @module\n * Content\n * \n * @description\n * Tooltip to be shown on hover or click events.\n * Wrap children to give them the tooltip upon them.\n * This is an auto-location Tooltip. Responsive solution to the tooltip.\n *\n * @example\n * // Default props\n * \n * Click me to open the tooltip!\n * \n * \n * @example \n * // center bottom directions\n * \n * Text to open the tooltip upon on the bottom\n * \n *\n * @param {*} [tooltip] tooltip inner content, can be text or elements\n * @param {\"top\"|\"bottom\"} [side=top] preferred placement of the tooltip relative to the element\n * @param {\"center\"|\"start\"|\"end\"} [alignment=center] preferred alignment of the tooltip relative to the element\n * @param {\"click\"|\"hover\"} [trigger=click] trigger event, on mobile you should stick with click\n * @param {function} [onTooltipOpen] callback for when the tooltip is opened\n * @param {function} [onAfterTooltipOpen] callback for after the tooltip is opened\n * @param {Boolean} [isOpen] controlled prop to open/close the tooltip\n * @param {node} [children] the element/s to be triggering the tooltip appearance\n */"};
\ No newline at end of file
+export default {"Card":"/**\n * @name \n * Card\n * \n * @module\n * Cards\n * \n * @description\n * Card container with border and box-shadow\n * Wrap children for showing them in a nice frame\n *\n * @example\n * // card with children\n * \n * Card content
\n *
\n * \n * \n *\n * @param {String} [className] Append class name to container\n * @param {node} [children] Children elements\n */","CodeCard":"/**\n * @name\n * CodeCard\n * \n * @module\n * Cards\n * \n * @description\n * CodeCard with border and inset box-shadow\n * to show code examples with \"pre\" tag\n *\n * @example\n * \n * console.log('You are awesome!');\n * \n *\n * @param {string} [className] additional className to the element\n * @param {node} children the code that will be formatted\n *\n */","Collapsible":"/**\n * @name\n * Collapsible\n * \n * @module\n * Content\n *\n * @description\n * An element that can be closed or opened by clicking on the title\n *\n * @example\n * // Example with some style minimalism\n * \n * This is the content\n * \n *\n * @param {node} title clickable string/element to open/close the content\n * @param {node} children inner content of the Collapsible\n * @param {boolean} [isOpen=true] to control the Collapsible from the outside by passing a boolean value\n * @param {function} [onChange] callback for change of the content state\n * @param {object} [style] outer style of the element\n * @param {object} [titleStyle] the style of the title only\n * @param {object} [contentStyle] the title of the content only\n */","ErrorIndicator":"/**\n * @name\n * ErrorIndicator\n * \n * @module\n * Content\n * \n * @description\n * Show an error tooltip on a container\n * \n * @example\n * \n * My error-full content\n * \n * \n * @param {node} children\n * @param {String|Error} [error]\n */","Menu":"/**\n * @name\n * Menu \n * \n * @module\n * Menus\n * \n * @description\n * Menu to select items\n * Wrap MenuItem components with the prop \"value\"\n * and control the chosen value by the Menu prop \"value\"\n * \n * @example\n * // Choose a color\n * \n * \n * @param {string|number} [value] current selected value of MenuItem inside the menu\n * @param {function} [onChange] callback for changes in selected value\n * @param {node} children MenuItems to be selected\n */","MenuItem":"/**\n * @name \n * MenuItem\n * \n * @module\n * Menus\n * \n * @description\n * Menu Item to be used in Menu component\n * as a child. fill props.value with this item value.\n * \n * @example \n * \n *\n * @param {string|number} value this item value in the menu\n * @param {node} children\n */","Modal":"/**\n * @name\n * Modal\n * \n * @module\n * Content\n * \n * @description\n * Change isOpen to open/close the modal\n * \n * @example\n * \n * inner modal content\n * \n * \n * @param {boolean} [isOpen] change to open/close the modal\n * @param {string} title the modal title\n * @param {node} children the modal content\n * @param {function} onChange will execute the callback on change with the new value\n */","RaisedButton":"/**\n * @name \n * RaisedButton\n * \n * @module\n * Inputs\n * \n * @description\n * A styled button for clicking and hovering\n * \n * @example\n * console.log('RaisedButton was clicked, event: ', e)}>\n * Raised Button Text\n * \n * \n * @param {function} [onClick] callback for the tooltip was clicked\n * @param {node} children the clickable element that will open the tooltip\n */","Separator":"/**\n * @name\n * Separator\n * \n * @module\n * General\n * \n * @description\n * A generic separator to be used between components\n *\n * @example\n * \n */","TextField":"/**\n * @name\n * TextField\n * \n * @module\n * Inputs\n * \n * @description\n * Text Field with adjustable height\n * It will lengthen the textarea up to 5 rows based on the content in it\n * use props.value to control the component.\n * Try writing in it!\n * \n * @example\n * // Without value\n * console.log('TextField was changed with value: ', e.target.value)}/>\n * \n * @example\n * // With controlled value, change only from the parent\n * console.log('TextField was changed with value: ', e.target.value)}/>\n * \n * @param {string} [placeholder] value as a placeholder until user input another value\n * @param {string} [value] current value of the text field\n * @param {function} onChange\n */","Tooltip":"/**\n * @name\n * Tooltip\n * \n * @module\n * Content\n * \n * @description\n * Tooltip to be shown on hover or click events.\n * Wrap children to give them the tooltip upon them.\n * This is an auto-location Tooltip. Responsive solution to the tooltip.\n *\n * @example\n * // Default props\n * \n * Click me to open the tooltip!\n * \n * \n * @example \n * // center bottom directions\n * \n * Text to open the tooltip upon on the bottom\n * \n *\n * @param {*} [tooltip] tooltip inner content, can be text or elements\n * @param {\"top\"|\"bottom\"} [side=top] preferred placement of the tooltip relative to the element\n * @param {\"center\"|\"start\"|\"end\"} [alignment=center] preferred alignment of the tooltip relative to the element\n * @param {\"click\"|\"hover\"} [trigger=click] trigger event, on mobile you should stick with click\n * @param {function} [onTooltipOpen] callback for when the tooltip is opened\n * @param {function} [onAfterTooltipOpen] callback for after the tooltip is opened\n * @param {Boolean} [isOpen] controlled prop to open/close the tooltip\n * @param {node} [children] the element/s to be triggering the tooltip appearance\n */"};
\ No newline at end of file