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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-design-system",
3
- "version": "1.8.4",
3
+ "version": "1.8.5",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 (instead of a placeholder):
27
+ To see **live renders** of components in the dashboard (isolated, no app chrome):
28
28
 
29
- 1. Add the preview route to your app (e.g. in the same router):
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 copy VdsPreview.tsx from node_modules/vibe-design-system/vds-core-template/
31
+ import VdsPreview from "./vds-core/VdsPreview"; // or from node_modules/vibe-design-system/vds-core-template/
32
32
  // ...
33
- <Route path="/vds-preview" element={<VdsPreview />} />
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
- * Add to your router, e.g.:
4
- * <Route path="/vds-preview" element={<VdsPreview />} />
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
+