vibe-design-system 2.8.30 → 2.8.32

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.30",
3
+ "version": "2.8.32",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -415,6 +415,11 @@ function inferTier(rel, content) {
415
415
  const localImports = (content.match(/from\s+['"](?:\.\.?\/?|@\/components\/)/g) || []).length;
416
416
 
417
417
  if (lines >= 400 || localImports >= 12) return "page";
418
+ // Section/Layout/View/Panel/Screen named components with few imports are view-level feature tier.
419
+ // e.g. BusinessModelSection, HeroSection, CheckoutView, AppLayout, CartPanel — regardless of line count.
420
+ // Threshold localImports <= 3: genuine sections import very little; aggregators already hit lines/imports above.
421
+ const SECTION_FILENAME_PATTERNS = /Section$|Layout$|View$|Panel$|Screen$/;
422
+ if (SECTION_FILENAME_PATTERNS.test(filename) && localImports <= 3) return "feature";
418
423
  // Semantic keyword: treat as feature even if small (fetches data via context/hooks)
419
424
  if (lines >= 200 || localImports >= 7 || FEATURE_FILENAME_KEYWORDS.test(filename)) return "feature";
420
425
  return "component";
@@ -1219,6 +1219,74 @@ function buildArgTypeEntry(prop) {
1219
1219
  return `{ control: "object", description: ${JSON.stringify(t.slice(0, 60))} }`;
1220
1220
  }
1221
1221
 
1222
+ // ─── Component Profile System ─────────────────────────────────────────────────
1223
+ // Replaces the 6 scattered boolean flags (omitChildren, useReactNodeChildrenRender,
1224
+ // isNoPropsFeature, useSafeWrapper, argsParam, propsArg) with a single canonical
1225
+ // profile that drives all story generation decisions deterministically.
1226
+ //
1227
+ // Priority order (first match wins):
1228
+ // SAFE — SAFE_WRAPPER_DEFAULTS entry exists (complex required props)
1229
+ // SECTION — no extractable props (context/hook-driven, e.g. HeroSection, Footer)
1230
+ // WRAPPER — has children: ReactNode prop (wraps child content)
1231
+ // VARIANT — has variant prop (cva-based, produces multi-story exports)
1232
+ // CONFIGURED — default: has props, no special pattern
1233
+ // ─────────────────────────────────────────────────────────────────────────────
1234
+
1235
+ /**
1236
+ * Classifies a component into one of 5 story profiles.
1237
+ * @param {string} componentName
1238
+ * @param {string} source — raw component source
1239
+ * @param {Array<{name:string,type:string,required:boolean}>} effectiveProps
1240
+ * @returns {"SAFE"|"SECTION"|"WRAPPER"|"VARIANT"|"CONFIGURED"}
1241
+ */
1242
+ function getStoryProfile(componentName, source, effectiveProps) {
1243
+ if (componentName && SAFE_WRAPPER_DEFAULTS[componentName]) return "SAFE";
1244
+ if (effectiveProps.length === 0) return "SECTION";
1245
+ if (hasChildrenPropReactNode(effectiveProps)) return "WRAPPER";
1246
+ if (effectiveProps.some(p => p.name === "variant")) return "VARIANT";
1247
+ return "CONFIGURED";
1248
+ }
1249
+
1250
+ /**
1251
+ * Builds the render: line for a story based on its component profile.
1252
+ * @param {"SAFE"|"SECTION"|"WRAPPER"|"VARIANT"|"CONFIGURED"} profile
1253
+ * @param {"SafeWrapper"|"ComponentRef"} RenderTarget
1254
+ * @param {string} argsFallback — extra inline props spread or ""
1255
+ * @returns {string}
1256
+ */
1257
+ function buildProfileRenderLine(profile, RenderTarget, argsFallback) {
1258
+ const suspense = (inner) =>
1259
+ `React.createElement(React.Suspense, { fallback: null }, ${inner})`;
1260
+ switch (profile) {
1261
+ case "SECTION":
1262
+ return ` render: () => ${suspense(`React.createElement(${RenderTarget})`)},`;
1263
+ case "WRAPPER":
1264
+ return ` render: (args) => ${suspense(
1265
+ `React.createElement(${RenderTarget}, { ...args, children: args.children || React.createElement('span', null, 'Example') })`
1266
+ )},`;
1267
+ case "SAFE":
1268
+ return ` render: (args = {}) => ${suspense(`React.createElement(${RenderTarget}, args)`)},`;
1269
+ default: { // CONFIGURED, VARIANT
1270
+ const argsParam = argsFallback ? "(args = {})" : "(args)";
1271
+ const propsArg = argsFallback ? `{ ...args${argsFallback} }` : "args";
1272
+ return ` render: ${argsParam} => ${suspense(`React.createElement(${RenderTarget}, ${propsArg})`)},`;
1273
+ }
1274
+ }
1275
+ }
1276
+
1277
+ /**
1278
+ * Returns a function that produces a `children: "label",` arg line or null.
1279
+ * Only WRAPPER profile includes a children arg so Storybook shows a children control.
1280
+ * @param {"SAFE"|"SECTION"|"WRAPPER"|"VARIANT"|"CONFIGURED"} profile
1281
+ * @returns {(label: string) => string|null}
1282
+ */
1283
+ function buildProfileChildrenArgLine(profile) {
1284
+ return (label) => {
1285
+ if (profile === "WRAPPER") return ` children: ${JSON.stringify(label)},`;
1286
+ return null;
1287
+ };
1288
+ }
1289
+
1222
1290
  function buildStoryFileContent(comp) {
1223
1291
  const componentName = toSafeComponentName(comp.name, comp.file);
1224
1292
  const fileNoExt = comp.file.replace(/\.(tsx|jsx)$/, "");
@@ -1283,14 +1351,23 @@ function buildStoryFileContent(comp) {
1283
1351
  // ignore
1284
1352
  }
1285
1353
  const exportStyle = detectExportStyle(source, componentName);
1286
- const omitChildren = componentWrapsVoidElement(source);
1287
1354
  const isPage = isPageFile;
1288
1355
 
1289
1356
  // Props: manifest or parse from source for default args (LucideIcon, array types, ReactNode, contextual placeholders)
1290
1357
  const effectiveProps = Array.isArray(comp.props) && comp.props.length > 0 ? comp.props : parsePropsFromSource(source);
1291
1358
  const usageFromPages = findComponentUsageInPages(componentName, PROJECT_ROOT);
1292
- const { argLines: defaultArgLines, lucideImports, iconPropNames, needReact } = buildDefaultArgsForRequiredProps(effectiveProps, usageFromPages, componentName, source);
1293
- const useReactNodeChildrenRender = !omitChildren && hasChildrenPropReactNode(effectiveProps);
1359
+
1360
+ // Profile: single source of truth — replaces omitChildren, useReactNodeChildrenRender,
1361
+ // isNoPropsFeature, useSafeWrapper, argsParam, propsArg flags.
1362
+ const profile = getStoryProfile(componentName, source, effectiveProps);
1363
+ const propSummary = effectiveProps.length > 0 ? ` (${effectiveProps.length} props)` : "";
1364
+ console.log(`[VDS] ${componentName} → ${profile}${propSummary}`);
1365
+
1366
+ // For SECTION, argLines are unused; still need lucideImports/iconPropNames/needReact for imports+argTypes.
1367
+ const { argLines: defaultArgLines, lucideImports, iconPropNames, needReact } =
1368
+ profile === "SECTION"
1369
+ ? { argLines: [], lucideImports: [], iconPropNames: [], needReact: false }
1370
+ : buildDefaultArgsForRequiredProps(effectiveProps, usageFromPages, componentName, source);
1294
1371
 
1295
1372
  // Skip story only if not a page and no export found
1296
1373
  if (exportStyle === "unknown" && !isPage && (!source.includes("export") || !new RegExp(`\\b${componentName}\\b`).test(source))) {
@@ -1358,24 +1435,24 @@ function buildStoryFileContent(comp) {
1358
1435
  return lines.join("\n");
1359
1436
  }
1360
1437
 
1361
- // Kalıcı çözüm: SafeWrapper varsa her zaman onu kullan (Docs/Controls args geçmese bile safeDefaults uygulanır)
1362
- const useSafeWrapper = componentName && SAFE_WRAPPER_DEFAULTS[componentName];
1363
- const RenderTarget = useSafeWrapper ? "SafeWrapper" : "ComponentRef";
1364
- const argsFallback = !useSafeWrapper && (componentName && RENDER_ARGS_FALLBACKS[componentName]) || "";
1365
- const argsParam = (useSafeWrapper || argsFallback) ? "(args = {})" : "(args)";
1366
- const propsArg = argsFallback ? `{ ...args${argsFallback} }` : (useSafeWrapper ? "args" : "args");
1367
- const renderLine = useReactNodeChildrenRender
1368
- ? ` render: (args) => React.createElement(React.Suspense, { fallback: null }, React.createElement(${RenderTarget}, { ...args, children: args.children || React.createElement('span', null, 'Example') })),`
1369
- : ` render: ${argsParam} => React.createElement(React.Suspense, { fallback: null }, React.createElement(${RenderTarget}, ${propsArg})),`;
1370
- const childrenArgLine = (label) => (!omitChildren && !useReactNodeChildrenRender ? ` children: ${JSON.stringify(label)},` : null);
1438
+ // Profile-driven render + children single source of truth via getStoryProfile()
1439
+ const useSafeWrapper = profile === "SAFE";
1440
+ const RenderTarget = useSafeWrapper ? "SafeWrapper" : "ComponentRef";
1441
+ const argsFallback = !useSafeWrapper && (componentName && RENDER_ARGS_FALLBACKS[componentName]) || "";
1442
+ const renderLine = buildProfileRenderLine(profile, RenderTarget, argsFallback);
1443
+ const childrenArgLine = buildProfileChildrenArgLine(profile);
1371
1444
 
1372
1445
  if (!variants.length) {
1373
1446
  lines.push(`export const Default: Story = {`);
1374
1447
  lines.push(renderLine);
1375
- lines.push(` args: {`);
1376
- if (childrenArgLine(componentName)) lines.push(childrenArgLine(componentName));
1377
- for (const line of defaultArgLines) lines.push(line);
1378
- lines.push(` },`);
1448
+ const storyArgLines = [];
1449
+ if (childrenArgLine(componentName)) storyArgLines.push(childrenArgLine(componentName));
1450
+ for (const line of defaultArgLines) storyArgLines.push(line);
1451
+ if (storyArgLines.length > 0) {
1452
+ lines.push(` args: {`);
1453
+ for (const line of storyArgLines) lines.push(line);
1454
+ lines.push(` },`);
1455
+ }
1379
1456
  lines.push(`};`);
1380
1457
  } else {
1381
1458
  const defaultVariant = variants[0];