vibe-design-system 2.8.31 → 2.8.33
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
|
@@ -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
|
-
|
|
1293
|
-
|
|
1294
|
-
//
|
|
1295
|
-
|
|
1296
|
-
const
|
|
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))) {
|
|
@@ -1326,7 +1400,8 @@ function buildStoryFileContent(comp) {
|
|
|
1326
1400
|
lines.push(`const meta = {`);
|
|
1327
1401
|
lines.push(` title: ${JSON.stringify(title)},`);
|
|
1328
1402
|
lines.push(` component: ComponentRef,`);
|
|
1329
|
-
|
|
1403
|
+
// SECTION: no props/args → autodocs tries to render React.lazy without Suspense → useRef crash
|
|
1404
|
+
if (profile !== "SECTION") lines.push(` tags: ["autodocs"],`);
|
|
1330
1405
|
|
|
1331
1406
|
// Build argTypes from extracted TypeScript props + icon-specific overrides
|
|
1332
1407
|
const argTypeEntries = [];
|
|
@@ -1361,21 +1436,12 @@ function buildStoryFileContent(comp) {
|
|
|
1361
1436
|
return lines.join("\n");
|
|
1362
1437
|
}
|
|
1363
1438
|
|
|
1364
|
-
//
|
|
1365
|
-
const useSafeWrapper =
|
|
1366
|
-
const RenderTarget
|
|
1367
|
-
const argsFallback
|
|
1368
|
-
const
|
|
1369
|
-
const
|
|
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
|
-
};
|
|
1439
|
+
// Profile-driven render + children — single source of truth via getStoryProfile()
|
|
1440
|
+
const useSafeWrapper = profile === "SAFE";
|
|
1441
|
+
const RenderTarget = useSafeWrapper ? "SafeWrapper" : "ComponentRef";
|
|
1442
|
+
const argsFallback = !useSafeWrapper && (componentName && RENDER_ARGS_FALLBACKS[componentName]) || "";
|
|
1443
|
+
const renderLine = buildProfileRenderLine(profile, RenderTarget, argsFallback);
|
|
1444
|
+
const childrenArgLine = buildProfileChildrenArgLine(profile);
|
|
1379
1445
|
|
|
1380
1446
|
if (!variants.length) {
|
|
1381
1447
|
lines.push(`export const Default: Story = {`);
|
|
@@ -204,12 +204,32 @@ function injectAliases(projectRoot, specifiers) {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/** Inject resolve.dedupe into .storybook/main.* viteFinal to prevent multiple React instances.
|
|
208
|
+
* Multiple React instances cause "Cannot read properties of null (reading 'useRef')" at MemoryRouter. */
|
|
209
|
+
function injectDedupe(projectRoot) {
|
|
210
|
+
const mainPath = getMainPath(projectRoot);
|
|
211
|
+
if (!mainPath) return;
|
|
212
|
+
let content = fs.readFileSync(mainPath, "utf-8");
|
|
213
|
+
if (content.includes("dedupe")) return; // idempotent
|
|
214
|
+
// Match: alias block + its trailing comma + whitespace/newline + resolve closing }
|
|
215
|
+
// e.g. alias: { "@": path.resolve(...) },\n },
|
|
216
|
+
const pattern = /(\balias\s*:\s*\{[^}]*\}),(\s*\})/;
|
|
217
|
+
if (!pattern.test(content)) return;
|
|
218
|
+
content = content.replace(pattern, (_, aliasBlock, resolveClose) =>
|
|
219
|
+
`${aliasBlock},\n dedupe: ["react", "react-dom", "react-router-dom"],${resolveClose}`
|
|
220
|
+
);
|
|
221
|
+
fs.writeFileSync(mainPath, content, "utf-8");
|
|
222
|
+
console.log("[VDS] Storybook adapt: injected React dedupe into viteFinal");
|
|
223
|
+
}
|
|
224
|
+
|
|
207
225
|
function main() {
|
|
208
226
|
const projectRoot = PROJECT_ROOT;
|
|
209
227
|
if (!fs.existsSync(path.join(projectRoot, ".storybook"))) {
|
|
210
228
|
console.log("[VDS] .storybook not found; skip storybook-adapt.");
|
|
211
229
|
return;
|
|
212
230
|
}
|
|
231
|
+
// Always inject dedupe — prevents "useRef null" crashes from multiple React instances
|
|
232
|
+
injectDedupe(projectRoot);
|
|
213
233
|
reportUnresolvedImports(projectRoot);
|
|
214
234
|
const problematic = collectProblematicImports(projectRoot);
|
|
215
235
|
if (problematic.size === 0) return;
|