vibe-design-system 2.3.0 → 2.4.0

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.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -452,32 +452,39 @@ function humanizeName(filePath) {
452
452
  return base.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
453
453
  }
454
454
 
455
- const COMPONENT_SUGGESTION_KEYWORDS = [
455
+ // Priority order: first match in pattern wins
456
+ const COMPONENT_SUGGESTION_KEYWORDS_ORDERED = [
456
457
  { re: /\bcard\b/i, name: "Card" },
457
458
  { re: /\bsection\b/i, name: "Section" },
458
- { re: /\bitem\b/i, name: "Item" },
459
- { re: /\bbadge\b/i, name: "Badge" },
460
- { re: /\brow\b/i, name: "Row" },
461
459
  { re: /\bgrid\b/i, name: "Grid" },
462
460
  { re: /\bhero\b/i, name: "Hero" },
463
461
  { re: /\bcta\b/i, name: "Cta" },
464
- { re: /\bstat\b/i, name: "Stat" },
465
- { re: /\bprice\b/i, name: "Pricing" },
466
- { re: /\bfeature\b/i, name: "Feature" },
467
- { re: /\bbutton\b/i, name: "Button" },
468
- { re: /\btag\b/i, name: "Tag" },
469
- { re: /\bchip\b/i, name: "Chip" },
470
- { re: /\btile\b/i, name: "Tile" },
471
- { re: /\bbanner\b/i, name: "Banner" },
462
+ { re: /\bnav\b/i, name: "Nav" },
463
+ { re: /\bfooter\b/i, name: "Footer" },
464
+ { re: /\bheader\b/i, name: "Header" },
465
+ { re: /\bform\b/i, name: "Form" },
466
+ { re: /\bmodal\b/i, name: "Modal" },
467
+ { re: /\bbadge\b/i, name: "Badge" },
468
+ { re: /\bitem\b/i, name: "Item" },
469
+ { re: /\brow\b/i, name: "Row" },
470
+ { re: /\blist\b/i, name: "List" },
472
471
  ];
473
472
 
474
- function suggestNameFromPattern(pattern, index) {
475
- const parts = [];
476
- for (const { re, name } of COMPONENT_SUGGESTION_KEYWORDS) {
477
- if (re.test(pattern)) parts.push(name);
473
+ function suggestNameFromPattern(pattern) {
474
+ for (const { re, name } of COMPONENT_SUGGESTION_KEYWORDS_ORDERED) {
475
+ if (re.test(pattern)) return name;
478
476
  }
479
- if (parts.length > 0) return parts.join("");
480
- return "Block" + (index + 1);
477
+ const firstToken = pattern.split(/\s+/)[0] || "";
478
+ if (/^max-w|^mx-auto|^container$/i.test(firstToken)) return "Container";
479
+ if (/^flex\b/i.test(firstToken)) return "FlexRow";
480
+ if (/^grid\b/i.test(firstToken)) return "Grid";
481
+ if (firstToken.length > 0) {
482
+ const pascal = firstToken
483
+ .replace(/-([a-z0-9])/gi, (_, c) => c.toUpperCase())
484
+ .replace(/^(.)/, (_, c) => c.toUpperCase());
485
+ return pascal.replace(/[^a-zA-Z0-9]/g, "") || "Block";
486
+ }
487
+ return "Block";
481
488
  }
482
489
 
483
490
  /** Scan src/pages/*.tsx for repeated className clusters; return component suggestions. */
@@ -516,11 +523,9 @@ function extractComponentSuggestions() {
516
523
  }
517
524
 
518
525
  const suggestions = [];
519
- let blockIndex = 0;
520
526
  for (const [pattern, { count, files, snippet }] of byPattern.entries()) {
521
527
  if (count < 2) continue;
522
- const suggestedName = suggestNameFromPattern(pattern, blockIndex);
523
- if (suggestedName.startsWith("Block")) blockIndex += 1;
528
+ const suggestedName = suggestNameFromPattern(pattern);
524
529
  suggestions.push({
525
530
  suggestedName,
526
531
  occurrences: count,
@@ -750,8 +755,6 @@ function extractFoundations() {
750
755
  const radius = {};
751
756
  if (cssRadiusVars.radius) radius.base = cssRadiusVars.radius;
752
757
  if (Object.keys(borderRadiusScale).length > 0) radius.borderRadius = borderRadiusScale;
753
- const foundationsColors = { ...colors };
754
- if (Object.keys(colorsDark).length > 0) foundationsColors._dark = colorsDark;
755
758
 
756
759
  // Fallback: if no typography tokens from config, extract from Google Fonts @import
757
760
  if (Object.keys(typography).length === 0 || (!typography.body && !typography.bodyFontFamily)) {
@@ -777,19 +780,34 @@ function extractFoundations() {
777
780
  }
778
781
 
779
782
  // ── FALLBACK 1: Arbitrary Tailwind values ──────────────────
780
- const srcRelFiles = getAllTsxJsxInDir(SRC_DIR);
781
- const allSourceFiles = [
782
- ...srcRelFiles.map((f) => path.join(SRC_DIR, f)),
783
- path.join(PROJECT_ROOT, "index.html"),
784
- path.join(PROJECT_ROOT, "src", "index.css"),
785
- path.join(PROJECT_ROOT, "src", "globals.css"),
786
- path.join(PROJECT_ROOT, "src", "App.css"),
787
- path.join(PROJECT_ROOT, "app", "globals.css"),
788
- ].filter((p) => fs.existsSync(p));
783
+ let allTsxFiles = [];
784
+ try {
785
+ const { globSync } = projectRequire("glob");
786
+ const ROOT = PROJECT_ROOT;
787
+ allTsxFiles = globSync("src/**/*.{tsx,jsx,ts,js}", {
788
+ cwd: ROOT,
789
+ absolute: true,
790
+ ignore: ["**/*.stories.*", "**/node_modules/**"],
791
+ });
792
+ } catch (_) {
793
+ function walkDir(dir, list) {
794
+ if (!fs.existsSync(dir)) return;
795
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
796
+ for (const e of entries) {
797
+ const full = path.join(dir, e.name);
798
+ if (e.isDirectory()) {
799
+ if (e.name !== "node_modules" && e.name !== "dist") walkDir(full, list);
800
+ } else if (/\.(tsx|jsx|ts|js)$/i.test(e.name) && !e.name.includes(".stories.")) {
801
+ list.push(full);
802
+ }
803
+ }
804
+ }
805
+ walkDir(SRC_DIR, allTsxFiles);
806
+ }
789
807
 
790
808
  const ARBITRARY_HEX = /(?:bg|text|border|ring|fill|stroke|from|to|via)-\[#([0-9a-fA-F]{3,8})\]/g;
791
809
  const arbitraryColors = new Map();
792
- for (const file of allSourceFiles) {
810
+ for (const file of allTsxFiles) {
793
811
  try {
794
812
  const content = fs.readFileSync(file, "utf-8");
795
813
  for (const match of content.matchAll(ARBITRARY_HEX)) {
@@ -813,18 +831,23 @@ function extractFoundations() {
813
831
  const name = "arbitrary-" + hex.slice(1).toLowerCase();
814
832
  colors[name] = { value: hex, source: "arbitrary-tailwind", frequency: count };
815
833
  }
816
-
817
834
  // ── FALLBACK 2: Google Fonts URL ──────────────────────────
818
835
  const GFONTS_PATTERN = /family=([A-Za-z+]+)(?::.*?)?(?:&|$)/g;
819
- const fontFiles = allSourceFiles.filter(
820
- (f) =>
821
- f.endsWith("index.html") ||
822
- f.includes("layout.") ||
823
- f.includes("_document.") ||
824
- f.endsWith("index.css") ||
825
- f.includes("globals.css")
826
- );
827
- for (const file of fontFiles) {
836
+ const fontFileCandidates = [
837
+ ...allTsxFiles.filter(
838
+ (f) =>
839
+ f.includes("layout.") ||
840
+ f.includes("_document.") ||
841
+ f.endsWith("index.css") ||
842
+ f.includes("globals.css")
843
+ ),
844
+ path.join(PROJECT_ROOT, "index.html"),
845
+ path.join(PROJECT_ROOT, "src", "index.css"),
846
+ path.join(PROJECT_ROOT, "src", "globals.css"),
847
+ path.join(PROJECT_ROOT, "src", "App.css"),
848
+ path.join(PROJECT_ROOT, "app", "globals.css"),
849
+ ].filter((p) => fs.existsSync(p));
850
+ for (const file of fontFileCandidates) {
828
851
  try {
829
852
  const content = fs.readFileSync(file, "utf-8");
830
853
  if (!content.includes("fonts.googleapis.com")) continue;
@@ -839,6 +862,9 @@ function extractFoundations() {
839
862
  } catch (_) {}
840
863
  }
841
864
 
865
+ const foundationsColors = { ...colors };
866
+ if (Object.keys(colorsDark).length > 0) foundationsColors._dark = colorsDark;
867
+
842
868
  const twTheme = getTailwindTheme();
843
869
  const normalizeThemeObj = (obj) => {
844
870
  if (!obj || typeof obj !== "object") return {};
@@ -831,7 +831,7 @@ function writeChangelogStory(changelog) {
831
831
  " render: () => (",
832
832
  " <div style={{ fontFamily: \"monospace\", padding: 24 }}>",
833
833
  " <h2>CHANGELOG</h2>",
834
- " {changelog.length === 0 ? <p>No changes recorded yet. Run npm run vds again after making changes.</p> : changelog.map((entry) => (",
834
+ " {changelog.length === 0 ? <p>No changes recorded yet.</p> : changelog.map((entry) => (",
835
835
  " <div key={entry.version}>",
836
836
  " <h3>v{entry.version} — {entry.date}</h3>",
837
837
  " {entry.changes.map((c, i) => <div key={i}>{c.type}: {c.name ?? c.key ?? \"\"}</div>)}",