vibe-design-system 1.9.3 → 1.9.5
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 +331 -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 = [
|
|
@@ -42,8 +38,270 @@ const SKIP_LIST = [
|
|
|
42
38
|
"ToggleGroup",
|
|
43
39
|
"Sidebar",
|
|
44
40
|
"TestComponent",
|
|
41
|
+
"Chart",
|
|
42
|
+
"InputOtp",
|
|
43
|
+
"Resizable",
|
|
44
|
+
"Sonner",
|
|
45
45
|
];
|
|
46
46
|
|
|
47
|
+
/** shadcn/ui composite component recipes: component name → imports + render. */
|
|
48
|
+
const RECIPES = {
|
|
49
|
+
Accordion: {
|
|
50
|
+
imports: ["AccordionItem", "AccordionTrigger", "AccordionContent"],
|
|
51
|
+
render: `(args) => (
|
|
52
|
+
<ComponentRef type="single" collapsible {...args}>
|
|
53
|
+
<AccordionItem value="item-1">
|
|
54
|
+
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
|
55
|
+
<AccordionContent>Yes. It adheres to the WAI-ARIA design pattern.</AccordionContent>
|
|
56
|
+
</AccordionItem>
|
|
57
|
+
<AccordionItem value="item-2">
|
|
58
|
+
<AccordionTrigger>Is it styled?</AccordionTrigger>
|
|
59
|
+
<AccordionContent>Yes. It comes with default styles.</AccordionContent>
|
|
60
|
+
</AccordionItem>
|
|
61
|
+
</ComponentRef>
|
|
62
|
+
)`,
|
|
63
|
+
},
|
|
64
|
+
Dialog: {
|
|
65
|
+
imports: ["DialogTrigger", "DialogContent", "DialogHeader", "DialogTitle", "DialogDescription"],
|
|
66
|
+
render: `(args) => (
|
|
67
|
+
<ComponentRef {...args}>
|
|
68
|
+
<DialogTrigger asChild><button>Open Dialog</button></DialogTrigger>
|
|
69
|
+
<DialogContent>
|
|
70
|
+
<DialogHeader>
|
|
71
|
+
<DialogTitle>Dialog Title</DialogTitle>
|
|
72
|
+
<DialogDescription>This is a dialog description.</DialogDescription>
|
|
73
|
+
</DialogHeader>
|
|
74
|
+
</DialogContent>
|
|
75
|
+
</ComponentRef>
|
|
76
|
+
)`,
|
|
77
|
+
},
|
|
78
|
+
AlertDialog: {
|
|
79
|
+
imports: ["AlertDialogTrigger", "AlertDialogContent", "AlertDialogHeader", "AlertDialogTitle", "AlertDialogDescription", "AlertDialogFooter", "AlertDialogCancel", "AlertDialogAction"],
|
|
80
|
+
render: `(args) => (
|
|
81
|
+
<ComponentRef {...args}>
|
|
82
|
+
<AlertDialogTrigger asChild><button>Open</button></AlertDialogTrigger>
|
|
83
|
+
<AlertDialogContent>
|
|
84
|
+
<AlertDialogHeader>
|
|
85
|
+
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
|
86
|
+
<AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
|
|
87
|
+
</AlertDialogHeader>
|
|
88
|
+
<AlertDialogFooter>
|
|
89
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
90
|
+
<AlertDialogAction>Continue</AlertDialogAction>
|
|
91
|
+
</AlertDialogFooter>
|
|
92
|
+
</AlertDialogContent>
|
|
93
|
+
</ComponentRef>
|
|
94
|
+
)`,
|
|
95
|
+
},
|
|
96
|
+
Alert: {
|
|
97
|
+
imports: ["AlertTitle", "AlertDescription"],
|
|
98
|
+
render: `(args) => (
|
|
99
|
+
<ComponentRef {...args}>
|
|
100
|
+
<AlertTitle>Heads up!</AlertTitle>
|
|
101
|
+
<AlertDescription>You can add components to your app using the CLI.</AlertDescription>
|
|
102
|
+
</ComponentRef>
|
|
103
|
+
)`,
|
|
104
|
+
},
|
|
105
|
+
Tooltip: {
|
|
106
|
+
imports: ["TooltipProvider", "TooltipTrigger", "TooltipContent"],
|
|
107
|
+
render: `(args) => (
|
|
108
|
+
<TooltipProvider>
|
|
109
|
+
<ComponentRef {...args}>
|
|
110
|
+
<TooltipTrigger asChild><button>Hover me</button></TooltipTrigger>
|
|
111
|
+
<TooltipContent><p>Tooltip content</p></TooltipContent>
|
|
112
|
+
</ComponentRef>
|
|
113
|
+
</TooltipProvider>
|
|
114
|
+
)`,
|
|
115
|
+
},
|
|
116
|
+
Popover: {
|
|
117
|
+
imports: ["PopoverTrigger", "PopoverContent"],
|
|
118
|
+
render: `(args) => (
|
|
119
|
+
<ComponentRef {...args}>
|
|
120
|
+
<PopoverTrigger asChild><button>Open Popover</button></PopoverTrigger>
|
|
121
|
+
<PopoverContent>Place content here.</PopoverContent>
|
|
122
|
+
</ComponentRef>
|
|
123
|
+
)`,
|
|
124
|
+
},
|
|
125
|
+
HoverCard: {
|
|
126
|
+
imports: ["HoverCardTrigger", "HoverCardContent"],
|
|
127
|
+
render: `(args) => (
|
|
128
|
+
<ComponentRef {...args}>
|
|
129
|
+
<HoverCardTrigger asChild><button>Hover</button></HoverCardTrigger>
|
|
130
|
+
<HoverCardContent>Card content on hover.</HoverCardContent>
|
|
131
|
+
</ComponentRef>
|
|
132
|
+
)`,
|
|
133
|
+
},
|
|
134
|
+
Tabs: {
|
|
135
|
+
imports: ["TabsList", "TabsTrigger", "TabsContent"],
|
|
136
|
+
render: `(args) => (
|
|
137
|
+
<ComponentRef defaultValue="tab1" {...args}>
|
|
138
|
+
<TabsList>
|
|
139
|
+
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
140
|
+
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
141
|
+
</TabsList>
|
|
142
|
+
<TabsContent value="tab1">Content for tab 1</TabsContent>
|
|
143
|
+
<TabsContent value="tab2">Content for tab 2</TabsContent>
|
|
144
|
+
</ComponentRef>
|
|
145
|
+
)`,
|
|
146
|
+
},
|
|
147
|
+
Table: {
|
|
148
|
+
imports: ["TableHeader", "TableBody", "TableRow", "TableHead", "TableCell"],
|
|
149
|
+
render: `(args) => (
|
|
150
|
+
<ComponentRef {...args}>
|
|
151
|
+
<TableHeader><TableRow><TableHead>Name</TableHead><TableHead>Status</TableHead></TableRow></TableHeader>
|
|
152
|
+
<TableBody><TableRow><TableCell>Item 1</TableCell><TableCell>Active</TableCell></TableRow></TableBody>
|
|
153
|
+
</ComponentRef>
|
|
154
|
+
)`,
|
|
155
|
+
},
|
|
156
|
+
DropdownMenu: {
|
|
157
|
+
imports: ["DropdownMenuTrigger", "DropdownMenuContent", "DropdownMenuItem"],
|
|
158
|
+
render: `(args) => (
|
|
159
|
+
<ComponentRef {...args}>
|
|
160
|
+
<DropdownMenuTrigger asChild><button>Open Menu</button></DropdownMenuTrigger>
|
|
161
|
+
<DropdownMenuContent>
|
|
162
|
+
<DropdownMenuItem>Profile</DropdownMenuItem>
|
|
163
|
+
<DropdownMenuItem>Settings</DropdownMenuItem>
|
|
164
|
+
<DropdownMenuItem>Logout</DropdownMenuItem>
|
|
165
|
+
</DropdownMenuContent>
|
|
166
|
+
</ComponentRef>
|
|
167
|
+
)`,
|
|
168
|
+
},
|
|
169
|
+
ContextMenu: {
|
|
170
|
+
imports: ["ContextMenuTrigger", "ContextMenuContent", "ContextMenuItem"],
|
|
171
|
+
render: `(args) => (
|
|
172
|
+
<ComponentRef {...args}>
|
|
173
|
+
<ContextMenuTrigger><div style={{padding: 40, border: '1px dashed #ccc'}}>Right click here</div></ContextMenuTrigger>
|
|
174
|
+
<ContextMenuContent>
|
|
175
|
+
<ContextMenuItem>Back</ContextMenuItem>
|
|
176
|
+
<ContextMenuItem>Forward</ContextMenuItem>
|
|
177
|
+
<ContextMenuItem>Reload</ContextMenuItem>
|
|
178
|
+
</ContextMenuContent>
|
|
179
|
+
</ComponentRef>
|
|
180
|
+
)`,
|
|
181
|
+
},
|
|
182
|
+
Select: {
|
|
183
|
+
imports: ["SelectTrigger", "SelectValue", "SelectContent", "SelectItem"],
|
|
184
|
+
render: `(args) => (
|
|
185
|
+
<ComponentRef {...args}>
|
|
186
|
+
<SelectTrigger className="w-[180px]"><SelectValue placeholder="Select a fruit" /></SelectTrigger>
|
|
187
|
+
<SelectContent>
|
|
188
|
+
<SelectItem value="apple">Apple</SelectItem>
|
|
189
|
+
<SelectItem value="banana">Banana</SelectItem>
|
|
190
|
+
<SelectItem value="orange">Orange</SelectItem>
|
|
191
|
+
</SelectContent>
|
|
192
|
+
</ComponentRef>
|
|
193
|
+
)`,
|
|
194
|
+
},
|
|
195
|
+
Card: {
|
|
196
|
+
imports: ["CardHeader", "CardTitle", "CardDescription", "CardContent", "CardFooter"],
|
|
197
|
+
render: `(args) => (
|
|
198
|
+
<ComponentRef className="w-[340px]" {...args}>
|
|
199
|
+
<CardHeader>
|
|
200
|
+
<CardTitle>Card title</CardTitle>
|
|
201
|
+
<CardDescription>Short description.</CardDescription>
|
|
202
|
+
</CardHeader>
|
|
203
|
+
<CardContent><p>Card body content here.</p></CardContent>
|
|
204
|
+
<CardFooter>Footer</CardFooter>
|
|
205
|
+
</ComponentRef>
|
|
206
|
+
)`,
|
|
207
|
+
},
|
|
208
|
+
Avatar: {
|
|
209
|
+
imports: ["AvatarImage", "AvatarFallback"],
|
|
210
|
+
render: `(args) => (
|
|
211
|
+
<ComponentRef {...args}>
|
|
212
|
+
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
|
213
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
214
|
+
</ComponentRef>
|
|
215
|
+
)`,
|
|
216
|
+
},
|
|
217
|
+
Checkbox: {
|
|
218
|
+
extraImports: [{ from: "@/components/ui/label", names: ["Label"] }],
|
|
219
|
+
render: `(args) => (
|
|
220
|
+
<div className="flex items-center space-x-2">
|
|
221
|
+
<ComponentRef id="terms" {...args} />
|
|
222
|
+
<Label htmlFor="terms">Accept terms</Label>
|
|
223
|
+
</div>
|
|
224
|
+
)`,
|
|
225
|
+
},
|
|
226
|
+
Switch: {
|
|
227
|
+
extraImports: [{ from: "@/components/ui/label", names: ["Label"] }],
|
|
228
|
+
render: `(args) => (
|
|
229
|
+
<div className="flex items-center space-x-2">
|
|
230
|
+
<ComponentRef id="switch" {...args} />
|
|
231
|
+
<Label htmlFor="switch">Enable notifications</Label>
|
|
232
|
+
</div>
|
|
233
|
+
)`,
|
|
234
|
+
},
|
|
235
|
+
RadioGroup: {
|
|
236
|
+
imports: ["RadioGroupItem"],
|
|
237
|
+
extraImports: [{ from: "@/components/ui/label", names: ["Label"] }],
|
|
238
|
+
render: `(args) => (
|
|
239
|
+
<ComponentRef className="flex flex-col space-y-2" {...args}>
|
|
240
|
+
<div className="flex items-center space-x-2">
|
|
241
|
+
<RadioGroupItem value="comfortable" id="comfortable" />
|
|
242
|
+
<Label htmlFor="comfortable">Comfortable</Label>
|
|
243
|
+
</div>
|
|
244
|
+
<div className="flex items-center space-x-2">
|
|
245
|
+
<RadioGroupItem value="compact" id="compact" />
|
|
246
|
+
<Label htmlFor="compact">Compact</Label>
|
|
247
|
+
</div>
|
|
248
|
+
</ComponentRef>
|
|
249
|
+
)`,
|
|
250
|
+
},
|
|
251
|
+
Sheet: {
|
|
252
|
+
imports: ["SheetTrigger", "SheetContent", "SheetHeader", "SheetTitle", "SheetDescription"],
|
|
253
|
+
render: `(args) => (
|
|
254
|
+
<ComponentRef {...args}>
|
|
255
|
+
<SheetTrigger asChild><button>Open Sheet</button></SheetTrigger>
|
|
256
|
+
<SheetContent>
|
|
257
|
+
<SheetHeader>
|
|
258
|
+
<SheetTitle>Sheet Title</SheetTitle>
|
|
259
|
+
<SheetDescription>Sheet description here.</SheetDescription>
|
|
260
|
+
</SheetHeader>
|
|
261
|
+
</SheetContent>
|
|
262
|
+
</ComponentRef>
|
|
263
|
+
)`,
|
|
264
|
+
},
|
|
265
|
+
Drawer: {
|
|
266
|
+
imports: ["DrawerTrigger", "DrawerContent", "DrawerHeader", "DrawerTitle", "DrawerDescription"],
|
|
267
|
+
render: `(args) => (
|
|
268
|
+
<ComponentRef {...args}>
|
|
269
|
+
<DrawerTrigger asChild><button>Open Drawer</button></DrawerTrigger>
|
|
270
|
+
<DrawerContent>
|
|
271
|
+
<DrawerHeader>
|
|
272
|
+
<DrawerTitle>Drawer Title</DrawerTitle>
|
|
273
|
+
<DrawerDescription>Drawer description here.</DrawerDescription>
|
|
274
|
+
</DrawerHeader>
|
|
275
|
+
</DrawerContent>
|
|
276
|
+
</ComponentRef>
|
|
277
|
+
)`,
|
|
278
|
+
},
|
|
279
|
+
Collapsible: {
|
|
280
|
+
imports: ["CollapsibleTrigger", "CollapsibleContent"],
|
|
281
|
+
render: `(args) => (
|
|
282
|
+
<ComponentRef {...args}>
|
|
283
|
+
<CollapsibleTrigger asChild><button>Toggle</button></CollapsibleTrigger>
|
|
284
|
+
<CollapsibleContent>Collapsible content here.</CollapsibleContent>
|
|
285
|
+
</ComponentRef>
|
|
286
|
+
)`,
|
|
287
|
+
},
|
|
288
|
+
Menubar: {
|
|
289
|
+
imports: ["MenubarMenu", "MenubarTrigger", "MenubarContent", "MenubarItem"],
|
|
290
|
+
render: `(args) => (
|
|
291
|
+
<ComponentRef {...args}>
|
|
292
|
+
<MenubarMenu>
|
|
293
|
+
<MenubarTrigger>File</MenubarTrigger>
|
|
294
|
+
<MenubarContent>
|
|
295
|
+
<MenubarItem>New</MenubarItem>
|
|
296
|
+
<MenubarItem>Open</MenubarItem>
|
|
297
|
+
<MenubarItem>Save</MenubarItem>
|
|
298
|
+
</MenubarContent>
|
|
299
|
+
</MenubarMenu>
|
|
300
|
+
</ComponentRef>
|
|
301
|
+
)`,
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
47
305
|
function ensureDir(dir) {
|
|
48
306
|
if (!fs.existsSync(dir)) {
|
|
49
307
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -58,27 +316,22 @@ function needsRouter(source) {
|
|
|
58
316
|
return false;
|
|
59
317
|
}
|
|
60
318
|
|
|
61
|
-
/** If component
|
|
62
|
-
function
|
|
319
|
+
/** 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. */
|
|
320
|
+
function componentWrapsVoidElement(source) {
|
|
63
321
|
if (!source || typeof source !== "string") return false;
|
|
64
|
-
const
|
|
65
|
-
if (!
|
|
322
|
+
const hasVoid = /return\s+<(?:img|input|hr|br)\b|<\s*(?:img|input|hr|br)\s+/.test(source);
|
|
323
|
+
if (!hasVoid) return false;
|
|
66
324
|
return /\bchildren\b/.test(source);
|
|
67
325
|
}
|
|
68
326
|
|
|
69
327
|
function toSafeComponentName(name, file) {
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
const parts = base.split(/[\\/]/g);
|
|
73
|
-
const last = parts[parts.length - 1] || "Component";
|
|
74
|
-
return last.charAt(0).toUpperCase() + last.slice(1);
|
|
328
|
+
if (name && typeof name === "string") {
|
|
329
|
+
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, "");
|
|
75
330
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.replace(/^\w/, (c) => c.toUpperCase())
|
|
81
|
-
.replace(/\s+/g, "");
|
|
331
|
+
const base = (file || "").replace(/\.[^.]+$/, "");
|
|
332
|
+
const parts = base.split(/[\\/]/g);
|
|
333
|
+
const last = parts[parts.length - 1] || "Component";
|
|
334
|
+
return last.charAt(0).toUpperCase() + last.slice(1);
|
|
82
335
|
}
|
|
83
336
|
|
|
84
337
|
function parseUnionLiterals(type) {
|
|
@@ -170,67 +423,6 @@ function buildSpecialStories(componentName, variants) {
|
|
|
170
423
|
return lines.join("\n");
|
|
171
424
|
}
|
|
172
425
|
|
|
173
|
-
if (componentName === "Checkbox") {
|
|
174
|
-
lines.push(`export const Default: Story = {`);
|
|
175
|
-
lines.push(` render: (args) => (`);
|
|
176
|
-
lines.push(` <div className="flex items-center space-x-2">`);
|
|
177
|
-
lines.push(` <ComponentRef id="terms" {...args} />`);
|
|
178
|
-
lines.push(` <Label htmlFor="terms">Accept terms</Label>`);
|
|
179
|
-
lines.push(` </div>`);
|
|
180
|
-
lines.push(` ),`);
|
|
181
|
-
lines.push(`};`);
|
|
182
|
-
return lines.join("\n");
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (componentName === "Switch") {
|
|
186
|
-
lines.push(`export const Default: Story = {`);
|
|
187
|
-
lines.push(` render: (args) => (`);
|
|
188
|
-
lines.push(` <div className="flex items-center space-x-2">`);
|
|
189
|
-
lines.push(` <ComponentRef id="notifications" {...args} />`);
|
|
190
|
-
lines.push(` <Label htmlFor="notifications">Enable notifications</Label>`);
|
|
191
|
-
lines.push(` </div>`);
|
|
192
|
-
lines.push(` ),`);
|
|
193
|
-
lines.push(`};`);
|
|
194
|
-
return lines.join("\n");
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (componentName === "RadioGroup") {
|
|
198
|
-
lines.push(`export const Default: Story = {`);
|
|
199
|
-
lines.push(` render: (args) => (`);
|
|
200
|
-
lines.push(` <ComponentRef className="flex flex-col space-y-2" {...args}>`);
|
|
201
|
-
lines.push(` <div className="flex items-center space-x-2">`);
|
|
202
|
-
lines.push(` <RadioGroupItem value="comfortable" id="comfortable" />`);
|
|
203
|
-
lines.push(` <Label htmlFor="comfortable">Comfortable</Label>`);
|
|
204
|
-
lines.push(` </div>`);
|
|
205
|
-
lines.push(` <div className="flex items-center space-x-2">`);
|
|
206
|
-
lines.push(` <RadioGroupItem value="compact" id="compact" />`);
|
|
207
|
-
lines.push(` <Label htmlFor="compact">Compact</Label>`);
|
|
208
|
-
lines.push(` </div>`);
|
|
209
|
-
lines.push(` </ComponentRef>`);
|
|
210
|
-
lines.push(` ),`);
|
|
211
|
-
lines.push(`};`);
|
|
212
|
-
return lines.join("\n");
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (componentName === "Select") {
|
|
216
|
-
lines.push(`export const Default: Story = {`);
|
|
217
|
-
lines.push(` args: { defaultValue: "apple" },`);
|
|
218
|
-
lines.push(` render: (args) => (`);
|
|
219
|
-
lines.push(` <ComponentRef {...args}>`);
|
|
220
|
-
lines.push(` <SelectTrigger className="w-[180px]">`);
|
|
221
|
-
lines.push(` <SelectValue placeholder="Select a fruit" />`);
|
|
222
|
-
lines.push(` </SelectTrigger>`);
|
|
223
|
-
lines.push(` <SelectContent>`);
|
|
224
|
-
lines.push(` <SelectItem value="apple">Apple</SelectItem>`);
|
|
225
|
-
lines.push(` <SelectItem value="banana">Banana</SelectItem>`);
|
|
226
|
-
lines.push(` <SelectItem value="orange">Orange</SelectItem>`);
|
|
227
|
-
lines.push(` </SelectContent>`);
|
|
228
|
-
lines.push(` </ComponentRef>`);
|
|
229
|
-
lines.push(` ),`);
|
|
230
|
-
lines.push(`};`);
|
|
231
|
-
return lines.join("\n");
|
|
232
|
-
}
|
|
233
|
-
|
|
234
426
|
if (componentName === "Badge") {
|
|
235
427
|
const vs = variants && variants.length ? variants : ["default", "secondary", "destructive", "outline"];
|
|
236
428
|
vs.forEach((v, idx) => {
|
|
@@ -246,38 +438,56 @@ function buildSpecialStories(componentName, variants) {
|
|
|
246
438
|
return lines.join("\n");
|
|
247
439
|
}
|
|
248
440
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
lines.push(` <ComponentRef className="w-[340px]" {...args}>`);
|
|
253
|
-
lines.push(` <CardHeader>`);
|
|
254
|
-
lines.push(` <CardTitle>Card title</CardTitle>`);
|
|
255
|
-
lines.push(` <CardDescription>Short description about this card.</CardDescription>`);
|
|
256
|
-
lines.push(` </CardHeader>`);
|
|
257
|
-
lines.push(` <CardContent>`);
|
|
258
|
-
lines.push(` <p>Here is some representative content inside the card body.</p>`);
|
|
259
|
-
lines.push(` </CardContent>`);
|
|
260
|
-
lines.push(` <CardFooter>Footer content</CardFooter>`);
|
|
261
|
-
lines.push(` </ComponentRef>`);
|
|
262
|
-
lines.push(` ),`);
|
|
263
|
-
lines.push(`};`);
|
|
264
|
-
return lines.join("\n");
|
|
265
|
-
}
|
|
441
|
+
// Fallback: let the generic variant-based logic or RECIPES handle it
|
|
442
|
+
return "";
|
|
443
|
+
}
|
|
266
444
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
lines.push(`
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
445
|
+
function buildRecipeStoryContent(comp, componentName, importPath, title, source, exportStyle, recipe) {
|
|
446
|
+
const lines = [];
|
|
447
|
+
lines.push(`import type { Meta, StoryObj } from "@storybook/react";`);
|
|
448
|
+
const useRouterDecorator = needsRouter(source);
|
|
449
|
+
if (exportStyle === "default") {
|
|
450
|
+
lines.push(`import ${componentName} from "${importPath}";`);
|
|
451
|
+
if (recipe.imports?.length) {
|
|
452
|
+
lines.push(`import { ${recipe.imports.join(", ")} } from "${importPath}";`);
|
|
453
|
+
}
|
|
454
|
+
lines.push(`const ComponentRef = ${componentName};`);
|
|
455
|
+
} else if (exportStyle === "named") {
|
|
456
|
+
const names = recipe.imports?.length ? [componentName, ...recipe.imports] : [componentName];
|
|
457
|
+
lines.push(`import { ${names.join(", ")} } from "${importPath}";`);
|
|
458
|
+
lines.push(`const ComponentRef = ${componentName};`);
|
|
459
|
+
} else {
|
|
460
|
+
const defaultAlias = `${componentName}Default`;
|
|
461
|
+
const namedAlias = `${componentName}Named`;
|
|
462
|
+
const extra = recipe.imports?.length ? ", " + recipe.imports.join(", ") : "";
|
|
463
|
+
lines.push(`import ${defaultAlias}, { ${componentName} as ${namedAlias}${extra} } from "${importPath}";`);
|
|
464
|
+
lines.push(`const ComponentRef = ${namedAlias} ?? ${defaultAlias};`);
|
|
277
465
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
466
|
+
for (const ext of recipe.extraImports || []) {
|
|
467
|
+
lines.push(`import { ${ext.names.join(", ")} } from "${ext.from}";`);
|
|
468
|
+
}
|
|
469
|
+
if (useRouterDecorator) lines.push(`import { MemoryRouter } from "react-router-dom";`);
|
|
470
|
+
lines.push("");
|
|
471
|
+
lines.push(`const meta = {`);
|
|
472
|
+
lines.push(` title: ${JSON.stringify(title)},`);
|
|
473
|
+
lines.push(` component: ComponentRef,`);
|
|
474
|
+
lines.push(` tags: ["autodocs"],`);
|
|
475
|
+
if (useRouterDecorator) {
|
|
476
|
+
lines.push(` decorators: [(Story) => (`);
|
|
477
|
+
lines.push(` <MemoryRouter>`);
|
|
478
|
+
lines.push(` <Story />`);
|
|
479
|
+
lines.push(` </MemoryRouter>`);
|
|
480
|
+
lines.push(` )],`);
|
|
481
|
+
}
|
|
482
|
+
lines.push(`} satisfies Meta<typeof ComponentRef>;`);
|
|
483
|
+
lines.push("");
|
|
484
|
+
lines.push(`export default meta;`);
|
|
485
|
+
lines.push(`type Story = StoryObj<typeof meta>;`);
|
|
486
|
+
lines.push("");
|
|
487
|
+
lines.push(`export const Default: Story = {`);
|
|
488
|
+
lines.push(` render: ${recipe.render},`);
|
|
489
|
+
lines.push(`};`);
|
|
490
|
+
return lines.join("\n");
|
|
281
491
|
}
|
|
282
492
|
|
|
283
493
|
function buildStoryFileContent(comp) {
|
|
@@ -328,7 +538,11 @@ function buildStoryFileContent(comp) {
|
|
|
328
538
|
// ignore
|
|
329
539
|
}
|
|
330
540
|
const exportStyle = detectExportStyle(source, componentName);
|
|
331
|
-
const omitChildren =
|
|
541
|
+
const omitChildren = componentWrapsVoidElement(source);
|
|
542
|
+
|
|
543
|
+
if (RECIPES[componentName]) {
|
|
544
|
+
return buildRecipeStoryContent(comp, componentName, importPath, title, source, exportStyle, RECIPES[componentName]);
|
|
545
|
+
}
|
|
332
546
|
|
|
333
547
|
const lines = [];
|
|
334
548
|
lines.push(`import type { Meta, StoryObj } from "@storybook/react";`);
|
|
@@ -348,26 +562,6 @@ function buildStoryFileContent(comp) {
|
|
|
348
562
|
lines.push(`const ComponentRef = ${namedAlias} ?? ${defaultAlias};`);
|
|
349
563
|
}
|
|
350
564
|
|
|
351
|
-
// Extra imports for composite components
|
|
352
|
-
if (componentName === "Checkbox" || componentName === "Switch" || componentName === "RadioGroup") {
|
|
353
|
-
lines.push(`import { Label } from "@/components/ui/label";`);
|
|
354
|
-
}
|
|
355
|
-
if (componentName === "RadioGroup") {
|
|
356
|
-
lines.push(`import { RadioGroupItem } from "@/components/ui/radio-group";`);
|
|
357
|
-
}
|
|
358
|
-
if (componentName === "Select") {
|
|
359
|
-
lines.push(
|
|
360
|
-
`import { SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select";`,
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
if (componentName === "Avatar") {
|
|
364
|
-
lines.push(`import { AvatarImage, AvatarFallback } from "@/components/ui/avatar";`);
|
|
365
|
-
}
|
|
366
|
-
if (componentName === "Card") {
|
|
367
|
-
lines.push(
|
|
368
|
-
`import { CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card";`,
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
565
|
const useRouterDecorator = needsRouter(source);
|
|
372
566
|
if (useRouterDecorator) {
|
|
373
567
|
lines.push(`import { MemoryRouter } from "react-router-dom";`);
|
|
@@ -395,7 +589,7 @@ function buildStoryFileContent(comp) {
|
|
|
395
589
|
const specialStories = buildSpecialStories(componentName, variants);
|
|
396
590
|
if (specialStories) {
|
|
397
591
|
lines.push(specialStories);
|
|
398
|
-
return
|
|
592
|
+
return lines.join("\n");
|
|
399
593
|
}
|
|
400
594
|
|
|
401
595
|
// Generic variant-based stories (omit children for img/void components)
|
|
@@ -426,7 +620,7 @@ function buildStoryFileContent(comp) {
|
|
|
426
620
|
}
|
|
427
621
|
}
|
|
428
622
|
|
|
429
|
-
return
|
|
623
|
+
return lines.join("\n");
|
|
430
624
|
}
|
|
431
625
|
|
|
432
626
|
/** Build color entries from foundations.colors (skip _dark; flatten to { name, hex }). */
|
|
@@ -449,7 +643,6 @@ function writeFoundationsStories(foundations) {
|
|
|
449
643
|
|
|
450
644
|
const colorEntries = getColorEntries(foundations?.colors);
|
|
451
645
|
const colorsContent =
|
|
452
|
-
STORY_CSS_HEADER +
|
|
453
646
|
[
|
|
454
647
|
"import type { Meta, StoryObj } from \"@storybook/react\";",
|
|
455
648
|
"",
|
|
@@ -483,7 +676,6 @@ function writeFoundationsStories(foundations) {
|
|
|
483
676
|
value: Array.isArray(v) ? v.join(", ") : String(v),
|
|
484
677
|
}));
|
|
485
678
|
const typoContent =
|
|
486
|
-
STORY_CSS_HEADER +
|
|
487
679
|
[
|
|
488
680
|
"import type { Meta, StoryObj } from \"@storybook/react\";",
|
|
489
681
|
"",
|
|
@@ -516,7 +708,6 @@ function writeFoundationsStories(foundations) {
|
|
|
516
708
|
const brandAssets = foundations?.brand?.assets;
|
|
517
709
|
const assets = Array.isArray(brandAssets) ? brandAssets : [];
|
|
518
710
|
const brandContent =
|
|
519
|
-
STORY_CSS_HEADER +
|
|
520
711
|
[
|
|
521
712
|
"import type { Meta, StoryObj } from \"@storybook/react\";",
|
|
522
713
|
"",
|
|
@@ -593,6 +784,8 @@ function main() {
|
|
|
593
784
|
const componentName = toSafeComponentName(comp.name, comp.file);
|
|
594
785
|
if (onlyName && componentName !== onlyName) continue;
|
|
595
786
|
if (SKIP_LIST.includes(componentName)) continue;
|
|
787
|
+
const requiredCount = Array.isArray(comp.props) ? comp.props.filter((p) => p.required === true).length : 0;
|
|
788
|
+
if (requiredCount > 3) continue;
|
|
596
789
|
|
|
597
790
|
const storyFileName = `${componentName}.stories.tsx`;
|
|
598
791
|
const storyPath = path.join(STORIES_DIR, storyFileName);
|
|
@@ -603,3 +796,4 @@ function main() {
|
|
|
603
796
|
}
|
|
604
797
|
|
|
605
798
|
main();
|
|
799
|
+
|