Skip to content

Commit

Permalink
Create MVP React component layout (primer#366)
Browse files Browse the repository at this point in the history
* Scaffold React component page layout

* Render component data in react component layout

* Move title and status

* Update markdownlint
  • Loading branch information
colebemis authored Jan 30, 2023
1 parent f61cbcc commit f78a870
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 19 deletions.
9 changes: 4 additions & 5 deletions .markdownlint-cli2.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ const options = githubMarkdownOpinions.init({
'no-trailing-punctuation': false,
'single-trailing-newline': false,
'ul-indent': false,
'no-hard-tabs': false
'no-hard-tabs': false,
'first-line-heading': false,
})

module.exports = {
config: options,
customRules: ["@github/markdownlint-github"],
outputFormatters: [
[ "markdownlint-cli2-formatter-pretty", { "appendLink": true } ]
]
customRules: ['@github/markdownlint-github'],
outputFormatters: [['markdownlint-cli2-formatter-pretty', {appendLink: true}]],
}
File renamed without changes.
6 changes: 6 additions & 0 deletions content/components/tree-view/react.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
id: tree_view
---

import {ReactComponentLayout} from '~/src/layouts'
export default ReactComponentLayout
15 changes: 8 additions & 7 deletions gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@ module.exports = {
siteMetadata: {
title: 'Interface guidelines',
shortName: 'Interface guidelines',
description: 'Principles, standards, and usage guidelines for designing GitHub interfaces.',
description: 'Principles, standards, and usage guidelines for designing GitHub interfaces.'
},
pathPrefix: '/design',
plugins: [
{
resolve: '@primer/gatsby-theme-doctocat',
options: {
defaultBranch: 'main',
},
defaultBranch: 'main'
}
},
{
resolve: 'gatsby-plugin-alias-imports',
options: {
alias: {
react: path.resolve(__dirname, 'node_modules', 'react'),
},
},
},
],
'~': path.resolve(__dirname)
}
}
}
]
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
"markdownlint": "markdownlint-cli2 \"**/*.{md,mdx}\" \"!.github\" \"!node_modules\""
},
"dependencies": {
"@github/prettier-config": "^0.0.4",
"@github/prettier-config": "^0.0.6",
"@primer/gatsby-theme-doctocat": "^4.3.0",
"@primer/octicons-react": "^17.3.0",
"@primer/react": "^35.5.0",
"@tanstack/react-query": "^4.23.0",
"eslint": "^8.21.0",
"eslint-plugin-github": "^4.3.7",
"gatsby": "^3.7.2",
Expand All @@ -34,5 +35,6 @@
"resolutions": {
"sharp": "0.29.3"
},
"private": true
"private": true,
"prettier": "@github/prettier-config"
}
2 changes: 1 addition & 1 deletion src/@primer/gatsby-theme-doctocat/nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,4 @@
- title: Tokens
url: /components/tokens
- title: Tree view
url: /components/treeview
url: /components/tree-view
1 change: 1 addition & 0 deletions src/layouts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './react-component-layout'
157 changes: 157 additions & 0 deletions src/layouts/react-component-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import Code from '@primer/gatsby-theme-doctocat/src/components/code'
import {H2, H3} from '@primer/gatsby-theme-doctocat/src/components/heading'
import DoctocatLayout from '@primer/gatsby-theme-doctocat/src/components/layout'
import Table from '@primer/gatsby-theme-doctocat/src/components/table'
import {Box, Label, Spinner, Text} from '@primer/react'
import {QueryClient, QueryClientProvider, useQuery} from '@tanstack/react-query'
import React from 'react'

async function fetchPrimerReactData() {
const json = await fetch('https://api.github.com/repos/primer/react/contents/generated/components.json').then(
response => response.json(),
)
// TODO: Handle errors
const content = atob(json.content)
return JSON.parse(content)
}

function Page({children, ...props}: any) {
const {id = ''} = props.pageContext.frontmatter
// TODO: Fetch inital data at build time, then hydrate
const queryResult = useQuery(['react-component-data'], fetchPrimerReactData)
const componentData = queryResult.data?.components[id]
const importStatement = `import {${componentData?.name}} from '@primer/react${
componentData?.status === 'draft' ? '/drafts' : ''
}'`

const tableOfContents = {
items: [
{url: '#import', title: 'Import'},
{url: '#props', title: 'Props'},
],
}

const frontmatter = {
title: componentData?.name,
status: sentenceCase(componentData?.status || ''),
a11yReviewed: componentData?.a11yReviewed,
}

const pageContext = deepMerge(props.pageContext, {tableOfContents, frontmatter})

return (
<DoctocatLayout {...deepMerge(props, {pageContext})}>
{queryResult.isLoading ? (
<Box sx={{display: 'flex', width: '100%', justifyContent: 'center'}}>
<Spinner />
</Box>
) : null}
{queryResult.isError ? <pre>{JSON.stringify(queryResult.error, null, 2)}</pre> : null}
{queryResult.isSuccess && componentData ? (
<>
<H2>Import</H2>
{/* @ts-ignore */}
<Code className="language-javascript">{importStatement}</Code>
{/* TODO: Link to source code */}
{/* TODO: Link to storybook */}
<H2>Props</H2>
<H3>{componentData.name}</H3>
<PropsTable props={componentData.props} />
{componentData.subcomponents.map(subcomponent => (
<>
<H3>{subcomponent.name}</H3>
<PropsTable props={subcomponent.props} />
</>
))}
</>
) : null}
</DoctocatLayout>
)
}

