vibe-design-system 2.8.75 → 2.8.78
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
|
@@ -1397,9 +1397,14 @@ function extractFoundations() {
|
|
|
1397
1397
|
if (cssChunks.length > 0) {
|
|
1398
1398
|
const css = cssChunks.join("\n");
|
|
1399
1399
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1400
|
+
// Collect ALL :root blocks (a file may have multiple, e.g. a small one early
|
|
1401
|
+
// and the main one inside @layer base { :root { } })
|
|
1402
|
+
const rootBlocks = [];
|
|
1403
|
+
const rootGlobalRe = /:root\s*\{([\s\S]*?)\}/g;
|
|
1404
|
+
let rootM;
|
|
1405
|
+
while ((rootM = rootGlobalRe.exec(css)) !== null) { rootBlocks.push(rootM[1]); }
|
|
1406
|
+
if (rootBlocks.length > 0) {
|
|
1407
|
+
const rootVars = parseCssVarBlock(rootBlocks.join("\n"));
|
|
1403
1408
|
for (const [name, value] of Object.entries(rootVars)) {
|
|
1404
1409
|
if (/\d+\s+\d+%/.test(value) || /\d+%/.test(value)) {
|
|
1405
1410
|
const hsl = `hsl(${value})`;
|
|
@@ -1424,11 +1429,13 @@ function extractFoundations() {
|
|
|
1424
1429
|
}
|
|
1425
1430
|
}
|
|
1426
1431
|
|
|
1427
|
-
|
|
1432
|
+
// body { font-family } — supports nested @layer base { body { } } structure
|
|
1433
|
+
const bodyMatch = css.match(/\bbody\s*\{[\s\S]*?font-family:\s*([^;]+);/) ||
|
|
1434
|
+
css.match(/body\s*\{[^}]*font-family:\s*([^;]+);/s);
|
|
1428
1435
|
if (bodyMatch) typography.body = bodyMatch[1].trim();
|
|
1429
1436
|
const monoMatch = css.match(/code,\s*pre,\s*\.font-mono\s*\{[^}]*font-family:\s*([^;]+);/s);
|
|
1430
1437
|
if (monoMatch) typography.mono = monoMatch[1].trim();
|
|
1431
|
-
//
|
|
1438
|
+
// CSS custom properties for fonts (--font-sans, --font-mono, --font-display, etc.)
|
|
1432
1439
|
const fontVarRe = /--font([\w-]*):\s*([^;\n]+);/g;
|
|
1433
1440
|
let fvm;
|
|
1434
1441
|
while ((fvm = fontVarRe.exec(css)) !== null) {
|
|
@@ -1436,6 +1443,18 @@ function extractFoundations() {
|
|
|
1436
1443
|
const val = fvm[2].trim();
|
|
1437
1444
|
if (!typography[key]) typography[key] = val;
|
|
1438
1445
|
}
|
|
1446
|
+
// Resolve var(--font-*) references in body/mono to actual font names
|
|
1447
|
+
// Key construction must match the fontVarRe loop: --font-sans → "font-sans" → "fontSans"
|
|
1448
|
+
function resolveTypoVar(val) {
|
|
1449
|
+
if (!val || !val.startsWith("var(")) return val;
|
|
1450
|
+
const m = val.match(/var\(--font-([\w-]+)\)/);
|
|
1451
|
+
if (!m) return val;
|
|
1452
|
+
// prepend "-" so camelCase conversion matches: "font-" + "sans" → "fontSans"
|
|
1453
|
+
const key = `font-${m[1]}`.replace(/-([a-zA-Z])/g, (_, c) => c.toUpperCase());
|
|
1454
|
+
return (typography[key] && !typography[key].startsWith("var(")) ? typography[key] : val;
|
|
1455
|
+
}
|
|
1456
|
+
if (typography.body) typography.body = resolveTypoVar(typography.body);
|
|
1457
|
+
if (typography.mono) typography.mono = resolveTypoVar(typography.mono);
|
|
1439
1458
|
|
|
1440
1459
|
// ── Type Scale: extract --text-* font sizes + line-heights from compiled CSS
|
|
1441
1460
|
const textSteps = ["xs","sm","base","lg","xl","2xl","3xl","4xl","5xl","6xl","7xl","8xl","9xl"];
|
|
@@ -1910,6 +1929,33 @@ function extractFoundations() {
|
|
|
1910
1929
|
}
|
|
1911
1930
|
}
|
|
1912
1931
|
|
|
1932
|
+
// ── Scan source files for arbitrary Tailwind [font-family:'FontName'] usage ──
|
|
1933
|
+
// Covers projects like Blurple where the font is only set via arbitrary Tailwind classes
|
|
1934
|
+
if (SRC_DIR && fs.existsSync(SRC_DIR)) {
|
|
1935
|
+
const arbFonts = new Set();
|
|
1936
|
+
const arbRe = /\[font-family:\s*'([^']+)'/g;
|
|
1937
|
+
function scanSrcForFonts(dir) {
|
|
1938
|
+
if (!fs.existsSync(dir)) return;
|
|
1939
|
+
let entries;
|
|
1940
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
1941
|
+
for (const e of entries) {
|
|
1942
|
+
const full = path.join(dir, e.name);
|
|
1943
|
+
if (e.isDirectory()) {
|
|
1944
|
+
if (!IGNORE_DIRS.includes(e.name)) scanSrcForFonts(full);
|
|
1945
|
+
} else if (/\.(tsx|jsx|ts|js|css)$/i.test(e.name)) {
|
|
1946
|
+
try {
|
|
1947
|
+
const content = fs.readFileSync(full, "utf-8");
|
|
1948
|
+
arbRe.lastIndex = 0;
|
|
1949
|
+
let m;
|
|
1950
|
+
while ((m = arbRe.exec(content)) !== null) arbFonts.add(m[1]);
|
|
1951
|
+
} catch (_) {}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
scanSrcForFonts(SRC_DIR);
|
|
1956
|
+
if (arbFonts.size > 0) typography.arbitraryFonts = Array.from(arbFonts);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1913
1959
|
return {
|
|
1914
1960
|
colors: foundationsColors,
|
|
1915
1961
|
typography,
|
|
@@ -2101,6 +2147,66 @@ function extractButtonUsage() {
|
|
|
2101
2147
|
};
|
|
2102
2148
|
}
|
|
2103
2149
|
|
|
2150
|
+
/**
|
|
2151
|
+
* Scan all non-component source files and count how many times each variant value
|
|
2152
|
+
* is explicitly used for each component (e.g. <Button variant="destructive">).
|
|
2153
|
+
* Returns a map: { [compName]: { [variantValue]: count } }
|
|
2154
|
+
* Only static string literals are counted; dynamic values are ignored.
|
|
2155
|
+
*/
|
|
2156
|
+
function extractVariantUsage(componentResults) {
|
|
2157
|
+
if (!SRC_DIR || !fs.existsSync(SRC_DIR)) return;
|
|
2158
|
+
const compDir = COMPONENTS_DIR ? path.resolve(COMPONENTS_DIR) : null;
|
|
2159
|
+
|
|
2160
|
+
// Collect all .tsx/.jsx/.ts/.js files in SRC_DIR that are NOT inside COMPONENTS_DIR
|
|
2161
|
+
function collectNonCompFiles(dir, acc = []) {
|
|
2162
|
+
if (!fs.existsSync(dir)) return acc;
|
|
2163
|
+
let entries;
|
|
2164
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return acc; }
|
|
2165
|
+
for (const e of entries) {
|
|
2166
|
+
const full = path.join(dir, e.name);
|
|
2167
|
+
if (e.isDirectory()) {
|
|
2168
|
+
if (IGNORE_DIRS.includes(e.name)) continue;
|
|
2169
|
+
if (compDir && path.resolve(full) === compDir) continue; // skip components dir
|
|
2170
|
+
collectNonCompFiles(full, acc);
|
|
2171
|
+
} else if (e.isFile() && /\.(tsx|jsx|ts|js)$/i.test(e.name) &&
|
|
2172
|
+
!e.name.endsWith(".stories.tsx") && !e.name.endsWith(".stories.ts") &&
|
|
2173
|
+
!e.name.endsWith(".test.tsx") && !e.name.endsWith(".test.ts") &&
|
|
2174
|
+
!e.name.endsWith(".spec.tsx") && !e.name.endsWith(".spec.ts")) {
|
|
2175
|
+
acc.push(full);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
return acc;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
const nonCompFiles = collectNonCompFiles(SRC_DIR);
|
|
2182
|
+
if (nonCompFiles.length === 0) return;
|
|
2183
|
+
|
|
2184
|
+
for (const comp of componentResults) {
|
|
2185
|
+
const name = comp.name;
|
|
2186
|
+
// Match <ComponentName ...variant="X" or variant='X' anywhere on the same tag
|
|
2187
|
+
// Simple regex: looks for <Name followed (anywhere on the line) by variant="X"
|
|
2188
|
+
const variantRe = new RegExp(
|
|
2189
|
+
`<${name}[\\s\\S]{0,300}?\\bvariant=(?:"([^"]+)"|'([^']+)')`,
|
|
2190
|
+
"g"
|
|
2191
|
+
);
|
|
2192
|
+
const usage = {};
|
|
2193
|
+
for (const file of nonCompFiles) {
|
|
2194
|
+
let content;
|
|
2195
|
+
try { content = fs.readFileSync(file, "utf-8"); } catch (_) { continue; }
|
|
2196
|
+
// Reset lastIndex for global regex per file
|
|
2197
|
+
variantRe.lastIndex = 0;
|
|
2198
|
+
let m;
|
|
2199
|
+
while ((m = variantRe.exec(content)) !== null) {
|
|
2200
|
+
const val = m[1] || m[2];
|
|
2201
|
+
if (val) usage[val] = (usage[val] || 0) + 1;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
if (Object.keys(usage).length > 0) {
|
|
2205
|
+
comp.variantUsage = usage;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2104
2210
|
function scan() {
|
|
2105
2211
|
const relativeFiles = COMPONENTS_DIR ? getAllComponentFiles(COMPONENTS_DIR) : [];
|
|
2106
2212
|
if (!COMPONENTS_DIR) {
|
|
@@ -2144,6 +2250,9 @@ function scan() {
|
|
|
2144
2250
|
});
|
|
2145
2251
|
}
|
|
2146
2252
|
}
|
|
2253
|
+
// Attach real-world variant usage (which variant values are actually passed in pages/layouts)
|
|
2254
|
+
extractVariantUsage(results);
|
|
2255
|
+
|
|
2147
2256
|
const foundations = extractFoundations();
|
|
2148
2257
|
foundations.icons = extractLucideIconsUsed(SRC_DIR);
|
|
2149
2258
|
foundations.brand = { assets: extractBrandAssets() };
|
|
@@ -679,7 +679,7 @@ function buildComponentVariantMap(source, effectiveProps) {
|
|
|
679
679
|
/**
|
|
680
680
|
* Generate multi-dimension stories: individual per variant, per size, per state, + AllVariants grid.
|
|
681
681
|
*/
|
|
682
|
-
function buildMultiDimensionStories(variantMap, renderLine, childrenArgLine, defaultArgLines, componentName) {
|
|
682
|
+
function buildMultiDimensionStories(variantMap, renderLine, childrenArgLine, defaultArgLines, componentName, variantUsage) {
|
|
683
683
|
const lines = [];
|
|
684
684
|
const { dimensions, booleanStates, primaryDimension, secondaryDimensions } = variantMap;
|
|
685
685
|
|
|
@@ -690,10 +690,26 @@ function buildMultiDimensionStories(variantMap, renderLine, childrenArgLine, def
|
|
|
690
690
|
return !match || !allDimNames.has(match[1]);
|
|
691
691
|
});
|
|
692
692
|
|
|
693
|
+
// Pre-compute filtered primary values (used in sections A and D).
|
|
694
|
+
// variantUsage is comp.variantUsage: { [variantValue]: count } (only for "variant" prop)
|
|
695
|
+
// If usage data exists, keep only values that were actually used + the default value.
|
|
696
|
+
// If no usage data exists (old scans / dynamic-only usage) → keep all values.
|
|
697
|
+
let filteredPrimaryValues = null;
|
|
698
|
+
if (primaryDimension && dimensions[primaryDimension]) {
|
|
699
|
+
const _pd = dimensions[primaryDimension];
|
|
700
|
+
const _def = _pd.default || _pd.values[0];
|
|
701
|
+
// Only apply filtering when the primary dimension is "variant" (that's what we scan)
|
|
702
|
+
const _umap = (primaryDimension === "variant" && variantUsage && Object.keys(variantUsage).length > 0)
|
|
703
|
+
? variantUsage : null;
|
|
704
|
+
filteredPrimaryValues = _umap
|
|
705
|
+
? _pd.values.filter(v => v === _def || _umap[v])
|
|
706
|
+
: _pd.values;
|
|
707
|
+
}
|
|
708
|
+
|
|
693
709
|
// --- A. Primary dimension stories (one per value) ---
|
|
694
710
|
if (primaryDimension && dimensions[primaryDimension]) {
|
|
695
|
-
const dim = dimensions[primaryDimension];
|
|
696
|
-
const defaultValue = dim.default ||
|
|
711
|
+
const dim = { ...dimensions[primaryDimension], values: filteredPrimaryValues || dimensions[primaryDimension].values };
|
|
712
|
+
const defaultValue = dim.default || dimensions[primaryDimension].values[0];
|
|
697
713
|
|
|
698
714
|
// Default story uses the default value
|
|
699
715
|
lines.push(`export const Default: Story = {`);
|
|
@@ -706,7 +722,7 @@ function buildMultiDimensionStories(variantMap, renderLine, childrenArgLine, def
|
|
|
706
722
|
lines.push(`};`);
|
|
707
723
|
lines.push("");
|
|
708
724
|
|
|
709
|
-
// Remaining values get their own stories
|
|
725
|
+
// Remaining values get their own stories (filtered by actual project usage)
|
|
710
726
|
for (const val of dim.values) {
|
|
711
727
|
if (val === defaultValue) continue;
|
|
712
728
|
const storyName = capitalize(val);
|
|
@@ -778,9 +794,9 @@ function buildMultiDimensionStories(variantMap, renderLine, childrenArgLine, def
|
|
|
778
794
|
lines.push("");
|
|
779
795
|
}
|
|
780
796
|
|
|
781
|
-
// --- D. AllVariants grid story (only if primary has 2+ values) ---
|
|
782
|
-
if (primaryDimension &&
|
|
783
|
-
const primaryValues =
|
|
797
|
+
// --- D. AllVariants grid story (only if primary has 2+ used values) ---
|
|
798
|
+
if (primaryDimension && filteredPrimaryValues && filteredPrimaryValues.length >= 2) {
|
|
799
|
+
const primaryValues = filteredPrimaryValues;
|
|
784
800
|
const secondaryDim = secondaryDimensions.find((d) => dimensions[d] && dimensions[d].values.length >= 2);
|
|
785
801
|
|
|
786
802
|
lines.push(`export const AllVariants: Story = {`);
|
|
@@ -2116,7 +2132,8 @@ function buildStoryFileContent(comp) {
|
|
|
2116
2132
|
|
|
2117
2133
|
if (hasDimensions) {
|
|
2118
2134
|
const multiStories = buildMultiDimensionStories(
|
|
2119
|
-
variantMap, renderLine, childrenArgLine, defaultArgLines, componentName
|
|
2135
|
+
variantMap, renderLine, childrenArgLine, defaultArgLines, componentName,
|
|
2136
|
+
comp.variantUsage || null
|
|
2120
2137
|
);
|
|
2121
2138
|
if (multiStories) {
|
|
2122
2139
|
lines.push(multiStories);
|
|
@@ -2182,9 +2199,11 @@ function buildStoryFileContent(comp) {
|
|
|
2182
2199
|
const resolvedColors = colorRaw.map(token => {
|
|
2183
2200
|
const m = token.match(/^(?:bg|text|border|ring|from|to|fill|stroke)-(.+)$/);
|
|
2184
2201
|
const key = m ? m[1] : null;
|
|
2185
|
-
|
|
2202
|
+
// Strip opacity modifier (e.g. "muted/20" → "muted") as a fallback
|
|
2203
|
+
const baseKey = key ? key.replace(/\/[\d.]+$/, "") : null;
|
|
2204
|
+
const entry = key ? (foundColors[key] || (baseKey !== key ? foundColors[baseKey] : null)) : null;
|
|
2186
2205
|
const hex = entry?.hex && /^#[0-9a-fA-F]{3,8}$/.test(entry.hex) ? entry.hex : null;
|
|
2187
|
-
return { token, hex, label: key };
|
|
2206
|
+
return { token, hex, label: baseKey || key };
|
|
2188
2207
|
});
|
|
2189
2208
|
|
|
2190
2209
|
const hasContent = resolvedColors.length > 0 || spacingRaw.length > 0 || typographyRaw.length > 0 || radiusRaw.length > 0 || animRaw.length > 0;
|
|
@@ -2634,15 +2653,34 @@ function writeFoundationsStories(foundations, components) {
|
|
|
2634
2653
|
const usedTextSizes = (foundations?.tokenUsage?.textSizes || []);
|
|
2635
2654
|
const usedTextMap = Object.fromEntries(usedTextSizes.map(({ token, count }) => [token, count]));
|
|
2636
2655
|
|
|
2637
|
-
// Font families
|
|
2656
|
+
// Font families — build from known keys, then add arbitrary fonts found in source
|
|
2638
2657
|
const fontFamilyKeys = ["fontSans", "fontMono", "body", "heading", "tailwindSans", "tailwindMono"];
|
|
2639
|
-
const
|
|
2640
|
-
.filter(([k]) =>
|
|
2658
|
+
const rawFamilyRows = Object.entries(typo)
|
|
2659
|
+
.filter(([k]) => k !== "arbitraryFonts" &&
|
|
2660
|
+
(fontFamilyKeys.includes(k) || (k.toLowerCase().includes("font") && !k.toLowerCase().includes("weight") && k !== "typeScale" && k !== "fontSize")))
|
|
2641
2661
|
.map(([k, v]) => {
|
|
2642
2662
|
const val = typeof v === "object" && v !== null ? (v.family || v.value || "") : Array.isArray(v) ? v.join(", ") : String(v);
|
|
2643
2663
|
return { token: k, value: val };
|
|
2644
2664
|
})
|
|
2645
|
-
.filter((r) => r.value);
|
|
2665
|
+
.filter((r) => r.value && !r.value.startsWith("var(")); // drop unresolved CSS var refs
|
|
2666
|
+
|
|
2667
|
+
// Add arbitrary [font-family:'Name'] fonts found in source files (e.g. Blurple's Urbanist)
|
|
2668
|
+
if (Array.isArray(typo.arbitraryFonts)) {
|
|
2669
|
+
for (const f of typo.arbitraryFonts) {
|
|
2670
|
+
if (!rawFamilyRows.some((r) => r.value.includes(f))) {
|
|
2671
|
+
rawFamilyRows.push({ token: "projectFont", value: `'${f}', sans-serif` });
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
// Deduplicate: keep only the first entry per unique primary font name
|
|
2677
|
+
const seenFontNames = new Set();
|
|
2678
|
+
const familyRows = rawFamilyRows.filter((r) => {
|
|
2679
|
+
const primary = r.value.split(",")[0].trim().replace(/['"]/g, "").toLowerCase();
|
|
2680
|
+
if (seenFontNames.has(primary)) return false;
|
|
2681
|
+
seenFontNames.add(primary);
|
|
2682
|
+
return true;
|
|
2683
|
+
});
|
|
2646
2684
|
|
|
2647
2685
|
// Font weights — check fontWeightScale (from twTheme.fontWeight), then legacy specific keys, then Tailwind defaults
|
|
2648
2686
|
const weightKeys = ["fontWeightNormal", "fontWeightMedium", "fontWeightSemibold", "fontWeightBold"];
|
|
@@ -2665,7 +2703,9 @@ function writeFoundationsStories(foundations, components) {
|
|
|
2665
2703
|
// Reverse type scale for display (largest first)
|
|
2666
2704
|
const scaleDesc = [...typeScale].reverse();
|
|
2667
2705
|
|
|
2668
|
-
const
|
|
2706
|
+
const firstArbitraryFont = Array.isArray(typo.arbitraryFonts) && typo.arbitraryFonts.length > 0
|
|
2707
|
+
? `'${typo.arbitraryFonts[0]}', sans-serif` : null;
|
|
2708
|
+
const sansFamily = typo.fontSans || typo.body || firstArbitraryFont || typo.tailwindSans || "system-ui, sans-serif";
|
|
2669
2709
|
|
|
2670
2710
|
const typoContent = [
|
|
2671
2711
|
"import React from \"react\";",
|