vibe-design-system 2.5.18 → 2.5.21
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
|
@@ -538,6 +538,19 @@ function extractComponentSuggestions() {
|
|
|
538
538
|
return suggestions;
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
+
/** src/pages/ içinde tanımlı ama src/components'a çıkarılmamış visual section'ları listele (component adayı raporu). */
|
|
542
|
+
function extractUnreleasedSectionCandidates() {
|
|
543
|
+
if (!fs.existsSync(PAGES_DIR)) return [];
|
|
544
|
+
const suggestions = extractComponentSuggestions();
|
|
545
|
+
return suggestions.map((s) => ({
|
|
546
|
+
suggestedName: s.suggestedName,
|
|
547
|
+
file: Array.isArray(s.foundIn) && s.foundIn[0] ? s.foundIn[0] : null,
|
|
548
|
+
files: s.foundIn || [],
|
|
549
|
+
occurrences: s.occurrences || 0,
|
|
550
|
+
snippet: s.snippet,
|
|
551
|
+
}));
|
|
552
|
+
}
|
|
553
|
+
|
|
541
554
|
const VDS_GENERATED_DIR = path.join(COMPONENTS_DIR, "vds-generated");
|
|
542
555
|
|
|
543
556
|
/** Ensure component name is valid JS: no leading digits/special chars, PascalCase. */
|
|
@@ -1145,6 +1158,7 @@ function scan() {
|
|
|
1145
1158
|
foundations.icons = extractLucideIconsUsed(SRC_DIR);
|
|
1146
1159
|
foundations.brand = { assets: extractBrandAssets() };
|
|
1147
1160
|
const componentSuggestions = extractComponentSuggestions();
|
|
1161
|
+
const unreleasedSectionCandidates = extractUnreleasedSectionCandidates();
|
|
1148
1162
|
const output = {
|
|
1149
1163
|
branch: getGitBranch(),
|
|
1150
1164
|
engineer: getGitEngineer(),
|
|
@@ -1153,6 +1167,7 @@ function scan() {
|
|
|
1153
1167
|
components: results,
|
|
1154
1168
|
foundations,
|
|
1155
1169
|
componentSuggestions,
|
|
1170
|
+
unreleasedSectionCandidates,
|
|
1156
1171
|
};
|
|
1157
1172
|
|
|
1158
1173
|
const prevOutput = readPreviousOutputFull();
|
|
@@ -445,7 +445,7 @@ function parsePropsFromSource(source) {
|
|
|
445
445
|
/** Default icon for LucideIcon prop when name is generic (icon, leftIcon, etc.). */
|
|
446
446
|
const LUCIDE_ICON_DEFAULT = "Star";
|
|
447
447
|
|
|
448
|
-
/** Yaygın required string prop isimleri için
|
|
448
|
+
/** Yaygın required string prop isimleri için fallback (sayfada kullanım bulunamazsa). */
|
|
449
449
|
const DEFAULT_STRINGS_BY_PROP_NAME = {
|
|
450
450
|
eyebrow: "Label",
|
|
451
451
|
title: "Section title",
|
|
@@ -456,32 +456,186 @@ const DEFAULT_STRINGS_BY_PROP_NAME = {
|
|
|
456
456
|
subtitle: "Subtitle text",
|
|
457
457
|
text: "Sample text",
|
|
458
458
|
label: "Label",
|
|
459
|
+
status: "Active",
|
|
460
|
+
sector: "Technology",
|
|
461
|
+
step: "01",
|
|
462
|
+
role: "Team Member",
|
|
459
463
|
};
|
|
460
464
|
|
|
461
|
-
/**
|
|
462
|
-
|
|
465
|
+
/** Component adına göre contextual placeholder (kullanım yoksa). */
|
|
466
|
+
const COMPONENT_PLACEHOLDERS = {
|
|
467
|
+
ProcessStep: { step: "01", title: "Discovery Phase", description: "We analyze your current setup and requirements." },
|
|
468
|
+
StatBlock: { title: "Metric", value: "100%", description: "Key performance indicator." },
|
|
469
|
+
BenefitCard: { title: "Benefit", description: "Why this matters for you.", value: "Value proposition." },
|
|
470
|
+
TeamCard: { name: "Jane Doe", role: "Team Member", quote: "Quote text here." },
|
|
471
|
+
ManifestoSection: { title: "Our mission", description: "What we believe in." },
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
/** ReactNode prop adına göre kısa placeholder metin (createElement içinde). */
|
|
475
|
+
const REACTNODE_PLACEHOLDER_TEXT = {
|
|
476
|
+
quote: "Quote text here.",
|
|
477
|
+
title: "Title text",
|
|
478
|
+
children: "Example content",
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
/** Recursive list of .tsx file paths under dir (relative to dir). */
|
|
482
|
+
function getAllTsxUnderDir(dir) {
|
|
483
|
+
if (!fs.existsSync(dir)) return [];
|
|
484
|
+
const out = [];
|
|
485
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
486
|
+
for (const e of entries) {
|
|
487
|
+
const full = path.join(dir, e.name);
|
|
488
|
+
if (e.isDirectory()) {
|
|
489
|
+
out.push(...getAllTsxUnderDir(full).map((r) => path.join(e.name, r)));
|
|
490
|
+
} else if (e.isFile() && e.name.endsWith(".tsx")) {
|
|
491
|
+
out.push(e.name);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return out;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** Extract prop="value" and prop='value' and prop={"value"} from JSX tag content. Returns { propName: "value", ... }. */
|
|
498
|
+
function extractPropsFromJsxTagContent(tagContent) {
|
|
499
|
+
const props = {};
|
|
500
|
+
let m;
|
|
501
|
+
const doubleQuoted = /\b(\w+)=["]([^"]*)["]/g;
|
|
502
|
+
while ((m = doubleQuoted.exec(tagContent)) !== null) props[m[1]] = m[2];
|
|
503
|
+
const singleQuoted = /\b(\w+)=[']([^']*)[']/g;
|
|
504
|
+
while ((m = singleQuoted.exec(tagContent)) !== null) if (props[m[1]] === undefined) props[m[1]] = m[2];
|
|
505
|
+
const jsxDouble = /\b(\w+)=\{"([^"]*)"\}/g;
|
|
506
|
+
while ((m = jsxDouble.exec(tagContent)) !== null) if (props[m[1]] === undefined) props[m[1]] = m[2];
|
|
507
|
+
const jsxSingle = /\b(\w+)=\{'([^']*)'\}/g;
|
|
508
|
+
while ((m = jsxSingle.exec(tagContent)) !== null) if (props[m[1]] === undefined) props[m[1]] = m[2];
|
|
509
|
+
return props;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/** Parse source for interface ItemTypeName { field: type; ... } and return list of field names. */
|
|
513
|
+
function getItemTypeFieldsFromSource(source, itemTypeName) {
|
|
514
|
+
if (!source || !itemTypeName) return [];
|
|
515
|
+
const escaped = itemTypeName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
516
|
+
let body = null;
|
|
517
|
+
const typeRe = new RegExp(`(?:type|interface)\\s+${escaped}\\s*=\\s*\\{([^}]+)\\}`, "m");
|
|
518
|
+
const ifaceRe = new RegExp(`interface\\s+${escaped}\\s*\\{([^}]+)\\}`, "m");
|
|
519
|
+
const m1 = source.match(typeRe);
|
|
520
|
+
const m2 = source.match(ifaceRe);
|
|
521
|
+
if (m1) body = m1[1];
|
|
522
|
+
else if (m2) body = m2[1];
|
|
523
|
+
if (!body) return [];
|
|
524
|
+
const fieldRe = /(\w+)\s*[:?]/g;
|
|
525
|
+
const fields = [];
|
|
526
|
+
let m;
|
|
527
|
+
while ((m = fieldRe.exec(body)) !== null) fields.push(m[1]);
|
|
528
|
+
return fields;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Build 2-3 mock items for array prop from component source (item type fields). */
|
|
532
|
+
function buildMockArrayItems(componentSource, itemTypeName, propName) {
|
|
533
|
+
const fields = getItemTypeFieldsFromSource(componentSource, itemTypeName);
|
|
534
|
+
const defaultFields = ["label", "value", "title", "id", "name", "description"];
|
|
535
|
+
const useFields = fields.length ? fields : defaultFields;
|
|
536
|
+
const items = [];
|
|
537
|
+
for (let i = 1; i <= 3; i++) {
|
|
538
|
+
const item = {};
|
|
539
|
+
for (const f of useFields) {
|
|
540
|
+
if (f === "id") item[f] = String(i);
|
|
541
|
+
else if (f === "value") item[f] = String(100 - i * 25);
|
|
542
|
+
else item[f] = `Example ${i}`;
|
|
543
|
+
}
|
|
544
|
+
items.push(item);
|
|
545
|
+
}
|
|
546
|
+
return items;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/** Find first real usage of component in src/pages or src/app; return { propName: "literalValue", ... } or null. */
|
|
550
|
+
function findComponentUsageInPages(componentName, projectRoot) {
|
|
551
|
+
const srcDir = path.join(projectRoot, "src");
|
|
552
|
+
const pagesDir = path.join(srcDir, "pages");
|
|
553
|
+
const appDir = path.join(srcDir, "app");
|
|
554
|
+
const dirs = [];
|
|
555
|
+
if (fs.existsSync(pagesDir)) dirs.push(pagesDir);
|
|
556
|
+
if (fs.existsSync(appDir)) dirs.push(appDir);
|
|
557
|
+
const escapedName = componentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
558
|
+
const tagOpenRe = new RegExp(`<${escapedName}\\s+([\\s\\S]*?)\\s*\\/?>`, "m");
|
|
559
|
+
for (const dir of dirs) {
|
|
560
|
+
const files = getAllTsxUnderDir(dir);
|
|
561
|
+
for (const rel of files) {
|
|
562
|
+
const fullPath = path.join(dir, rel);
|
|
563
|
+
try {
|
|
564
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
565
|
+
const match = content.match(tagOpenRe);
|
|
566
|
+
if (match) {
|
|
567
|
+
const props = extractPropsFromJsxTagContent(match[1]);
|
|
568
|
+
if (Object.keys(props).length > 0) return props;
|
|
569
|
+
}
|
|
570
|
+
} catch (_) {}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** Build default args lines, lucide imports, argTypes for icon (no raw dump), and needReact for ReactNode. */
|
|
577
|
+
function buildDefaultArgsForRequiredProps(props, usageFromPages = null, componentName = "", componentSource = "") {
|
|
463
578
|
const argLines = [];
|
|
464
579
|
const lucideImports = new Set();
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
580
|
+
const iconPropNames = [];
|
|
581
|
+
let needReact = false;
|
|
582
|
+
const fromPages = usageFromPages && typeof usageFromPages === "object" ? usageFromPages : {};
|
|
583
|
+
const componentPlaceholders = (componentName && COMPONENT_PLACEHOLDERS[componentName]) || {};
|
|
584
|
+
const added = new Set();
|
|
585
|
+
|
|
586
|
+
const stringFallback = (name) => {
|
|
587
|
+
if (fromPages[name] !== undefined && fromPages[name] !== null) return JSON.stringify(String(fromPages[name]));
|
|
588
|
+
if (componentPlaceholders[name] !== undefined) return JSON.stringify(componentPlaceholders[name]);
|
|
589
|
+
if (DEFAULT_STRINGS_BY_PROP_NAME[name] !== undefined) return JSON.stringify(DEFAULT_STRINGS_BY_PROP_NAME[name]);
|
|
590
|
+
return JSON.stringify("Example");
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
if (Array.isArray(props) && props.length > 0) {
|
|
594
|
+
for (const p of props) {
|
|
595
|
+
const type = String(p.type || "").trim();
|
|
596
|
+
const name = p.name;
|
|
597
|
+
const required = p.required === true;
|
|
598
|
+
if (/ReactNode|React\.ReactNode/.test(type)) {
|
|
599
|
+
const text = REACTNODE_PLACEHOLDER_TEXT[name] || componentPlaceholders[name] || "Content";
|
|
600
|
+
argLines.push(` ${name}: React.createElement('p', null, ${JSON.stringify(text)}),`);
|
|
601
|
+
needReact = true;
|
|
602
|
+
added.add(name);
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
if (!required) continue;
|
|
606
|
+
if (/LucideIcon|lucide-react/.test(type)) {
|
|
607
|
+
const iconName = LUCIDE_ICON_DEFAULT;
|
|
608
|
+
lucideImports.add(iconName);
|
|
609
|
+
argLines.push(` ${name}: ${iconName},`);
|
|
610
|
+
iconPropNames.push(name);
|
|
611
|
+
added.add(name);
|
|
612
|
+
} else if (/\[\]/.test(type)) {
|
|
613
|
+
if (/string\s*\[\]/.test(type)) {
|
|
614
|
+
argLines.push(` ${name}: ["Example"],`);
|
|
615
|
+
} else {
|
|
616
|
+
const itemTypeMatch = type.match(/([A-Za-z0-9_]+)(?=\s*\[\])/);
|
|
617
|
+
const itemType = itemTypeMatch ? itemTypeMatch[1] : "Item";
|
|
618
|
+
const mockItems = buildMockArrayItems(componentSource, itemType, name);
|
|
619
|
+
argLines.push(` ${name}: ${JSON.stringify(mockItems)},`);
|
|
620
|
+
}
|
|
621
|
+
added.add(name);
|
|
622
|
+
} else if (fromPages[name] !== undefined && fromPages[name] !== null) {
|
|
623
|
+
argLines.push(` ${name}: ${JSON.stringify(String(fromPages[name]))},`);
|
|
624
|
+
added.add(name);
|
|
625
|
+
} else if (/string/.test(type)) {
|
|
626
|
+
argLines.push(` ${name}: ${stringFallback(name)},`);
|
|
627
|
+
added.add(name);
|
|
479
628
|
}
|
|
480
|
-
} else if (/string/.test(type) && DEFAULT_STRINGS_BY_PROP_NAME[name] !== undefined) {
|
|
481
|
-
argLines.push(` ${name}: ${JSON.stringify(DEFAULT_STRINGS_BY_PROP_NAME[name])},`);
|
|
482
629
|
}
|
|
483
630
|
}
|
|
484
|
-
|
|
631
|
+
for (const name of Object.keys(fromPages)) {
|
|
632
|
+
if (added.has(name)) continue;
|
|
633
|
+
const val = fromPages[name];
|
|
634
|
+
if (val !== undefined && val !== null && typeof val === "string") {
|
|
635
|
+
argLines.push(` ${name}: ${JSON.stringify(val)},`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return { argLines, lucideImports: [...lucideImports], iconPropNames, needReact };
|
|
485
639
|
}
|
|
486
640
|
|
|
487
641
|
function getExampleItemForArrayType(itemType) {
|
|
@@ -600,9 +754,12 @@ function buildSpecialStories(componentName, variants) {
|
|
|
600
754
|
return "";
|
|
601
755
|
}
|
|
602
756
|
|
|
603
|
-
function buildRecipeStoryContent(comp, componentName, importPath, title, source, exportStyle, recipe, defaultArgLines = [], lucideImports = []) {
|
|
757
|
+
function buildRecipeStoryContent(comp, componentName, importPath, title, source, exportStyle, recipe, defaultArgLines = [], lucideImports = [], iconPropNames = [], needReact = false) {
|
|
604
758
|
const lines = [];
|
|
605
759
|
lines.push(`import type { Meta, StoryObj } from "@storybook/react";`);
|
|
760
|
+
if (needReact) {
|
|
761
|
+
lines.push(`import React from "react";`);
|
|
762
|
+
}
|
|
606
763
|
if (lucideImports.length > 0) {
|
|
607
764
|
lines.push(`import { ${lucideImports.join(", ")} } from "lucide-react";`);
|
|
608
765
|
}
|
|
@@ -647,6 +804,13 @@ function buildRecipeStoryContent(comp, componentName, importPath, title, source,
|
|
|
647
804
|
lines.push(` title: ${JSON.stringify(title)},`);
|
|
648
805
|
lines.push(` component: ComponentRef,`);
|
|
649
806
|
lines.push(` tags: ["autodocs"],`);
|
|
807
|
+
if (iconPropNames && iconPropNames.length > 0) {
|
|
808
|
+
lines.push(` argTypes: {`);
|
|
809
|
+
for (const name of iconPropNames) {
|
|
810
|
+
lines.push(` ${name}: { control: false },`);
|
|
811
|
+
}
|
|
812
|
+
lines.push(` },`);
|
|
813
|
+
}
|
|
650
814
|
lines.push(`} satisfies Meta<typeof ComponentRef>;`);
|
|
651
815
|
lines.push("");
|
|
652
816
|
lines.push(`export default meta;`);
|
|
@@ -721,9 +885,10 @@ function buildStoryFileContent(comp) {
|
|
|
721
885
|
const omitChildren = componentWrapsVoidElement(source);
|
|
722
886
|
const isPage = comp.file.startsWith("pages/");
|
|
723
887
|
|
|
724
|
-
// Props: manifest or parse from source for default args (LucideIcon, array types)
|
|
888
|
+
// Props: manifest or parse from source for default args (LucideIcon, array types, ReactNode, contextual placeholders)
|
|
725
889
|
const effectiveProps = Array.isArray(comp.props) && comp.props.length > 0 ? comp.props : parsePropsFromSource(source);
|
|
726
|
-
const
|
|
890
|
+
const usageFromPages = findComponentUsageInPages(componentName, PROJECT_ROOT);
|
|
891
|
+
const { argLines: defaultArgLines, lucideImports, iconPropNames, needReact } = buildDefaultArgsForRequiredProps(effectiveProps, usageFromPages, componentName, source);
|
|
727
892
|
const useReactNodeChildrenRender = !omitChildren && hasChildrenPropReactNode(effectiveProps);
|
|
728
893
|
|
|
729
894
|
// Skip story only if not a page and no export found
|
|
@@ -732,12 +897,12 @@ function buildStoryFileContent(comp) {
|
|
|
732
897
|
}
|
|
733
898
|
|
|
734
899
|
if (RECIPES[componentName]) {
|
|
735
|
-
return buildRecipeStoryContent(comp, componentName, importPath, title, source, exportStyle, RECIPES[componentName], defaultArgLines, lucideImports);
|
|
900
|
+
return buildRecipeStoryContent(comp, componentName, importPath, title, source, exportStyle, RECIPES[componentName], defaultArgLines, lucideImports, iconPropNames, needReact);
|
|
736
901
|
}
|
|
737
902
|
|
|
738
903
|
const lines = [];
|
|
739
904
|
lines.push(`import type { Meta, StoryObj } from "@storybook/react";`);
|
|
740
|
-
if (useReactNodeChildrenRender) {
|
|
905
|
+
if (useReactNodeChildrenRender || needReact) {
|
|
741
906
|
lines.push(`import React from "react";`);
|
|
742
907
|
}
|
|
743
908
|
if (lucideImports.length > 0) {
|
|
@@ -767,6 +932,13 @@ function buildStoryFileContent(comp) {
|
|
|
767
932
|
lines.push(` title: ${JSON.stringify(title)},`);
|
|
768
933
|
lines.push(` component: ComponentRef,`);
|
|
769
934
|
lines.push(` tags: ["autodocs"],`);
|
|
935
|
+
if (iconPropNames && iconPropNames.length > 0) {
|
|
936
|
+
lines.push(` argTypes: {`);
|
|
937
|
+
for (const name of iconPropNames) {
|
|
938
|
+
lines.push(` ${name}: { control: false },`);
|
|
939
|
+
}
|
|
940
|
+
lines.push(` },`);
|
|
941
|
+
}
|
|
770
942
|
lines.push(`} satisfies Meta<typeof ComponentRef>;`);
|
|
771
943
|
lines.push("");
|
|
772
944
|
lines.push(`export default meta;`);
|