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* - {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 * console.log(val)}>\n * \n * Color pink\n * \n * \n * Color cyan\n * \n * \n * Color green\n * \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 * Menu Item\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 * console.log(val)}>\n * \n * Color pink\n * \n * \n * Color cyan\n * \n * \n * Color green\n * \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 * Menu Item\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