-
Notifications
You must be signed in to change notification settings - Fork 2k
Prototype props table for design system documentation #103561
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
base: trunk
Are you sure you want to change the base?
Changes from 2 commits
eb7fa84
e446e95
7f2d6ab
fcc2522
9b79e39
a746e2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import PropsTable from '#components/props-table'; | ||
|
||
# Breadcrumbs | ||
|
||
<PropsTable component="Breadcrumbs" /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
--- | ||
sidebar_position: 0 | ||
--- | ||
|
||
# Components | ||
|
||
Add content here. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import PropsTable from '#components/props-table'; | ||
|
||
# SummaryButton | ||
|
||
<PropsTable component="SummaryButton" /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.required { | ||
color: var(--ifm-color-danger); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import metadata from '@automattic/components/metadata'; | ||
import { VisuallyHidden } from '@wordpress/components'; | ||
import Markdown from 'react-markdown'; | ||
import styles from './props-table.module.css'; | ||
|
||
interface PropsTableProps { | ||
component: string; | ||
} | ||
|
||
function PropsTable( { component }: PropsTableProps ) { | ||
if ( ! metadata[ component ] ) { | ||
return null; | ||
} | ||
|
||
const { props } = metadata[ component ]; | ||
|
||
return ( | ||
<table> | ||
<thead> | ||
<tr> | ||
<th>Name</th> | ||
<th>Description</th> | ||
<th>Type</th> | ||
<th>Default</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{ Object.entries( props ).map( ( [ key, value ] ) => ( | ||
<tr key={ key }> | ||
<td width="20%"> | ||
{ key } | ||
{ value.required && ( | ||
<> | ||
<VisuallyHidden>(Required)</VisuallyHidden> | ||
<span className={ styles.required } aria-hidden> | ||
* | ||
</span> | ||
</> | ||
) } | ||
</td> | ||
<td width="50%"> | ||
<Markdown>{ value.description }</Markdown> | ||
</td> | ||
<td width="15%">{ value.type.name }</td> | ||
<td width="15%">{ value.defaultValue?.value }</td> | ||
</tr> | ||
) ) } | ||
</tbody> | ||
</table> | ||
); | ||
} | ||
|
||
export default PropsTable; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { mkdir, writeFile } from 'node:fs/promises'; | ||
import { join } from 'node:path'; | ||
import { parse } from 'react-docgen-typescript'; | ||
|
||
type ComponentMetadata = { | ||
props: Record< | ||
string, | ||
{ | ||
description: string; | ||
defaultValue?: { value: any }; | ||
required: boolean; | ||
type: { name: string }; | ||
} | ||
>; | ||
}; | ||
|
||
const DOCUMENTED_COMPONENTS = [ 'breadcrumbs', 'summary-button' ]; | ||
|
||
const files = DOCUMENTED_COMPONENTS.map( ( component ) => `${ component }/index.tsx` ); | ||
Comment on lines
+17
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think in the future this would ideally be something we don't maintain as a list and instead |
||
|
||
const pick = < O extends Record< string, any >, K extends Array< keyof O > >( | ||
obj: O, | ||
keys: K | ||
): Pick< O, K[ number ] > => | ||
Object.fromEntries( Object.entries( obj ).filter( ( [ key ] ) => keys.includes( key ) ) ) as Pick< | ||
O, | ||
K[ number ] | ||
>; | ||
|
||
const omit = < O extends Record< string, any >, K extends Array< keyof O > >( | ||
obj: O, | ||
keys: K | ||
): Omit< O, K[ number ] > => | ||
Object.fromEntries( | ||
Object.entries( obj ).filter( ( [ key ] ) => ! keys.includes( key ) ) | ||
) as Omit< O, K[ number ] >; | ||
|
||
const mapValues = < V, MV >( | ||
obj: Record< string, V >, | ||
fn: ( value: V ) => MV | ||
): Record< string, MV > => | ||
Object.fromEntries( Object.entries( obj ).map( ( [ key, value ] ) => [ key, fn( value ) ] ) ); | ||
Comment on lines
+21
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good example of where rolling our own utilities is not so fun 🙃 (Re: #103048 (comment)) |
||
|
||
async function getMetadataEntry( file: string ): Promise< [ string, ComponentMetadata ] > { | ||
const parsed = await parse( join( process.cwd(), 'src', file ) ); | ||
const [ { displayName, props } ] = parsed; | ||
return [ | ||
displayName, | ||
{ | ||
props: mapValues( omit( props, [ 'ref', 'key' ] ), ( value ) => | ||
pick( value, [ 'description', 'defaultValue', 'required', 'type' ] ) | ||
), | ||
}, | ||
]; | ||
} | ||
|
||
const metadata = Object.fromEntries( await Promise.all( files.map( getMetadataEntry ) ) ); | ||
|
||
await mkdir( 'dist/types', { recursive: true } ); | ||
|
||
await Promise.all( [ | ||
writeFile( 'dist/metadata.js', `export default ${ JSON.stringify( metadata, null, 2 ) };` ), | ||
writeFile( | ||
'dist/types/metadata.d.ts', | ||
`declare const metadata: Record< string, { | ||
props: Record< string, { | ||
description: string; | ||
defaultValue?: { value: any }; | ||
required: boolean; | ||
type: { name: string; }; | ||
} >; | ||
} >; | ||
|
||
export default metadata;` | ||
), | ||
] ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,10 +16,15 @@ | |
"require": "./dist/cjs/index.js" | ||
}, | ||
"./src/*": { | ||
"calypso:src": "./src/*" | ||
"calypso:src": "./src/*", | ||
"import": "./src/*" | ||
}, | ||
"./styles/*": { | ||
"calypso:src": "./styles/*" | ||
}, | ||
"./metadata": { | ||
"dsdocs:src": "./dist/metadata.js", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This and the inline Docusaurus plugin are meant to kinda "hide" this from the public interface of the package. In the future, if we want to standardize and expose this, we should probably just have this available as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I reverted this in 7f2d6ab to use plain, "public" Example:
|
||
"types": "./dist/types/metadata.d.ts" | ||
} | ||
}, | ||
"sideEffects": [ | ||
|
@@ -92,15 +97,21 @@ | |
"@testing-library/user-event": "^14.6.1", | ||
"@types/canvas-confetti": "^1.6.0", | ||
"@types/node": "^22.7.5", | ||
"npm-run-all": "^4.1.5", | ||
"postcss": "^8.5.3", | ||
"react-docgen-typescript": "^2.2.2", | ||
"storybook": "^8.6.12", | ||
"typescript": "^5.8.3", | ||
"webpack": "^5.97.1" | ||
}, | ||
"scripts": { | ||
"clean": "tsc --build ./tsconfig.json ./tsconfig-cjs.json --clean && rm -rf dist", | ||
"build": "tsc --build ./tsconfig.json ./tsconfig-cjs.json && copy-assets", | ||
"build": "run-p 'build:*'", | ||
"build:metadata": "node --experimental-strip-types bin/build-metadata.mts", | ||
"build:components": "tsc --build ./tsconfig.json", | ||
"build:assets": "copy-assets", | ||
"prepack": "yarn run clean && yarn run build", | ||
"prepare": "yarn run build", | ||
"storybook:start": "storybook dev -p 56833", | ||
"storybook:build": "storybook build" | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Problem: I'm running into some strange errors when building the site, specifically during static site generation. I'm wondering if there might be some incompatibility with using WordPress components in a statically rendered context?
I don't see Gutenberg rendering this Emotion Cache provider anywhere. I also tried to "swizzle" a wrapper to provide the context, but the error persisted.
Possible solution could be to use
ScreenReaderText
from@automattic/components
, or create a duplicate copy in this project, or avoid the visually-hidden content altogether.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opted to replace in fcc2522 with static text. This table will need some design refinement anyways, so let's make it a future problem to solve.