Skip to content

Commit 9c59c34

Browse files
author
Nayden Naydenov
committed
chore: migrate samples prepare to storybook
1 parent 9c36b48 commit 9c59c34

File tree

6 files changed

+1575
-178
lines changed

6 files changed

+1575
-178
lines changed

packages/playground/build-scripts-storybook/samples-prepare.js

Lines changed: 0 additions & 170 deletions
This file was deleted.
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import fs from "fs/promises";
2+
import path from "path";
3+
import type CEM from "@ui5/webcomponents-tools/lib/cem/types.d.ts";
4+
5+
const STORIES_ROOT_FOLDER_NAME = '../_stories';
6+
7+
const isCustomElementDeclaration = (object: any): object is CEM.CustomElementDeclaration => {
8+
return "customElement" in object && object.customElement;
9+
};
10+
11+
type ControlType = "text" | "select" | "multi-select" | boolean;
12+
13+
type ArgsTypes = {
14+
[key: string]: {
15+
control?: ControlType | { type: ControlType; /* See below for more */ };
16+
description?: string;
17+
mapping?: { [key: string]: { [option: string]: any } };
18+
name?: string;
19+
options?: string[];
20+
table?: {
21+
category?: string;
22+
defaultValue?: { summary: string; detail?: string };
23+
subcategory?: string;
24+
type?: { summary?: string; detail?: string };
25+
},
26+
UI5CustomData?: {
27+
parameters?: Array<CEM.Parameter>,
28+
returnValue?: {
29+
description?: string
30+
summary?: string
31+
type?: CEM.Type
32+
}
33+
}
34+
}
35+
}
36+
37+
type APIData = {
38+
info: {
39+
package: string;
40+
since: string | undefined;
41+
};
42+
slotNames: Array<string>;
43+
storyArgsTypes: string;
44+
}
45+
46+
// run the script to generate the argTypes for the stories available in the _stories folder
47+
const main = async () => {
48+
const api: CEM.MySchema = JSON.parse((await fs.readFile(`./.storybook/custom-elements.json`)).toString());
49+
50+
// read all directories inside _stories folder and create a list of components
51+
const packages = await fs.readdir(path.join(__dirname, STORIES_ROOT_FOLDER_NAME));
52+
for (const currPackage of packages) {
53+
// packages [main, fiori]
54+
55+
const packagePath = path.join(__dirname, STORIES_ROOT_FOLDER_NAME, currPackage);
56+
const packageStats = await fs.stat(packagePath);
57+
if (packageStats.isDirectory()) {
58+
const componentsInPackage = await fs.readdir(packagePath);
59+
for (const component of componentsInPackage) {
60+
// components [Button, Card, ...]
61+
const componentPath = path.join(packagePath, component);
62+
const componentStats = await fs.stat(componentPath);
63+
if (componentStats.isDirectory()) {
64+
generateStoryDoc(componentPath, component, api, currPackage);
65+
}
66+
}
67+
}
68+
}
69+
70+
async function generateStoryDoc(componentPath: string, component: string, api: CEM.MySchema, componentPackage: string) {
71+
console.log(`Generating argTypes for story ${component}`);
72+
const apiData = getAPIData(api, component, componentPackage);
73+
74+
if (!apiData) {
75+
return;
76+
}
77+
78+
const { storyArgsTypes, slotNames, info } = apiData;
79+
80+
await fs.writeFile(componentPath + '/argTypes.ts', `export default ${storyArgsTypes};
81+
export const componentInfo = ${JSON.stringify(info, null, 4)};
82+
export type StoryArgsSlots = {
83+
${slotNames.map(slotName => `${slotName}: string;`).join('\n ')}
84+
}`);
85+
};
86+
87+
function getAPIData(api: CEM.MySchema, module: string, componentPackage: string): APIData | undefined {
88+
const moduleAPI = api.modules?.find(currModule => currModule.declarations?.find(s => s._ui5reference?.name === module && s._ui5reference?.package === `@ui5/webcomponents${componentPackage !== 'main' ? `-${componentPackage}` : ''}`));
89+
const declaration = moduleAPI?.declarations?.find(s => s._ui5reference?.name === module && s._ui5reference?.package === `@ui5/webcomponents${componentPackage !== 'main' ? `-${componentPackage}` : ''}`);
90+
91+
if (!declaration) {
92+
return;
93+
}
94+
95+
const data = getArgsTypes(api, declaration as CEM.CustomElementDeclaration, componentPackage);
96+
97+
return {
98+
info: {
99+
package: `@ui5/webcomponents${componentPackage !== 'main' ? `-${componentPackage}` : ''}`,
100+
since: declaration?._ui5since
101+
},
102+
slotNames: data.slotNames,
103+
storyArgsTypes: JSON.stringify(data.args, null, "\t")
104+
};
105+
}
106+
107+
function getArgsTypes(api: CEM.MySchema, moduleAPI: CEM.CustomElementDeclaration | CEM.ClassDeclaration, componentPackage: string): { args: any, slotNames: Array<string> } {
108+
let args: ArgsTypes = {};
109+
let slotNames: Array<string> = [];
110+
111+
moduleAPI.members
112+
?.filter((member): member is CEM.ClassField => "kind" in member && member.kind === "field")
113+
.forEach(prop => {
114+
let typeEnum: CEM.EnumDeclaration | undefined;
115+
116+
if (prop.type?.references?.length) {
117+
for (let currModule of api.modules) {
118+
if (!currModule.declarations) {
119+
continue;
120+
}
121+
122+
for (let s of currModule.declarations) {
123+
if (s?._ui5reference?.name === prop.type?.references[0].name && s?._ui5reference?.package === prop.type?.references[0].package && s.kind === "enum") {
124+
typeEnum = s;
125+
break;
126+
}
127+
}
128+
}
129+
}
130+
131+
if (prop.readonly) {
132+
args[prop.name] = {
133+
control: {
134+
type: false
135+
},
136+
};
137+
} else if (typeEnum && Array.isArray(typeEnum.members)) {
138+
args[prop.name] = {
139+
control: "select",
140+
options: typeEnum.members.map(a => a.name),
141+
};
142+
}
143+
});
144+
145+
if (isCustomElementDeclaration(moduleAPI)) {
146+
moduleAPI.slots?.forEach(prop => {
147+
args[prop.name] = {
148+
control: {
149+
type: "text"
150+
}
151+
};
152+
slotNames.push(prop.name);
153+
});
154+
}
155+
156+
// methods parsing because Storybook does not include them in the args by default from the custom-elements.json
157+
// only changing the category to Methods so they are not displayed in the Properties tab
158+
moduleAPI.members
159+
?.filter((member): member is CEM.ClassMethod => "kind" in member && member.kind === "method")
160+
.forEach((prop) => {
161+
args[prop.name] = {
162+
description: prop.description,
163+
table: {
164+
category: "methods",
165+
},
166+
};
167+
168+
// methods can have custom descriptions with parameters and return value
169+
if (prop.parameters || prop.return) {
170+
args[prop.name].UI5CustomData = {
171+
parameters: prop.parameters,
172+
returnValue: prop.return,
173+
}
174+
}
175+
176+
(prop as unknown as CEM.ClassField).kind = "field";
177+
});
178+
179+
// events also have custom descriptions with parameters of their detail objec
180+
if (isCustomElementDeclaration(moduleAPI)) {
181+
moduleAPI.events?.forEach((prop) => {
182+
if (prop.privacy === "public" && prop.params?.length) {
183+
args[prop.name] = {
184+
description: prop.description,
185+
table: {
186+
category: "events",
187+
},
188+
UI5CustomData: {
189+
parameters: prop.params,
190+
},
191+
};
192+
}
193+
});
194+
}
195+
196+
const packages = ["@ui5/webcomponents", "@ui5/webcomponents-fiori"]
197+
198+
// recursively merging the args from the parent/parents
199+
const moduleAPIBeingExtended = moduleAPI.superclass && api.modules
200+
?.find(currModule => currModule.declarations
201+
?.find(s => s?._ui5reference?.name === moduleAPI.superclass?.name && s?._ui5reference?.package === moduleAPI.superclass?.package))
202+
?.declarations
203+
?.find(s => s?._ui5reference?.name === moduleAPI.superclass?.name && s?._ui5reference?.package === moduleAPI.superclass?.package) as CEM.ClassDeclaration;
204+
205+
const referencePackage = moduleAPIBeingExtended?._ui5reference?.package
206+
207+
if (moduleAPIBeingExtended && referencePackage && packages.includes(referencePackage)) {
208+
const { args: nextArgs, slotNames: nextSlotNames } = getArgsTypes(api, moduleAPIBeingExtended, referencePackage === "@ui5/webcomponents" ? "main" : "fiori");
209+
args = { ...args, ...nextArgs };
210+
slotNames = [...slotNames, ...nextSlotNames].filter((v, i, a) => a.indexOf(v) === i);
211+
}
212+
213+
return {
214+
args,
215+
slotNames
216+
};
217+
}
218+
};
219+
220+
main();

0 commit comments

Comments
 (0)