@ng-cn/core 1.0.5 → 1.0.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.
@@ -1,89 +1,494 @@
1
+ import { join, normalize, Path } from '@angular-devkit/core';
1
2
  import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
3
  import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
3
4
  import { parse as parseJsonc } from 'jsonc-parser';
4
5
 
6
+ type ThemeVariant = 'shadcn' | 'github' | 'vercel' | 'apple' | 'openai' | 'clickup' | 'linear';
7
+
5
8
  interface NgAddOptions {
6
9
  project?: string;
10
+ theme?: ThemeVariant;
7
11
  skipInstall?: boolean;
8
12
  skipStyles?: boolean;
9
- components?: string[];
13
+ components?: string; // Can be 'all' or comma-separated list like 'button,card,dialog'
14
+ }
15
+
16
+ // All available components in the registry
17
+ const ALL_COMPONENTS = [
18
+ 'accordion', 'alert', 'alert-dialog', 'aspect-ratio', 'avatar', 'badge',
19
+ 'breadcrumb', 'button', 'button-group', 'calendar', 'card', 'carousel',
20
+ 'chart', 'checkbox', 'collapsible', 'combobox', 'command', 'context-menu',
21
+ 'data-table', 'date-picker', 'dialog', 'drawer', 'dropdown-menu', 'empty',
22
+ 'form', 'hover-card', 'input', 'input-group', 'input-otp', 'kbd', 'label',
23
+ 'menubar', 'native-select', 'navigation-menu', 'pagination', 'popover',
24
+ 'progress', 'radio-group', 'resizable', 'scroll-area', 'segmented', 'select',
25
+ 'separator', 'sheet', 'sidebar', 'skeleton', 'slider', 'spinner', 'switch',
26
+ 'table', 'tabs', 'textarea', 'toast', 'toggle', 'toggle-group', 'tooltip',
27
+ 'typography'
28
+ ];
29
+
30
+ // Registry of all utility files that need to be copied
31
+ const UTILS_FILES_REGISTRY = [
32
+ // Core utils
33
+ 'cn.ts',
34
+ 'index.ts',
35
+ // Animation utilities
36
+ 'animation/index.ts',
37
+ 'animation/animation.types.ts',
38
+ 'animation/animation.utils.ts',
39
+ 'animation/animation-tokens.service.ts',
40
+ 'animation/animated.directive.ts',
41
+ 'animation/presence.component.ts',
42
+ 'animation/presence.directive.ts',
43
+ // Accessibility utilities
44
+ 'accessibility/index.ts',
45
+ 'accessibility/aria-id.service.ts',
46
+ 'accessibility/click-outside.directive.ts',
47
+ 'accessibility/focus-management.service.ts',
48
+ 'accessibility/focus-trap.directive.ts',
49
+ 'accessibility/keyboard-navigation.directive.ts',
50
+ 'accessibility/live-region.directive.ts',
51
+ 'accessibility/touch-target.directive.ts',
52
+ 'accessibility/visually-hidden.component.ts',
53
+ // Positioning utilities
54
+ 'positioning/index.ts',
55
+ ];
56
+
57
+ // Theme color definitions for all variants
58
+ interface ThemeColors {
59
+ background: string;
60
+ foreground: string;
61
+ card: string;
62
+ cardForeground: string;
63
+ popover: string;
64
+ popoverForeground: string;
65
+ primary: string;
66
+ primaryForeground: string;
67
+ secondary: string;
68
+ secondaryForeground: string;
69
+ muted: string;
70
+ mutedForeground: string;
71
+ accent: string;
72
+ accentForeground: string;
73
+ destructive: string;
74
+ destructiveForeground: string;
75
+ border: string;
76
+ input: string;
77
+ ring: string;
78
+ }
79
+
80
+ interface ThemePalette {
81
+ name: string;
82
+ description: string;
83
+ light: ThemeColors;
84
+ dark: ThemeColors;
10
85
  }
11
86
 
