vibe-design-system 1.9.8 → 1.9.11
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
|
@@ -28,6 +28,7 @@ const CLI_LOCALE = (process.env.VDS_LOCALE === "tr" ? "tr" : "en");
|
|
|
28
28
|
const cliT = (key, n) => CLI_LOCALES[CLI_LOCALE][key].replace("{n}", String(n));
|
|
29
29
|
const SRC_DIR = path.join(PROJECT_ROOT, "src");
|
|
30
30
|
const COMPONENTS_DIR = path.join(PROJECT_ROOT, "src", "components");
|
|
31
|
+
const PAGES_DIR = path.join(PROJECT_ROOT, "src", "pages");
|
|
31
32
|
const OUTPUT_FILE = path.join(PROJECT_ROOT, "vds-output.json");
|
|
32
33
|
const PUBLIC_MANIFEST = path.join(PROJECT_ROOT, "public", "vds-output.json");
|
|
33
34
|
const HISTORY_FILE = path.join(PROJECT_ROOT, "vds-history.json");
|
|
@@ -351,6 +352,80 @@ function humanizeName(filePath) {
|
|
|
351
352
|
return base.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
|
|
352
353
|
}
|
|
353
354
|
|
|
355
|
+
const COMPONENT_SUGGESTION_KEYWORDS = [
|
|
356
|
+
{ re: /\bcard\b/i, name: "Card" },
|
|
357
|
+
{ re: /\bsection\b/i, name: "Section" },
|
|
358
|
+
{ re: /\bitem\b/i, name: "Item" },
|
|
359
|
+
{ re: /\bbadge\b/i, name: "Badge" },
|
|
360
|
+
{ re: /\brow\b/i, name: "Row" },
|
|
361
|
+
{ re: /\bgrid\b/i, name: "Grid" },
|
|
362
|
+
{ re: /\bhero\b/i, name: "Hero" },
|
|
363
|
+
{ re: /\bcta\b/i, name: "Cta" },
|
|
364
|
+
{ re: /\bstat\b/i, name: "Stat" },
|
|
365
|
+
{ re: /\bprice\b/i, name: "Pricing" },
|
|
366
|
+
{ re: /\bfeature\b/i, name: "Feature" },
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
function suggestNameFromPattern(pattern, index) {
|
|
370
|
+
const parts = [];
|
|
371
|
+
for (const { re, name } of COMPONENT_SUGGESTION_KEYWORDS) {
|
|
372
|
+
if (re.test(pattern)) parts.push(name);
|
|
373
|
+
}
|
|
374
|
+
if (parts.length > 0) return parts.join("");
|
|
375
|
+
return "Block" + (index + 1);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Scan src/pages/*.tsx for repeated className clusters; return component suggestions. */
|
|
379
|
+
function extractComponentSuggestions() {
|
|
380
|
+
if (!fs.existsSync(PAGES_DIR)) return [];
|
|
381
|
+
const pageFiles = getAllComponentFiles(PAGES_DIR);
|
|
382
|
+
const byPattern = new Map();
|
|
383
|
+
const re = /className\s*=\s*["']([^"']+)["']|className\s*=\s*\{\s*["']([^"']+)["']/g;
|
|
384
|
+
|
|
385
|
+
for (const rel of pageFiles) {
|
|
386
|
+
const fullPath = path.join(PAGES_DIR, rel);
|
|
387
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
388
|
+
const srcRel = "src/pages/" + rel.replace(/\\/g, "/");
|
|
389
|
+
let m;
|
|
390
|
+
re.lastIndex = 0;
|
|
391
|
+
while ((m = re.exec(content)) !== null) {
|
|
392
|
+
const raw = (m[1] ?? m[2] ?? "").trim().replace(/\s+/g, " ").trim();
|
|
393
|
+
if (!raw) continue;
|
|
394
|
+
if (!byPattern.has(raw)) {
|
|
395
|
+
const before = content.substring(0, m.index);
|
|
396
|
+
const lastOpen = before.lastIndexOf("<");
|
|
397
|
+
const tagMatch = lastOpen >= 0 ? content.slice(lastOpen).match(/<(\w+)/) : null;
|
|
398
|
+
const tagName = tagMatch ? tagMatch[1] : "div";
|
|
399
|
+
byPattern.set(raw, {
|
|
400
|
+
count: 0,
|
|
401
|
+
files: new Set(),
|
|
402
|
+
snippet: `<${tagName} className="${raw}">...</${tagName}>`,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
const entry = byPattern.get(raw);
|
|
406
|
+
entry.count += 1;
|
|
407
|
+
entry.files.add(srcRel);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const suggestions = [];
|
|
412
|
+
let blockIndex = 0;
|
|
413
|
+
for (const [pattern, { count, files, snippet }] of byPattern.entries()) {
|
|
414
|
+
if (count < 2) continue;
|
|
415
|
+
const suggestedName = suggestNameFromPattern(pattern, blockIndex);
|
|
416
|
+
if (suggestedName.startsWith("Block")) blockIndex += 1;
|
|
417
|
+
suggestions.push({
|
|
418
|
+
suggestedName,
|
|
419
|
+
occurrences: count,
|
|
420
|
+
foundIn: [...files].sort(),
|
|
421
|
+
pattern,
|
|
422
|
+
snippet,
|
|
423
|
+
reason: `Same className cluster appears ${count} times`,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return suggestions;
|
|
427
|
+
}
|
|
428
|
+
|
|
354
429
|
function extractTailwindTokens(content) {
|
|
355
430
|
const tokens = new Set();
|
|
356
431
|
const patterns = [
|
|
@@ -646,9 +721,27 @@ function scan() {
|
|
|
646
721
|
const tokens = extractTailwindTokens(content);
|
|
647
722
|
results.push({ file: rel, name, group, category, description, tokens });
|
|
648
723
|
}
|
|
724
|
+
if (fs.existsSync(PAGES_DIR)) {
|
|
725
|
+
const pageFiles = getAllComponentFiles(PAGES_DIR);
|
|
726
|
+
for (const rel of pageFiles) {
|
|
727
|
+
const fullPath = path.join(PAGES_DIR, rel);
|
|
728
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
729
|
+
const name = humanizeName(rel);
|
|
730
|
+
const tokens = extractTailwindTokens(content);
|
|
731
|
+
results.push({
|
|
732
|
+
file: "pages/" + rel,
|
|
733
|
+
name,
|
|
734
|
+
group: "Sections",
|
|
735
|
+
category: "Pages",
|
|
736
|
+
description: "",
|
|
737
|
+
tokens,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
649
741
|
const foundations = extractFoundations();
|
|
650
742
|
foundations.icons = extractLucideIconsUsed(SRC_DIR);
|
|
651
743
|
foundations.brand = { assets: extractBrandAssets() };
|
|
744
|
+
const componentSuggestions = extractComponentSuggestions();
|
|
652
745
|
const output = {
|
|
653
746
|
branch: getGitBranch(),
|
|
654
747
|
engineer: getGitEngineer(),
|
|
@@ -656,6 +749,7 @@ function scan() {
|
|
|
656
749
|
totalComponents: results.length,
|
|
657
750
|
components: results,
|
|
658
751
|
foundations,
|
|
752
|
+
componentSuggestions,
|
|
659
753
|
};
|
|
660
754
|
|
|
661
755
|
const prevComponents = readPreviousOutput();
|
|
@@ -769,6 +769,50 @@ function writeFoundationsStories(foundations) {
|
|
|
769
769
|
console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "Brand.stories.tsx")));
|
|
770
770
|
}
|
|
771
771
|
|
|
772
|
+
function writeComponentSuggestionsStory(componentSuggestions) {
|
|
773
|
+
if (!Array.isArray(componentSuggestions) || componentSuggestions.length === 0) return;
|
|
774
|
+
const foundationsDir = path.join(STORIES_DIR, "foundations");
|
|
775
|
+
ensureDir(foundationsDir);
|
|
776
|
+
const suggestions = componentSuggestions.map((s) => ({
|
|
777
|
+
suggestedName: s.suggestedName,
|
|
778
|
+
occurrences: s.occurrences,
|
|
779
|
+
foundIn: s.foundIn,
|
|
780
|
+
pattern: s.pattern,
|
|
781
|
+
snippet: s.snippet,
|
|
782
|
+
reason: s.reason,
|
|
783
|
+
}));
|
|
784
|
+
const content =
|
|
785
|
+
[
|
|
786
|
+
"import type { Meta, StoryObj } from \"@storybook/react\";",
|
|
787
|
+
"",
|
|
788
|
+
"const meta = { title: \"Foundations/Component Suggestions\" } satisfies Meta;",
|
|
789
|
+
"export default meta;",
|
|
790
|
+
"type Story = StoryObj;",
|
|
791
|
+
"",
|
|
792
|
+
`const suggestions = ${JSON.stringify(suggestions)};`,
|
|
793
|
+
"",
|
|
794
|
+
"export const Default: Story = {",
|
|
795
|
+
" render: () => (",
|
|
796
|
+
" <div style={{ padding: 24, fontFamily: \"system-ui, sans-serif\" }}>",
|
|
797
|
+
" <h2 style={{ marginBottom: 16 }}>Repeated className patterns (component candidates)</h2>",
|
|
798
|
+
" <p style={{ color: \"#888\", marginBottom: 24 }}>These patterns appear 2+ times across src/pages. Consider extracting as components.</p>",
|
|
799
|
+
" <div style={{ display: \"flex\", flexDirection: \"column\", gap: 24 }}>",
|
|
800
|
+
" {suggestions.map((s, i) => (",
|
|
801
|
+
" <div key={i} style={{ border: \"1px solid #333\", borderRadius: 8, padding: 16, background: \"#111\" }}>",
|
|
802
|
+
" <div style={{ fontWeight: 600, marginBottom: 8 }}>{s.suggestedName}</div>",
|
|
803
|
+
" <div style={{ fontSize: 12, color: \"#888\", marginBottom: 8 }}>{s.reason} · {s.foundIn.join(\", \")}</div>",
|
|
804
|
+
" <pre style={{ margin: 0, padding: 12, background: \"#000\", borderRadius: 4, overflow: \"auto\", fontSize: 12 }}><code>{s.snippet}</code></pre>",
|
|
805
|
+
" </div>",
|
|
806
|
+
" ))}",
|
|
807
|
+
" </div>",
|
|
808
|
+
" </div>",
|
|
809
|
+
" ),",
|
|
810
|
+
"};",
|
|
811
|
+
].join("\n");
|
|
812
|
+
fs.writeFileSync(path.join(foundationsDir, "ComponentSuggestions.stories.tsx"), content, "utf-8");
|
|
813
|
+
console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "ComponentSuggestions.stories.tsx")));
|
|
814
|
+
}
|
|
815
|
+
|
|
772
816
|
function main() {
|
|
773
817
|
if (!fs.existsSync(VDS_OUTPUT)) {
|
|
774
818
|
console.error("[VDS] vds-output.json not found. Run `npm run vds` first.");
|
|
@@ -784,6 +828,10 @@ function main() {
|
|
|
784
828
|
ensureDir(STORIES_DIR);
|
|
785
829
|
ensureDir(path.join(STORIES_DIR, "foundations"));
|
|
786
830
|
writeFoundationsStories(foundations);
|
|
831
|
+
const componentSuggestions = data.componentSuggestions;
|
|
832
|
+
if (componentSuggestions?.length) {
|
|
833
|
+
writeComponentSuggestionsStory(componentSuggestions);
|
|
834
|
+
}
|
|
787
835
|
try {
|
|
788
836
|
const fd = path.join(STORIES_DIR, "foundations");
|
|
789
837
|
if (fs.existsSync(fd)) {
|