vibe-design-system 2.8.74 → 2.8.75

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.74",
3
+ "version": "2.8.75",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -522,26 +522,74 @@ function extractBrandAssets() {
522
522
  function extractLucideIconsUsed(srcDir) {
523
523
  const allFiles = getAllTsxJsxInDir(srcDir);
524
524
  const files = allFiles.filter((rel) => !/^stories[/\\]/.test(rel.replace(/\\/g, "/")));
525
+ // Pass 1: collect all imported icon names + track alias renames
525
526
  const names = new Set();
527
+ const aliasMap = new Map(); // localAlias → originalName (for `import { X as Y }`)
526
528
  const importRe = /import\s*\{([^}]+)\}\s*from\s*["']lucide-react["']/g;
529
+ const fileContents = new Map();
527
530
  for (const rel of files) {
528
531
  const fullPath = path.join(srcDir, rel);
529
532
  try {
530
533
  const content = fs.readFileSync(fullPath, "utf-8");
534
+ fileContents.set(rel, content);
531
535
  let m;
532
536
  while ((m = importRe.exec(content)) !== null) {
533
537
  const block = m[1];
534
538
  block.split(",").forEach((part) => {
535
539
  const trimmed = part.trim();
536
- const asMatch = trimmed.match(/^(\w+)\s+as\s+/);
537
- const name = asMatch ? asMatch[1] : trimmed.split(/\s+/)[0];
538
- if (name && /^[A-Z][a-zA-Z0-9]*$/.test(name)) names.add(name);
540
+ const asMatch = trimmed.match(/^(\w+)\s+as\s+(\w+)/);
541
+ if (asMatch) {
542
+ names.add(asMatch[1]);
543
+ aliasMap.set(asMatch[2], asMatch[1]); // Y → X
544
+ } else {
545
+ const name = trimmed.split(/\s+/)[0];
546
+ if (name && /^[A-Z][a-zA-Z0-9]*$/.test(name)) names.add(name);
547
+ }
539
548
  });
540
549
  }
541
550
  } catch (_) {}
542
551
  importRe.lastIndex = 0;
543
552
  }
544
- return [...names].sort();
553
+ if (names.size === 0) return [];
554
+
555
+ // Pass 2: count JSX usages per icon per component file
556
+ // Build lookup: all names + aliases → canonical icon name
557
+ const nameArr = [...names];
558
+ const allLocalNames = new Set([...nameArr]);
559
+ for (const [alias, orig] of aliasMap) if (names.has(orig)) allLocalNames.add(alias);
560
+
561
+ // usageCounts: iconName → Map<componentName, count>
562
+ const usageCounts = new Map();
563
+ for (const n of nameArr) usageCounts.set(n, new Map());
564
+
565
+ // JSX tag regex: matches <IconName, <IconName/ or <IconName>
566
+ const jsxTagRe = new RegExp(`<(${[...allLocalNames].join("|")})(?=[\\s/>])`, "g");
567
+
568
+ for (const [rel, content] of fileContents) {
569
+ const componentName = path.basename(rel).replace(/\.(tsx|jsx|ts|js)$/, "");
570
+ jsxTagRe.lastIndex = 0;
571
+ let m;
572
+ while ((m = jsxTagRe.exec(content)) !== null) {
573
+ const localName = m[1];
574
+ const canonName = aliasMap.has(localName) ? aliasMap.get(localName) : localName;
575
+ if (usageCounts.has(canonName)) {
576
+ const fm = usageCounts.get(canonName);
577
+ fm.set(componentName, (fm.get(componentName) || 0) + 1);
578
+ }
579
+ }
580
+ }
581
+
582
+ return nameArr
583
+ .map((name) => {
584
+ const fileMap = usageCounts.get(name) || new Map();
585
+ const total = [...fileMap.values()].reduce((a, b) => a + b, 0);
586
+ const topFiles = [...fileMap.entries()]
587
+ .sort((a, b) => b[1] - a[1])
588
+ .slice(0, 5)
589
+ .map(([f]) => f);
590
+ return { name, total, topFiles };
591
+ })
592
+ .sort((a, b) => b.total - a.total || a.name.localeCompare(b.name));
545
593
  }
546
594
 
547
595
  function extractVdsTags(content) {
@@ -1217,27 +1265,68 @@ function getTailwindExtendColors() {
1217
1265
  }
1218
1266
 
1219
1267
  function parseTailwindThemeFromText(raw) {
1220
- const result = { shadows: {}, spacing: {}, breakpoints: {}, zIndex: {}, transitionDuration: {}, transitionTimingFunction: {}, animation: {}, colors: {} };
1221
- try {
1222
- const spacingM = raw.match(/spacing\s*:\s*\{([\s\S]*?)\}/);
1223
- if (spacingM) {
1224
- const re = /["']?([\w.-]+)["']?\s*:\s*["']([^"']+)["']/g;
1225
- let m;
1226
- while ((m = re.exec(spacingM[1])) !== null) result.spacing[m[1]] = m[2];
1268
+ const result = {
1269
+ shadows: {}, spacing: {}, breakpoints: {}, zIndex: {},
1270
+ transitionDuration: {}, transitionTimingFunction: {}, animation: {}, colors: {},
1271
+ fontSize: {}, fontWeight: {},
1272
+ };
1273
+ // Helper: extract a shallow key:value block (handles one level of nesting)
1274
+ function extractBlock(key) {
1275
+ // Match "key: {" and capture until the matching closing brace (one level deep)
1276
+ const re = new RegExp(key + "\\s*:\\s*\\{");
1277
+ const start = raw.search(re);
1278
+ if (start === -1) return null;
1279
+ const open = raw.indexOf("{", start);
1280
+ if (open === -1) return null;
1281
+ let depth = 0, i = open;
1282
+ while (i < raw.length) {
1283
+ if (raw[i] === "{") depth++;
1284
+ else if (raw[i] === "}") { depth--; if (depth === 0) return raw.slice(open + 1, i); }
1285
+ i++;
1227
1286
  }
1228
- const screensM = raw.match(/screens\s*:\s*\{([\s\S]*?)\}/);
1229
- if (screensM) {
1230
- const re = /["']?([\w.-]+)["']?\s*:\s*["']([^"']+)["']/g;
1231
- let m;
1232
- while ((m = re.exec(screensM[1])) !== null) result.breakpoints[m[1]] = m[2];
1287
+ return null;
1288
+ }
1289
+ // Helper: parse simple key:"value" pairs from a block string
1290
+ function parseKV(block) {
1291
+ const out = {};
1292
+ if (!block) return out;
1293
+ const re = /["']?([\w.-]+)["']?\s*:\s*["']([^"']+)["']/g;
1294
+ let m;
1295
+ while ((m = re.exec(block)) !== null) out[m[1]] = m[2];
1296
+ return out;
1297
+ }
1298
+ // Helper: parse fontSize which can be string OR [string, {...}] (array form)
1299
+ function parseFontSize(block) {
1300
+ const out = {};
1301
+ if (!block) return out;
1302
+ // Array form: "base": ["1rem", { lineHeight: "1.5rem" }]
1303
+ const arrRe = /["']?([\w.-]+)["']?\s*:\s*\[\s*["']([^"']+)["']/g;
1304
+ let m;
1305
+ while ((m = arrRe.exec(block)) !== null) out[m[1]] = m[2];
1306
+ // Simple string form (fills in what array form missed): "xs": "0.75rem"
1307
+ const strRe = /["']?([\w.-]+)["']?\s*:\s*["']([^"']+)["']/g;
1308
+ while ((m = strRe.exec(block)) !== null) {
1309
+ if (!out[m[1]]) out[m[1]] = m[2];
1233
1310
  }
1311
+ return out;
1312
+ }
1313
+
1314
+ try {
1315
+ result.spacing = parseKV(extractBlock("spacing"));
1316
+ result.breakpoints = parseKV(extractBlock("screens"));
1317
+ result.zIndex = parseKV(extractBlock("zIndex"));
1318
+ result.transitionDuration = parseKV(extractBlock("transitionDuration"));
1319
+ result.transitionTimingFunction = parseKV(extractBlock("transitionTimingFunction"));
1320
+ result.animation = parseKV(extractBlock("animation"));
1321
+ result.fontWeight = parseKV(extractBlock("fontWeight"));
1322
+ result.fontSize = parseFontSize(extractBlock("fontSize"));
1234
1323
  } catch (_) {}
1235
1324
  return result;
1236
1325
  }
1237
1326
 
1238
1327
  /** Resolve Tailwind theme for boxShadow, spacing, screens, zIndex, motion. Uses resolveConfig for .js/.mjs, text-parse for .ts. */
1239
1328
  function getTailwindTheme() {
1240
- const empty = { shadows: {}, spacing: {}, breakpoints: {}, zIndex: {}, transitionDuration: {}, transitionTimingFunction: {}, animation: {}, colors: {} };
1329
+ const empty = { shadows: {}, spacing: {}, breakpoints: {}, zIndex: {}, transitionDuration: {}, transitionTimingFunction: {}, animation: {}, colors: {}, fontSize: {}, fontWeight: {} };
1241
1330
  const twPath = path.join(PROJECT_ROOT, "tailwind.config.js");
1242
1331
  const twPathMjs = path.join(PROJECT_ROOT, "tailwind.config.mjs");
1243
1332
  const twPathTs = path.join(PROJECT_ROOT, "tailwind.config.ts");
@@ -1260,6 +1349,8 @@ function getTailwindTheme() {
1260
1349
  transitionTimingFunction: toObj(theme.transitionTimingFunction),
1261
1350
  animation: toObj(theme.animation),
1262
1351
  colors: toObj(theme.colors),
1352
+ fontSize: toObj(theme.fontSize),
1353
+ fontWeight: toObj(theme.fontWeight),
1263
1354
  };
1264
1355
  } catch (_) {
1265
1356
  return empty;
@@ -1379,6 +1470,14 @@ function extractFoundations() {
1379
1470
  });
1380
1471
  if (typeScale.length > 0) typography.typeScale = typeScale;
1381
1472
 
1473
+ // Tailwind v4: font-weight custom props (--font-weight-*)
1474
+ const fontWeightVarRe = /--font-weight-([\w-]+)\s*:\s*([^;\n]+);/g;
1475
+ let fwvm;
1476
+ while ((fwvm = fontWeightVarRe.exec(css)) !== null) {
1477
+ if (!typography.fontWeightScale) typography.fontWeightScale = {};
1478
+ typography.fontWeightScale[fwvm[1]] = fwvm[2].trim();
1479
+ }
1480
+
1382
1481
  // Tailwind v4: extract design tokens from @layer theme { :root { ... } } or bare :root
1383
1482
  // Variables: --spacing (base), --radius-*, --shadow-*, --ease-*, --duration-*, --animate-*
1384
1483
  const v4ThemeVars = {};
@@ -1778,6 +1877,39 @@ function extractFoundations() {
1778
1877
  const mergedDurations = { ...v4Durations, ...normalizeThemeObj(twTheme.transitionDuration) };
1779
1878
  const mergedAnimations = { ...v4Animations, ...normalizeThemeObj(twTheme.animation) };
1780
1879
 
1880
+ // ── Fallback: build typeScale from twTheme.fontSize if CSS vars produced nothing ──
1881
+ if (!typography.typeScale || typography.typeScale.length === 0) {
1882
+ const fsObj = normalizeThemeObj(twTheme.fontSize);
1883
+ const fsEntries = Object.entries(fsObj);
1884
+ if (fsEntries.length > 0) {
1885
+ // Order: canonical Tailwind steps first, then any custom steps
1886
+ const ORDER = ["xs","sm","base","lg","xl","2xl","3xl","4xl","5xl","6xl","7xl","8xl","9xl"];
1887
+ const ordered = [
1888
+ ...ORDER.filter((k) => fsObj[k]).map((k) => [k, fsObj[k]]),
1889
+ ...fsEntries.filter(([k]) => !ORDER.includes(k)),
1890
+ ];
1891
+ typography.typeScale = ordered.map(([step, size]) => {
1892
+ // fontSize can be "[size, {lineHeight: ...}]" (array serialized) or plain "1rem"
1893
+ // After toObj + toString it looks like "1rem,1.5rem" or just "1rem" or "[object Object]"
1894
+ let resolvedSize = size;
1895
+ // Handle tuple-ish form from resolveConfig: e.g. "0.75rem,..." or ["0.75rem",{lineHeight:"1rem"}]
1896
+ if (resolvedSize && resolvedSize.includes(",")) resolvedSize = resolvedSize.split(",")[0].trim();
1897
+ if (!resolvedSize || resolvedSize === "[object Object]") return null;
1898
+ const pxM = resolvedSize.match(/^([\d.]+)rem$/);
1899
+ const px = pxM ? Math.round(parseFloat(pxM[1]) * 16) + "px" : null;
1900
+ return { step, size: resolvedSize, px, lineHeight: null };
1901
+ }).filter(Boolean);
1902
+ }
1903
+ }
1904
+
1905
+ // ── Fallback: build fontWeightScale from twTheme.fontWeight if CSS vars produced nothing ──
1906
+ if (!typography.fontWeightScale || Object.keys(typography.fontWeightScale).length === 0) {
1907
+ const fwObj = normalizeThemeObj(twTheme.fontWeight);
1908
+ if (Object.keys(fwObj).length > 0) {
1909
+ typography.fontWeightScale = fwObj;
1910
+ }
1911
+ }
1912
+
1781
1913
  return {
1782
1914
  colors: foundationsColors,
1783
1915
  typography,
@@ -2605,7 +2605,32 @@ function writeFoundationsStories(foundations, components) {
2605
2605
 
2606
2606
  {
2607
2607
  const typo = foundations?.typography || {};
2608
- const typeScale = Array.isArray(typo.typeScale) ? typo.typeScale : [];
2608
+ // Tailwind v3 default type scale (canonical sizes)
2609
+ const TW_DEFAULT_TYPE_SCALE = [
2610
+ { step: "xs", size: "0.75rem", px: "12px", lineHeight: "1rem" },
2611
+ { step: "sm", size: "0.875rem", px: "14px", lineHeight: "1.25rem" },
2612
+ { step: "base", size: "1rem", px: "16px", lineHeight: "1.5rem" },
2613
+ { step: "lg", size: "1.125rem", px: "18px", lineHeight: "1.75rem" },
2614
+ { step: "xl", size: "1.25rem", px: "20px", lineHeight: "1.75rem" },
2615
+ { step: "2xl", size: "1.5rem", px: "24px", lineHeight: "2rem" },
2616
+ { step: "3xl", size: "1.875rem", px: "30px", lineHeight: "2.25rem" },
2617
+ { step: "4xl", size: "2.25rem", px: "36px", lineHeight: "2.5rem" },
2618
+ { step: "5xl", size: "3rem", px: "48px", lineHeight: "1" },
2619
+ ];
2620
+ const TW_DEFAULT_FONT_WEIGHTS = [
2621
+ { token: "thin", value: "100" },
2622
+ { token: "extralight", value: "200" },
2623
+ { token: "light", value: "300" },
2624
+ { token: "normal", value: "400" },
2625
+ { token: "medium", value: "500" },
2626
+ { token: "semibold", value: "600" },
2627
+ { token: "bold", value: "700" },
2628
+ { token: "extrabold", value: "800" },
2629
+ { token: "black", value: "900" },
2630
+ ];
2631
+ let typeScale = Array.isArray(typo.typeScale) ? typo.typeScale : [];
2632
+ const typeScaleIsDefault = typeScale.length === 0;
2633
+ if (typeScaleIsDefault) typeScale = TW_DEFAULT_TYPE_SCALE;
2609
2634
  const usedTextSizes = (foundations?.tokenUsage?.textSizes || []);
2610
2635
  const usedTextMap = Object.fromEntries(usedTextSizes.map(({ token, count }) => [token, count]));
2611
2636
 
@@ -2619,11 +2644,23 @@ function writeFoundationsStories(foundations, components) {
2619
2644
  })
2620
2645
  .filter((r) => r.value);
2621
2646
 
2622
- // Font weights
2647
+ // Font weights — check fontWeightScale (from twTheme.fontWeight), then legacy specific keys, then Tailwind defaults
2623
2648
  const weightKeys = ["fontWeightNormal", "fontWeightMedium", "fontWeightSemibold", "fontWeightBold"];
2624
- const weightRows = weightKeys
2649
+ let weightRows = weightKeys
2625
2650
  .filter((k) => typo[k])
2626
2651
  .map((k) => ({ token: k, value: String(typo[k]) }));
2652
+ if (weightRows.length === 0 && typo.fontWeightScale && typeof typo.fontWeightScale === "object") {
2653
+ const WGT_ORDER = ["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"];
2654
+ const wEntries = Object.entries(typo.fontWeightScale);
2655
+ wEntries.sort(([a], [b]) => {
2656
+ const ia = WGT_ORDER.indexOf(a), ib = WGT_ORDER.indexOf(b);
2657
+ if (ia >= 0 && ib >= 0) return ia - ib;
2658
+ return parseInt(a) - parseInt(b) || a.localeCompare(b);
2659
+ });
2660
+ weightRows = wEntries.map(([token, value]) => ({ token, value: String(value) }));
2661
+ }
2662
+ const weightRowsIsDefault = weightRows.length === 0;
2663
+ if (weightRowsIsDefault) weightRows = TW_DEFAULT_FONT_WEIGHTS;
2627
2664
 
2628
2665
  // Reverse type scale for display (largest first)
2629
2666
  const scaleDesc = [...typeScale].reverse();
@@ -2639,9 +2676,11 @@ function writeFoundationsStories(foundations, components) {
2639
2676
  "type Story = StoryObj;",
2640
2677
  "",
2641
2678
  `const typeScale: { step: string; size: string; px: string | null; lineHeight: string | null }[] = ${JSON.stringify(scaleDesc)};`,
2679
+ `const typeScaleIsDefault: boolean = ${typeScaleIsDefault};`,
2642
2680
  `const usedTextSizes: { token: string; count: number }[] = ${JSON.stringify(usedTextSizes)};`,
2643
2681
  `const familyRows: { token: string; value: string }[] = ${JSON.stringify(familyRows)};`,
2644
2682
  `const weightRows: { token: string; value: string }[] = ${JSON.stringify(weightRows)};`,
2683
+ `const weightRowsIsDefault: boolean = ${weightRowsIsDefault};`,
2645
2684
  `const sansFamily = ${JSON.stringify(sansFamily)};`,
2646
2685
  "",
2647
2686
  "export const Default: Story = {",
@@ -2664,10 +2703,8 @@ function writeFoundationsStories(foundations, components) {
2664
2703
  " </div>",
2665
2704
  " </div>",
2666
2705
  " )}",
2667
- " {typeScale.length === 0 ? (",
2668
- " <p style={{ color: \"#999\", fontSize: 13, marginBottom: 32 }}>No type scale detected.</p>",
2669
- " ) : (",
2670
- " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 0, marginBottom: 48, border: \"1px solid #e5e7eb\", borderRadius: 10, overflow: \"hidden\" }}>",
2706
+ " {typeScaleIsDefault && <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 16, display: \"inline-block\" }}>ℹ️ Tailwind defaults — no custom font sizes found in this project</p>}",
2707
+ " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 0, marginBottom: 48, border: \"1px solid #e5e7eb\", borderRadius: 10, overflow: \"hidden\" }}>",
2671
2708
  " {typeScale.map(({ step, size, px, lineHeight }, i) => {",
2672
2709
  " const usage = usedTextSizes.find(u => u.token === `text-${step}`);",
2673
2710
  " const usageCount = usage?.count;",
@@ -2701,8 +2738,7 @@ function writeFoundationsStories(foundations, components) {
2701
2738
  " </div>",
2702
2739
  " );",
2703
2740
  " })}",
2704
- " </div>",
2705
- " )}",
2741
+ " </div>",
2706
2742
  "",
2707
2743
  " {/* ── FONT FAMILIES ── */}",
2708
2744
  " {familyRows.length > 0 && (",
@@ -2725,6 +2761,7 @@ function writeFoundationsStories(foundations, components) {
2725
2761
  " )}",
2726
2762
  "",
2727
2763
  " {/* ── FONT WEIGHTS ── */}",
2764
+ " {weightRowsIsDefault && <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 12, display: \"inline-block\" }}>ℹ️ Tailwind defaults — no custom font weights found in this project</p>}",
2728
2765
  " {weightRows.length > 0 && (",
2729
2766
  " <>",
2730
2767
  " <h3 style={{ fontSize: 15, fontWeight: 600, margin: \"0 0 14px\", color: \"#374151\" }}>Font Weights</h3>",
@@ -3237,6 +3274,13 @@ function writeFoundationsStories(foundations, components) {
3237
3274
  {
3238
3275
  const spacing = foundations?.spacing || {};
3239
3276
  const breakpoints = foundations?.breakpoints || {};
3277
+ // Tailwind v3 default spacing scale
3278
+ const TW_DEFAULT_SPACING = { "0": "0px", "px": "1px", "0.5": "0.125rem", "1": "0.25rem", "1.5": "0.375rem", "2": "0.5rem", "2.5": "0.625rem", "3": "0.75rem", "3.5": "0.875rem", "4": "1rem", "5": "1.25rem", "6": "1.5rem", "7": "1.75rem", "8": "2rem", "9": "2.25rem", "10": "2.5rem", "11": "2.75rem", "12": "3rem", "14": "3.5rem", "16": "4rem", "20": "5rem", "24": "6rem", "28": "7rem", "32": "8rem", "36": "9rem", "40": "10rem", "44": "11rem", "48": "12rem", "52": "13rem", "56": "14rem", "60": "15rem", "64": "16rem", "72": "18rem", "80": "20rem", "96": "24rem" };
3279
+ const TW_DEFAULT_BREAKPOINTS = { "sm": "640px", "md": "768px", "lg": "1024px", "xl": "1280px", "2xl": "1536px" };
3280
+ const spacingIsDefault = Object.keys(spacing).length === 0;
3281
+ const spacingSource = spacingIsDefault ? TW_DEFAULT_SPACING : spacing;
3282
+ const breakpointsIsDefault = Object.keys(breakpoints).length === 0;
3283
+ const breakpointsSource = breakpointsIsDefault ? TW_DEFAULT_BREAKPOINTS : breakpoints;
3240
3284
 
3241
3285
  function remToPxNum(val) {
3242
3286
  const m = String(val).match(/^([\d.]+)rem$/);
@@ -3246,7 +3290,7 @@ function writeFoundationsStories(foundations, components) {
3246
3290
  return -1;
3247
3291
  }
3248
3292
 
3249
- const spacingRows = Object.entries(spacing)
3293
+ const spacingRows = Object.entries(spacingSource)
3250
3294
  .map(([token, value]) => {
3251
3295
  const px = remToPxNum(value);
3252
3296
  const label = px >= 0 ? `${value} · ${px}px` : value;
@@ -3261,7 +3305,7 @@ function writeFoundationsStories(foundations, components) {
3261
3305
  return a.token.localeCompare(b.token);
3262
3306
  });
3263
3307
 
3264
- const breakpointRows = Object.entries(breakpoints)
3308
+ const breakpointRows = Object.entries(breakpointsSource)
3265
3309
  .map(([token, value]) => ({ token, value }))
3266
3310
  .sort((a, b) => parseInt(a.value) - parseInt(b.value));
3267
3311
 
@@ -3275,7 +3319,9 @@ function writeFoundationsStories(foundations, components) {
3275
3319
  "type Story = StoryObj;",
3276
3320
  "",
3277
3321
  `const spacingTokens: { token: string; label: string; barWidth: number }[] = ${JSON.stringify(spacingRows)};`,
3322
+ `const spacingIsDefault: boolean = ${spacingIsDefault};`,
3278
3323
  `const breakpointTokens: { token: string; value: string }[] = ${JSON.stringify(breakpointRows)};`,
3324
+ `const breakpointsIsDefault: boolean = ${breakpointsIsDefault};`,
3279
3325
  `const usedSpacing: { token: string; count: number }[] = ${JSON.stringify(usedSpacing)};`,
3280
3326
  "",
3281
3327
  "export const Default: Story = {",
@@ -3295,22 +3341,20 @@ function writeFoundationsStories(foundations, components) {
3295
3341
  " </div>",
3296
3342
  " </>",
3297
3343
  " )}",
3298
- " {spacingTokens.length === 0 ? (",
3299
- " <p style={{ color: \"#999\", fontSize: 13 }}>No spacing tokens detected. Ensure <code>tailwind.config.ts</code> is present.</p>",
3300
- " ) : (",
3301
- " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 3 }}>",
3302
- " {spacingTokens.map(({ token, label, barWidth }) => (",
3344
+ " {spacingIsDefault && <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 16, display: \"inline-block\" }}>ℹ️ Tailwind defaults — no custom spacing detected in this project</p>}",
3345
+ " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 3 }}>",
3346
+ " {spacingTokens.map(({ token, label, barWidth }) => (",
3303
3347
  " <div key={token} style={{ display: \"flex\", alignItems: \"center\", gap: 10, minHeight: 24 }}>",
3304
3348
  " <span style={{ width: 40, fontSize: 11, color: \"#9ca3af\", textAlign: \"right\", flexShrink: 0, fontVariantNumeric: \"tabular-nums\" }}>{token}</span>",
3305
3349
  " <div style={{ width: Math.max(barWidth, 2), height: 16, background: barWidth === 0 ? \"transparent\" : \"var(--color-primary, #6366f1)\", opacity: barWidth === 0 ? 1 : 0.85, borderRadius: 3, flexShrink: 0, border: barWidth === 0 ? \"1px dashed #555\" : \"none\", boxSizing: \"border-box\" as any }} />",
3306
3350
  " <code style={{ fontSize: 11, color: \"#6b7280\" }}>{label}</code>",
3307
3351
  " </div>",
3308
3352
  " ))}",
3309
- " </div>",
3310
- " )}",
3353
+ " </div>",
3311
3354
  " {breakpointTokens.length > 0 && (",
3312
3355
  " <>",
3313
3356
  " <h3 style={{ fontSize: 16, fontWeight: 600, margin: \"40px 0 12px\" }}>Breakpoints</h3>",
3357
+ " {breakpointsIsDefault && <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 10, display: \"inline-block\" }}>ℹ️ Tailwind defaults — no custom breakpoints found in this project</p>}",
3314
3358
  " <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fill, minmax(130px, 1fr))\", gap: 8 }}>",
3315
3359
  " {breakpointTokens.map(({ token, value }) => (",
3316
3360
  " <div key={token} style={{ padding: \"10px 12px\", border: \"1px solid #e5e7eb\", borderRadius: 8, background: \"#fafafa\" }}>",
@@ -3346,7 +3390,11 @@ function writeFoundationsStories(foundations, components) {
3346
3390
  .map(([token, value]) => ({ token, value }));
3347
3391
 
3348
3392
  const zSemanticLabels = { "0": "Base layer", "10": "Low elevation", "20": "Dropdown / popover", "30": "Sticky header", "40": "Fixed overlay", "50": "Modal / dialog", "auto": "Auto" };
3349
- const zIndexRows = Object.entries(zIndex)
3393
+ // Tailwind v3 default z-index scale
3394
+ const TW_DEFAULT_Z_INDEX = { "0": "0", "10": "10", "20": "20", "30": "30", "40": "40", "50": "50", "auto": "auto" };
3395
+ const zIndexIsDefault = Object.keys(zIndex).length === 0;
3396
+ const zIndexSource = zIndexIsDefault ? TW_DEFAULT_Z_INDEX : zIndex;
3397
+ const zIndexRows = Object.entries(zIndexSource)
3350
3398
  .sort(([a], [b]) => {
3351
3399
  if (a === "auto") return 1;
3352
3400
  if (b === "auto") return -1;
@@ -3384,6 +3432,7 @@ function writeFoundationsStories(foundations, components) {
3384
3432
  "",
3385
3433
  `const shadowTokens: { token: string; value: string }[] = ${JSON.stringify(shadowDisplayRows)};`,
3386
3434
  `const zIndexTokens: { token: string; value: string; label: string }[] = ${JSON.stringify(zIndexRows)};`,
3435
+ `const zIndexIsDefault: boolean = ${zIndexIsDefault};`,
3387
3436
  `const usedShadows: { token: string; count: number }[] = ${JSON.stringify(usedShadows)};`,
3388
3437
  `const usedZIndex: { token: string; count: number }[] = ${JSON.stringify(usedZIndex)};`,
3389
3438
  `const zSemantics: Record<string, string> = ${JSON.stringify(zIndexSemantics)};`,
@@ -3434,9 +3483,8 @@ function writeFoundationsStories(foundations, components) {
3434
3483
  " ))}",
3435
3484
  " </div>",
3436
3485
  " )}",
3437
- " {zIndexTokens.length === 0 && usedZIndex.length === 0 ? (",
3438
- " <p style={{ color: \"#999\", fontSize: 13 }}>No z-index tokens detected.</p>",
3439
- " ) : zIndexTokens.length > 0 ? (",
3486
+ " {zIndexIsDefault && <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 14, display: \"inline-block\" }}>ℹ️ Tailwind defaults — no custom z-index found in this project</p>}",
3487
+ " {zIndexTokens.length > 0 ? (",
3440
3488
  " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 6, maxWidth: 380 }}>",
3441
3489
  " {zIndexTokens.map(({ token, value, label }) => (",
3442
3490
  " <div key={token} style={{ display: \"flex\", alignItems: \"center\", gap: 12, padding: \"8px 14px\", background: \"#fff\", borderRadius: 8, border: \"1px solid #e5e7eb\" }}>",