12
- // CSS Variables template for shadcn theming
13
- const CSS_VARIABLES_TEMPLATE = `/* ng-cn/core - shadcn-angular styles */
87
+ const THEME_PALETTES: Record<ThemeVariant, ThemePalette> = {
88
+ shadcn: {
89
+ name: 'shadcn',
90
+ description: 'The default shadcn/ui theme - clean, minimal, and professional',
91
+ light: {
92
+ background: 'oklch(1 0 0)',
93
+ foreground: 'oklch(0.145 0 0)',
94
+ card: 'oklch(1 0 0)',
95
+ cardForeground: 'oklch(0.145 0 0)',
96
+ popover: 'oklch(1 0 0)',
97
+ popoverForeground: 'oklch(0.145 0 0)',
98
+ primary: 'oklch(0.205 0 0)',
99
+ primaryForeground: 'oklch(0.985 0 0)',
100
+ secondary: 'oklch(0.97 0 0)',
101
+ secondaryForeground: 'oklch(0.205 0 0)',
102
+ muted: 'oklch(0.97 0 0)',
103
+ mutedForeground: 'oklch(0.556 0 0)',
104
+ accent: 'oklch(0.97 0 0)',
105
+ accentForeground: 'oklch(0.205 0 0)',
106
+ destructive: 'oklch(0.577 0.245 27.325)',
107
+ destructiveForeground: 'oklch(0.985 0 0)',
108
+ border: 'oklch(0.922 0 0)',
109
+ input: 'oklch(0.922 0 0)',
110
+ ring: 'oklch(0.708 0 0)',
111
+ },
112
+ dark: {
113
+ background: 'oklch(0.145 0 0)',
114
+ foreground: 'oklch(0.985 0 0)',
115
+ card: 'oklch(0.205 0 0)',
116
+ cardForeground: 'oklch(0.985 0 0)',
117
+ popover: 'oklch(0.205 0 0)',
118
+ popoverForeground: 'oklch(0.985 0 0)',
119
+ primary: 'oklch(0.985 0 0)',
120
+ primaryForeground: 'oklch(0.205 0 0)',
121
+ secondary: 'oklch(0.269 0 0)',
122
+ secondaryForeground: 'oklch(0.985 0 0)',
123
+ muted: 'oklch(0.269 0 0)',
124
+ mutedForeground: 'oklch(0.708 0 0)',
125
+ accent: 'oklch(0.269 0 0)',
126
+ accentForeground: 'oklch(0.985 0 0)',
127
+ destructive: 'oklch(0.396 0.141 25.723)',
128
+ destructiveForeground: 'oklch(0.985 0 0)',
129
+ border: 'oklch(0.269 0 0)',
130
+ input: 'oklch(0.269 0 0)',
131
+ ring: 'oklch(0.439 0 0)',
132
+ },
133
+ },
134
+ github: {
135
+ name: 'GitHub',
136
+ description: "Inspired by GitHub's clean developer-focused design",
137
+ light: {
138
+ background: 'oklch(1 0 0)',
139
+ foreground: 'oklch(0.208 0.042 265.755)',
140
+ card: 'oklch(1 0 0)',
141
+ cardForeground: 'oklch(0.208 0.042 265.755)',
142
+ popover: 'oklch(1 0 0)',
143
+ popoverForeground: 'oklch(0.208 0.042 265.755)',
144
+ primary: 'oklch(0.546 0.192 262.881)',
145
+ primaryForeground: 'oklch(1 0 0)',
146
+ secondary: 'oklch(0.968 0.007 247.896)',
147
+ secondaryForeground: 'oklch(0.208 0.042 265.755)',
148
+ muted: 'oklch(0.968 0.007 247.896)',
149
+ mutedForeground: 'oklch(0.443 0.024 264.052)',
150
+ accent: 'oklch(0.968 0.007 247.896)',
151
+ accentForeground: 'oklch(0.208 0.042 265.755)',
152
+ destructive: 'oklch(0.577 0.215 27.325)',
153
+ destructiveForeground: 'oklch(1 0 0)',
154
+ border: 'oklch(0.883 0.012 247.896)',
155
+ input: 'oklch(0.883 0.012 247.896)',
156
+ ring: 'oklch(0.546 0.192 262.881)',
157
+ },
158
+ dark: {
159
+ background: 'oklch(0.177 0.015 265.755)',
160
+ foreground: 'oklch(0.922 0.007 247.896)',
161
+ card: 'oklch(0.208 0.018 265.755)',
162
+ cardForeground: 'oklch(0.922 0.007 247.896)',
163
+ popover: 'oklch(0.208 0.018 265.755)',
164
+ popoverForeground: 'oklch(0.922 0.007 247.896)',
165
+ primary: 'oklch(0.637 0.183 259.533)',
166
+ primaryForeground: 'oklch(1 0 0)',
167
+ secondary: 'oklch(0.272 0.022 265.755)',
168
+ secondaryForeground: 'oklch(0.922 0.007 247.896)',
169
+ muted: 'oklch(0.272 0.022 265.755)',
170
+ mutedForeground: 'oklch(0.627 0.018 264.052)',
171
+ accent: 'oklch(0.272 0.022 265.755)',
172
+ accentForeground: 'oklch(0.922 0.007 247.896)',
173
+ destructive: 'oklch(0.527 0.215 27.325)',
174
+ destructiveForeground: 'oklch(1 0 0)',
175
+ border: 'oklch(0.336 0.024 265.755)',
176
+ input: 'oklch(0.336 0.024 265.755)',
177
+ ring: 'oklch(0.637 0.183 259.533)',
178
+ },
179
+ },
180
+ vercel: {
181
+ name: 'Vercel',
182
+ description: "Vercel's bold black and white design aesthetic",
183
+ light: {
184
+ background: 'oklch(1 0 0)',
185
+ foreground: 'oklch(0 0 0)',
186
+ card: 'oklch(1 0 0)',
187
+ cardForeground: 'oklch(0 0 0)',
188
+ popover: 'oklch(1 0 0)',
189
+ popoverForeground: 'oklch(0 0 0)',
190
+ primary: 'oklch(0 0 0)',
191
+ primaryForeground: 'oklch(1 0 0)',
192
+ secondary: 'oklch(0.968 0 0)',
193
+ secondaryForeground: 'oklch(0 0 0)',
194
+ muted: 'oklch(0.968 0 0)',
195
+ mutedForeground: 'oklch(0.443 0 0)',
196
+ accent: 'oklch(0.968 0 0)',
197
+ accentForeground: 'oklch(0 0 0)',
198
+ destructive: 'oklch(0.577 0.245 27.325)',
199
+ destructiveForeground: 'oklch(1 0 0)',
200
+ border: 'oklch(0.883 0 0)',
201
+ input: 'oklch(0.883 0 0)',
202
+ ring: 'oklch(0 0 0)',
203
+ },
204
+ dark: {
205
+ background: 'oklch(0 0 0)',
206
+ foreground: 'oklch(1 0 0)',
207
+ card: 'oklch(0.117 0 0)',
208
+ cardForeground: 'oklch(1 0 0)',
209
+ popover: 'oklch(0.117 0 0)',
210
+ popoverForeground: 'oklch(1 0 0)',
211
+ primary: 'oklch(1 0 0)',
212
+ primaryForeground: 'oklch(0 0 0)',
213
+ secondary: 'oklch(0.177 0 0)',
214
+ secondaryForeground: 'oklch(1 0 0)',
215
+ muted: 'oklch(0.177 0 0)',
216
+ mutedForeground: 'oklch(0.627 0 0)',
217
+ accent: 'oklch(0.177 0 0)',
218
+ accentForeground: 'oklch(1 0 0)',
219
+ destructive: 'oklch(0.577 0.245 27.325)',
220
+ destructiveForeground: 'oklch(1 0 0)',
221
+ border: 'oklch(0.269 0 0)',
222
+ input: 'oklch(0.269 0 0)',
223
+ ring: 'oklch(1 0 0)',
224
+ },
225
+ },
226
+ apple: {
227
+ name: 'Apple',
228
+ description: "Apple's elegant, sophisticated design language",
229
+ light: {
230
+ background: 'oklch(1 0 0)',
231
+ foreground: 'oklch(0.208 0 0)',
232
+ card: 'oklch(0.985 0.003 247.858)',
233
+ cardForeground: 'oklch(0.208 0 0)',
234
+ popover: 'oklch(0.985 0.003 247.858)',
235
+ popoverForeground: 'oklch(0.208 0 0)',
236
+ primary: 'oklch(0.586 0.228 259.815)',
237
+ primaryForeground: 'oklch(1 0 0)',
238
+ secondary: 'oklch(0.968 0.003 247.858)',
239
+ secondaryForeground: 'oklch(0.208 0 0)',
240
+ muted: 'oklch(0.968 0.003 247.858)',
241
+ mutedForeground: 'oklch(0.506 0 0)',
242
+ accent: 'oklch(0.968 0.003 247.858)',
243
+ accentForeground: 'oklch(0.208 0 0)',
244
+ destructive: 'oklch(0.577 0.245 27.325)',
245
+ destructiveForeground: 'oklch(1 0 0)',
246
+ border: 'oklch(0.922 0.003 247.858)',
247
+ input: 'oklch(0.922 0.003 247.858)',
248
+ ring: 'oklch(0.586 0.228 259.815)',
249
+ },
250
+ dark: {
251
+ background: 'oklch(0 0 0)',
252
+ foreground: 'oklch(0.985 0 0)',
253
+ card: 'oklch(0.145 0.003 247.858)',
254
+ cardForeground: 'oklch(0.985 0 0)',
255
+ popover: 'oklch(0.145 0.003 247.858)',
256
+ popoverForeground: 'oklch(0.985 0 0)',
257
+ primary: 'oklch(0.637 0.237 259.815)',
258
+ primaryForeground: 'oklch(1 0 0)',
259
+ secondary: 'oklch(0.227 0.003 247.858)',
260
+ secondaryForeground: 'oklch(0.985 0 0)',
261
+ muted: 'oklch(0.227 0.003 247.858)',
262
+ mutedForeground: 'oklch(0.627 0 0)',
263
+ accent: 'oklch(0.227 0.003 247.858)',
264
+ accentForeground: 'oklch(0.985 0 0)',
265
+ destructive: 'oklch(0.527 0.215 27.325)',
266
+ destructiveForeground: 'oklch(1 0 0)',
267
+ border: 'oklch(0.295 0.003 247.858)',
268
+ input: 'oklch(0.295 0.003 247.858)',
269
+ ring: 'oklch(0.637 0.237 259.815)',
270
+ },
271
+ },
272
+ openai: {
273
+ name: 'OpenAI',
274
+ description: "OpenAI's modern, tech-forward aesthetic",
275
+ light: {
276
+ background: 'oklch(1 0 0)',
277
+ foreground: 'oklch(0.208 0.01 255.841)',
278
+ card: 'oklch(1 0 0)',
279
+ cardForeground: 'oklch(0.208 0.01 255.841)',
280
+ popover: 'oklch(1 0 0)',
281
+ popoverForeground: 'oklch(0.208 0.01 255.841)',
282
+ primary: 'oklch(0.538 0.163 163.319)',
283
+ primaryForeground: 'oklch(1 0 0)',
284
+ secondary: 'oklch(0.968 0.005 255.841)',
285
+ secondaryForeground: 'oklch(0.208 0.01 255.841)',
286
+ muted: 'oklch(0.968 0.005 255.841)',
287
+ mutedForeground: 'oklch(0.475 0.01 255.841)',
288
+ accent: 'oklch(0.968 0.005 255.841)',
289
+ accentForeground: 'oklch(0.208 0.01 255.841)',
290
+ destructive: 'oklch(0.577 0.245 27.325)',
291
+ destructiveForeground: 'oklch(1 0 0)',
292
+ border: 'oklch(0.906 0.006 255.841)',
293
+ input: 'oklch(0.906 0.006 255.841)',
294
+ ring: 'oklch(0.538 0.163 163.319)',
295
+ },
296
+ dark: {
297
+ background: 'oklch(0.168 0.015 255.841)',
298
+ foreground: 'oklch(0.968 0.005 255.841)',
299
+ card: 'oklch(0.208 0.015 255.841)',
300
+ cardForeground: 'oklch(0.968 0.005 255.841)',
301
+ popover: 'oklch(0.208 0.015 255.841)',
302
+ popoverForeground: 'oklch(0.968 0.005 255.841)',
303
+ primary: 'oklch(0.608 0.183 163.319)',
304
+ primaryForeground: 'oklch(0.168 0.015 255.841)',
305
+ secondary: 'oklch(0.268 0.015 255.841)',
306
+ secondaryForeground: 'oklch(0.968 0.005 255.841)',
307
+ muted: 'oklch(0.268 0.015 255.841)',
308
+ mutedForeground: 'oklch(0.627 0.01 255.841)',
309
+ accent: 'oklch(0.268 0.015 255.841)',
310
+ accentForeground: 'oklch(0.968 0.005 255.841)',
311
+ destructive: 'oklch(0.527 0.215 27.325)',
312
+ destructiveForeground: 'oklch(1 0 0)',
313
+ border: 'oklch(0.336 0.015 255.841)',
314
+ input: 'oklch(0.336 0.015 255.841)',
315
+ ring: 'oklch(0.608 0.183 163.319)',
316
+ },
317
+ },
318
+ clickup: {
319
+ name: 'ClickUp',
320
+ description: "ClickUp's vibrant, colorful productivity aesthetic",
321
+ light: {
322
+ background: 'oklch(1 0 0)',
323
+ foreground: 'oklch(0.229 0.034 277.508)',
324
+ card: 'oklch(1 0 0)',
325
+ cardForeground: 'oklch(0.229 0.034 277.508)',
326
+ popover: 'oklch(1 0 0)',
327
+ popoverForeground: 'oklch(0.229 0.034 277.508)',
328
+ primary: 'oklch(0.638 0.238 288.545)',
329
+ primaryForeground: 'oklch(1 0 0)',
330
+ secondary: 'oklch(0.962 0.021 288.545)',
331
+ secondaryForeground: 'oklch(0.229 0.034 277.508)',
332
+ muted: 'oklch(0.962 0.021 288.545)',
333
+ mutedForeground: 'oklch(0.475 0.028 277.508)',
334
+ accent: 'oklch(0.807 0.181 85.391)',
335
+ accentForeground: 'oklch(0.229 0.034 277.508)',
336
+ destructive: 'oklch(0.577 0.245 27.325)',
337
+ destructiveForeground: 'oklch(1 0 0)',
338
+ border: 'oklch(0.906 0.018 277.508)',
339
+ input: 'oklch(0.906 0.018 277.508)',
340
+ ring: 'oklch(0.638 0.238 288.545)',
341
+ },
342
+ dark: {
343
+ background: 'oklch(0.177 0.028 277.508)',
344
+ foreground: 'oklch(0.968 0.008 277.508)',
345
+ card: 'oklch(0.208 0.032 277.508)',
346
+ cardForeground: 'oklch(0.968 0.008 277.508)',
347
+ popover: 'oklch(0.208 0.032 277.508)',
348
+ popoverForeground: 'oklch(0.968 0.008 277.508)',
349
+ primary: 'oklch(0.688 0.238 288.545)',
350
+ primaryForeground: 'oklch(1 0 0)',
351
+ secondary: 'oklch(0.268 0.038 277.508)',
352
+ secondaryForeground: 'oklch(0.968 0.008 277.508)',
353
+ muted: 'oklch(0.268 0.038 277.508)',
354
+ mutedForeground: 'oklch(0.627 0.018 277.508)',
355
+ accent: 'oklch(0.757 0.181 85.391)',
356
+ accentForeground: 'oklch(0.177 0.028 277.508)',
357
+ destructive: 'oklch(0.527 0.215 27.325)',
358
+ destructiveForeground: 'oklch(1 0 0)',
359
+ border: 'oklch(0.336 0.038 277.508)',
360
+ input: 'oklch(0.336 0.038 277.508)',
361
+ ring: 'oklch(0.688 0.238 288.545)',
362
+ },
363
+ },
364
+ linear: {
365
+ name: 'Linear',
366
+ description: "Linear's sleek, issue tracking inspired design",
367
+ light: {
368
+ background: 'oklch(0.985 0.003 250)',
369
+ foreground: 'oklch(0.229 0.024 265)',
370
+ card: 'oklch(1 0 0)',
371
+ cardForeground: 'oklch(0.229 0.024 265)',
372
+ popover: 'oklch(1 0 0)',
373
+ popoverForeground: 'oklch(0.229 0.024 265)',
374
+ primary: 'oklch(0.538 0.207 262.881)',
375
+ primaryForeground: 'oklch(1 0 0)',
376
+ secondary: 'oklch(0.962 0.008 250)',
377
+ secondaryForeground: 'oklch(0.229 0.024 265)',
378
+ muted: 'oklch(0.962 0.008 250)',
379
+ mutedForeground: 'oklch(0.506 0.018 265)',
380
+ accent: 'oklch(0.962 0.008 250)',
381
+ accentForeground: 'oklch(0.229 0.024 265)',
382
+ destructive: 'oklch(0.577 0.245 27.325)',
383
+ destructiveForeground: 'oklch(1 0 0)',
384
+ border: 'oklch(0.906 0.012 250)',
385
+ input: 'oklch(0.906 0.012 250)',
386
+ ring: 'oklch(0.538 0.207 262.881)',
387
+ },
388
+ dark: {
389
+ background: 'oklch(0.145 0.018 265)',
390
+ foreground: 'oklch(0.962 0.008 250)',
391
+ card: 'oklch(0.177 0.022 265)',
392
+ cardForeground: 'oklch(0.962 0.008 250)',
393
+ popover: 'oklch(0.177 0.022 265)',
394
+ popoverForeground: 'oklch(0.962 0.008 250)',
395
+ primary: 'oklch(0.608 0.217 262.881)',
396
+ primaryForeground: 'oklch(1 0 0)',
397
+ secondary: 'oklch(0.227 0.028 265)',
398
+ secondaryForeground: 'oklch(0.962 0.008 250)',
399
+ muted: 'oklch(0.227 0.028 265)',
400
+ mutedForeground: 'oklch(0.577 0.015 265)',
401
+ accent: 'oklch(0.227 0.028 265)',
402
+ accentForeground: 'oklch(0.962 0.008 250)',
403
+ destructive: 'oklch(0.527 0.215 27.325)',
404
+ destructiveForeground: 'oklch(1 0 0)',
405
+ border: 'oklch(0.295 0.028 265)',
406
+ input: 'oklch(0.295 0.028 265)',
407
+ ring: 'oklch(0.608 0.217 262.881)',
408
+ },
409
+ },
410
+ };
411
+
412
+ // Generate CSS Variables template based on selected theme
413
+ function generateCssVariablesTemplate(theme: ThemeVariant): string {
414
+ const palette = THEME_PALETTES[theme];
415
+ const { light, dark } = palette;
416
+
417
+ return `/* ng-cn/core - shadcn-angular styles */
418
+ /* Theme: ${palette.name} - ${palette.description} */
14
419
  @use "tailwindcss";
