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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-design-system",
3
- "version": "2.5.3",
3
+ "version": "2.5.5",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- let importLines = ['import * as React from "react";'];
727
- if (lucideTags.length > 0) {
728
- importLines.push(`import { ${lucideTags.join(", ")} } from "lucide-react";`);
729
- }
730
- for (const tag of uiTags) {
731
- if (!lucideTags.includes(tag)) {
732
- importLines.push(`import { ${tag} } from "@/components/ui/${toKebab(tag)}";`);
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 * as React from "react";
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");