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
|
@@ -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:**
|
|
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).
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|