vibe-design-system 2.5.28 → 2.5.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-design-system",
3
- "version": "2.5.28",
3
+ "version": "2.5.30",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -56,7 +56,7 @@ To see **live renders** of components in the dashboard (isolated, no app chrome)
56
56
  "vds:stories": "node vds-core/story-generator.mjs"
57
57
  ```
58
58
 
59
- 2. **Context providers:** If components use React context (e.g. `useTimer`, `useSidebar`, `useCircles`, drag-drop context), they will error in Storybook unless wrapped with the right providers. In your project, add decorators in `.storybook/preview.tsx` to wrap all stories (or per-story) with your app’s providers (e.g. `TimerProvider`, `SidebarProvider`, `CirclesProvider`). See [Storybook decorators](https://storybook.js.org/docs/writing-stories/decorators).
59
+ 2. **Context providers:** Run `node vds-core/setup-storybook-providers.mjs` to detect hooks (useTimer, useSidebar, useCircles, useAuth, useSearch) and inject the matching providers into `.storybook/preview.ts`. The script prefers the provider from the same file as the hook (e.g. `context/SidebarContext.tsx` for useSidebar over `components/ui/sidebar`). If a component still errors with "Cannot read properties of undefined (reading 'x')", add default args or mock context state in that component’s story.
60
60
 
61
61
  3. **Icons:** The Foundations/Icons story lists only icons that are imported from `lucide-react` in your app code (src/, excluding `src/stories`), so it reflects real usage.
62
62
 
@@ -22,6 +22,7 @@ const HOOK_TO_PROVIDER = {
22
22
  useCircles: "CirclesProvider",
23
23
  useSidebar: "SidebarProvider",
24
24
  useAuth: "AuthProvider",
25
+ useSearch: "SearchProvider",
25
26
  };
26
27
 
27
28
  const IGNORE_DIRS = new Set(["node_modules", "dist", ".next", "build", ".storybook", "stories"]);
@@ -67,24 +68,29 @@ function detectHooksUsedInProject(projectRoot) {
67
68
  return byFile;
68
69
  }
69
70
 
70
- /** Find file that exports providerName (e.g. TimerProvider). Returns import path from project root for @/ alias: "contexts/TimerContext" */
71
- function findProviderExportPath(projectRoot, providerName) {
71
+ /** Find file that exports providerName (e.g. TimerProvider). If hookName is given, prefer a file that also contains that hook (e.g. context/SidebarContext.tsx for useSidebar over ui/sidebar). */
72
+ function findProviderExportPath(projectRoot, providerName, hookName) {
72
73
  const srcDir = path.join(projectRoot, "src");
73
74
  const files = getAllSourceFiles(srcDir, srcDir).map((r) => path.join("src", r));
74
75
  const exportRe = new RegExp(
75
76
  "export\\s+(?:default\\s+)?(?:const|function|class)\\s+" + providerName + "\\b|export\\s*\\{[^}]*\\b" + providerName + "\\b[^}]*\\}"
76
77
  );
78
+ const candidates = [];
77
79
  for (const rel of files) {
78
80
  const full = path.join(projectRoot, rel);
79
81
  try {
80
82
  const content = fs.readFileSync(full, "utf-8");
81
83
  if (exportRe.test(content)) {
82
84
  const withoutExt = rel.replace(/\.(tsx?|jsx?)$/i, "").replace(/^src\/?/, "");
83
- return "@/" + withoutExt;
85
+ const pathForImport = "@/" + withoutExt;
86
+ const hasHook = hookName && new RegExp("\\b" + hookName + "\\b").test(content);
87
+ candidates.push({ pathForImport, hasHook });
84
88
  }
85
89
  } catch (_) {}
86
90
  }
87
- return null;
91
+ const preferred = hookName ? candidates.find((c) => c.hasHook) : null;
92
+ const pick = preferred || candidates[0];
93
+ return pick ? pick.pathForImport : null;
88
94
  }
89
95
 
90
96
  function getPreviewPath(projectRoot) {
@@ -99,7 +105,28 @@ function getPreviewPath(projectRoot) {
99
105
  return null;
100
106
  }
101
107
 
102
- /** Collect all hooks used in project; return { providersToAdd: [{ name, importPath }], hooksWithoutProvider: Set, hooksByFile: Map } */
108
+ /** Detect if project uses react-dnd (useDrag, useDrop, or import from react-dnd). */
109
+ function detectReactDnd(projectRoot) {
110
+ const srcDir = path.join(projectRoot, "src");
111
+ const files = getAllSourceFiles(srcDir, srcDir).map((r) => path.join(projectRoot, "src", r));
112
+ for (const full of files) {
113
+ try {
114
+ const content = fs.readFileSync(full, "utf-8");
115
+ if (/\b(useDrag|useDrop|DndProvider)\b/.test(content) || /from\s+["']react-dnd["']/.test(content)) {
116
+ const pkgPath = path.join(projectRoot, "package.json");
117
+ if (fs.existsSync(pkgPath)) {
118
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
119
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
120
+ if (deps["react-dnd"]) return true;
121
+ }
122
+ return true;
123
+ }
124
+ } catch (_) {}
125
+ }
126
+ return false;
127
+ }
128
+
129
+ /** Collect all hooks used in project; return { providersToAdd: [{ name, importPath, props? }], ... } */
103
130
  function collectProvidersAndWarnings(projectRoot) {
104
131
  const hooksByFile = detectHooksUsedInProject(projectRoot);
105
132
  const allHooksUsed = new Set();
@@ -110,7 +137,7 @@ function collectProvidersAndWarnings(projectRoot) {
110
137
  const hooksWithoutProvider = new Set();
111
138
  for (const hook of allHooksUsed) {
112
139
  const providerName = HOOK_TO_PROVIDER[hook];
113
- const importPath = findProviderExportPath(projectRoot, providerName);
140
+ const importPath = findProviderExportPath(projectRoot, providerName, hook);
114
141
  if (importPath) {
115
142
  if (!providersToAdd.some((p) => p.name === providerName)) {
116
143
  providersToAdd.push({ name: providerName, importPath });
@@ -119,15 +146,25 @@ function collectProvidersAndWarnings(projectRoot) {
119
146
  hooksWithoutProvider.add(hook);
120
147
  }
121
148
  }
149
+ if (detectReactDnd(projectRoot) && !providersToAdd.some((p) => p.name === "DndProvider")) {
150
+ providersToAdd.unshift({
151
+ name: "DndProvider",
152
+ importPath: "react-dnd",
153
+ props: "{ backend: HTML5Backend }",
154
+ extraImport: { name: "HTML5Backend", from: "react-dnd-html5-backend" },
155
+ });
156
+ }
122
157
  return { providersToAdd, hooksWithoutProvider, hooksByFile };
123
158
  }
124
159
 
125
- /** Build withProviders decorator using React.createElement (no JSX so preview can stay .ts) */
160
+ /** Build withProviders decorator using React.createElement (no JSX so preview can stay .ts). Supports provider.props. */
126
161
  function buildWithProvidersCode(providers) {
127
162
  if (providers.length === 0) return null;
128
163
  let inner = "React.createElement(Story)";
129
164
  for (let i = providers.length - 1; i >= 0; i--) {
130
- inner = `React.createElement(${providers[i].name}, null, ${inner})`;
165
+ const p = providers[i];
166
+ const propsArg = p.props ? p.props : "null";
167
+ inner = `React.createElement(${p.name}, ${propsArg}, ${inner})`;
131
168
  }
132
169
  return `const withProviders = (Story: any) => ${inner};`;
133
170
  }
@@ -154,7 +191,11 @@ function injectProviderDecorators(projectRoot) {
154
191
  if (!content.includes(p.name)) {
155
192
  const lastImportIdx = content.search(/\nimport\s+.+?;\s*$/m);
156
193
  const insertAt = lastImportIdx >= 0 ? content.indexOf("\n", lastImportIdx) + 1 : content.indexOf("\n") + 1;
157
- content = content.slice(0, insertAt) + `import { ${p.name} } from "${p.importPath}";\n` + content.slice(insertAt);
194
+ let importLine = `import { ${p.name} } from "${p.importPath}";\n`;
195
+ if (p.extraImport && !content.includes(p.extraImport.name)) {
196
+ importLine += `import { ${p.extraImport.name} } from "${p.extraImport.from}";\n`;
197
+ }
198
+ content = content.slice(0, insertAt) + importLine + content.slice(insertAt);
158
199
  }
159
200
  }
160
201
  content = content.replace(/const withProviders\s*=\s*\([^)]*\)\s*=>\s*[\s\S]+?;\s*\n?/g, "").replace(/\n{3,}/g, "\n\n");
@@ -652,6 +652,24 @@ function buildDefaultArgsForRequiredProps(props, usageFromPages = null, componen
652
652
  } else if (/string/.test(type)) {
653
653
  argLines.push(` ${name}: ${stringFallback(name)},`);
654
654
  added.add(name);
655
+ } else if (/number/.test(type) || /^(value|amount|total|count|hours|minutes|progress|percent)$/.test(name)) {
656
+ argLines.push(` ${name}: 0,`);
657
+ added.add(name);
658
+ }
659
+ }
660
+ // Optional props that often cause "reading X of undefined" in Storybook if missing
661
+ const NUMBER_LIKE_OPTIONAL = new Set(["value", "amount", "total", "count", "hours", "minutes", "progress", "percent", "visibility"]);
662
+ const DATE_RANGE_DEFAULT = `{ dateRange: { from: new Date().toISOString(), to: new Date().toISOString() } }`;
663
+ for (const p of props) {
664
+ const type = String(p.type || "").trim();
665
+ const name = p.name;
666
+ if (added.has(name)) continue;
667
+ if (name === "filters" || /dateRange|Filter.*object/.test(type)) {
668
+ argLines.push(` ${name}: ${DATE_RANGE_DEFAULT},`);
669
+ added.add(name);
670
+ } else if (/number/.test(type) || NUMBER_LIKE_OPTIONAL.has(name)) {
671
+ argLines.push(` ${name}: ${name === "visibility" ? "1" : "0"},`);
672
+ added.add(name);
655
673
  }
656
674
  }
657
675
  }