diff options
Diffstat (limited to 'stories/react/utils')
-rw-r--r-- | stories/react/utils/BeautifyHTML.js | 33 | ||||
-rw-r--r-- | stories/react/utils/Examples.js | 23 | ||||
-rw-r--r-- | stories/react/utils/InsertSVGIcons.js | 15 | ||||
-rw-r--r-- | stories/react/utils/SourceToggle.js | 73 | ||||
-rw-r--r-- | stories/react/utils/components/DropdownMenu.js | 14 | ||||
-rw-r--r-- | stories/react/utils/jsxToString.js | 74 |
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; |