vibe-design-system 2.8.14 → 2.8.16

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.14",
3
+ "version": "2.8.16",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -1273,6 +1273,39 @@ function extractFoundations() {
1273
1273
  if (!typography[key]) typography[key] = val;
1274
1274
  }
1275
1275
 
1276
+ // ── Type Scale: extract --text-* font sizes + line-heights from compiled CSS
1277
+ const textSteps = ["xs","sm","base","lg","xl","2xl","3xl","4xl","5xl","6xl","7xl","8xl","9xl"];
1278
+ const textSizes = {};
1279
+ const textLineHeights = {};
1280
+ const textSizeRe = new RegExp(`--text-(${textSteps.join("|")})\\s*:\\s*([^;\\n]+);`, "g");
1281
+ const textLhRe = new RegExp(`--text-(${textSteps.join("|")})--line-height\\s*:\\s*([^;\\n]+);`, "g");
1282
+ let tsm;
1283
+ while ((tsm = textSizeRe.exec(css)) !== null) {
1284
+ // Normalize ".75rem" → "0.75rem"
1285
+ const raw = tsm[2].trim();
1286
+ textSizes[tsm[1]] = raw.startsWith(".") ? "0" + raw : raw;
1287
+ }
1288
+ while ((tsm = textLhRe.exec(css)) !== null) {
1289
+ const raw = tsm[2].trim();
1290
+ // Resolve calc(a / b) → rounded ratio string
1291
+ const calcM = raw.match(/calc\(([\d.]+)\s*\/\s*([\d.]+)\)/);
1292
+ if (calcM) {
1293
+ const ratio = parseFloat(calcM[1]) / parseFloat(calcM[2]);
1294
+ textLineHeights[tsm[1]] = (Math.round(ratio * 1000) / 1000) + "";
1295
+ } else {
1296
+ textLineHeights[tsm[1]] = raw;
1297
+ }
1298
+ }
1299
+ const typeScale = textSteps
1300
+ .filter((step) => textSizes[step])
1301
+ .map((step) => {
1302
+ const size = textSizes[step];
1303
+ const pxM = size.match(/^([\d.]+)rem$/);
1304
+ const px = pxM ? Math.round(parseFloat(pxM[1]) * 16) + "px" : null;
1305
+ return { step, size, px, lineHeight: textLineHeights[step] || null };
1306
+ });
1307
+ if (typeScale.length > 0) typography.typeScale = typeScale;
1308
+
1276
1309
  // Tailwind v4: extract design tokens from @layer theme { :root { ... } } or bare :root
1277
1310
  // Variables: --spacing (base), --radius-*, --shadow-*, --ease-*, --duration-*, --animate-*
1278
1311
  const v4ThemeVars = {};
@@ -1696,6 +1729,7 @@ function extractTokenUsage() {
1696
1729
  const zIndexCounts = new Map();
1697
1730
  const radiusCounts = new Map();
1698
1731
  const animateCounts = new Map();
1732
+ const textSizeCounts = new Map();
1699
1733
 
1700
1734
  // Spacing utilities: gap-*, p-*, px-*, py-*, pt-*, pb-*, pl-*, pr-*, m-*, mx-*, my-*, mt-*, mb-*, ml-*, mr-*, space-*, w-*, h-*
1701
1735
  const spacingRe = /\b(gap|p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|space-x|space-y|w|h|size|inset|top|bottom|left|right|min-w|min-h|max-w|max-h)-((?:\d+(\.\d+)?|px|full|screen|auto|fit|max|min|svh|dvh|svw|dvw))\b/g;
@@ -1707,6 +1741,8 @@ function extractTokenUsage() {
1707
1741
  const roundedRe = /\brounded(?:-(none|sm|md|lg|xl|2xl|3xl|full|t|r|b|l|tl|tr|br|bl|s|e|ss|se|es|ee|[a-z]+(?:-(?:none|sm|md|lg|xl|2xl|3xl|full))?))?(?!\w)/g;
1708
1742
  // Animate utilities: animate-spin, animate-ping, animate-pulse, animate-bounce, animate-none
1709
1743
  const animateRe = /\banimate-(spin|ping|pulse|bounce|none|[\w-]+)\b/g;
1744
+ // Text-size utilities: text-xs, text-sm, text-base, text-lg, text-xl, text-2xl, text-3xl...
1745
+ const textSizeUtilRe = /\btext-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)\b/g;
1710
1746
 
1711
1747
  for (const rel of files) {
1712
1748
  if (rel.includes("stories")) continue;
@@ -1755,6 +1791,13 @@ function extractTokenUsage() {
1755
1791
  const key = `animate-${m[1]}`;
1756
1792
  animateCounts.set(key, (animateCounts.get(key) || 0) + 1);
1757
1793
  }
1794
+
1795
+ // Text sizes
1796
+ const textSizeUtilReCopy = new RegExp(textSizeUtilRe.source, "g");
1797
+ while ((m = textSizeUtilReCopy.exec(content)) !== null) {
1798
+ const key = `text-${m[1]}`;
1799
+ textSizeCounts.set(key, (textSizeCounts.get(key) || 0) + 1);
1800
+ }
1758
1801
  }
1759
1802
 
1760
1803
  const toTop = (map, n) =>
@@ -1769,6 +1812,7 @@ function extractTokenUsage() {
1769
1812
  zIndex: toTop(zIndexCounts, 10),
1770
1813
  radius: toTop(radiusCounts, 10),
1771
1814
  animations: toTop(animateCounts, 8),
1815
+ textSizes: toTop(textSizeCounts, 12),
1772
1816
  };
1773
1817
  }
