aboutsummaryrefslogtreecommitdiffstats
path: root/stories/react/utils
diff options
context:
space:
mode:
Diffstat (limited to 'stories/react/utils')
-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
6 files changed, 232 insertions, 0 deletions
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;