├── .gitignore ├── examples └── basic │ ├── docs │ ├── index.mdx │ └── typography │ │ └── index.mdx │ ├── gatsby-config.js │ └── package.json ├── lerna.json ├── now.json ├── package.json ├── packages ├── designql │ ├── index.js │ └── package.json ├── gatsby-theme-designql │ ├── gatsby-browser.js │ ├── gatsby-config.js │ ├── gatsby-node.js │ ├── gatsby-ssr.js │ ├── index.js │ ├── package.json │ ├── readme.md │ └── src │ │ └── components │ │ ├── Component.js │ │ ├── Layout.js │ │ └── templates │ │ └── ComponentDoc.js └── gatsby-transformer-styled-system │ ├── .babelrc │ ├── .gitignore │ ├── index.js │ ├── package.json │ ├── readme.md │ └── src │ └── gatsby-node.js ├── readme.md └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | .cache 4 | .env 5 | -------------------------------------------------------------------------------- /examples/basic/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Gatsby UI 3 | --- 4 | 5 | # Gatsby UI -------------------------------------------------------------------------------- /examples/basic/docs/typography/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Typography 3 | --- 4 | 5 | # Typography -------------------------------------------------------------------------------- /examples/basic/gatsby-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const path = require('path') 3 | const Components = require('gatsby-ui') 4 | 5 | module.exports = { 6 | siteMetadata: { 7 | title: 'Gatsby UI' 8 | }, 9 | __experimentalThemes: [ 10 | { 11 | resolve: 'gatsby-theme-designql', 12 | options: { 13 | components: Components, 14 | theme: Components.theme, 15 | docsPath: path.join(__dirname, './docs'), 16 | componentsPath: path.join(__dirname, '../../node_modules/gatsby-ui/src'), 17 | figma: { 18 | fileId: process.env.FIGMA_FILE_ID, 19 | //projectId: process.env.FIGMA_PROJECT_ID, 20 | accessToken: process.env.FIGMA_TOKEN, 21 | } 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "basic", 4 | "version": "0.0.5", 5 | "scripts": { 6 | "start": "gatsby develop", 7 | "build": "gatsby build" 8 | }, 9 | "dependencies": { 10 | "dotenv": "^6.2.0", 11 | "gatsby": "latest", 12 | "gatsby-theme-designql": "*" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.0.1-alpha.0" 6 | } 7 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "alias": [ 4 | "designql.com", 5 | "www.designql.com" 6 | ], 7 | "builds": [ 8 | { "src": "docs/*.md", "use": "mdx-builder" } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "designql", 4 | "version": "0.0.1", 5 | "description": "A self-documenting design system using GraphQL", 6 | "scripts": { 7 | "start": "yarn workspace basic start", 8 | "publish": "lerna publish" 9 | }, 10 | "workspaces": [ 11 | "packages/*", 12 | "examples/*", 13 | "docs" 14 | ], 15 | "dependencies": { 16 | "lerna": "^3.13.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/designql/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/system-ui/designql/5f5d5a298b65d2ee6b86bfc46fca171a878b1118/packages/designql/index.js -------------------------------------------------------------------------------- /packages/designql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "designql", 3 | "version": "0.0.1-alpha.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /packages/gatsby-theme-designql/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Provider} from 'gatsby-ui' 3 | 4 | export const wrapRootElement = ({ element }) => ( 5 | React.createElement(Provider, {}, element) 6 | ) 7 | -------------------------------------------------------------------------------- /packages/gatsby-theme-designql/gatsby-config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { resolver } = require('react-docgen') 3 | const annotationResolver = require('react-docgen-annotation-resolver').default 4 | 5 | module.exports = ({ 6 | defaultLayouts = {}, 7 | docsPath = 'docs', 8 | componentsPath = 'src/docs', 9 | components = {}, 10 | theme = {}, 11 | figma 12 | } = {}) => { 13 | const themeLayouts = { 14 | default: require.resolve('./src/components/Layout') 15 | } 16 | 17 | const plugins = [ 18 | { 19 | resolve: 'gatsby-mdx', 20 | options: { 21 | extensions: [".md", ".mdx"], 22 | defaultLayouts: { 23 | ...themeLayouts, 24 | ...defaultLayouts 25 | } 26 | } 27 | }, 28 | { 29 | resolve: 'gatsby-source-filesystem', 30 | options: { 31 | name: 'components', 32 | path: componentsPath 33 | } 34 | }, 35 | { 36 | resolve: 'gatsby-plugin-page-creator', 37 | options: { 38 | name: 'docs', 39 | path: docsPath, 40 | ignore: ['**/\.*'] 41 | }, 42 | }, 43 | { 44 | resolve: 'gatsby-transformer-react-docgen', 45 | options: { 46 | resolver: (ast, recast) => { 47 | const { findAllExportedComponentDefinitions } = resolver 48 | const annotatedComponents = annotationResolver(ast, recast) 49 | const exportedComponents = findAllExportedComponentDefinitions(ast, recast) 50 | 51 | return annotatedComponents.concat(exportedComponents) 52 | } 53 | } 54 | }, 55 | { 56 | resolve: 'gatsby-transformer-styled-system', 57 | options: { components, theme } 58 | } 59 | ] 60 | 61 | if (figma) { 62 | plugins.push({ 63 | resolve: 'gatsby-source-figma', 64 | options: figma 65 | }) 66 | } 67 | 68 | return { 69 | siteMetadata: { 70 | title: 'Gatsby Blog', 71 | siteUrl: 'https://gatsbyjs.org' 72 | }, 73 | plugins 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/gatsby-theme-designql/gatsby-node.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const mkdirp = require('mkdirp') 4 | 5 | const ComponentDoc = require.resolve('./src/components/templates/ComponentDoc') 6 | 7 | exports.createPages = async ({ graphql, actions }, pluginOptions) => { 8 | const { createPage } = actions 9 | const { formatDisplayName, componentDocsPath = '/components' } = pluginOptions 10 | 11 | // const { docsPath } = pluginOptions 12 | 13 | const result = await graphql(` 14 | { 15 | docs: allFile( 16 | filter: { 17 | sourceInstanceName: { eq: "components" } 18 | extension: { eq: "mdx" } 19 | } 20 | ) { 21 | edges { 22 | node { 23 | name 24 | sourceInstanceName 25 | childMdx { 26 | rawBody 27 | code { 28 | body 29 | } 30 | } 31 | } 32 | } 33 | } 34 | 35 | components: allComponentMetadata { 36 | edges { 37 | node { 38 | displayName 39 | parent { 40 | ... on File { 41 | name 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | system: allStyledSystem { 49 | edges { 50 | node { 51 | id 52 | name 53 | tagName 54 | css 55 | propTypes 56 | defaultProps 57 | } 58 | } 59 | } 60 | } 61 | `) 62 | 63 | if (result.errors) { 64 | console.log(result.errors) 65 | throw new Error('Could not query components', result.errors) 66 | } 67 | 68 | // Aggregate colocated MDX files 69 | const { components } = result.data 70 | const componentDocs = result.data.docs.edges.reduce((acc, { node }) => { 71 | acc[node.name] = node 72 | return acc 73 | }, {}) 74 | 75 | // Create component pages 76 | components.edges.forEach(({ node }) => { 77 | const { frontmatter = {} } = node 78 | const title = frontmatter.title || node.parent.name 79 | const path = frontmatter.path || `${componentDocsPath}/${title}` 80 | 81 | let docs = '' 82 | if (componentDocs[title]) { 83 | docs = componentDocs[title].childMdx.code.body 84 | } 85 | 86 | createPage({ 87 | path, 88 | context: { 89 | ...node, 90 | title, 91 | docs 92 | }, 93 | component: ComponentDoc 94 | }) 95 | }) 96 | } 97 | 98 | exports.onPreBootstrap = ({ store }) => { 99 | const { program } = store.getState() 100 | 101 | const dirs = [ 102 | path.join(program.directory, 'src/pages') 103 | ] 104 | 105 | dirs.forEach(dir => { 106 | if (!fs.existsSync(dir)) { 107 | mkdirp.sync(dir) 108 | } 109 | }) 110 | } 111 | 112 | exports.onCreateWebpackConfig = ({ loaders, actions }) => { 113 | actions.setWebpackConfig({ 114 | module: { 115 | rules: [ 116 | { 117 | test: /\.js$/, 118 | include: path.dirname(require.resolve('gatsby-theme-designql')), 119 | use: [loaders.js()] 120 | } 121 | ] 122 | } 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /packages/gatsby-theme-designql/gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Provider} from 'gatsby-ui' 3 | 4 | export const wrapRootElement = ({ element }) => ( 5 | React.createElement(Provider, {}, element) 6 | ) 7 | -------------------------------------------------------------------------------- /packages/gatsby-theme-designql/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/system-ui/designql/5f5d5a298b65d2ee6b86bfc46fca171a878b1118/packages/gatsby-theme-designql/index.js -------------------------------------------------------------------------------- /packages/gatsby-theme-designql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-theme-designql", 3 | "description": "DesignQL implemented as a Gatsby theme", 4 | "version": "0.0.1-alpha.0", 5 | "license": "MIT", 6 | "author": "John Otander", 7 | "main": "gatsby-config.js", 8 | "scripts": {}, 9 | "keywords": [ 10 | "gatsby", 11 | "gatsby-theme" 12 | ], 13 | "dependencies": { 14 | "@mdx-js/mdx": "^0.16.6", 15 | "@mdx-js/tag": "^0.16.6", 16 | "gatsby-mdx": "^0.3.3", 17 | "gatsby-plugin-page-creator": "^2.0.5", 18 | "gatsby-source-figma": "^1.0.0", 19 | "gatsby-source-filesystem": "^2.0.13", 20 | "gatsby-theme-redirects": "^0.0.5", 21 | "gatsby-transformer-react-docgen": "^3.0.0", 22 | "gatsby-transformer-styled-system": "^0.0.1-alpha.0", 23 | "gatsby-ui": "^0.0.5", 24 | "lodash.kebabcase": "^4.1.1", 25 | "mkdirp": "^0.5.1", 26 | "prop-types": "^15.6.2", 27 | "react": "^16.7.0", 28 | "react-docgen-annotation-resolver": "^1.1.0", 29 | "react-dom": "^16.7.0", 30 | "sidepane": "^1.0.0-4", 31 | "styled-components": "^4.1.3", 32 | "styled-system": "^3.2.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/gatsby-theme-designql/readme.md: -------------------------------------------------------------------------------- 1 | # gatsby-theme-designql 2 | 3 | > :warning: This is experimental and subject to breaking changes. 4 | 5 | ## Installation 6 | 7 | ```sh 8 | yarn add gatsby-theme-designql 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | // gatsby-config.js 15 | module.exports = { 16 | return { 17 | __experimentalThemes: [ 18 | { 19 | resolve: 'gatsby-theme-designql', 20 | options: { 21 | docsPath: '/docs' 22 | } 23 | } 24 | ] 25 | } 26 | } 27 | ``` 28 | 29 | ### Configuration 30 | 31 | Key | Default | Description 32 | --- | --- | --- 33 | `docsPath` | `/` | Root path for documentation pages 34 | `componentDocsPath` | `/components` | Path for rendered component docs pages 35 | `componentsPath` | `src/components` | Path for components source files 36 | -------------------------------------------------------------------------------- /packages/gatsby-theme-designql/src/components/Component.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Code, Container, Heading, Table, Text, Provider} from 'gatsby-ui' 3 | import MDXRenderer from 'gatsby-mdx/mdx-renderer' 4 | 5 | import Layout from './Layout' 6 | 7 | const DESCRIPTIONS = { 8 | as: 'Element that the component will render as.', 9 | alignSelf: 'Aligns flex items of the current flex line overriding the align-items value', 10 | bg: 'Set background color, pulls from colors in theme', 11 | borderColor: 'Set border color, pulls from colors in theme', 12 | boxShadow: 'Set box shadow, pulls from shadows in theme', 13 | color: 'Set text color, pulls from colors in theme', 14 | display: 'Set display for the element', 15 | flex: 'Set flex attribute', 16 | order: 'Set flex order', 17 | textAlign: 'Set text alignment', 18 | lineHeight: 'Set line height', 19 | width: 'Set width', 20 | fontWeight: 'Set font weight', 21 | fontSize: 'Set font size', 22 | p: 'Set padding', 23 | pl: 'Set left padding', 24 | pr: 'Set right padding', 25 | pt: 'Set top padding', 26 | pb: 'Set bottom padding', 27 | py: 'Set vertical padding', 28 | px: 'Set horizontal padding', 29 | m: 'Set margin', 30 | ml: 'Set left margin', 31 | mr: 'Set right margin', 32 | mt: 'Set top margin', 33 | mb: 'Set bottom margin', 34 | my: 'Set vertical margin', 35 | mx: 'Set horizontal margin' 36 | } 37 | 38 | const formatDefaultValue = value => value.replace(/^'/, '').replace(/'$/, '') 39 | 40 | export default ({ 41 | title, 42 | metadata, 43 | docs 44 | }) => { 45 | 46 | if (docs) { 47 | return ( 48 | 49 | 50 | {docs} 51 | 52 | Table of properties 53 | 54 | 55 | 56 | Prop 57 | Default 58 | Description 59 | 60 | 61 | 62 | {metadata.props.map(prop => 63 | 64 | {prop.name} 65 | 66 | 67 | {prop.defaultValue ? formatDefaultValue(prop.defaultValue.value) : 'None'} 68 | 69 | 70 | 71 | 72 | {prop.description || DESCRIPTIONS[prop.name] || 'None'} 73 | 74 | 75 | 76 | )} 77 | 78 |
79 |
80 |
81 |
82 | ) 83 | } 84 | 85 | return ( 86 | 87 | {title} 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | {metadata.props.map(prop => 98 | 99 | 100 | 101 | 102 | 103 | )} 104 | 105 |
PropDefaultDescription
{prop.name}{prop.defaultValue ? prop.defaultValue.value : 'None'}{prop.description ? prop.description.text : ''}
106 |
107 | ) 108 | } -------------------------------------------------------------------------------- /packages/gatsby-theme-designql/src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StaticQuery, Link as GatsbyLink, graphql } from 'gatsby' 3 | import Sidepane from 'sidepane' 4 | import { Box, Flex, Link, Provider } from 'gatsby-ui' 5 | 6 | const SIDEPANE_WIDTH = 256 7 | 8 | export default ({ children }) => ( 9 | 10 | ( 43 | <> 44 | 45 | 46 | 47 | {data.pages.edges.map(({ node: page }) => { 48 | if (page.path === '/') { 49 | return null 50 | } 51 | 52 | let name = page.path 53 | if (page.context && (page.context.title || page.context.displayName)) { 54 | name = page.context.title || page.context.displayName 55 | } else if (page.parent && page.parent.name) { 56 | name = page.parent.name 57 | } 58 | 59 | return ( 60 | 61 | 67 | {name} 68 | 69 | 70 | ) 71 | })} 72 | 73 | 74 | 75 | 76 | {children} 77 | 78 | 79 | )} 80 | /> 81 | 82 | ) 83 | -------------------------------------------------------------------------------- /packages/gatsby-theme-designql/src/components/templates/ComponentDoc.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { graphql } from 'gatsby' 3 | 4 | import Component from '../Component' 5 | 6 | export default props => ( 7 | <> 8 | 9 | 10 | ) 11 | 12 | export const pageQuery = graphql` 13 | query($displayName: String!) { 14 | metadata: componentMetadata(displayName: { eq: $displayName }) { 15 | displayName 16 | description { 17 | text 18 | } 19 | props { 20 | name 21 | type { 22 | name 23 | value 24 | } 25 | defaultValue { 26 | value 27 | } 28 | description { 29 | text 30 | } 31 | } 32 | } 33 | } 34 | ` -------------------------------------------------------------------------------- /packages/gatsby-transformer-styled-system/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["babel-preset-gatsby-package"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /packages/gatsby-transformer-styled-system/.gitignore: -------------------------------------------------------------------------------- 1 | /*.js 2 | !index.js 3 | -------------------------------------------------------------------------------- /packages/gatsby-transformer-styled-system/index.js: -------------------------------------------------------------------------------- 1 | // noop 2 | -------------------------------------------------------------------------------- /packages/gatsby-transformer-styled-system/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-transformer-styled-system", 3 | "description": "Gatsby transformer to create styled-system docs nodes", 4 | "version": "0.0.1-alpha.0", 5 | "author": "John Otander", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "scripts": { 9 | "build": "babel src -d .", 10 | "prepublish": "yarn build" 11 | }, 12 | "keywords": [ 13 | "gatsby", 14 | "gatsby-plugin", 15 | "gatsby-transformer" 16 | ], 17 | "devDependencies": { 18 | "@babel/cli": "^7.2.3", 19 | "@babel/core": "^7.2.2", 20 | "babel-preset-gatsby-package": "^0.1.3" 21 | }, 22 | "dependencies": { 23 | "system-docs": "^1.0.0-1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/gatsby-transformer-styled-system/readme.md: -------------------------------------------------------------------------------- 1 | # gatsby-transformer-styled-system -------------------------------------------------------------------------------- /packages/gatsby-transformer-styled-system/src/gatsby-node.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const crypto = require('crypto') 3 | 4 | const { GraphQLString, GraphQLJSON, } = require('gatsby/graphql') 5 | 6 | const digest = str => 7 | crypto 8 | .createHash('md5') 9 | .update(str) 10 | .digest('hex') 11 | 12 | const getPropTypes = (propTypes = {}) => { 13 | const metadata = Object.keys(propTypes).reduce((a, key) => { 14 | const type = propTypes[key] 15 | const meta = type.meta 16 | if (!meta || typeof meta !== 'object') return a 17 | a[key] = meta 18 | return a 19 | }, {}) 20 | return metadata 21 | } 22 | 23 | const getTagName = ext => { 24 | const [ last ] = ext.slice(-1) 25 | return last || 'div' 26 | } 27 | 28 | const getExtensions = (Comp, ext = []) => { 29 | if (!Comp.defaultProps || !Comp.defaultProps.is) return ext 30 | const e = Comp.defaultProps.is 31 | if (typeof e !== 'function') return ext 32 | ext.push(e) 33 | // recursive - side effects 34 | getExtensions(e, ext) 35 | return ext 36 | } 37 | 38 | const isComponentNode = node => 39 | node.internal.mediaType === `application/javascript` || 40 | node.internal.mediaType === `text/jsx` 41 | 42 | const isComponent = Comp => 43 | Comp && Comp['$$typeof'] && typeof Comp === 'object' && typeof Comp.render === 'function' 44 | 45 | // Copied from https://github.com/jxnblk/styled-system 46 | const systemDocs = Comp => { 47 | if (!Comp) return {} 48 | if (!isComponent(Comp)) return {} 49 | 50 | const metadata = Object.assign({}, Comp) 51 | metadata.propTypes = getPropTypes(Comp.propTypes) 52 | metadata.extensions = getExtensions(Comp) 53 | metadata.tagName = getTagName(metadata.extensions) 54 | return metadata 55 | } 56 | 57 | exports.onCreateNode = async ({ 58 | node, 59 | loadNodeContent, 60 | actions, 61 | createNodeId 62 | }, { 63 | components 64 | }) => { 65 | const { createNode, createParentChildLink } = actions 66 | 67 | if (!isComponentNode(node)) { 68 | return 69 | } 70 | 71 | const Component = components[node.name] 72 | if (!isComponent(Component)) { 73 | return 74 | } 75 | 76 | const name = Component.displayName || node.name 77 | 78 | const content = await loadNodeContent(node) 79 | const metadata = systemDocs(Component) 80 | const { tagName, propTypes, defaultProps } = metadata 81 | 82 | const nodeId = `${node.id}--${name}--StyledSystem` 83 | const contentDigest = digest(content) 84 | const css = metadata.componentStyle 85 | ? metadata.componentStyle.rules.filter(s => typeof s === 'string').join('') 86 | : '' 87 | 88 | let styledSystemNode = { 89 | name, 90 | tagName, 91 | propTypes, 92 | defaultProps, 93 | css, 94 | src: content, 95 | id: createNodeId(nodeId), 96 | children: [], 97 | parent: node.id, 98 | internal: { 99 | contentDigest, 100 | type: 'StyledSystem', 101 | }, 102 | } 103 | 104 | createParentChildLink({ parent: node, child: styledSystemNode }) 105 | createNode(styledSystemNode) 106 | } 107 | 108 | exports.setFieldsOnGraphQLNodeType = ({ type }) => { 109 | if (type.name !== 'StyledSystem') { 110 | return 111 | } 112 | 113 | return { 114 | name: { type: GraphQLString }, 115 | tagName: { type: GraphQLString }, 116 | css: { type: GraphQLString }, 117 | propTypes: { type: GraphQLJSON }, 118 | defaultProps: { type: GraphQLJSON }, 119 | src: { type: GraphQLString } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # DesignQL 2 | 3 | A self-documenting design system specification for GraphQL. 4 | 5 | ```gql 6 | query { 7 | theme { 8 | colors 9 | boxShadows 10 | a11yCombos { 11 | contrast 12 | backgroundColor 13 | color 14 | } 15 | } 16 | component(name: { eq: "Button" }) { 17 | docs 18 | propsTable { 19 | key 20 | defaultValue 21 | description 22 | type { 23 | name 24 | description 25 | } 26 | } 27 | styledApi { 28 | permutations 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | ## What is it? 35 | 36 | DesignQL provides a GraphQL based definition of a design system's primitives that can be queried _and_ written to by all members of a team. 37 | This provides a powerful API for documenting components that still remains flexible for design system teams to have control of design and branding. 38 | It also provides a convenient mechanism for cloning for prototyping one-offs or experimenting with changes to the design system itself (theme, components, etc.). 39 | 40 | ## Why? 41 | 42 | When we build and document design systems we're often forced to reinvent the wheel with bespoke implementations. 43 | There are numerous design system and styleguide tools out there, but they often couple you to custom DSLs or lack flexibility. 44 | DesignQL seeks to provide users with a free and open API to build out design systems and document them with ease. 45 | Tables of props, playgrounds, and much more can be scaffolded out just from the source code and a colocated MDX file. 46 | 47 | These days design and development for teams often have numerous render targets. 48 | Keeping them in sync can be difficult, especially when attempting to design generatively, access programmatically, or combine with tools lacking read/write APIs. 49 | Future goals of this project will include outputting a design system's primitives and tokens to React (styled-components/emotion/css modules), Vue, vanilla HTML/CSS (Tachyons, BEM, Tailwind). 50 | 51 | We want to share it with the community for a few reasons: 52 | 53 | - :family: Improve upon it as a community 54 | - :recycle: Encourage adoption with other design/development tools 55 | - :wrench: Community-based implementations for other libraries/frameworks/etc 56 | - :lock: Avoid lock in for users 57 | 58 | ## Table of contents 59 | 60 | - [Features](#features) 61 | - [How does it work?](#how-does-it-work) 62 | - [Implementation](#implementation) 63 | - [Naming conventions](#namingconventions) 64 | - [Specification](#specification) 65 | - [Types](#types) 66 | - [DesignSystem](#designsystem) 67 | - [Component](#component) 68 | - [Theme](#theme) 69 | - [Branding](#branding) 70 | - [StyledFunction](#styledfunction) 71 | - [Primitives](#primitives) 72 | - [ColorMap](#colormap) 73 | - [JSON](#json) 74 | 75 | ## How does it work? 76 | 77 | A design system typically consists of the same key parts: 78 | 79 | - :art: theme 80 | - :nail_care: branding 81 | - :ballot_box: components 82 | - :books: documentation 83 | - :trophy: icons 84 | 85 | DesignQL boils this down to a schema that can be shared amongst a team and their projects. 86 | It exposes a GraphQL API that can be used to document React code with many CSS-in-JS libraries and down the road export to other render targets like (Atomic CSS, React Native, Vue). 87 | 88 | More importantly, it defines and provides an interface for programmatic access mentioned earlier. 89 | 90 | ### Implementation 91 | 92 | DesignQL consists of multiple libraries that handle different types of source files. 93 | 94 | Libraries: 95 | 96 | - react-docgen 97 | - styled-system 98 | - MDX 99 | - Gatsby 100 | 101 | #### Example 102 | 103 | Here's an example Button component: 104 | 105 | ```js 106 | import styled from 'styled-components' 107 | import { color, space } from 'styled-system' 108 | import { variant, size } from './styled-functions' 109 | 110 | /* @component */ 111 | export const Button = styled.button` 112 | appearance: button; 113 | 114 | ${color} 115 | ${space} 116 | ${variant} 117 | ${size} 118 | ` 119 | 120 | Button.displayName = 'Button' 121 | 122 | Button.defaultProps = { 123 | variant: 'primary', 124 | size: 'md' 125 | } 126 | Button.propTypes = { 127 | color: color.propTypes, 128 | variant: variant.propTypes, 129 | size: size.propTypes, 130 | ...space.propTypes 131 | } 132 | ``` 133 | 134 | ### Naming conventions 135 | 136 | In order to keep the MVP as simple as possible, DesignQL currently expects a particular layout structure: 137 | 138 | - `src/Button.js` 139 | - `src/Button.mdx` 140 | 141 | If there isn't a colocated MDX file it's no problem, DesignQL will do its best to parse out metadata. 142 | 143 | ### Future 144 | 145 | - Source theme data from numerous locations 146 | - CSS Stats 147 | - Figma 148 | - Sketch 149 | - Framer 150 | 151 | ## Specification 152 | 153 | The DesignQL is defined by the following GraphQL schema: 154 | 155 | ### Types 156 | 157 | The following types are used to define styling, styled functions, components, and theming. 158 | 159 | #### DesignSystem 160 | 161 | The DesignSystem is the top level type that contains a theme, components, and docs. 162 | 163 | ```gql 164 | type DesignSystem: { 165 | theme: Theme! 166 | branding: Branding! 167 | components: [Components!] 168 | } 169 | ``` 170 | 171 | #### Component 172 | 173 | Components are elements that have a name, props, styling, styled functions and documentation. 174 | 175 | ```gql 176 | type Component: { 177 | """ 178 | Name of the component, Pascal cased 179 | """ 180 | name: String! 181 | """ 182 | Description of the component 183 | """ 184 | description: String 185 | """ 186 | HTML element type, also accepts react native primitive types 187 | """ 188 | element: String! 189 | """ 190 | Default props to apply to the component 191 | """ 192 | defaultProps: JSON 193 | """ 194 | Property types that the component accepts 195 | """ 196 | propTypes: JSON 197 | """ 198 | Property control API of the component, defined as a static property 199 | """ 200 | propertyControls: JSON 201 | """ 202 | List of StyledFunctions for the component 203 | """ 204 | StyledFunctions: [StyledFunction!] 205 | """ 206 | Additional metadata for the component, including information like status 207 | """ 208 | metadata: JSON 209 | """ 210 | Component specific documentation 211 | """ 212 | docs: String 213 | """ 214 | Location of the source file on disk 215 | """ 216 | srcPath: String! 217 | """ 218 | Location of the MDX file on disk 219 | """ 220 | docsPath: String 221 | } 222 | ``` 223 | 224 | #### Theme 225 | 226 | Themes are objects that define the values used by style props. 227 | Themes ensure consistent margin, padding, colors, font sizes, and other UI constants. 228 | 229 | A design system can also define multiple themes. 230 | For example, a team might have a theme for apps and a theme for marketing pages. 231 | 232 | ```gql 233 | type Theme: { 234 | """typography""" 235 | fonts: JSON 236 | fontSizes: JSON 237 | fontWeights: JSON 238 | lineHeights: JSON 239 | letterSpacings: JSON 240 | 241 | """skins""" 242 | colors: ColorMap 243 | shadows: JSON 244 | 245 | """layout""" 246 | space: JSON 247 | widths: JSON 248 | minWidths: JSON 249 | maxWidths: JSON 250 | heights: JSON 251 | minHeights: JSON 252 | maxHeights: JSON 253 | 254 | """borders""" 255 | borders: JSON 256 | radii: JSON 257 | 258 | """variants""" 259 | variants: JSON 260 | 261 | """media queries""" 262 | mediaQueries: [String!] 263 | } 264 | ``` 265 | 266 | #### Branding 267 | 268 | Branding consists of a brand's primary colors and its logos. 269 | Queries can be made that automatically return the proper logo type and its color based on parameters. 270 | 271 | For example, if the background where the logo will be placed is a dark purple, the white logo is returned to ensure proper contrast. 272 | 273 | ```gql 274 | type Branding { 275 | """ 276 | List of Logos 277 | """ 278 | logos: [Logo!] 279 | """ 280 | Branding specific colors 281 | """ 282 | colors: ColorMap! 283 | """ 284 | Documentation for branding 285 | """ 286 | docs: String 287 | } 288 | ``` 289 | 290 | #### Styled Function 291 | 292 | A styled function is based on Styled System. 293 | Styled functions have access to the theme, props, and optionally core Styled System functions. 294 | They return a JSON object. 295 | 296 | ```gql 297 | type StyledFunction { 298 | """ 299 | Name for the function 300 | """ 301 | name: String! 302 | """ 303 | Component property 304 | """ 305 | prop: String! 306 | """ 307 | Name of core styled-system function 308 | """ 309 | styledSystem: String 310 | """ 311 | JSON property to set, defaults to prop 312 | """ 313 | JSONProp: String 314 | """ 315 | Convert numbers to px values 316 | """ 317 | toPx: Boolean 318 | """ 319 | Variant key (for example: buttons) 320 | """ 321 | variantKey: String 322 | """ 323 | Does this map to a theme key 324 | """ 325 | themeKey: String 326 | """ 327 | Documentation for the function 328 | """ 329 | docs: String 330 | } 331 | ``` 332 | 333 | ### Primitive Types 334 | 335 | - ColorMap 336 | - JSON 337 | 338 | #### ColorMap 339 | 340 | ColorMap consists of an object with nested objects, arrays, and strings. 341 | A color can be represented in hex, rgb, rgba, hsl, or a color name. 342 | 343 | Values can only be strings. 344 | 345 | Here's an example: 346 | 347 | ```js 348 | { 349 | base: 'black', 350 | bg: 'white', 351 | blue: '#07c', 352 | grays: [ 353 | '#999', 354 | '#555', 355 | '#111' 356 | ] 357 | } 358 | ``` 359 | 360 | #### JSON 361 | 362 | JSON properties can be an Int, Float, or String so we introduce a `JSONProperty` type. 363 | 364 | ```js 365 | import { GraphQLJSON } from 'designql' 366 | 367 | const typeDef = ` 368 | scalar JSON 369 | ` 370 | 371 | const resolvers = { 372 | JSON: GraphQLJSON 373 | } 374 | ``` 375 | 376 | ## Authors 377 | 378 | - John Otander 379 | 380 | ## License 381 | 382 | MIT 383 | --------------------------------------------------------------------------------