1774
1818
 
@@ -1288,40 +1288,136 @@ function writeFoundationsStories(foundations) {
1288
1288
  fs.writeFileSync(path.join(foundationsDir, "Colors.stories.tsx"), colorsContent, "utf-8");
1289
1289
  console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "Colors.stories.tsx")));
1290
1290
 
1291
- const typo = foundations?.typography;
1292
- if (typo && typeof typo === "object" && Object.keys(typo).length > 0) {
1293
- const typoRows = Object.entries(typo).map(([k, v]) => {
1294
- const displayValue = typeof v === "object" && v !== null
1295
- ? (v.family || v.value || JSON.stringify(v))
1296
- : Array.isArray(v) ? v.join(", ") : String(v);
1297
- return { token: k, value: displayValue, fontFamily: displayValue };
1298
- });
1299
- const typoContent =
1300
- [
1301
- "import type { Meta, StoryObj } from \"@storybook/react\";",
1302
- "",
1303
- "const meta = { title: \"Foundations/Typography\" } satisfies Meta;",
1304
- "export default meta;",
1305
- "type Story = StoryObj;",
1306
- "",
1307
- `const typography = ${JSON.stringify(typoRows)};`,
1308
- "",
1309
- "export const Default: Story = {",
1310
- " render: () => (",
1311
- " <div>",
1312
- " <h2 style={{ marginBottom: 16 }}>Font family & scale</h2>",
1313
- " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 24 }}>",
1314
- " {typography.map(({ token, value, fontFamily }) => (",
1315
- " <div key={token} style={{ borderBottom: \"1px solid #222\", paddingBottom: 16 }}>",
1316
- " <div style={{ marginBottom: 8 }}><strong>{token}</strong> <code style={{ fontSize: 12, color: \"#888\" }}>{value}</code></div>",
1317
- " <div style={{ fontFamily: fontFamily || \"inherit\", fontSize: 18 }}>Aa — The quick brown fox jumps over the lazy dog.</div>",
1318
- " </div>",
1319
- " ))}",
1320
- " </div>",
1321
- " </div>",
1322
- " ),",
1323
- "};",
1324
- ].join("\n");
1291
+ {
1292
+ const typo = foundations?.typography || {};
1293
+ const typeScale = Array.isArray(typo.typeScale) ? typo.typeScale : [];
1294
+ const usedTextSizes = (foundations?.tokenUsage?.textSizes || []);
1295
+ const usedTextMap = Object.fromEntries(usedTextSizes.map(({ token, count }) => [token, count]));
1296
+
1297
+ // Font families
1298
+ const fontFamilyKeys = ["fontSans", "fontMono", "body", "heading", "tailwindSans", "tailwindMono"];
1299
+ const familyRows = Object.entries(typo)
1300
+ .filter(([k]) => fontFamilyKeys.includes(k) || k.toLowerCase().includes("font") && !k.toLowerCase().includes("weight") && k !== "typeScale" && k !== "fontSize")
1301
+ .map(([k, v]) => {
1302
+ const val = typeof v === "object" && v !== null ? (v.family || v.value || "") : Array.isArray(v) ? v.join(", ") : String(v);
1303
+ return { token: k, value: val };
1304
+ })
1305
+ .filter((r) => r.value);
1306
+
1307
+ // Font weights
1308
+ const weightKeys = ["fontWeightNormal", "fontWeightMedium", "fontWeightSemibold", "fontWeightBold"];
1309
+ const weightRows = weightKeys
1310
+ .filter((k) => typo[k])
1311
+ .map((k) => ({ token: k, value: String(typo[k]) }));
1312
+
1313
+ // Reverse type scale for display (largest first)
1314
+ const scaleDesc = [...typeScale].reverse();
1315
+
1316
+ const sansFamily = typo.fontSans || typo.tailwindSans || typo.body || "system-ui, sans-serif";
1317
+
1318
+ const typoContent = [
1319
+ "import type { Meta, StoryObj } from \"@storybook/react\";",
1320
+ "",
1321
+ "const meta = { title: \"Foundations/Typography\" } satisfies Meta;",
1322
+ "export default meta;",
1323
+ "type Story = StoryObj;",
1324
+ "",
1325
+ `const typeScale: { step: string; size: string; px: string | null; lineHeight: string | null }[] = ${JSON.stringify(scaleDesc)};`,
1326
+ `const usedTextSizes: { token: string; count: number }[] = ${JSON.stringify(usedTextSizes)};`,
1327
+ `const familyRows: { token: string; value: string }[] = ${JSON.stringify(familyRows)};`,
1328
+ `const weightRows: { token: string; value: string }[] = ${JSON.stringify(weightRows)};`,
1329
+ `const sansFamily = ${JSON.stringify(sansFamily)};`,
1330
+ "",
1331
+ "export const Default: Story = {",
1332
+ " render: () => (",
1333
+ " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, maxWidth: 860, color: \"#111\" }}>",
1334
+ " <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\" }}>Typography</h2>",
1335
+ " <p style={{ fontSize: 13, color: \"#888\", margin: \"0 0 32px\" }}>Type scale, font families, and weights</p>",
1336
+ "",
1337
+ " {/* ── TYPE SCALE ── */}",
1338
+ " <h3 style={{ fontSize: 15, fontWeight: 600, margin: \"0 0 14px\", color: \"#374151\" }}>Type Scale</h3>",
1339
+ " {usedTextSizes.length > 0 && (",
1340
+ " <div style={{ marginBottom: 16 }}>",
1341
+ " <p style={{ fontSize: 12, color: \"#6b7280\", margin: \"0 0 8px\", fontWeight: 500 }}>Most used in this project</p>",
1342
+ " <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 6 }}>",
1343
+ " {usedTextSizes.map(({ token, count }) => (",
1344
+ " <span key={token} style={{ padding: \"3px 10px\", background: \"#ede9fe\", color: \"#5b21b6\", borderRadius: 20, fontSize: 12, fontWeight: 500 }}>",
1345
+ " {token} <span style={{ opacity: 0.6 }}>×{count}</span>",
1346
+ " </span>",
1347
+ " ))}",
1348
+ " </div>",
1349
+ " </div>",
1350
+ " )}",
1351
+ " {typeScale.length === 0 ? (",
1352
+ " <p style={{ color: \"#999\", fontSize: 13, marginBottom: 32 }}>No type scale detected.</p>",
1353
+ " ) : (",
1354
+ " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 0, marginBottom: 48, border: \"1px solid #e5e7eb\", borderRadius: 10, overflow: \"hidden\" }}>",
1355
+ " {typeScale.map(({ step, size, px, lineHeight }, i) => {",
1356
+ " const usageCount = usedTextSizes.find(u => u.token === `text-${step}`)?.count;",
1357
+ " const remVal = parseFloat(size);",
1358
+ " const pxVal = px ? parseInt(px) : Math.round(remVal * 16);",
1359
+ " return (",
1360
+ " <div key={step} style={{ display: \"flex\", alignItems: \"center\", gap: 20, padding: \"14px 20px\", background: i % 2 === 0 ? \"#fff\" : \"#f9fafb\", borderTop: i === 0 ? \"none\" : \"1px solid #f0f0f0\" }}>",
1361
+ " <div style={{ width: 120, flexShrink: 0 }}>",
1362
+ " <div style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>",
1363
+ " <code style={{ fontSize: 12, fontWeight: 600, color: \"#6366f1\", background: \"#eef2ff\", padding: \"2px 7px\", borderRadius: 4 }}>text-{step}</code>",
1364
+ " {usageCount && <span style={{ fontSize: 11, color: \"#9ca3af\" }}>×{usageCount}</span>}",
1365
+ " </div>",
1366
+ " <div style={{ fontSize: 11, color: \"#9ca3af\", marginTop: 4 }}>",
1367
+ " {size}{px && px !== size ? ` · ${px}` : \"\"}",
1368
+ " {lineHeight ? <span style={{ marginLeft: 8 }}>lh {lineHeight}</span> : null}",
1369
+ " </div>",
1370
+ " </div>",
1371
+ " <div style={{ fontSize: size, fontFamily: sansFamily, color: \"#111\", lineHeight: lineHeight || 1.5, overflow: \"hidden\", whiteSpace: \"nowrap\", textOverflow: \"ellipsis\", flex: 1 }}>",
1372
+ " The quick brown fox jumps over the lazy dog",
1373
+ " </div>",
1374
+ " </div>",
1375
+ " );",
1376
+ " })}",
1377
+ " </div>",
1378
+ " )}",
1379
+ "",
1380
+ " {/* ── FONT FAMILIES ── */}",
1381
+ " {familyRows.length > 0 && (",
1382
+ " <>",
1383
+ " <h3 style={{ fontSize: 15, fontWeight: 600, margin: \"0 0 14px\", color: \"#374151\" }}>Font Families</h3>",
1384
+ " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 16, marginBottom: 40 }}>",
1385
+ " {familyRows.map(({ token, value }) => (",
1386
+ " <div key={token} style={{ padding: \"12px 16px\", background: \"#f8fafc\", borderRadius: 8, border: \"1px solid #e5e7eb\" }}>",
1387
+ " <div style={{ display: \"flex\", gap: 12, alignItems: \"baseline\", marginBottom: 6 }}>",
1388
+ " <span style={{ fontSize: 12, fontWeight: 600, color: \"#374151\", minWidth: 140 }}>{token}</span>",
1389
+ " <code style={{ fontSize: 11, color: \"#9ca3af\", wordBreak: \"break-all\" as any }}>{value.length > 60 ? value.slice(0, 60) + \"…\" : value}</code>",
1390
+ " </div>",
1391
+ " <div style={{ fontFamily: value, fontSize: 18, color: \"#374151\", letterSpacing: \"-0.01em\" }}>",
1392
+ " Aa — The quick brown fox jumps over the lazy dog.",
1393
+ " </div>",
1394
+ " </div>",
1395
+ " ))}",
1396
+ " </div>",
1397
+ " </>",
1398
+ " )}",
1399
+ "",
1400
+ " {/* ── FONT WEIGHTS ── */}",
1401
+ " {weightRows.length > 0 && (",
1402
+ " <>",
1403
+ " <h3 style={{ fontSize: 15, fontWeight: 600, margin: \"0 0 14px\", color: \"#374151\" }}>Font Weights</h3>",
1404
+ " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 10, marginBottom: 40, maxWidth: 560 }}>",
1405
+ " {weightRows.map(({ token, value }) => (",
1406
+ " <div key={token} style={{ display: \"flex\", alignItems: \"baseline\", gap: 16, padding: \"8px 0\", borderBottom: \"1px solid #f0f0f0\" }}>",
1407
+ " <code style={{ width: 64, fontSize: 11, color: \"#9ca3af\", flexShrink: 0 }}>{value}</code>",
1408
+ " <span style={{ fontSize: 11, color: \"#9ca3af\", width: 120, flexShrink: 0 }}>{token}</span>",
1409
+ " <span style={{ fontFamily: sansFamily, fontSize: 17, fontWeight: parseInt(value) || value as any }}>",
1410
+ " The quick brown fox",
1411
+ " </span>",
1412
+ " </div>",
1413
+ " ))}",
1414
+ " </div>",
1415
+ " </>",
1416
+ " )}",
1417
+ " </div>",
1418
+ " ),",
1419
+ "};",
1420
+ ].join("\n");
1325
1421
  fs.writeFileSync(path.join(foundationsDir, "Typography.stories.tsx"), typoContent, "utf-8");
1326
1422
  console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "Typography.stories.tsx")));
