Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(examples and docs): adding react-live… #666

Merged
merged 3 commits into from
Sep 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you able to do this on gatsby node? It might speed up client-side rendering. Not a big deal if it is not possible though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, I will try it out

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had trouble getting the raw file contents in gatsby-node. Going to stick with this for now.

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, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Internally it looks like React-Live uses (buble)[https://buble.surge.sh/guide/#jsx] which supports jsx. I did not try this without babel so it might actually be needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I had more issues with buble so I switched to babel

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