@xbg.solutions/create-frontend 1.1.1
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/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -0
- package/package.json +21 -0
- package/src/generate-component.cjs +602 -0
- package/src/generate-route.cjs +774 -0
- package/src/generate-service.cjs +1306 -0
- package/src/index.ts +2 -0
- package/src/manage-auth-users.cjs +410 -0
- package/src/setup.cjs +1049 -0
- package/src/validate-setup.cjs +341 -0
- package/tsconfig.json +20 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/lib/index.js
ADDED
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,+DAA+D;AAC/D,sCAAsC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xbg.solutions/create-frontend",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Setup wizard and generators for XBG frontend projects",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"build:watch": "tsc --watch",
|
|
9
|
+
"clean": "rm -rf lib"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"chalk": "^4.1.2"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.11.0",
|
|
19
|
+
"typescript": "^5.8.2"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Component Generator
|
|
5
|
+
*
|
|
6
|
+
* Generates a new Svelte component with TypeScript support, tests, and documentation.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node __scripts__/generate-component.js <ComponentName> [options]
|
|
10
|
+
* npm run generate:component <ComponentName> [options]
|
|
11
|
+
*
|
|
12
|
+
* Options:
|
|
13
|
+
* --path <path> Custom path (default: src/lib/components/ui)
|
|
14
|
+
* --type <type> Component type: ui|page|layout|feature (default: ui)
|
|
15
|
+
* --with-test Generate test file
|
|
16
|
+
* --with-story Generate Storybook story
|
|
17
|
+
* --with-docs Generate documentation
|
|
18
|
+
*
|
|
19
|
+
* Examples:
|
|
20
|
+
* node __scripts__/generate-component.js UserProfile --type=feature --with-test
|
|
21
|
+
* node __scripts__/generate-component.js Button --path=src/lib/components/custom
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
// Parse command line arguments
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const componentName = args[0];
|
|
30
|
+
|
|
31
|
+
if (!componentName) {
|
|
32
|
+
console.error('โ Component name is required!');
|
|
33
|
+
console.log('Usage: node __scripts__/generate-component.js <ComponentName> [options]');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Parse options
|
|
38
|
+
const options = {
|
|
39
|
+
path: 'src/lib/components/ui',
|
|
40
|
+
type: 'ui',
|
|
41
|
+
withTest: false,
|
|
42
|
+
withStory: false,
|
|
43
|
+
withDocs: false
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
args.slice(1).forEach(arg => {
|
|
47
|
+
if (arg.startsWith('--path=')) {
|
|
48
|
+
options.path = arg.split('=')[1];
|
|
49
|
+
} else if (arg.startsWith('--type=')) {
|
|
50
|
+
options.type = arg.split('=')[1];
|
|
51
|
+
} else if (arg === '--with-test') {
|
|
52
|
+
options.withTest = true;
|
|
53
|
+
} else if (arg === '--with-story') {
|
|
54
|
+
options.withStory = true;
|
|
55
|
+
} else if (arg === '--with-docs') {
|
|
56
|
+
options.withDocs = true;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Validate component name
|
|
61
|
+
if (!/^[A-Z][a-zA-Z0-9]*$/.test(componentName)) {
|
|
62
|
+
console.error('โ Component name must be PascalCase (e.g., UserProfile, DataTable)');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Generate file names
|
|
67
|
+
const kebabName = componentName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
68
|
+
const componentDir = path.join(options.path, kebabName);
|
|
69
|
+
const componentFile = path.join(componentDir, `${componentName}.svelte`);
|
|
70
|
+
const indexFile = path.join(componentDir, 'index.ts');
|
|
71
|
+
const testFile = path.join(componentDir, `${componentName}.test.ts`);
|
|
72
|
+
const storyFile = path.join(componentDir, `${componentName}.stories.ts`);
|
|
73
|
+
const docFile = path.join(componentDir, `${componentName}.md`);
|
|
74
|
+
|
|
75
|
+
// Create directory
|
|
76
|
+
if (!fs.existsSync(componentDir)) {
|
|
77
|
+
fs.mkdirSync(componentDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Generate component template based on type
|
|
81
|
+
function generateComponentTemplate() {
|
|
82
|
+
const templates = {
|
|
83
|
+
ui: `<script lang="ts">
|
|
84
|
+
import { cn } from '$lib/utils/cn';
|
|
85
|
+
import type { ComponentProps } from 'svelte';
|
|
86
|
+
|
|
87
|
+
type $$Props = ComponentProps<'div'> & {
|
|
88
|
+
variant?: 'default' | 'primary' | 'secondary';
|
|
89
|
+
size?: 'sm' | 'md' | 'lg';
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
let className: $$Props['class'] = undefined;
|
|
93
|
+
export { className as class };
|
|
94
|
+
export let variant: $$Props['variant'] = 'default';
|
|
95
|
+
export let size: $$Props['size'] = 'md';
|
|
96
|
+
|
|
97
|
+
const variants = {
|
|
98
|
+
default: 'bg-white border-gray-200',
|
|
99
|
+
primary: 'bg-blue-500 text-white',
|
|
100
|
+
secondary: 'bg-gray-100 text-gray-900',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const sizes = {
|
|
104
|
+
sm: 'p-2 text-sm',
|
|
105
|
+
md: 'p-4 text-base',
|
|
106
|
+
lg: 'p-6 text-lg',
|
|
107
|
+
};
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<div
|
|
111
|
+
class={cn(
|
|
112
|
+
'rounded-lg border transition-colors',
|
|
113
|
+
variants[variant],
|
|
114
|
+
sizes[size],
|
|
115
|
+
className
|
|
116
|
+
)}
|
|
117
|
+
{...$$restProps}
|
|
118
|
+
>
|
|
119
|
+
<slot />
|
|
120
|
+
</div>`,
|
|
121
|
+
|
|
122
|
+
page: `<script lang="ts">
|
|
123
|
+
import { page } from '$app/stores';
|
|
124
|
+
import { onMount } from 'svelte';
|
|
125
|
+
import { authService } from '$lib/services/auth';
|
|
126
|
+
import { Button } from '$lib/components/ui/button';
|
|
127
|
+
import { Card, CardHeader, CardTitle, CardContent } from '$lib/components/ui/card';
|
|
128
|
+
|
|
129
|
+
// Page data and state
|
|
130
|
+
let loading = false;
|
|
131
|
+
let error: string | null = null;
|
|
132
|
+
|
|
133
|
+
// Authentication
|
|
134
|
+
const user = authService.getUser();
|
|
135
|
+
const claims = authService.getUserClaims();
|
|
136
|
+
|
|
137
|
+
onMount(() => {
|
|
138
|
+
loadData();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
async function loadData() {
|
|
142
|
+
loading = true;
|
|
143
|
+
error = null;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
// Load page data
|
|
147
|
+
} catch (err) {
|
|
148
|
+
error = 'Failed to load data';
|
|
149
|
+
console.error('${componentName} error:', err);
|
|
150
|
+
} finally {
|
|
151
|
+
loading = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<svelte:head>
|
|
157
|
+
<title>${componentName}</title>
|
|
158
|
+
<meta name="description" content="${componentName} page" />
|
|
159
|
+
</svelte:head>
|
|
160
|
+
|
|
161
|
+
<div class="container mx-auto py-6">
|
|
162
|
+
<div class="mb-6">
|
|
163
|
+
<h1 class="text-3xl font-bold">${componentName}</h1>
|
|
164
|
+
<p class="text-gray-600">Page description goes here</p>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{#if loading}
|
|
168
|
+
<div class="flex justify-center py-12">
|
|
169
|
+
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
|
170
|
+
</div>
|
|
171
|
+
{:else if error}
|
|
172
|
+
<Card>
|
|
173
|
+
<CardContent class="p-6">
|
|
174
|
+
<p class="text-red-600">{error}</p>
|
|
175
|
+
<Button on:click={loadData} variant="outline" class="mt-4">
|
|
176
|
+
Try Again
|
|
177
|
+
</Button>
|
|
178
|
+
</CardContent>
|
|
179
|
+
</Card>
|
|
180
|
+
{:else}
|
|
181
|
+
<Card>
|
|
182
|
+
<CardHeader>
|
|
183
|
+
<CardTitle>Content</CardTitle>
|
|
184
|
+
</CardHeader>
|
|
185
|
+
<CardContent>
|
|
186
|
+
<p>Your page content goes here.</p>
|
|
187
|
+
</CardContent>
|
|
188
|
+
</Card>
|
|
189
|
+
{/if}
|
|
190
|
+
</div>`,
|
|
191
|
+
|
|
192
|
+
layout: `<script lang="ts">
|
|
193
|
+
import { page } from '$app/stores';
|
|
194
|
+
import { authService } from '$lib/services/auth';
|
|
195
|
+
import { Button } from '$lib/components/ui/button';
|
|
196
|
+
|
|
197
|
+
// Layout props
|
|
198
|
+
export let title: string = '';
|
|
199
|
+
export let showHeader: boolean = true;
|
|
200
|
+
export let showSidebar: boolean = true;
|
|
201
|
+
|
|
202
|
+
const user = authService.getUser();
|
|
203
|
+
</script>
|
|
204
|
+
|
|
205
|
+
<div class="min-h-screen bg-gray-50">
|
|
206
|
+
{#if showHeader}
|
|
207
|
+
<header class="bg-white shadow-sm border-b">
|
|
208
|
+
<div class="container mx-auto px-4">
|
|
209
|
+
<div class="flex items-center justify-between h-16">
|
|
210
|
+
<div class="flex items-center">
|
|
211
|
+
<h1 class="text-xl font-semibold">{title || '${componentName}'}</h1>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<div class="flex items-center space-x-4">
|
|
215
|
+
{#if $user}
|
|
216
|
+
<span class="text-sm text-gray-600">Welcome, {$user.email}</span>
|
|
217
|
+
<Button variant="outline" size="sm" on:click={() => authService.logout()}>
|
|
218
|
+
Logout
|
|
219
|
+
</Button>
|
|
220
|
+
{:else}
|
|
221
|
+
<Button href="/login" size="sm">
|
|
222
|
+
Login
|
|
223
|
+
</Button>
|
|
224
|
+
{/if}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</header>
|
|
229
|
+
{/if}
|
|
230
|
+
|
|
231
|
+
<div class="flex">
|
|
232
|
+
{#if showSidebar}
|
|
233
|
+
<aside class="w-64 bg-white shadow-sm min-h-screen">
|
|
234
|
+
<nav class="p-4">
|
|
235
|
+
<div class="space-y-2">
|
|
236
|
+
<a href="/dashboard" class="block px-3 py-2 rounded-md text-sm font-medium hover:bg-gray-100">
|
|
237
|
+
Dashboard
|
|
238
|
+
</a>
|
|
239
|
+
<!-- Add more navigation items -->
|
|
240
|
+
</div>
|
|
241
|
+
</nav>
|
|
242
|
+
</aside>
|
|
243
|
+
{/if}
|
|
244
|
+
|
|
245
|
+
<main class="flex-1 p-6">
|
|
246
|
+
<slot />
|
|
247
|
+
</main>
|
|
248
|
+
</div>
|
|
249
|
+
</div>`,
|
|
250
|
+
|
|
251
|
+
feature: `<script lang="ts">
|
|
252
|
+
import { createEventDispatcher, onMount } from 'svelte';
|
|
253
|
+
import { writable, derived } from 'svelte/store';
|
|
254
|
+
import { apiService } from '$lib/services/api';
|
|
255
|
+
import { toast } from '$lib/services/toast';
|
|
256
|
+
import { handleError } from '$lib/utils/error-handler';
|
|
257
|
+
import { Button } from '$lib/components/ui/button';
|
|
258
|
+
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '$lib/components/ui/card';
|
|
259
|
+
|
|
260
|
+
// Props
|
|
261
|
+
export let data: any = null;
|
|
262
|
+
export let readonly: boolean = false;
|
|
263
|
+
|
|
264
|
+
// Events
|
|
265
|
+
const dispatch = createEventDispatcher<{
|
|
266
|
+
save: { data: any };
|
|
267
|
+
delete: { id: string };
|
|
268
|
+
cancel: void;
|
|
269
|
+
}>();
|
|
270
|
+
|
|
271
|
+
// State management
|
|
272
|
+
const loading = writable(false);
|
|
273
|
+
const error = writable<string | null>(null);
|
|
274
|
+
const formData = writable(data || {});
|
|
275
|
+
|
|
276
|
+
// Derived state
|
|
277
|
+
const hasChanges = derived(
|
|
278
|
+
formData,
|
|
279
|
+
($formData) => JSON.stringify($formData) !== JSON.stringify(data)
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
onMount(() => {
|
|
283
|
+
if (data?.id) {
|
|
284
|
+
loadData();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
async function loadData() {
|
|
289
|
+
if (!data?.id) return;
|
|
290
|
+
|
|
291
|
+
loading.set(true);
|
|
292
|
+
error.set(null);
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const response = await apiService.get(\`/api/items/\${data.id}\`);
|
|
296
|
+
formData.set(response.data);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
error.set(handleError(err, 'Failed to load data'));
|
|
299
|
+
} finally {
|
|
300
|
+
loading.set(false);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function handleSave() {
|
|
305
|
+
if (readonly) return;
|
|
306
|
+
|
|
307
|
+
loading.set(true);
|
|
308
|
+
error.set(null);
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const response = data?.id
|
|
312
|
+
? await apiService.put(\`/api/items/\${data.id}\`, $formData)
|
|
313
|
+
: await apiService.post('/api/items', $formData);
|
|
314
|
+
|
|
315
|
+
dispatch('save', { data: response.data });
|
|
316
|
+
toast.success(\`\${componentName} saved successfully\`);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
error.set(handleError(err, \`Failed to save \${componentName.toLowerCase()}\`));
|
|
319
|
+
} finally {
|
|
320
|
+
loading.set(false);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function handleDelete() {
|
|
325
|
+
if (readonly || !data?.id) return;
|
|
326
|
+
|
|
327
|
+
if (!confirm('Are you sure you want to delete this item?')) return;
|
|
328
|
+
|
|
329
|
+
loading.set(true);
|
|
330
|
+
error.set(null);
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
await apiService.delete(\`/api/items/\${data.id}\`);
|
|
334
|
+
dispatch('delete', { id: data.id });
|
|
335
|
+
toast.success(\`\${componentName} deleted successfully\`);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
error.set(handleError(err, \`Failed to delete \${componentName.toLowerCase()}\`));
|
|
338
|
+
} finally {
|
|
339
|
+
loading.set(false);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function handleCancel() {
|
|
344
|
+
if ($hasChanges && !confirm('You have unsaved changes. Are you sure you want to cancel?')) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
formData.set(data || {});
|
|
348
|
+
dispatch('cancel');
|
|
349
|
+
}
|
|
350
|
+
</script>
|
|
351
|
+
|
|
352
|
+
<Card>
|
|
353
|
+
<CardHeader>
|
|
354
|
+
<CardTitle>{data?.id ? 'Edit' : 'Create'} ${componentName}</CardTitle>
|
|
355
|
+
</CardHeader>
|
|
356
|
+
|
|
357
|
+
<CardContent>
|
|
358
|
+
{#if $error}
|
|
359
|
+
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded-md">
|
|
360
|
+
<p class="text-red-600 text-sm">{$error}</p>
|
|
361
|
+
</div>
|
|
362
|
+
{/if}
|
|
363
|
+
|
|
364
|
+
<!-- Form fields go here -->
|
|
365
|
+
<div class="space-y-4">
|
|
366
|
+
<p class="text-gray-500">Add your form fields here</p>
|
|
367
|
+
</div>
|
|
368
|
+
</CardContent>
|
|
369
|
+
|
|
370
|
+
{#if !readonly}
|
|
371
|
+
<CardFooter class="flex justify-between">
|
|
372
|
+
<div>
|
|
373
|
+
{#if data?.id}
|
|
374
|
+
<Button variant="destructive" on:click={handleDelete} disabled={$loading}>
|
|
375
|
+
Delete
|
|
376
|
+
</Button>
|
|
377
|
+
{/if}
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
<div class="flex space-x-2">
|
|
381
|
+
<Button variant="outline" on:click={handleCancel} disabled={$loading}>
|
|
382
|
+
Cancel
|
|
383
|
+
</Button>
|
|
384
|
+
<Button on:click={handleSave} disabled={$loading || !$hasChanges}>
|
|
385
|
+
{$loading ? 'Saving...' : 'Save'}
|
|
386
|
+
</Button>
|
|
387
|
+
</div>
|
|
388
|
+
</CardFooter>
|
|
389
|
+
{/if}
|
|
390
|
+
</Card>`
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
return templates[options.type] || templates.ui;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Generate index file
|
|
397
|
+
function generateIndexTemplate() {
|
|
398
|
+
return `export { default as ${componentName} } from './${componentName}.svelte';
|
|
399
|
+
export type { ComponentProps as ${componentName}Props } from './${componentName}.svelte';`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Generate test file
|
|
403
|
+
function generateTestTemplate() {
|
|
404
|
+
return `import { render, fireEvent, screen } from '@testing-library/svelte';
|
|
405
|
+
import { expect, test, vi } from 'vitest';
|
|
406
|
+
import ${componentName} from './${componentName}.svelte';
|
|
407
|
+
|
|
408
|
+
test('renders ${componentName} correctly', () => {
|
|
409
|
+
render(${componentName});
|
|
410
|
+
|
|
411
|
+
// Add your test assertions here
|
|
412
|
+
expect(screen.getByRole('generic')).toBeInTheDocument();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test('handles props correctly', () => {
|
|
416
|
+
const props = {
|
|
417
|
+
// Add test props
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
render(${componentName}, { props });
|
|
421
|
+
|
|
422
|
+
// Test prop handling
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test('emits events correctly', async () => {
|
|
426
|
+
const { component } = render(${componentName});
|
|
427
|
+
|
|
428
|
+
const handleEvent = vi.fn();
|
|
429
|
+
component.$on('event', handleEvent);
|
|
430
|
+
|
|
431
|
+
// Trigger event and test
|
|
432
|
+
// await fireEvent.click(screen.getByRole('button'));
|
|
433
|
+
// expect(handleEvent).toHaveBeenCalledWith(expect.objectContaining({
|
|
434
|
+
// detail: expectedData
|
|
435
|
+
// }));
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('handles accessibility requirements', () => {
|
|
439
|
+
render(${componentName});
|
|
440
|
+
|
|
441
|
+
// Test keyboard navigation
|
|
442
|
+
// Test screen reader compatibility
|
|
443
|
+
// Test focus management
|
|
444
|
+
});`;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Generate Storybook story
|
|
448
|
+
function generateStoryTemplate() {
|
|
449
|
+
return `import type { Meta, StoryObj } from '@storybook/svelte';
|
|
450
|
+
import ${componentName} from './${componentName}.svelte';
|
|
451
|
+
|
|
452
|
+
const meta: Meta<${componentName}> = {
|
|
453
|
+
title: 'Components/${options.type}/${componentName}',
|
|
454
|
+
component: ${componentName},
|
|
455
|
+
parameters: {
|
|
456
|
+
layout: 'centered',
|
|
457
|
+
docs: {
|
|
458
|
+
description: {
|
|
459
|
+
component: '${componentName} component description.'
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
tags: ['autodocs'],
|
|
464
|
+
argTypes: {
|
|
465
|
+
// Define argTypes for Storybook controls
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
export default meta;
|
|
470
|
+
type Story = StoryObj<meta>;
|
|
471
|
+
|
|
472
|
+
export const Default: Story = {
|
|
473
|
+
args: {
|
|
474
|
+
// Default props
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
export const Variants: Story = {
|
|
479
|
+
args: {
|
|
480
|
+
// Variant props
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
export const Interactive: Story = {
|
|
485
|
+
args: {
|
|
486
|
+
// Interactive props
|
|
487
|
+
},
|
|
488
|
+
play: async ({ canvasElement }) => {
|
|
489
|
+
// Interaction testing
|
|
490
|
+
}
|
|
491
|
+
};`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Generate documentation
|
|
495
|
+
function generateDocTemplate() {
|
|
496
|
+
return `# ${componentName}
|
|
497
|
+
|
|
498
|
+
${componentName} component description.
|
|
499
|
+
|
|
500
|
+
## Usage
|
|
501
|
+
|
|
502
|
+
\`\`\`svelte
|
|
503
|
+
<script>
|
|
504
|
+
import { ${componentName} } from '$lib/components/${options.path.replace('src/lib/components/', '')}/${kebabName}';
|
|
505
|
+
</script>
|
|
506
|
+
|
|
507
|
+
<${componentName}>
|
|
508
|
+
Content goes here
|
|
509
|
+
</${componentName}>
|
|
510
|
+
\`\`\`
|
|
511
|
+
|
|
512
|
+
## Props
|
|
513
|
+
|
|
514
|
+
| Prop | Type | Default | Description |
|
|
515
|
+
|------|------|---------|-------------|
|
|
516
|
+
| \`class\` | \`string\` | \`undefined\` | Additional CSS classes |
|
|
517
|
+
|
|
518
|
+
## Events
|
|
519
|
+
|
|
520
|
+
| Event | Detail | Description |
|
|
521
|
+
|-------|--------|-------------|
|
|
522
|
+
| \`click\` | \`MouseEvent\` | Fired when component is clicked |
|
|
523
|
+
|
|
524
|
+
## Slots
|
|
525
|
+
|
|
526
|
+
| Slot | Props | Description |
|
|
527
|
+
|------|-------|-------------|
|
|
528
|
+
| default | - | Main content slot |
|
|
529
|
+
|
|
530
|
+
## Examples
|
|
531
|
+
|
|
532
|
+
### Basic Usage
|
|
533
|
+
|
|
534
|
+
\`\`\`svelte
|
|
535
|
+
<${componentName}>
|
|
536
|
+
Hello World
|
|
537
|
+
</${componentName}>
|
|
538
|
+
\`\`\`
|
|
539
|
+
|
|
540
|
+
### With Props
|
|
541
|
+
|
|
542
|
+
\`\`\`svelte
|
|
543
|
+
<${componentName} variant="primary" size="lg">
|
|
544
|
+
Large Primary Component
|
|
545
|
+
</${componentName}>
|
|
546
|
+
\`\`\`
|
|
547
|
+
|
|
548
|
+
## Accessibility
|
|
549
|
+
|
|
550
|
+
- Follows WAI-ARIA guidelines
|
|
551
|
+
- Supports keyboard navigation
|
|
552
|
+
- Compatible with screen readers
|
|
553
|
+
|
|
554
|
+
## Styling
|
|
555
|
+
|
|
556
|
+
This component uses Tailwind CSS classes and can be customized using the \`class\` prop or by modifying the component's default styles.`;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Write files
|
|
560
|
+
try {
|
|
561
|
+
console.log(`๐ Generating ${componentName} component...`);
|
|
562
|
+
|
|
563
|
+
// Write main component file
|
|
564
|
+
fs.writeFileSync(componentFile, generateComponentTemplate());
|
|
565
|
+
console.log(`โ
Created ${componentFile}`);
|
|
566
|
+
|
|
567
|
+
// Write index file
|
|
568
|
+
fs.writeFileSync(indexFile, generateIndexTemplate());
|
|
569
|
+
console.log(`โ
Created ${indexFile}`);
|
|
570
|
+
|
|
571
|
+
// Write test file if requested
|
|
572
|
+
if (options.withTest) {
|
|
573
|
+
fs.writeFileSync(testFile, generateTestTemplate());
|
|
574
|
+
console.log(`โ
Created ${testFile}`);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Write story file if requested
|
|
578
|
+
if (options.withStory) {
|
|
579
|
+
fs.writeFileSync(storyFile, generateStoryTemplate());
|
|
580
|
+
console.log(`โ
Created ${storyFile}`);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Write documentation if requested
|
|
584
|
+
if (options.withDocs) {
|
|
585
|
+
fs.writeFileSync(docFile, generateDocTemplate());
|
|
586
|
+
console.log(`โ
Created ${docFile}`);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
console.log(`\n๐ ${componentName} component generated successfully!`);
|
|
590
|
+
console.log(`\n๐ Files created in: ${componentDir}`);
|
|
591
|
+
console.log(`\n๐ Import your component:`);
|
|
592
|
+
console.log(` import { ${componentName} } from '$lib/components/${options.path.replace('src/lib/components/', '')}/${kebabName}';`);
|
|
593
|
+
|
|
594
|
+
if (options.withTest) {
|
|
595
|
+
console.log(`\n๐งช Run tests:`);
|
|
596
|
+
console.log(` npm test ${testFile}`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
} catch (error) {
|
|
600
|
+
console.error('โ Error generating component:', error.message);
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|