1327
1423
  }
@@ -1576,7 +1672,8 @@ function writeFoundationsStories(foundations) {
1576
1672
  "shadow-2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)",
1577
1673
  };
1578
1674
  // If token CSS values available use those; otherwise use defaults keyed by used token names
1579
- const shadowDisplayRows = shadowRows.length > 0
1675
+ const hasCustomShadows = shadowRows.length > 0;
1676
+ const shadowDisplayRows = hasCustomShadows
1580
1677
  ? shadowRows
1581
1678
  : usedShadows.map(({ token }) => ({ token, value: shadowDefaults[token] || "" })).filter((r) => r.value);
1582
1679
 
@@ -1594,6 +1691,7 @@ function writeFoundationsStories(foundations) {
1594
1691
  `const usedShadows: { token: string; count: number }[] = ${JSON.stringify(usedShadows)};`,
1595
1692
  `const usedZIndex: { token: string; count: number }[] = ${JSON.stringify(usedZIndex)};`,
1596
1693
  `const zSemantics: Record<string, string> = ${JSON.stringify(zIndexSemantics)};`,
1694
+ `const hasCustomShadows: boolean = ${hasCustomShadows};`,
1597
1695
  "",
1598
1696
  "export const Default: Story = {",
1599
1697
  " render: () => (",
@@ -1610,14 +1708,22 @@ function writeFoundationsStories(foundations) {
1610
1708
  " ))}",
1611
1709
  " </div>",
1612
1710
  " )}",
1711
+ " {!hasCustomShadows && shadowTokens.length > 0 && (",
1712
+ " <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 16, display: \"inline-block\" }}>",
1713
+ " ℹ️ Tailwind built-in shadows — this project has no custom <code>--shadow-*</code> CSS vars",
1714
+ " </p>",
1715
+ " )}",
1613
1716
  " {shadowTokens.length === 0 ? (",
1614
1717
  " <p style={{ color: \"#999\", fontSize: 13, marginBottom: 32 }}>No shadow tokens detected.</p>",
1615
1718
  " ) : (",
1616
- " <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 24, marginBottom: 48 }}>",
1719
+ " <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 28, marginBottom: 48 }}>",
1617
1720
  " {shadowTokens.map(({ token, value }) => (",
1618
- " <div key={token} style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 10 }}>",
1619
- " <div style={{ width: 80, height: 80, background: \"#fff\", borderRadius: 10, boxShadow: value, border: token === \"none\" ? \"1px dashed #ccc\" : \"none\" }} />",
1620
- " <span style={{ fontSize: 12, fontWeight: 600, color: \"#374151\" }}>{token === \"DEFAULT\" ? \"default\" : token}</span>",
1721
+ " <div key={token} style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 8, maxWidth: 100 }}>",
1722
+ " <div style={{ width: 80, height: 72, background: \"#fff\", borderRadius: 10, boxShadow: value, border: token === \"none\" ? \"1px dashed #ccc\" : \"none\", flexShrink: 0 }} />",
1723
+ " <span style={{ fontSize: 12, fontWeight: 600, color: \"#374151\", textAlign: \"center\" }}>{token === \"DEFAULT\" ? \"default\" : token}</span>",
1724
+ " <code style={{ fontSize: 10, color: \"#9ca3af\", textAlign: \"center\", wordBreak: \"break-all\" as any, lineHeight: 1.4 }}>",
1725
+ " {value.length > 36 ? value.slice(0, 36) + \"…\" : value}",
1726
+ " </code>",
1621
1727
  " </div>",
1622
1728
  " ))}",
1623
1729
  " </div>",