vibe-design-system 2.8.31 → 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.31",
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,17 +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);
1294
- // Any component with no extractable props: suppress children arg + simplify render.
1295
- // Covers feature, page, and section-type components regardless of tier classification (e.g. HeroSection, Footer)
1296
- const isNoPropsFeature = effectiveProps.length === 0;
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);
1297
1371
 
1298
1372
  // Skip story only if not a page and no export found
1299
1373
  if (exportStyle === "unknown" && !isPage && (!source.includes("export") || !new RegExp(`\\b${componentName}\\b`).test(source))) {
@@ -1361,21 +1435,12 @@ function buildStoryFileContent(comp) {
1361
1435
  return lines.join("\n");
1362
1436
  }
1363
1437
 
1364
- // Kalıcı çözüm: SafeWrapper varsa her zaman onu kullan (Docs/Controls args geçmese bile safeDefaults uygulanır)
1365
- const useSafeWrapper = componentName && SAFE_WRAPPER_DEFAULTS[componentName];
1366
- const RenderTarget = useSafeWrapper ? "SafeWrapper" : "ComponentRef";
1367
- const argsFallback = !useSafeWrapper && (componentName && RENDER_ARGS_FALLBACKS[componentName]) || "";
1368
- const argsParam = (useSafeWrapper || argsFallback) ? "(args = {})" : "(args)";
1369
- const propsArg = argsFallback ? `{ ...args${argsFallback} }` : (useSafeWrapper ? "args" : "args");
1370
- const renderLine = useReactNodeChildrenRender
1371
- ? ` render: (args) => React.createElement(React.Suspense, { fallback: null }, React.createElement(${RenderTarget}, { ...args, children: args.children || React.createElement('span', null, 'Example') })),`
1372
- : isNoPropsFeature
1373
- ? ` render: () => React.createElement(React.Suspense, { fallback: null }, React.createElement(${RenderTarget})),`
1374
- : ` render: ${argsParam} => React.createElement(React.Suspense, { fallback: null }, React.createElement(${RenderTarget}, ${propsArg})),`;
1375
- const childrenArgLine = (label) => {
1376
- if (isNoPropsFeature) return null; // Feature/page with no props: no children arg
1377
- return !omitChildren && !useReactNodeChildrenRender ? ` children: ${JSON.stringify(label)},` : null;
1378
- };
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);
1379
1444
 
1380
1445
  if (!variants.length) {
1381
1446
  lines.push(`export const Default: Story = {`);