vibe-design-system 2.8.79 → 2.8.82

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.79",
3
+ "version": "2.8.82",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -1429,11 +1429,17 @@ function extractFoundations() {
1429
1429
  }
1430
1430
  }
1431
1431
 
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);
1432
+ // body { font-family } — Tailwind v4 sets font on html/:host, NOT body
1433
+ // Use [^}]* to stay within block boundaries ([\s\S]*? crosses blocks → mono font false-positive)
1434
+ const bodyMatch =
1435
+ css.match(/\bbody\s*\{[^}]*font-family:\s*([^;]+);/) ||
1436
+ css.match(/html\s*(?:,\s*:host)?\s*\{[^}]*font-family:\s*([^;]+);/); // Tailwind v4
1435
1437
  if (bodyMatch) typography.body = bodyMatch[1].trim();
1436
- const monoMatch = css.match(/code,\s*pre,\s*\.font-mono\s*\{[^}]*font-family:\s*([^;]+);/s);
1438
+ // Mono: code/kbd/samp/pre block (Tailwind v4 uses code,kbd,samp,pre not code,pre,.font-mono)
1439
+ const monoMatch =
1440
+ css.match(/code,\s*pre,\s*\.font-mono\s*\{[^}]*font-family:\s*([^;]+);/) ||
1441
+ css.match(/code,\s*kbd[^{]*\{[^}]*font-family:\s*([^;]+);/) ||
1442
+ css.match(/\bcode\b[^{,]*\{[^}]*font-family:\s*([^;]+);/);
1437
1443
  if (monoMatch) typography.mono = monoMatch[1].trim();
1438
1444
  // CSS custom properties for fonts (--font-sans, --font-mono, --font-display, etc.)
1439
1445
  const fontVarRe = /--font([\w-]*):\s*([^;\n]+);/g;
@@ -1444,14 +1450,27 @@ function extractFoundations() {
1444
1450
  if (!typography[key]) typography[key] = val;
1445
1451
  }
1446
1452
  // Resolve var(--font-*) references in body/mono to actual font names
1447
- // Key construction must match the fontVarRe loop: --font-sans "font-sans" → "fontSans"
1453
+ // Also handles Tailwind v4 --default-font-family / --default-mono-font-family chain
1448
1454
  function resolveTypoVar(val) {
1449
1455
  if (!val || !val.startsWith("var(")) return val;
1456
+ // Direct --font-* reference: var(--font-sans) → typography.fontSans
1450
1457
  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;
1458
+ if (m) {
1459
+ const key = `font-${m[1]}`.replace(/-([a-zA-Z])/g, (_, c) => c.toUpperCase());
1460
+ return (typography[key] && !typography[key].startsWith("var(")) ? typography[key] : val;
1461
+ }
1462
+ // Tailwind v4: var(--default-font-family, ...) → resolve via fontSans
1463
+ if (val.includes("--default-font-family") && !val.includes("--default-mono")) {
1464
+ if (typography.fontSans && !typography.fontSans.startsWith("var(")) return typography.fontSans;
1465
+ // Fallback: strip the var() wrapper to get the fallback list
1466
+ return val.replace(/^var\(--default-font-family,\s*/, "").replace(/\)\s*$/, "").trim() || val;
1467
+ }
1468
+ // Tailwind v4: var(--default-mono-font-family, ...) → resolve via fontMono
1469
+ if (val.includes("--default-mono-font-family")) {
1470
+ if (typography.fontMono && !typography.fontMono.startsWith("var(")) return typography.fontMono;
1471
+ return val.replace(/^var\(--default-mono-font-family,\s*/, "").replace(/\)\s*$/, "").trim() || val;
1472
+ }
1473
+ return val;
1455
1474
  }
1456
1475
  if (typography.body) typography.body = resolveTypoVar(typography.body);
1457
1476
  if (typography.mono) typography.mono = resolveTypoVar(typography.mono);
@@ -2207,6 +2226,21 @@ function extractVariantUsage(componentResults) {
2207
2226
  }
2208
2227
  }
2209
2228
 
2229
+ /**
2230
+ * Heuristic: detect complex page-level components that should not get individual stories.
2231
+ * Criteria: 500+ lines AND 4+ internal capitalized component definitions AND no cva()/forwardRef.
2232
+ * Examples: a 900-line MethodologyMap.tsx with 8 inline sub-components.
2233
+ */
2234
+ function isComplexPageComponent(content) {
2235
+ const lines = content.split("\n").length;
2236
+ if (lines < 500) return false;
2237
+ const internalComps = (content.match(/\bconst\s+[A-Z][A-Za-z]+\s*[:=]\s*\(/g) || []).length;
2238
+ if (internalComps < 4) return false;
2239
+ // Don't flag UI primitives that use cva() or forwardRef — those are styled components
2240
+ if (content.includes("cva(") || content.includes("forwardRef")) return false;
2241
+ return true;
2242
+ }
2243
+
2210
2244
  function scan() {
2211
2245
  const relativeFiles = COMPONENTS_DIR ? getAllComponentFiles(COMPONENTS_DIR) : [];
2212
2246
  if (!COMPONENTS_DIR) {
@@ -2231,7 +2265,8 @@ function scan() {
2231
2265
  description = "";
2232
2266
  }
2233
2267
  const tokens = extractTailwindTokens(content);
2234
- results.push({ file: rel, name, group, category, description, tokens });
2268
+ const isPageComponent = isComplexPageComponent(content);
2269
+ results.push({ file: rel, name, group, category, description, tokens, ...(isPageComponent ? { isPageComponent: true } : {}) });
2235
2270
  }
2236
2271
  if (PAGES_DIR && fs.existsSync(PAGES_DIR)) {
2237
2272
  const pageFiles = getAllComponentFiles(PAGES_DIR);
@@ -1792,6 +1792,96 @@ function buildRecipeStoryContent(comp, componentName, importPath, title, source,
1792
1792
  }
1793
1793
  lines.push(`};`);
1794
1794
  }
1795
+
1796
+ // --- Design Tokens story (same block as generateStoryFile) ---
1797
+ {
1798
+ const compTokens = Array.isArray(comp.tokens) ? comp.tokens : [];
1799
+ const foundColors = FOUNDATIONS_DATA?.colors || {};
1800
+ if (compTokens.length >= 3) {
1801
+ const cleanTokens = compTokens.filter(t => !/:/.test(t));
1802
+ const colorRaw = cleanTokens.filter(t =>
1803
+ /^(bg|text|border|ring|from|to|fill|stroke)-/.test(t) &&
1804
+ !/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl|\d)/.test(t)
1805
+ );
1806
+ const spacingRaw = cleanTokens.filter(t => /^(p[xylrbt]?-|m[xylrbt]?-|gap|space-[xy]|w-|h-|min-[wh]|max-[wh]|size-)/.test(t));
1807
+ const typographyRaw = cleanTokens.filter(t => /^(text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl)|font-)/.test(t));
1808
+ const radiusRaw = cleanTokens.filter(t => /^rounded/.test(t));
1809
+ const animRaw = cleanTokens.filter(t => /^(transition|duration|animate|ease|delay)-/.test(t));
1810
+ const resolvedColors = colorRaw.map(token => {
1811
+ const m = token.match(/^(?:bg|text|border|ring|from|to|fill|stroke)-(.+)$/);
1812
+ const key = m ? m[1] : null;
1813
+ const baseKey = key ? key.replace(/\/[\d.]+$/, "") : null;
1814
+ const entry = key ? (foundColors[key] || (baseKey !== key ? foundColors[baseKey] : null)) : null;
1815
+ const isValidCssColor = (v) => /^#[0-9a-fA-F]{3,8}$/.test(v) || /^(rgb|rgba|hsl|hsla|oklch|oklab|lch|lab|color)\s*\(/.test(v) || v === 'transparent';
1816
+ const hex = entry?.hex && isValidCssColor(entry.hex) ? entry.hex : null;
1817
+ return { token, hex, label: baseKey || key };
1818
+ });
1819
+ const hasContent = resolvedColors.length > 0 || spacingRaw.length > 0 || typographyRaw.length > 0 || radiusRaw.length > 0 || animRaw.length > 0;
1820
+ if (hasContent) {
1821
+ lines.push("");
1822
+ lines.push(`export const Tokens: Story = {`);
1823
+ lines.push(` name: "Design Tokens",`);
1824
+ lines.push(` parameters: { layout: "fullscreen" },`);
1825
+ lines.push(` render: () => {`);
1826
+ lines.push(` const colorTokens = ${JSON.stringify(resolvedColors)};`);
1827
+ lines.push(` const spacingTokens = ${JSON.stringify(spacingRaw)};`);
1828
+ lines.push(` const typographyTokens = ${JSON.stringify(typographyRaw)};`);
1829
+ lines.push(` const radiusTokens = ${JSON.stringify(radiusRaw)};`);
1830
+ lines.push(` const animationTokens = ${JSON.stringify(animRaw)};`);
1831
+ lines.push(` const chip = (label: string, bg: string, color: string) => (`);
1832
+ lines.push(` <span key={label} style={{ fontFamily: "monospace", fontSize: 11, background: bg, color, padding: "3px 9px", borderRadius: 5, border: \`1px solid \${bg === "#f9fafb" ? "#e5e7eb" : bg}\`, whiteSpace: "nowrap" as any }}>{label}</span>`);
1833
+ lines.push(` );`);
1834
+ lines.push(` const section = (title: string, children: any) => (`);
1835
+ lines.push(` <section style={{ marginBottom: 28 }}>`);
1836
+ lines.push(` <p style={{ margin: "0 0 10px", fontSize: 11, fontWeight: 700, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.08em" }}>{title}</p>`);
1837
+ lines.push(` {children}`);
1838
+ lines.push(` </section>`);
1839
+ lines.push(` );`);
1840
+ lines.push(` return (`);
1841
+ lines.push(` <div style={{ padding: 40, background: "#fff", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh", width: "100%" }}>`);
1842
+ lines.push(` <h2 style={{ fontSize: 22, fontWeight: 700, margin: "0 0 6px" }}>Design Tokens</h2>`);
1843
+ lines.push(` <p style={{ fontSize: 13, color: "#6b7280", margin: "0 0 32px" }}>Tailwind utilities used in <code style={{ background: "#f3f4f6", padding: "1px 6px", borderRadius: 4, fontSize: 12 }}>${componentName}</code> — resolved to project values.</p>`);
1844
+ lines.push(` {colorTokens.length > 0 && section("Color", (`);
1845
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>`);
1846
+ lines.push(` {colorTokens.map(({ token, hex, label }) => (`);
1847
+ lines.push(` <div key={token} style={{ display: "flex", alignItems: "center", gap: 7, padding: "7px 12px", border: "1px solid #e5e7eb", borderRadius: 8, background: "#f9fafb" }}>`);
1848
+ lines.push(` {hex && <span style={{ display: "inline-block", width: 16, height: 16, borderRadius: 4, background: hex, border: "1px solid rgba(0,0,0,0.1)", flexShrink: 0 }} />}`);
1849
+ lines.push(` <code style={{ fontSize: 12, color: "#374151", fontWeight: 600 }}>{token}</code>`);
1850
+ lines.push(` {hex && <span style={{ fontSize: 11, color: "#9ca3af" }}>{hex}</span>}`);
1851
+ lines.push(` </div>`);
1852
+ lines.push(` ))}`);
1853
+ lines.push(` </div>`);
1854
+ lines.push(` ))}`);
1855
+ lines.push(` {spacingTokens.length > 0 && section("Spacing", (`);
1856
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>{spacingTokens.map(t => chip(t, "#faf5ff", "#6d28d9"))}</div>`);
1857
+ lines.push(` ))}`);
1858
+ lines.push(` {typographyTokens.length > 0 && section("Typography", (`);
1859
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>{typographyTokens.map(t => chip(t, "#fffbeb", "#92400e"))}</div>`);
1860
+ lines.push(` ))}`);
1861
+ lines.push(` {radiusTokens.length > 0 && section("Border Radius", (`);
1862
+ lines.push(` <div style={{ display: "flex", gap: 16, flexWrap: "wrap", alignItems: "flex-end" }}>`);
1863
+ lines.push(` {radiusTokens.map(t => {`);
1864
+ lines.push(` const px = t === "rounded-none" ? 0 : t === "rounded-sm" ? 2 : t === "rounded" ? 4 : t === "rounded-md" ? 6 : t === "rounded-lg" ? 8 : t === "rounded-xl" ? 12 : t === "rounded-2xl" ? 16 : t === "rounded-3xl" ? 24 : t === "rounded-full" ? 9999 : 4;`);
1865
+ lines.push(` return (`);
1866
+ lines.push(` <div key={t} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }}>`);
1867
+ lines.push(` <div style={{ width: 44, height: 44, background: "#6366f1", borderRadius: px }} />`);
1868
+ lines.push(` <code style={{ fontSize: 10, color: "#6b7280" }}>{t}</code>`);
1869
+ lines.push(` </div>`);
1870
+ lines.push(` );`);
1871
+ lines.push(` })}`);
1872
+ lines.push(` </div>`);
1873
+ lines.push(` ))}`);
1874
+ lines.push(` {animationTokens.length > 0 && section("Motion", (`);
1875
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>{animationTokens.map(t => chip(t, "#f0fdf4", "#166534"))}</div>`);
1876
+ lines.push(` ))}`);
1877
+ lines.push(` </div>`);
1878
+ lines.push(` );`);
1879
+ lines.push(` },`);
1880
+ lines.push(`};`);
1881
+ }
1882
+ }
1883
+ }
1884
+
1795
1885
  return lines.join("\n");
