aboutsummaryrefslogtreecommitdiffstats
path: root/stories/react
diff options
context:
space:
mode:
authorIsrael Lavi <israel.lavi@intl.att.com>2018-05-21 17:42:00 +0300
committerIsrael Lavi <il0695@att.com>2018-05-21 17:52:01 +0300
commit1994c98063c27a41797dec01f2ca9fcbe33ceab0 (patch)
treef30beeaf15a8358f6da78fdd74bcbda74bd334f8 /stories/react
parent4749f4631426fcbe29ed98cef8f24cab18b501d0 (diff)
init commit onap ui
Change-Id: I1dace78817dbba752c550c182dfea118b4a38646 Issue-ID: SDC-1350 Signed-off-by: Israel Lavi <il0695@att.com>
Diffstat (limited to 'stories/react')
-rw-r--r--stories/react/Accordion.stories.js16
-rw-r--r--stories/react/Checkbox.stories.js33
-rw-r--r--stories/react/Checklist.stories.js65
-rw-r--r--stories/react/Colors.stories.js53
-rw-r--r--stories/react/Input.stories.js51
-rw-r--r--stories/react/Modal.stories.js133
-rw-r--r--stories/react/Panel.stories.js22
-rw-r--r--stories/react/PopupMenu.stories.js37
-rw-r--r--stories/react/Radio.stories.js33
-rw-r--r--stories/react/RadioGroup.stories.js34
-rw-r--r--stories/react/SVGIcon.stories.js103
-rw-r--r--stories/react/Tabs.stories.js48
-rw-r--r--stories/react/Tiles.stories.js89
-rw-r--r--stories/react/Typography.stories.js62
-rw-r--r--stories/react/buttons/LinkButtons.stories.js49
-rw-r--r--stories/react/buttons/PrimaryButtons.stories.js49
-rw-r--r--stories/react/buttons/SecondaryButtons.stories.js49
-rw-r--r--stories/react/index.js66
-rw-r--r--stories/react/utils/BeautifyHTML.js33
-rw-r--r--stories/react/utils/Examples.js23
-rw-r--r--stories/react/utils/InsertSVGIcons.js15
-rw-r--r--stories/react/utils/SourceToggle.js73
-rw-r--r--stories/react/utils/components/DropdownMenu.js14
-rw-r--r--stories/react/utils/jsxToString.js74
24 files changed, 1224 insertions, 0 deletions
diff --git a/stories/react/Accordion.stories.js b/stories/react/Accordion.stories.js
new file mode 100644
index 0000000..85fdae3
--- /dev/null
+++ b/stories/react/Accordion.stories.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import Examples from './utils/Examples.js';
+import Accordion from '../../src/react/Accordion.js';
+import HTMLBasic from '../../components/accordion/accordion-basic.html';
+let examples = {
+ Basic: {
+ jsx: <Accordion title='Accordion Title'><div>Accordion body</div></Accordion>,
+ html: HTMLBasic
+ }
+};
+
+const Checkboxes = () => (
+ <Examples examples={examples} />
+);
+
+export default Checkboxes;
diff --git a/stories/react/Checkbox.stories.js b/stories/react/Checkbox.stories.js
new file mode 100644
index 0000000..3fb3ad1
--- /dev/null
+++ b/stories/react/Checkbox.stories.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import Examples from './utils/Examples.js';
+
+import Checkbox from '../../src/react/Checkbox';
+import HTMLCheckboxChecked from '../../components/checkbox/checkbox-checked.html';
+import HTMLCheckboxUnchecked from '../../components/checkbox/checkbox-unchecked.html';
+import HTMLCheckboxDisabled from '../../components/checkbox/checkbox-disabled.html';
+import HTMLCheckboxDisabledChecked from '../../components/checkbox/checkbox-disabled-checked.html';
+
+let examples = {
+ Checked: {
+ jsx: <Checkbox checked={true} label='This is the checkbox label' value='myVal' onChange={()=>{}} data-test-id='mycheckbox-1' inputRef={() => {} } />,
+ html: HTMLCheckboxChecked
+ },
+ Unchecked: {
+ jsx: <Checkbox label='This is the checkbox label' value='myVal' onChange={()=>{}} data-test-id='mycheckbox-2' inputRef={() => {} } />,
+ html: HTMLCheckboxUnchecked
+ },
+ Disabled: {
+ jsx: <Checkbox label='This is the checkbox label' disabled={true} value='myVal' onChange={()=>{}} data-test-id='mycheckbox-4' inputRef={() => {} } />,
+ html: HTMLCheckboxDisabled
+ },
+ 'Disabled and Checked': {
+ jsx: <Checkbox label='This is the checkbox label' disabled={true} checked={true} value='myVal' onChange={()=>{}} data-test-id='mycheckbox-4' inputRef={() => {} } />,
+ html: HTMLCheckboxDisabledChecked
+ }
+};
+
+const Checkboxes = () => (
+ <Examples examples={examples} />
+);
+
+export default Checkboxes;
diff --git a/stories/react/Checklist.stories.js b/stories/react/Checklist.stories.js
new file mode 100644
index 0000000..0fd089b
--- /dev/null
+++ b/stories/react/Checklist.stories.js
@@ -0,0 +1,65 @@
+import React from 'react';
+import Examples from './utils/Examples.js';
+import Checklist from '../../src/react/Checklist.js';
+import HTMLListChecked from '../../components/checklist/checklist-with-checked-items.html';
+import HTMLListDisabled from '../../components/checklist/checklist-with-disabled-items.html';
+const items = [
+ {
+ label: 'apple',
+ value: 'apple',
+ dataTestId: 'apple',
+ checked: true
+ },
+ {
+ label: 'banana',
+ value: 'banana',
+ dataTestId: 'banana',
+ checked: false
+ },
+ {
+ label: 'orange',
+ value: 'orange',
+ dataTestId: 'orange',
+ checked: true
+ }
+];
+
+const itemsDisabled = [
+ {
+ label: 'apple',
+ value: 'apple',
+ dataTestId: 'apple',
+ checked: true,
+ disabled: true
+ },
+ {
+ label: 'banana',
+ value: 'banana',
+ dataTestId: 'banana',
+ checked: false,
+ disabled: true
+ },
+ {
+ label: 'orange',
+ value: 'orange',
+ dataTestId: 'orange',
+ checked: false
+ }
+];
+
+let examples = {
+ Basic: {
+ jsx: <Checklist items={items} onChange={() => { }} />,
+ html: HTMLListChecked
+ },
+ Disabled: {
+ jsx: <Checklist items={itemsDisabled} onChange={() => { }} />,
+ html: HTMLListDisabled
+ }
+};
+
+const ChecklistStory = () => (
+ <Examples examples={examples} />
+);
+
+export default ChecklistStory; \ No newline at end of file
diff --git a/stories/react/Colors.stories.js b/stories/react/Colors.stories.js
new file mode 100644
index 0000000..d6758ce
--- /dev/null
+++ b/stories/react/Colors.stories.js
@@ -0,0 +1,53 @@
+import React, {Component} from 'react';
+
+const colorMap = {
+ '$white': '#ffffff',
+ '$blue': '#009fdb',
+ '$light-blue': '#1eb9f3',
+ '$lighter-blue': '#e6f6fb',
+ '$blue-disabled': '#9dd9ef',
+ '$dark-blue': '#0568ae',
+ '$black': '#000000',
+ '$rich-black': '#323943',
+ '$text-black': '#191919',
+ '$dark-gray': '#5a5a5a',
+ '$gray': '#959595',
+ '$light-gray': '#d2d2d2',
+ '$silver': '#eaeaea',
+ '$light-silver': '#f2f2f2',
+ '$green': '#4ca90c',
+ '$functional-red': '#cf2a2a',
+ '$yellow': '#ffb81c',
+ '$dark-purple': '#702f8a',
+ '$purple': '#9063cd',
+ '$light-purple': '#caa2dd'
+};
+
+function Color({colorName, colorValue}) {
+ return (
+ <div key={colorName} className='color-section'>
+ <div className='color-circle' style={{backgroundColor: colorValue}} />
+ <div>{colorName.replace('$', '')}</div>
+ <div>{colorValue}</div>
+ </div>
+ );
+}
+
+class Colors extends Component {
+
+ render() {
+ return (
+ <div>
+ <h1>Colors Palette</h1>
+ <div className='colors-table'>
+ {
+ Object.keys(colorMap).map(colorName => <Color key={colorName} colorValue={colorMap[colorName]} colorName={colorName}/>)
+ }
+ </div>
+ </div>
+ );
+ }
+
+}
+
+export default Colors;
diff --git a/stories/react/Input.stories.js b/stories/react/Input.stories.js
new file mode 100644
index 0000000..869bafa
--- /dev/null
+++ b/stories/react/Input.stories.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import { action } from '@storybook/addon-actions';
+import Examples from './utils/Examples.js';
+
+import ReactInput from '../../src/react/Input.js';
+
+import InputDefaultHtml from '../../components/input/input.html';
+import InputRequiredHtml from '../../components/input/input-required.html';
+import InputNumberHtml from '../../components/input/input-number.html';
+import InputViewOnlyHtml from '../../components/input/input-view-only.html';
+import InputDisabledHtml from '../../components/input/input-disabled.html';
+import InputPlaceholderHtml from '../../components/input/input-placeholder.html';
+import InputErrorHtml from '../../components/input/input-error.html';
+
+let examples = {
+ 'Input Default': {
+ jsx: <ReactInput name='input1' value='Default' label='I am a label' onChange={ action('input-change')}/>,
+ html: InputDefaultHtml
+ },
+ 'Input Required': {
+ jsx: <ReactInput name='input2' value='Default' label='I am a label' onChange={ action('input-change')} isRequired/>,
+ html: InputRequiredHtml
+ },
+ 'Input Number': {
+ jsx: <ReactInput name='input3' value='3' label='I am a label' type="number" onChange={ action('input-change')}/>,
+ html: InputNumberHtml
+ },
+ 'Input View Only': {
+ jsx: <ReactInput value='Read Only Text' label='I am a label' onChange={ action('input-change')} readOnly/>,
+ html: InputViewOnlyHtml
+ },
+ 'Input Disabled': {
+ jsx: <ReactInput value='Default' label='I am a label' onChange={ action('input-change')} disabled/>,
+ html: InputDisabledHtml
+ },
+ 'Input Placeholder': {
+ jsx: <ReactInput name='input5' placeholder='Write Here...' label='I am a label' onChange={ action('input-change')}/>,
+ html: InputPlaceholderHtml
+ },
+ 'Input Error': {
+ jsx: <ReactInput value='Default' name='input6' label='I am a label' errorMessage='This is the error message' onChange={ action('input-change')}/>,
+ html: InputErrorHtml
+ }
+
+}
+
+const Inputs = () => (
+ <Examples examples={examples} />
+);
+
+export default Inputs;
diff --git a/stories/react/Modal.stories.js b/stories/react/Modal.stories.js
new file mode 100644
index 0000000..29ff7a5
--- /dev/null
+++ b/stories/react/Modal.stories.js
@@ -0,0 +1,133 @@
+import React from 'react';
+import Examples from './utils/Examples.js';
+import Button from '../../src/react/Button.js';
+import Modal from '../../src/react/Modal.js';
+import Input from '../../src/react/Input.js';
+import HTMLStandardModal from '../../components/modal/standard-modal.html';
+import HTMLAlertModal from '../../components/modal/alert-modal.html';
+import HTMLErrorModal from '../../components/modal/error-modal.html';
+import HTMLCustomModal from '../../components/modal/custom-modal.html';
+
+class Example extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ show: false
+ };
+ }
+ render() {
+ const { children } = this.props;
+ const { show } = this.state;
+ var childrenWithProps = React.Children.map(children, child => {
+ let childChildrenWithProps = [];
+ if (child.props.children) {
+ let childChildren = child.props.children;
+ childChildrenWithProps = React.Children.map(childChildren, child =>
+ React.cloneElement(child, { onClose: ()=>this.setState({show: !show}) }));
+
+ }
+ return React.cloneElement(child, { show: this.state.show, onClose: ()=>this.setState({show: !show}), children: childChildrenWithProps});
+ }
+ );
+
+ return (
+ <div>
+ <Button onClick={() => this.setState({show: !show})}>Modal</Button>
+ {childrenWithProps}
+ </div>
+ );
+ }
+}
+
+const ModalBody = () => {
+ return (
+ <div>
+ <Input
+ name='input1'
+ value='Default'
+ label='I am a label'
+ type='text' />
+ <Input
+ name='input1'
+ value='Default'
+ label='I am a label'
+ type='text' />
+ <Input
+ name='input1'
+ value='Default'
+ label='I am a label'
+ type='text' />
+
+ </div>);
+};
+
+const BODY_TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed risus nisl, egestas vitae erat non,' +
+ 'pulvinar lacinia libero. Integer pulvinar pellentesque accumsan. Sed hendrerit lacus eu tempus pharetra';
+
+const isShown = false;
+
+let examples = {
+ Standard: {
+ jsx: <Example>
+ <Modal show={() => isShown()} size='small'>
+ <Modal.Header><Modal.Title>Standard Modal</Modal.Title></Modal.Header>
+ <Modal.Body>
+ {BODY_TEXT}
+ </Modal.Body>
+ <Modal.Footer actionButtonText='Yes' actionButtonClick={()=>{}}/>
+ </Modal>
+ </Example>,
+ html: HTMLStandardModal,
+ exclude: 'Example',
+ renderFromJsx: true
+ },
+ Alert: {
+ jsx: <Example>
+ <Modal show={() => isShown()} type='alert' size='small'>
+ <Modal.Header type='alert'><Modal.Title>Title</Modal.Title></Modal.Header>
+ <Modal.Body>
+ {BODY_TEXT}
+ </Modal.Body>
+ <Modal.Footer closeButtonText='Ok'/>
+ </Modal>
+ </Example>,
+ html: HTMLAlertModal,
+ exclude: 'Example',
+ renderFromJsx: true
+ },
+ Error: {
+ jsx: <Example>
+ <Modal show={() => isShown()} size='small' type='error'>
+ <Modal.Header onClose={()=>isShown(false)} type='error'><Modal.Title>Title</Modal.Title></Modal.Header>
+ <Modal.Body>
+ {BODY_TEXT}
+ </Modal.Body>
+ <Modal.Footer onClose={()=>isShown(false)} closeButtonText='Ok'/>
+ </Modal>
+ </Example>,
+ html: HTMLErrorModal,
+ exclude: 'Example',
+ renderFromJsx: true
+ },
+
+ Custom: {
+ jsx: <Example>
+ <Modal show={() => isShown()} type='custom'>
+ <Modal.Header type='custom'><Modal.Title>Title</Modal.Title></Modal.Header>
+ <Modal.Body>
+ <ModalBody/>
+ </Modal.Body>
+ <Modal.Footer actionButtonText='Ok' actionButtonClick={()=>{}}/>
+ </Modal>
+ </Example>,
+ html: HTMLCustomModal,
+ exclude: 'Example',
+ renderFromJsx: true
+ }
+};
+
+const Modals = () => (
+ <Examples examples={examples}/>
+);
+
+export default Modals; \ No newline at end of file
diff --git a/stories/react/Panel.stories.js b/stories/react/Panel.stories.js
new file mode 100644
index 0000000..f87eefb
--- /dev/null
+++ b/stories/react/Panel.stories.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import Examples from './utils/Examples.js';
+import Panel from '../../src/react/Panel.js';
+import Checkbox from '../../src/react/Checkbox.js';
+import HTMLBasic from '../../components/panel/basic-panel.html';
+let examples = {
+ Basic: {
+ jsx:
+ <Panel>
+ <h3>Panel</h3>
+ <Checkbox label='filter-item' />
+ <Checkbox checked label='filter-item-checked' />
+ </Panel>,
+ html: HTMLBasic
+ }
+};
+
+const PanelStory = () => (
+ <Examples examples={examples} />
+);
+
+export default PanelStory;
diff --git a/stories/react/PopupMenu.stories.js b/stories/react/PopupMenu.stories.js
new file mode 100644
index 0000000..9d94522
--- /dev/null
+++ b/stories/react/PopupMenu.stories.js
@@ -0,0 +1,37 @@
+
+import React from 'react';
+import Examples from './utils/Examples.js';
+import PopupMenu from '../../src/react/PopupMenu.js';
+import PopupMenuItem from '../../src/react/PopupMenuItem.js';
+import HTMLPopupMenu from '../../components/menu/popup-menu.html';
+import HTMLPopupMenuRelative from '../../components/menu/relative-popup-menu.html';
+
+let examples = {
+ 'Basic popup menu (static)': {
+ jsx: <PopupMenu onMenuItemClick={() => {}}>
+ <PopupMenuItem itemId='1' value='item 1 (selected)' selected/>
+ <PopupMenuItem itemId='2' value='item 2' disabled/>
+ <PopupMenu.Separator />
+ <PopupMenuItem itemId='3' value='item 3'/>
+ <PopupMenuItem itemId='4' value='custom action' onClick={function customCallback() {}}/>
+ </PopupMenu>,
+ html: HTMLPopupMenu
+ },
+ 'Basic popup menu (relative)': {
+ jsx: <div className='sdc-popup-menu'>
+ <PopupMenu onMenuItemClick={()=> {}} position={{x: 10, y: 10}} relative>
+ <PopupMenuItem itemId='1' value='item 1 (selected)' selected/>
+ <PopupMenuItem itemId='2' value='item 2' disabled/>
+ <PopupMenu.Separator />
+ <PopupMenuItem itemId='3' value='item 3' onClick={function customCallback() {}}/>
+ </PopupMenu>
+ </div>,
+ html: HTMLPopupMenuRelative
+ }
+};
+
+const PopupMenuReactComponent = () => (
+ <Examples examples={examples} />
+);
+
+export default PopupMenuReactComponent; \ No newline at end of file
diff --git a/stories/react/Radio.stories.js b/stories/react/Radio.stories.js
new file mode 100644
index 0000000..151f947
--- /dev/null
+++ b/stories/react/Radio.stories.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import Examples from './utils/Examples.js';
+
+import Radio from '../../src/react/Radio';
+import HTMLRadioChecked from '../../components/radio/radio-checked.html';
+import HTMLRadioUnchecked from '../../components/radio/radio-unchecked.html';
+import HTMLRadioDisabled from '../../components/radio/radio-disabled.html';
+import HTMLRadioDisabledChecked from '../../components/radio/radio-disabled-checked.html';
+
+let examples = {
+ Checked: {
+ jsx: <Radio name='grp1' checked={true} label='This is the radio label' value='myVal' onChange={()=>{}} data-test-id='myradio-1' inputRef={() => {} } />,
+ html: HTMLRadioChecked
+ },
+ Unchecked: {
+ jsx: <Radio name='grp2' label='This is the radio label' value='myVal' onChange={()=>{}} data-test-id='myradio-2' inputRef={() => {} } />,
+ html: HTMLRadioUnchecked
+ },
+ Disabled: {
+ jsx: <Radio name='grp3' label='This is the radio label' disabled={true} value='myVal' onChange={()=>{}} data-test-id='myradio-4' inputRef={() => {} } />,
+ html: HTMLRadioDisabled
+ },
+ 'Disabled and Checked': {
+ jsx: <Radio name='grp4' label='This is the radio label' disabled={true} checked={true} value='myVal' onChange={()=>{}} data-test-id='myradio-4' inputRef={() => {} } />,
+ html: HTMLRadioDisabledChecked
+ }
+};
+
+const Radios = () => (
+ <Examples examples={examples} />
+);
+
+export default Radios;
diff --git a/stories/react/RadioGroup.stories.js b/stories/react/RadioGroup.stories.js
new file mode 100644
index 0000000..912f9b9
--- /dev/null
+++ b/stories/react/RadioGroup.stories.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import Examples from './utils/Examples.js';
+
+import RadioGroup from '../../src/react/RadioGroup';
+import HTMLRadioGroup from '../../components/radioGroup/radio-group.html';
+import HTMLRadioGroupValue from '../../components/radioGroup/radio-group-value.html';
+import HTMLRadioGroupDisabled from '../../components/radioGroup/radio-group-disabled.html';
+import HTMLRadioGroupNoTitle from '../../components/radioGroup/radio-group-no-title.html';
+
+let examples = {
+ 'Value': {
+ jsx: <RadioGroup name='grp2' value='1' title='Group B' onChange={()=>{}} data-test-id='grp2' options={[{value: '1', label: 'option 1'}, {value: '2', label: 'option 2'}]} />,
+ html: HTMLRadioGroupValue
+ },
+ 'No Value': {
+ jsx: <RadioGroup name='grp3' title='Group C' onChange={()=>{}} data-test-id='grp3' options={[{value: '1', label: 'option 1'}, {value: '2', label: 'option 2'}]} />,
+ html: HTMLRadioGroup
+ },
+ 'Disabled': {
+ jsx: <RadioGroup name='grp4' disabled={true} title='Group D' onChange={()=>{}} data-test-id='grp4' options={[{value: '1', label: 'option 1'}, {value: '2', label: 'option 2'}]} />,
+ html: HTMLRadioGroupDisabled
+ },
+ 'No title': {
+ jsx: <RadioGroup name='grp5' onChange={()=>{}} data-test-id='grp4' options={[{value: '1', label: 'option 1'}, {value: '2', label: 'option 2'}]} />,
+ html: HTMLRadioGroupNoTitle
+ }
+
+};
+
+const RadioGroups = () => (
+ <Examples examples={examples} />
+);
+
+export default RadioGroups;
diff --git a/stories/react/SVGIcon.stories.js b/stories/react/SVGIcon.stories.js
new file mode 100644
index 0000000..2c2ffc2
--- /dev/null
+++ b/stories/react/SVGIcon.stories.js
@@ -0,0 +1,103 @@
+import React from 'react';
+import Examples from './utils/Examples.js';
+import DropdownMenu from './utils/components/DropdownMenu.js';
+import SVGIcon from '../../src/react/SVGIcon.js';
+
+const iconLabelPositions = [
+ '', 'bottom', 'top', 'left', 'right'
+];
+
+const iconColors = [
+ '',
+ 'primary',
+ 'secondary',
+ 'positive',
+ 'negative',
+ 'warning'
+];
+
+const disabledStates = ['false', 'true'];
+
+function buildExamples({iconName, iconLabel, labelPosition, color, disabled}) {
+ return {
+ Example: {
+ jsx: <SVGIcon
+ label={iconLabel}
+ labelPosition={labelPosition}
+ color={color}
+ name={iconName}
+ disabled={disabled === 'true'} />
+ }
+ };
+}
+
+const IconTable = ({onClick}) => (
+ <div className='icons-table'>
+ {ICON_NAMES.map(icon => (
+ <div key={icon} className='icon-section'>
+ <SVGIcon
+ onClick={() => onClick(icon)}
+ label={icon}
+ iconClassName='storybook-small'
+ name={icon} />
+ </div>
+ ))}
+ </div>
+);
+
+class Icons extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ iconName: ICON_NAMES[0],
+ iconLabel: '',
+ labelPosition: iconLabelPositions[0],
+ color : iconColors[0]
+ };
+ }
+
+ render() {
+ let {iconName, iconLabel, labelPosition, color, disabled} = this.state;
+ return (
+ <div className='icons-screen'>
+ <h1>Icons</h1>
+ <div className='icons-option-selector'>
+ <DropdownMenu
+ title='Icon name'
+ value={iconName}
+ onChange={e => this.setState({iconName: e.target.value})}
+ options={ICON_NAMES} />
+ <div className='option-container'>
+ <label>Icon label</label>
+ <input value={iconLabel} onChange={e => this.setState({iconLabel: e.target.value})}/>
+ </div>
+ <DropdownMenu
+ title='Label position'
+ value={labelPosition}
+ onChange={e => this.setState({labelPosition: e.target.value})}
+ options={iconLabelPositions} />
+ <DropdownMenu
+ title='Color'
+ value={color}
+ onChange={e => this.setState({color: e.target.value})}
+ options={iconColors} />
+ <DropdownMenu
+ title='Disabled'
+ value={disabled}
+ onChange={e => this.setState({disabled: e.target.value})}
+ options={disabledStates} />
+ </div>
+ <Examples examples={buildExamples({iconName, iconLabel, labelPosition, color, disabled})} />
+ <IconTable onClick={icon => this.setState({iconName: icon})} />
+ <div className='missing-icon-section'>
+ <div >You will see the following if the icon name you used is not found:</div>
+ <SVGIcon
+ onClick={() => {}}
+ name='MissingIcon' />
+ </div>
+ </div>
+ );
+ };
+}
+
+export default Icons;
diff --git a/stories/react/Tabs.stories.js b/stories/react/Tabs.stories.js
new file mode 100644
index 0000000..74f163c
--- /dev/null
+++ b/stories/react/Tabs.stories.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import Examples from './utils/Examples.js';
+import {default as TabsComp} from '../../src/react/Tabs.js';
+import Tab from '../../src/react/Tab.js';
+import HTMLTabsHeader from '../../components/tabs/tabs-header.html';
+import HTMLTabsDisabled from '../../components/tabs/tabs-disabled.html';
+import HTMLTabsMenu from '../../components/tabs/tabs-menu.html';
+
+let examples = {
+ 'Menu Tabs': {
+ jsx: <TabsComp type='menu' activeTab='1' onTabClick={(tabId) => {console.log(tabId);}}>
+ <Tab title='tab 1' tabId='1'>
+ <div>This is the active tab content</div>
+ </Tab>
+ <Tab title='tab 2' tabId='2' />
+ <Tab title='tab 3' tabId='3' />
+ </TabsComp>,
+ html: HTMLTabsMenu
+ },
+ 'Header Tabs': {
+ jsx: <TabsComp type='header' activeTab='1' onTabClick={(tabId) => {console.log(tabId);}}>
+ <Tab title='tab 1' tabId='1'>
+ <div>This is the active tab content</div>
+ </Tab>
+ <Tab title='tab 2' tabId='2' />
+ <Tab title='tab 3' tabId='3' />
+ </TabsComp>,
+ html: HTMLTabsHeader
+ },
+ 'Disabled Tabs': {
+ jsx: (
+ <TabsComp type='header' activeTab='1' onTabClick={(tabId) => {console.log(tabId);}}>
+ <Tab title='tab 1' tabId='1'>
+ <div>This is the active tab content</div>
+ </Tab>
+ <Tab title='tab 2' tabId='2' disabled/>
+ <Tab title='tab 3' tabId='3' disabled/>
+ </TabsComp>
+ ),
+ html: HTMLTabsDisabled
+ }
+};
+
+const Tabs = () => (
+ <Examples examples={examples} />
+);
+
+export default Tabs;
diff --git a/stories/react/Tiles.stories.js b/stories/react/Tiles.stories.js
new file mode 100644
index 0000000..04a6fb5
--- /dev/null
+++ b/stories/react/Tiles.stories.js
@@ -0,0 +1,89 @@
+import React from 'react';
+
+import Examples from './utils/Examples.js';
+import SVGIcon from '../../src/react/SVGIcon.js';
+import Button from '../../src/react/Button.js';
+
+import Tile from '../../src/react/Tile.js';
+import TileInfo from '../../src/react/TileInfo.js';
+import TileInfoLine from '../../src/react/TileInfoLine.js';
+import TileFooter from '../../src/react/TileFooter.js';
+import TileFooterCell from '../../src/react/TileFooterCell.js';
+
+import HTMLTileWithoutFooter from '../../components/tile/tile-without-footer.html';
+import HTMLVspTile from '../../components/tile/vsp-tile.html';
+import HTMLVlmTile from '../../components/tile/vlm-tile.html';
+import HTMLVendorTile from '../../components/tile/vendor-tile.html';
+import HTMLVfcTile from '../../components/tile/vfc-tile.html';
+
+let examples = {
+ 'Without footer': {
+ jsx: <Tile headerText='header' headerColor='blue' iconName='vsp' iconColor='blue'>
+ <TileInfo>
+ <TileInfoLine type='supertitle'>Supertitle</TileInfoLine>
+ <TileInfoLine type='title'>Title</TileInfoLine>
+ </TileInfo>
+ </Tile>,
+ html: HTMLTileWithoutFooter
+ },
+ VFC: {
+ jsx: <Tile headerText='vfc' headerColor='purple' iconName='network'>
+ <TileInfo>
+ <TileInfoLine type='title'>Title</TileInfoLine>
+ <TileInfoLine type='subtitle'>V 1.0</TileInfoLine>
+ </TileInfo>
+ <TileFooter>
+ <TileFooterCell>Certified</TileFooterCell>
+ </TileFooter>
+ </Tile>,
+ html: HTMLVfcTile
+ },
+ VSP: {
+ jsx: <Tile headerText='vsp' headerColor='blue' iconName='vsp' iconColor='blue'>
+ <TileInfo>
+ <TileInfoLine type='supertitle'>VLM</TileInfoLine>
+ <TileInfoLine type='title'>VSP name</TileInfoLine>
+ </TileInfo>
+ <TileFooter>
+ <TileFooterCell>Draft</TileFooterCell>
+ </TileFooter>
+ </Tile>,
+ html: HTMLVspTile
+ },
+ VLM: {
+ jsx: <Tile headerText='vlm' headerColor='purple' iconName='vlm' iconColor='purple'>
+ <TileInfo>
+ <TileInfoLine type='title'>VLM name</TileInfoLine>
+ </TileInfo>
+ <TileFooter>
+ <TileFooterCell>Certified</TileFooterCell>
+ <TileFooterCell>
+ <SVGIcon name='versionControllerPermissions' label='Owner' labelPosition='left' />
+ </TileFooterCell>
+ </TileFooter>
+ </Tile>,
+ html: HTMLVlmTile
+ },
+ Vendor: {
+ jsx: <Tile iconName='vendor' iconColor='dark-gray'>
+ <TileInfo align='center'>
+ <TileInfoLine type='title'>Vendor name</TileInfoLine>
+ <TileInfoLine>
+ <Button btnType='primary' onClick={() => {}}>100 VSPs</Button>
+ </TileInfoLine>
+ </TileInfo>
+ <TileFooter align='center'>
+ <TileFooterCell>
+ <Button btnType='link' color='primary' iconName='plusThin' onClick={() => {}}>Create new VSP</Button>
+ </TileFooterCell>
+ </TileFooter>
+ </Tile>,
+ html: HTMLVendorTile
+ },
+};
+
+const Tiles = () => (
+ <Examples examples={examples} />
+);
+
+export default Tiles;
diff --git a/stories/react/Typography.stories.js b/stories/react/Typography.stories.js
new file mode 100644
index 0000000..f1475c6
--- /dev/null
+++ b/stories/react/Typography.stories.js
@@ -0,0 +1,62 @@
+import React, {Component} from 'react';
+
+const typos = [
+ {className: 'heading-1', size: 28, text: 'Major Section Heading'},
+ {className: 'heading-2', size: 24, text: 'Sub-Section Heading'},
+ {className: 'heading-3', size: 20, text: 'Small Heading'},
+ {className: 'heading-4', size: 16, text: 'Small Heading'},
+ {className: 'heading-4-emphasis', size: 16, text: 'Small Heading'},
+ {className: 'heading-5', size: 14, text: 'Small Heading'},
+ {className: 'body-1', size: 14, text: 'Body (Standard) Text'},
+ {className: 'body-1-italic', size: 14, text: 'Body (Standard) Text'},
+ {className: 'body-2', size: 13, text: 'Text in Tables'},
+ {className: 'body-2-emphasis', size: 13, text: 'Text in Tables'},
+ {className: 'body-3', size: 12, text: 'Input Labels, Table Titles'},
+ {className: 'body-3-emphasis', size: 12, text: 'Even Smaller Text'},
+ {className: 'body-4', size: 10, text: 'Even Much Smaller Text'}
+];
+
+const fontWeights = ['OpenSans Regular 400', 'OpenSans Semibold 600'];
+
+function TextRow({className, size, text}) {
+ return (
+ <div className={`typo-section ${className}`}>
+ <div>{className}</div>
+ <div>{size}px</div>
+ <div className='sample-text'>{text}</div>
+ </div>
+ );
+}
+
+class Typography extends Component {
+
+ render() {
+ return (
+ <div className='typography-screen'>
+ <h1>Typography</h1>
+ <div className='typography-section'>
+ <h3>Font Family</h3>
+ <ul>
+ <li>OpenSans</li>
+ <li style={{'fontFamily': 'Arial'}}>Arial</li>
+ <li style={{'fontFamily':'sans-serif'}}>sans-serif</li>
+ </ul>
+ </div>
+ <div className='typography-section'>
+ <h3>Font Weights</h3>
+ <ul>{fontWeights.map(font => <li key={font} className={font}>{font}</li>)}</ul>
+ </div>
+ <div className='typography-section'>
+ <h3>Font Size</h3>
+ <div className='typo-table'>
+ <TextRow className='SCSS mixin name (@include ....)' size='Size (in Pixels)' text='Sample Text'/>
+ {typos.map(typo => <TextRow key={typo.className} {...typo}/>)}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+export default Typography;
diff --git a/stories/react/buttons/LinkButtons.stories.js b/stories/react/buttons/LinkButtons.stories.js
new file mode 100644
index 0000000..ef32a22
--- /dev/null
+++ b/stories/react/buttons/LinkButtons.stories.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import Examples from '../utils/Examples.js';
+
+import ReactButton from '../../../src/react/Button.js';
+
+import LinkButton from '../../../components/button/button-link.html';
+import LinkButtonDisabled from '../../../components/button/button-link-disabled.html';
+import ExtraSmall from '../../../components/button/button-link-extra-small.html';
+import Small from '../../../components/button/button-link-small.html';
+import Medium from '../../../components/button/button-link-medium.html';
+import Large from '../../../components/button/button-link-large.html';
+import Auto from '../../../components/button/button-link-auto.html';
+
+let examples = {
+ 'Link Default': {
+ jsx: <ReactButton btnType='link' onClick={() => {}}>Click Me</ReactButton>,
+ html: LinkButton
+ },
+ 'Link Disabled': {
+ jsx: <ReactButton btnType='link' onClick={() => {}} disabled>Click Me</ReactButton>,
+ html: LinkButtonDisabled,
+ },
+ 'Extra Small': {
+ jsx: <ReactButton btnType='link' size='x-small' onClick={() => {}}>Click Me</ReactButton>,
+ html: ExtraSmall
+ },
+ 'Small': {
+ jsx: <ReactButton btnType='link' size='small' onClick={() => {}}>Click Me</ReactButton>,
+ html: Small,
+ },
+ 'Medium': {
+ jsx: <ReactButton btnType='link' size='medium' onClick={() => {}}>Click Me</ReactButton>,
+ html: Medium
+ },
+ 'Large': {
+ jsx: <ReactButton btnType='link' size='large' onClick={() => {}}>Click Me</ReactButton>,
+ html: Large,
+ },
+ 'Auto Sizing': {
+ jsx: <ReactButton btnType='link' size='default' onClick={() => {}}>Click Me</ReactButton>,
+ html: Auto,
+ }
+};
+
+const DefaultButtons = () => (
+ <Examples examples={examples} />
+);
+
+export default DefaultButtons;
diff --git a/stories/react/buttons/PrimaryButtons.stories.js b/stories/react/buttons/PrimaryButtons.stories.js
new file mode 100644
index 0000000..db732b9
--- /dev/null
+++ b/stories/react/buttons/PrimaryButtons.stories.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import Examples from '../utils/Examples.js';
+
+import ReactButton from '../../../src/react/Button.js';
+
+import PrimaryButton from '../../../components/button/button-primary.html';
+import PrimaryButtonDisabled from '../../../components/button/button-primary-disabled.html';
+import ExtraSmall from '../../../components/button/button-primary-extra-small.html';
+import Small from '../../../components/button/button-primary-small.html';
+import Medium from '../../../components/button/button-primary-medium.html';
+import Large from '../../../components/button/button-primary-large.html';
+import Auto from '../../../components/button/button-primary-auto.html';
+
+let examples = {
+ 'Primary Default': {
+ jsx: <ReactButton onClick={() => {}}>Click Me</ReactButton>,
+ html: PrimaryButton
+ },
+ 'Primary Disabled': {
+ jsx: <ReactButton onClick={() => {}} disabled>Click Me</ReactButton>,
+ html: PrimaryButtonDisabled,
+ },
+ 'Extra Small': {
+ jsx: <ReactButton size='x-small' onClick={() => {}}>Click Me</ReactButton>,
+ html: ExtraSmall
+ },
+ 'Small': {
+ jsx: <ReactButton size='small' onClick={() => {}}>Click Me</ReactButton>,
+ html: Small,
+ },
+ 'Medium': {
+ jsx: <ReactButton size='medium' onClick={() => {}}>Click Me</ReactButton>,
+ html: Medium
+ },
+ 'Large': {
+ jsx: <ReactButton size='large' onClick={() => {}}>Click Me</ReactButton>,
+ html: Large,
+ },
+ 'Auto Sizing': {
+ jsx: <ReactButton size='default' onClick={() => {}}>Click Me</ReactButton>,
+ html: Auto,
+ }
+};
+
+const DefaultButtons = () => (
+ <Examples examples={examples} />
+);
+
+export default DefaultButtons;
diff --git a/stories/react/buttons/SecondaryButtons.stories.js b/stories/react/buttons/SecondaryButtons.stories.js
new file mode 100644
index 0000000..75f9d54
--- /dev/null
+++ b/stories/react/buttons/SecondaryButtons.stories.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import Examples from '../utils/Examples.js';
+
+import ReactButton from '../../../src/react/Button.js';
+
+import SecondaryButton from '../../../components/button/button-secondary.html';
+import SecondaryButtonDisabled from '../../../components/button/button-secondary-disabled.html';
+import ExtraSmall from '../../../components/button/button-secondary-extra-small.html';
+import Small from '../../../components/button/button-secondary-small.html';
+import Medium from '../../../components/button/button-secondary-medium.html';
+import Large from '../../../components/button/button-secondary-large.html';
+import Auto from '../../../components/button/button-secondary-auto.html';
+
+let examples = {
+ 'Secondary Default': {
+ jsx: <ReactButton btnType='secondary' onClick={() => {}}>Click Me</ReactButton>,
+ html: SecondaryButton
+ },
+ 'Secondary Disabled': {
+ jsx: <ReactButton btnType='secondary' onClick={() => {}} disabled>Click Me</ReactButton>,
+ html: SecondaryButtonDisabled,
+ },
+ 'Extra Small': {
+ jsx: <ReactButton btnType='secondary' size='x-small' onClick={() => {}}>Click Me</ReactButton>,
+ html: ExtraSmall
+ },
+ 'Small': {
+ jsx: <ReactButton btnType='secondary' size='small' onClick={() => {}}>Click Me</ReactButton>,
+ html: Small,
+ },
+ 'Medium': {
+ jsx: <ReactButton btnType='secondary' size='medium' onClick={() => {}}>Click Me</ReactButton>,
+ html: Medium
+ },
+ 'Large': {
+ jsx: <ReactButton btnType='secondary' size='large' onClick={() => {}}>Click Me</ReactButton>,
+ html: Large,
+ },
+ 'Auto Sizing': {
+ jsx: <ReactButton btnType='secondary' size='default' onClick={() => {}}>Click Me</ReactButton>,
+ html: Auto,
+ }
+};
+
+const DefaultButtons = () => (
+ <Examples examples={examples} />
+);
+
+export default DefaultButtons;
diff --git a/stories/react/index.js b/stories/react/index.js
new file mode 100644
index 0000000..6d425ba
--- /dev/null
+++ b/stories/react/index.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+
+import PrimaryButtons from './buttons/PrimaryButtons.stories.js';
+import SecondaryButtons from './buttons/SecondaryButtons.stories.js';
+import LinkButtons from './buttons/LinkButtons.stories.js';
+
+import Colors from './Colors.stories.js';
+import Typography from './Typography.stories.js';
+import Checkboxes from './Checkbox.stories.js';
+import Checklist from './Checklist.stories.js';
+import Input from './Input.stories.js';
+import Icons from './SVGIcon.stories.js';
+import Tiles from './Tiles.stories.js';
+import Tabs from './Tabs.stories.js';
+import Radios from './Radio.stories.js';
+import RadioGroups from './RadioGroup.stories.js';
+import Modals from './Modal.stories.js';
+import PopupMenu from './PopupMenu.stories.js';
+import Accordion from './Accordion.stories.js';
+import Panel from './Panel.stories.js';
+
+storiesOf('Colors', module)
+ .add('Color Palette', () => <Colors />);
+
+storiesOf('Typography', module)
+ .add('Typography', () => <Typography />);
+
+storiesOf('Accordion', module)
+ .add('Accordion', () => <Accordion />);
+
+storiesOf('Buttons', module)
+ .add('Primary', () => <PrimaryButtons />)
+ .add('Secondary', () => <SecondaryButtons />)
+ .add('Link', () => <LinkButtons />);
+
+storiesOf('Checkboxes', module)
+ .add('Checkboxes', () => <Checkboxes />);
+
+storiesOf('Checklist', module)
+ .add('Checklist', () => <Checklist />);
+
+storiesOf('Input Fields', module)
+ .add('Input Text', () => <Input />);
+
+storiesOf('Icons', module)
+ .add('SVG Icons', () => <Icons />);
+
+storiesOf('Menu', module)
+ .add('Popup Menu', () => <PopupMenu />);
+
+storiesOf('Modals', module)
+ .add('Modal examples', () => <Modals />);
+
+storiesOf('Radios', module)
+ .add('Radio Buttons', () => <Radios />)
+ .add('Radio Button Groups', () => <RadioGroups />);
+
+storiesOf('Panel', module)
+ .add('Panel', () => <Panel />);
+
+storiesOf('Tabs', module)
+ .add('Tabs', () => <Tabs />);
+
+storiesOf('Tiles', module)
+ .add('Tiles', () => <Tiles />);
diff --git a/stories/react/utils/BeautifyHTML.js b/stories/react/utils/BeautifyHTML.js
new file mode 100644
index 0000000..1a29b00
--- /dev/null
+++ b/stories/react/utils/BeautifyHTML.js
@@ -0,0 +1,33 @@
+export default function beautifyHTML({html, indentChar = ' ', startingIndentCount = 0}) {
+ html = html.replace(/[ ]{2,}/g, ' ');
+
+ let result = '', indentCount = startingIndentCount, parsingText = false;
+ for (let i = 0; i < html.length; i++) {
+
+ let startOfTag, endOfTag, closingTag, upcomingTag, afterTag, numTabs;
+ if (html[i] === '<') { startOfTag = true; }
+ else if (html[i] === '>') { endOfTag = true; }
+ else if (html[i - 1] === '>') { afterTag = true; }
+ if (html[i + 1] === '/') { closingTag = true; }
+ else if (html[i + 1 ] === '<') { upcomingTag = true; }
+
+ if (startOfTag) {
+ if (closingTag) { numTabs = --indentCount; }
+ else { numTabs = indentCount++; }
+ }
+
+ if (parsingText && afterTag) {
+ numTabs = indentCount;
+ }
+
+ result += indentChar.repeat(numTabs) + html[i];
+
+ if (endOfTag || parsingText && upcomingTag) {
+ result += '\n';
+ parsingText = false;
+ if (!upcomingTag) { parsingText = true; }
+ }
+ }
+
+ return result.slice(0, -1);
+}
diff --git a/stories/react/utils/Examples.js b/stories/react/utils/Examples.js
new file mode 100644
index 0000000..5948b68
--- /dev/null
+++ b/stories/react/utils/Examples.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import {renderToStaticMarkup} from 'react-dom/server';
+import SourceToggle from './SourceToggle.js';
+import beautifyHTML from './BeautifyHTML.js';
+import insertSVGIcons from './InsertSVGIcons.js';
+
+const Examples = ({examples}) => (
+ <div className={'examples'}>
+ {Object.keys(examples).map(key => {
+ let title = key;
+ let {jsx, html, displayTitle = true, exclude, renderFromJsx = false} = examples[key];
+ if (!html) {
+ html = renderToStaticMarkup(jsx);
+ html = beautifyHTML({html, indentChar: ' '});
+ } else {
+ html = insertSVGIcons({html, jsx});
+ }
+ return <SourceToggle title={displayTitle && title} jsx={jsx} html={html} key={key} exclude={exclude} renderFromJsx={renderFromJsx}/>;
+ })}
+ </div>
+);
+
+export default Examples;
diff --git a/stories/react/utils/InsertSVGIcons.js b/stories/react/utils/InsertSVGIcons.js
new file mode 100644
index 0000000..5a5e390
--- /dev/null
+++ b/stories/react/utils/InsertSVGIcons.js
@@ -0,0 +1,15 @@
+import {renderToStaticMarkup} from 'react-dom/server';
+import beautifyHTML from './BeautifyHTML.js';
+
+const insertSVGIcons = ({html, jsx, indentChar = ' '}) => {
+ let svgCode = renderToStaticMarkup(jsx).match(/(<svg\b[^<>]*>)[\s\S]*?(<\/svg>)/g);
+ let newHTML = html.replace(/\s*<!-- insert SVG -->/g, str => {
+ let html = '\n' + svgCode.shift();
+ let indentRegExp = new RegExp(`[${indentChar}]*`);
+ let startingIndentCount = str.slice(2).match(indentRegExp)[0].length / indentChar.length;
+ return beautifyHTML({html, startingIndentCount, indentChar});
+ });
+ return newHTML;
+};
+
+export default insertSVGIcons;
diff --git a/stories/react/utils/SourceToggle.js b/stories/react/utils/SourceToggle.js
new file mode 100644
index 0000000..a05c8d0
--- /dev/null
+++ b/stories/react/utils/SourceToggle.js
@@ -0,0 +1,73 @@
+/* eslint-disable react/no-danger */
+import React from 'react';
+import jsxToString from './jsxToString.js';
+
+import Prism from 'prismjs';
+
+import PrismJsx from 'prismjs/components/prism-jsx.js'; // eslint-disable-line no-unused-vars
+
+const sources = {
+ React: 'React',
+ HTML: 'HTML'
+};
+
+export default class SourceToggle extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ source: sources.React
+ };
+ }
+
+ renderFromSource() {
+ let {jsx, html, renderFromJsx} = this.props;
+ let {source} = this.state;
+ let classname = 'source-toggle-example';
+ switch (source) {
+ case sources.HTML:
+ return renderFromJsx ? <div className={classname}>{jsx}</div> : <div className={classname} dangerouslySetInnerHTML={{__html: html}} />;
+ case sources.React:
+ default:
+ return <div className={classname}>{jsx}</div>;
+ }
+ }
+
+ renderMarkdown() {
+ let {jsx, html, exclude} = this.props;
+ let {source} = this.state;
+ switch (source) {
+ case sources.HTML:
+ return {__html: Prism.highlight(html, Prism.languages.html)};
+ case sources.React:
+ default:
+ return {__html: Prism.highlight(jsxToString({jsx, exclude}), Prism.languages.jsx)};
+ }
+ }
+
+ render() {
+ let {title} = this.props;
+ return (
+ <div className='source-toggle-wrapper'>
+ {title && <div className='source-toggle-title'>{title}</div>}
+ <div className='source-toggle'>
+ {this.renderFromSource()}
+ <div className='source-toggle-code'>
+ <div className='source-toggle-code-tabs'>
+ {Object.keys(sources).map((source, i) => (
+ <div
+ key={i}
+ className={`source-toggle-tab${this.state.source === source ? ' selected' : ''}`}
+ onClick={() => this.setState({source})}>
+ {source}
+ </div>
+ ))}
+ </div>
+ <pre>
+ <code dangerouslySetInnerHTML={this.renderMarkdown()} />
+ </pre>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/stories/react/utils/components/DropdownMenu.js b/stories/react/utils/components/DropdownMenu.js
new file mode 100644
index 0000000..4a69463
--- /dev/null
+++ b/stories/react/utils/components/DropdownMenu.js
@@ -0,0 +1,14 @@
+import React from 'react';
+
+const DropdownMenu = ({title, value, onChange, options}) => (
+ <div className='option-container'>
+ <label>{title}</label>
+ <select value={value} onChange={onChange}>
+ {options.map((option, i) =>
+ <option key={i} value={option}>{option}</option>
+ )}
+ </select>
+ </div>
+);
+
+export default DropdownMenu;
diff --git a/stories/react/utils/jsxToString.js b/stories/react/utils/jsxToString.js
new file mode 100644
index 0000000..8b799ad
--- /dev/null
+++ b/stories/react/utils/jsxToString.js
@@ -0,0 +1,74 @@
+import React, {Children} from 'react';
+
+const INDENT = ' ';
+
+function stringRepresentationForJsx(item) {
+ if (typeof item === 'string') {
+ return `'${item}'`;
+ } else if (typeof item === 'number') {
+ return item.toString();
+ } else if (Array.isArray(item)) {
+ return `[${item.map(val => stringRepresentationForJsx(val)).toString()}]`;
+ }
+ else if (typeof item === 'boolean') {
+ return item.toString();
+ }
+ else if (typeof item === 'function') {
+ return item.toString().replace(/\s{2,}/g, ' ');
+ } else if (typeof item === 'object') {
+ let repr = '{';
+ for (let key in item) {
+ if (item.hasOwnProperty(key)) {
+ repr += `${key}: ${stringRepresentationForJsx(item[key])}, `;
+ }
+ }
+ repr = repr.slice(0, -2);
+ repr += '}';
+ return repr;
+ }
+}
+
+function parseProps(jsx, indentCount) {
+ let result = '';
+ for (let prop in jsx.props) {
+ let value = jsx.props[prop];
+ if (prop !== 'children' && value) {
+ let repr = stringRepresentationForJsx(value);
+ let isString = repr.startsWith("'");
+ result += `\n${INDENT.repeat(indentCount)}${prop}`;
+ if (value !== true) {
+ result += `=${isString ? '' : '{ '}${stringRepresentationForJsx(value)}${isString ? '' : ' }'}`;
+ }
+ }
+ }
+ return result;
+}
+
+function jsxToString({jsx, indentCount=0, exclude}) {
+ if (typeof jsx === 'string'){
+ return jsx;
+ }
+
+ let name = typeof jsx.type === 'string' ? jsx.type : jsx.type.name;
+ let result = name === exclude ? ''
+ : `${INDENT.repeat(indentCount)}<${name}${parseProps(jsx, indentCount + 1)}`;
+
+ if (jsx.props.hasOwnProperty('children')) {
+ let {children} = jsx.props;
+ let childrenArr = Children.toArray(children);
+ if (name !== exclude) { result += '>\n';}
+ if (typeof children === 'string') {
+ result += `${INDENT.repeat(indentCount + 1)}${children}\n`;
+ } else {
+ let newIndentCount = name === exclude ? indentCount : indentCount + 1;
+ childrenArr.forEach(child => result += `${jsxToString({jsx: child, indentCount: newIndentCount})}\n`);
+ }
+ const closingTag = name === exclude ? ''
+ : `${INDENT.repeat(indentCount)}</${name}>`;
+ return result + closingTag;
+ }
+
+ return result + ' />';
+}
+
+export default jsxToString;