Skip to content

Commit f3f620f

Browse files
authored
tooling: add support for jsx functions for usage generate (react-native-elements#3711)
1 parent dc2e5d1 commit f3f620f

File tree

19 files changed

+435
-350
lines changed

19 files changed

+435
-350
lines changed

README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Cross Platform <a href="https://reactnative.dev">React Native</a> UI Toolkit
1515
<p align="center">
1616
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&color=0089E3"></a>
1717
<a href="https://github.com/react-native-elements/react-native-elements"><img src="https://img.shields.io/github/stars/react-native-elements/react-native-elements?label=stars&logo&style=flat-square&color=0089E3"></a>
18-
<a href="https://github.com/react-native-elements/react-native-elements/actions/workflows/dist.yml"><img src="https://img.shields.io/github/workflow/status/react-native-elements/react-native-elements/Bleeding%20Edge%20version?style=flat-square"></a>
18+
<a href="https://github.com/react-native-elements/react-native-elements/actions/workflows/bleeding-edge-dist.yml"><img src="https://img.shields.io/github/actions/workflow/status/react-native-elements/react-native-elements/bleeding-edge-dist.yml?branch=next&style=flat-square"></a>
1919
<a href="https://github.com/prettier/prettier"><img src="https://img.shields.io/badge/styled_with-prettier-F7B93E.svg?style=flat-square"></a>
2020
<a href="https://codecov.io/gh/react-native-elements/react-native-elements"><img src="https://img.shields.io/codecov/c/gh/react-native-elements/react-native-elements?color=F01F7A&style=flat-square"></a>
2121
</p>
@@ -124,11 +124,11 @@ an
124124
or a
125125
[pull request](https://github.com/react-native-elements/react-native-elements/pulls).
126126

127-
### Discord Community
127+
### Community
128128

129-
In case you have any other question or would like to come say **Hi!** to the RNE
130-
community, join our [Discord Server](https://discord.com/invite/e9RBHjkKHa).
131-
See you on the other side! 👋😃
129+
- [Discord](https://discord.com/invite/e9RBHjkKHa) - In case you have any other question or would like to come say **Hi!** to the RNE community, join our [Discord Server](https://discord.com/invite/e9RBHjkKHa). See you on the other side! 👋😃
130+
131+
- [Twitter](https://twitter.com/rn_elements) - Follow us on Twitter to get the latest updates.
132132

133133
## Backers
134134

lerna.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"packages": ["packages/*", "example"],
2+
"packages": ["packages/*", "example", "scripts/docgen/lib"],
33
"npmClient": "yarn",
44
"useWorkspaces": true,
55
"version": "independent",

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"workspaces": {
66
"packages": [
77
"packages/*",
8-
"example"
8+
"example",
9+
"scripts/docgen/lib"
910
]
1011
},
1112
"scripts": {
@@ -24,7 +25,6 @@
2425
},
2526
"devDependencies": {
2627
"@react-native-community/eslint-config": "^2.0.0",
27-
"@rneui/doc-gen": "./scripts/docgen/lib",
2828
"@testing-library/jest-dom": "^5.11.10",
2929
"@testing-library/react": "^11.2.6",
3030
"@testing-library/react-native": "^7.0.2",
@@ -45,6 +45,7 @@
4545
"lerna": "^4.0.0",
4646
"lint-staged": "^12.4.1",
4747
"lodash.orderby": "^4.6.0",
48+
"ora": "5.4.1",
4849
"prettier": "^2.6.2",
4950
"react": "^17.0.2",
5051
"react-docgen-typescript": "^2.2.2",

packages/base/src/CheckBox/CheckBox.usage.tsx

+31-16
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,35 @@ info(
77
);
88

99
usage(
10-
'Controlled CheckBox',
11-
"```tsx live\nfunction RNECheckBox() {const [open, setOpen] = React.useState(false);return (<Stack row align='center'><CheckBox checked={open} onPress={() => setOpen(!open)} /></Stack>);}\n ``` \n ### More examples",
12-
() => (
13-
<Stack row align="center">
14-
<CheckBox checked />
15-
<CheckBox checked checkedIcon="dot-circle-o" uncheckedIcon="circle-o" />
16-
<CheckBox
17-
center
18-
checked
19-
iconType="material"
20-
checkedIcon="clear"
21-
uncheckedIcon="add"
22-
checkedColor="red"
23-
/>
24-
</Stack>
25-
)
10+
'Simple CheckBox',
11+
'',
12+
() =>
13+
function CheckBoxComponent() {
14+
const [state, setState] = React.useState(false);
15+
const toggleCheckbox = () => setState(!state);
16+
return (
17+
<Stack row align="center">
18+
<CheckBox checked={state} onPress={toggleCheckbox} />
19+
<CheckBox
20+
checked
21+
checkedIcon="dot-circle-o"
22+
uncheckedIcon="circle-o"
23+
onPress={toggleCheckbox}
24+
/>
25+
<CheckBox
26+
center
27+
checked
28+
iconType="material"
29+
checkedIcon="clear"
30+
uncheckedIcon="add"
31+
checkedColor="red"
32+
onPress={toggleCheckbox}
33+
/>
34+
</Stack>
35+
);
36+
},
37+
{
38+
showCode: false,
39+
live: true,
40+
}
2641
);

scripts/docgen/lib/index.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@ import { StackProps } from '@rneui/layout';
33

44
declare const Stack: React.ElementType<StackProps>;
55

6+
type UsageMetadata = Record<string, any> & {
7+
live: boolean;
8+
showCode: boolean;
9+
lang: string;
10+
showLineNumbers: boolean;
11+
};
12+
613
declare function meta(args: Record<string, any>): void;
714
declare function info(...args: string[]): void;
815
declare function usage(
916
title: string,
1017
desc: string | string[],
11-
component: (...other: any[]) => React.ReactNode
12-
// live?: boolean
18+
component: (...other: any[]) => React.ReactNode,
19+
usageMetadata?: Partial<UsageMetadata>
1320
): void;
1421

1522
export { info, usage, meta, Stack };

scripts/docgen/lib/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"src"
88
],
99
"scripts": {
10-
"test": "echo \"Error: no test specified\" && exit 1"
10+
"test": "echo \"Error: no test specified\""
1111
},
1212
"author": "",
1313
"license": "ISC",

scripts/docgen/src/components.ts

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import {
2+
// tabify,
3+
codify,
4+
snippetToCode,
5+
removeNewline,
6+
filterPropType,
7+
} from './utils/common';
8+
import { MUST_INCLUDE_PROP_TYPES } from './utils/parentProps';
9+
import { ComponentDoc, Props } from 'react-docgen-typescript';
10+
import orderBy from 'lodash/orderBy';
11+
import prettier from 'prettier';
12+
import dedent from 'dedent';
13+
import path from 'path';
14+
import fs from 'fs';
15+
import {
16+
Method,
17+
StringIndexedObject,
18+
} from 'react-docgen-typescript/lib/parser';
19+
import Handlebars from 'handlebars';
20+
import { ComponentUsage, usageGenParser } from './parser/usageParser';
21+
22+
type PropRowT = {
23+
name?: string;
24+
description?: string;
25+
default?: string;
26+
type?: string;
27+
};
28+
29+
type TemplateOptionsT = {
30+
id: string;
31+
title: string;
32+
description: string;
33+
imports: string;
34+
installation: string;
35+
showUsage: boolean;
36+
parentComponent: string;
37+
usageFileExists: boolean;
38+
playgroundExists: boolean;
39+
usage: string;
40+
usages: ComponentUsage['usage'];
41+
showProps: boolean;
42+
props?: PropRowT[];
43+
themeKey: string;
44+
includeProps?: string;
45+
};
46+
47+
const root = path.join(__dirname, '../../../');
48+
// const pkgRegExp = new RegExp('packages/(.*)/src');
49+
// const pkgPath = path.join(root, 'packages');
50+
const docsPath = path.join(root, 'website/docs');
51+
const usagePath = path.join(docsPath, 'component_usage');
52+
const playgroundPath = path.join(docsPath, '..', 'playground');
53+
54+
const File = {
55+
exist(...paths: string[]) {
56+
return fs.existsSync(path.join(...paths));
57+
},
58+
read(...paths: string[]) {
59+
return String(fs.readFileSync(path.join(...paths)));
60+
},
61+
write(content: string, ...paths: string[]) {
62+
fs.writeFileSync(path.join(...paths), content);
63+
},
64+
};
65+
66+
const template = Handlebars.compile(
67+
File.read(__dirname, './templates/mdx-template.hbs')
68+
);
69+
70+
export class Component implements ComponentDoc {
71+
description: string;
72+
displayName: string;
73+
filePath: string;
74+
props: Props;
75+
tags?: StringIndexedObject<string>;
76+
methods: Method[];
77+
usages: ComponentUsage['usage'];
78+
meta: ComponentUsage['meta'];
79+
80+
static parents: Record<string, string[]>;
81+
82+
constructor(component: ComponentDoc) {
83+
this.displayName = component.displayName;
84+
this.description = component.description;
85+
this.filePath = component.filePath;
86+
this.props = component.props;
87+
this.methods = component.methods;
88+
this.tags = component.tags;
89+
90+
this.extractUsage();
91+
}
92+
93+
extractUsage() {
94+
const usageFilePath = path.resolve(
95+
this.filePath,
96+
'..',
97+
`${this.displayName}.usage.tsx`
98+
);
99+
if (!File.exist(usageFilePath)) {
100+
return;
101+
}
102+
const { desc, meta, usage } = usageGenParser(usageFilePath);
103+
this.usages = usage;
104+
this.meta = meta;
105+
this.description = desc || this.description;
106+
}
107+
108+
async generate() {
109+
return new Promise((resolve) => {
110+
const { displayName, tags, description } = this;
111+
const id = displayName.toLowerCase().replace('.', '_');
112+
const themeKey = displayName.replace('.', '');
113+
const [parentComponent] = displayName.split('.');
114+
// const [, pkg] = this.filePath.match(pkgRegExp);
115+
116+
const { imports = '', installation = '', usage = '' } = tags || {};
117+
118+
const usageFileExists = File.exist(usagePath, `${displayName}.mdx`);
119+
120+
const playgroundExists = File.exist(
121+
playgroundPath,
122+
displayName,
123+
`${id}.playground.tsx`
124+
);
125+
const handleBar: TemplateOptionsT = {
126+
id,
127+
title: displayName,
128+
description,
129+
imports,
130+
parentComponent,
131+
installation,
132+
showUsage: !!this.usages?.length || Boolean(usage) || usageFileExists,
133+
usageFileExists,
134+
playgroundExists,
135+
usage: this.makeUsages() || dedent(snippetToCode(usage).trim()),
136+
usages: this.usages,
137+
showProps: true,
138+
themeKey,
139+
...this.propTable(),
140+
};
141+
const mdFile = prettier.format(template(handleBar), { parser: 'mdx' });
142+
143+
const mdFilePath = path.join(
144+
docsPath,
145+
'components',
146+
`${this.displayName}.mdx`
147+
);
148+
// console.log(pkg, parentComponent, childComponent);
149+
150+
File.write(mdFile, mdFilePath);
151+
resolve(displayName);
152+
});
153+
}
154+
155+
private makeUsages() {
156+
return (
157+
this.usages
158+
?.map(({ title, desc, code, metaData }) => {
159+
let lang = 'tsx';
160+
let live = 'live';
161+
const defTags = {
162+
lang: (v) => ((lang = v), ''),
163+
live: (v) => ((live = v ? 'live' : ''), ''),
164+
};
165+
const props = metaData
166+
?.map(([tag, value]) =>
167+
defTags[tag] ? defTags[tag](value) : `${tag}=${value}`
168+
)
169+
.join(' ');
170+
171+
return `### ${title} \n ${desc} \n \`\`\`${lang} ${live} ${props} \n ${code} \n\`\`\`\n `;
172+
})
173+
.join('\n') || ''
174+
);
175+
}
176+
177+
private propTable() {
178+
const orderedProps = orderBy(Object.values(this.props), ['name'], ['asc']);
179+
if (!orderedProps.length) {
180+
return '';
181+
}
182+
183+
const rows: PropRowT[] = [];
184+
185+
for (const props of orderedProps) {
186+
const { name, type, description, defaultValue, parent } = props;
187+
if (parent) {
188+
const { name: parentName, fileName: parentFileName } = parent;
189+
if (!MUST_INCLUDE_PROP_TYPES.includes(parentName)) {
190+
if (
191+
parentFileName.includes('node_modules') ||
192+
(parentFileName.includes('base/src') &&
193+
!this.filePath.includes(parentFileName))
194+
) {
195+
continue;
196+
}
197+
}
198+
}
199+
200+
rows.push({
201+
name: codify(name),
202+
type: filterPropType(type.name),
203+
default: codify(defaultValue?.value),
204+
description: removeNewline(description),
205+
});
206+
}
207+
return {
208+
props: rows,
209+
includeProps: Component.parents[this.displayName].sort().join(', '),
210+
};
211+
}
212+
}

0 commit comments

Comments
 (0)