Skip to content

Commit

Permalink
fix(examples and docs): adding react-live… (patternfly#666)
Browse files Browse the repository at this point in the history
* fix(@patternfly/react-core @patternfly/react-docs): adding react-live for the docs examples

affects: patternfly4-react-lerna-root, @patternfly/react-core, @patternfly/react-docs

* flag to enable/disable
  • Loading branch information
jschuler authored and amarie401 committed Sep 27, 2018
1 parent d78e2e7 commit da3d4b6
Show file tree
Hide file tree
Showing 14 changed files with 232 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { Button } from '@patternfly/react-core';
import getContainerProps from './common/getContainerProps';

class LinkButtons extends React.Component {
class LinkButton extends React.Component {
static title = 'Links';
static description = `Links with button styling. Semantic buttons and links are important for usability as well as accessibility. Using an "a" instead of a "button" element to perform user initiated actions should be avoided, unless absolutely necessary.`;
static getContainerProps = getContainerProps;
Expand All @@ -21,4 +21,4 @@ class LinkButtons extends React.Component {
}
}

export default LinkButtons;
export default LinkButton;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { Dropdown, DropdownToggle, DropdownItem, DropdownSeparator, DropdownDirection } from '@patternfly/react-core';

export default class ExampleDropdown extends Component {
export default class DirectionUpDropdown extends Component {
static title = 'Dropdown - direction up';

constructor(props) {
Expand All @@ -13,14 +13,12 @@ export default class ExampleDropdown extends Component {

onToggle = isOpen => {
this.setState({
...this.state,
isOpen
});
};

onSelect = event => {
this.setState({
...this.state,
isOpen: !this.state.isOpen
});
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { Dropdown, KebabToggle, DropdownItem, DropdownSeparator } from '@patternfly/react-core';

export default class ExampleDropdown extends Component {
export default class KebabDropdown extends Component {
static title = 'Kebab';

constructor(props) {
Expand All @@ -13,14 +13,12 @@ export default class ExampleDropdown extends Component {

onToggle = isOpen => {
this.setState({
...this.state,
isOpen
});
};

onSelect = event => {
this.setState({
...this.state,
isOpen: !this.state.isOpen
});
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { Dropdown, DropdownToggle, DropdownItem, DropdownSeparator, DropdownPosition } from '@patternfly/react-core';

export default class ExampleDropdown extends Component {
export default class PositionRightDropdown extends Component {
static title = 'Dropdown - position right';

constructor(props) {
Expand All @@ -13,14 +13,12 @@ export default class ExampleDropdown extends Component {

onToggle = isOpen => {
this.setState({
...this.state,
isOpen
});
};

onSelect = event => {
this.setState({
...this.state,
isOpen: !this.state.isOpen
});
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { Dropdown, DropdownToggle, DropdownItem, DropdownSeparator } from '@patternfly/react-core';

export default class ExampleDropdown extends Component {
export default class SimpleDropdown extends Component {
static title = 'Simple dropdown';

constructor(props) {
Expand All @@ -13,14 +13,12 @@ export default class ExampleDropdown extends Component {

onToggle = isOpen => {
this.setState({
...this.state,
isOpen
});
};

onSelect = event => {
this.setState({
...this.state,
isOpen: !this.state.isOpen
});
};
Expand Down
1 change: 1 addition & 0 deletions packages/patternfly-4/react-docs/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
LIVE_EXAMPLES=true
1 change: 1 addition & 0 deletions packages/patternfly-4/react-docs/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
LIVE_EXAMPLES=true
35 changes: 33 additions & 2 deletions packages/patternfly-4/react-docs/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ exports.onCreateNode = ({ node, boundActionCreators }) => {

exports.createPages = async ({ boundActionCreators, graphql }) => {
const {
data: { docs, examples }
data: { docs, examples, exampleImages }
} = await graphql(`
fragment DocFile on File {
relativePath
relativeDirectory
absolutePath
base
name
Expand All @@ -78,17 +79,47 @@ exports.createPages = async ({ boundActionCreators, graphql }) => {
}
}
}
exampleImages: allFile(filter: { extension: { regex: "/(png|svg)/" } }) {
edges {
node {
...DocFile
}
}
}
}
`);
const docsComponentPath = path.resolve(__dirname, './src/components/componentDocs');
docs.edges.forEach(({ node: doc }) => {
const filePath = path.resolve(__dirname, '.tmp', doc.base);

const rawExamples = [];
examples.edges.forEach(({ node: example }) => {
if (
example.relativeDirectory
.split('/')
.slice(0, 2)
.join('/') === doc.relativeDirectory
) {
// e.g. components/Alert/examples/DangerAlert.js
const examplePath = `../../react-core/src/${example.relativePath}`;
rawExamples.push(`{name: '${example.name}', path: '${examplePath}', file: require('!!raw!${examplePath}')}`);
}
});
const allImages = [];
exampleImages.edges.forEach(({ node: image }) => {
const imagePath = `../../react-core/src/${image.relativePath}`;
allImages.push(`{name: '${image.base}', file: require('${imagePath}')}`);
});

const content = `
import React from 'react';
import docs from '${doc.absolutePath}';
import ComponentDocs from '${docsComponentPath}';
const rawExamples = [${rawExamples}];
const images = [${allImages}];
export default () => <ComponentDocs {...docs} />
export default () => <ComponentDocs rawExamples={rawExamples} images={images} {...docs} />
`;
fs.outputFileSync(filePath, content);
boundActionCreators.createPage({
Expand Down
5 changes: 3 additions & 2 deletions packages/patternfly-4/react-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@patternfly/react-icons": "*",
"@patternfly/react-styles": "^2.0.0",
"@patternfly/react-tokens": "^1.0.0",
"babel-plugin-react-docgen": "^v1.9.0",
"babel-standalone": "^6.26.0",
"emotion": "^9.2.9",
"emotion-server": "^9.2.9",
"gatsby": "^1.9.247",
Expand All @@ -27,7 +27,8 @@
"prop-types": "^15.6.1",
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-helmet": "^5.2.0"
"react-helmet": "^5.2.0",
"react-live": "^1.11.0"
},
"keywords": [
"gatsby"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,41 @@ const propTypes = {
description: PropTypes.string,
examples: PropTypes.arrayOf(PropTypes.func),
components: PropTypes.objectOf(PropTypes.func),
enumValues: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.any))
enumValues: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.any)),
rawExamples: PropTypes.array,
images: PropTypes.array
};

const defaultProps = {
description: '',
examples: [],
components: {},
enumValues: {}
enumValues: {},
rawExamples: [],
images: []
};

const ComponentDocs = ({ title, description, examples, components, enumValues }) => (
const ComponentDocs = ({ title, description, examples, components, enumValues, rawExamples, images }) => (
<Content>
<Title size="3xl">{title}</Title>
{Boolean(description) && <p className={css(styles.description)}>{description}</p>}
<Section title="Examples">
{examples.map((ComponentExample, i) => (
<Example
key={i}
title={ComponentExample.title}
description={ComponentExample.description}
{...(ComponentExample.getContainerProps ? ComponentExample.getContainerProps() : {})}
>
<ComponentExample />
</Example>
))}
{examples.map((ComponentExample, i) => {
const { __docgenInfo: componentDocs } = ComponentExample;
const rawExample = rawExamples.find(example => example.name === componentDocs.displayName);
return (
<Example
key={i}
raw={rawExample && rawExample.file}
images={images}
title={ComponentExample.title}
description={ComponentExample.description}
{...(ComponentExample.getContainerProps ? ComponentExample.getContainerProps() : {})}
>
<ComponentExample />
</Example>
);
})}
</Section>
{Object.entries(components).map(([componentName, { __docgenInfo: componentDocs }]) => (
<PropsTable key={componentName} name={componentName} props={componentDocs.props} enumValues={enumValues} />
Expand Down
25 changes: 18 additions & 7 deletions packages/patternfly-4/react-docs/src/components/example/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,37 @@ import { css } from '@patternfly/react-styles';
import styles from './example.styles';
import PropTypes from 'prop-types';
import { Title } from '@patternfly/react-core';
import LiveDemo from './liveDemo';

const propTypes = {
children: PropTypes.node.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string,
className: PropTypes.string
className: PropTypes.string,
raw: PropTypes.string,
images: PropTypes.array
};

const defaultProps = {
className: '',
description: ''
description: '',
raw: '',
images: []
};

const Example = ({ children, title, className, description, ...props }) => (
const LIVE_EXAMPLES = /true/i.test(process.env.LIVE_EXAMPLES);

const Example = ({ children, title, className, description, raw, images, ...props }) => (
<div>
<Title size="md">{title}</Title>
<Title size="lg">{title}</Title>
{Boolean(description) && <p className={css(styles.description)}>{description}</p>}
<div className={css(className, styles.example)} {...props}>
{children}
</div>
{LIVE_EXAMPLES ? (
<LiveDemo raw={raw.trim()} images={images} className={className} />
) : (
<div className={css(className, styles.example)} {...props}>
{children}
</div>
)}
</div>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { css } from '@patternfly/react-styles';
import styles from './example.styles';
import PropTypes from 'prop-types';
import * as CoreComponents from '@patternfly/react-core';
import * as CoreIcons from '@patternfly/react-icons';
import { LiveProvider, LiveEditor, LiveError, LivePreview, withLive } from 'react-live';
import { transform } from 'babel-standalone';

const propTypes = {
className: PropTypes.string,
raw: PropTypes.string.isRequired,
images: PropTypes.array
};

const defaultProps = {
className: '',
images: []
};

const scopePlayground = { React, ...CoreComponents, ...CoreIcons };

const transformCode = code => {
try {
// LiveEditor doesn't work properly with these so need to remove
code = code.replace(/^\s*import.*$/gm, '');
code = code.replace(/^\s*export default class/gm, 'class');
code = code.replace(/extends Component/gm, 'extends React.Component');
code = code.replace(/^\s*export.*$/gm, '');
code = code.replace(/^\s*static.*$/gm, '');
const transformedCode = transform(code, {
presets: ['react', 'stage-2']
}).code;
return transformedCode;
} catch (e) {
console.log(e);
// todo: handle error
return code;
}
};

const LiveDemo = ({ className, raw, images, ...props }) => {
const scope = {
...scopePlayground
};
for (const image of images) {
const searchIndex = raw.search(image.name);
if (searchIndex > -1) {
const startIndex = raw.lastIndexOf('import', searchIndex);
const importName = raw.substring(startIndex, searchIndex).split(' ')[1];
scope[importName] = image.file;
}
}

return (
<LiveProvider code={raw} scope={scope} transformCode={transformCode}>
<LivePreview className={css(className, styles.example)} />
<LiveEditor style={{ marginBottom: '30px' }} />
<LiveError />
</LiveProvider>
);
};

LiveDemo.propTypes = propTypes;
LiveDemo.defaultProps = defaultProps;

export default withLive(LiveDemo);
13 changes: 7 additions & 6 deletions packages/patternfly-4/react-docs/src/layouts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ const Layout = ({ children, data }) => {

return (
<React.Fragment>
<Helmet
meta={[
{ name: 'description', content: 'PatternFly React Documentation' },
{ name: 'keywords', content: 'React, PatternFly, Red Hat' }
]}
/>
<Helmet>
<meta charSet="utf-8" />
<meta name="description" content="PatternFly React Documentation" />
<meta name="keywords" content="React, PatternFly, Red Hat" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/codemirror.min.css" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/5.0.0/theme/monokai.min.css" />
</Helmet>
<Page
title="Patternfly React"
navigation={<Navigation componentRoutes={componentRoutes} layoutRoutes={layoutRoutes} />}
Expand Down
Loading

0 comments on commit da3d4b6

Please sign in to comment.