vibe-design-system 2.5.40 → 2.5.42
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
|
@@ -542,6 +542,7 @@ function extractComponentSuggestions() {
|
|
|
542
542
|
const pageDirs = [
|
|
543
543
|
{ dir: PAGES_DIR, prefix: "src/pages/" },
|
|
544
544
|
{ dir: APP_DIR, prefix: "src/app/" },
|
|
545
|
+
{ dir: COMPONENTS_DIR, prefix: "src/components/" },
|
|
545
546
|
];
|
|
546
547
|
const allPageFiles = [];
|
|
547
548
|
for (const { dir, prefix } of pageDirs) {
|
|
@@ -1195,6 +1196,64 @@ function extractFoundations() {
|
|
|
1195
1196
|
};
|
|
1196
1197
|
}
|
|
1197
1198
|
|
|
1199
|
+
function extractButtonUsage() {
|
|
1200
|
+
if (!fs.existsSync(SRC_DIR)) return null;
|
|
1201
|
+
const files = getAllTsxJsxInDir(SRC_DIR);
|
|
1202
|
+
if (!Array.isArray(files) || files.length === 0) return null;
|
|
1203
|
+
|
|
1204
|
+
const combos = new Map();
|
|
1205
|
+
let total = 0;
|
|
1206
|
+
|
|
1207
|
+
const extractPropLiteral = (attrs, name) => {
|
|
1208
|
+
if (!attrs) return null;
|
|
1209
|
+
// variant="primary"
|
|
1210
|
+
let re = new RegExp(name + '\\\\s*=\\\\s*["\']([^"\']+)["\']');
|
|
1211
|
+
let m = attrs.match(re);
|
|
1212
|
+
if (m) return m[1];
|
|
1213
|
+
// variant={"primary"}
|
|
1214
|
+
re = new RegExp(name + '\\\\s*=\\\\s*\\\\{\\\\s*["\']([^"\']+)["\']\\\\s*\\\\}');
|
|
1215
|
+
m = attrs.match(re);
|
|
1216
|
+
if (m) return m[1];
|
|
1217
|
+
return null;
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
for (const rel of files) {
|
|
1221
|
+
const fullPath = path.join(SRC_DIR, rel);
|
|
1222
|
+
let content;
|
|
1223
|
+
try {
|
|
1224
|
+
content = fs.readFileSync(fullPath, "utf-8");
|
|
1225
|
+
} catch (_) {
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
const re = /<Button\\b([^>]*)>/g;
|
|
1229
|
+
let m;
|
|
1230
|
+
while ((m = re.exec(content)) !== null) {
|
|
1231
|
+
total += 1;
|
|
1232
|
+
const attrs = m[1] || "";
|
|
1233
|
+
const variant = extractPropLiteral(attrs, "variant") || "default";
|
|
1234
|
+
const size = extractPropLiteral(attrs, "size") || "default";
|
|
1235
|
+
const asChild = /\\basChild\\b/.test(attrs);
|
|
1236
|
+
const key = `${variant}__${size}__${asChild ? "1" : "0"}`;
|
|
1237
|
+
if (!combos.has(key)) {
|
|
1238
|
+
combos.set(key, {
|
|
1239
|
+
key,
|
|
1240
|
+
variant,
|
|
1241
|
+
size,
|
|
1242
|
+
asChild,
|
|
1243
|
+
count: 0,
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
combos.get(key).count += 1;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
if (total === 0 || combos.size === 0) return null;
|
|
1251
|
+
return {
|
|
1252
|
+
total,
|
|
1253
|
+
combos: Array.from(combos.values()),
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1198
1257
|
function scan() {
|
|
1199
1258
|
if (!fs.existsSync(COMPONENTS_DIR)) {
|
|
1200
1259
|
console.error(CLI_LOCALES[CLI_LOCALE].componentsNotFound);
|
|
@@ -1242,6 +1301,10 @@ function scan() {
|
|
|
1242
1301
|
const foundations = extractFoundations();
|
|
1243
1302
|
foundations.icons = extractLucideIconsUsed(SRC_DIR);
|
|
1244
1303
|
foundations.brand = { assets: extractBrandAssets() };
|
|
1304
|
+
const buttonUsage = extractButtonUsage();
|
|
1305
|
+
if (buttonUsage) {
|
|
1306
|
+
foundations.buttonUsage = buttonUsage;
|
|
1307
|
+
}
|
|
1245
1308
|
const componentSuggestions = extractComponentSuggestions();
|
|
1246
1309
|
const unreleasedSectionCandidates = extractUnreleasedSectionCandidates();
|
|
1247
1310
|
const output = {
|
|
@@ -478,7 +478,7 @@ const REACTNODE_PLACEHOLDER_TEXT = {
|
|
|
478
478
|
children: "Example content",
|
|
479
479
|
};
|
|
480
480
|
|
|
481
|
-
/** Component adına göre ekstra args (toString/
|
|
481
|
+
/** Component adına göre ekstra args (toString/dateRange/toFixed hatalarını önlemek için; her prop güvenli değer alır). */
|
|
482
482
|
const COMPONENT_EXTRA_ARGS = {
|
|
483
483
|
TaskEstimateInput: [
|
|
484
484
|
" task: { id: \"1\", title: \"Example\", estimate: 0, description: \"\" },",
|
|
@@ -491,6 +491,22 @@ const COMPONENT_EXTRA_ARGS = {
|
|
|
491
491
|
" step: 1,",
|
|
492
492
|
" taskId: \"1\",",
|
|
493
493
|
],
|
|
494
|
+
TimeFilterPanel: [
|
|
495
|
+
" filter: { dateRange: {}, userIds: [], tags: [], billable: \"all\" },",
|
|
496
|
+
" onFilterChange: () => {},",
|
|
497
|
+
" onSaveFilter: () => {},",
|
|
498
|
+
" onDeleteFilter: () => {},",
|
|
499
|
+
" filteredCount: 0,",
|
|
500
|
+
" savedFilters: [],",
|
|
501
|
+
],
|
|
502
|
+
TimeStats: [
|
|
503
|
+
" totalLogged: 0,",
|
|
504
|
+
" totalBillable: 0,",
|
|
505
|
+
" totalNonBillable: 0,",
|
|
506
|
+
" totalBilled: 0,",
|
|
507
|
+
" totalEstimated: 0,",
|
|
508
|
+
" projectTotalEstimated: 0,",
|
|
509
|
+
],
|
|
494
510
|
};
|
|
495
511
|
|
|
496
512
|
/** Render'da args'a uygulanacak fallback (useState(estimate.toString()) gibi kullanımlar için; args/Controls undefined yapsa bile). */
|
|
@@ -498,6 +514,13 @@ const RENDER_ARGS_FALLBACKS = {
|
|
|
498
514
|
TaskEstimateInput: ", estimate: (args && args.estimate) ?? 0, value: (args && args.value) ?? 0, task: (args && args.task) ?? { id: \"1\", title: \"Example\", estimate: 0 }",
|
|
499
515
|
};
|
|
500
516
|
|
|
517
|
+
/** Kalıcı çözüm: Storybook Docs/Controls args'ı geçmezse bile component'a güvenli props veren wrapper. Önce safeDefaults uygulanır, sonra gelen props. */
|
|
518
|
+
const SAFE_WRAPPER_DEFAULTS = {
|
|
519
|
+
TaskEstimateInput: `{ estimate: 0, onUpdate: () => {}, value: 0, task: { id: "1", title: "Example", estimate: 0 }, compact: false }`,
|
|
520
|
+
TimeFilterPanel: `{ filter: { dateRange: {}, userIds: [], tags: [], billable: "all" }, onFilterChange: () => {}, onSaveFilter: () => {}, onDeleteFilter: () => {}, filteredCount: 0, savedFilters: [] }`,
|
|
521
|
+
TimeStats: `{ totalLogged: 0, totalBillable: 0, totalNonBillable: 0, totalBilled: 0, totalEstimated: 0, projectTotalEstimated: 0 }`,
|
|
522
|
+
};
|
|
523
|
+
|
|
501
524
|
/** Recursive list of .tsx/.jsx file paths under dir (relative to dir). Index.tsx / index.tsx first for deterministic "first usage". */
|
|
502
525
|
function getAllTsxJsxUnderDir(dir) {
|
|
503
526
|
if (!fs.existsSync(dir)) return [];
|
|
@@ -1063,6 +1086,12 @@ function buildStoryFileContent(comp) {
|
|
|
1063
1086
|
lines.push(`const ComponentRef = React.lazy(() => import(/* @vite-ignore */ "${importPath}").then(${getDefault}).catch(() => ({ default: ${loadFallback} })));`);
|
|
1064
1087
|
lines.push("");
|
|
1065
1088
|
|
|
1089
|
+
const safeDefaultsObj = componentName && SAFE_WRAPPER_DEFAULTS[componentName];
|
|
1090
|
+
if (safeDefaultsObj) {
|
|
1091
|
+
lines.push(`const safeDefaults = ${safeDefaultsObj};`);
|
|
1092
|
+
lines.push(`const SafeWrapper = (props) => React.createElement(ComponentRef, { ...safeDefaults, ...(props || {}) });`);
|
|
1093
|
+
lines.push("");
|
|
1094
|
+
}
|
|
1066
1095
|
lines.push(`const meta = {`);
|
|
1067
1096
|
lines.push(` title: ${JSON.stringify(title)},`);
|
|
1068
1097
|
lines.push(` component: ComponentRef,`);
|
|
@@ -1087,13 +1116,15 @@ function buildStoryFileContent(comp) {
|
|
|
1087
1116
|
return lines.join("\n");
|
|
1088
1117
|
}
|
|
1089
1118
|
|
|
1090
|
-
//
|
|
1091
|
-
const
|
|
1092
|
-
const
|
|
1093
|
-
const
|
|
1119
|
+
// Kalıcı çözüm: SafeWrapper varsa her zaman onu kullan (Docs/Controls args geçmese bile safeDefaults uygulanır)
|
|
1120
|
+
const useSafeWrapper = componentName && SAFE_WRAPPER_DEFAULTS[componentName];
|
|
1121
|
+
const RenderTarget = useSafeWrapper ? "SafeWrapper" : "ComponentRef";
|
|
1122
|
+
const argsFallback = !useSafeWrapper && (componentName && RENDER_ARGS_FALLBACKS[componentName]) || "";
|
|
1123
|
+
const argsParam = (useSafeWrapper || argsFallback) ? "(args = {})" : "(args)";
|
|
1124
|
+
const propsArg = argsFallback ? `{ ...args${argsFallback} }` : (useSafeWrapper ? "args" : "args");
|
|
1094
1125
|
const renderLine = useReactNodeChildrenRender
|
|
1095
|
-
? ` render: (args) => React.createElement(React.Suspense, { fallback: null }, React.createElement(
|
|
1096
|
-
: ` render: ${argsParam} => React.createElement(React.Suspense, { fallback: null }, React.createElement(
|
|
1126
|
+
? ` render: (args) => React.createElement(React.Suspense, { fallback: null }, React.createElement(${RenderTarget}, { ...args, children: args.children || React.createElement('span', null, 'Example') })),`
|
|
1127
|
+
: ` render: ${argsParam} => React.createElement(React.Suspense, { fallback: null }, React.createElement(${RenderTarget}, ${propsArg})),`;
|
|
1097
1128
|
const childrenArgLine = (label) => (!omitChildren && !useReactNodeChildrenRender ? ` children: ${JSON.stringify(label)},` : null);
|
|
1098
1129
|
|
|
1099
1130
|
if (!variants.length) {
|
|
@@ -1282,6 +1313,58 @@ function writeFoundationsStories(foundations) {
|
|
|
1282
1313
|
fs.writeFileSync(path.join(foundationsDir, "Icons.stories.tsx"), iconsContent, "utf-8");
|
|
1283
1314
|
console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "Icons.stories.tsx")));
|
|
1284
1315
|
}
|
|
1316
|
+
|
|
1317
|
+
const buttonUsage = foundations?.buttonUsage;
|
|
1318
|
+
if (buttonUsage && Array.isArray(buttonUsage.combos) && buttonUsage.combos.length > 0) {
|
|
1319
|
+
const combos = buttonUsage.combos.map((c) => ({
|
|
1320
|
+
key: c.key,
|
|
1321
|
+
variant: c.variant,
|
|
1322
|
+
size: c.size,
|
|
1323
|
+
asChild: !!c.asChild,
|
|
1324
|
+
count: c.count ?? 0,
|
|
1325
|
+
}));
|
|
1326
|
+
const buttonsContent =
|
|
1327
|
+
[
|
|
1328
|
+
"import type { Meta, StoryObj } from \"@storybook/react\";",
|
|
1329
|
+
"",
|
|
1330
|
+
"const meta = { title: \"Foundations/Button Usage\" } satisfies Meta;",
|
|
1331
|
+
"export default meta;",
|
|
1332
|
+
"type Story = StoryObj;",
|
|
1333
|
+
"",
|
|
1334
|
+
`const combos = ${JSON.stringify(combos)};`,
|
|
1335
|
+
"",
|
|
1336
|
+
"export const Default: Story = {",
|
|
1337
|
+
" render: () => (",
|
|
1338
|
+
" <div style={{ padding: 24, fontFamily: \"system-ui, sans-serif\" }}>",
|
|
1339
|
+
" <h2 style={{ marginBottom: 8 }}>Used Button variants in app code</h2>",
|
|
1340
|
+
" <p style={{ marginBottom: 16, color: \"#888\", fontSize: 13 }}>Based on <Button ... /> usages in src/ (ignores lowercase <button>).</p>",
|
|
1341
|
+
" <table style={{ borderCollapse: \"collapse\", width: \"100%\", fontSize: 13 }}>",
|
|
1342
|
+
" <thead>",
|
|
1343
|
+
" <tr>",
|
|
1344
|
+
" <th style={{ textAlign: \"left\", padding: \"4px 8px\", borderBottom: \"1px solid #333\" }}>Variant</th>",
|
|
1345
|
+
" <th style={{ textAlign: \"left\", padding: \"4px 8px\", borderBottom: \"1px solid #333\" }}>Size</th>",
|
|
1346
|
+
" <th style={{ textAlign: \"left\", padding: \"4px 8px\", borderBottom: \"1px solid #333\" }}>asChild</th>",
|
|
1347
|
+
" <th style={{ textAlign: \"right\", padding: \"4px 8px\", borderBottom: \"1px solid #333\" }}>Count</th>",
|
|
1348
|
+
" </tr>",
|
|
1349
|
+
" </thead>",
|
|
1350
|
+
" <tbody>",
|
|
1351
|
+
" {combos.map((c) => (",
|
|
1352
|
+
" <tr key={c.key}>",
|
|
1353
|
+
" <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #222\" }}>{c.variant}</td>",
|
|
1354
|
+
" <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #222\" }}>{c.size}</td>",
|
|
1355
|
+
" <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #222\" }}>{c.asChild ? \"yes\" : \"no\"}</td>",
|
|
1356
|
+
" <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #222\", textAlign: \"right\" }}>{c.count}</td>",
|
|
1357
|
+
" </tr>",
|
|
1358
|
+
" ))}",
|
|
1359
|
+
" </tbody>",
|
|
1360
|
+
" </table>",
|
|
1361
|
+
" </div>",
|
|
1362
|
+
" ),",
|
|
1363
|
+
"};",
|
|
1364
|
+
].join("\n");
|
|
1365
|
+
fs.writeFileSync(path.join(foundationsDir, "Buttons.stories.tsx"), buttonsContent, "utf-8");
|
|
1366
|
+
console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "Buttons.stories.tsx")));
|
|
1367
|
+
}
|
|
1285
1368
|
}
|
|
1286
1369
|
|
|
1287
1370
|
function writeComponentSuggestionsStory(componentSuggestions) {
|
|
@@ -1310,7 +1393,7 @@ function writeComponentSuggestionsStory(componentSuggestions) {
|
|
|
1310
1393
|
" render: () => (",
|
|
1311
1394
|
" <div style={{ padding: 24, fontFamily: \"system-ui, sans-serif\" }}>",
|
|
1312
1395
|
" <h2 style={{ marginBottom: 16 }}>Visual section candidates (component suggestions)</h2>",
|
|
1313
|
-
" <p style={{ color: \"#888\", marginBottom: 24 }}>From src/pages
|
|
1396
|
+
" <p style={{ color: \"#888\", marginBottom: 24 }}>From src/pages, src/app ve src/components: kendi className kümeleri, 2+ child element, 2+ tekrar. Aynı yapı birden fazla dosyada geçiyorsa ayrı component olarak düşünün.</p>",
|
|
1314
1397
|
" <div style={{ display: \"flex\", flexDirection: \"column\", gap: 24 }}>",
|
|
1315
1398
|
" {suggestions.map((s, i) => (",
|
|
1316
1399
|
" <div key={i} style={{ border: \"1px solid #333\", borderRadius: 8, padding: 16, background: \"#111\" }}>",
|