create-aziz-app 2.0.3 ā 2.0.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/bin/create-aziz-app.js +521 -3
- package/package.json +1 -1
- package/template/README.md +8 -0
- package/template/app.html +3 -108
- package/template/docs/CSS-FRAMEWORKS.md +221 -0
- package/template/docs/STYLING.md +269 -0
- package/template/package.json +1 -1
- package/template/styles/globals.css +190 -0
- package/template/styles/tailwind.css +122 -0
package/bin/create-aziz-app.js
CHANGED
|
@@ -62,10 +62,22 @@ async function main() {
|
|
|
62
62
|
const includeApiExamples = await question(' Include example API routes? (Y/n): ');
|
|
63
63
|
const includeDocs = await question(' Include documentation? (Y/n): ');
|
|
64
64
|
|
|
65
|
+
// Ask for CSS framework
|
|
66
|
+
console.log('\nšØ CSS Framework:');
|
|
67
|
+
console.log(' 1. None (custom CSS)');
|
|
68
|
+
console.log(' 2. Bootstrap 5');
|
|
69
|
+
console.log(' 3. Tailwind CSS');
|
|
70
|
+
const cssChoice = await question(' Choose (1/2/3): ');
|
|
71
|
+
|
|
72
|
+
let cssFramework = 'none';
|
|
73
|
+
if (cssChoice === '2') cssFramework = 'bootstrap';
|
|
74
|
+
else if (cssChoice === '3') cssFramework = 'tailwind';
|
|
75
|
+
|
|
65
76
|
const features = {
|
|
66
77
|
examples: includeExamples.toLowerCase() !== 'n' && includeExamples.toLowerCase() !== 'no',
|
|
67
78
|
apiExamples: includeApiExamples.toLowerCase() !== 'n' && includeApiExamples.toLowerCase() !== 'no',
|
|
68
|
-
docs: includeDocs.toLowerCase() !== 'n' && includeDocs.toLowerCase() !== 'no'
|
|
79
|
+
docs: includeDocs.toLowerCase() !== 'n' && includeDocs.toLowerCase() !== 'no',
|
|
80
|
+
cssFramework: cssFramework
|
|
69
81
|
};
|
|
70
82
|
|
|
71
83
|
console.log(`\nā” Creating "${projectName}"...\n`);
|
|
@@ -81,6 +93,9 @@ async function main() {
|
|
|
81
93
|
// Update dev server port
|
|
82
94
|
updateDevServerPort(targetDir, port);
|
|
83
95
|
|
|
96
|
+
// Setup CSS framework
|
|
97
|
+
setupCSSFramework(targetDir, features.cssFramework);
|
|
98
|
+
|
|
84
99
|
console.log(`ā
Done! Your project is ready.\n`);
|
|
85
100
|
console.log(`š Welcome to Aziz App - A React-like Framework\n`);
|
|
86
101
|
console.log(`š Created project: ${projectName}\n`);
|
|
@@ -104,8 +119,13 @@ async function main() {
|
|
|
104
119
|
console.log(` ⢠File-based Routing - pages/About.jsx ā #/about`);
|
|
105
120
|
console.log(` ⢠API Routes - Next.js-like API system`);
|
|
106
121
|
console.log(` ⢠Auto-Import - No manual script tags!`);
|
|
107
|
-
console.log(` ⢠Live Reload - Instant browser refresh
|
|
108
|
-
|
|
122
|
+
console.log(` ⢠Live Reload - Instant browser refresh`);
|
|
123
|
+
if (features.cssFramework === 'bootstrap') {
|
|
124
|
+
console.log(` ⢠Bootstrap 5 - Responsive CSS framework`);
|
|
125
|
+
} else if (features.cssFramework === 'tailwind') {
|
|
126
|
+
console.log(` ⢠Tailwind CSS - Utility-first CSS framework`);
|
|
127
|
+
}
|
|
128
|
+
console.log(`\nš” Quick tips:\n`);
|
|
109
129
|
console.log(` ⢠Create pages/MyPage.jsx ā Auto-imported!`);
|
|
110
130
|
console.log(` ⢠Create api/todos.js ā Auto-imported!`);
|
|
111
131
|
console.log(` ⢠Just save and browser refreshes automatically`);
|
|
@@ -170,6 +190,504 @@ function updateDevServerPort(targetDir, port) {
|
|
|
170
190
|
}
|
|
171
191
|
}
|
|
172
192
|
|
|
193
|
+
// Setup CSS framework
|
|
194
|
+
function setupCSSFramework(targetDir, framework) {
|
|
195
|
+
const htmlPath = path.join(targetDir, 'app.html');
|
|
196
|
+
if (!fs.existsSync(htmlPath)) return;
|
|
197
|
+
|
|
198
|
+
let html = fs.readFileSync(htmlPath, 'utf8');
|
|
199
|
+
|
|
200
|
+
if (framework === 'bootstrap') {
|
|
201
|
+
// Replace globals.css with Bootstrap CDN
|
|
202
|
+
html = html.replace(
|
|
203
|
+
'<link rel="stylesheet" href="styles/globals.css">',
|
|
204
|
+
`<!-- Bootstrap 5 CSS -->
|
|
205
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
206
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">`
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Add Bootstrap JS before closing body tag
|
|
210
|
+
const bootstrapJS = `
|
|
211
|
+
<!-- Bootstrap 5 JS -->
|
|
212
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
213
|
+
|
|
214
|
+
`;
|
|
215
|
+
html = html.replace('</body>', bootstrapJS + '</body>');
|
|
216
|
+
|
|
217
|
+
// Update components
|
|
218
|
+
updateComponentsForBootstrap(targetDir);
|
|
219
|
+
|
|
220
|
+
} else if (framework === 'tailwind') {
|
|
221
|
+
// Replace globals.css with Tailwind CSS
|
|
222
|
+
html = html.replace(
|
|
223
|
+
'<link rel="stylesheet" href="styles/globals.css">',
|
|
224
|
+
`<!-- Tailwind CSS -->
|
|
225
|
+
<link rel="stylesheet" href="styles/tailwind.css">
|
|
226
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
227
|
+
<script>
|
|
228
|
+
tailwind.config = {
|
|
229
|
+
theme: {
|
|
230
|
+
extend: {
|
|
231
|
+
colors: {
|
|
232
|
+
primary: '#61dafb',
|
|
233
|
+
dark: '#282c34'
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
</script>`
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// Update components
|
|
242
|
+
updateComponentsForTailwind(targetDir);
|
|
243
|
+
}
|
|
244
|
+
// If 'none', keep globals.css as is
|
|
245
|
+
|
|
246
|
+
fs.writeFileSync(htmlPath, html);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Update components for Bootstrap
|
|
250
|
+
function updateComponentsForBootstrap(targetDir) {
|
|
251
|
+
// Update Navbar
|
|
252
|
+
const navbarPath = path.join(targetDir, 'components', 'Navbar.jsx');
|
|
253
|
+
if (fs.existsSync(navbarPath)) {
|
|
254
|
+
const navbarContent = `/** @jsx h */
|
|
255
|
+
|
|
256
|
+
function Navbar() {
|
|
257
|
+
const path = getCurrentPath();
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
|
261
|
+
<div class="container-fluid">
|
|
262
|
+
<span class="navbar-brand" onClick={() => navigate("home")} style={{ cursor: 'pointer' }}>
|
|
263
|
+
ā” AzizApp
|
|
264
|
+
</span>
|
|
265
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
266
|
+
<span class="navbar-toggler-icon"></span>
|
|
267
|
+
</button>
|
|
268
|
+
<div class="collapse navbar-collapse" id="navbarNav">
|
|
269
|
+
<ul class="navbar-nav">
|
|
270
|
+
<li class="nav-item">
|
|
271
|
+
<a href="#/home" class={\`nav-link \${path === "home" ? "active" : ""}\`}>Home</a>
|
|
272
|
+
</li>
|
|
273
|
+
<li class="nav-item">
|
|
274
|
+
<a href="#/about" class={\`nav-link \${path === "about" ? "active" : ""}\`}>About</a>
|
|
275
|
+
</li>
|
|
276
|
+
<li class="nav-item">
|
|
277
|
+
<a href="#/contact" class={\`nav-link \${path === "contact" ? "active" : ""}\`}>Contact</a>
|
|
278
|
+
</li>
|
|
279
|
+
<li class="nav-item">
|
|
280
|
+
<a href="#/test" class={\`nav-link \${path === "test" ? "active" : ""}\`}>Test</a>
|
|
281
|
+
</li>
|
|
282
|
+
<li class="nav-item">
|
|
283
|
+
<a href="#/api-demo" class={\`nav-link \${path === "api-demo" ? "active" : ""}\`}>API Demo</a>
|
|
284
|
+
</li>
|
|
285
|
+
</ul>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
</nav>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
`;
|
|
292
|
+
fs.writeFileSync(navbarPath, navbarContent);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Update Footer
|
|
296
|
+
const footerPath = path.join(targetDir, 'components', 'Footer.jsx');
|
|
297
|
+
if (fs.existsSync(footerPath)) {
|
|
298
|
+
const footerContent = `/** @jsx h */
|
|
299
|
+
|
|
300
|
+
function Footer() {
|
|
301
|
+
return (
|
|
302
|
+
<footer class="bg-dark text-light text-center py-3 mt-auto">
|
|
303
|
+
<div class="container">
|
|
304
|
+
<p class="mb-0">Made with ā” AzizApp</p>
|
|
305
|
+
</div>
|
|
306
|
+
</footer>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
`;
|
|
310
|
+
fs.writeFileSync(footerPath, footerContent);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Update all pages
|
|
314
|
+
updatePagesForBootstrap(targetDir);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Update components for Tailwind
|
|
318
|
+
function updateComponentsForTailwind(targetDir) {
|
|
319
|
+
// Update Navbar
|
|
320
|
+
const navbarPath = path.join(targetDir, 'components', 'Navbar.jsx');
|
|
321
|
+
if (fs.existsSync(navbarPath)) {
|
|
322
|
+
const navbarContent = `/** @jsx h */
|
|
323
|
+
|
|
324
|
+
function Navbar() {
|
|
325
|
+
const path = getCurrentPath();
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<nav class="bg-dark shadow-lg">
|
|
329
|
+
<div class="container mx-auto px-4">
|
|
330
|
+
<div class="flex items-center justify-between h-16">
|
|
331
|
+
<span
|
|
332
|
+
class="text-primary text-xl font-bold cursor-pointer hover:text-blue-300 transition"
|
|
333
|
+
onClick={() => navigate("home")}
|
|
334
|
+
>
|
|
335
|
+
ā” AzizApp
|
|
336
|
+
</span>
|
|
337
|
+
<div class="flex space-x-4">
|
|
338
|
+
<a href="#/home" class={\`px-3 py-2 rounded-md text-sm font-medium transition \${path === "home" ? "bg-primary text-dark" : "text-gray-300 hover:bg-gray-700 hover:text-white"}\`}>
|
|
339
|
+
Home
|
|
340
|
+
</a>
|
|
341
|
+
<a href="#/about" class={\`px-3 py-2 rounded-md text-sm font-medium transition \${path === "about" ? "bg-primary text-dark" : "text-gray-300 hover:bg-gray-700 hover:text-white"}\`}>
|
|
342
|
+
About
|
|
343
|
+
</a>
|
|
344
|
+
<a href="#/contact" class={\`px-3 py-2 rounded-md text-sm font-medium transition \${path === "contact" ? "bg-primary text-dark" : "text-gray-300 hover:bg-gray-700 hover:text-white"}\`}>
|
|
345
|
+
Contact
|
|
346
|
+
</a>
|
|
347
|
+
<a href="#/test" class={\`px-3 py-2 rounded-md text-sm font-medium transition \${path === "test" ? "bg-primary text-dark" : "text-gray-300 hover:bg-gray-700 hover:text-white"}\`}>
|
|
348
|
+
Test
|
|
349
|
+
</a>
|
|
350
|
+
<a href="#/api-demo" class={\`px-3 py-2 rounded-md text-sm font-medium transition \${path === "api-demo" ? "bg-primary text-dark" : "text-gray-300 hover:bg-gray-700 hover:text-white"}\`}>
|
|
351
|
+
API Demo
|
|
352
|
+
</a>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
</nav>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
`;
|
|
360
|
+
fs.writeFileSync(navbarPath, navbarContent);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Update Footer
|
|
364
|
+
const footerPath = path.join(targetDir, 'components', 'Footer.jsx');
|
|
365
|
+
if (fs.existsSync(footerPath)) {
|
|
366
|
+
const footerContent = `/** @jsx h */
|
|
367
|
+
|
|
368
|
+
function Footer() {
|
|
369
|
+
return (
|
|
370
|
+
<footer class="bg-dark text-gray-400 text-center py-4 mt-auto">
|
|
371
|
+
<div class="container mx-auto">
|
|
372
|
+
<p>Made with ā” AzizApp</p>
|
|
373
|
+
</div>
|
|
374
|
+
</footer>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
378
|
+
fs.writeFileSync(footerPath, footerContent);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Update all pages
|
|
382
|
+
updatePagesForTailwind(targetDir);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Update all pages for Bootstrap
|
|
386
|
+
function updatePagesForBootstrap(targetDir) {
|
|
387
|
+
const pagesDir = path.join(targetDir, 'pages');
|
|
388
|
+
if (!fs.existsSync(pagesDir)) return;
|
|
389
|
+
|
|
390
|
+
// Update Home.jsx
|
|
391
|
+
const homePath = path.join(pagesDir, 'Home.jsx');
|
|
392
|
+
if (fs.existsSync(homePath)) {
|
|
393
|
+
fs.writeFileSync(homePath, getBootstrapHomePage());
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Update test.jsx
|
|
397
|
+
const testPath = path.join(pagesDir, 'test.jsx');
|
|
398
|
+
if (fs.existsSync(testPath)) {
|
|
399
|
+
fs.writeFileSync(testPath, getBootstrapTestPage());
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// About and Contact pages are simple, they work with any framework
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Update all pages for Tailwind
|
|
406
|
+
function updatePagesForTailwind(targetDir) {
|
|
407
|
+
const pagesDir = path.join(targetDir, 'pages');
|
|
408
|
+
if (!fs.existsSync(pagesDir)) return;
|
|
409
|
+
|
|
410
|
+
// Update Home.jsx
|
|
411
|
+
const homePath = path.join(pagesDir, 'Home.jsx');
|
|
412
|
+
if (fs.existsSync(homePath)) {
|
|
413
|
+
fs.writeFileSync(homePath, getTailwindHomePage());
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Update test.jsx
|
|
417
|
+
const testPath = path.join(pagesDir, 'test.jsx');
|
|
418
|
+
if (fs.existsSync(testPath)) {
|
|
419
|
+
fs.writeFileSync(testPath, getTailwindTestPage());
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Bootstrap Home Page
|
|
424
|
+
function getBootstrapHomePage() {
|
|
425
|
+
return `/** @jsx h */
|
|
426
|
+
|
|
427
|
+
registerPage("home", HomePage);
|
|
428
|
+
|
|
429
|
+
function HomePage() {
|
|
430
|
+
return (
|
|
431
|
+
<div class="container py-5">
|
|
432
|
+
<h1 class="display-4 mb-4">Welcome Home š</h1>
|
|
433
|
+
|
|
434
|
+
<div class="card mb-4">
|
|
435
|
+
<div class="card-body">
|
|
436
|
+
<h2 class="card-title">Global State Counter</h2>
|
|
437
|
+
<p class="card-text">This uses the global state. Click the buttons to update it.</p>
|
|
438
|
+
|
|
439
|
+
<div class="text-center my-4">
|
|
440
|
+
<h1 class="display-1 fw-bold text-primary">{state.count}</h1>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<div class="d-flex gap-2 justify-content-center">
|
|
444
|
+
<button class="btn btn-danger" onClick={() => setState({ count: state.count - 1 })}>ā</button>
|
|
445
|
+
<button class="btn btn-primary" onClick={() => setState({ count: state.count + 1 })}>+</button>
|
|
446
|
+
<button class="btn btn-secondary" onClick={() => setState({ count: 0 })}>Reset</button>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
<div class="card">
|
|
452
|
+
<div class="card-body">
|
|
453
|
+
<h2 class="card-title">File-based Routing</h2>
|
|
454
|
+
<p class="card-text">
|
|
455
|
+
Each file in <strong>pages/</strong> is a route ā just like Next.js.
|
|
456
|
+
Add a new file, run the build script, and it's a new page.
|
|
457
|
+
</p>
|
|
458
|
+
<div class="d-flex gap-2">
|
|
459
|
+
<button class="btn btn-primary" onClick={() => navigate("test")}>
|
|
460
|
+
Go to Test Page ā
|
|
461
|
+
</button>
|
|
462
|
+
<button class="btn btn-outline-primary" onClick={() => navigate("about")}>
|
|
463
|
+
Go to About ā
|
|
464
|
+
</button>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
`;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Tailwind Home Page
|
|
475
|
+
function getTailwindHomePage() {
|
|
476
|
+
return `/** @jsx h */
|
|
477
|
+
|
|
478
|
+
registerPage("home", HomePage);
|
|
479
|
+
|
|
480
|
+
function HomePage() {
|
|
481
|
+
return (
|
|
482
|
+
<div class="container mx-auto py-10 px-4">
|
|
483
|
+
<h1 class="text-4xl font-bold mb-6 text-gray-800">Welcome Home š</h1>
|
|
484
|
+
|
|
485
|
+
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
|
486
|
+
<h2 class="text-2xl font-bold mb-3 text-gray-800">Global State Counter</h2>
|
|
487
|
+
<p class="text-gray-600 mb-4">This uses the global state. Click the buttons to update it.</p>
|
|
488
|
+
|
|
489
|
+
<div class="text-center my-6">
|
|
490
|
+
<h1 class="text-7xl font-bold text-primary">{state.count}</h1>
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<div class="flex gap-3 justify-center">
|
|
494
|
+
<button class="px-6 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition" onClick={() => setState({ count: state.count - 1 })}>ā</button>
|
|
495
|
+
<button class="px-6 py-2 bg-primary text-dark font-bold rounded-lg hover:opacity-80 transition" onClick={() => setState({ count: state.count + 1 })}>+</button>
|
|
496
|
+
<button class="px-6 py-2 bg-gray-400 text-white rounded-lg hover:bg-gray-500 transition" onClick={() => setState({ count: 0 })}>Reset</button>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
|
|
500
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
501
|
+
<h2 class="text-2xl font-bold mb-3 text-gray-800">File-based Routing</h2>
|
|
502
|
+
<p class="text-gray-600 mb-4">
|
|
503
|
+
Each file in <strong>pages/</strong> is a route ā just like Next.js.
|
|
504
|
+
Add a new file, run the build script, and it's a new page.
|
|
505
|
+
</p>
|
|
506
|
+
<div class="flex gap-3">
|
|
507
|
+
<button class="px-6 py-2 bg-primary text-dark font-bold rounded-lg hover:opacity-80 transition" onClick={() => navigate("test")}>
|
|
508
|
+
Go to Test Page ā
|
|
509
|
+
</button>
|
|
510
|
+
<button class="px-6 py-2 border-2 border-primary text-primary rounded-lg hover:bg-primary hover:text-dark transition" onClick={() => navigate("about")}>
|
|
511
|
+
Go to About ā
|
|
512
|
+
</button>
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Bootstrap Test Page
|
|
522
|
+
function getBootstrapTestPage() {
|
|
523
|
+
return `/** @jsx h */
|
|
524
|
+
|
|
525
|
+
registerPage("test", TestPage);
|
|
526
|
+
|
|
527
|
+
function TestPage() {
|
|
528
|
+
const [count, setCount] = useState(0);
|
|
529
|
+
const [name, setName] = useState('');
|
|
530
|
+
const [email, setEmail] = useState('');
|
|
531
|
+
|
|
532
|
+
const handleSubmit = (e) => {
|
|
533
|
+
e.preventDefault();
|
|
534
|
+
alert(\`Form submitted!\\nName: \${name}\\nEmail: \${email}\\nCount: \${count}\`);
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
return (
|
|
538
|
+
<div class="container py-5">
|
|
539
|
+
<h1 class="display-4 mb-4">Test Page š§Ŗ</h1>
|
|
540
|
+
|
|
541
|
+
<div class="card mb-4">
|
|
542
|
+
<div class="card-body">
|
|
543
|
+
<h2 class="card-title">Counter Example</h2>
|
|
544
|
+
<p class="card-text">This demonstrates the state management system.</p>
|
|
545
|
+
|
|
546
|
+
<div class="text-center my-4">
|
|
547
|
+
<h1 class="display-1 fw-bold text-primary">{count}</h1>
|
|
548
|
+
</div>
|
|
549
|
+
<div class="d-flex gap-2 justify-content-center">
|
|
550
|
+
<button class="btn btn-primary" onClick={() => setCount(count + 1)}>
|
|
551
|
+
Increment
|
|
552
|
+
</button>
|
|
553
|
+
<button class="btn btn-danger" onClick={() => setCount(0)}>
|
|
554
|
+
Reset
|
|
555
|
+
</button>
|
|
556
|
+
</div>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
|
|
560
|
+
<div class="card mb-4">
|
|
561
|
+
<div class="card-body">
|
|
562
|
+
<h2 class="card-title">Form Example</h2>
|
|
563
|
+
<p class="card-text">Try the form with state management.</p>
|
|
564
|
+
|
|
565
|
+
<form onSubmit={handleSubmit}>
|
|
566
|
+
<div class="mb-3">
|
|
567
|
+
<label class="form-label">Name:</label>
|
|
568
|
+
<input
|
|
569
|
+
type="text"
|
|
570
|
+
class="form-control"
|
|
571
|
+
value={name}
|
|
572
|
+
onInput={(e) => setName(e.target.value)}
|
|
573
|
+
placeholder="Enter your name"
|
|
574
|
+
/>
|
|
575
|
+
</div>
|
|
576
|
+
|
|
577
|
+
<div class="mb-3">
|
|
578
|
+
<label class="form-label">Email:</label>
|
|
579
|
+
<input
|
|
580
|
+
type="email"
|
|
581
|
+
class="form-control"
|
|
582
|
+
value={email}
|
|
583
|
+
onInput={(e) => setEmail(e.target.value)}
|
|
584
|
+
placeholder="Enter your email"
|
|
585
|
+
/>
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
<button type="submit" class="btn btn-primary">
|
|
589
|
+
Submit Form
|
|
590
|
+
</button>
|
|
591
|
+
</form>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
|
|
595
|
+
<div class="card">
|
|
596
|
+
<div class="card-body">
|
|
597
|
+
<h2 class="card-title">State Information</h2>
|
|
598
|
+
<p class="mb-2">Current count: <strong class="text-primary">{count}</strong></p>
|
|
599
|
+
<p class="mb-2">Current name: <strong class="text-primary">{name || 'Not set'}</strong></p>
|
|
600
|
+
<p class="mb-0">Current email: <strong class="text-primary">{email || 'Not set'}</strong></p>
|
|
601
|
+
</div>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
`;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Tailwind Test Page
|
|
610
|
+
function getTailwindTestPage() {
|
|
611
|
+
return `/** @jsx h */
|
|
612
|
+
|
|
613
|
+
registerPage("test", TestPage);
|
|
614
|
+
|
|
615
|
+
function TestPage() {
|
|
616
|
+
const [count, setCount] = useState(0);
|
|
617
|
+
const [name, setName] = useState('');
|
|
618
|
+
const [email, setEmail] = useState('');
|
|
619
|
+
|
|
620
|
+
const handleSubmit = (e) => {
|
|
621
|
+
e.preventDefault();
|
|
622
|
+
alert(\`Form submitted!\\nName: \${name}\\nEmail: \${email}\\nCount: \${count}\`);
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
return (
|
|
626
|
+
<div class="container mx-auto py-10 px-4">
|
|
627
|
+
<h1 class="text-4xl font-bold mb-6 text-gray-800">Test Page š§Ŗ</h1>
|
|
628
|
+
|
|
629
|
+
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
|
630
|
+
<h2 class="text-2xl font-bold mb-3 text-gray-800">Counter Example</h2>
|
|
631
|
+
<p class="text-gray-600 mb-4">This demonstrates the state management system.</p>
|
|
632
|
+
|
|
633
|
+
<div class="text-center my-6">
|
|
634
|
+
<h1 class="text-7xl font-bold text-primary">{count}</h1>
|
|
635
|
+
</div>
|
|
636
|
+
<div class="flex gap-3 justify-center">
|
|
637
|
+
<button class="px-6 py-2 bg-primary text-dark font-bold rounded-lg hover:opacity-80 transition" onClick={() => setCount(count + 1)}>
|
|
638
|
+
Increment
|
|
639
|
+
</button>
|
|
640
|
+
<button class="px-6 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition" onClick={() => setCount(0)}>
|
|
641
|
+
Reset
|
|
642
|
+
</button>
|
|
643
|
+
</div>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
|
647
|
+
<h2 class="text-2xl font-bold mb-3 text-gray-800">Form Example</h2>
|
|
648
|
+
<p class="text-gray-600 mb-4">Try the form with state management.</p>
|
|
649
|
+
|
|
650
|
+
<form onSubmit={handleSubmit} class="space-y-4">
|
|
651
|
+
<div>
|
|
652
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Name:</label>
|
|
653
|
+
<input
|
|
654
|
+
type="text"
|
|
655
|
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none transition"
|
|
656
|
+
value={name}
|
|
657
|
+
onInput={(e) => setName(e.target.value)}
|
|
658
|
+
placeholder="Enter your name"
|
|
659
|
+
/>
|
|
660
|
+
</div>
|
|
661
|
+
|
|
662
|
+
<div>
|
|
663
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Email:</label>
|
|
664
|
+
<input
|
|
665
|
+
type="email"
|
|
666
|
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none transition"
|
|
667
|
+
value={email}
|
|
668
|
+
onInput={(e) => setEmail(e.target.value)}
|
|
669
|
+
placeholder="Enter your email"
|
|
670
|
+
/>
|
|
671
|
+
</div>
|
|
672
|
+
|
|
673
|
+
<button type="submit" class="px-6 py-2 bg-primary text-dark font-bold rounded-lg hover:opacity-80 transition">
|
|
674
|
+
Submit Form
|
|
675
|
+
</button>
|
|
676
|
+
</form>
|
|
677
|
+
</div>
|
|
678
|
+
|
|
679
|
+
<div class="bg-white rounded-lg shadow-md p-6">
|
|
680
|
+
<h2 class="text-2xl font-bold mb-3 text-gray-800">State Information</h2>
|
|
681
|
+
<p class="mb-2 text-gray-700">Current count: <strong class="text-primary">{count}</strong></p>
|
|
682
|
+
<p class="mb-2 text-gray-700">Current name: <strong class="text-primary">{name || 'Not set'}</strong></p>
|
|
683
|
+
<p class="mb-0 text-gray-700">Current email: <strong class="text-primary">{email || 'Not set'}</strong></p>
|
|
684
|
+
</div>
|
|
685
|
+
</div>
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
`;
|
|
689
|
+
}
|
|
690
|
+
|
|
173
691
|
// Run main function
|
|
174
692
|
main().catch(err => {
|
|
175
693
|
console.error('ā Error:', err);
|
package/package.json
CHANGED
package/template/README.md
CHANGED
|
@@ -122,6 +122,14 @@ const response = await fetchApi('/api/todos');
|
|
|
122
122
|
const data = await response.json();
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
### CSS Frameworks
|
|
126
|
+
Choose your preferred styling:
|
|
127
|
+
- **Custom CSS** - Built-in responsive styles
|
|
128
|
+
- **Bootstrap 5** - Popular component framework
|
|
129
|
+
- **Tailwind CSS** - Utility-first framework
|
|
130
|
+
|
|
131
|
+
The CLI will automatically set up your chosen framework!
|
|
132
|
+
|
|
125
133
|
### Auto-Import
|
|
126
134
|
- Create files in `pages/`, `components/`, or `api/`
|
|
127
135
|
- Dev server automatically detects them
|