vibe-design-system 2.8.2 → 2.8.4
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.8.
|
|
3
|
+
"version": "2.8.4",
|
|
4
4
|
"description": "Auto-generate design systems for vibe coding projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,4 +38,4 @@
|
|
|
38
38
|
"typescript": "^5.8.3",
|
|
39
39
|
"vite": "^5.4.19"
|
|
40
40
|
}
|
|
41
|
-
}
|
|
41
|
+
}
|
|
@@ -951,6 +951,17 @@ function writeVdsGeneratedComponents(suggestions) {
|
|
|
951
951
|
|
|
952
952
|
let bodyJsx;
|
|
953
953
|
const tokens = [];
|
|
954
|
+
// Known shadcn/UI component names — not Lucide icons
|
|
955
|
+
const KNOWN_UI_COMPONENTS = new Set([
|
|
956
|
+
"Button", "Card", "CardHeader", "CardTitle", "CardContent", "CardFooter",
|
|
957
|
+
"Input", "Label", "Textarea", "Select", "Checkbox", "Switch", "Badge",
|
|
958
|
+
"Avatar", "Dialog", "Sheet", "Drawer", "Tooltip", "Popover", "Tabs",
|
|
959
|
+
"Table", "Accordion", "Alert", "Toast", "Form", "Skeleton", "Separator",
|
|
960
|
+
"Progress", "Slider", "RadioGroup", "Toggle", "Command", "Calendar",
|
|
961
|
+
"DropdownMenu", "ContextMenu", "NavigationMenu", "Menubar", "HoverCard",
|
|
962
|
+
"Collapsible", "ScrollArea", "AspectRatio", "Carousel", "Resizable",
|
|
963
|
+
"Link", "Nav", "Header", "Footer", "Section", "Container", "Wrapper",
|
|
964
|
+
]);
|
|
954
965
|
if (s.fullJsx && s.fullJsx.trim().length > 0) {
|
|
955
966
|
bodyJsx = flattenJsx(s.fullJsx);
|
|
956
967
|
bodyJsx = convertHtmlStyleToReact(bodyJsx);
|
|
@@ -958,17 +969,28 @@ function writeVdsGeneratedComponents(suggestions) {
|
|
|
958
969
|
const lucideTags = [];
|
|
959
970
|
const uiTags = [];
|
|
960
971
|
for (const tag of componentTags) {
|
|
961
|
-
if (
|
|
962
|
-
|
|
972
|
+
if (KNOWN_UI_COMPONENTS.has(tag)) {
|
|
973
|
+
uiTags.push(tag);
|
|
974
|
+
} else if (/^[A-Z][a-zA-Z0-9]+$/.test(tag) && tag.length > 2) {
|
|
975
|
+
lucideTags.push(tag); // likely a Lucide icon or unknown component
|
|
976
|
+
} else {
|
|
977
|
+
uiTags.push(tag);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (lucideTags.length > 0) {
|
|
981
|
+
bodyJsx = applyLucideNamespace(bodyJsx, lucideTags);
|
|
982
|
+
}
|
|
983
|
+
const importLines = ['import React from "react";'];
|
|
984
|
+
if (bodyJsx.includes("motion.") || bodyJsx.includes("<motion")) {
|
|
985
|
+
importLines.push('import { motion } from "framer-motion";');
|
|
986
|
+
}
|
|
987
|
+
if (lucideTags.length > 0) {
|
|
988
|
+
importLines.push('import * as LucideIcons from "lucide-react";');
|
|
989
|
+
}
|
|
990
|
+
for (const tag of uiTags) {
|
|
991
|
+
if (!KNOWN_UI_COMPONENTS.has(tag)) continue;
|
|
992
|
+
importLines.push(`import { ${tag} } from "@/components/ui/${toKebab(tag)}";`);
|
|
963
993
|
}
|
|
964
|
-
bodyJsx = applyLucideNamespace(bodyJsx, lucideTags);
|
|
965
|
-
const standardImports = [
|
|
966
|
-
'import React from "react";',
|
|
967
|
-
'import { motion } from "framer-motion";',
|
|
968
|
-
'import * as LucideIcons from "lucide-react";',
|
|
969
|
-
];
|
|
970
|
-
const uiImportLines = uiTags.map((tag) => `import { ${tag} } from "@/components/ui/${toKebab(tag)}";`);
|
|
971
|
-
const importLines = [...standardImports, ...uiImportLines];
|
|
972
994
|
const content = importLines.join("\n") + "\n\n" +
|
|
973
995
|
`export function ${componentName}() {\n return (\n ` +
|
|
974
996
|
bodyJsx.replace(/\n/g, "\n ") + "\n );\n}\n";
|
|
@@ -978,16 +1000,7 @@ function writeVdsGeneratedComponents(suggestions) {
|
|
|
978
1000
|
const tagName = s.tagName || "div";
|
|
979
1001
|
const className = s.pattern || "";
|
|
980
1002
|
bodyJsx = `<${tagName} className="${className.replace(/"/g, '\\"')}">\n {null}\n </${tagName}>`;
|
|
981
|
-
const content = `import React from "react"
|
|
982
|
-
import { motion } from "framer-motion";
|
|
983
|
-
import * as LucideIcons from "lucide-react";
|
|
984
|
-
|
|
985
|
-
export function ${componentName}() {
|
|
986
|
-
return (
|
|
987
|
-
${bodyJsx}
|
|
988
|
-
);
|
|
989
|
-
}
|
|
990
|
-
`;
|
|
1003
|
+
const content = `import React from "react";\n\nexport function ${componentName}() {\n return (\n ${bodyJsx}\n );\n}\n`;
|
|
991
1004
|
fs.writeFileSync(fullPath, content, "utf-8");
|
|
992
1005
|
tokens.push(...(className || "").split(/\s+/).filter(Boolean));
|
|
993
1006
|
}
|
|
@@ -995,8 +1008,9 @@ export function ${componentName}() {
|
|
|
995
1008
|
entries.push({
|
|
996
1009
|
file: relPath,
|
|
997
1010
|
name: componentName.replace(/([A-Z])/g, " $1").trim(),
|
|
998
|
-
|
|
999
|
-
|
|
1011
|
+
exportName: componentName,
|
|
1012
|
+
group: "Generated",
|
|
1013
|
+
category: "Generated",
|
|
1000
1014
|
description: "Auto-extracted from repeated className patterns.",
|
|
1001
1015
|
tokens: [...new Set(tokens)],
|
|
1002
1016
|
occurrences: s.occurrences ?? 0,
|
|
@@ -1635,6 +1649,7 @@ function scan() {
|
|
|
1635
1649
|
engineer: getGitEngineer(),
|
|
1636
1650
|
scannedAt: new Date().toISOString(),
|
|
1637
1651
|
totalComponents: results.length,
|
|
1652
|
+
componentsRelDir: COMPONENTS_DIR ? path.relative(PROJECT_ROOT, COMPONENTS_DIR).replace(/\\/g, "/") : "src/components",
|
|
1638
1653
|
components: results,
|
|
1639
1654
|
foundations,
|
|
1640
1655
|
componentSuggestions,
|
|
@@ -294,6 +294,29 @@ function buildWithProvidersCode(providers) {
|
|
|
294
294
|
return `const withProviders = (Story: any) => ${inner};`;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
/** ErrorBoundary class – injected into .storybook/preview to catch story render errors (missing providers, undefined props, etc.) */
|
|
298
|
+
function buildErrorBoundaryCode() {
|
|
299
|
+
return [
|
|
300
|
+
"class VdsErrorBoundary extends React.Component {",
|
|
301
|
+
" constructor(props) { super(props); this.state = { error: null }; }",
|
|
302
|
+
" static getDerivedStateFromError(error) { return { error }; }",
|
|
303
|
+
" componentDidCatch(err) { console.error(\"[VDS Story Error]\", err); }",
|
|
304
|
+
" render() {",
|
|
305
|
+
" const s = this.state; const p = this.props;",
|
|
306
|
+
" if (s && s.error) return React.createElement(\"div\", {",
|
|
307
|
+
" style: { padding: 16, color: \"#ff6b6b\", border: \"1px solid #ff6b6b\", borderRadius: 8, fontFamily: \"monospace\", margin: 8 }",
|
|
308
|
+
" }, React.createElement(\"b\", null, \"⚠️ Story Error: \"), React.createElement(\"code\", { style: { whiteSpace: \"pre-wrap\" } }, String(s.error.message)));",
|
|
309
|
+
" return p.children;",
|
|
310
|
+
" }",
|
|
311
|
+
"}",
|
|
312
|
+
"const withErrorBoundary = (Story) => React.createElement(VdsErrorBoundary, null, React.createElement(Story));",
|
|
313
|
+
].join("\n");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// VDS markers for idempotent preview injection
|
|
317
|
+
const VDS_BLOCK_START = "// [VDS:providers-start]";
|
|
318
|
+
const VDS_BLOCK_END = "// [VDS:providers-end]";
|
|
319
|
+
|
|
297
320
|
function injectProviderDecorators(projectRoot) {
|
|
298
321
|
const previewPath = getPreviewPath(projectRoot);
|
|
299
322
|
if (!previewPath) {
|
|
@@ -302,44 +325,83 @@ function injectProviderDecorators(projectRoot) {
|
|
|
302
325
|
}
|
|
303
326
|
|
|
304
327
|
const { providersToAdd, hooksWithoutProvider, hooksByFile } = collectProvidersAndWarnings(projectRoot);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
328
|
+
let content = fs.readFileSync(previewPath, "utf-8");
|
|
329
|
+
|
|
330
|
+
// 1. Ensure React import exists (needed for error boundary + providers)
|
|
331
|
+
if (!content.includes("import React") && !content.includes("import React from")) {
|
|
332
|
+
const firstImport = content.match(/^import\s+/m);
|
|
333
|
+
const insertAt = firstImport ? content.indexOf(firstImport[0]) : 0;
|
|
334
|
+
content = content.slice(0, insertAt) + "import React from \"react\";\n" + content.slice(insertAt);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 2. Add provider imports if not already present (use regex to detect existing import)
|
|
338
|
+
const uniqueProviders = [...new Map(providersToAdd.map((p) => [p.name, p])).values()];
|
|
339
|
+
for (const p of uniqueProviders) {
|
|
340
|
+
const alreadyImported = new RegExp(`\\bimport[^;]*\\b${p.name}\\b[^;]*from`).test(content);
|
|
341
|
+
if (!alreadyImported) {
|
|
342
|
+
const importMatches = [...content.matchAll(/^import\s+[^\n]+;/gm)];
|
|
343
|
+
const lastMatch = importMatches[importMatches.length - 1];
|
|
344
|
+
const insertAt = lastMatch ? lastMatch.index + lastMatch[0].length + 1 : content.indexOf("\n") + 1;
|
|
345
|
+
let importLine = `import { ${p.name} } from "${p.importPath}";\n`;
|
|
346
|
+
if (p.extraImport && !new RegExp(`\\b${p.extraImport.name}\\b`).test(content)) {
|
|
347
|
+
importLine += `import { ${p.extraImport.name} } from "${p.extraImport.from}";\n`;
|
|
324
348
|
}
|
|
349
|
+
content = content.slice(0, insertAt) + importLine + content.slice(insertAt);
|
|
325
350
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// 3. Remove previous VDS injections (idempotent re-runs)
|
|
354
|
+
content = content.replace(new RegExp(VDS_BLOCK_START.replace(/[\[\]]/g, "\\$&") + "[\\s\\S]*?" + VDS_BLOCK_END.replace(/[\[\]]/g, "\\$&") + "\\n?"), "");
|
|
355
|
+
// Remove old-style single-line withProviders (pre-marker era)
|
|
356
|
+
content = content.replace(/^const withProviders\s*=\s*\([^)]*\)\s*=>.*;\n?/m, "");
|
|
357
|
+
// Remove VDS decorator names from decorators arrays so we can re-inject cleanly
|
|
358
|
+
content = content.replace(/\bwithErrorBoundary\s*,\s*/g, "").replace(/,\s*\bwithErrorBoundary\b/g, "").replace(/\bwithErrorBoundary\b/g, "");
|
|
359
|
+
content = content.replace(/\bwithProviders\s*,\s*/g, "").replace(/,\s*\bwithProviders\b/g, "").replace(/\bwithProviders\b/g, "");
|
|
360
|
+
// Fix any syntax damage: leading comma, trailing comma, empty decorators
|
|
361
|
+
content = content.replace(/\[\s*,\s*/g, "[").replace(/,\s*\]/g, "]");
|
|
362
|
+
content = content.replace(/\s*decorators:\s*\[\s*\]\s*,?\n?/g, "");
|
|
363
|
+
content = content.replace(/\n{3,}/g, "\n\n");
|
|
364
|
+
|
|
365
|
+
// 4. Build new VDS block (error boundary + optional provider wrapper)
|
|
366
|
+
const errorBoundaryCode = buildErrorBoundaryCode();
|
|
367
|
+
const withProvidersCode = uniqueProviders.length > 0 ? buildWithProvidersCode(providersToAdd) : null;
|
|
368
|
+
const vdsCode = [errorBoundaryCode, withProvidersCode].filter(Boolean).join("\n\n");
|
|
369
|
+
const vdsBlock = VDS_BLOCK_START + "\n" + vdsCode + "\n" + VDS_BLOCK_END;
|
|
370
|
+
|
|
371
|
+
// 5. Find insertion point: before "const preview" or "export default {"
|
|
372
|
+
let insertBefore = content.indexOf("const preview");
|
|
373
|
+
if (insertBefore === -1) {
|
|
374
|
+
const edMatch = content.match(/export\s+default\s*[\{(]/);
|
|
375
|
+
if (edMatch) insertBefore = content.indexOf(edMatch[0]);
|
|
376
|
+
}
|
|
377
|
+
if (insertBefore === -1) insertBefore = content.length;
|
|
378
|
+
content = content.slice(0, insertBefore) + vdsBlock + "\n\n" + content.slice(insertBefore);
|
|
379
|
+
|
|
380
|
+
// 6. Inject decorators into preview object
|
|
381
|
+
const decoratorItems = ["withErrorBoundary", ...(withProvidersCode ? ["withProviders"] : [])];
|
|
382
|
+
const decoratorsStr = decoratorItems.join(", ");
|
|
383
|
+
if (content.includes("decorators:")) {
|
|
384
|
+
// Prepend VDS decorators to existing decorators array
|
|
385
|
+
content = content.replace(/\bdecorators:\s*\[/, `decorators: [${decoratorsStr}, `);
|
|
386
|
+
content = content.replace(/,\s*\]/g, "]");
|
|
387
|
+
} else {
|
|
388
|
+
// Add new decorators key to preview object
|
|
389
|
+
const constPreviewMatch = content.match(/(const\s+preview\s*[^=\n]*=\s*\{)/);
|
|
390
|
+
if (constPreviewMatch) {
|
|
391
|
+
content = content.replace(constPreviewMatch[0], constPreviewMatch[0] + `\n decorators: [${decoratorsStr}],`);
|
|
392
|
+
} else {
|
|
393
|
+
const exportDefaultMatch = content.match(/(export\s+default\s*\{)/);
|
|
394
|
+
if (exportDefaultMatch) {
|
|
395
|
+
content = content.replace(exportDefaultMatch[0], exportDefaultMatch[0] + `\n decorators: [${decoratorsStr}],`);
|
|
337
396
|
}
|
|
338
397
|
}
|
|
339
|
-
fs.writeFileSync(previewPath, content, "utf-8");
|
|
340
|
-
console.log("[VDS] Storybook preview: " + uniqueProviders.map((p) => p.name).join(", "));
|
|
341
398
|
}
|
|
342
399
|
|
|
400
|
+
content = content.replace(/\n{3,}/g, "\n\n");
|
|
401
|
+
fs.writeFileSync(previewPath, content, "utf-8");
|
|
402
|
+
console.log("[VDS] Storybook preview: ErrorBoundary" + (uniqueProviders.length ? " + " + uniqueProviders.map((p) => p.name).join(", ") : ""));
|
|
403
|
+
|
|
404
|
+
// 7. Handle hooks without provider: add warning comment to affected story files
|
|
343
405
|
if (hooksWithoutProvider.size > 0) {
|
|
344
406
|
const componentNameFromPath = (rel) => path.basename(rel, path.extname(rel));
|
|
345
407
|
const storiesDir = path.join(projectRoot, "src", "stories");
|
|
@@ -22,6 +22,8 @@ const STORIES_DIR = path.join(SRC_DIR, "stories");
|
|
|
22
22
|
|
|
23
23
|
// Filled in main() after reading vds-output.json so helpers (buildSpecialStories) can see foundations data (e.g. buttonUsage).
|
|
24
24
|
let FOUNDATIONS_DATA = null;
|
|
25
|
+
// componentsRelDir from vds-output.json — defaults to "src/components"
|
|
26
|
+
let COMPONENTS_REL_DIR = "src/components";
|
|
25
27
|
|
|
26
28
|
// CSS is loaded from .storybook/preview.tsx — never add CSS import to story files.
|
|
27
29
|
|
|
@@ -521,7 +523,7 @@ const COMPONENT_EXTRA_ARGS = {
|
|
|
521
523
|
" taskId: \"1\",",
|
|
522
524
|
],
|
|
523
525
|
TimeFilterPanel: [
|
|
524
|
-
" filter: { dateRange: {}, userIds: [], tags: [], billable: \"all\" },",
|
|
526
|
+
" filter: { preset: null, dateRange: {}, userIds: [], tags: [], billable: \"all\" },",
|
|
525
527
|
" onFilterChange: () => {},",
|
|
526
528
|
" onSaveFilter: () => {},",
|
|
527
529
|
" onDeleteFilter: () => {},",
|
|
@@ -546,7 +548,7 @@ const RENDER_ARGS_FALLBACKS = {
|
|
|
546
548
|
/** Kalıcı çözüm: Storybook Docs/Controls args'ı geçmezse bile component'a güvenli props veren wrapper. Önce safeDefaults uygulanır, sonra gelen props. */
|
|
547
549
|
const SAFE_WRAPPER_DEFAULTS = {
|
|
548
550
|
TaskEstimateInput: `{ estimate: 0, onUpdate: () => {}, value: 0, task: { id: "1", title: "Example", estimate: 0 }, compact: false }`,
|
|
549
|
-
TimeFilterPanel: `{ filter: { dateRange: {}, userIds: [], tags: [], billable: "all" }, onFilterChange: () => {}, onSaveFilter: () => {}, onDeleteFilter: () => {}, filteredCount: 0, savedFilters: [] }`,
|
|
551
|
+
TimeFilterPanel: `{ filter: { preset: null, dateRange: {}, userIds: [], tags: [], billable: "all" }, onFilterChange: () => {}, onSaveFilter: () => {}, onDeleteFilter: () => {}, filteredCount: 0, savedFilters: [] }`,
|
|
550
552
|
TimeStats: `{ totalLogged: 0, totalBillable: 0, totalNonBillable: 0, totalBilled: 0, totalEstimated: 0, projectTotalEstimated: 0 }`,
|
|
551
553
|
};
|
|
552
554
|
|
|
@@ -1052,12 +1054,18 @@ function buildRecipeStoryContent(comp, componentName, importPath, title, source,
|
|
|
1052
1054
|
function buildStoryFileContent(comp) {
|
|
1053
1055
|
const componentName = toSafeComponentName(comp.name, comp.file);
|
|
1054
1056
|
const fileNoExt = comp.file.replace(/\.(tsx|jsx)$/, "");
|
|
1057
|
+
// Compute import path relative to src/stories/ using the actual components directory.
|
|
1055
1058
|
let importPath = "";
|
|
1056
|
-
|
|
1057
|
-
|
|
1059
|
+
const isPageFile = fileNoExt.startsWith("pages/") || fileNoExt.startsWith("src/pages/");
|
|
1060
|
+
if (isPageFile) {
|
|
1061
|
+
const normalized = fileNoExt.replace(/^src\//, "");
|
|
1062
|
+
importPath = path.posix.relative("src/stories", normalized);
|
|
1058
1063
|
} else {
|
|
1059
|
-
|
|
1064
|
+
const targetPath = path.posix.join(COMPONENTS_REL_DIR, fileNoExt);
|
|
1065
|
+
importPath = path.posix.relative("src/stories", targetPath);
|
|
1060
1066
|
}
|
|
1067
|
+
if (!importPath.startsWith(".")) importPath = "./" + importPath;
|
|
1068
|
+
|
|
1061
1069
|
const group = comp.group || "Components";
|
|
1062
1070
|
const category = comp.category || null;
|
|
1063
1071
|
const titleParts = [group, category, componentName].filter(Boolean);
|
|
@@ -1067,9 +1075,9 @@ function buildStoryFileContent(comp) {
|
|
|
1067
1075
|
const variantProp = props.find((p) => p.name === "variant");
|
|
1068
1076
|
let variants = parseUnionLiterals(variantProp && variantProp.type);
|
|
1069
1077
|
|
|
1070
|
-
const srcPath =
|
|
1071
|
-
? path.join(
|
|
1072
|
-
: path.join(
|
|
1078
|
+
const srcPath = isPageFile
|
|
1079
|
+
? path.join(PROJECT_ROOT, fileNoExt.replace(/^src\//, "src/") + ".tsx")
|
|
1080
|
+
: path.join(PROJECT_ROOT, COMPONENTS_REL_DIR, comp.file);
|
|
1073
1081
|
|
|
1074
1082
|
// Fallback: if manifest doesn't have variant metadata yet, parse cva() directly from component file.
|
|
1075
1083
|
if (!variants.length) {
|
|
@@ -1105,7 +1113,7 @@ function buildStoryFileContent(comp) {
|
|
|
1105
1113
|
}
|
|
1106
1114
|
const exportStyle = detectExportStyle(source, componentName);
|
|
1107
1115
|
const omitChildren = componentWrapsVoidElement(source);
|
|
1108
|
-
const isPage =
|
|
1116
|
+
const isPage = isPageFile;
|
|
1109
1117
|
|
|
1110
1118
|
// Props: manifest or parse from source for default args (LucideIcon, array types, ReactNode, contextual placeholders)
|
|
1111
1119
|
const effectiveProps = Array.isArray(comp.props) && comp.props.length > 0 ? comp.props : parsePropsFromSource(source);
|
|
@@ -1499,6 +1507,7 @@ function main() {
|
|
|
1499
1507
|
}
|
|
1500
1508
|
const raw = fs.readFileSync(VDS_OUTPUT, "utf-8");
|
|
1501
1509
|
const data = JSON.parse(raw);
|
|
1510
|
+
COMPONENTS_REL_DIR = data.componentsRelDir || "src/components";
|
|
1502
1511
|
const components = Array.isArray(data.components) ? data.components : [];
|
|
1503
1512
|
const foundations = data.foundations || null;
|
|
1504
1513
|
FOUNDATIONS_DATA = foundations;
|