Skip to content

Commit

Permalink
docs: make props table more awesome (#4067)
Browse files Browse the repository at this point in the history
Co-authored-by: aromko <[email protected]>
Co-authored-by: Marcel Köhler <[email protected]>
  • Loading branch information
3 people authored Aug 5, 2024
1 parent d822398 commit f85d05d
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 89 deletions.
174 changes: 169 additions & 5 deletions docs/scripts/build-component-props.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// @ts-check
import * as prettier from 'prettier';
import docgen from 'react-docgen-typescript';
import { codeToHtml } from 'shiki';
import { fileURLToPath } from 'url';
import { fs, globby, path } from 'zx';

Expand All @@ -11,7 +13,14 @@ const parser = docgen.withCustomConfig('./tsconfig.json', {
shouldExtractValuesFromUnion: false,
skipChildrenPropWithoutDoc: false,
propFilter: {
skipPropsWithName: ['variant', 'size', 'key', 'style'],
skipPropsWithName: [
'variant',
'size',
'key',
'style',
'UNSTABLE_childItems',
'UNSAFE_selectionState',
],
},
customComponentTypes: [
'AutocompleteComponent',
Expand All @@ -22,6 +31,152 @@ const parser = docgen.withCustomConfig('./tsconfig.json', {
],
});

const transformDefaultValue = async val => {
let x = val.defaultValue.value;
x = /^[a-zA-Z]/.test(x) ? `"${x}"` : x;

return await codeToHtml(`${x}`, {
lang: 'ts',
theme: 'min-light',
});
};

const formatText = (text, pattern, replacementText) => {
if (pattern.test(text)) {
return text.replace(pattern, replacementText);
}

return text;
};

const applyFormatSteps = text => {
text = formatText(text, /\=>\s+void/g, '=> xxx');
text = formatText(text, /<\.\.\.>/g, '<xxx>');

return text;
};

const revertFormatSteps = text => {
text = formatText(text, /\=>\s+xxx/g, '=> void');
text = formatText(text, /<xxx>/g, '<...>');

return text;
};

const replacePropName = property => {
let spaceTypeName = 'GapSpaceProp';

if (
property.name === 'space' &&
property.declarations !== undefined &&
property.declarations.some(declaration =>
['Inset.tsx'].some(fileName => declaration.fileName.includes(fileName))
)
) {
spaceTypeName = 'PaddingSpaceProp';
}

const transformations = {
width: {
typeName: 'WidthProp',
},
space: {
typeName: spaceTypeName,
},
height: {
typeName: 'HeightProp',
},
p: {
typeName: 'PaddingSpaceProp',
},
pb: {
typeName: 'PaddingBottomProp',
},
pt: {
typeName: 'PaddingTopProp',
},
pl: {
typeName: 'PaddingLeftProp',
},
pr: {
typeName: 'PaddingRightProp',
},
py: {
typeName: 'PaddingSpacePropY',
},
px: {
typeName: 'PaddingSpacePropX',
},
spaceY: {
typeName: 'PaddingSpacePropY',
},
spaceX: {
typeName: 'PaddingSpacePropX',
},
position: {
typeName: 'ObjectFitProp',
},
fontSize: {
typeName: 'FontSizeProp',
},
weight: {
typeName: 'FontWeightProp',
},
cursor: {
typeName: 'CursorProp',
},
orientation: {
typeName: 'AlignmentProp',
},
};

const transformation = transformations[property.name];
if (transformation) {
property.type.name = transformation.typeName;
}
};

const transformTypeValue = async val => {
//List of types prettier can't handle see https://prettier.io/playground
const ignorePrettier = [
'any[]',
'string | number | readonly string[]',
'string[]',
'(number | "fit")[]',
'TemplateValue[]',
'0 | "auto" | "full" | "fit" | "min" | "max" | "screen" | "svh" | "lvh" | "dvh" | "px" | "0.5" | 1 | "1.5" | 2 | "2.5" | 3 | "3.5" | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 16 | 20 | 24 | 28 | ... 37 more ...',
'{ vertical?: { alignY?: "none" | "center" | "top" | "bottom"; alignX?: "none" | "left" | "center" | "right"; } | undefined; horizontal?: { alignX?: "none" | "left" | "center" | "right" | undefined; alignY?: "none" | ... 3 more ... | undefined; } | undefined; } | undefined',
'{ input?: string; action?: string; } | undefined',
'(path: string, routerOptions: undefined) => void',
'ReactNode[]',
'number | number[]',
'CellElement | CellElement[] | CellRenderer',
'"none" | "auto" | "default" | "pointer" | "wait" | "text" | "move" | "help" | "notAllowed" | "progress" | "cell" | "crosshair" | "vertical" | "alias" | "copy" | "noDrop" | "grap" | ... 8 more ...',
'"Accordion" | "Badge" | "Body" | "Button" | "Card" | "DateField" | "Dialog" | "Divider" | "Field" | "Footer" | "Header" | "Headline" | "Popover" | "HelpText" | "Image" | "Checkbox" | ... 21 more ... | "ComboBox"',
'string | { [slot in keyof ThemeComponent<C>]?: string; }',
'keyof NumberFormatOptionsCurrencyDisplayRegistry',
'boolean | keyof NumberFormatOptionsUseGroupingRegistry | "true" | "false"',
'keyof NumberFormatOptionsSignDisplayRegistry',
];
let text = val.type.name;

if (!ignorePrettier.includes(text)) {
text = applyFormatSteps(text);

text = await prettier
.format(text, {
printWidth: 85,
parser: 'typescript',
})
.then(text => revertFormatSteps(text));
}

return codeToHtml(text.replace(/^\((.*)\)$/, '$1'), {
lang: 'ts',
theme: 'min-light',
});
};

// Resolve __dirname for ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand All @@ -46,11 +201,11 @@ const files = await globby([

const output = {};

files.forEach(file => {
for await (const file of files) {
const docs = parser.parse(file);

if (docs.length === 0) {
return;
continue;
}

const { name } = path.parse(file);
Expand All @@ -60,10 +215,19 @@ files.forEach(file => {

for (const key in props) {
// Remove properties we do not need.
const { parent, declarations, ...val } = props[key];
const { parent, ...val } = props[key];

replacePropName(val);

val.type.value = await transformTypeValue(val);

if (val.defaultValue) {
val.defaultValue.value = await transformDefaultValue(val);
}

output[name][key] = val;
}
});
}

await fs.writeJson(outputFilePath, output);
console.log(`✅ Successfully generated props table!`);
92 changes: 41 additions & 51 deletions docs/ui/PropsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import componentProps from '@/registry/props.json';
import { Inline, Table, Text } from '@/ui';
import { Inline, Stack, Text } from '@/ui';
import { BlankCanvas } from './icons';
import { Markdown } from './mdx';

// Helper
// ---------------
const parseType = (val: string) =>
// Remove "()" when the type is wrapped im them (this is done by prettier)
val.replace(/^\((.*)\)$/, '$1');

// Types
// ---------------
export interface PropsTableProps {
Expand All @@ -19,11 +13,13 @@ interface Prop {
name: string;
type: {
name: string;
value: string;
};
defaultValue: {
value: any;
};
description: string;
required: boolean;
}

// Component
Expand All @@ -46,49 +42,43 @@ export const PropsTable = ({ component }: PropsTableProps) => {
}

return (
<Table aria-label="Table with component props" variant="hover" stretch>
<Table.Header>
<Table.Column key="property" width="1/6">
Property
</Table.Column>
<Table.Column key="type" width="2/6">
Type
</Table.Column>
<Table.Column key="default" width="1/6">
Default
</Table.Column>
<Table.Column key="description" width="2/6">
Description
</Table.Column>
</Table.Header>
<Table.Body>
{props.map(prop => (
<Table.Row key={prop.name}>
<Table.Cell>
<code className="before:content-none after:content-none">
{prop.name}
</code>
</Table.Cell>
<Table.Cell>
<code className="before:content-none after:content-none">
{parseType(prop.type.name)}
</code>
</Table.Cell>
<Table.Cell>
<code className="before:content-none after:content-none">
{prop.defaultValue ? prop.defaultValue.value : '-'}
</code>
</Table.Cell>
<Table.Cell>
<Markdown
// Reset <code> for now
className="text-pretty *:bg-transparent *:p-0 *:text-xs"
contents={prop.description}
/>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<div className="border-secondary-200 divide-y rounded-lg border bg-white/40">
{props.map(prop => (
<div
className="text-text-primary-muted flex flex-col gap-2 px-3 py-3.5 text-sm"
key={prop.name}
>
<Inline space={2} alignY="center">
<code className="before:content-none after:content-none">
{prop.name}
{prop.required ? '' : '?'}
</code>
<div
dangerouslySetInnerHTML={{ __html: prop.type.value }}
className="*:m-0 *:!bg-transparent *:p-0 *:text-xs"
/>
</Inline>

<Stack space={1}>
<Markdown
// Reset <code> for now
className="text-pretty text-xs *:bg-transparent *:p-0 *:text-xs"
contents={prop.description}
/>
{prop.defaultValue ? (
<Inline space={2} alignY="center">
Defaults to:{' '}
<div
dangerouslySetInnerHTML={{
__html: prop.defaultValue.value,
}}
className="*:m-0 *:!bg-transparent *:p-0 *:text-xs"
/>
</Inline>
) : null}
</Stack>
</div>
))}
</div>
);
};
4 changes: 2 additions & 2 deletions packages/components/src/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface CardProps
size?: string;

/**
* Padding of the component.
* Padding of the component. You can see allowed tokens [here](../../introduction/design-tokens?theme=core#spacing).
*/
p?: PaddingSpaceProp['space'];

Expand All @@ -46,7 +46,7 @@ export interface CardProps
px?: PaddingSpacePropX['spaceX'];

/**
* Padding vertical (top and bottom) of the component.
* Padding vertical (top and bottom) of the component. You can see allowed tokens [here](../../introduction/design-tokens?theme=core#spacing).
*/
py?: PaddingSpacePropY['spaceY'];
}
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/Headline/Headline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface HeadlineProps extends TextAlignProp {
variant?: string;
size?: string;
/**
* Set a different level from theme, values are from 1 - 6.
* Set a different level.
*/
level?: '1' | '2' | '3' | '4' | '5' | '6' | 1 | 2 | 3 | 4 | 5 | 6;
/**
Expand Down
6 changes: 3 additions & 3 deletions packages/components/src/Inset/Inset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ export type InsetProps =
children: ReactNode;
space?: never;
/**
* Horizontal alignment for the children
* Horizontal alignment for the children. You can see allowed tokens [here](../../introduction/design-tokens?theme=core#spacing).
*/
spaceX?: PaddingSpacePropX['spaceX'];
/**
* Vertical alignment for the children
* Vertical alignment for the children. You can see allowed tokens [here](../../introduction/design-tokens?theme=core#spacing).
*/
spaceY?: PaddingSpacePropY['spaceY'];
}
| {
children: ReactNode;
/**
* The space between the children
* The space between the children. You can see allowed tokens [here](../../introduction/design-tokens?theme=core#spacing).
*/
space?: PaddingSpaceProp['space'];
spaceX?: never;
Expand Down
3 changes: 1 addition & 2 deletions packages/components/src/Table/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { ReactNode } from 'react';
import { useRef } from 'react';
import { ReactNode, useRef } from 'react';
import { AriaTableProps, useTable } from '@react-aria/table';
import {
TableBody as Body,
Expand Down
Loading

0 comments on commit f85d05d

Please sign in to comment.