xertica-ui 1.4.4 → 1.4.7
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/cli.ts +292 -372
- package/components/Header.tsx +58 -11
- package/components/TemplateContent.tsx +767 -734
- package/components/ThemeToggle.tsx +66 -64
- package/components/XerticaProvider.tsx +5 -3
- package/components/index.ts +66 -59
- package/contexts/BrandColorsContext.tsx +222 -217
- package/contexts/ThemeContext.tsx +109 -91
- package/dist/cli.js +139 -198
- package/dist/components/Header.d.ts +10 -4
- package/dist/components/ThemeToggle.d.ts +1 -1
- package/dist/components/XerticaProvider.d.ts +3 -1
- package/dist/components/index.d.ts +3 -0
- package/dist/contexts/BrandColorsContext.d.ts +4 -2
- package/dist/contexts/ThemeContext.d.ts +5 -2
- package/dist/index.es.js +306 -126
- package/dist/index.umd.js +305 -125
- package/package.json +126 -136
- package/templates/.env.example +2 -0
- package/templates/eslint.config.js +29 -0
- package/templates/index.css +10 -0
- package/templates/index.html +13 -0
- package/templates/package.json +40 -0
- package/{postcss.config.js → templates/postcss.config.js} +1 -1
- package/{App.tsx → templates/src/App.tsx} +19 -48
- package/templates/src/content/HomeContent.tsx +88 -0
- package/templates/src/content/TemplateContent.tsx +720 -0
- package/templates/src/main.tsx +10 -0
- package/templates/src/pages/ForgotPasswordPage.tsx +148 -0
- package/templates/src/pages/HomePage.tsx +61 -0
- package/templates/src/pages/LoginPage.tsx +183 -0
- package/templates/src/pages/ResetPasswordPage.tsx +199 -0
- package/templates/src/pages/TemplatePage.tsx +40 -0
- package/templates/src/pages/VerifyEmailPage.tsx +166 -0
- package/{routes.tsx → templates/src/routes.tsx} +1 -1
- package/templates/tsconfig.json +21 -0
- package/templates/tsconfig.node.json +12 -0
- package/templates/vite-env.d.ts +1 -0
- package/templates/vite.config.ts +7 -0
- package/eslint.config.js +0 -41
- package/index.css +0 -4364
- package/index.html +0 -14
- package/main.tsx +0 -10
- package/tsconfig.json +0 -46
- package/tsconfig.node.json +0 -10
- package/vite-env.d.ts +0 -12
- package/vite.config.ts +0 -59
package/bin/cli.ts
CHANGED
|
@@ -1,372 +1,292 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import prompts from 'prompts';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import ora from 'ora';
|
|
6
|
-
import fs from 'fs-extra';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { execa } from 'execa';
|
|
10
|
-
import { colorThemes } from '../contexts/theme-data';
|
|
11
|
-
import { generateTokensCss } from './generate-tokens';
|
|
12
|
-
|
|
13
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
-
const __dirname = path.dirname(__filename);
|
|
15
|
-
|
|
16
|
-
const program = new Command();
|
|
17
|
-
|
|
18
|
-
program
|
|
19
|
-
.name('xertica-ui')
|
|
20
|
-
.description('CLI to initialize Xertica UI projects')
|
|
21
|
-
.version('1.0.0');
|
|
22
|
-
|
|
23
|
-
program
|
|
24
|
-
.command('init')
|
|
25
|
-
.description('Initialize a new Xertica UI project')
|
|
26
|
-
.argument('[directory]', 'Directory to initialize in', '.')
|
|
27
|
-
.action(async (directory) => {
|
|
28
|
-
const targetDir = path.resolve(process.cwd(), directory);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
await fs.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Copy
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
console.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
// Ask if user wants to update theme
|
|
294
|
-
const themeResponse = await prompts({
|
|
295
|
-
type: 'confirm',
|
|
296
|
-
name: 'updateTheme',
|
|
297
|
-
message: 'Do you want to update/change the project theme?',
|
|
298
|
-
initial: false
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
if (themeResponse.updateTheme) {
|
|
302
|
-
const themeSelection = await prompts({
|
|
303
|
-
type: 'select',
|
|
304
|
-
name: 'theme',
|
|
305
|
-
message: 'Select the new color theme:',
|
|
306
|
-
choices: colorThemes.map(t => ({
|
|
307
|
-
title: t.name,
|
|
308
|
-
description: t.description,
|
|
309
|
-
value: t.id
|
|
310
|
-
})),
|
|
311
|
-
initial: 0
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
if (themeSelection.theme) {
|
|
315
|
-
const spinnerTheme = ora('Updating theme...').start();
|
|
316
|
-
// 1. Update React Context
|
|
317
|
-
const contextPath = path.join(targetDir, 'contexts', 'BrandColorsContext.tsx');
|
|
318
|
-
if (await fs.pathExists(contextPath)) {
|
|
319
|
-
let content = await fs.readFile(contextPath, 'utf8');
|
|
320
|
-
// Replace existing default. We can try to find any string like "return saved || '...';"
|
|
321
|
-
// The regex needs to be robust to catch whatever is there currently.
|
|
322
|
-
// It might be 'xertica-original' or previously set 'rose', etc.
|
|
323
|
-
// Regex: /return saved \|\| '([a-zA-Z0-9-]+)';/
|
|
324
|
-
content = content.replace(
|
|
325
|
-
/return saved \|\| '([a-zA-Z0-9-]+)';/,
|
|
326
|
-
`return saved || '${themeSelection.theme}';`
|
|
327
|
-
);
|
|
328
|
-
await fs.writeFile(contextPath, content);
|
|
329
|
-
} else {
|
|
330
|
-
spinnerTheme.warn('BrandColorsContext.tsx not found. Skipping context update.');
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// 2. Update styles/xertica/tokens.css
|
|
334
|
-
const tokensPath = path.join(targetDir, 'styles', 'xertica', 'tokens.css');
|
|
335
|
-
const selectedTheme = colorThemes.find(t => t.id === themeSelection.theme);
|
|
336
|
-
|
|
337
|
-
if (selectedTheme) {
|
|
338
|
-
await fs.ensureDir(path.dirname(tokensPath));
|
|
339
|
-
const newTokensCss = generateTokensCss(selectedTheme);
|
|
340
|
-
await fs.writeFile(tokensPath, newTokensCss);
|
|
341
|
-
}
|
|
342
|
-
spinnerTheme.succeed('Theme updated successfully!');
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const spinner = ora('Updating components...').start();
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
let updatedCount = 0;
|
|
350
|
-
for (const component of componentsToUpdate) {
|
|
351
|
-
const srcPath = path.join(sourceUiDir, component);
|
|
352
|
-
const destPath = path.join(uiComponentsDir, component);
|
|
353
|
-
|
|
354
|
-
if (await fs.pathExists(srcPath)) {
|
|
355
|
-
await fs.copy(srcPath, destPath);
|
|
356
|
-
updatedCount++;
|
|
357
|
-
} else {
|
|
358
|
-
spinner.warn(`Component ${component} not found in source.`);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (updatedCount > 0) {
|
|
362
|
-
spinner.succeed(`Successfully updated ${updatedCount} components!`);
|
|
363
|
-
} else {
|
|
364
|
-
spinner.info('No components updated.');
|
|
365
|
-
}
|
|
366
|
-
} catch (error) {
|
|
367
|
-
spinner.fail('Failed to update components');
|
|
368
|
-
console.error(error);
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
program.parse();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { execa } from 'execa';
|
|
10
|
+
import { colorThemes } from '../contexts/theme-data';
|
|
11
|
+
import { generateTokensCss } from './generate-tokens';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name('xertica-ui')
|
|
20
|
+
.description('CLI to initialize Xertica UI projects')
|
|
21
|
+
.version('1.0.0');
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command('init')
|
|
25
|
+
.description('Initialize a new Xertica UI project')
|
|
26
|
+
.argument('[directory]', 'Directory to initialize in', '.')
|
|
27
|
+
.action(async (directory) => {
|
|
28
|
+
const targetDir = path.resolve(process.cwd(), directory);
|
|
29
|
+
// The templates directory is bundled alongside the CLI
|
|
30
|
+
const templatesDir = path.resolve(__dirname, '../templates');
|
|
31
|
+
|
|
32
|
+
console.log(chalk.blue('🚀 Welcome to Xertica UI CLI!'));
|
|
33
|
+
|
|
34
|
+
const response = await prompts([
|
|
35
|
+
{
|
|
36
|
+
type: 'multiselect',
|
|
37
|
+
name: 'pages',
|
|
38
|
+
message: 'Which pages/templates to include?',
|
|
39
|
+
choices: [
|
|
40
|
+
{ title: 'Login Page (+ Forgot / Verify / Reset Password)', value: 'login', selected: true },
|
|
41
|
+
{ title: 'Home Page', value: 'home', selected: true },
|
|
42
|
+
{ title: 'Template Page (components showcase)', value: 'template', selected: true },
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'select',
|
|
47
|
+
name: 'theme',
|
|
48
|
+
message: 'Select the default color theme for your project:',
|
|
49
|
+
choices: colorThemes.map(t => ({
|
|
50
|
+
title: t.name,
|
|
51
|
+
description: t.description,
|
|
52
|
+
value: t.id
|
|
53
|
+
})),
|
|
54
|
+
initial: 0
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'confirm',
|
|
58
|
+
name: 'install',
|
|
59
|
+
message: 'Install dependencies automatically?',
|
|
60
|
+
initial: true
|
|
61
|
+
}
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
if (!response.pages) return;
|
|
65
|
+
|
|
66
|
+
const spinner = ora('Initializing project...').start();
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// 1. Create Target Directory
|
|
70
|
+
await fs.ensureDir(targetDir);
|
|
71
|
+
|
|
72
|
+
const pages = response.pages || [];
|
|
73
|
+
const hasLogin = pages.includes('login');
|
|
74
|
+
const hasHome = pages.includes('home');
|
|
75
|
+
const hasTemplate = pages.includes('template');
|
|
76
|
+
|
|
77
|
+
// 2. Copy root config files from templates/
|
|
78
|
+
const rootFilesToCopy = [
|
|
79
|
+
'index.html',
|
|
80
|
+
'vite.config.ts',
|
|
81
|
+
'tsconfig.json',
|
|
82
|
+
'tsconfig.node.json',
|
|
83
|
+
'postcss.config.js',
|
|
84
|
+
'vite-env.d.ts',
|
|
85
|
+
'eslint.config.js',
|
|
86
|
+
'index.css',
|
|
87
|
+
'.env.example',
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
for (const file of rootFilesToCopy) {
|
|
91
|
+
const srcPath = path.join(templatesDir, file);
|
|
92
|
+
const destPath = path.join(targetDir, file);
|
|
93
|
+
if (await fs.pathExists(srcPath)) {
|
|
94
|
+
await fs.copy(srcPath, destPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 3. Copy package.json template and customize it
|
|
99
|
+
const pkgTemplatePath = path.join(templatesDir, 'package.json');
|
|
100
|
+
if (await fs.pathExists(pkgTemplatePath)) {
|
|
101
|
+
const pkgContent = await fs.readJson(pkgTemplatePath);
|
|
102
|
+
// Use directory name as project name
|
|
103
|
+
const projectName = path.basename(targetDir) || 'my-xertica-app';
|
|
104
|
+
pkgContent.name = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
105
|
+
await fs.writeJson(path.join(targetDir, 'package.json'), pkgContent, { spaces: 2 });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 4. Ensure src directory exists
|
|
109
|
+
await fs.ensureDir(path.join(targetDir, 'src'));
|
|
110
|
+
|
|
111
|
+
// 5. Copy src/App.tsx
|
|
112
|
+
await fs.copy(
|
|
113
|
+
path.join(templatesDir, 'src', 'App.tsx'),
|
|
114
|
+
path.join(targetDir, 'src', 'App.tsx')
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// 6. Copy src/main.tsx
|
|
118
|
+
await fs.copy(
|
|
119
|
+
path.join(templatesDir, 'src', 'main.tsx'),
|
|
120
|
+
path.join(targetDir, 'src', 'main.tsx')
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// 7. Copy src/routes.tsx
|
|
124
|
+
await fs.copy(
|
|
125
|
+
path.join(templatesDir, 'src', 'routes.tsx'),
|
|
126
|
+
path.join(targetDir, 'src', 'routes.tsx')
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// 8. Copy pages
|
|
130
|
+
await fs.ensureDir(path.join(targetDir, 'src', 'pages'));
|
|
131
|
+
|
|
132
|
+
// Always needed pages
|
|
133
|
+
const alwaysCopyPages = ['LoginPage.tsx', 'ForgotPasswordPage.tsx', 'VerifyEmailPage.tsx', 'ResetPasswordPage.tsx'];
|
|
134
|
+
const optionalPages: Record<string, boolean> = {
|
|
135
|
+
'LoginPage.tsx': hasLogin,
|
|
136
|
+
'ForgotPasswordPage.tsx': hasLogin,
|
|
137
|
+
'VerifyEmailPage.tsx': hasLogin,
|
|
138
|
+
'ResetPasswordPage.tsx': hasLogin,
|
|
139
|
+
'HomePage.tsx': hasHome,
|
|
140
|
+
'TemplatePage.tsx': hasTemplate,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const srcPagesDir = path.join(templatesDir, 'src', 'pages');
|
|
144
|
+
for (const [file, shouldCopy] of Object.entries(optionalPages)) {
|
|
145
|
+
if (shouldCopy) {
|
|
146
|
+
const srcPath = path.join(srcPagesDir, file);
|
|
147
|
+
if (await fs.pathExists(srcPath)) {
|
|
148
|
+
await fs.copy(srcPath, path.join(targetDir, 'src', 'pages', file));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 9. Copy content components
|
|
154
|
+
await fs.ensureDir(path.join(targetDir, 'src', 'content'));
|
|
155
|
+
const srcContentDir = path.join(templatesDir, 'src', 'content');
|
|
156
|
+
|
|
157
|
+
if (hasHome && await fs.pathExists(path.join(srcContentDir, 'HomeContent.tsx'))) {
|
|
158
|
+
await fs.copy(
|
|
159
|
+
path.join(srcContentDir, 'HomeContent.tsx'),
|
|
160
|
+
path.join(targetDir, 'src', 'content', 'HomeContent.tsx')
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (hasTemplate && await fs.pathExists(path.join(srcContentDir, 'TemplateContent.tsx'))) {
|
|
165
|
+
await fs.copy(
|
|
166
|
+
path.join(srcContentDir, 'TemplateContent.tsx'),
|
|
167
|
+
path.join(targetDir, 'src', 'content', 'TemplateContent.tsx')
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 10. If NOT all pages selected, create a minimal App.tsx
|
|
172
|
+
if (!hasLogin || !hasHome || !hasTemplate) {
|
|
173
|
+
const imports: string[] = [];
|
|
174
|
+
const routes: string[] = [];
|
|
175
|
+
|
|
176
|
+
if (hasLogin) {
|
|
177
|
+
imports.push(`import { LoginPage } from './pages/LoginPage';`);
|
|
178
|
+
imports.push(`import { ForgotPasswordPage } from './pages/ForgotPasswordPage';`);
|
|
179
|
+
imports.push(`import { VerifyEmailPage } from './pages/VerifyEmailPage';`);
|
|
180
|
+
imports.push(`import { ResetPasswordPage } from './pages/ResetPasswordPage';`);
|
|
181
|
+
routes.push(` <Route path="/login" element={<LoginPage onLogin={handleLogin} />} />`);
|
|
182
|
+
routes.push(` <Route path="/forgot-password" element={<ForgotPasswordPage />} />`);
|
|
183
|
+
routes.push(` <Route path="/verify-email" element={<VerifyEmailPage />} />`);
|
|
184
|
+
routes.push(` <Route path="/reset-password" element={<ResetPasswordPage />} />`);
|
|
185
|
+
}
|
|
186
|
+
if (hasHome) {
|
|
187
|
+
imports.push(`import { HomePage } from './pages/HomePage';`);
|
|
188
|
+
routes.push(` <Route path="/home" element={<HomePage user={user} onLogout={handleLogout} />} />`);
|
|
189
|
+
}
|
|
190
|
+
if (hasTemplate) {
|
|
191
|
+
imports.push(`import { TemplatePage } from './pages/TemplatePage';`);
|
|
192
|
+
routes.push(` <Route path="/template" element={<TemplatePage user={user} onLogout={handleLogout} />} />`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const minimalApp = `import React, { useState, useEffect, useLayoutEffect } from 'react';
|
|
196
|
+
import { BrowserRouter as Router, Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';
|
|
197
|
+
import { XerticaProvider, Toaster } from 'xertica-ui';
|
|
198
|
+
import 'xertica-ui/style.css';
|
|
199
|
+
${imports.join('\n')}
|
|
200
|
+
|
|
201
|
+
export default function App() {
|
|
202
|
+
return (
|
|
203
|
+
<XerticaProvider>
|
|
204
|
+
<Router>
|
|
205
|
+
<Routes>
|
|
206
|
+
${routes.join('\n')}
|
|
207
|
+
<Route path="/" element={<Navigate to="${hasLogin ? '/login' : hasHome ? '/home' : '/template'}" replace />} />
|
|
208
|
+
<Route path="*" element={<Navigate to="${hasLogin ? '/login' : hasHome ? '/home' : '/template'}" replace />} />
|
|
209
|
+
</Routes>
|
|
210
|
+
<Toaster position="top-right" richColors />
|
|
211
|
+
</Router>
|
|
212
|
+
</XerticaProvider>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
`;
|
|
216
|
+
await fs.writeFile(path.join(targetDir, 'src', 'App.tsx'), minimalApp);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 11. Generate theme tokens
|
|
220
|
+
const selectedTheme = colorThemes.find(t => t.id === response.theme) || colorThemes[0];
|
|
221
|
+
|
|
222
|
+
// Create styles/xertica directory
|
|
223
|
+
const tokensDir = path.join(targetDir, 'styles', 'xertica');
|
|
224
|
+
await fs.ensureDir(tokensDir);
|
|
225
|
+
const newTokensCss = generateTokensCss(selectedTheme);
|
|
226
|
+
await fs.writeFile(path.join(tokensDir, 'tokens.css'), newTokensCss);
|
|
227
|
+
|
|
228
|
+
spinner.succeed('Project initialized successfully!');
|
|
229
|
+
|
|
230
|
+
if (response.install) {
|
|
231
|
+
const installSpinner = ora('Installing dependencies...').start();
|
|
232
|
+
await execa('npm', ['install'], { cwd: targetDir });
|
|
233
|
+
installSpinner.succeed('Dependencies installed!');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log(chalk.green('\n✅ Done! Your Xertica UI project is ready.'));
|
|
237
|
+
console.log(chalk.cyan(`\n cd ${directory}`));
|
|
238
|
+
if (!response.install) {
|
|
239
|
+
console.log(chalk.cyan(' npm install'));
|
|
240
|
+
}
|
|
241
|
+
console.log(chalk.cyan(' npm run dev'));
|
|
242
|
+
console.log();
|
|
243
|
+
console.log(chalk.gray(' Components are imported from the xertica-ui package.'));
|
|
244
|
+
console.log(chalk.gray(' Customize the theme in styles/xertica/tokens.css'));
|
|
245
|
+
|
|
246
|
+
} catch (error) {
|
|
247
|
+
spinner.fail('Failed to initialize project');
|
|
248
|
+
console.error(error);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
program
|
|
253
|
+
.command('update')
|
|
254
|
+
.description('Update theme tokens in your project')
|
|
255
|
+
.action(async () => {
|
|
256
|
+
const targetDir = process.cwd();
|
|
257
|
+
|
|
258
|
+
const themeResponse = await prompts({
|
|
259
|
+
type: 'select',
|
|
260
|
+
name: 'theme',
|
|
261
|
+
message: 'Select the new color theme:',
|
|
262
|
+
choices: colorThemes.map(t => ({
|
|
263
|
+
title: t.name,
|
|
264
|
+
description: t.description,
|
|
265
|
+
value: t.id
|
|
266
|
+
})),
|
|
267
|
+
initial: 0
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (!themeResponse.theme) return;
|
|
271
|
+
|
|
272
|
+
const spinner = ora('Updating theme...').start();
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const tokensPath = path.join(targetDir, 'styles', 'xertica', 'tokens.css');
|
|
276
|
+
const selectedTheme = colorThemes.find(t => t.id === themeResponse.theme);
|
|
277
|
+
|
|
278
|
+
if (selectedTheme) {
|
|
279
|
+
await fs.ensureDir(path.dirname(tokensPath));
|
|
280
|
+
const newTokensCss = generateTokensCss(selectedTheme);
|
|
281
|
+
await fs.writeFile(tokensPath, newTokensCss);
|
|
282
|
+
spinner.succeed(`Theme updated to "${selectedTheme.name}" successfully!`);
|
|
283
|
+
} else {
|
|
284
|
+
spinner.fail('Theme not found.');
|
|
285
|
+
}
|
|
286
|
+
} catch (error) {
|
|
287
|
+
spinner.fail('Failed to update theme');
|
|
288
|
+
console.error(error);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
program.parse();
|