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 +1 -1
- package/vds-core-template/story-generator.mjs +360 -137
package/package.json
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
66
|
-
function
|
|
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
|
|
69
|
-
if (!
|
|
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 (
|
|
75
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
lines.push(`
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
284
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
+
|