create-universal-dapp 1.0.0

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.
@@ -0,0 +1,784 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import ora from 'ora';
4
+ export async function createViteApp(options) {
5
+ const spinner = ora('Setting up Vite project...').start();
6
+ try {
7
+ // Create package.json
8
+ await createVitePackageJson(options);
9
+ // Create Vite configuration files
10
+ await createViteConfig(options);
11
+ // Create source structure
12
+ await createViteSourceFiles(options);
13
+ // Create Push Chain integration files
14
+ await createPushChainIntegration(options);
15
+ // Create ESLint config if requested
16
+ if (options.eslint) {
17
+ await createESLintConfig(options);
18
+ }
19
+ // Create Tailwind config (Tailwind-only setup)
20
+ await createTailwindConfig(options);
21
+ spinner.succeed('Vite project structure created');
22
+ }
23
+ catch (error) {
24
+ spinner.fail('Failed to create Vite project');
25
+ throw error;
26
+ }
27
+ }
28
+ async function createVitePackageJson(options) {
29
+ const packageJson = {
30
+ name: options.projectName,
31
+ private: true,
32
+ version: "0.0.0",
33
+ type: "module",
34
+ scripts: {
35
+ dev: "vite",
36
+ build: "tsc && vite build",
37
+ lint: options.eslint ? "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" : undefined,
38
+ preview: "vite preview"
39
+ },
40
+ dependencies: {
41
+ react: "^18.3.1",
42
+ "react-dom": "^18.3.1",
43
+ "@pushchain/ui-kit": "^1.1.33"
44
+ },
45
+ devDependencies: {
46
+ "@types/react": "^18.2.43",
47
+ "@types/react-dom": "^18.2.17",
48
+ "@vitejs/plugin-react": "^4.2.1",
49
+ typescript: "^5.2.2",
50
+ vite: "^5.0.8",
51
+ tailwindcss: "^3.3.0",
52
+ autoprefixer: "^10.4.16",
53
+ postcss: "^8.4.31"
54
+ }
55
+ };
56
+ // Remove undefined values
57
+ Object.keys(packageJson.scripts).forEach(key => {
58
+ const scripts = packageJson.scripts;
59
+ if (scripts[key] === undefined) {
60
+ delete scripts[key];
61
+ }
62
+ });
63
+ await fs.writeJSON(path.join(options.targetDir, 'package.json'), packageJson, { spaces: 2 });
64
+ }
65
+ async function createViteConfig(options) {
66
+ // Vite config
67
+ const viteConfig = `import { defineConfig } from 'vite'
68
+ import react from '@vitejs/plugin-react'
69
+ import path from 'path'
70
+
71
+ // https://vitejs.dev/config/
72
+ export default defineConfig({
73
+ plugins: [react()],
74
+ resolve: {
75
+ alias: {
76
+ "@": path.resolve(__dirname, "./src"),
77
+ },
78
+ },
79
+ })`;
80
+ await fs.writeFile(path.join(options.targetDir, 'vite.config.ts'), viteConfig);
81
+ // TypeScript config
82
+ const tsConfig = {
83
+ compilerOptions: {
84
+ target: "ES2020",
85
+ useDefineForClassFields: true,
86
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
87
+ module: "ESNext",
88
+ skipLibCheck: true,
89
+ moduleResolution: "bundler",
90
+ allowImportingTsExtensions: true,
91
+ resolveJsonModule: true,
92
+ isolatedModules: true,
93
+ noEmit: true,
94
+ jsx: "react-jsx",
95
+ strict: true,
96
+ noUnusedLocals: true,
97
+ noUnusedParameters: true,
98
+ noFallthroughCasesInSwitch: true,
99
+ baseUrl: ".",
100
+ paths: {
101
+ "@/*": ["./src/*"]
102
+ }
103
+ },
104
+ include: ["src"],
105
+ references: [{ "path": "./tsconfig.node.json" }]
106
+ };
107
+ await fs.writeJSON(path.join(options.targetDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
108
+ // Node TypeScript config
109
+ const tsNodeConfig = {
110
+ compilerOptions: {
111
+ composite: true,
112
+ skipLibCheck: true,
113
+ module: "ESNext",
114
+ moduleResolution: "bundler",
115
+ allowSyntheticDefaultImports: true
116
+ },
117
+ include: ["vite.config.ts"]
118
+ };
119
+ await fs.writeJSON(path.join(options.targetDir, 'tsconfig.node.json'), tsNodeConfig, { spaces: 2 });
120
+ }
121
+ async function createViteSourceFiles(options) {
122
+ // Create src directory structure
123
+ const srcDir = path.join(options.targetDir, 'src');
124
+ const componentsDir = path.join(srcDir, 'components');
125
+ const libDir = path.join(srcDir, 'lib');
126
+ await fs.ensureDir(srcDir);
127
+ await fs.ensureDir(componentsDir);
128
+ await fs.ensureDir(libDir);
129
+ // Create index.html
130
+ const indexHtml = `<!doctype html>
131
+ <html lang="en">
132
+ <head>
133
+ <meta charset="UTF-8" />
134
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
135
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
136
+ <title>${options.projectName}</title>
137
+ </head>
138
+ <body>
139
+ <div id="root"></div>
140
+ <script type="module" src="/src/main.tsx"></script>
141
+ </body>
142
+ </html>`;
143
+ await fs.writeFile(path.join(options.targetDir, 'index.html'), indexHtml);
144
+ // Create main.tsx
145
+ const mainTsx = `import { StrictMode } from 'react'
146
+ import { createRoot } from 'react-dom/client'
147
+ import './index.css'
148
+ import App from './App.tsx'
149
+ import { PushChainProviders } from './providers/PushChainProviders.tsx'
150
+
151
+ createRoot(document.getElementById('root')!).render(
152
+ <StrictMode>
153
+ <PushChainProviders>
154
+ <App />
155
+ </PushChainProviders>
156
+ </StrictMode>,
157
+ )`;
158
+ await fs.writeFile(path.join(srcDir, 'main.tsx'), mainTsx);
159
+ // Create App.tsx
160
+ const appTsx = `import './App.css'
161
+ import { PushUniversalAccountButton, usePushWalletContext } from '@pushchain/ui-kit'
162
+
163
+ function App() {
164
+ const { universalAccount } = usePushWalletContext();
165
+
166
+ console.log("universalAccount", universalAccount);
167
+
168
+ return (
169
+ <div style={{ minHeight: '100vh', padding: '2rem' }}>
170
+ <div style={{ maxWidth: '800px', margin: '0 auto' }}>
171
+ <div style={{ textAlign: 'center', marginBottom: '2rem' }}>
172
+ <h1 style={{ fontSize: '2.5rem', fontWeight: 'bold', marginBottom: '1rem' }}>
173
+ Welcome to ${options.projectName}
174
+ </h1>
175
+ <p style={{ fontSize: '1.2rem', color: '#666', marginBottom: '2rem' }}>
176
+ Your Push Chain application is ready to go!
177
+ </p>
178
+ </div>
179
+
180
+ <div style={{
181
+ maxWidth: '600px',
182
+ margin: '0 auto',
183
+ padding: '2rem',
184
+ border: '1px solid #ddd',
185
+ borderRadius: '8px',
186
+ textAlign: 'center'
187
+ }}>
188
+ <h2 style={{ marginBottom: '1rem' }}>Push Chain Integration</h2>
189
+ <div style={{ marginBottom: '1rem' }}>
190
+ <span style={{
191
+ padding: '4px 8px',
192
+ borderRadius: '4px',
193
+ backgroundColor: universalAccount ? '#22c55e' : '#6b7280',
194
+ color: 'white',
195
+ fontSize: '12px'
196
+ }}>
197
+ {universalAccount ? "Connected" : "Not Connected"}
198
+ </span>
199
+ </div>
200
+
201
+ <div style={{ marginBottom: '2rem' }}>
202
+ <PushUniversalAccountButton />
203
+ </div>
204
+
205
+ {universalAccount && (
206
+ <div style={{
207
+ marginTop: '2rem',
208
+ padding: '1rem',
209
+ backgroundColor: '#f5f5f5',
210
+ borderRadius: '6px',
211
+ textAlign: 'left'
212
+ }}>
213
+ <h3 style={{ fontWeight: 'bold', marginBottom: '0.5rem' }}>Account Details:</h3>
214
+ <pre style={{ fontSize: '0.8rem', overflow: 'auto' }}>
215
+ {JSON.stringify(universalAccount, null, 2)}
216
+ </pre>
217
+ </div>
218
+ )}
219
+
220
+ <div style={{ fontSize: '0.9rem', color: '#666', marginTop: '2rem', textAlign: 'left' }}>
221
+ <p><strong>Next steps:</strong></p>
222
+ <ul style={{ margin: '0.5rem 0', paddingLeft: '1.5rem' }}>
223
+ <li>Configure your Push Chain settings in providers/PushChainProviders.tsx</li>
224
+ <li>Customize the app metadata with your branding</li>
225
+ <li>Add your chain configuration and RPC URLs</li>
226
+ <li>Explore the @pushchain/ui-kit for more components</li>
227
+ </ul>
228
+ </div>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ )
233
+ }
234
+
235
+ export default App`;
236
+ await fs.writeFile(path.join(srcDir, 'App.tsx'), appTsx);
237
+ // Create App.css
238
+ const appCss = `#root {
239
+ max-width: 1280px;
240
+ margin: 0 auto;
241
+ padding: 2rem;
242
+ text-align: center;
243
+ }
244
+
245
+ .logo {
246
+ height: 6em;
247
+ padding: 1.5em;
248
+ will-change: filter;
249
+ transition: filter 300ms;
250
+ }
251
+ .logo:hover {
252
+ filter: drop-shadow(0 0 2em #646cffaa);
253
+ }
254
+ .logo.react:hover {
255
+ filter: drop-shadow(0 0 2em #61dafbaa);
256
+ }
257
+
258
+ @keyframes logo-spin {
259
+ from {
260
+ transform: rotate(0deg);
261
+ }
262
+ to {
263
+ transform: rotate(360deg);
264
+ }
265
+ }
266
+
267
+ @media (prefers-reduced-motion: no-preference) {
268
+ a:nth-of-type(2) .logo {
269
+ animation: logo-spin infinite 20s linear;
270
+ }
271
+ }
272
+
273
+ .card {
274
+ padding: 2em;
275
+ }
276
+
277
+ .read-the-docs {
278
+ color: #888;
279
+ }`;
280
+ await fs.writeFile(path.join(srcDir, 'App.css'), appCss);
281
+ // Create index.css (Tailwind directives only)
282
+ const indexCss = `@tailwind base;
283
+ @tailwind components;
284
+ @tailwind utilities;
285
+
286
+ :root {
287
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
288
+ line-height: 1.5;
289
+ font-weight: 400;
290
+
291
+ color-scheme: light dark;
292
+ color: rgba(255, 255, 255, 0.87);
293
+ background-color: #242424;
294
+
295
+ font-synthesis: none;
296
+ text-rendering: optimizeLegibility;
297
+ -webkit-font-smoothing: antialiased;
298
+ -moz-osx-font-smoothing: grayscale;
299
+ -webkit-text-size-adjust: 100%;
300
+ }
301
+
302
+ a {
303
+ font-weight: 500;
304
+ color: #646cff;
305
+ text-decoration: inherit;
306
+ }
307
+ a:hover {
308
+ color: #535bf2;
309
+ }
310
+
311
+ body {
312
+ margin: 0;
313
+ display: flex;
314
+ place-items: center;
315
+ min-width: 320px;
316
+ min-height: 100vh;
317
+ }
318
+
319
+ h1 {
320
+ font-size: 3.2em;
321
+ line-height: 1.1;
322
+ }
323
+
324
+ button {
325
+ border-radius: 8px;
326
+ border: 1px solid transparent;
327
+ padding: 0.6em 1.2em;
328
+ font-size: 1em;
329
+ font-weight: 500;
330
+ font-family: inherit;
331
+ background-color: #1a1a1a;
332
+ cursor: pointer;
333
+ transition: border-color 0.25s;
334
+ }
335
+ button:hover {
336
+ border-color: #646cff;
337
+ }
338
+ button:focus,
339
+ button:focus-visible {
340
+ outline: 4px auto -webkit-focus-ring-color;
341
+ }
342
+
343
+ .container {
344
+ max-width: 1200px;
345
+ margin: 0 auto;
346
+ padding: 0 20px;
347
+ }
348
+
349
+ @media (prefers-color-scheme: light) {
350
+ :root {
351
+ color: #213547;
352
+ background-color: #ffffff;
353
+ }
354
+ a:hover {
355
+ color: #747bff;
356
+ }
357
+ button {
358
+ background-color: #f9f9f9;
359
+ }
360
+ }`;
361
+ await fs.writeFile(path.join(srcDir, 'index.css'), indexCss);
362
+ // Create vite-env.d.ts
363
+ const viteEnv = `/// <reference types="vite/client" />`;
364
+ await fs.writeFile(path.join(srcDir, 'vite-env.d.ts'), viteEnv);
365
+ }
366
+ async function createPushChainIntegration(options) {
367
+ // Create providers directory
368
+ const providersDir = path.join(options.targetDir, 'src', 'providers');
369
+ await fs.ensureDir(providersDir);
370
+ // Create PushChainProviders.tsx following your pattern
371
+ const pushchainProviders = `import {
372
+ PushUI,
373
+ PushUniversalWalletProvider,
374
+ type AppMetadata,
375
+ type ProviderConfigProps,
376
+ } from "@pushchain/ui-kit";
377
+
378
+ /**
379
+ * PushChainProviders Component
380
+ *
381
+ * This component wraps your entire application with Push Chain's Universal Wallet Provider.
382
+ * It configures the wallet connection options, network settings, and app metadata.
383
+ *
384
+ * Configuration Guide:
385
+ * - network: Choose between MAINNET, TESTNET, or DEV
386
+ * - login: Configure authentication methods (email, google, wallet)
387
+ * - modal: Customize the UI appearance and behavior
388
+ * - chainConfig: Add your custom RPC endpoints
389
+ * - appMetadata: Brand your app with logo, title, and description
390
+ */
391
+
392
+ const PushChainProviders = ({ children }: { children: React.ReactNode }) => {
393
+ // Wallet configuration - customize these settings for your app
394
+ const walletConfig: ProviderConfigProps = {
395
+ // Network selection: MAINNET for production, TESTNET for development
396
+ network: PushUI.CONSTANTS.PUSH_NETWORK.TESTNET,
397
+
398
+ // Login options - enable/disable authentication methods
399
+ login: {
400
+ email: true, // Allow email authentication
401
+ google: true, // Allow Google OAuth
402
+ wallet: {
403
+ enabled: true, // Allow wallet connection (MetaMask, etc.)
404
+ },
405
+ appPreview: true, // Show app preview in login modal
406
+ },
407
+
408
+ // Modal UI customization
409
+ modal: {
410
+ // Layout: SPLIT (side-by-side) or STACKED (vertical)
411
+ loginLayout: PushUI.CONSTANTS.LOGIN.LAYOUT.SPLIT,
412
+
413
+ // Connected wallet display: HOVER (on hover) or FULL (always visible)
414
+ connectedLayout: PushUI.CONSTANTS.CONNECTED.LAYOUT.HOVER,
415
+
416
+ // Show app preview in modals
417
+ appPreview: true,
418
+
419
+ // Background interaction when modal is open: BLUR or NONE
420
+ connectedInteraction: PushUI.CONSTANTS.CONNECTED.INTERACTION.BLUR,
421
+ },
422
+
423
+ // Chain configuration - add your custom RPC endpoints here
424
+ chainConfig: {
425
+ rpcUrls: {
426
+ // Ethereum Sepolia testnet (for testing)
427
+ "eip155:11155111": ["https://sepolia.gateway.tenderly.co/"],
428
+
429
+ // Add more chains as needed:
430
+ // "eip155:1": ["https://mainnet.infura.io/v3/YOUR_PROJECT_ID"], // Ethereum Mainnet
431
+ // "eip155:137": ["https://polygon-rpc.com/"], // Polygon
432
+ // "eip155:42161": ["https://arb1.arbitrum.io/rpc"], // Arbitrum
433
+ },
434
+ },
435
+ };
436
+
437
+ // App metadata - customize with your app's branding
438
+ const appMetadata: AppMetadata = {
439
+ // Your app's logo URL (can be a URL or base64 data URI)
440
+ logoUrl: "https://avatars.githubusercontent.com/u/64157541?v=4",
441
+
442
+ // Your app's display name
443
+ title: "${options.projectName}",
444
+
445
+ // Brief description of your app (shown in wallet connection prompts)
446
+ description:
447
+ "Push Chain is a shared state L1 blockchain that allows all chains to unify, enabling apps of any chain to be accessed by users of any chain.",
448
+ };
449
+
450
+ return (
451
+ <PushUniversalWalletProvider config={walletConfig} app={appMetadata}>
452
+ {children}
453
+ </PushUniversalWalletProvider>
454
+ );
455
+ };
456
+
457
+ export { PushChainProviders };`;
458
+ await fs.writeFile(path.join(providersDir, 'PushChainProviders.tsx'), pushchainProviders);
459
+ // Create .env file with configuration notes
460
+ const envFile = `# Push Chain Configuration
461
+ #
462
+ # This file contains environment variables for your Push Chain application.
463
+ # Copy this file to .env.local for local development.
464
+ #
465
+ # IMPORTANT: Never commit sensitive API keys to version control!
466
+
467
+ # Push Chain Network Configuration
468
+ # Options: mainnet, testnet, dev
469
+ VITE_PUSHCHAIN_NETWORK=testnet
470
+
471
+ # Optional: Custom API endpoints (if you have specific requirements)
472
+ # VITE_PUSHCHAIN_API_ENDPOINT=https://your-custom-endpoint.com
473
+
474
+ # Optional: App-specific configuration
475
+ # VITE_APP_NAME=${options.projectName}
476
+ # VITE_APP_VERSION=1.0.0`;
477
+ await fs.writeFile(path.join(options.targetDir, '.env'), envFile);
478
+ }
479
+ async function createESLintConfig(options) {
480
+ const eslintConfig = {
481
+ root: true,
482
+ env: { browser: true, es2020: true },
483
+ extends: [
484
+ 'eslint:recommended',
485
+ '@typescript-eslint/recommended',
486
+ 'eslint-plugin-react-hooks/recommended',
487
+ ],
488
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
489
+ parser: '@typescript-eslint/parser',
490
+ plugins: ['react-refresh'],
491
+ rules: {
492
+ 'react-refresh/only-export-components': [
493
+ 'warn',
494
+ { allowConstantExport: true },
495
+ ],
496
+ },
497
+ };
498
+ await fs.writeJSON(path.join(options.targetDir, '.eslintrc.json'), eslintConfig, { spaces: 2 });
499
+ }
500
+ async function createTailwindConfig(options) {
501
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
502
+ export default {
503
+ darkMode: ["class"],
504
+ content: [
505
+ "./index.html",
506
+ "./src/**/*.{js,ts,jsx,tsx}",
507
+ ],
508
+ theme: {
509
+ container: {
510
+ center: true,
511
+ padding: "2rem",
512
+ screens: {
513
+ "2xl": "1400px",
514
+ },
515
+ },
516
+ extend: {
517
+ colors: {
518
+ border: "hsl(var(--border))",
519
+ input: "hsl(var(--input))",
520
+ ring: "hsl(var(--ring))",
521
+ background: "hsl(var(--background))",
522
+ foreground: "hsl(var(--foreground))",
523
+ primary: {
524
+ DEFAULT: "hsl(var(--primary))",
525
+ foreground: "hsl(var(--primary-foreground))",
526
+ },
527
+ secondary: {
528
+ DEFAULT: "hsl(var(--secondary))",
529
+ foreground: "hsl(var(--secondary-foreground))",
530
+ },
531
+ destructive: {
532
+ DEFAULT: "hsl(var(--destructive))",
533
+ foreground: "hsl(var(--destructive-foreground))",
534
+ },
535
+ muted: {
536
+ DEFAULT: "hsl(var(--muted))",
537
+ foreground: "hsl(var(--muted-foreground))",
538
+ },
539
+ accent: {
540
+ DEFAULT: "hsl(var(--accent))",
541
+ foreground: "hsl(var(--accent-foreground))",
542
+ },
543
+ popover: {
544
+ DEFAULT: "hsl(var(--popover))",
545
+ foreground: "hsl(var(--popover-foreground))",
546
+ },
547
+ card: {
548
+ DEFAULT: "hsl(var(--card))",
549
+ foreground: "hsl(var(--card-foreground))",
550
+ },
551
+ },
552
+ borderRadius: {
553
+ lg: "var(--radius)",
554
+ md: "calc(var(--radius) - 2px)",
555
+ sm: "calc(var(--radius) - 4px)",
556
+ },
557
+ keyframes: {
558
+ "accordion-down": {
559
+ from: { height: 0 },
560
+ to: { height: "var(--radix-accordion-content-height)" },
561
+ },
562
+ "accordion-up": {
563
+ from: { height: "var(--radix-accordion-content-height)" },
564
+ to: { height: 0 },
565
+ },
566
+ },
567
+ animation: {
568
+ "accordion-down": "accordion-down 0.2s ease-out",
569
+ "accordion-up": "accordion-up 0.2s ease-out",
570
+ },
571
+ },
572
+ },
573
+ plugins: [require("tailwindcss-animate")],
574
+ }`;
575
+ await fs.writeFile(path.join(options.targetDir, 'tailwind.config.js'), tailwindConfig);
576
+ // PostCSS config
577
+ const postcssConfig = `export default {
578
+ plugins: {
579
+ tailwindcss: {},
580
+ autoprefixer: {},
581
+ },
582
+ }`;
583
+ await fs.writeFile(path.join(options.targetDir, 'postcss.config.js'), postcssConfig);
584
+ }
585
+ async function createShadcnConfig(options) {
586
+ const componentsJson = {
587
+ "$schema": "https://ui.shadcn.com/schema.json",
588
+ "style": "default",
589
+ "rsc": false,
590
+ "tsx": true,
591
+ "tailwind": {
592
+ "config": "tailwind.config.js",
593
+ "css": "src/index.css",
594
+ "baseColor": "slate",
595
+ "cssVariables": true
596
+ },
597
+ "aliases": {
598
+ "components": "@/components",
599
+ "utils": "@/lib/utils"
600
+ }
601
+ };
602
+ await fs.writeJSON(path.join(options.targetDir, 'components.json'), componentsJson, { spaces: 2 });
603
+ // Create basic shadcn components (same as Next.js version)
604
+ const componentsDir = path.join(options.targetDir, 'src', 'components', 'ui');
605
+ await fs.ensureDir(componentsDir);
606
+ // Button component (same as Next.js)
607
+ const buttonComponent = `import * as React from "react"
608
+ import { Slot } from "@radix-ui/react-slot"
609
+ import { cva, type VariantProps } from "class-variance-authority"
610
+
611
+ import { cn } from "@/lib/utils"
612
+
613
+ const buttonVariants = cva(
614
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
615
+ {
616
+ variants: {
617
+ variant: {
618
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
619
+ destructive:
620
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
621
+ outline:
622
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
623
+ secondary:
624
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
625
+ ghost: "hover:bg-accent hover:text-accent-foreground",
626
+ link: "text-primary underline-offset-4 hover:underline",
627
+ },
628
+ size: {
629
+ default: "h-10 px-4 py-2",
630
+ sm: "h-9 rounded-md px-3",
631
+ lg: "h-11 rounded-md px-8",
632
+ icon: "h-10 w-10",
633
+ },
634
+ },
635
+ defaultVariants: {
636
+ variant: "default",
637
+ size: "default",
638
+ },
639
+ }
640
+ )
641
+
642
+ export interface ButtonProps
643
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
644
+ VariantProps<typeof buttonVariants> {
645
+ asChild?: boolean
646
+ }
647
+
648
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
649
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
650
+ const Comp = asChild ? Slot : "button"
651
+ return (
652
+ <Comp
653
+ className={cn(buttonVariants({ variant, size, className }))}
654
+ ref={ref}
655
+ {...props}
656
+ />
657
+ )
658
+ }
659
+ )
660
+ Button.displayName = "Button"
661
+
662
+ export { Button, buttonVariants }`;
663
+ await fs.writeFile(path.join(componentsDir, 'button.tsx'), buttonComponent);
664
+ // Card and Badge components (same as Next.js)
665
+ const cardComponent = `import * as React from "react"
666
+
667
+ import { cn } from "@/lib/utils"
668
+
669
+ const Card = React.forwardRef<
670
+ HTMLDivElement,
671
+ React.HTMLAttributes<HTMLDivElement>
672
+ >(({ className, ...props }, ref) => (
673
+ <div
674
+ ref={ref}
675
+ className={cn(
676
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
677
+ className
678
+ )}
679
+ {...props}
680
+ />
681
+ ))
682
+ Card.displayName = "Card"
683
+
684
+ const CardHeader = React.forwardRef<
685
+ HTMLDivElement,
686
+ React.HTMLAttributes<HTMLDivElement>
687
+ >(({ className, ...props }, ref) => (
688
+ <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
689
+ ))
690
+ CardHeader.displayName = "CardHeader"
691
+
692
+ const CardTitle = React.forwardRef<
693
+ HTMLParagraphElement,
694
+ React.HTMLAttributes<HTMLHeadingElement>
695
+ >(({ className, ...props }, ref) => (
696
+ <h3
697
+ ref={ref}
698
+ className={cn(
699
+ "text-2xl font-semibold leading-none tracking-tight",
700
+ className
701
+ )}
702
+ {...props}
703
+ />
704
+ ))
705
+ CardTitle.displayName = "CardTitle"
706
+
707
+ const CardDescription = React.forwardRef<
708
+ HTMLParagraphElement,
709
+ React.HTMLAttributes<HTMLParagraphElement>
710
+ >(({ className, ...props }, ref) => (
711
+ <p
712
+ ref={ref}
713
+ className={cn("text-sm text-muted-foreground", className)}
714
+ {...props}
715
+ />
716
+ ))
717
+ CardDescription.displayName = "CardDescription"
718
+
719
+ const CardContent = React.forwardRef<
720
+ HTMLDivElement,
721
+ React.HTMLAttributes<HTMLDivElement>
722
+ >(({ className, ...props }, ref) => (
723
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
724
+ ))
725
+ CardContent.displayName = "CardContent"
726
+
727
+ const CardFooter = React.forwardRef<
728
+ HTMLDivElement,
729
+ React.HTMLAttributes<HTMLDivElement>
730
+ >(({ className, ...props }, ref) => (
731
+ <div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
732
+ ))
733
+ CardFooter.displayName = "CardFooter"
734
+
735
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }`;
736
+ await fs.writeFile(path.join(componentsDir, 'card.tsx'), cardComponent);
737
+ const badgeComponent = `import * as React from "react"
738
+ import { cva, type VariantProps } from "class-variance-authority"
739
+
740
+ import { cn } from "@/lib/utils"
741
+
742
+ const badgeVariants = cva(
743
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
744
+ {
745
+ variants: {
746
+ variant: {
747
+ default:
748
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
749
+ secondary:
750
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
751
+ destructive:
752
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
753
+ outline: "text-foreground",
754
+ },
755
+ },
756
+ defaultVariants: {
757
+ variant: "default",
758
+ },
759
+ }
760
+ )
761
+
762
+ export interface BadgeProps
763
+ extends React.HTMLAttributes<HTMLDivElement>,
764
+ VariantProps<typeof badgeVariants> {}
765
+
766
+ function Badge({ className, variant, ...props }: BadgeProps) {
767
+ return (
768
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
769
+ )
770
+ }
771
+
772
+ export { Badge, badgeVariants }`;
773
+ await fs.writeFile(path.join(componentsDir, 'badge.tsx'), badgeComponent);
774
+ // Utils file (same as Next.js)
775
+ const libDir = path.join(options.targetDir, 'src', 'lib');
776
+ const utilsFile = `import { type ClassValue, clsx } from "clsx"
777
+ import { twMerge } from "tailwind-merge"
778
+
779
+ export function cn(...inputs: ClassValue[]) {
780
+ return twMerge(clsx(inputs))
781
+ }`;
782
+ await fs.writeFile(path.join(libDir, 'utils.ts'), utilsFile);
783
+ }
784
+ //# sourceMappingURL=vite.js.map