Skip to content

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

Open
wants to merge 6 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ module.exports = {
},
},
{
files: [ 'bin/**/*', 'test/**/*' ],
files: [ '**/bin/**/*', '**/test/**/*' ],
...nodeConfig,
},
{
Expand All @@ -87,7 +87,7 @@ module.exports = {
require( 'eslint-config-prettier' ),
// Our own overrides
{
files: [ '**/*.ts', '**/*.tsx' ],
files: [ '**/*.ts', '**/*.mts', '**/*.tsx' ],
rules: {
// Disable vanilla eslint rules that have a Typescript implementation
// See https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/README.md#extension-rules
Expand Down
5 changes: 5 additions & 0 deletions apps/design-system-docs/docs/components/breadcrumbs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import PropsTable from '#components/props-table';

# Breadcrumbs

<PropsTable component="Breadcrumbs" />
4 changes: 4 additions & 0 deletions apps/design-system-docs/docs/components/index.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
sidebar_position: 0
---

# Components

Add content here.
5 changes: 5 additions & 0 deletions apps/design-system-docs/docs/components/summary-button.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import PropsTable from '#components/props-table';

# SummaryButton

<PropsTable component="SummaryButton" />
12 changes: 12 additions & 0 deletions apps/design-system-docs/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ const config: Config = {
locales: [ 'en' ],
},

plugins: [
() => ( {
name: 'dsdocs-resolver-plugin',
configureWebpack( config ) {
config.resolve ??= {};
config.resolve.conditionNames ??= [ '...' ];
config.resolve.conditionNames.unshift( 'dsdocs:src' );
return config;
},
} ),
],

presets: [
[
'classic',
Expand Down
6 changes: 5 additions & 1 deletion apps/design-system-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"url": "https://github.com/Automattic/wp-calypso.git",
"directory": "apps/design-system-docs"
},
"imports": {
"#components/*": "./src/components/*"
},
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start -p 42987",
Expand All @@ -28,7 +31,8 @@
"@mdx-js/react": "^3.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-markdown": "^9.0.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.7.0",
Expand Down
3 changes: 3 additions & 0 deletions apps/design-system-docs/src/components/props-table.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.required {
color: var(--ifm-color-danger);
}
53 changes: 53 additions & 0 deletions apps/design-system-docs/src/components/props-table.tsx
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';
Copy link
Member Author

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.

      [errors]: [
        Error: Can't render static file for pathname "/components/summary-button"
            at generateStaticFile (/Code/wp-calypso/node_modules/@docusaurus/core/lib/ssg/ssg.js:167:15)
            at processTicksAndRejections (node:internal/process/task_queues:105:5)
            at runNextTicks (node:internal/process/task_queues:69:3)
            at process.processImmediate (node:internal/timers:459:9)
            at async /Code/wp-calypso/node_modules/p-map/index.js:57:22 {
          [cause]: Error: The `useCx` hook should be only used within a valid Emotion Cache Context
              at server.bundle.js:7113:13
              at useContextSystem (server.bundle.js:7176:19)
              at UnconnectedVisuallyHidden (server.bundle.js:7608:7)
              at Uc (server.bundle.js:4062:44)
              at Xc (server.bundle.js:4067:395)
              at Z (server.bundle.js:4070:89)
              at Yc (server.bundle.js:4073:98)
              at $c (server.bundle.js:4072:140)
              at Z (server.bundle.js:4070:345)
              at Xc (server.bundle.js:4065:476)

Copy link
Member Author

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.

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;
7 changes: 6 additions & 1 deletion apps/design-system-docs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": [ "@docusaurus/tsconfig", "@automattic/calypso-build/tsconfig.json" ],
"compilerOptions": {
"baseUrl": "."
"module": "ESNext",
"moduleResolution": "bundler",
"baseUrl": ".",
"paths": {
"#components/*": [ "./src/components/*" ]
}
},
"exclude": [ ".docusaurus", "dist" ]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
"lint": "run-s -s 'lint:*'",
"lint:config-defaults": "node bin/validate-config-keys.js",
"lint:css": "stylelint \"**/*.{css,scss}\"",
"lint:js": "eslint --ext .js,.jsx,.ts,.tsx,.mjs,.json --cache .",
"lint:js": "eslint --ext .js,.jsx,.ts,.tsx,.mjs,.mts,.json --cache .",
"lint:mixedindent": "mixedindentlint --ignore-comments \"client/**/*.scss\" \"assets/**/*.scss\" \"!build/**\" \"!node_modules/**\" \"!public/**\"",
"lint:unused-state-action-types": "bin/find-unused-state-action-types.sh",
"postcss": "postcss -r --config packages/calypso-build/postcss.config.js",
Expand Down
76 changes: 76 additions & 0 deletions packages/components/bin/build-metadata.mts
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
Copy link
Member Author

Choose a reason for hiding this comment

The 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 glob( 'src/*/index.tsx' ). But react-docgen-typescript is pretty slow at parsing, and we have a lot of components -- most of which we don't plan on keeping here long-term. So for now I went with an allowlist.


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
Copy link
Member Author

Choose a reason for hiding this comment

The 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;`
),
] );
15 changes: 13 additions & 2 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Member Author

@aduth aduth May 20, 2025

Choose a reason for hiding this comment

The 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 "import"

Copy link
Member Author

Choose a reason for hiding this comment

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

I reverted this in 7f2d6ab to use plain, "public" import. Docusaurus doesn't seem to be very compatible with using conditionNames overrides in the Webpack configuration. I was getting a bunch of errors in how the internal theme's CSS modules were being processed:

Example:

Syntax error: /Code/wp-calypso/node_modules/@docusaurus/theme-classic/lib/theme/AnnouncementBar/CloseButton/styles.module.css Unknown word "closeButton_rfix" (2:31)

  1 | // extracted by mini-css-extract-plugin
> 2 | export default {"closeButton":"closeButton_rfix"};
    |                               ^

"types": "./dist/types/metadata.d.ts"
}
},
"sideEffects": [
Expand Down Expand Up @@ -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"
}
Expand Down
47 changes: 32 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -873,8 +873,10 @@ __metadata:
gridicons: "npm:^3.4.2"
i18n-calypso: "workspace:^"
lodash: "npm:^4.17.21"
npm-run-all: "npm:^4.1.5"
postcss: "npm:^8.5.3"
prop-types: "npm:^15.8.1"
react-docgen-typescript: "npm:^2.2.2"
react-modal: "npm:^3.16.3"
react-router-dom: "npm:^6.23.1"
storybook: "npm:^8.6.12"
Expand Down Expand Up @@ -1097,6 +1099,7 @@ __metadata:
prism-react-renderer: "npm:^2.3.0"
react: "npm:^18.3.1"
react-dom: "npm:^18.3.1"
react-markdown: "npm:^9.0.1"
typescript: "npm:^5.8.3"
languageName: unknown
linkType: soft
Expand Down Expand Up @@ -9423,11 +9426,11 @@ __metadata:
linkType: hard

"@types/hast@npm:^2.0.0":
version: 2.3.4
resolution: "@types/hast@npm:2.3.4"
version: 2.3.10
resolution: "@types/hast@npm:2.3.10"
dependencies:
"@types/unist": "npm:*"
checksum: 635cfe9a8e91f6b3c15c9929455d0136ac4d75c5b7f596ce21b453cecdfda785e89b10eb2b2d9da9d43e548b1d65ba3e20c741bbaf83823575c9c45001ade4bb
"@types/unist": "npm:^2"
checksum: 16daac35d032e656defe1f103f9c09c341a6dc553c7ec17b388274076fa26e904a71ea5ea41fd368a6d5f1e9e53be275c80af7942b9c466d8511d261c9529c7e
languageName: node
linkType: hard

Expand Down Expand Up @@ -9599,11 +9602,11 @@ __metadata:
linkType: hard

"@types/mdast@npm:^3.0.0":
version: 3.0.11
resolution: "@types/mdast@npm:3.0.11"
version: 3.0.15
resolution: "@types/mdast@npm:3.0.15"
dependencies:
"@types/unist": "npm:*"
checksum: 569ec32ac16deb42f2c9e7cdbfb5be0f67d2407036b49ba9cfa07ad0258b044c259922acba170eaed165ebcf5eb168032fbb4b3e35023fe8c581fe46e9bcbad0
"@types/unist": "npm:^2"
checksum: fcbf716c03d1ed5465deca60862e9691414f9c43597c288c7d2aefbe274552e1bbd7aeee91b88a02597e88a28c139c57863d0126fcf8416a95fdc681d054ee3d
languageName: node
linkType: hard

Expand Down Expand Up @@ -9773,13 +9776,20 @@ __metadata:
languageName: node
linkType: hard

"@types/prop-types@npm:*, @types/prop-types@npm:^15.0.0":
"@types/prop-types@npm:*":
version: 15.7.5
resolution: "@types/prop-types@npm:15.7.5"
checksum: 648aae41423821c61c83823ae36116c8d0f68258f8b609bdbc257752dcd616438d6343d554262aa9a7edaee5a19aca2e028a74fa2d0f40fffaf2816bc7056857
languageName: node
linkType: hard

"@types/prop-types@npm:^15.0.0":
version: 15.7.14
resolution: "@types/prop-types@npm:15.7.14"
checksum: 1ec775160bfab90b67a782d735952158c7e702ca4502968aa82565bd8e452c2de8601c8dfe349733073c31179116cf7340710160d3836aa8a1ef76d1532893b1
languageName: node
linkType: hard

"@types/qs@npm:*, @types/qs@npm:^6.9.7":
version: 6.9.7
resolution: "@types/qs@npm:6.9.7"
Expand Down Expand Up @@ -10066,6 +10076,13 @@ __metadata:
languageName: node
linkType: hard

"@types/unist@npm:^2":
version: 2.0.11
resolution: "@types/unist@npm:2.0.11"
checksum: 24dcdf25a168f453bb70298145eb043cfdbb82472db0bc0b56d6d51cd2e484b9ed8271d4ac93000a80da568f2402e9339723db262d0869e2bf13bc58e081768d
languageName: node
linkType: hard

"@types/unist@npm:^2.0.0, @types/unist@npm:^2.0.2":
version: 2.0.6
resolution: "@types/unist@npm:2.0.6"
Expand Down Expand Up @@ -17357,9 +17374,9 @@ __metadata:
linkType: hard

"diff@npm:^5.0.0":
version: 5.1.0
resolution: "diff@npm:5.1.0"
checksum: 77a0d9beb9ed54796154ac2511872288432124ac90a1cabb1878783c9b4d81f1847f3b746a0630b1e836181461d2c76e1e6b95559bef86ed16294d114862e364
version: 5.2.0
resolution: "diff@npm:5.2.0"
checksum: aed0941f206fe261ecb258dc8d0ceea8abbde3ace5827518ff8d302f0fc9cc81ce116c4d8f379151171336caf0516b79e01abdc1ed1201b6440d895a66689eb4
languageName: node
linkType: hard

Expand Down Expand Up @@ -33844,11 +33861,11 @@ __metadata:
linkType: hard

"style-to-object@npm:^0.4.0":
version: 0.4.1
resolution: "style-to-object@npm:0.4.1"
version: 0.4.4
resolution: "style-to-object@npm:0.4.4"
dependencies:
inline-style-parser: "npm:0.1.1"
checksum: bde789dab148ec01032d75ea3a7d9294aa8dac369e9ef44f9a8f504df565f806b5a2c7226e3355f09a7e5d127684c65a0b7a7ade08780e0f893299e945d1464e
checksum: 3a733080da66952881175b17d65f92985cf94c1ca358a92cf21b114b1260d49b94a404ed79476047fb95698d64c7e366ca7443f0225939e2fb34c38bbc9c7639
languageName: node
linkType: hard

Expand Down