vibe-design-system 1.9.4 → 1.9.6

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": "1.9.4",
3
+ "version": "1.9.6",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,11 +20,7 @@ const VDS_OUTPUT = path.join(PROJECT_ROOT, "vds-output.json");
20
20
  const SRC_DIR = path.join(PROJECT_ROOT, "src");
21
21
  const STORIES_DIR = path.join(SRC_DIR, "stories");
22
22
 
23
- // Top of every story file for CSS variables
24
- const STORY_CSS_HEADER = `// @ts-ignore
25
- import '../index.css'
26
-
27
- `;
23
+ // CSS is loaded from .storybook/preview.tsx never add CSS import to story files.
28
24
 
29
25
  // Components we don't want to auto-generate stories for (project-specific dashboards, heavy UIs, etc.)
30
26
  const SKIP_LIST = [
@@ -46,8 +42,299 @@ const SKIP_LIST = [
46
42
  "InputOtp",
47
43
  "Resizable",
48
44
  "Sonner",
45
+ "ImageWithFallback",
46
+ "ConnectionLines",
47
+ "ScrollToTop",
48
+ "Form",
49
+ "Toaster",
50
+ "Toast",
49
51
  ];
50
52
 
53
+ /** shadcn/ui composite component recipes: component name → imports + render. */
54
+ const RECIPES = {
55
+ Accordion: {
56
+ imports: ["AccordionItem", "AccordionTrigger", "AccordionContent"],
57
+ render: `(args) => (
58
+ <ComponentRef type="single" collapsible {...args}>
59
+ <AccordionItem value="item-1">
60
+ <AccordionTrigger>Is it accessible?</AccordionTrigger>
61
+ <AccordionContent>Yes. It adheres to the WAI-ARIA design pattern.</AccordionContent>
62
+ </AccordionItem>
63
+ <AccordionItem value="item-2">
64
+ <AccordionTrigger>Is it styled?</AccordionTrigger>
65
+ <AccordionContent>Yes. It comes with default styles.</AccordionContent>
66
+ </AccordionItem>
67
+ </ComponentRef>
68
+ )`,
69
+ },
70
+ Dialog: {
71
+ imports: ["DialogTrigger", "DialogContent", "DialogHeader", "DialogTitle", "DialogDescription"],
72
+ render: `(args) => (
73
+ <ComponentRef {...args}>
74
+ <DialogTrigger asChild><button>Open Dialog</button></DialogTrigger>
75
+ <DialogContent>
76
+ <DialogHeader>
77
+ <DialogTitle>Dialog Title</DialogTitle>
78
+ <DialogDescription>This is a dialog description.</DialogDescription>
79
+ </DialogHeader>
80
+ </DialogContent>
81
+ </ComponentRef>
82
+ )`,
83
+ },
84
+ AlertDialog: {
85
+ imports: ["AlertDialogTrigger", "AlertDialogContent", "AlertDialogHeader", "AlertDialogTitle", "AlertDialogDescription", "AlertDialogFooter", "AlertDialogCancel", "AlertDialogAction"],
86
+ render: `(args) => (
87
+ <ComponentRef {...args}>
88
+ <AlertDialogTrigger asChild><button>Open</button></AlertDialogTrigger>
89
+ <AlertDialogContent>
90
+ <AlertDialogHeader>
91
+ <AlertDialogTitle>Are you sure?</AlertDialogTitle>
92
+ <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
93
+ </AlertDialogHeader>
94
+ <AlertDialogFooter>
95
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
96
+ <AlertDialogAction>Continue</AlertDialogAction>
97
+ </AlertDialogFooter>
98
+ </AlertDialogContent>
99
+ </ComponentRef>
100
+ )`,
101
+ },
102
+ Alert: {
103
+ imports: ["AlertTitle", "AlertDescription"],
104
+ render: `(args) => (
105
+ <ComponentRef {...args}>
106
+ <AlertTitle>Heads up!</AlertTitle>
107
+ <AlertDescription>You can add components to your app using the CLI.</AlertDescription>
108
+ </ComponentRef>
109
+ )`,
110
+ },
111
+ Tooltip: {
112
+ imports: ["TooltipProvider", "TooltipTrigger", "TooltipContent"],
113
+ render: `(args) => (
114
+ <TooltipProvider>
115
+ <ComponentRef {...args}>
116
+ <TooltipTrigger asChild><button>Hover me</button></TooltipTrigger>
117
+ <TooltipContent><p>Tooltip content</p></TooltipContent>
118
+ </ComponentRef>
119
+ </TooltipProvider>
120
+ )`,
121
+ },
122
+ Popover: {
123
+ imports: ["PopoverTrigger", "PopoverContent"],
124
+ render: `(args) => (
125
+ <ComponentRef {...args}>
126
+ <PopoverTrigger asChild><button>Open Popover</button></PopoverTrigger>
127
+ <PopoverContent>Place content here.</PopoverContent>
128
+ </ComponentRef>
129
+ )`,
130
+ },
131
+ HoverCard: {
132
+ imports: ["HoverCardTrigger", "HoverCardContent"],
133
+ render: `(args) => (
134
+ <ComponentRef {...args}>
135
+ <HoverCardTrigger asChild><button>Hover</button></HoverCardTrigger>
136
+ <HoverCardContent>Card content on hover.</HoverCardContent>
137
+ </ComponentRef>
138
+ )`,
139
+ },
140
+ Tabs: {
141
+ imports: ["TabsList", "TabsTrigger", "TabsContent"],
142
+ render: `(args) => (
143
+ <ComponentRef defaultValue="tab1" {...args}>
144
+ <TabsList>
145
+ <TabsTrigger value="tab1">Tab 1</TabsTrigger>
146
+ <TabsTrigger value="tab2">Tab 2</TabsTrigger>
147
+ </TabsList>
148
+ <TabsContent value="tab1">Content for tab 1</TabsContent>
149
+ <TabsContent value="tab2">Content for tab 2</TabsContent>
150
+ </ComponentRef>
151
+ )`,
152
+ },
153
+ Table: {
154
+ imports: ["TableHeader", "TableBody", "TableRow", "TableHead", "TableCell"],
155
+ render: `(args) => (
156
+ <ComponentRef {...args}>
157
+ <TableHeader><TableRow><TableHead>Name</TableHead><TableHead>Status</TableHead></TableRow></TableHeader>
158
+ <TableBody><TableRow><TableCell>Item 1</TableCell><TableCell>Active</TableCell></TableRow></TableBody>
159
+ </ComponentRef>
160
+ )`,
161
+ },
162
+ DropdownMenu: {
163
+ imports: ["DropdownMenuTrigger", "DropdownMenuContent", "DropdownMenuItem"],
164
+ render: `(args) => (
165
+ <ComponentRef {...args}>
166
+ <DropdownMenuTrigger asChild><button>Open Menu</button></DropdownMenuTrigger>
167
+ <DropdownMenuContent>
168
+ <DropdownMenuItem>Profile</DropdownMenuItem>
169
+ <DropdownMenuItem>Settings</DropdownMenuItem>
170
+ <DropdownMenuItem>Logout</DropdownMenuItem>
171
+ </DropdownMenuContent>
172
+ </ComponentRef>
173
+ )`,
174
+ },
175
+ ContextMenu: {
176
+ imports: ["ContextMenuTrigger", "ContextMenuContent", "ContextMenuItem"],
177
+ render: `(args) => (
178
+ <ComponentRef {...args}>
179
+ <ContextMenuTrigger><div style={{padding: 40, border: '1px dashed #ccc'}}>Right click here</div></ContextMenuTrigger>
180
+ <ContextMenuContent>
181
+ <ContextMenuItem>Back</ContextMenuItem>
182
+ <ContextMenuItem>Forward</ContextMenuItem>
183
+ <ContextMenuItem>Reload</ContextMenuItem>
184
+ </ContextMenuContent>
185
+ </ComponentRef>
186
+ )`,
187
+ },
188
+ Select: {
189
+ imports: ["SelectTrigger", "SelectValue", "SelectContent", "SelectItem"],
190
+ render: `(args) => (
191
+ <ComponentRef {...args}>
192
+ <SelectTrigger className="w-[180px]"><SelectValue placeholder="Select a fruit" /></SelectTrigger>
193
+ <SelectContent>
194
+ <SelectItem value="apple">Apple</SelectItem>
195
+ <SelectItem value="banana">Banana</SelectItem>
196
+ <SelectItem value="orange">Orange</SelectItem>
197
+ </SelectContent>
198
+ </ComponentRef>
199
+ )`,
200
+ },
201
+ Card: {
202
+ imports: ["CardHeader", "CardTitle", "CardDescription", "CardContent", "CardFooter"],
203
+ render: `(args) => (
204
+ <ComponentRef className="w-[340px]" {...args}>
205
+ <CardHeader>
206
+ <CardTitle>Card title</CardTitle>
207
+ <CardDescription>Short description.</CardDescription>
208
+ </CardHeader>
209
+ <CardContent><p>Card body content here.</p></CardContent>
210
+ <CardFooter>Footer</CardFooter>
211
+ </ComponentRef>
212
+ )`,
213
+ },
214
+ Avatar: {
215
+ imports: ["AvatarImage", "AvatarFallback"],
216
+ render: `(args) => (
217
+ <ComponentRef {...args}>
218
+ <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
219
+ <AvatarFallback>JD</AvatarFallback>
220
+ </ComponentRef>
221
+ )`,
222
+ },
223
+ Checkbox: {
224
+ render: `(args) => (
225
+ <div className="flex items-center space-x-2">
226
+ <ComponentRef id="terms" {...args} />
227
+ <label htmlFor="terms" className="text-sm font-medium">Accept terms</label>
228
+ </div>
229
+ )`,
230
+ },
231
+ Switch: {
232
+ render: `(args) => (
233
+ <div className="flex items-center space-x-2">
234
+ <ComponentRef id="switch" {...args} />
235
+ <label htmlFor="switch" className="text-sm font-medium">Enable notifications</label>
236
+ </div>
237
+ )`,
238
+ },
239
+ RadioGroup: {
240
+ imports: ["RadioGroupItem"],
241
+ render: `(args) => (
242
+ <ComponentRef className="flex flex-col space-y-2" {...args}>
243
+ <div className="flex items-center space-x-2">
244
+ <RadioGroupItem value="comfortable" id="comfortable" />
245
+ <label htmlFor="comfortable" className="text-sm font-medium">Comfortable</label>
246
+ </div>
247
+ <div className="flex items-center space-x-2">
248
+ <RadioGroupItem value="compact" id="compact" />
249
+ <label htmlFor="compact" className="text-sm font-medium">Compact</label>
250
+ </div>
251
+ </ComponentRef>
252
+ )`,
253
+ },
254
+ Sheet: {
255
+ imports: ["SheetTrigger", "SheetContent", "SheetHeader", "SheetTitle", "SheetDescription"],
256
+ render: `(args) => (
257
+ <ComponentRef {...args}>
258
+ <SheetTrigger asChild><button>Open Sheet</button></SheetTrigger>
259
+ <SheetContent>
260
+ <SheetHeader>
261
+ <SheetTitle>Sheet Title</SheetTitle>
262
+ <SheetDescription>Sheet description here.</SheetDescription>
263
+ </SheetHeader>
264
+ </SheetContent>
265
+ </ComponentRef>
266
+ )`,
267
+ },
268
+ Drawer: {
269
+ imports: ["DrawerTrigger", "DrawerContent", "DrawerHeader", "DrawerTitle", "DrawerDescription"],
270
+ render: `(args) => (
271
+ <ComponentRef {...args}>
272
+ <DrawerTrigger asChild><button>Open Drawer</button></DrawerTrigger>
273
+ <DrawerContent>
274
+ <DrawerHeader>
275
+ <DrawerTitle>Drawer Title</DrawerTitle>
276
+ <DrawerDescription>Drawer description here.</DrawerDescription>
277
+ </DrawerHeader>
278
+ </DrawerContent>
279
+ </ComponentRef>
280
+ )`,
281
+ },
282
+ Command: {
283
+ imports: ["CommandInput", "CommandList", "CommandEmpty", "CommandGroup", "CommandItem"],
284
+ render: `(args) => (
285
+ <ComponentRef className="rounded-lg border shadow-md" {...args}>
286
+ <CommandInput placeholder="Type a command..." />
287
+ <CommandList>
288
+ <CommandEmpty>No results found.</CommandEmpty>
289
+ <CommandGroup heading="Suggestions">
290
+ <CommandItem>Calendar</CommandItem>
291
+ <CommandItem>Search</CommandItem>
292
+ <CommandItem>Settings</CommandItem>
293
+ </CommandGroup>
294
+ </CommandList>
295
+ </ComponentRef>
296
+ )`,
297
+ },
298
+ Carousel: {
299
+ imports: ["CarouselContent", "CarouselItem", "CarouselPrevious", "CarouselNext"],
300
+ render: `(args) => (
301
+ <ComponentRef className="w-full max-w-xs" {...args}>
302
+ <CarouselContent>
303
+ <CarouselItem><div className="p-4 text-center border rounded">Slide 1</div></CarouselItem>
304
+ <CarouselItem><div className="p-4 text-center border rounded">Slide 2</div></CarouselItem>
305
+ <CarouselItem><div className="p-4 text-center border rounded">Slide 3</div></CarouselItem>
306
+ </CarouselContent>
307
+ <CarouselPrevious />
308
+ <CarouselNext />
309
+ </ComponentRef>
310
+ )`,
311
+ },
312
+ Collapsible: {
313
+ imports: ["CollapsibleTrigger", "CollapsibleContent"],
314
+ render: `(args) => (
315
+ <ComponentRef {...args}>
316
+ <CollapsibleTrigger asChild><button className="text-sm font-medium">Toggle content</button></CollapsibleTrigger>
317
+ <CollapsibleContent className="mt-2 p-2 border rounded">Hidden content revealed.</CollapsibleContent>
318
+ </ComponentRef>
319
+ )`,
320
+ },
321
+ Menubar: {
322
+ imports: ["MenubarMenu", "MenubarTrigger", "MenubarContent", "MenubarItem"],
323
+ render: `(args) => (
324
+ <ComponentRef {...args}>
325
+ <MenubarMenu>
326
+ <MenubarTrigger>File</MenubarTrigger>
327
+ <MenubarContent>
328
+ <MenubarItem>New</MenubarItem>
329
+ <MenubarItem>Open</MenubarItem>
330
+ <MenubarItem>Save</MenubarItem>
331
+ </MenubarContent>
332
+ </MenubarMenu>
333
+ </ComponentRef>
334
+ )`,
335
+ },
336
+ };
337
+
51
338
  function ensureDir(dir) {
52
339
  if (!fs.existsSync(dir)) {
53
340
  fs.mkdirSync(dir, { recursive: true });
@@ -62,27 +349,22 @@ function needsRouter(source) {
62
349
  return false;
63
350
  }
64
351
 
65
- /** If component has <img and accepts children prop, we should omit children from story args (void element). */
66
- function componentHasImgAndChildren(source) {
352
+ /** Void HTML elements: img, input, hr, br — must not receive children. If component wraps one and has children in props, omit children in story args. */
353
+ function componentWrapsVoidElement(source) {
67
354
  if (!source || typeof source !== "string") return false;
68
- const hasImg = /return\s+<img|<\s*img\s+/.test(source);
69
- if (!hasImg) return false;
355
+ const hasVoid = /return\s+<(?:img|input|hr|br)\b|<\s*(?:img|input|hr|br)\s+/.test(source);
356
+ if (!hasVoid) return false;
70
357
  return /\bchildren\b/.test(source);
71
358
  }
72
359
 
73
360
  function toSafeComponentName(name, file) {
74
- if (!name || typeof name !== "string") {
75
- const base = (file || "").replace(/\.[^.]+$/, "");
76
- const parts = base.split(/[\\/]/g);
77
- const last = parts[parts.length - 1] || "Component";
78
- return last.charAt(0).toUpperCase() + last.slice(1);
361
+ if (name && typeof name === "string") {
362
+ return name.replace(/[^A-Za-z0-9]+/g, " ").trim().replace(/\s+([a-z])/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase()).replace(/\s+/g, "");
79
363
  }
80
- return name
81
- .replace(/[^A-Za-z0-9]+/g, " ")
82
- .trim()
83
- .replace(/\s+([a-z])/g, (_, c) => c.toUpperCase())
84
- .replace(/^\w/, (c) => c.toUpperCase())
85
- .replace(/\s+/g, "");
364
+ const base = (file || "").replace(/\.[^.]+$/, "");
365
+ const parts = base.split(/[\\/]/g);
366
+ const last = parts[parts.length - 1] || "Component";
367
+ return last.charAt(0).toUpperCase() + last.slice(1);
86
368
  }
87
369
 
88
370
  function parseUnionLiterals(type) {
@@ -174,67 +456,6 @@ function buildSpecialStories(componentName, variants) {
174
456
  return lines.join("\n");
175
457
  }
176
458
 
177
- if (componentName === "Checkbox") {
178
- lines.push(`export const Default: Story = {`);
179
- lines.push(` render: (args) => (`);
180
- lines.push(` <div className="flex items-center space-x-2">`);
181
- lines.push(` <ComponentRef id="terms" {...args} />`);
182
- lines.push(` <Label htmlFor="terms">Accept terms</Label>`);
183
- lines.push(` </div>`);
184
- lines.push(` ),`);
185
- lines.push(`};`);
186
- return lines.join("\n");
187
- }
188
-
189
- if (componentName === "Switch") {
190
- lines.push(`export const Default: Story = {`);
191
- lines.push(` render: (args) => (`);
192
- lines.push(` <div className="flex items-center space-x-2">`);
193
- lines.push(` <ComponentRef id="notifications" {...args} />`);
194
- lines.push(` <Label htmlFor="notifications">Enable notifications</Label>`);
195
- lines.push(` </div>`);
196
- lines.push(` ),`);
197
- lines.push(`};`);
198
- return lines.join("\n");
199
- }
200
-
201
- if (componentName === "RadioGroup") {
202
- lines.push(`export const Default: Story = {`);
203
- lines.push(` render: (args) => (`);
204
- lines.push(` <ComponentRef className="flex flex-col space-y-2" {...args}>`);
205
- lines.push(` <div className="flex items-center space-x-2">`);
206
- lines.push(` <RadioGroupItem value="comfortable" id="comfortable" />`);
207
- lines.push(` <Label htmlFor="comfortable">Comfortable</Label>`);
208
- lines.push(` </div>`);
209
- lines.push(` <div className="flex items-center space-x-2">`);
210
- lines.push(` <RadioGroupItem value="compact" id="compact" />`);
211
- lines.push(` <Label htmlFor="compact">Compact</Label>`);
212
- lines.push(` </div>`);
213
- lines.push(` </ComponentRef>`);
214
- lines.push(` ),`);
215
- lines.push(`};`);
216
- return lines.join("\n");
217
- }
218
-
219
- if (componentName === "Select") {
220
- lines.push(`export const Default: Story = {`);
221
- lines.push(` args: { defaultValue: "apple" },`);
222
- lines.push(` render: (args) => (`);
223
- lines.push(` <ComponentRef {...args}>`);
224
- lines.push(` <SelectTrigger className="w-[180px]">`);
225
- lines.push(` <SelectValue placeholder="Select a fruit" />`);
226
- lines.push(` </SelectTrigger>`);
227
- lines.push(` <SelectContent>`);
228
- lines.push(` <SelectItem value="apple">Apple</SelectItem>`);
229
- lines.push(` <SelectItem value="banana">Banana</SelectItem>`);
230
- lines.push(` <SelectItem value="orange">Orange</SelectItem>`);
231
- lines.push(` </SelectContent>`);
232
- lines.push(` </ComponentRef>`);
233
- lines.push(` ),`);
234
- lines.push(`};`);
235
- return lines.join("\n");
236
- }
237
-
238
459
  if (componentName === "Badge") {
239
460
  const vs = variants && variants.length ? variants : ["default", "secondary", "destructive", "outline"];
240
461
  vs.forEach((v, idx) => {
@@ -250,38 +471,56 @@ function buildSpecialStories(componentName, variants) {
250
471
  return lines.join("\n");
251
472
  }
252
473
 
253
- if (componentName === "Card") {
254
- lines.push(`export const Default: Story = {`);
255
- lines.push(` render: (args) => (`);
256
- lines.push(` <ComponentRef className="w-[340px]" {...args}>`);
257
- lines.push(` <CardHeader>`);
258
- lines.push(` <CardTitle>Card title</CardTitle>`);
259
- lines.push(` <CardDescription>Short description about this card.</CardDescription>`);
260
- lines.push(` </CardHeader>`);
261
- lines.push(` <CardContent>`);
262
- lines.push(` <p>Here is some representative content inside the card body.</p>`);
263
- lines.push(` </CardContent>`);
264
- lines.push(` <CardFooter>Footer content</CardFooter>`);
265
- lines.push(` </ComponentRef>`);
266
- lines.push(` ),`);
267
- lines.push(`};`);
268
- return lines.join("\n");
269
- }
474
+ // Fallback: let the generic variant-based logic or RECIPES handle it
475
+ return "";
476
+ }
270
477
 
271
- if (componentName === "Avatar") {
272
- lines.push(`export const Default: Story = {`);
273
- lines.push(` render: (args) => (`);
274
- lines.push(` <ComponentRef {...args}>`);
275
- lines.push(` <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />`);
276
- lines.push(` <AvatarFallback>JD</AvatarFallback>`);
277
- lines.push(` </ComponentRef>`);
278
- lines.push(` ),`);
279
- lines.push(`};`);
280
- return lines.join("\n");
478
+ function buildRecipeStoryContent(comp, componentName, importPath, title, source, exportStyle, recipe) {
479
+ const lines = [];
480
+ lines.push(`import type { Meta, StoryObj } from "@storybook/react";`);
481
+ const useRouterDecorator = needsRouter(source);
482
+ if (exportStyle === "default") {
483
+ lines.push(`import ${componentName} from "${importPath}";`);
484
+ if (recipe.imports?.length) {
485
+ lines.push(`import { ${recipe.imports.join(", ")} } from "${importPath}";`);
486
+ }
487
+ lines.push(`const ComponentRef = ${componentName};`);
488
+ } else if (exportStyle === "named") {
489
+ const names = recipe.imports?.length ? [componentName, ...recipe.imports] : [componentName];
490
+ lines.push(`import { ${names.join(", ")} } from "${importPath}";`);
491
+ lines.push(`const ComponentRef = ${componentName};`);
492
+ } else {
493
+ const defaultAlias = `${componentName}Default`;
494
+ const namedAlias = `${componentName}Named`;
495
+ const extra = recipe.imports?.length ? ", " + recipe.imports.join(", ") : "";
496
+ lines.push(`import ${defaultAlias}, { ${componentName} as ${namedAlias}${extra} } from "${importPath}";`);
497
+ lines.push(`const ComponentRef = ${namedAlias} ?? ${defaultAlias};`);
281
498
  }
282
-
283
- // Fallback: let the generic variant-based logic handle it
284
- return "";
499
+ for (const ext of recipe.extraImports || []) {
500
+ lines.push(`import { ${ext.names.join(", ")} } from "${ext.from}";`);
501
+ }
502
+ if (useRouterDecorator) lines.push(`import { MemoryRouter } from "react-router-dom";`);
503
+ lines.push("");
504
+ lines.push(`const meta = {`);
505
+ lines.push(` title: ${JSON.stringify(title)},`);
506
+ lines.push(` component: ComponentRef,`);
507
+ lines.push(` tags: ["autodocs"],`);
508
+ if (useRouterDecorator) {
509
+ lines.push(` decorators: [(Story) => (`);
510
+ lines.push(` <MemoryRouter>`);
511
+ lines.push(` <Story />`);
512
+ lines.push(` </MemoryRouter>`);
513
+ lines.push(` )],`);
514
+ }
515
+ lines.push(`} satisfies Meta<typeof ComponentRef>;`);
516
+ lines.push("");
517
+ lines.push(`export default meta;`);
518
+ lines.push(`type Story = StoryObj<typeof meta>;`);
519
+ lines.push("");
520
+ lines.push(`export const Default: Story = {`);
521
+ lines.push(` render: ${recipe.render},`);
522
+ lines.push(`};`);
523
+ return lines.join("\n");
285
524
  }
286
525
 
287
526
  function buildStoryFileContent(comp) {
@@ -332,7 +571,11 @@ function buildStoryFileContent(comp) {
332
571
  // ignore
333
572
  }
334
573
  const exportStyle = detectExportStyle(source, componentName);
335
- const omitChildren = componentHasImgAndChildren(source);
574
+ const omitChildren = componentWrapsVoidElement(source);
575
+
576
+ if (RECIPES[componentName]) {
577
+ return buildRecipeStoryContent(comp, componentName, importPath, title, source, exportStyle, RECIPES[componentName]);
578
+ }
336
579
 
337
580
  const lines = [];
338
581
  lines.push(`import type { Meta, StoryObj } from "@storybook/react";`);
@@ -352,26 +595,6 @@ function buildStoryFileContent(comp) {
352
595
  lines.push(`const ComponentRef = ${namedAlias} ?? ${defaultAlias};`);
353
596
  }
354
597
 
355
- // Extra imports for composite components
356
- if (componentName === "Checkbox" || componentName === "Switch" || componentName === "RadioGroup") {
357
- lines.push(`import { Label } from "@/components/ui/label";`);
358
- }
359
- if (componentName === "RadioGroup") {
360
- lines.push(`import { RadioGroupItem } from "@/components/ui/radio-group";`);
361
- }
362
- if (componentName === "Select") {
363
- lines.push(
364
- `import { SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select";`,
365
- );
366
- }
367
- if (componentName === "Avatar") {
368
- lines.push(`import { AvatarImage, AvatarFallback } from "@/components/ui/avatar";`);
369
- }
370
- if (componentName === "Card") {
371
- lines.push(
372
- `import { CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card";`,
373
- );
374
- }
375
598
  const useRouterDecorator = needsRouter(source);
376
599
  if (useRouterDecorator) {
377
600
  lines.push(`import { MemoryRouter } from "react-router-dom";`);
@@ -399,7 +622,7 @@ function buildStoryFileContent(comp) {
399
622
  const specialStories = buildSpecialStories(componentName, variants);
400
623
  if (specialStories) {
401
624
  lines.push(specialStories);
402
- return STORY_CSS_HEADER + lines.join("\n");
625
+ return lines.join("\n");
403
626
  }
404
627
 
405
628
  // Generic variant-based stories (omit children for img/void components)
@@ -430,7 +653,7 @@ function buildStoryFileContent(comp) {
430
653
  }
431
654
  }
432
655
 
433
- return STORY_CSS_HEADER + lines.join("\n");
656
+ return lines.join("\n");
434
657
  }
435
658
 
436
659
  /** Build color entries from foundations.colors (skip _dark; flatten to { name, hex }). */
@@ -453,7 +676,6 @@ function writeFoundationsStories(foundations) {
453
676
 
454
677
  const colorEntries = getColorEntries(foundations?.colors);
455
678
  const colorsContent =
456
- STORY_CSS_HEADER +
457
679
  [
458
680
  "import type { Meta, StoryObj } from \"@storybook/react\";",
459
681
  "",
@@ -487,7 +709,6 @@ function writeFoundationsStories(foundations) {
487
709
  value: Array.isArray(v) ? v.join(", ") : String(v),
488
710
  }));
489
711
  const typoContent =
490
- STORY_CSS_HEADER +
491
712
  [
492
713
  "import type { Meta, StoryObj } from \"@storybook/react\";",
493
714
  "",
@@ -520,7 +741,6 @@ function writeFoundationsStories(foundations) {
520
741
  const brandAssets = foundations?.brand?.assets;
521
742
  const assets = Array.isArray(brandAssets) ? brandAssets : [];
522
743
  const brandContent =
523
- STORY_CSS_HEADER +
524
744
  [
525
745
  "import type { Meta, StoryObj } from \"@storybook/react\";",
526
746
  "",
@@ -597,6 +817,8 @@ function main() {
597
817
  const componentName = toSafeComponentName(comp.name, comp.file);
598
818
  if (onlyName && componentName !== onlyName) continue;
599
819
  if (SKIP_LIST.includes(componentName)) continue;
820
+ const requiredCount = Array.isArray(comp.props) ? comp.props.filter((p) => p.required === true).length : 0;
821
+ if (requiredCount > 3) continue;
600
822
 
601
823
  const storyFileName = `${componentName}.stories.tsx`;
602
824
  const storyPath = path.join(STORIES_DIR, storyFileName);
@@ -607,3 +829,4 @@ function main() {
607
829
  }
608
830
 
609
831
  main();
832
+