1796
1886
  }
1797
1887
 
@@ -2115,42 +2205,46 @@ function buildStoryFileContent(comp) {
2115
2205
  // Component-specific stories for non-variant components (Input, Textarea, etc.)
2116
2206
  // Skip buildSpecialStories when multi-dimension detection found CVA dimensions —
2117
2207
  // generic detection produces better stories than hardcoded ones.
2208
+ let generatedSpecialStories = false;
2118
2209
  if (!hasDimensions) {
2119
2210
  const specialStories = buildSpecialStories(componentName, variants);
2120
2211
  if (specialStories) {
2121
2212
  lines.push(specialStories);
2122
- return lines.join("\n");
2213
+ generatedSpecialStories = true;
2214
+ // Do NOT return here — fall through to Tokens + Usage stories below
2123
2215
  }
2124
2216
  }
2125
2217
 
2126
- // Profile-driven render + children — single source of truth via getStoryProfile()
2127
- const useSafeWrapper = profile === "SAFE";
2128
- const RenderTarget = useSafeWrapper ? "SafeWrapper" : "ComponentRef";
2129
- const argsFallback = !useSafeWrapper && (componentName && RENDER_ARGS_FALLBACKS[componentName]) || "";
2130
- const renderLine = buildProfileRenderLine(profile, RenderTarget, argsFallback);
2131
- const childrenArgLine = buildProfileChildrenArgLine(profile);
2218
+ if (!generatedSpecialStories) {
2219
+ // Profile-driven render + children — single source of truth via getStoryProfile()
2220
+ const useSafeWrapper = profile === "SAFE";
2221
+ const RenderTarget = useSafeWrapper ? "SafeWrapper" : "ComponentRef";
2222
+ const argsFallback = !useSafeWrapper && (componentName && RENDER_ARGS_FALLBACKS[componentName]) || "";
2223
+ const renderLine = buildProfileRenderLine(profile, RenderTarget, argsFallback);
2224
+ const childrenArgLine = buildProfileChildrenArgLine(profile);
2132
2225
 
2133
- if (hasDimensions) {
2134
- const multiStories = buildMultiDimensionStories(
2135
- variantMap, renderLine, childrenArgLine, defaultArgLines, componentName,
2136
- comp.variantUsage || null
2137
- );
2138
- if (multiStories) {
2139
- lines.push(multiStories);
2140
- }
2141
- } else {
2142
- // No dimensions detected: single Default story
2143
- lines.push(`export const Default: Story = {`);
2144
- lines.push(renderLine);
2145
- const storyArgLines = [];
2146
- if (childrenArgLine(componentName)) storyArgLines.push(childrenArgLine(componentName));
2147
- for (const line of defaultArgLines) storyArgLines.push(line);
2148
- if (storyArgLines.length > 0) {
2149
- lines.push(` args: {`);
2150
- for (const line of storyArgLines) lines.push(line);
2151
- lines.push(` },`);
2226
+ if (hasDimensions) {
2227
+ const multiStories = buildMultiDimensionStories(
2228
+ variantMap, renderLine, childrenArgLine, defaultArgLines, componentName,
2229
+ comp.variantUsage || null
2230
+ );
2231
+ if (multiStories) {
2232
+ lines.push(multiStories);
2233
+ }
2234
+ } else {
2235
+ // No dimensions detected: single Default story
2236
+ lines.push(`export const Default: Story = {`);
2237
+ lines.push(renderLine);
2238
+ const storyArgLines = [];
2239
+ if (childrenArgLine(componentName)) storyArgLines.push(childrenArgLine(componentName));
2240
+ for (const line of defaultArgLines) storyArgLines.push(line);
2241
+ if (storyArgLines.length > 0) {
2242
+ lines.push(` args: {`);
2243
+ for (const line of storyArgLines) lines.push(line);
2244
+ lines.push(` },`);
2245
+ }
2246
+ lines.push(`};`);
2152
2247
  }
2153
- lines.push(`};`);
2154
2248
  }
2155
2249
 
2156
2250
  // --- Project-specific usage stories ---
@@ -2190,7 +2284,7 @@ function buildStoryFileContent(comp) {
2190
2284
  /^(bg|text|border|ring|from|to|fill|stroke)-/.test(t) &&
2191
2285
  !/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl|\d)/.test(t)
2192
2286
  );
2193
- const spacingRaw = cleanTokens.filter(t => /^(p[xylrbt]?|m[xylrbt]?|gap|space-[xy]|w-|h-|min-[wh]|max-[wh]|size-)/.test(t));
2287
+ const spacingRaw = cleanTokens.filter(t => /^(p[xylrbt]?-|m[xylrbt]?-|gap|space-[xy]|w-|h-|min-[wh]|max-[wh]|size-)/.test(t));
2194
2288
  const typographyRaw = cleanTokens.filter(t => /^(text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl)|font-)/.test(t));
2195
2289
  const radiusRaw = cleanTokens.filter(t => /^rounded/.test(t));
2196
2290
  const animRaw = cleanTokens.filter(t => /^(transition|duration|animate|ease|delay)-/.test(t));
@@ -2202,7 +2296,8 @@ function buildStoryFileContent(comp) {
2202
2296
  // Strip opacity modifier (e.g. "muted/20" → "muted") as a fallback
2203
2297
  const baseKey = key ? key.replace(/\/[\d.]+$/, "") : null;
2204
2298
  const entry = key ? (foundColors[key] || (baseKey !== key ? foundColors[baseKey] : null)) : null;
2205
- const hex = entry?.hex && /^#[0-9a-fA-F]{3,8}$/.test(entry.hex) ? entry.hex : null;
2299
+ const isValidCssColor = (v) => /^#[0-9a-fA-F]{3,8}$/.test(v) || /^(rgb|rgba|hsl|hsla|oklch|oklab|lch|lab|color)\s*\(/.test(v) || v === 'transparent';
2300
+ const hex = entry?.hex && isValidCssColor(entry.hex) ? entry.hex : null;
2206
2301
  return { token, hex, label: baseKey || key };
2207
2302
  });
