vibe-design-system 2.8.47 → 2.8.49
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
|
@@ -74,7 +74,7 @@ const DEFAULT_SKIP_LIST = [
|
|
|
74
74
|
"Form",
|
|
75
75
|
"Toaster",
|
|
76
76
|
"Toast",
|
|
77
|
-
|
|
77
|
+
// Button removed — now handled by multi-dimension detection (variant × size × disabled)
|
|
78
78
|
];
|
|
79
79
|
|
|
80
80
|
// Merge default with project-specific overrides from vds.config.js
|
|
@@ -441,6 +441,305 @@ function parseUnionLiterals(type) {
|
|
|
441
441
|
return matches.map((s) => s.replace(/"/g, ""));
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
// ─── Multi-Dimension State Detection ─────────────────────────────────────────
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Parse ALL CVA variant dimensions from component source (not just "variant").
|
|
448
|
+
* Returns: { variant: ["default", "secondary", ...], size: ["sm", "md", "lg"], ... }
|
|
449
|
+
*/
|
|
450
|
+
function parseCvaAllDimensions(source) {
|
|
451
|
+
if (!source) return {};
|
|
452
|
+
// Find "variants:" or "variants :" block
|
|
453
|
+
const variantsIdx = source.search(/variants\s*:\s*\{/);
|
|
454
|
+
if (variantsIdx === -1) return {};
|
|
455
|
+
|
|
456
|
+
const braceStart = source.indexOf("{", variantsIdx + 8);
|
|
457
|
+
if (braceStart === -1) return {};
|
|
458
|
+
|
|
459
|
+
// Brace-count to find matching close brace
|
|
460
|
+
let depth = 0, braceEnd = -1;
|
|
461
|
+
for (let i = braceStart; i < source.length; i++) {
|
|
462
|
+
if (source[i] === "{") depth++;
|
|
463
|
+
else if (source[i] === "}") { depth--; if (depth === 0) { braceEnd = i; break; } }
|
|
464
|
+
}
|
|
465
|
+
if (braceEnd === -1) return {};
|
|
466
|
+
|
|
467
|
+
const variantsBody = source.slice(braceStart + 1, braceEnd);
|
|
468
|
+
|
|
469
|
+
// Extract each dimension key and its sub-block keys
|
|
470
|
+
const result = {};
|
|
471
|
+
const dimRe = /(\w+)\s*:\s*\{/g;
|
|
472
|
+
let dm;
|
|
473
|
+
while ((dm = dimRe.exec(variantsBody)) !== null) {
|
|
474
|
+
const dimName = dm[1];
|
|
475
|
+
const subStart = dm.index + dm[0].length - 1;
|
|
476
|
+
// Brace-count for this dimension's sub-block
|
|
477
|
+
let d = 0, subEnd = -1;
|
|
478
|
+
for (let i = subStart; i < variantsBody.length; i++) {
|
|
479
|
+
if (variantsBody[i] === "{") d++;
|
|
480
|
+
else if (variantsBody[i] === "}") { d--; if (d === 0) { subEnd = i; break; } }
|
|
481
|
+
}
|
|
482
|
+
if (subEnd === -1) continue;
|
|
483
|
+
const subBody = variantsBody.slice(subStart + 1, subEnd);
|
|
484
|
+
const keys = [];
|
|
485
|
+
const keyRe = /^\s*([A-Za-z_][\w]*)\s*:/gm;
|
|
486
|
+
let km;
|
|
487
|
+
while ((km = keyRe.exec(subBody)) !== null) {
|
|
488
|
+
keys.push(km[1]);
|
|
489
|
+
}
|
|
490
|
+
if (keys.length > 0) result[dimName] = keys;
|
|
491
|
+
}
|
|
492
|
+
return result;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Parse CVA defaultVariants block.
|
|
497
|
+
* Returns: { variant: "default", size: "default" }
|
|
498
|
+
*/
|
|
499
|
+
function parseCvaDefaultVariants(source) {
|
|
500
|
+
if (!source) return {};
|
|
501
|
+
const idx = source.search(/defaultVariants\s*:\s*\{/);
|
|
502
|
+
if (idx === -1) return {};
|
|
503
|
+
const braceStart = source.indexOf("{", idx + 15);
|
|
504
|
+
if (braceStart === -1) return {};
|
|
505
|
+
let depth = 0, braceEnd = -1;
|
|
506
|
+
for (let i = braceStart; i < source.length; i++) {
|
|
507
|
+
if (source[i] === "{") depth++;
|
|
508
|
+
else if (source[i] === "}") { depth--; if (depth === 0) { braceEnd = i; break; } }
|
|
509
|
+
}
|
|
510
|
+
if (braceEnd === -1) return {};
|
|
511
|
+
const body = source.slice(braceStart + 1, braceEnd);
|
|
512
|
+
const result = {};
|
|
513
|
+
const re = /(\w+)\s*:\s*["']([^"']+)["']/g;
|
|
514
|
+
let m;
|
|
515
|
+
while ((m = re.exec(body)) !== null) {
|
|
516
|
+
result[m[1]] = m[2];
|
|
517
|
+
}
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Known boolean prop names that represent visual states worth showing in Storybook. */
|
|
522
|
+
const BOOLEAN_STATE_PROP_NAMES = new Set([
|
|
523
|
+
"disabled", "loading", "isLoading", "active", "isActive",
|
|
524
|
+
"checked", "isChecked", "selected", "isSelected",
|
|
525
|
+
"error", "isError", "readOnly", "isReadOnly",
|
|
526
|
+
]);
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Detect boolean state props from effectiveProps.
|
|
530
|
+
* Returns: ["disabled", "loading"]
|
|
531
|
+
*/
|
|
532
|
+
function detectBooleanStateProps(effectiveProps) {
|
|
533
|
+
if (!Array.isArray(effectiveProps)) return [];
|
|
534
|
+
return effectiveProps
|
|
535
|
+
.filter((p) => {
|
|
536
|
+
const type = (p.type || "").trim();
|
|
537
|
+
const isBool = /^boolean/.test(type) || type === "boolean | undefined" || type === "boolean | null";
|
|
538
|
+
return isBool && BOOLEAN_STATE_PROP_NAMES.has(p.name);
|
|
539
|
+
})
|
|
540
|
+
.map((p) => p.name);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Detect all string union/enum props from effectiveProps (e.g., color: "red" | "blue").
|
|
545
|
+
* Returns: [{ name: "color", values: ["red", "blue", "green"] }]
|
|
546
|
+
*/
|
|
547
|
+
function detectUnionEnumProps(effectiveProps) {
|
|
548
|
+
if (!Array.isArray(effectiveProps)) return [];
|
|
549
|
+
const results = [];
|
|
550
|
+
for (const p of effectiveProps) {
|
|
551
|
+
const type = (p.type || "").trim();
|
|
552
|
+
if (/^['"][^'"]+['"](\s*\|\s*['"][^'"]+['"])+/.test(type)) {
|
|
553
|
+
const values = parseUnionLiterals(type);
|
|
554
|
+
if (values.length >= 2) {
|
|
555
|
+
results.push({ name: p.name, values });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return results;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** Priority order for determining which dimension is "primary" (gets individual stories). */
|
|
563
|
+
const DIMENSION_PRIORITY = ["variant", "type", "status", "color", "kind", "mode", "side"];
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Build a complete variant map from source + effectiveProps.
|
|
567
|
+
* Combines CVA dimensions, TypeScript union props, and boolean states.
|
|
568
|
+
*/
|
|
569
|
+
function buildComponentVariantMap(source, effectiveProps) {
|
|
570
|
+
const map = { dimensions: {}, booleanStates: [], primaryDimension: null, secondaryDimensions: [] };
|
|
571
|
+
|
|
572
|
+
// 1. Parse CVA dimensions from source
|
|
573
|
+
const cvaDims = parseCvaAllDimensions(source);
|
|
574
|
+
const cvaDefaults = parseCvaDefaultVariants(source);
|
|
575
|
+
for (const [dimName, values] of Object.entries(cvaDims)) {
|
|
576
|
+
map.dimensions[dimName] = { values, default: cvaDefaults[dimName] || values[0], source: "cva" };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// 2. Parse union enum props from TypeScript types (only if not already from CVA)
|
|
580
|
+
const unionProps = detectUnionEnumProps(effectiveProps);
|
|
581
|
+
for (const { name, values } of unionProps) {
|
|
582
|
+
if (!map.dimensions[name]) {
|
|
583
|
+
map.dimensions[name] = { values, default: values[0], source: "typescript" };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// 3. Detect boolean state props
|
|
588
|
+
map.booleanStates = detectBooleanStateProps(effectiveProps);
|
|
589
|
+
|
|
590
|
+
// 4. Determine primary and secondary dimensions
|
|
591
|
+
const dimNames = Object.keys(map.dimensions);
|
|
592
|
+
for (const candidate of DIMENSION_PRIORITY) {
|
|
593
|
+
if (dimNames.includes(candidate)) {
|
|
594
|
+
map.primaryDimension = candidate;
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (!map.primaryDimension && dimNames.length > 0) {
|
|
599
|
+
map.primaryDimension = dimNames[0];
|
|
600
|
+
}
|
|
601
|
+
map.secondaryDimensions = dimNames.filter((d) => d !== map.primaryDimension);
|
|
602
|
+
|
|
603
|
+
return map;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Generate multi-dimension stories: individual per variant, per size, per state, + AllVariants grid.
|
|
608
|
+
*/
|
|
609
|
+
function buildMultiDimensionStories(variantMap, renderLine, childrenArgLine, defaultArgLines, componentName) {
|
|
610
|
+
const lines = [];
|
|
611
|
+
const { dimensions, booleanStates, primaryDimension, secondaryDimensions } = variantMap;
|
|
612
|
+
|
|
613
|
+
// Filter defaultArgLines to exclude dimension props (they are set explicitly per story)
|
|
614
|
+
const allDimNames = new Set(Object.keys(dimensions));
|
|
615
|
+
const filteredArgLines = defaultArgLines.filter((line) => {
|
|
616
|
+
const match = line.match(/^\s+(\w+)\s*:/);
|
|
617
|
+
return !match || !allDimNames.has(match[1]);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// --- A. Primary dimension stories (one per value) ---
|
|
621
|
+
if (primaryDimension && dimensions[primaryDimension]) {
|
|
622
|
+
const dim = dimensions[primaryDimension];
|
|
623
|
+
const defaultValue = dim.default || dim.values[0];
|
|
624
|
+
|
|
625
|
+
// Default story uses the default value
|
|
626
|
+
lines.push(`export const Default: Story = {`);
|
|
627
|
+
lines.push(renderLine);
|
|
628
|
+
lines.push(` args: {`);
|
|
629
|
+
lines.push(` ${primaryDimension}: "${defaultValue}",`);
|
|
630
|
+
if (childrenArgLine(componentName)) lines.push(childrenArgLine(componentName));
|
|
631
|
+
for (const line of filteredArgLines) lines.push(line);
|
|
632
|
+
lines.push(` },`);
|
|
633
|
+
lines.push(`};`);
|
|
634
|
+
lines.push("");
|
|
635
|
+
|
|
636
|
+
// Remaining values get their own stories
|
|
637
|
+
for (const val of dim.values) {
|
|
638
|
+
if (val === defaultValue) continue;
|
|
639
|
+
const storyName = capitalize(val);
|
|
640
|
+
lines.push(`export const ${storyName}: Story = {`);
|
|
641
|
+
lines.push(renderLine);
|
|
642
|
+
lines.push(` args: {`);
|
|
643
|
+
lines.push(` ${primaryDimension}: "${val}",`);
|
|
644
|
+
if (childrenArgLine(capitalize(val))) lines.push(childrenArgLine(capitalize(val)));
|
|
645
|
+
for (const line of filteredArgLines) lines.push(line);
|
|
646
|
+
lines.push(` },`);
|
|
647
|
+
lines.push(`};`);
|
|
648
|
+
lines.push("");
|
|
649
|
+
}
|
|
650
|
+
} else {
|
|
651
|
+
// No primary dimension — single Default story
|
|
652
|
+
lines.push(`export const Default: Story = {`);
|
|
653
|
+
lines.push(renderLine);
|
|
654
|
+
const storyArgLines = [];
|
|
655
|
+
if (childrenArgLine(componentName)) storyArgLines.push(childrenArgLine(componentName));
|
|
656
|
+
for (const line of filteredArgLines) storyArgLines.push(line);
|
|
657
|
+
if (storyArgLines.length > 0) {
|
|
658
|
+
lines.push(` args: {`);
|
|
659
|
+
for (const line of storyArgLines) lines.push(line);
|
|
660
|
+
lines.push(` },`);
|
|
661
|
+
}
|
|
662
|
+
lines.push(`};`);
|
|
663
|
+
lines.push("");
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// --- B. Secondary dimension stories (one per non-default value) ---
|
|
667
|
+
for (const dimName of secondaryDimensions) {
|
|
668
|
+
const dim = dimensions[dimName];
|
|
669
|
+
if (!dim || dim.values.length <= 1) continue;
|
|
670
|
+
for (const val of dim.values) {
|
|
671
|
+
if (val === (dim.default || dim.values[0])) continue;
|
|
672
|
+
const storyName = capitalize(dimName) + capitalize(val);
|
|
673
|
+
lines.push(`export const ${storyName}: Story = {`);
|
|
674
|
+
lines.push(renderLine);
|
|
675
|
+
lines.push(` args: {`);
|
|
676
|
+
if (primaryDimension && dimensions[primaryDimension]) {
|
|
677
|
+
lines.push(` ${primaryDimension}: "${dimensions[primaryDimension].default || dimensions[primaryDimension].values[0]}",`);
|
|
678
|
+
}
|
|
679
|
+
lines.push(` ${dimName}: "${val}",`);
|
|
680
|
+
if (childrenArgLine(componentName)) lines.push(childrenArgLine(componentName));
|
|
681
|
+
for (const line of filteredArgLines) lines.push(line);
|
|
682
|
+
lines.push(` },`);
|
|
683
|
+
lines.push(`};`);
|
|
684
|
+
lines.push("");
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// --- C. Boolean state stories ---
|
|
689
|
+
for (const boolProp of booleanStates) {
|
|
690
|
+
if (boolProp === "open" || boolProp === "isOpen") continue;
|
|
691
|
+
const storyName = capitalize(boolProp);
|
|
692
|
+
lines.push(`export const ${storyName}: Story = {`);
|
|
693
|
+
lines.push(renderLine);
|
|
694
|
+
lines.push(` args: {`);
|
|
695
|
+
if (primaryDimension && dimensions[primaryDimension]) {
|
|
696
|
+
lines.push(` ${primaryDimension}: "${dimensions[primaryDimension].default || dimensions[primaryDimension].values[0]}",`);
|
|
697
|
+
}
|
|
698
|
+
lines.push(` ${boolProp}: true,`);
|
|
699
|
+
if (childrenArgLine(componentName)) lines.push(childrenArgLine(componentName));
|
|
700
|
+
for (const line of filteredArgLines) lines.push(line);
|
|
701
|
+
lines.push(` },`);
|
|
702
|
+
lines.push(`};`);
|
|
703
|
+
lines.push("");
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// --- D. AllVariants grid story (only if primary has 2+ values) ---
|
|
707
|
+
if (primaryDimension && dimensions[primaryDimension] && dimensions[primaryDimension].values.length >= 2) {
|
|
708
|
+
const primaryValues = dimensions[primaryDimension].values;
|
|
709
|
+
const secondaryDim = secondaryDimensions.find((d) => dimensions[d] && dimensions[d].values.length >= 2);
|
|
710
|
+
|
|
711
|
+
lines.push(`export const AllVariants: Story = {`);
|
|
712
|
+
lines.push(` render: () => (`);
|
|
713
|
+
lines.push(` <div style={{ display: "flex", flexDirection: "column", gap: 16, padding: 16 }}>`);
|
|
714
|
+
|
|
715
|
+
if (secondaryDim) {
|
|
716
|
+
const secondaryValues = dimensions[secondaryDim].values;
|
|
717
|
+
for (const pVal of primaryValues) {
|
|
718
|
+
lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }}>`);
|
|
719
|
+
lines.push(` <span style={{ width: 90, fontSize: 12, color: "#888", fontFamily: "monospace" }}>${pVal}</span>`);
|
|
720
|
+
for (const sVal of secondaryValues) {
|
|
721
|
+
lines.push(` <ComponentRef ${primaryDimension}="${pVal}" ${secondaryDim}="${sVal}">${pVal}</ComponentRef>`);
|
|
722
|
+
}
|
|
723
|
+
lines.push(` </div>`);
|
|
724
|
+
}
|
|
725
|
+
} else {
|
|
726
|
+
lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>`);
|
|
727
|
+
for (const val of primaryValues) {
|
|
728
|
+
lines.push(` <ComponentRef ${primaryDimension}="${val}">${val}</ComponentRef>`);
|
|
729
|
+
}
|
|
730
|
+
lines.push(` </div>`);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
lines.push(` </div>`);
|
|
734
|
+
lines.push(` ),`);
|
|
735
|
+
lines.push(`};`);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return lines.join("\n");
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// ─── End Multi-Dimension Detection ───────────────────────────────────────────
|
|
742
|
+
|
|
444
743
|
/** Parse props with types from component source (interface/type or inline props). Returns [{ name, type, required }]. */
|
|
445
744
|
function parsePropsFromSource(source) {
|
|
446
745
|
if (!source || typeof source !== "string") return [];
|
|
@@ -1246,8 +1545,15 @@ function buildArgTypeEntry(prop) {
|
|
|
1246
1545
|
* @param {Array<{name:string,type:string,required:boolean}>} effectiveProps
|
|
1247
1546
|
* @returns {"SAFE"|"SECTION"|"WRAPPER"|"VARIANT"|"CONFIGURED"}
|
|
1248
1547
|
*/
|
|
1249
|
-
function getStoryProfile(componentName, source, effectiveProps) {
|
|
1548
|
+
function getStoryProfile(componentName, source, effectiveProps, variantMap) {
|
|
1250
1549
|
if (componentName && SAFE_WRAPPER_DEFAULTS[componentName]) return "SAFE";
|
|
1550
|
+
// Check for ANY multi-value dimensions (variant, size, color, etc.) or boolean states
|
|
1551
|
+
// This must run BEFORE the SECTION check because CVA components (Button, Badge) may have
|
|
1552
|
+
// empty effectiveProps (types come from VariantProps<typeof ...>) but still have dimensions.
|
|
1553
|
+
if (variantMap) {
|
|
1554
|
+
const hasDimensions = Object.keys(variantMap.dimensions).length > 0;
|
|
1555
|
+
if (hasDimensions || variantMap.booleanStates.length > 0) return "VARIANT";
|
|
1556
|
+
}
|
|
1251
1557
|
if (effectiveProps.length === 0) return "SECTION";
|
|
1252
1558
|
if (hasChildrenPropReactNode(effectiveProps)) return "WRAPPER";
|
|
1253
1559
|
if (effectiveProps.some(p => p.name === "variant")) return "VARIANT";
|
|
@@ -1319,38 +1625,11 @@ function buildStoryFileContent(comp) {
|
|
|
1319
1625
|
// Title: "Module/ComponentName" (category intentionally dropped — folder is the context)
|
|
1320
1626
|
const title = `${group}/${componentName}`;
|
|
1321
1627
|
|
|
1322
|
-
const props = Array.isArray(comp.props) ? comp.props : [];
|
|
1323
|
-
const variantProp = props.find((p) => p.name === "variant");
|
|
1324
|
-
let variants = parseUnionLiterals(variantProp && variantProp.type);
|
|
1325
|
-
|
|
1326
1628
|
const srcPath = isPageFile
|
|
1327
1629
|
? path.join(PROJECT_ROOT, fileNoExt.replace(/^src\//, "src/") + ".tsx")
|
|
1328
1630
|
: path.join(PROJECT_ROOT, COMPONENTS_REL_DIR, comp.file);
|
|
1329
1631
|
|
|
1330
|
-
//
|
|
1331
|
-
if (!variants.length) {
|
|
1332
|
-
try {
|
|
1333
|
-
if (fs.existsSync(srcPath)) {
|
|
1334
|
-
const code = fs.readFileSync(srcPath, "utf-8");
|
|
1335
|
-
// Roughly match shadcn-style: variants: { variant: { ... }, size: { ... } }
|
|
1336
|
-
const m = code.match(/variant\s*:\s*{([\s\S]*?)}\s*,\s*size\s*:/);
|
|
1337
|
-
if (m) {
|
|
1338
|
-
const body = m[1];
|
|
1339
|
-
const names = [];
|
|
1340
|
-
const lineRe = /^\s*([A-Za-z0-9_]+)\s*:/gm;
|
|
1341
|
-
let lm;
|
|
1342
|
-
while ((lm = lineRe.exec(body))) {
|
|
1343
|
-
names.push(lm[1]);
|
|
1344
|
-
}
|
|
1345
|
-
if (names.length) variants = names;
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
} catch {
|
|
1349
|
-
// best-effort; ignore parsing errors
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
// Read component source to detect export style for import
|
|
1632
|
+
// Read component source once (used for export style, props, CVA parsing)
|
|
1354
1633
|
let source = "";
|
|
1355
1634
|
try {
|
|
1356
1635
|
if (fs.existsSync(srcPath)) {
|
|
@@ -1366,9 +1645,15 @@ function buildStoryFileContent(comp) {
|
|
|
1366
1645
|
const effectiveProps = Array.isArray(comp.props) && comp.props.length > 0 ? comp.props : parsePropsFromSource(source);
|
|
1367
1646
|
const usageFromPages = findComponentUsageInPages(componentName, PROJECT_ROOT);
|
|
1368
1647
|
|
|
1648
|
+
// Build complete variant map: CVA dimensions + TypeScript union props + boolean states
|
|
1649
|
+
const variantMap = buildComponentVariantMap(source, effectiveProps);
|
|
1650
|
+
|
|
1651
|
+
// Legacy compat: extract flat variants array for buildSpecialStories
|
|
1652
|
+
const variants = variantMap.dimensions.variant ? variantMap.dimensions.variant.values : [];
|
|
1653
|
+
|
|
1369
1654
|
// Profile: single source of truth — replaces omitChildren, useReactNodeChildrenRender,
|
|
1370
1655
|
// isNoPropsFeature, useSafeWrapper, argsParam, propsArg flags.
|
|
1371
|
-
const profile = getStoryProfile(componentName, source, effectiveProps);
|
|
1656
|
+
const profile = getStoryProfile(componentName, source, effectiveProps, variantMap);
|
|
1372
1657
|
const propSummary = effectiveProps.length > 0 ? ` (${effectiveProps.length} props)` : "";
|
|
1373
1658
|
console.log(`[VDS] ${componentName} → ${profile}${propSummary}`);
|
|
1374
1659
|
|
|
@@ -1439,11 +1724,15 @@ function buildStoryFileContent(comp) {
|
|
|
1439
1724
|
lines.push(`type Story = StoryObj<typeof meta>;`);
|
|
1440
1725
|
lines.push("");
|
|
1441
1726
|
|
|
1442
|
-
// Component-specific stories
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1727
|
+
// Component-specific stories for non-variant components (Input, Textarea, etc.)
|
|
1728
|
+
// Button and Badge are now handled generically by multi-dimension detection.
|
|
1729
|
+
const skipSpecialFor = new Set(["Button", "Badge"]);
|
|
1730
|
+
if (!skipSpecialFor.has(componentName)) {
|
|
1731
|
+
const specialStories = buildSpecialStories(componentName, variants);
|
|
1732
|
+
if (specialStories) {
|
|
1733
|
+
lines.push(specialStories);
|
|
1734
|
+
return lines.join("\n");
|
|
1735
|
+
}
|
|
1447
1736
|
}
|
|
1448
1737
|
|
|
1449
1738
|
// Profile-driven render + children — single source of truth via getStoryProfile()
|
|
@@ -1453,7 +1742,18 @@ function buildStoryFileContent(comp) {
|
|
|
1453
1742
|
const renderLine = buildProfileRenderLine(profile, RenderTarget, argsFallback);
|
|
1454
1743
|
const childrenArgLine = buildProfileChildrenArgLine(profile);
|
|
1455
1744
|
|
|
1456
|
-
|
|
1745
|
+
// Multi-dimension story generation: variant × size × disabled × AllVariants grid
|
|
1746
|
+
const hasDimensions = Object.keys(variantMap.dimensions).length > 0 || variantMap.booleanStates.length > 0;
|
|
1747
|
+
|
|
1748
|
+
if (hasDimensions) {
|
|
1749
|
+
const multiStories = buildMultiDimensionStories(
|
|
1750
|
+
variantMap, renderLine, childrenArgLine, defaultArgLines, componentName
|
|
1751
|
+
);
|
|
1752
|
+
if (multiStories) {
|
|
1753
|
+
lines.push(multiStories);
|
|
1754
|
+
}
|
|
1755
|
+
} else {
|
|
1756
|
+
// No dimensions detected: single Default story
|
|
1457
1757
|
lines.push(`export const Default: Story = {`);
|
|
1458
1758
|
lines.push(renderLine);
|
|
1459
1759
|
const storyArgLines = [];
|
|
@@ -1465,29 +1765,6 @@ function buildStoryFileContent(comp) {
|
|
|
1465
1765
|
lines.push(` },`);
|
|
1466
1766
|
}
|
|
1467
1767
|
lines.push(`};`);
|
|
1468
|
-
} else {
|
|
1469
|
-
const defaultVariant = variants[0];
|
|
1470
|
-
lines.push(`export const ${capitalize(defaultVariant)}: Story = {`);
|
|
1471
|
-
lines.push(renderLine);
|
|
1472
|
-
lines.push(` args: {`);
|
|
1473
|
-
lines.push(` variant: "${defaultVariant}",`);
|
|
1474
|
-
if (childrenArgLine(componentName)) lines.push(childrenArgLine(componentName));
|
|
1475
|
-
for (const line of defaultArgLines) lines.push(line);
|
|
1476
|
-
lines.push(` },`);
|
|
1477
|
-
lines.push(`};`);
|
|
1478
|
-
lines.push("");
|
|
1479
|
-
for (const v of variants.slice(1)) {
|
|
1480
|
-
const storyName = capitalize(v);
|
|
1481
|
-
lines.push(`export const ${storyName}: Story = {`);
|
|
1482
|
-
lines.push(renderLine);
|
|
1483
|
-
lines.push(` args: {`);
|
|
1484
|
-
lines.push(` variant: "${v}",`);
|
|
1485
|
-
if (childrenArgLine(storyName)) lines.push(childrenArgLine(storyName));
|
|
1486
|
-
for (const line of defaultArgLines) lines.push(line);
|
|
1487
|
-
lines.push(` },`);
|
|
1488
|
-
lines.push(`};`);
|
|
1489
|
-
lines.push("");
|
|
1490
|
-
}
|
|
1491
1768
|
}
|
|
1492
1769
|
|
|
1493
1770
|
return lines.join("\n");
|
|
@@ -3054,14 +3331,27 @@ function main() {
|
|
|
3054
3331
|
// Skip unclassified and shadcn/ui primitives (UI group) — they're documented at ui.shadcn.com
|
|
3055
3332
|
// Only project-specific module groups (Circles, Finance, Projects, Time, …) get stories
|
|
3056
3333
|
const g = comp.group || "Components";
|
|
3334
|
+
// Skip shadcn/ui primitives by default — they're documented at ui.shadcn.com
|
|
3335
|
+
// Users can override with vds.config.js: includeGroups: ["shadcn"] or includeComponents: ["Button", "Badge"]
|
|
3336
|
+
const includeGroups = VDS_CONFIG.includeGroups || [];
|
|
3337
|
+
const includeComponents = VDS_CONFIG.includeComponents || [];
|
|
3057
3338
|
if (g === "Uncategorized" || g === "UI" || g === "shadcn" || g === "ui") {
|
|
3058
|
-
|
|
3059
|
-
const
|
|
3060
|
-
if (
|
|
3061
|
-
|
|
3062
|
-
|
|
3339
|
+
const groupIncluded = includeGroups.some((ig) => ig.toLowerCase() === g.toLowerCase());
|
|
3340
|
+
const compIncluded = includeComponents.includes(componentName);
|
|
3341
|
+
if (!groupIncluded && !compIncluded) {
|
|
3342
|
+
// Clean up leftover story files for components that are now in the skip group
|
|
3343
|
+
const oldStory = path.join(STORIES_DIR, `${componentName}.stories.tsx`);
|
|
3344
|
+
if (fs.existsSync(oldStory)) {
|
|
3345
|
+
try {
|
|
3346
|
+
const firstLine = fs.readFileSync(oldStory, "utf-8").split("\n")[0] || "";
|
|
3347
|
+
if (firstLine.includes("@vds-regenerate")) {
|
|
3348
|
+
fs.unlinkSync(oldStory);
|
|
3349
|
+
console.log(`[VDS] Removed shadcn/ui story: ${componentName}.stories.tsx`);
|
|
3350
|
+
}
|
|
3351
|
+
} catch { /* ignore */ }
|
|
3352
|
+
}
|
|
3353
|
+
continue;
|
|
3063
3354
|
}
|
|
3064
|
-
continue;
|
|
3065
3355
|
}
|
|
3066
3356
|
if (onlyName && componentName !== onlyName) continue;
|
|
3067
3357
|
|