15
420
 
16
421
  @custom-variant dark (&:is(.dark *));
17
422
 
18
423
  :root {
19
424
  --radius: 0.625rem;
20
- --background: oklch(1 0 0);
21
- --foreground: oklch(0.145 0 0);
22
- --card: oklch(1 0 0);
23
- --card-foreground: oklch(0.145 0 0);
24
- --popover: oklch(1 0 0);
25
- --popover-foreground: oklch(0.145 0 0);
26
- --primary: oklch(0.205 0 0);
27
- --primary-foreground: oklch(0.985 0 0);
28
- --secondary: oklch(0.97 0 0);
29
- --secondary-foreground: oklch(0.205 0 0);
30
- --muted: oklch(0.97 0 0);
31
- --muted-foreground: oklch(0.556 0 0);
32
- --accent: oklch(0.97 0 0);
33
- --accent-foreground: oklch(0.205 0 0);
34
- --destructive: oklch(0.577 0.245 27.325);
35
- --destructive-foreground: oklch(0.577 0.245 27.325);
36
- --border: oklch(0.922 0 0);
37
- --input: oklch(0.922 0 0);
38
- --ring: oklch(0.708 0 0);
425
+ --background: ${light.background};
426
+ --foreground: ${light.foreground};
427
+ --card: ${light.card};
428
+ --card-foreground: ${light.cardForeground};
429
+ --popover: ${light.popover};
430
+ --popover-foreground: ${light.popoverForeground};
431
+ --primary: ${light.primary};
432
+ --primary-foreground: ${light.primaryForeground};
433
+ --secondary: ${light.secondary};
434
+ --secondary-foreground: ${light.secondaryForeground};
435
+ --muted: ${light.muted};
436
+ --muted-foreground: ${light.mutedForeground};
437
+ --accent: ${light.accent};
438
+ --accent-foreground: ${light.accentForeground};
439
+ --destructive: ${light.destructive};
440
+ --destructive-foreground: ${light.destructiveForeground};
441
+ --border: ${light.border};
442
+ --input: ${light.input};
443
+ --ring: ${light.ring};
39
444
  --chart-1: oklch(0.646 0.222 41.116);
40
445
  --chart-2: oklch(0.6 0.118 184.704);
41
446
  --chart-3: oklch(0.398 0.07 227.392);
42
447
  --chart-4: oklch(0.828 0.189 84.429);
43
448
  --chart-5: oklch(0.769 0.188 70.08);
44
- --sidebar: oklch(0.985 0 0);
45
- --sidebar-foreground: oklch(0.145 0 0);
46
- --sidebar-primary: oklch(0.205 0 0);
47
- --sidebar-primary-foreground: oklch(0.985 0 0);
48
- --sidebar-accent: oklch(0.97 0 0);
49
- --sidebar-accent-foreground: oklch(0.205 0 0);
50
- --sidebar-border: oklch(0.922 0 0);
51
- --sidebar-ring: oklch(0.708 0 0);
449
+ --sidebar: ${light.background};
450
+ --sidebar-foreground: ${light.foreground};
451
+ --sidebar-primary: ${light.primary};
452
+ --sidebar-primary-foreground: ${light.primaryForeground};
453
+ --sidebar-accent: ${light.accent};
454
+ --sidebar-accent-foreground: ${light.accentForeground};
455
+ --sidebar-border: ${light.border};
456
+ --sidebar-ring: ${light.ring};
52
457
  }
