vibe-design-system 1.8.4 → 1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/init.js
CHANGED
|
@@ -31,6 +31,7 @@ function addScripts(projectRoot) {
|
|
|
31
31
|
if (!scripts.vds) scripts.vds = "node vds-core/scan.mjs";
|
|
32
32
|
if (!scripts["vds:watch"]) scripts["vds:watch"] = "node vds-core/scan.mjs --watch";
|
|
33
33
|
if (!scripts["vds:dashboard"]) scripts["vds:dashboard"] = "node vds-core/dashboard-server.mjs";
|
|
34
|
+
if (!scripts["vds:stories"]) scripts["vds:stories"] = "node vds-core/story-generator.mjs";
|
|
34
35
|
pkg.scripts = scripts;
|
|
35
36
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), "utf-8");
|
|
36
37
|
}
|
|
@@ -67,6 +68,11 @@ function ensureVdsCore(projectRoot) {
|
|
|
67
68
|
console.error("vds-core-template/dashboard-server.mjs bulunamadı.");
|
|
68
69
|
process.exit(1);
|
|
69
70
|
}
|
|
71
|
+
|
|
72
|
+
const storyGenSrc = path.join(TEMPLATE_DIR, "story-generator.mjs");
|
|
73
|
+
if (fs.existsSync(storyGenSrc)) {
|
|
74
|
+
fs.copyFileSync(storyGenSrc, path.join(vdsCoreDest, "story-generator.mjs"));
|
|
75
|
+
}
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
function runScan(projectRoot) {
|
package/package.json
CHANGED
|
@@ -24,13 +24,20 @@ Zero-config, autonomous design system. Drop this folder into any Vite + React pr
|
|
|
24
24
|
|
|
25
25
|
## Live component preview (optional)
|
|
26
26
|
|
|
27
|
-
To see **live renders** of components in the dashboard (
|
|
27
|
+
To see **live renders** of components in the dashboard (isolated, no app chrome):
|
|
28
28
|
|
|
29
|
-
1. Add the preview route
|
|
29
|
+
1. Add the preview route **outside** any layout that has your app header, sidebar, or nav. Otherwise every preview iframe will show your full app shell instead of just the component.
|
|
30
30
|
```tsx
|
|
31
|
-
import VdsPreview from "./vds-core/VdsPreview"; // or
|
|
31
|
+
import VdsPreview from "./vds-core/VdsPreview"; // or from node_modules/vibe-design-system/vds-core-template/
|
|
32
32
|
// ...
|
|
33
|
-
<
|
|
33
|
+
<Routes>
|
|
34
|
+
{/* Preview must NOT be inside a layout with header/sidebar */}
|
|
35
|
+
<Route path="/vds-preview" element={<VdsPreview />} />
|
|
36
|
+
<Route path="/" element={<YourLayoutWithHeader />}>
|
|
37
|
+
<Route index element={<Home />} />
|
|
38
|
+
{/* ... */}
|
|
39
|
+
</Route>
|
|
40
|
+
</Routes>
|
|
34
41
|
```
|
|
35
42
|
|
|
36
43
|
2. If you open the dashboard from the **standalone server** (e.g. `npm run vds:watch` → Dashboard at `http://localhost:3334/vds`), run your app as well (e.g. `npm run dev` on port 5173). The dashboard will load previews from that app. If your app runs on another port (e.g. 3000), set:
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* VDS Component Preview — mount at /vds-preview so the dashboard can show live component renders.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Important: Render this route OUTSIDE any layout that contains your app header, sidebar, or nav.
|
|
5
|
+
* Otherwise every component preview iframe will show your full app shell instead of the component alone.
|
|
6
|
+
*
|
|
7
|
+
* Example (React Router):
|
|
8
|
+
* <Route path="/vds-preview" element={<VdsPreview />} /> // at root, not inside <LayoutWithHeader>
|
|
5
9
|
*
|
|
6
10
|
* Requires Vite; uses import.meta.glob. Path alias @/components must point to src/components.
|
|
7
11
|
*/
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* VDS Story generator
|
|
4
|
+
*
|
|
5
|
+
* Reads vds-output.json and generates Storybook .stories.tsx files
|
|
6
|
+
* under src/stories for each component.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node vds-core/story-generator.mjs # generate for all components
|
|
10
|
+
* node vds-core/story-generator.mjs Button # generate only for "Button"
|
|
11
|
+
*/
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const PROJECT_ROOT = path.join(__dirname, "..");
|
|
18
|
+
const VDS_OUTPUT = path.join(PROJECT_ROOT, "vds-output.json");
|
|
19
|
+
const SRC_DIR = path.join(PROJECT_ROOT, "src");
|
|
20
|
+
const STORIES_DIR = path.join(SRC_DIR, "stories");
|
|
21
|
+
|
|
22
|
+
// Components we don't want to auto-generate stories for (project-specific dashboards, heavy UIs, etc.)
|
|
23
|
+
const SKIP_LIST = [
|
|
24
|
+
"AnalysisDashboard",
|
|
25
|
+
"ComponentLibrary",
|
|
26
|
+
"EnterprisePushPanel",
|
|
27
|
+
"FigmaLibraryGenerator",
|
|
28
|
+
"IntegrationGuide",
|
|
29
|
+
"ProjectDropzone",
|
|
30
|
+
"RepoConnect",
|
|
31
|
+
"TokensStudioGuide",
|
|
32
|
+
"NavLink",
|
|
33
|
+
"CodeInput",
|
|
34
|
+
"SyncModeSelector",
|
|
35
|
+
"ToggleGroup",
|
|
36
|
+
"Sidebar",
|
|
37
|
+
"TestComponent",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Components that need a router context
|
|
41
|
+
const ROUTER_DECORATOR_LIST = ["Breadcrumb", "NavigationMenu", "Sidebar", "Menubar"];
|
|
42
|
+
|
|
43
|
+
function ensureDir(dir) {
|
|
44
|
+
if (!fs.existsSync(dir)) {
|
|
45
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toSafeComponentName(name, file) {
|
|
50
|
+
if (name && typeof name === "string") {
|
|
51
|
+
return name
|
|
52
|
+
.replace(/[^A-Za-z0-9]+/g, " ")
|
|
53
|
+
.trim()
|
|
54
|
+
.replace(/\s+([a-z])/g, (_, c) => c.toUpperCase())
|
|
55
|
+
.replace(/^\w/, (c) => c.toUpperCase())
|
|
56
|
+
.replace(/\s+/g, "");
|
|
57
|
+
}
|
|
58
|
+
const base = (file || "").replace(/\.[^.]+$/, "");
|
|
59
|
+
const parts = base.split(/[\\/]/g);
|
|
60
|
+
const last = parts[parts.length - 1] || "Component";
|
|
61
|
+
return last.charAt(0).toUpperCase() + last.slice(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseUnionLiterals(type) {
|
|
65
|
+
if (!type) return [];
|
|
66
|
+
const matches = String(type).match(/"([^"]+)"/g);
|
|
67
|
+
if (!matches) return [];
|
|
68
|
+
return matches.map((s) => s.replace(/"/g, ""));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function capitalize(str) {
|
|
72
|
+
if (!str) return "";
|
|
73
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function detectExportStyle(source, componentName) {
|
|
77
|
+
if (!source) return "unknown";
|
|
78
|
+
const hasDefault = /export\s+default\b/.test(source);
|
|
79
|
+
const namedPatterns = [
|
|
80
|
+
new RegExp(`export\\s+\\{[^}]*\\b${componentName}\\b[^}]*\\}`),
|
|
81
|
+
new RegExp(`export\\s+const\\s+${componentName}\\b`),
|
|
82
|
+
new RegExp(`export\\s+function\\s+${componentName}\\b`),
|
|
83
|
+
new RegExp(`export\\s+class\\s+${componentName}\\b`),
|
|
84
|
+
];
|
|
85
|
+
const hasNamed = namedPatterns.some((re) => re.test(source));
|
|
86
|
+
if (hasDefault && hasNamed) return "both";
|
|
87
|
+
if (hasDefault) return "default";
|
|
88
|
+
if (hasNamed) return "named";
|
|
89
|
+
return "unknown";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildSpecialStories(componentName, variants) {
|
|
93
|
+
const lines = [];
|
|
94
|
+
|
|
95
|
+
// Simple input-like primitives
|
|
96
|
+
if (componentName === "Input") {
|
|
97
|
+
lines.push(`export const Default: Story = {`);
|
|
98
|
+
lines.push(` args: {`);
|
|
99
|
+
lines.push(` placeholder: "Enter your email...",`);
|
|
100
|
+
lines.push(` type: "email",`);
|
|
101
|
+
lines.push(` },`);
|
|
102
|
+
lines.push(`};`);
|
|
103
|
+
return lines.join("\n");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (componentName === "Textarea") {
|
|
107
|
+
lines.push(`export const Default: Story = {`);
|
|
108
|
+
lines.push(` args: {`);
|
|
109
|
+
lines.push(` placeholder: "Write your message...",`);
|
|
110
|
+
lines.push(` rows: 4,`);
|
|
111
|
+
lines.push(` },`);
|
|
112
|
+
lines.push(`};`);
|
|
113
|
+
return lines.join("\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (componentName === "Label") {
|
|
117
|
+
lines.push(`export const Default: Story = {`);
|
|
118
|
+
lines.push(` args: {`);
|
|
119
|
+
lines.push(` children: "Email address",`);
|
|
120
|
+
lines.push(` htmlFor: "email",`);
|
|
121
|
+
lines.push(` },`);
|
|
122
|
+
lines.push(`};`);
|
|
123
|
+
return lines.join("\n");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (componentName === "Slider") {
|
|
127
|
+
lines.push(`export const Default: Story = {`);
|
|
128
|
+
lines.push(` args: {`);
|
|
129
|
+
lines.push(` defaultValue: [50],`);
|
|
130
|
+
lines.push(` max: 100,`);
|
|
131
|
+
lines.push(` step: 1,`);
|
|
132
|
+
lines.push(` },`);
|
|
133
|
+
lines.push(`};`);
|
|
134
|
+
return lines.join("\n");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (componentName === "Calendar") {
|
|
138
|
+
lines.push(`export const Default: Story = {`);
|
|
139
|
+
lines.push(` args: {},`);
|
|
140
|
+
lines.push(`};`);
|
|
141
|
+
return lines.join("\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (componentName === "InputOtp" || componentName === "InputOTP") {
|
|
145
|
+
lines.push(`export const Default: Story = {`);
|
|
146
|
+
lines.push(` args: {`);
|
|
147
|
+
lines.push(` maxLength: 6,`);
|
|
148
|
+
lines.push(` },`);
|
|
149
|
+
lines.push(`};`);
|
|
150
|
+
return lines.join("\n");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (componentName === "Checkbox") {
|
|
154
|
+
lines.push(`export const Default: Story = {`);
|
|
155
|
+
lines.push(` render: (args) => (`);
|
|
156
|
+
lines.push(` <div className="flex items-center space-x-2">`);
|
|
157
|
+
lines.push(` <ComponentRef id="terms" {...args} />`);
|
|
158
|
+
lines.push(` <Label htmlFor="terms">Accept terms</Label>`);
|
|
159
|
+
lines.push(` </div>`);
|
|
160
|
+
lines.push(` ),`);
|
|
161
|
+
lines.push(`};`);
|
|
162
|
+
return lines.join("\n");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (componentName === "Switch") {
|
|
166
|
+
lines.push(`export const Default: Story = {`);
|
|
167
|
+
lines.push(` render: (args) => (`);
|
|
168
|
+
lines.push(` <div className="flex items-center space-x-2">`);
|
|
169
|
+
lines.push(` <ComponentRef id="notifications" {...args} />`);
|
|
170
|
+
lines.push(` <Label htmlFor="notifications">Enable notifications</Label>`);
|
|
171
|
+
lines.push(` </div>`);
|
|
172
|
+
lines.push(` ),`);
|
|
173
|
+
lines.push(`};`);
|
|
174
|
+
return lines.join("\n");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (componentName === "RadioGroup") {
|
|
178
|
+
lines.push(`export const Default: Story = {`);
|
|
179
|
+
lines.push(` render: (args) => (`);
|
|
180
|
+
lines.push(` <ComponentRef className="flex flex-col space-y-2" {...args}>`);
|
|
181
|
+
lines.push(` <div className="flex items-center space-x-2">`);
|
|
182
|
+
lines.push(` <RadioGroupItem value="comfortable" id="comfortable" />`);
|
|
183
|
+
lines.push(` <Label htmlFor="comfortable">Comfortable</Label>`);
|
|
184
|
+
lines.push(` </div>`);
|
|
185
|
+
lines.push(` <div className="flex items-center space-x-2">`);
|
|
186
|
+
lines.push(` <RadioGroupItem value="compact" id="compact" />`);
|
|
187
|
+
lines.push(` <Label htmlFor="compact">Compact</Label>`);
|
|
188
|
+
lines.push(` </div>`);
|
|
189
|
+
lines.push(` </ComponentRef>`);
|
|
190
|
+
lines.push(` ),`);
|
|
191
|
+
lines.push(`};`);
|
|
192
|
+
return lines.join("\n");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (componentName === "Select") {
|
|
196
|
+
lines.push(`export const Default: Story = {`);
|
|
197
|
+
lines.push(` args: { defaultValue: "apple" },`);
|
|
198
|
+
lines.push(` render: (args) => (`);
|
|
199
|
+
lines.push(` <ComponentRef {...args}>`);
|
|
200
|
+
lines.push(` <SelectTrigger className="w-[180px]">`);
|
|
201
|
+
lines.push(` <SelectValue placeholder="Select a fruit" />`);
|
|
202
|
+
lines.push(` </SelectTrigger>`);
|
|
203
|
+
lines.push(` <SelectContent>`);
|
|
204
|
+
lines.push(` <SelectItem value="apple">Apple</SelectItem>`);
|
|
205
|
+
lines.push(` <SelectItem value="banana">Banana</SelectItem>`);
|
|
206
|
+
lines.push(` <SelectItem value="orange">Orange</SelectItem>`);
|
|
207
|
+
lines.push(` </SelectContent>`);
|
|
208
|
+
lines.push(` </ComponentRef>`);
|
|
209
|
+
lines.push(` ),`);
|
|
210
|
+
lines.push(`};`);
|
|
211
|
+
return lines.join("\n");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (componentName === "Badge") {
|
|
215
|
+
const vs = variants && variants.length ? variants : ["default", "secondary", "destructive", "outline"];
|
|
216
|
+
vs.forEach((v, idx) => {
|
|
217
|
+
const storyName = capitalize(v);
|
|
218
|
+
lines.push(`export const ${storyName}: Story = {`);
|
|
219
|
+
lines.push(` args: {`);
|
|
220
|
+
lines.push(` variant: "${v}",`);
|
|
221
|
+
lines.push(` children: "New",`);
|
|
222
|
+
lines.push(` },`);
|
|
223
|
+
lines.push(`};`);
|
|
224
|
+
if (idx < vs.length - 1) lines.push("");
|
|
225
|
+
});
|
|
226
|
+
return lines.join("\n");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (componentName === "Card") {
|
|
230
|
+
lines.push(`export const Default: Story = {`);
|
|
231
|
+
lines.push(` render: (args) => (`);
|
|
232
|
+
lines.push(` <ComponentRef className="w-[340px]" {...args}>`);
|
|
233
|
+
lines.push(` <CardHeader>`);
|
|
234
|
+
lines.push(` <CardTitle>Card title</CardTitle>`);
|
|
235
|
+
lines.push(` <CardDescription>Short description about this card.</CardDescription>`);
|
|
236
|
+
lines.push(` </CardHeader>`);
|
|
237
|
+
lines.push(` <CardContent>`);
|
|
238
|
+
lines.push(` <p>Here is some representative content inside the card body.</p>`);
|
|
239
|
+
lines.push(` </CardContent>`);
|
|
240
|
+
lines.push(` <CardFooter>Footer content</CardFooter>`);
|
|
241
|
+
lines.push(` </ComponentRef>`);
|
|
242
|
+
lines.push(` ),`);
|
|
243
|
+
lines.push(`};`);
|
|
244
|
+
return lines.join("\n");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (componentName === "Avatar") {
|
|
248
|
+
lines.push(`export const Default: Story = {`);
|
|
249
|
+
lines.push(` render: (args) => (`);
|
|
250
|
+
lines.push(` <ComponentRef {...args}>`);
|
|
251
|
+
lines.push(` <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />`);
|
|
252
|
+
lines.push(` <AvatarFallback>JD</AvatarFallback>`);
|
|
253
|
+
lines.push(` </ComponentRef>`);
|
|
254
|
+
lines.push(` ),`);
|
|
255
|
+
lines.push(`};`);
|
|
256
|
+
return lines.join("\n");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Fallback: let the generic variant-based logic handle it
|
|
260
|
+
return "";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function buildStoryFileContent(comp) {
|
|
264
|
+
const componentName = toSafeComponentName(comp.name, comp.file);
|
|
265
|
+
const fileNoExt = comp.file.replace(/\.(tsx|jsx)$/, "");
|
|
266
|
+
const importPath = `@/components/${fileNoExt}`;
|
|
267
|
+
const group = comp.group || "Components";
|
|
268
|
+
const category = comp.category || null;
|
|
269
|
+
const titleParts = [group, category, componentName].filter(Boolean);
|
|
270
|
+
const title = titleParts.join("/");
|
|
271
|
+
|
|
272
|
+
const props = Array.isArray(comp.props) ? comp.props : [];
|
|
273
|
+
const variantProp = props.find((p) => p.name === "variant");
|
|
274
|
+
let variants = parseUnionLiterals(variantProp && variantProp.type);
|
|
275
|
+
|
|
276
|
+
// Fallback: if manifest doesn't have variant metadata yet, parse cva() directly from component file.
|
|
277
|
+
if (!variants.length) {
|
|
278
|
+
try {
|
|
279
|
+
const srcPath = path.join(SRC_DIR, "components", comp.file);
|
|
280
|
+
if (fs.existsSync(srcPath)) {
|
|
281
|
+
const code = fs.readFileSync(srcPath, "utf-8");
|
|
282
|
+
// Roughly match shadcn-style: variants: { variant: { ... }, size: { ... } }
|
|
283
|
+
const m = code.match(/variant\s*:\s*{([\s\S]*?)}\s*,\s*size\s*:/);
|
|
284
|
+
if (m) {
|
|
285
|
+
const body = m[1];
|
|
286
|
+
const names = [];
|
|
287
|
+
const lineRe = /^\s*([A-Za-z0-9_]+)\s*:/gm;
|
|
288
|
+
let lm;
|
|
289
|
+
while ((lm = lineRe.exec(body))) {
|
|
290
|
+
names.push(lm[1]);
|
|
291
|
+
}
|
|
292
|
+
if (names.length) variants = names;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} catch {
|
|
296
|
+
// best-effort; ignore parsing errors
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Read component source to detect export style for import
|
|
301
|
+
let source = "";
|
|
302
|
+
try {
|
|
303
|
+
const srcPath = path.join(SRC_DIR, "components", comp.file);
|
|
304
|
+
if (fs.existsSync(srcPath)) {
|
|
305
|
+
source = fs.readFileSync(srcPath, "utf-8");
|
|
306
|
+
}
|
|
307
|
+
} catch {
|
|
308
|
+
// ignore
|
|
309
|
+
}
|
|
310
|
+
const exportStyle = detectExportStyle(source, componentName);
|
|
311
|
+
|
|
312
|
+
const lines = [];
|
|
313
|
+
lines.push(`import type { Meta, StoryObj } from "@storybook/react";`);
|
|
314
|
+
|
|
315
|
+
if (exportStyle === "default") {
|
|
316
|
+
lines.push(`import ${componentName} from "${importPath}";`);
|
|
317
|
+
lines.push(`const ComponentRef = ${componentName};`);
|
|
318
|
+
} else if (exportStyle === "named") {
|
|
319
|
+
lines.push(`import { ${componentName} } from "${importPath}";`);
|
|
320
|
+
lines.push(`const ComponentRef = ${componentName};`);
|
|
321
|
+
} else {
|
|
322
|
+
const defaultAlias = `${componentName}Default`;
|
|
323
|
+
const namedAlias = `${componentName}Named`;
|
|
324
|
+
lines.push(
|
|
325
|
+
`import ${defaultAlias}, { ${componentName} as ${namedAlias} } from "${importPath}";`,
|
|
326
|
+
);
|
|
327
|
+
lines.push(`const ComponentRef = ${namedAlias} ?? ${defaultAlias};`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Extra imports for composite components
|
|
331
|
+
if (componentName === "Checkbox" || componentName === "Switch" || componentName === "RadioGroup") {
|
|
332
|
+
lines.push(`import { Label } from "@/components/ui/label";`);
|
|
333
|
+
}
|
|
334
|
+
if (componentName === "RadioGroup") {
|
|
335
|
+
lines.push(`import { RadioGroupItem } from "@/components/ui/radio-group";`);
|
|
336
|
+
}
|
|
337
|
+
if (componentName === "Select") {
|
|
338
|
+
lines.push(
|
|
339
|
+
`import { SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select";`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
if (componentName === "Avatar") {
|
|
343
|
+
lines.push(`import { AvatarImage, AvatarFallback } from "@/components/ui/avatar";`);
|
|
344
|
+
}
|
|
345
|
+
if (componentName === "Card") {
|
|
346
|
+
lines.push(
|
|
347
|
+
`import { CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card";`,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (ROUTER_DECORATOR_LIST.includes(componentName)) {
|
|
351
|
+
lines.push(`import { MemoryRouter } from "react-router-dom";`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
lines.push("");
|
|
355
|
+
lines.push(`const meta = {`);
|
|
356
|
+
lines.push(` title: ${JSON.stringify(title)},`);
|
|
357
|
+
lines.push(` component: ComponentRef,`);
|
|
358
|
+
lines.push(` tags: ["autodocs"],`);
|
|
359
|
+
if (ROUTER_DECORATOR_LIST.includes(componentName)) {
|
|
360
|
+
lines.push(` decorators: [(Story) => (`);
|
|
361
|
+
lines.push(` <MemoryRouter>`);
|
|
362
|
+
lines.push(` <Story />`);
|
|
363
|
+
lines.push(` </MemoryRouter>`);
|
|
364
|
+
lines.push(` )],`);
|
|
365
|
+
}
|
|
366
|
+
lines.push(`} satisfies Meta<typeof ComponentRef>;`);
|
|
367
|
+
lines.push("");
|
|
368
|
+
lines.push(`export default meta;`);
|
|
369
|
+
lines.push(`type Story = StoryObj<typeof meta>;`);
|
|
370
|
+
lines.push("");
|
|
371
|
+
|
|
372
|
+
// Component-specific stories (inputs, composite components, etc.)
|
|
373
|
+
const specialStories = buildSpecialStories(componentName, variants);
|
|
374
|
+
if (specialStories) {
|
|
375
|
+
lines.push(specialStories);
|
|
376
|
+
return lines.join("\n");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Generic variant-based stories
|
|
380
|
+
if (!variants.length) {
|
|
381
|
+
lines.push(`export const Default: Story = {`);
|
|
382
|
+
lines.push(` args: {`);
|
|
383
|
+
lines.push(` children: "${componentName}",`);
|
|
384
|
+
lines.push(` },`);
|
|
385
|
+
lines.push(`};`);
|
|
386
|
+
} else {
|
|
387
|
+
const defaultVariant = variants[0];
|
|
388
|
+
lines.push(`export const ${capitalize(defaultVariant)}: Story = {`);
|
|
389
|
+
lines.push(` args: {`);
|
|
390
|
+
lines.push(` variant: "${defaultVariant}",`);
|
|
391
|
+
lines.push(` children: "${componentName}",`);
|
|
392
|
+
lines.push(` },`);
|
|
393
|
+
lines.push(`};`);
|
|
394
|
+
lines.push("");
|
|
395
|
+
for (const v of variants.slice(1)) {
|
|
396
|
+
const storyName = capitalize(v);
|
|
397
|
+
lines.push(`export const ${storyName}: Story = {`);
|
|
398
|
+
lines.push(` args: {`);
|
|
399
|
+
lines.push(` variant: "${v}",`);
|
|
400
|
+
lines.push(` children: "${storyName}",`);
|
|
401
|
+
lines.push(` },`);
|
|
402
|
+
lines.push(`};`);
|
|
403
|
+
lines.push("");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return lines.join("\n");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function main() {
|
|
411
|
+
if (!fs.existsSync(VDS_OUTPUT)) {
|
|
412
|
+
console.error("[VDS] vds-output.json not found. Run `npm run vds` first.");
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
const raw = fs.readFileSync(VDS_OUTPUT, "utf-8");
|
|
416
|
+
const data = JSON.parse(raw);
|
|
417
|
+
const components = Array.isArray(data.components) ? data.components : [];
|
|
418
|
+
|
|
419
|
+
const onlyName = process.argv[2] || null;
|
|
420
|
+
|
|
421
|
+
ensureDir(STORIES_DIR);
|
|
422
|
+
// Clear existing .stories.* files so only VDS-generated stories remain
|
|
423
|
+
try {
|
|
424
|
+
const existing = fs.readdirSync(STORIES_DIR);
|
|
425
|
+
for (const name of existing) {
|
|
426
|
+
if (
|
|
427
|
+
name.endsWith(".stories.tsx") ||
|
|
428
|
+
name.endsWith(".stories.ts") ||
|
|
429
|
+
name.endsWith(".stories.jsx") ||
|
|
430
|
+
name.endsWith(".stories.js")
|
|
431
|
+
) {
|
|
432
|
+
fs.unlinkSync(path.join(STORIES_DIR, name));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
// ignore
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
for (const comp of components) {
|
|
440
|
+
const componentName = toSafeComponentName(comp.name, comp.file);
|
|
441
|
+
if (onlyName && componentName !== onlyName) continue;
|
|
442
|
+
if (SKIP_LIST.includes(componentName)) continue;
|
|
443
|
+
|
|
444
|
+
const storyFileName = `${componentName}.stories.tsx`;
|
|
445
|
+
const storyPath = path.join(STORIES_DIR, storyFileName);
|
|
446
|
+
const content = buildStoryFileContent(comp);
|
|
447
|
+
fs.writeFileSync(storyPath, content, "utf-8");
|
|
448
|
+
console.log(`[VDS] Wrote ${path.relative(PROJECT_ROOT, storyPath)}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
main();
|
|
453
|
+
|