vibe-design-system 2.8.73 → 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 +1 -1
- package/vds-core-template/scan.mjs +149 -17
- package/vds-core-template/story-generator.mjs +95 -29
package/package.json
CHANGED
|
@@ -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
|
-
|
|
538
|
-
|
|
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 [
|
|
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 = {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
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,
|
|
@@ -2415,7 +2415,7 @@ function scanGridSystemFallback() {
|
|
|
2415
2415
|
return { breakpoints, gridCols, gaps, maxWidths, containerCount };
|
|
2416
2416
|
}
|
|
2417
2417
|
|
|
2418
|
-
function writeFoundationsStories(foundations) {
|
|
2418
|
+
function writeFoundationsStories(foundations, components) {
|
|
2419
2419
|
const foundationsDir = path.join(STORIES_DIR, "foundations");
|
|
2420
2420
|
ensureDir(foundationsDir);
|
|
2421
2421
|
|
|
@@ -2605,7 +2605,32 @@ function writeFoundationsStories(foundations) {
|
|
|
2605
2605
|
|
|
2606
2606
|
{
|
|
2607
2607
|
const typo = foundations?.typography || {};
|
|
2608
|
-
|
|
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) {
|
|
|
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
|
-
|
|
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) {
|
|
|
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) {
|
|
|
2664
2703
|
" </div>",
|
|
2665
2704
|
" </div>",
|
|
2666
2705
|
" )}",
|
|
2667
|
-
" {
|
|
2668
|
-
"
|
|
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) {
|
|
|
2701
2738
|
" </div>",
|
|
2702
2739
|
" );",
|
|
2703
2740
|
" })}",
|
|
2704
|
-
"
|
|
2705
|
-
" )}",
|
|
2741
|
+
" </div>",
|
|
2706
2742
|
"",
|
|
2707
2743
|
" {/* ── FONT FAMILIES ── */}",
|
|
2708
2744
|
" {familyRows.length > 0 && (",
|
|
@@ -2725,6 +2761,7 @@ function writeFoundationsStories(foundations) {
|
|
|
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) {
|
|
|
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) {
|
|
|
3246
3290
|
return -1;
|
|
3247
3291
|
}
|
|
3248
3292
|
|
|
3249
|
-
const spacingRows = Object.entries(
|
|
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) {
|
|
|
3261
3305
|
return a.token.localeCompare(b.token);
|
|
3262
3306
|
});
|
|
3263
3307
|
|
|
3264
|
-
const breakpointRows = Object.entries(
|
|
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) {
|
|
|
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) {
|
|
|
3295
3341
|
" </div>",
|
|
3296
3342
|
" </>",
|
|
3297
3343
|
" )}",
|
|
3298
|
-
" {
|
|
3299
|
-
"
|
|
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
|
-
"
|
|
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) {
|
|
|
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
|
-
|
|
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) {
|
|
|
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) {
|
|
|
3434
3483
|
" ))}",
|
|
3435
3484
|
" </div>",
|
|
3436
3485
|
" )}",
|
|
3437
|
-
" {
|
|
3438
|
-
"
|
|
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\" }}>",
|
|
@@ -3591,15 +3639,33 @@ function writeFoundationsStories(foundations) {
|
|
|
3591
3639
|
const effectiveEasings = easingRows.length > 0 ? easingRows.slice(0, 4) : TAILWIND_DEFAULT_EASINGS;
|
|
3592
3640
|
const hasCustomDurations = durationRows.length > 0;
|
|
3593
3641
|
|
|
3642
|
+
// ── Scan comp.tokens[] for all motion utilities (project-specific) ─────────
|
|
3643
|
+
// This captures transition-*, duration-*, animate-*, ease-*, delay-* from every component
|
|
3644
|
+
const motionPattern = /^(transition|duration|animate|ease|delay)-/;
|
|
3645
|
+
const compMotionCounts = {};
|
|
3646
|
+
for (const comp of (components || [])) {
|
|
3647
|
+
for (const tok of (comp.tokens || [])) {
|
|
3648
|
+
if (motionPattern.test(tok)) {
|
|
3649
|
+
compMotionCounts[tok] = (compMotionCounts[tok] || 0) + 1;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
// Merge with tokenUsage.animations (captures animate-* from page-level scans too)
|
|
3654
|
+
for (const a of (foundations?.tokenUsage?.animations || [])) {
|
|
3655
|
+
compMotionCounts[a.token] = (compMotionCounts[a.token] || 0) + a.count;
|
|
3656
|
+
}
|
|
3594
3657
|
// Which duration tokens are actually used in the project?
|
|
3595
3658
|
const usedDurationKeys = new Set(
|
|
3596
|
-
(
|
|
3597
|
-
.filter(
|
|
3598
|
-
.map(
|
|
3659
|
+
Object.keys(compMotionCounts)
|
|
3660
|
+
.filter(t => t.startsWith("duration-"))
|
|
3661
|
+
.map(t => t.replace("duration-", ""))
|
|
3599
3662
|
);
|
|
3600
3663
|
|
|
3601
|
-
// All motion-related token chips
|
|
3602
|
-
const motionChips =
|
|
3664
|
+
// All motion-related token chips sorted by usage count
|
|
3665
|
+
const motionChips = Object.entries(compMotionCounts)
|
|
3666
|
+
.sort((a, b) => b[1] - a[1])
|
|
3667
|
+
.slice(0, 24)
|
|
3668
|
+
.map(([token, count]) => ({ token, count }));
|
|
3603
3669
|
|
|
3604
3670
|
const motionContent = [
|
|
3605
3671
|
"import React from \"react\";",
|
|
@@ -4693,7 +4759,7 @@ function main() {
|
|
|
4693
4759
|
|
|
4694
4760
|
ensureDir(STORIES_DIR);
|
|
4695
4761
|
ensureDir(path.join(STORIES_DIR, "foundations"));
|
|
4696
|
-
writeFoundationsStories(foundations);
|
|
4762
|
+
writeFoundationsStories(foundations, components);
|
|
4697
4763
|
writeCursorRules(components, foundations);
|
|
4698
4764
|
const componentSuggestions = data.componentSuggestions;
|
|
4699
4765
|
if (componentSuggestions?.length) {
|