2208
2303
 
@@ -4246,7 +4341,8 @@ function writeComponentInventoryStory(components, foundations) {
4246
4341
  let key = m ? m[1] : null;
4247
4342
  if (key) key = key.replace(/\/\d+$/, "");
4248
4343
  const entry = key ? foundColors[key] : null;
4249
- const hex = entry?.hex && /^#[0-9a-fA-F]{3,8}$/.test(entry.hex) ? entry.hex : null;
4344
+ const isValidCssColor = (v) => /^#[0-9a-fA-F]{3,8}$/.test(v) || /^(rgb|rgba|hsl|hsla|oklch|oklab|lch|lab|color)\s*\(/.test(v) || v === 'transparent';
4345
+ const hex = entry?.hex && isValidCssColor(entry.hex) ? entry.hex : null;
4250
4346
  return { token, hex };
4251
4347
  })
4252
4348
  .filter(s => s.hex);
@@ -4906,6 +5002,11 @@ function main() {
4906
5002
  const storyFileName = `${componentName}.stories.tsx`;
4907
5003
  const storyPath = path.join(STORIES_DIR, storyFileName);
4908
5004
  if (SKIP_LIST.includes(componentName)) continue;
5005
+ // Skip complex page-level components detected by scan (500+ lines, 4+ inline sub-components)
5006
+ if (comp.isPageComponent) {
5007
+ console.log(`[VDS] ${componentName} → skipped (complex page component — add to extraSkipList to suppress this message)`);
5008
+ continue;
5009
+ }
4909
5010
  const requiredCount = Array.isArray(comp.props) ? comp.props.filter((p) => p.required === true).length : 0;
4910
5011
  if (requiredCount > 3) {
4911
5012
  console.log(`[VDS] ${componentName} → skipped (${requiredCount} required props — too complex to auto-generate)`);