vibe-design-system 2.5.3 → 2.5.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/package.json
CHANGED
|
@@ -666,7 +666,6 @@ function flattenJsx(jsx) {
|
|
|
666
666
|
continue;
|
|
667
667
|
}
|
|
668
668
|
if (jsx[i] === "{" && (i === 0 || jsx[i - 1] !== "{")) {
|
|
669
|
-
const start = i;
|
|
670
669
|
i++;
|
|
671
670
|
let depth = 1;
|
|
672
671
|
while (i < len && depth > 0) {
|
|
@@ -684,6 +683,39 @@ function flattenJsx(jsx) {
|
|
|
684
683
|
return result;
|
|
685
684
|
}
|
|
686
685
|
|
|
686
|
+
/** Convert HTML style="key: value; ..." to React style={{ key: 'value', ... }} (camelCase keys). */
|
|
687
|
+
function convertHtmlStyleToReact(jsx) {
|
|
688
|
+
return jsx.replace(
|
|
689
|
+
/style\s*=\s*["']([^"']*)["']/g,
|
|
690
|
+
(_, cssStr) => {
|
|
691
|
+
const obj = {};
|
|
692
|
+
const parts = (cssStr || "").split(";").map((s) => s.trim()).filter(Boolean);
|
|
693
|
+
for (const part of parts) {
|
|
694
|
+
const colon = part.indexOf(":");
|
|
695
|
+
if (colon <= 0) continue;
|
|
696
|
+
const key = part.slice(0, colon).trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
697
|
+
const value = part.slice(colon + 1).trim().replace(/^["']|["']$/g, "");
|
|
698
|
+
if (key) obj[key] = value;
|
|
699
|
+
}
|
|
700
|
+
if (Object.keys(obj).length === 0) return "style={{}}";
|
|
701
|
+
const entries = Object.entries(obj).map(([k, v]) => `${k}: "${String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`).join(", ");
|
|
702
|
+
return `style={{ ${entries} }}`;
|
|
703
|
+
}
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** Replace Lucide icon tags with LucideIcons.TagName so namespace import works. */
|
|
708
|
+
function applyLucideNamespace(jsx, lucideTags) {
|
|
709
|
+
let out = jsx;
|
|
710
|
+
for (const tag of lucideTags) {
|
|
711
|
+
const openRe = new RegExp("<" + tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?=[\\s/>])", "g");
|
|
712
|
+
const closeRe = new RegExp("</" + tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + ">", "g");
|
|
713
|
+
out = out.replace(openRe, "<LucideIcons." + tag);
|
|
714
|
+
out = out.replace(closeRe, "</LucideIcons." + tag + ">");
|
|
715
|
+
}
|
|
716
|
+
return out;
|
|
717
|
+
}
|
|
718
|
+
|
|
687
719
|
/** Collect PascalCase tag names from JSX that are not known HTML. */
|
|
688
720
|
function collectComponentTags(jsx) {
|
|
689
721
|
const tags = new Set();
|
|
@@ -716,6 +748,7 @@ function writeVdsGeneratedComponents(suggestions) {
|
|
|
716
748
|
const tokens = [];
|
|
717
749
|
if (s.fullJsx && s.fullJsx.trim().length > 0) {
|
|
718
750
|
bodyJsx = flattenJsx(s.fullJsx);
|
|
751
|
+
bodyJsx = convertHtmlStyleToReact(bodyJsx);
|
|
719
752
|
const componentTags = collectComponentTags(bodyJsx);
|
|
720
753
|
const lucideTags = [];
|
|
721
754
|
const uiTags = [];
|
|
@@ -723,15 +756,14 @@ function writeVdsGeneratedComponents(suggestions) {
|
|
|
723
756
|
if (/^[A-Z][a-z0-9]+$/.test(tag) && tag.length > 2) lucideTags.push(tag);
|
|
724
757
|
else uiTags.push(tag);
|
|
725
758
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
759
|
+
bodyJsx = applyLucideNamespace(bodyJsx, lucideTags);
|
|
760
|
+
const standardImports = [
|
|
761
|
+
'import React from "react";',
|
|
762
|
+
'import { motion } from "framer-motion";',
|
|
763
|
+
'import * as LucideIcons from "lucide-react";',
|
|
764
|
+
];
|
|
765
|
+
const uiImportLines = uiTags.map((tag) => `import { ${tag} } from "@/components/ui/${toKebab(tag)}";`);
|
|
766
|
+
const importLines = [...standardImports, ...uiImportLines];
|
|
735
767
|
const content = importLines.join("\n") + "\n\n" +
|
|
736
768
|
`export function ${componentName}() {\n return (\n ` +
|
|
737
769
|
bodyJsx.replace(/\n/g, "\n ") + "\n );\n}\n";
|
|
@@ -741,7 +773,9 @@ function writeVdsGeneratedComponents(suggestions) {
|
|
|
741
773
|
const tagName = s.tagName || "div";
|
|
742
774
|
const className = s.pattern || "";
|
|
743
775
|
bodyJsx = `<${tagName} className="${className.replace(/"/g, '\\"')}">\n {null}\n </${tagName}>`;
|
|
744
|
-
const content = `import
|
|
776
|
+
const content = `import React from "react";
|
|
777
|
+
import { motion } from "framer-motion";
|
|
778
|
+
import * as LucideIcons from "lucide-react";
|
|
745
779
|
|
|
746
780
|
export function ${componentName}() {
|
|
747
781
|
return (
|
|
@@ -760,6 +794,7 @@ export function ${componentName}() {
|
|
|
760
794
|
category: "Extracted",
|
|
761
795
|
description: "Auto-extracted from repeated className patterns.",
|
|
762
796
|
tokens: [...new Set(tokens)],
|
|
797
|
+
occurrences: s.occurrences ?? 0,
|
|
763
798
|
});
|
|
764
799
|
}
|
|
765
800
|
return entries;
|
|
@@ -22,7 +22,8 @@ const STORIES_DIR = path.join(SRC_DIR, "stories");
|
|
|
22
22
|
|
|
23
23
|
// CSS is loaded from .storybook/preview.tsx — never add CSS import to story files.
|
|
24
24
|
|
|
25
|
-
// Components we don't want to auto-generate stories for (project-specific dashboards, heavy UIs, etc.)
|
|
25
|
+
// Components we don't want to auto-generate import-based stories for (project-specific dashboards, heavy UIs, etc.)
|
|
26
|
+
// vds-generated/ is handled separately: placeholder stories only (no import of broken Figma/Make JSX).
|
|
26
27
|
const SKIP_LIST = [
|
|
27
28
|
"AnalysisDashboard",
|
|
28
29
|
"ComponentLibrary",
|
|
@@ -380,6 +381,33 @@ function capitalize(str) {
|
|
|
380
381
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
381
382
|
}
|
|
382
383
|
|
|
384
|
+
/** Placeholder story for vds-generated extracted patterns (no import; broken Figma/Make JSX is not loaded). */
|
|
385
|
+
function buildExtractedPlaceholderStory(comp) {
|
|
386
|
+
const componentName = toSafeComponentName(comp.name, comp.file);
|
|
387
|
+
const occurrenceCount = comp.occurrences ?? 0;
|
|
388
|
+
const title = `VDS Generated/${componentName}`;
|
|
389
|
+
return [
|
|
390
|
+
'import type { Meta, StoryObj } from "@storybook/react";',
|
|
391
|
+
"",
|
|
392
|
+
`const meta = { title: ${JSON.stringify(title)} } satisfies Meta;`,
|
|
393
|
+
"export default meta;",
|
|
394
|
+
"type Story = StoryObj;",
|
|
395
|
+
"",
|
|
396
|
+
"export const Default: Story = {",
|
|
397
|
+
" render: () => (",
|
|
398
|
+
" <div style={{ padding: 24, border: \"2px dashed #ccc\", borderRadius: 8 }}>",
|
|
399
|
+
" <p style={{ color: \"#999\", fontSize: 12 }}>EXTRACTED PATTERN</p>",
|
|
400
|
+
` <h3 style={{ margin: \"0 0 8px 0\" }}>${componentName}</h3>`,
|
|
401
|
+
" <p style={{ color: \"#666\", margin: 0 }}>",
|
|
402
|
+
` This pattern was detected ${occurrenceCount} times in src/pages/.`,
|
|
403
|
+
" Extract it as a proper component to see it rendered here.",
|
|
404
|
+
" </p>",
|
|
405
|
+
" </div>",
|
|
406
|
+
" ),",
|
|
407
|
+
"};",
|
|
408
|
+
].join("\n");
|
|
409
|
+
}
|
|
410
|
+
|
|
383
411
|
function detectExportStyle(source, componentName) {
|
|
384
412
|
if (!source) return "unknown";
|
|
385
413
|
const hasDefault = /export\s+default\b/.test(source);
|
|
@@ -960,12 +988,22 @@ function main() {
|
|
|
960
988
|
for (const comp of components) {
|
|
961
989
|
const componentName = toSafeComponentName(comp.name, comp.file);
|
|
962
990
|
if (onlyName && componentName !== onlyName) continue;
|
|
991
|
+
|
|
992
|
+
const storyFileName = `${componentName}.stories.tsx`;
|
|
993
|
+
const storyPath = path.join(STORIES_DIR, storyFileName);
|
|
994
|
+
|
|
995
|
+
if (comp.file.startsWith("vds-generated/")) {
|
|
996
|
+
const content = buildExtractedPlaceholderStory(comp);
|
|
997
|
+
fs.writeFileSync(storyPath, content, "utf-8");
|
|
998
|
+
console.log(`[VDS] Wrote ${path.relative(PROJECT_ROOT, storyPath)} (placeholder)`);
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (!comp.file.startsWith("ui/") && !comp.file.startsWith("pages/")) continue;
|
|
963
1003
|
if (SKIP_LIST.includes(componentName)) continue;
|
|
964
1004
|
const requiredCount = Array.isArray(comp.props) ? comp.props.filter((p) => p.required === true).length : 0;
|
|
965
1005
|
if (requiredCount > 3) continue;
|
|
966
1006
|
|
|
967
|
-
const storyFileName = `${componentName}.stories.tsx`;
|
|
968
|
-
const storyPath = path.join(STORIES_DIR, storyFileName);
|
|
969
1007
|
const content = buildStoryFileContent(comp);
|
|
970
1008
|
if (content == null) continue;
|
|
971
1009
|
fs.writeFileSync(storyPath, content, "utf-8");
|