53
458
 
54
459
  .dark {
55
- --background: oklch(0.145 0 0);
56
- --foreground: oklch(0.985 0 0);
57
- --card: oklch(0.205 0 0);
58
- --card-foreground: oklch(0.985 0 0);
59
- --popover: oklch(0.205 0 0);
60
- --popover-foreground: oklch(0.985 0 0);
61
- --primary: oklch(0.985 0 0);
62
- --primary-foreground: oklch(0.205 0 0);
63
- --secondary: oklch(0.269 0 0);
64
- --secondary-foreground: oklch(0.985 0 0);
65
- --muted: oklch(0.269 0 0);
66
- --muted-foreground: oklch(0.708 0 0);
67
- --accent: oklch(0.269 0 0);
68
- --accent-foreground: oklch(0.985 0 0);
69
- --destructive: oklch(0.396 0.141 25.723);
70
- --destructive-foreground: oklch(0.637 0.237 25.331);
71
- --border: oklch(0.269 0 0);
72
- --input: oklch(0.269 0 0);
73
- --ring: oklch(0.439 0 0);
460
+ --background: ${dark.background};
461
+ --foreground: ${dark.foreground};
462
+ --card: ${dark.card};
463
+ --card-foreground: ${dark.cardForeground};
464
+ --popover: ${dark.popover};
465
+ --popover-foreground: ${dark.popoverForeground};
466
+ --primary: ${dark.primary};
467
+ --primary-foreground: ${dark.primaryForeground};
468
+ --secondary: ${dark.secondary};
469
+ --secondary-foreground: ${dark.secondaryForeground};
470
+ --muted: ${dark.muted};
471
+ --muted-foreground: ${dark.mutedForeground};
472
+ --accent: ${dark.accent};
473
+ --accent-foreground: ${dark.accentForeground};
474
+ --destructive: ${dark.destructive};
475
+ --destructive-foreground: ${dark.destructiveForeground};
476
+ --border: ${dark.border};
477
+ --input: ${dark.input};
478
+ --ring: ${dark.ring};
74
479
  --chart-1: oklch(0.488 0.243 264.376);
75
480
  --chart-2: oklch(0.696 0.17 162.48);
76
481
  --chart-3: oklch(0.769 0.188 70.08);
77
482
  --chart-4: oklch(0.627 0.265 303.9);
78
483
  --chart-5: oklch(0.645 0.246 16.439);
79
- --sidebar: oklch(0.205 0 0);
80
- --sidebar-foreground: oklch(0.985 0 0);
81
- --sidebar-primary: oklch(0.488 0.243 264.376);
82
- --sidebar-primary-foreground: oklch(0.985 0 0);
83
- --sidebar-accent: oklch(0.269 0 0);
84
- --sidebar-accent-foreground: oklch(0.985 0 0);
85
- --sidebar-border: oklch(0.269 0 0);
86
- --sidebar-ring: oklch(0.439 0 0);
484
+ --sidebar: ${dark.card};
485
+ --sidebar-foreground: ${dark.foreground};
486
+ --sidebar-primary: ${dark.primary};
487
+ --sidebar-primary-foreground: ${dark.primaryForeground};
488
+ --sidebar-accent: ${dark.accent};
489
+ --sidebar-accent-foreground: ${dark.accentForeground};
490
+ --sidebar-border: ${dark.border};
491
+ --sidebar-ring: ${dark.ring};
87
492
  }
