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 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
- // src is absolute. We need relative validation.
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
+ };