diff --git a/examples/styleguide/.eslintrc b/examples/styleguide/.eslintrc new file mode 100644 index 0000000000000..aadde9c0aa03d --- /dev/null +++ b/examples/styleguide/.eslintrc @@ -0,0 +1,8 @@ +{ + "env": { + "browser": true + }, + "globals": { + "graphql": false + } +} \ No newline at end of file diff --git a/examples/styleguide/.gitignore b/examples/styleguide/.gitignore new file mode 100644 index 0000000000000..8f5b35a4a9cbc --- /dev/null +++ b/examples/styleguide/.gitignore @@ -0,0 +1,3 @@ +public +.cache +node_modules diff --git a/examples/styleguide/README.md b/examples/styleguide/README.md new file mode 100644 index 0000000000000..b6e45464bf595 --- /dev/null +++ b/examples/styleguide/README.md @@ -0,0 +1,5 @@ +# Gatsby Styleguide Example + +> A living styleguide proof-of-concept built using Gatsby. Inspired by [react-styleguidist](https://react-styleguidist.js.org/). + +https://styleguide.gatsbyjs.org diff --git a/examples/styleguide/gatsby-config.js b/examples/styleguide/gatsby-config.js new file mode 100644 index 0000000000000..7c54550f788da --- /dev/null +++ b/examples/styleguide/gatsby-config.js @@ -0,0 +1,19 @@ +const path = require(`path`) + +module.exports = { + plugins: [ + { + resolve: `gatsby-source-filesystem`, + options: { + path: path.join(__dirname, `src/components`), + name: `components`, + }, + }, + { + resolve: `gatsby-transformer-react-docgen`, + }, + { + resolve: `gatsby-transformer-remark`, + }, + ], +} diff --git a/examples/styleguide/gatsby-node.js b/examples/styleguide/gatsby-node.js new file mode 100644 index 0000000000000..3d4402edc5d23 --- /dev/null +++ b/examples/styleguide/gatsby-node.js @@ -0,0 +1,115 @@ +const path = require(`path`) +const fs = require(`fs`) +const appRootDir = require(`app-root-dir`).get() + +const componentPageTemplate = path.resolve( + `src/templates/ComponentPage/index.js` +) +const tableOfContentsTemplate = path.resolve(`src/templates/TOC/index.js`) + +exports.createPages = ({ graphql, boundActionCreators }) => { + const { createPage } = boundActionCreators + + return new Promise((resolve, reject) => { + resolve( + Promise.all([ + graphql(` + { + allComponentMetadata { + edges { + node { + id + displayName + description { + text + } + props { + name + type { + value + raw + name + } + description { + text + } + required + } + } + } + } + } + `), + graphql(` + { + allMarkdownRemark( + filter: { fileAbsolutePath: { regex: "/README.md/" } } + ) { + edges { + node { + fileAbsolutePath + html + } + } + } + } + `), + ]) + .then(([docgenResult, markdownResult]) => { + const errors = docgenResult.errors || markdownResult.errors + if (errors) { + reject(new Error(errors)) + return + } + + const allComponents = docgenResult.data.allComponentMetadata.edges.map( + (edge, i) => + Object.assign({}, edge.node, { + path: `/components/${edge.node.displayName.toLowerCase()}/`, + html: markdownResult.data.allMarkdownRemark.edges[i].node.html, + }) + ) + + const exportFileContents = + allComponents + .reduce((accumulator, { id, displayName }) => { + const absolutePath = id.replace(/ absPath of.*$/, ``) + accumulator.push( + `export { default as ${displayName} } from "${absolutePath}"` + ) + return accumulator + }, []) + .join(`\n`) + `\n` + + fs.writeFileSync( + path.join(appRootDir, `.cache/components.js`), + exportFileContents + ) + + allComponents.forEach(data => { + const { path } = data + const context = Object.assign({}, data, { + allComponents, + }) + createPage({ + path, + component: componentPageTemplate, + context, + }) + }) + + createPage({ + path: `/components/`, + component: tableOfContentsTemplate, + context: { + allComponents, + }, + }) + }) + .catch(err => { + console.log(err) + throw new Error(err) + }) + ) + }) +} diff --git a/examples/styleguide/package.json b/examples/styleguide/package.json new file mode 100644 index 0000000000000..1f699aa446c98 --- /dev/null +++ b/examples/styleguide/package.json @@ -0,0 +1,23 @@ +{ + "name": "styleguide", + "private": true, + "description": "Gatsby example site demoing living-style-guide", + "author": "scott.eckenthal@gmail.com", + "dependencies": { + "app-root-dir": "^1.0.2", + "gatsby": "latest", + "gatsby-link": "latest", + "gatsby-source-filesystem": "^1.5.11", + "gatsby-transformer-react-docgen": "^1.0.11", + "gatsby-transformer-remark": "^1.7.25", + "glamor": "^2.20.40", + "html-to-react": "^1.3.1", + "react-live": "^1.7.1" + }, + "license": "MIT", + "main": "n/a", + "scripts": { + "develop": "gatsby develop", + "build": "gatsby build" + } +} diff --git a/examples/styleguide/src/components/Button/Button.js b/examples/styleguide/src/components/Button/Button.js new file mode 100644 index 0000000000000..05dccdaf4993f --- /dev/null +++ b/examples/styleguide/src/components/Button/Button.js @@ -0,0 +1,84 @@ +import React from "react" +import PropTypes from "prop-types" +import { css } from "glamor" + +const blue = `blue` +const orange = `orange` +const green = `green` + +const colors = { + [blue]: { + primary: `#A0CED9`, + hover: `#92BCC6`, + }, + [orange]: { + primary: `#EAB69B`, + hover: `#E8AF91`, + }, + [green]: { + primary: `#ADF7B6`, + hover: `#9EE1A6`, + }, +} + +const sm = `sm` +const md = `md` +const lg = `lg` + +const sizes = { + [sm]: { + fontSize: `14px`, + padding: `12px 20px`, + minWidth: `160px`, + }, + [md]: { + fontSize: `18px`, + padding: `16px 24px`, + minWidth: `200px`, + }, + [lg]: { + fontSize: `22px`, + padding: `20px 28px`, + minWidth: `260px`, + }, +} + +const styles = ({ backgroundColor, size }) => { + const backgroundColorConfig = + colors[backgroundColor] || colors[Button.defaultProps.backgroundColor] + const sizeConfig = sizes[size] || sizes[Button.defaultProps.size] + return css({ + backgroundColor: backgroundColorConfig.primary, + ...sizeConfig, + color: `rgba(36, 47, 60, 0.66)`, + display: `inline-block`, + borderRadius: `3px`, + border: 0, + cursor: `pointer`, + "&:hover": { + backgroundColor: backgroundColorConfig.hover, + }, + }) +} + +/** + * The ` +``` + +Colors are configurable. + +``` +
+
+
+
+
+``` + +Sizes are also configurable. + +``` +
+
+
+
+
+``` diff --git a/examples/styleguide/src/components/Button/index.js b/examples/styleguide/src/components/Button/index.js new file mode 100644 index 0000000000000..b608dc7bd40f7 --- /dev/null +++ b/examples/styleguide/src/components/Button/index.js @@ -0,0 +1 @@ +export { default } from "./Button" diff --git a/examples/styleguide/src/pages/index.js b/examples/styleguide/src/pages/index.js new file mode 100644 index 0000000000000..3d5814fa1286b --- /dev/null +++ b/examples/styleguide/src/pages/index.js @@ -0,0 +1,12 @@ +import React from "react" +import { Route, Redirect } from "react-router-dom" + +class Home extends React.Component { + render() { + return ( + } /> + ) + } +} + +export default Home diff --git a/examples/styleguide/src/templates/ComponentPage/ComponentPage.js b/examples/styleguide/src/templates/ComponentPage/ComponentPage.js new file mode 100644 index 0000000000000..9090d7eeb2d69 --- /dev/null +++ b/examples/styleguide/src/templates/ComponentPage/ComponentPage.js @@ -0,0 +1,56 @@ +import React from "react" +import PropTypes from "prop-types" +import GatsbyLink from "gatsby-link" + +import Example from "./components/Example" + +class ComponentPage extends React.Component { + render() { + const { displayName, props, html, description } = this.props.pathContext + + return ( +
+

{displayName}

+

{description.text}

+

Props/Methods

+ + + + + + + + + + + {props.map(({ name, description, type, required }, index) => ( + + + + + + + ))} + +
NameDescriptionTypeRequired
{name}{description.text}{type.name}{String(Boolean(required))}
+ +

+ [index] +

+
+ ) + } +} + +ComponentPage.propTypes = { + pathContext: PropTypes.shape({ + displayName: PropTypes.string.isRequired, + props: PropTypes.array.isRequired, + html: PropTypes.string.isRequired, + description: PropTypes.shape({ + text: PropTypes.string.isRequired, + }), + }).isRequired, +} + +export default ComponentPage diff --git a/examples/styleguide/src/templates/ComponentPage/components/ComponentPreview/ComponentPreview.js b/examples/styleguide/src/templates/ComponentPage/components/ComponentPreview/ComponentPreview.js new file mode 100644 index 0000000000000..bece88b2a048b --- /dev/null +++ b/examples/styleguide/src/templates/ComponentPage/components/ComponentPreview/ComponentPreview.js @@ -0,0 +1,35 @@ +import React from "react" +import PropTypes from "prop-types" +import { LiveProvider, LiveEditor, LiveError, LivePreview } from "react-live" + +import * as components from "../../../../../.cache/components" + +import "./prism-theme.css" + +const editorStyles = { + backgroundColor: `#f2f2f2`, + boxSizing: `border-box`, + padding: `16px`, +} + +class ComponentPreview extends React.Component { + render() { + return ( + + + + + + ) + } +} + +ComponentPreview.propTypes = { + code: PropTypes.string.isRequired, +} + +export default ComponentPreview diff --git a/examples/styleguide/src/templates/ComponentPage/components/ComponentPreview/index.js b/examples/styleguide/src/templates/ComponentPage/components/ComponentPreview/index.js new file mode 100644 index 0000000000000..429b0e59858f7 --- /dev/null +++ b/examples/styleguide/src/templates/ComponentPage/components/ComponentPreview/index.js @@ -0,0 +1 @@ +export { default } from "./ComponentPreview" diff --git a/examples/styleguide/src/templates/ComponentPage/components/ComponentPreview/prism-theme.css b/examples/styleguide/src/templates/ComponentPage/components/ComponentPreview/prism-theme.css new file mode 100644 index 0000000000000..dbd7780ae8866 --- /dev/null +++ b/examples/styleguide/src/templates/ComponentPage/components/ComponentPreview/prism-theme.css @@ -0,0 +1,187 @@ +/* + +Name: Base16 Atelier Sulphurpool Light +Author: Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) + +Prism template by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/prism/) +Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ +code[class*="language-"], +pre[class*="language-"] { + font-family: Consolas, Menlo, Monaco, "Andale Mono WT", "Andale Mono", + "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", + "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", + "Courier New", Courier, monospace; + font-size: 14px; + line-height: 1.375; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + background: #f5f7ff; + color: #5e6687; +} + +pre[class*="language-"]::-moz-selection, +pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, +code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #dfe2f1; +} + +pre[class*="language-"]::selection, +pre[class*="language-"] ::selection, +code[class*="language-"]::selection, +code[class*="language-"] ::selection { + text-shadow: none; + background: #dfe2f1; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #898ea4; +} + +.token.punctuation { + color: #5e6687; +} + +.token.namespace { + opacity: 0.7; +} + +.token.operator, +.token.boolean, +.token.number { + color: #c76b29; +} + +.token.property { + color: #c08b30; +} + +.token.tag { + color: #3d8fd1; +} + +.token.string { + color: #22a2c9; +} + +.token.selector { + color: #6679cc; +} + +.token.attr-name { + color: #c76b29; +} + +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #22a2c9; +} + +.token.attr-value, +.token.keyword, +.token.control, +.token.directive, +.token.unit { + color: #ac9739; +} + +.token.statement, +.token.regex, +.token.atrule { + color: #22a2c9; +} + +.token.placeholder, +.token.variable { + color: #3d8fd1; +} + +.token.deleted { + text-decoration: line-through; +} + +.token.inserted { + border-bottom: 1px dotted #202746; + text-decoration: none; +} + +.token.italic { + font-style: italic; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.important { + color: #c94922; +} + +.token.entity { + cursor: help; +} + +pre > code.highlight { + outline: 0.4em solid #c94922; + outline-offset: 0.4em; +} + +/* overrides color-values for the Line Numbers plugin + * http://prismjs.com/plugins/line-numbers/ + */ +.line-numbers .line-numbers-rows { + border-right-color: #dfe2f1; +} + +.line-numbers-rows > span:before { + color: #979db4; +} + +/* overrides color-values for the Line Highlight plugin + * http://prismjs.com/plugins/line-highlight/ + */ +.line-highlight { + background: rgba(107, 115, 148, 0.2); + background: -webkit-linear-gradient( + left, + rgba(107, 115, 148, 0.2) 70%, + rgba(107, 115, 148, 0) + ); + background: linear-gradient( + to right, + rgba(107, 115, 148, 0.2) 70%, + rgba(107, 115, 148, 0) + ); +} diff --git a/examples/styleguide/src/templates/ComponentPage/components/Example/Example.js b/examples/styleguide/src/templates/ComponentPage/components/Example/Example.js new file mode 100644 index 0000000000000..90d027995c976 --- /dev/null +++ b/examples/styleguide/src/templates/ComponentPage/components/Example/Example.js @@ -0,0 +1,42 @@ +import React from "react" +import PropTypes from "prop-types" +import { Parser, ProcessNodeDefinitions } from "html-to-react" +import ComponentPreview from "../ComponentPreview" + +const isValidNode = () => true +const isCodeExample = ({ name = `` } = {}) => name === `pre` + +const parser = new Parser() +const processNodeDefinitions = new ProcessNodeDefinitions(React) +const getHtmlCode = children => children[0].children[0].data + +const ExampleNodeProcessor = ({ children }) => + React.createElement(ComponentPreview, { code: getHtmlCode(children) }) + +const processingInstructions = [ + { + shouldProcessNode: isCodeExample, + processNode: ExampleNodeProcessor, + }, + { + shouldProcessNode: isValidNode, + processNode: processNodeDefinitions.processDefaultNode, + }, +] + +class Example extends React.Component { + render() { + const html = parser.parseWithInstructions( + this.props.html, + isValidNode, + processingInstructions + ) + return
{React.Children.toArray(html)}
+ } +} + +Example.propTypes = { + html: PropTypes.string.isRequired, +} + +export default Example diff --git a/examples/styleguide/src/templates/ComponentPage/components/Example/index.js b/examples/styleguide/src/templates/ComponentPage/components/Example/index.js new file mode 100644 index 0000000000000..09e6b6756dd3e --- /dev/null +++ b/examples/styleguide/src/templates/ComponentPage/components/Example/index.js @@ -0,0 +1 @@ +export { default } from "./Example" diff --git a/examples/styleguide/src/templates/ComponentPage/index.js b/examples/styleguide/src/templates/ComponentPage/index.js new file mode 100644 index 0000000000000..a75e6920a0c86 --- /dev/null +++ b/examples/styleguide/src/templates/ComponentPage/index.js @@ -0,0 +1 @@ +export { default } from "./ComponentPage" diff --git a/examples/styleguide/src/templates/TOC/TOC.js b/examples/styleguide/src/templates/TOC/TOC.js new file mode 100644 index 0000000000000..df0c03a621216 --- /dev/null +++ b/examples/styleguide/src/templates/TOC/TOC.js @@ -0,0 +1,34 @@ +import React from "react" +import PropTypes from "prop-types" +import GatsbyLink from "gatsby-link" + +class TOC extends React.Component { + render() { + const { allComponents } = this.props.pathContext + return ( +
+

Component styleguide

+
    + {allComponents.map(({ displayName, path }, index) => ( +
  • + {displayName} +
  • + ))} +
+
+ ) + } +} + +TOC.propTypes = { + pathContext: PropTypes.shape({ + allComponents: PropTypes.arrayOf( + PropTypes.shape({ + displayName: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + }).isRequired + ).isRequired, + }).isRequired, +} + +export default TOC diff --git a/examples/styleguide/src/templates/TOC/index.js b/examples/styleguide/src/templates/TOC/index.js new file mode 100644 index 0000000000000..5991b1f3b5389 --- /dev/null +++ b/examples/styleguide/src/templates/TOC/index.js @@ -0,0 +1 @@ +export { default } from "./TOC"