88
493
 
89
494
  @theme inline {
@@ -143,6 +548,16 @@ const CSS_VARIABLES_TEMPLATE = `/* ng-cn/core - shadcn-angular styles */
143
548
  @keyframes collapsible-up { from { height: var(--collapsible-content-height); } to { height: 0; } }
144
549
  }
145
550
  `;
551
+ }
552
+
553
+ // PostCSS config template for Tailwind CSS v4
554
+ const POSTCSS_CONFIG_TEMPLATE = `/** @type {import('postcss').Config} */
555
+ export default {
556
+ plugins: {
557
+ '@tailwindcss/postcss': {},
558
+ },
559
+ };
560
+ `;
146
561
 
147
562
  // cn utility template
148
563
  const CN_UTILITY_TEMPLATE = `import { clsx, type ClassValue } from 'clsx';
@@ -218,18 +633,37 @@ export function ngAdd(options: NgAddOptions): Rule {
218
633
  const utilsPath = '/src/app/lib/utils';
219
634
  const uiPath = '/src/app/lib/components/ui';
220
635
 
221
- // Create cn utility
222
- if (!tree.exists(`${utilsPath}/cn.ts`)) {
223
- tree.create(`${utilsPath}/cn.ts`, CN_UTILITY_TEMPLATE);
224
- context.logger.info(` + src/app/lib/utils/cn.ts`);
225
- } else {
226
- context.logger.info(` ✓ utils/cn.ts exists`);
636
+ // Copy all utility files from the package
637
+ const sourceUtilsBase = 'node_modules/@ng-cn/core/src/app/lib/utils';
638
+ let utilsFilesCopied = 0;
639
+
640
+ for (const utilFile of UTILS_FILES_REGISTRY) {
641
+ const sourcePath = join(normalize(sourceUtilsBase), normalize(utilFile)) as Path;
642
+ const targetPath = join(normalize(utilsPath), normalize(utilFile)) as Path;
643
+
644
+ const content = tree.read(sourcePath);
645
+ if (content) {
646
+ if (tree.exists(targetPath)) {
647
+ tree.overwrite(targetPath, content);
648
+ } else {
649
+ tree.create(targetPath, content);
650
+ }
651
+ utilsFilesCopied++;
652
+ }
227
653
  }
228
654
 
229
- // Create utils index
230
- if (!tree.exists(`${utilsPath}/index.ts`)) {
231
- tree.create(`${utilsPath}/index.ts`, UTILS_INDEX_TEMPLATE);
232
- context.logger.info(` + src/app/lib/utils/index.ts`);
655
+ if (utilsFilesCopied > 0) {
656
+ context.logger.info(` + src/app/lib/utils/ (${utilsFilesCopied} files)`);
657
+ } else {
658
+ // Fallback to creating basic files if package files not found
659
+ if (!tree.exists(`${utilsPath}/cn.ts`)) {
660
+ tree.create(`${utilsPath}/cn.ts`, CN_UTILITY_TEMPLATE);
661
+ context.logger.info(` + src/app/lib/utils/cn.ts`);
662
+ }
663
+ if (!tree.exists(`${utilsPath}/index.ts`)) {
664
+ tree.create(`${utilsPath}/index.ts`, UTILS_INDEX_TEMPLATE);
665
+ context.logger.info(` + src/app/lib/utils/index.ts`);
666
+ }
233
667
  }
234
668
 
235
669
  // Create ui components directory
@@ -243,32 +677,58 @@ export function ngAdd(options: NgAddOptions): Rule {
243
677
  context.logger.info('');
244
678
  context.logger.info('🎨 Styles');
245
679
 
680
+ // Get the selected theme (default to shadcn)
681
+ const selectedTheme: ThemeVariant = options.theme || 'shadcn';
682
+ const themePalette = THEME_PALETTES[selectedTheme];
683
+
684
+ context.logger.info(` Theme: ${themePalette.name} - ${themePalette.description}`);
685
+
246
686
  const ngCnStylesPath = '/src/ng-cn.scss';
247
687
  const stylesPath = '/src/styles.scss';
248
688
  const stylesCssPath = '/src/styles.css';
249
689
 
250
- // Create ng-cn.scss with all CSS variables
251
- tree.create(ngCnStylesPath, CSS_VARIABLES_TEMPLATE);
252
- context.logger.info(` + src/ng-cn.scss (theme variables)`);
690
+ // Create ng-cn.scss with CSS variables based on selected theme
691
+ const cssTemplate = generateCssVariablesTemplate(selectedTheme);
692
+ tree.create(ngCnStylesPath, cssTemplate);
693
+ context.logger.info(` + src/ng-cn.scss (${themePalette.name} theme variables)`);
253
694
 
254
695
  // Import in styles.scss or styles.css
255
696
  if (tree.exists(stylesPath)) {
256
697
  const stylesContent = tree.read(stylesPath)!.toString('utf-8');
257
698
  if (!stylesContent.includes('ng-cn.scss')) {
258
- const newContent = `@import './ng-cn.scss';\n\n${stylesContent}`;
699
+ const newContent = `@use './ng-cn.scss';\n\n${stylesContent}`;
259
700
  tree.overwrite(stylesPath, newContent);
260
701
  context.logger.info(` ✓ Imported in styles.scss`);
261
702
  }
262
703
  } else if (tree.exists(stylesCssPath)) {
263
704
  const stylesContent = tree.read(stylesCssPath)!.toString('utf-8');
264
705
  if (!stylesContent.includes('ng-cn.scss')) {
265
- const newContent = `@import './ng-cn.scss';\n\n${stylesContent}`;
706
+ const newContent = `@use './ng-cn.scss';\n\n${stylesContent}`;
266
707
  tree.overwrite(stylesCssPath, newContent);
267
708
  context.logger.info(` ✓ Imported in styles.css`);
268
709
  }
269
710
  }
270
711
  }
271
712
 
713
+ // Create postcss.config.mjs for Tailwind CSS v4
714
+ context.logger.info('');
715
+ context.logger.info('🔧 PostCSS Configuration');
716
+
717
+ const postcssConfigPath = '/postcss.config.mjs';
718
+ if (!tree.exists(postcssConfigPath)) {
719
+ tree.create(postcssConfigPath, POSTCSS_CONFIG_TEMPLATE);
720
+ context.logger.info(` + postcss.config.mjs (Tailwind CSS v4)`);
721
+ } else {
722
+ const existingConfig = tree.read(postcssConfigPath)!.toString('utf-8');
723
+ if (!existingConfig.includes('@tailwindcss/postcss')) {
724
+ // Update existing config to use Tailwind CSS v4
725
+ tree.overwrite(postcssConfigPath, POSTCSS_CONFIG_TEMPLATE);
726
+ context.logger.info(` ✓ postcss.config.mjs updated for Tailwind CSS v4`);
727
+ } else {
728
+ context.logger.info(` ✓ postcss.config.mjs already configured`);
729
+ }
730
+ }
731
+
272
732
  // Update tsconfig paths
273
733
  context.logger.info('');
274
734
  context.logger.info('⚙️ TypeScript Config');
@@ -278,13 +738,19 @@ export function ngAdd(options: NgAddOptions): Rule {
278
738
  const tsconfigContent = tree.read(tsconfigPath)!.toString('utf-8');
279
739
  const tsconfig = parseJsonc(tsconfigContent) as Record<string, any>;
280
740
  tsconfig.compilerOptions = tsconfig.compilerOptions || {};
741
+
742
+ // Set baseUrl for path aliases to work
743
+ if (!tsconfig.compilerOptions.baseUrl) {
744
+ tsconfig.compilerOptions.baseUrl = '.';
745
+ }
746
+
281
747
  tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
282
748
 
283
749
  const pathAliases: Record<string, string[]> = {
284
- '@/*': ['src/*'],
285
- '@/lib/*': ['src/app/lib/*'],
286
- '@/ui/*': ['src/app/lib/components/ui/*'],
287
- '@/utils/*': ['src/app/lib/utils/*']
750
+ '@/*': ['./src/*'],
751
+ '@/lib/*': ['./src/app/lib/*'],
752
+ '@/ui/*': ['./src/app/lib/components/ui/*'],
753
+ '@/utils/*': ['./src/app/lib/utils/*']
288
754
  };
289
755
 
290
756
  let pathsUpdated = false;
@@ -304,13 +770,18 @@ export function ngAdd(options: NgAddOptions): Rule {
304
770
  }
305
771
 
306
772
  // Install selected components
307
- if (options.components && options.components.length > 0) {
773
+ const componentsToInstall = parseComponentsOption(options.components);
774
+ if (componentsToInstall.length > 0) {
308
775
  context.logger.info('');
309
- context.logger.info('📦 Selected Components');
776
+ if (options.components === 'all') {
777
+ context.logger.info(`🚀 Installing ALL ${componentsToInstall.length} components... (Magic Mode)`);
778
+ } else {
779
+ context.logger.info('📦 Selected Components');
780
+ }
310
781
 
311
782
  const packageJson = JSON.parse(tree.read(packageJsonPath)!.toString('utf-8'));
312
783
 
313
- for (const component of options.components) {
784
+ for (const component of componentsToInstall) {
314
785
  const packageName = `@ng-cn/${component}`;
315
786
  if (!packageJson.dependencies?.[packageName]) {
316
787
  packageJson.dependencies = packageJson.dependencies || {};
@@ -339,23 +810,40 @@ export function ngAdd(options: NgAddOptions): Rule {
339
810
  context.logger.info(' ╚═╝ ╚═══╝ ╚═════╝ ╚═════╝╚═╝ ╚═══╝');
340
811
  context.logger.info('');
341
812
  context.logger.info(' ✅ Setup complete! shadcn for Angular');
813
+
814
+ // Show theme info
815
+ const selectedTheme: ThemeVariant = options.theme || 'shadcn';
816
+ const themePalette = THEME_PALETTES[selectedTheme];
817
+ context.logger.info(` 🎨 Theme: ${themePalette.name}`);
342
818
  context.logger.info('');
343
819
 
344
820
  // Show selected components summary if any were installed
345
- if (options.components && options.components.length > 0) {
346
- context.logger.info('╭──────────────────────────────────────────────────╮');
347
- context.logger.info('│ ✨ Components installed: │');
348
- context.logger.info('│ │');
349
- for (const component of options.components) {
350
- const paddedComponent = `@ng-cn/${component}`.padEnd(40);
351
- context.logger.info(`│ ${paddedComponent}│`);
821
+ const componentsInstalled = parseComponentsOption(options.components);
822
+ if (componentsInstalled.length > 0) {
823
+ if (options.components === 'all') {
824
+ context.logger.info('╭──────────────────────────────────────────────────╮');
825
+ context.logger.info('│ ✨ MAGIC MODE - All components installed! │');
826
+ context.logger.info('│ │');
827
+ context.logger.info(`│ ${componentsInstalled.length} components ready to use │`);
828
+ context.logger.info('│ │');
829
+ context.logger.info('╰──────────────────────────────────────────────────╯');
830
+ } else {
831
+ context.logger.info('╭──────────────────────────────────────────────────╮');
832
+ context.logger.info('│ ✨ Components installed: │');
833
+ context.logger.info('│ │');
834
+ for (const component of componentsInstalled) {
835
+ const paddedComponent = `@ng-cn/${component}`.padEnd(40);
836
+ context.logger.info(`│ ${paddedComponent}│`);
837
+ }
838
+ context.logger.info('│ │');
839
+ context.logger.info('╰──────────────────────────────────────────────────╯');
352
840
  }
353
- context.logger.info('│ │');
354
- context.logger.info('╰──────────────────────────────────────────────────╯');
355
841
  context.logger.info('');
356
842
  }
357
843
 
358
844
  context.logger.info('╭──────────────────────────────────────────────────╮');
845
+ context.logger.info('│ ✅ Tailwind CSS v4 configured automatically! │');
846
+ context.logger.info('│ │');
359
847
  context.logger.info('│ 🚀 Next steps: │');
360
848
  context.logger.info('│ │');
361
849
  context.logger.info('│ 1. Add more components: │');
@@ -375,3 +863,25 @@ export function ngAdd(options: NgAddOptions): Rule {
375
863
  return tree;
376
864
  };
377
865
  }
866
+
867
+ /**
868
+ * Parse the components option which can be:
869
+ * - 'all' - install all components
870
+ * - comma-separated list like 'button,card,dialog'
871
+ * - empty string - no components
872
+ */
873
+ function parseComponentsOption(components?: string): string[] {
874
+ if (!components || components.trim() === '') {
875
+ return [];
876
+ }
877
+
878
+ if (components.toLowerCase() === 'all') {
879
+ return ALL_COMPONENTS;
880
+ }
881
+
882
+ // Parse comma-separated list
883
+ return components
884
+ .split(',')
885
+ .map(c => c.trim().toLowerCase())
886
+ .filter(c => c.length > 0 && ALL_COMPONENTS.includes(c));
887
+ }