/** Convert a string to sentence case. */
function sentenceCase(str: string) {
return str.replace(/([A-Z])/g, ' $1').replace(/^./, function (str) {
return str.toUpperCase()
})
}

/** Deeply merge two objects */
function deepMerge(obj1: any, obj2: any): any {
let result = {...obj1}
for (let key in obj2) {
if (obj2.hasOwnProperty(key)) {
if (typeof obj2[key] === 'object' && !Array.isArray(obj2[key])) {
result[key] = deepMerge(result[key], obj2[key])
} else {
result[key] = obj2[key]
}
}
}
return result
}

const queryClient = new QueryClient()

// TODO: Render provider at the root of the app
export function ReactComponentLayout(props) {
return (
<QueryClientProvider client={queryClient}>
<Page {...props} />
</QueryClientProvider>
)
}

function PropsTable({
props,
}: {
props: Array<{
name: string
type: string
defaultValue: string
required: boolean
deprecated: boolean
description: string
}>
}) {
return (
<Table>
<colgroup>
<col style={{width: '20%'}} />
<col style={{width: '30%'}} />
<col style={{width: '10%'}} />
<col style={{width: '40%'}} />
</colgroup>
<thead>
<tr>
<th align="left">Name</th>
<th align="left">Type</th>
<th align="left">Default</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
{props.map(prop => (
<tr key={prop.name}>
<td valign="top">
<Box sx={{display: 'flex', gap: 2, alignItems: 'center'}}>
<Text sx={{fontFamily: 'mono', fontSize: 1, whiteSpace: 'nowrap'}}>{prop.name}</Text>
{prop.required ? <Label variant="accent">Required</Label> : null}
{prop.deprecated ? <Label variant="danger">Deprecated</Label> : null}
</Box>
</td>
<td valign="top">
<Text as="pre" sx={{m: 0, fontFamily: 'mono', fontSize: 1, whiteSpace: 'pre-wrap'}}>
{prop.type}
</Text>
</td>
<td>
<Text sx={{fontFamily: 'mono', fontSize: 1, whiteSpace: 'nowrap'}}>{prop.defaultValue}</Text>
</td>
<td>{prop.description}</td>
</tr>
))}
</tbody>
</Table>
)
}
26 changes: 22 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1215,10 +1215,10 @@
dependencies:
lodash "^4.17.15"

"@github/prettier-config@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@github/prettier-config/-/prettier-config-0.0.4.tgz#cbfddb36a7f29a81c5af155dc5827e95b23b9ccd"
integrity sha512-ZOJ+U771Mw68qp2GPbcqPt2Xg0LEl0YwiIFHXwVLAFm2TgDnsgcCHhXO8PIxOWPqSFO4S7xIMD9CBobfaWGASA==
"@github/prettier-config@^0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@github/prettier-config/-/prettier-config-0.0.6.tgz#5118e6e9f67fef9c2cd74574da7a4d86229ffb44"
integrity sha512-Sdb089z+QbGnFF2NivbDeaJ62ooPlD31wE6Fkb/ESjAOXSjNJo+gjqzYYhlM7G3ERJmKFZRUJYMlsqB7Tym8lQ==

"@graphql-tools/batch-execute@^7.1.2":
version "7.1.2"
Expand Down Expand Up @@ -2060,6 +2060,19 @@
dependencies:
defer-to-connect "^2.0.0"

"@tanstack/[email protected]":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.22.4.tgz#aca622d2f8800a147ece5520d956a076ab92f0ea"
integrity sha512-t79CMwlbBnj+yL82tEcmRN93bL4U3pae2ota4t5NN2z3cIeWw74pzdWrKRwOfTvLcd+b30tC+ciDlfYOKFPGUw==

"@tanstack/react-query@^4.23.0":
version "4.23.0"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.23.0.tgz#0b9e14269a48cf5a4ffe46c8525cdb9df2ebd9cf"
integrity sha512-cfQsrecZQjYYueiow4WcK8ItokXJnv+b2OrK8Lf5kF7lM9uCo1ilyygFB8wo4MfxchUBVM6Cs8wq4Ed7fouwkA==
dependencies:
"@tanstack/query-core" "4.22.4"
use-sync-external-store "^1.2.0"

"@testing-library/dom@*":
version "8.16.1"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.16.1.tgz#96e528c9752d60061128f043e2b43566c0aba2d9"
Expand Down Expand Up @@ -14529,6 +14542,11 @@ use-sidecar@^1.1.2:
detect-node-es "^1.1.0"
tslib "^2.0.0"

use-sync-external-store@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==

use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
Expand Down

0 comments on commit f78a870

Please sign in to comment.