xertica-ui 1.1.0 → 1.2.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/bin/cli.ts +49 -7
- package/bin/generate-tokens.ts +230 -0
- package/contexts/BrandColorsContext.tsx +55 -389
- package/contexts/theme-data.ts +340 -0
- package/dist/cli.js +528 -2
- package/package.json +2 -2
package/bin/cli.ts
CHANGED
|
@@ -7,6 +7,8 @@ import fs from 'fs-extra';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { execa } from 'execa';
|
|
10
|
+
import { colorThemes } from '../contexts/theme-data';
|
|
11
|
+
import { generateTokensCss } from './generate-tokens';
|
|
10
12
|
|
|
11
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
14
|
const __dirname = path.dirname(__filename);
|
|
@@ -34,7 +36,7 @@ program
|
|
|
34
36
|
const files = await fs.readdir(uiComponentsDir);
|
|
35
37
|
const components = files
|
|
36
38
|
.filter(f => f.endsWith('.tsx') && !f.startsWith('index'))
|
|
37
|
-
.map(f => ({ title: f.replace('.tsx', ''), value: f }));
|
|
39
|
+
.map(f => ({ title: f.replace('.tsx', ''), value: f, selected: true }));
|
|
38
40
|
|
|
39
41
|
componentChoices = [
|
|
40
42
|
{ title: 'All Components', value: 'all', selected: true },
|
|
@@ -42,6 +44,8 @@ program
|
|
|
42
44
|
];
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
|
|
48
|
+
|
|
45
49
|
console.log(chalk.blue('🚀 Welcome to Xertica UI CLI!'));
|
|
46
50
|
|
|
47
51
|
const response = await prompts([
|
|
@@ -49,7 +53,12 @@ program
|
|
|
49
53
|
type: 'multiselect',
|
|
50
54
|
name: 'components',
|
|
51
55
|
message: 'Which components would you like to include?',
|
|
52
|
-
choices: componentChoices
|
|
56
|
+
choices: componentChoices.length > 1 ? componentChoices : [
|
|
57
|
+
{ title: 'All Components', value: 'all', selected: true },
|
|
58
|
+
// Fetch components logic was above, I'll rely on existing code for that part
|
|
59
|
+
// and just insert my code around existing blocks in separate steps if needed.
|
|
60
|
+
// But replace_file_content requires me to match content.
|
|
61
|
+
],
|
|
53
62
|
hint: '- Space to select. Return to submit',
|
|
54
63
|
min: 1
|
|
55
64
|
},
|
|
@@ -63,6 +72,17 @@ program
|
|
|
63
72
|
{ title: 'Template Page', value: 'template', selected: true },
|
|
64
73
|
]
|
|
65
74
|
},
|
|
75
|
+
{
|
|
76
|
+
type: 'select',
|
|
77
|
+
name: 'theme',
|
|
78
|
+
message: 'Select the default color theme for your project:',
|
|
79
|
+
choices: colorThemes.map(t => ({
|
|
80
|
+
title: t.name,
|
|
81
|
+
description: t.description,
|
|
82
|
+
value: t.id
|
|
83
|
+
})),
|
|
84
|
+
initial: 0
|
|
85
|
+
},
|
|
66
86
|
{
|
|
67
87
|
type: 'confirm',
|
|
68
88
|
name: 'install',
|
|
@@ -143,11 +163,7 @@ export default function App() {
|
|
|
143
163
|
if (basename === 'node_modules') return false;
|
|
144
164
|
|
|
145
165
|
// Component filtering
|
|
146
|
-
|
|
147
|
-
// Or just check if basename is in the list.
|
|
148
|
-
// Warning: 'button.tsx' might be in other places? Unlikely in flat structure components/ui
|
|
149
|
-
// Ideally we check if it is inside components/ui
|
|
150
|
-
const isUI = src.includes(path.join('components', 'ui')); // Simple check
|
|
166
|
+
const isUI = src.includes(path.join('components', 'ui'));
|
|
151
167
|
|
|
152
168
|
if (isUI && folder === 'components') {
|
|
153
169
|
if (basename === 'index.ts') return true;
|
|
@@ -177,6 +193,32 @@ export default function App() {
|
|
|
177
193
|
}
|
|
178
194
|
}
|
|
179
195
|
|
|
196
|
+
// --- Apply Selected Theme ---
|
|
197
|
+
if (response.theme) {
|
|
198
|
+
// 1. Update React Context
|
|
199
|
+
const contextPath = path.join(targetDir, 'contexts', 'BrandColorsContext.tsx');
|
|
200
|
+
if (await fs.pathExists(contextPath)) {
|
|
201
|
+
let content = await fs.readFile(contextPath, 'utf8');
|
|
202
|
+
content = content.replace(
|
|
203
|
+
/return saved \|\| 'xertica-original';/,
|
|
204
|
+
`return saved || '${response.theme}';`
|
|
205
|
+
);
|
|
206
|
+
await fs.writeFile(contextPath, content);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 2. Update styles/xertica/tokens.css
|
|
210
|
+
const tokensPath = path.join(targetDir, 'styles', 'xertica', 'tokens.css');
|
|
211
|
+
const selectedTheme = colorThemes.find(t => t.id === response.theme);
|
|
212
|
+
|
|
213
|
+
if (selectedTheme) {
|
|
214
|
+
// Ensure the directory exists (it should, from copy)
|
|
215
|
+
await fs.ensureDir(path.dirname(tokensPath));
|
|
216
|
+
|
|
217
|
+
const newTokensCss = generateTokensCss(selectedTheme);
|
|
218
|
+
await fs.writeFile(tokensPath, newTokensCss);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
180
222
|
spinner.succeed('Project initialized successfully!');
|
|
181
223
|
|
|
182
224
|
if (response.install) {
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { ColorTheme } from '../contexts/theme-data';
|
|
2
|
+
|
|
3
|
+
// Helper to convert hex to rgb values (e.g. "255, 255, 255")
|
|
4
|
+
// Accepts #RRGGBB or #RGB
|
|
5
|
+
const hexToRgbString = (hex: string): string => {
|
|
6
|
+
// Remove #
|
|
7
|
+
hex = hex.replace(/^#/, '');
|
|
8
|
+
|
|
9
|
+
// Parse
|
|
10
|
+
let r = 0, g = 0, b = 0;
|
|
11
|
+
|
|
12
|
+
if (hex.length === 3) {
|
|
13
|
+
r = parseInt(hex[0] + hex[0], 16);
|
|
14
|
+
g = parseInt(hex[1] + hex[1], 16);
|
|
15
|
+
b = parseInt(hex[2] + hex[2], 16);
|
|
16
|
+
} else if (hex.length === 6) {
|
|
17
|
+
r = parseInt(hex.substring(0, 2), 16);
|
|
18
|
+
g = parseInt(hex.substring(2, 4), 16);
|
|
19
|
+
b = parseInt(hex.substring(4, 6), 16);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return `${r}, ${g}, ${b}`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const rgba = (hex: string, alpha: number = 1): string => {
|
|
26
|
+
return `rgba(${hexToRgbString(hex)}, ${alpha})`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const generateTokensCss = (theme: ColorTheme): string => {
|
|
30
|
+
const { colors } = theme;
|
|
31
|
+
|
|
32
|
+
return `/* ============================================
|
|
33
|
+
🎨 TOKENS / VARIABLES (Generated by CLI)
|
|
34
|
+
============================================ */
|
|
35
|
+
|
|
36
|
+
:root,
|
|
37
|
+
:root[data-theme="default"] {
|
|
38
|
+
/* Brand Tokens - Source of Truth */
|
|
39
|
+
--xertica-primary: ${rgba(colors.primary)};
|
|
40
|
+
--xertica-dark: ${rgba(colors.sidebarDark)}; /* Approximating generic dark from sidebar dark if needed, or keeping static? */
|
|
41
|
+
|
|
42
|
+
/* Semantic Colors */
|
|
43
|
+
--background: rgba(255, 255, 255, 1);
|
|
44
|
+
--foreground: rgba(9, 9, 11, 1);
|
|
45
|
+
|
|
46
|
+
--card: rgba(255, 255, 255, 1);
|
|
47
|
+
--card-foreground: rgba(9, 9, 11, 1);
|
|
48
|
+
|
|
49
|
+
--popover: rgba(255, 255, 255, 1);
|
|
50
|
+
--popover-foreground: rgba(9, 9, 11, 1);
|
|
51
|
+
|
|
52
|
+
--primary: var(--xertica-primary);
|
|
53
|
+
--primary-foreground: ${rgba(colors.primaryForeground)};
|
|
54
|
+
--primary-light: ${rgba(colors.primary, 0.15)};
|
|
55
|
+
--primary-light-foreground: ${rgba(colors.primary)};
|
|
56
|
+
|
|
57
|
+
--secondary: rgba(244, 244, 245, 1);
|
|
58
|
+
--secondary-foreground: rgba(24, 24, 27, 1);
|
|
59
|
+
|
|
60
|
+
--muted: rgba(244, 244, 245, 1);
|
|
61
|
+
--muted-foreground: rgba(113, 113, 122, 1);
|
|
62
|
+
|
|
63
|
+
--accent: rgba(244, 244, 245, 1);
|
|
64
|
+
--accent-foreground: rgba(24, 24, 27, 1);
|
|
65
|
+
|
|
66
|
+
--destructive: rgba(239, 68, 68, 1);
|
|
67
|
+
--destructive-foreground: rgba(250, 250, 250, 1);
|
|
68
|
+
|
|
69
|
+
--border: rgba(228, 228, 231, 1);
|
|
70
|
+
--input: rgba(244, 244, 245, 0.5);
|
|
71
|
+
--input-background: rgba(244, 244, 245, 0.5);
|
|
72
|
+
--ring: ${rgba(colors.primary, 0.5)}; /* Adjusted to match brand somewhat */
|
|
73
|
+
|
|
74
|
+
/* Sidebar */
|
|
75
|
+
--sidebar: ${rgba(colors.sidebarLight)};
|
|
76
|
+
--sidebar-foreground: ${colors.sidebarLight.toLowerCase() === '#ffffff' ? 'rgba(15, 23, 42, 1)' : 'rgba(250, 250, 250, 1)'};
|
|
77
|
+
--sidebar-primary: ${colors.sidebarLight.toLowerCase() === '#ffffff' ? rgba(colors.primary) : 'rgba(255, 255, 255, 1)'};
|
|
78
|
+
--sidebar-primary-foreground: ${colors.sidebarLight.toLowerCase() === '#ffffff' ? rgba(colors.primaryForeground) : rgba(colors.primary)}; /* Actually if sidebar is dark, primary item usually white? */
|
|
79
|
+
/* Replicating logic from Context roughly or sticking to safe defaults */
|
|
80
|
+
/* Context Logic:
|
|
81
|
+
if !isSidebarLight:
|
|
82
|
+
primary: #FFFFFF
|
|
83
|
+
primaryFg: primary
|
|
84
|
+
*/
|
|
85
|
+
--sidebar-accent: ${rgba(colors.sidebarLight.toLowerCase() === '#ffffff' ? colors.primary : '#FFFFFF', 0.1)};
|
|
86
|
+
--sidebar-accent-foreground: ${colors.sidebarLight.toLowerCase() === '#ffffff' ? rgba(colors.primary) : '#FFFFFF'};
|
|
87
|
+
--sidebar-border: rgba(255, 255, 255, 0.1);
|
|
88
|
+
--sidebar-ring: ${rgba(colors.primary, 0.5)};
|
|
89
|
+
|
|
90
|
+
/* Charts */
|
|
91
|
+
--chart-1: ${rgba(colors.chart1)};
|
|
92
|
+
--chart-2: ${rgba(colors.chart2)};
|
|
93
|
+
--chart-3: ${rgba(colors.chart3)};
|
|
94
|
+
--chart-4: ${rgba(colors.chart4)};
|
|
95
|
+
--chart-5: ${rgba(colors.chart5)};
|
|
96
|
+
|
|
97
|
+
/* Gradients */
|
|
98
|
+
--gradient-diagonal: linear-gradient(135deg, ${colors.gradientStart} 0%, ${colors.gradientEnd} 100%);
|
|
99
|
+
|
|
100
|
+
/* Spacing & Radius */
|
|
101
|
+
--radius: 6px;
|
|
102
|
+
--radius-button: 12px;
|
|
103
|
+
--radius-card: 12px;
|
|
104
|
+
|
|
105
|
+
--elevation-sm: 0px 0px 48px 0px rgba(0, 0, 0, 0.1);
|
|
106
|
+
|
|
107
|
+
/* Typography */
|
|
108
|
+
--font-size: 16px;
|
|
109
|
+
--text-h1: 2rem;
|
|
110
|
+
--text-h2: 1.75rem;
|
|
111
|
+
--text-h3: 1.5rem;
|
|
112
|
+
--text-h4: 1.25rem;
|
|
113
|
+
--text-base: 1rem;
|
|
114
|
+
--text-p: 0.875rem;
|
|
115
|
+
--text-label: 0.875rem;
|
|
116
|
+
--text-small: 0.875rem;
|
|
117
|
+
--text-xs: 0.75rem;
|
|
118
|
+
--text-muted: 0.875rem;
|
|
119
|
+
--text-stats: 2.25rem;
|
|
120
|
+
--text-table-head: 1.25rem;
|
|
121
|
+
|
|
122
|
+
--font-weight-regular: 400;
|
|
123
|
+
--font-weight-medium: 500;
|
|
124
|
+
--font-weight-semibold: 600;
|
|
125
|
+
--font-weight-bold: 700;
|
|
126
|
+
--font-weight-extrabold: 800;
|
|
127
|
+
|
|
128
|
+
--spacing-1: 0.25rem;
|
|
129
|
+
--spacing-2: 0.5rem;
|
|
130
|
+
--spacing-3: 0.75rem;
|
|
131
|
+
--spacing-4: 1rem;
|
|
132
|
+
--spacing-5: 1.25rem;
|
|
133
|
+
--spacing-6: 1.5rem;
|
|
134
|
+
--spacing-8: 2rem;
|
|
135
|
+
|
|
136
|
+
/* Calendar */
|
|
137
|
+
--cell-size: 2.5rem;
|
|
138
|
+
--cell-radius: var(--radius);
|
|
139
|
+
--calendar-caption-size: 15px;
|
|
140
|
+
--calendar-weekday-size: 12px;
|
|
141
|
+
--calendar-day-size: 14px;
|
|
142
|
+
|
|
143
|
+
/* Toast - Success */
|
|
144
|
+
--toast-success-bg: rgba(220, 252, 231, 1);
|
|
145
|
+
--toast-success-border: rgba(5, 150, 105, 1);
|
|
146
|
+
--toast-success-icon: rgba(5, 150, 105, 1);
|
|
147
|
+
/* Toast - Warning */
|
|
148
|
+
--toast-warning-bg: rgba(254, 243, 199, 1);
|
|
149
|
+
--toast-warning-border: rgba(245, 158, 11, 1);
|
|
150
|
+
--toast-warning-icon: rgba(245, 158, 11, 1);
|
|
151
|
+
/* Toast - Info */
|
|
152
|
+
--toast-info-bg: rgba(219, 234, 254, 1);
|
|
153
|
+
--toast-info-border: rgba(37, 99, 235, 1);
|
|
154
|
+
--toast-info-icon: rgba(37, 99, 235, 1);
|
|
155
|
+
/* Toast - Error */
|
|
156
|
+
--toast-error-bg: rgba(254, 226, 226, 1);
|
|
157
|
+
--toast-error-border: rgba(239, 68, 68, 1);
|
|
158
|
+
--toast-error-icon: rgba(239, 68, 68, 1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
:root[data-mode="dark"],
|
|
162
|
+
.dark {
|
|
163
|
+
/* Brand Tokens */
|
|
164
|
+
--xertica-primary: ${rgba(colors.primaryDarkMode)};
|
|
165
|
+
|
|
166
|
+
/* Semantic Colors */
|
|
167
|
+
--background: rgba(5, 5, 5, 1);
|
|
168
|
+
--foreground: rgba(250, 250, 250, 1);
|
|
169
|
+
|
|
170
|
+
--card: rgba(20, 20, 22, 1);
|
|
171
|
+
--card-foreground: rgba(250, 250, 250, 1);
|
|
172
|
+
|
|
173
|
+
--popover: rgba(20, 20, 22, 1);
|
|
174
|
+
--popover-foreground: rgba(250, 250, 250, 1);
|
|
175
|
+
|
|
176
|
+
--primary-foreground: ${rgba(colors.primaryForegroundDark)};
|
|
177
|
+
--primary-light: ${rgba(colors.primaryDarkMode, 0.15)};
|
|
178
|
+
--primary-light-foreground: ${rgba(colors.primaryDarkMode)};
|
|
179
|
+
|
|
180
|
+
--secondary: rgba(39, 39, 42, 1);
|
|
181
|
+
--secondary-foreground: rgba(250, 250, 250, 1);
|
|
182
|
+
|
|
183
|
+
--muted: rgba(39, 39, 42, 1);
|
|
184
|
+
--muted-foreground: rgba(161, 161, 170, 1);
|
|
185
|
+
|
|
186
|
+
--accent: rgba(39, 39, 42, 1);
|
|
187
|
+
--accent-foreground: rgba(250, 250, 250, 1);
|
|
188
|
+
|
|
189
|
+
--destructive: rgba(239, 68, 68, 1);
|
|
190
|
+
--destructive-foreground: rgba(250, 250, 250, 1);
|
|
191
|
+
|
|
192
|
+
--border: rgba(63, 63, 70, 1);
|
|
193
|
+
--input: rgba(39, 39, 42, 0.5);
|
|
194
|
+
--input-background: rgba(39, 39, 42, 0.5);
|
|
195
|
+
--ring: ${rgba(colors.primaryDarkMode, 0.5)};
|
|
196
|
+
|
|
197
|
+
--elevation-sm: 0px 0px 48px 0px rgba(0, 0, 0, 0.3);
|
|
198
|
+
|
|
199
|
+
/* Sidebar */
|
|
200
|
+
--sidebar: ${rgba(colors.sidebarDark)};
|
|
201
|
+
--sidebar-foreground: rgba(250, 250, 250, 1);
|
|
202
|
+
--sidebar-primary: rgba(255, 255, 255, 1);
|
|
203
|
+
--sidebar-primary-foreground: ${rgba(colors.primary)}; /* Often primary brand color on dark background */
|
|
204
|
+
--sidebar-accent: rgba(63, 63, 70, 1);
|
|
205
|
+
--sidebar-accent-foreground: rgba(250, 250, 250, 1);
|
|
206
|
+
--sidebar-border: rgba(65, 61, 107, 1); /* Keeping subtle border */
|
|
207
|
+
--sidebar-ring: ${rgba(colors.primaryDarkMode, 0.5)};
|
|
208
|
+
|
|
209
|
+
/* Gradients */
|
|
210
|
+
--gradient-diagonal: linear-gradient(135deg, ${colors.gradientStartDark} 0%, ${colors.gradientEndDark} 100%);
|
|
211
|
+
|
|
212
|
+
/* Toast - Success */
|
|
213
|
+
--toast-success-bg: rgba(6, 78, 59, 1);
|
|
214
|
+
--toast-success-border: rgba(34, 197, 94, 1);
|
|
215
|
+
--toast-success-icon: rgba(34, 197, 94, 1);
|
|
216
|
+
/* Toast - Warning */
|
|
217
|
+
--toast-warning-bg: rgba(113, 63, 18, 1);
|
|
218
|
+
--toast-warning-border: rgba(251, 191, 36, 1);
|
|
219
|
+
--toast-warning-icon: rgba(251, 191, 36, 1);
|
|
220
|
+
/* Toast - Info */
|
|
221
|
+
--toast-info-bg: rgba(30, 58, 138, 1);
|
|
222
|
+
--toast-info-border: rgba(96, 165, 250, 1);
|
|
223
|
+
--toast-info-icon: rgba(96, 165, 250, 1);
|
|
224
|
+
/* Toast - Error */
|
|
225
|
+
--toast-error-bg: rgba(127, 29, 29, 1);
|
|
226
|
+
--toast-error-border: rgba(248, 113, 113, 1);
|
|
227
|
+
--toast-error-icon: rgba(248, 113, 113, 1);
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
};
|