@gilav21/shadcn-angular 0.0.9 → 0.0.11

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.
@@ -7,8 +7,6 @@ const baseColors = {
7
7
  '--card-foreground': 'oklch(0.145 0 0)',
8
8
  '--popover': 'oklch(1 0 0)',
9
9
  '--popover-foreground': 'oklch(0.145 0 0)',
10
- '--primary': 'oklch(0.205 0 0)',
11
- '--primary-foreground': 'oklch(0.985 0 0)',
12
10
  '--secondary': 'oklch(0.97 0 0)',
13
11
  '--secondary-foreground': 'oklch(0.205 0 0)',
14
12
  '--muted': 'oklch(0.97 0 0)',
@@ -18,7 +16,6 @@ const baseColors = {
18
16
  '--destructive': 'oklch(0.577 0.245 27.325)',
19
17
  '--border': 'oklch(0.922 0 0)',
20
18
  '--input': 'oklch(0.922 0 0)',
21
- '--ring': 'oklch(0.708 0 0)',
22
19
  },
23
20
  dark: {
24
21
  '--background': 'oklch(0.145 0 0)',
@@ -27,8 +24,6 @@ const baseColors = {
27
24
  '--card-foreground': 'oklch(0.985 0 0)',
28
25
  '--popover': 'oklch(0.269 0 0)',
29
26
  '--popover-foreground': 'oklch(0.985 0 0)',
30
- '--primary': 'oklch(0.922 0 0)',
31
- '--primary-foreground': 'oklch(0.205 0 0)',
32
27
  '--secondary': 'oklch(0.269 0 0)',
33
28
  '--secondary-foreground': 'oklch(0.985 0 0)',
34
29
  '--muted': 'oklch(0.269 0 0)',
@@ -38,7 +33,6 @@ const baseColors = {
38
33
  '--destructive': 'oklch(0.704 0.191 22.216)',
39
34
  '--border': 'oklch(1 0 0 / 10%)',
40
35
  '--input': 'oklch(1 0 0 / 15%)',
41
- '--ring': 'oklch(0.556 0 0)',
42
36
  },
43
37
  },
44
38
  slate: {
@@ -49,8 +43,6 @@ const baseColors = {
49
43
  '--card-foreground': 'oklch(0.129 0.042 264.695)',
50
44
  '--popover': 'oklch(1 0 0)',
51
45
  '--popover-foreground': 'oklch(0.129 0.042 264.695)',
52
- '--primary': 'oklch(0.208 0.042 265.755)',
53
- '--primary-foreground': 'oklch(0.984 0.003 247.858)',
54
46
  '--secondary': 'oklch(0.968 0.007 247.896)',
55
47
  '--secondary-foreground': 'oklch(0.208 0.042 265.755)',
56
48
  '--muted': 'oklch(0.968 0.007 247.896)',
@@ -60,7 +52,6 @@ const baseColors = {
60
52
  '--destructive': 'oklch(0.577 0.245 27.325)',
61
53
  '--border': 'oklch(0.929 0.013 255.508)',
62
54
  '--input': 'oklch(0.929 0.013 255.508)',
63
- '--ring': 'oklch(0.704 0.04 256.788)',
64
55
  },
65
56
  dark: {
66
57
  '--background': 'oklch(0.129 0.042 264.695)',
@@ -69,8 +60,6 @@ const baseColors = {
69
60
  '--card-foreground': 'oklch(0.984 0.003 247.858)',
70
61
  '--popover': 'oklch(0.269 0.04 260.031)',
71
62
  '--popover-foreground': 'oklch(0.984 0.003 247.858)',
72
- '--primary': 'oklch(0.929 0.013 255.508)',
73
- '--primary-foreground': 'oklch(0.208 0.042 265.755)',
74
63
  '--secondary': 'oklch(0.269 0.04 260.031)',
75
64
  '--secondary-foreground': 'oklch(0.984 0.003 247.858)',
76
65
  '--muted': 'oklch(0.269 0.04 260.031)',
@@ -80,7 +69,6 @@ const baseColors = {
80
69
  '--destructive': 'oklch(0.704 0.191 22.216)',
81
70
  '--border': 'oklch(1 0 0 / 10%)',
82
71
  '--input': 'oklch(1 0 0 / 15%)',
83
- '--ring': 'oklch(0.554 0.046 257.417)',
84
72
  },
85
73
  },
86
74
  stone: {
@@ -91,8 +79,6 @@ const baseColors = {
91
79
  '--card-foreground': 'oklch(0.147 0.004 49.25)',
92
80
  '--popover': 'oklch(1 0 0)',
93
81
  '--popover-foreground': 'oklch(0.147 0.004 49.25)',
94
- '--primary': 'oklch(0.216 0.006 56.043)',
95
- '--primary-foreground': 'oklch(0.985 0.001 106.423)',
96
82
  '--secondary': 'oklch(0.97 0.001 106.424)',
97
83
  '--secondary-foreground': 'oklch(0.216 0.006 56.043)',
98
84
  '--muted': 'oklch(0.97 0.001 106.424)',
@@ -102,7 +88,6 @@ const baseColors = {
102
88
  '--destructive': 'oklch(0.577 0.245 27.325)',
103
89
  '--border': 'oklch(0.923 0.003 48.717)',
104
90
  '--input': 'oklch(0.923 0.003 48.717)',
105
- '--ring': 'oklch(0.709 0.01 56.259)',
106
91
  },
107
92
  dark: {
108
93
  '--background': 'oklch(0.147 0.004 49.25)',
@@ -111,8 +96,6 @@ const baseColors = {
111
96
  '--card-foreground': 'oklch(0.985 0.001 106.423)',
112
97
  '--popover': 'oklch(0.268 0.007 34.298)',
113
98
  '--popover-foreground': 'oklch(0.985 0.001 106.423)',
114
- '--primary': 'oklch(0.923 0.003 48.717)',
115
- '--primary-foreground': 'oklch(0.216 0.006 56.043)',
116
99
  '--secondary': 'oklch(0.268 0.007 34.298)',
117
100
  '--secondary-foreground': 'oklch(0.985 0.001 106.423)',
118
101
  '--muted': 'oklch(0.268 0.007 34.298)',
@@ -122,7 +105,6 @@ const baseColors = {
122
105
  '--destructive': 'oklch(0.704 0.191 22.216)',
123
106
  '--border': 'oklch(1 0 0 / 10%)',
124
107
  '--input': 'oklch(1 0 0 / 15%)',
125
- '--ring': 'oklch(0.553 0.013 58.071)',
126
108
  },
127
109
  },
128
110
  gray: {
@@ -133,8 +115,6 @@ const baseColors = {
133
115
  '--card-foreground': 'oklch(0.13 0.028 261.692)',
134
116
  '--popover': 'oklch(1 0 0)',
135
117
  '--popover-foreground': 'oklch(0.13 0.028 261.692)',
136
- '--primary': 'oklch(0.21 0.028 264.532)',
137
- '--primary-foreground': 'oklch(0.985 0.002 247.839)',
138
118
  '--secondary': 'oklch(0.967 0.003 264.542)',
139
119
  '--secondary-foreground': 'oklch(0.21 0.028 264.532)',
140
120
  '--muted': 'oklch(0.967 0.003 264.542)',
@@ -144,7 +124,6 @@ const baseColors = {
144
124
  '--destructive': 'oklch(0.577 0.245 27.325)',
145
125
  '--border': 'oklch(0.928 0.006 264.531)',
146
126
  '--input': 'oklch(0.928 0.006 264.531)',
147
- '--ring': 'oklch(0.707 0.022 264.436)',
148
127
  },
149
128
  dark: {
150
129
  '--background': 'oklch(0.13 0.028 261.692)',
@@ -153,8 +132,6 @@ const baseColors = {
153
132
  '--card-foreground': 'oklch(0.985 0.002 247.839)',
154
133
  '--popover': 'oklch(0.274 0.029 256.848)',
155
134
  '--popover-foreground': 'oklch(0.985 0.002 247.839)',
156
- '--primary': 'oklch(0.928 0.006 264.531)',
157
- '--primary-foreground': 'oklch(0.21 0.028 264.532)',
158
135
  '--secondary': 'oklch(0.274 0.029 256.848)',
159
136
  '--secondary-foreground': 'oklch(0.985 0.002 247.839)',
160
137
  '--muted': 'oklch(0.274 0.029 256.848)',
@@ -164,7 +141,6 @@ const baseColors = {
164
141
  '--destructive': 'oklch(0.704 0.191 22.216)',
165
142
  '--border': 'oklch(1 0 0 / 10%)',
166
143
  '--input': 'oklch(1 0 0 / 15%)',
167
- '--ring': 'oklch(0.551 0.027 264.364)',
168
144
  },
169
145
  },
170
146
  zinc: {
@@ -175,8 +151,6 @@ const baseColors = {
175
151
  '--card-foreground': 'oklch(0.141 0.005 285.823)',
176
152
  '--popover': 'oklch(1 0 0)',
177
153
  '--popover-foreground': 'oklch(0.141 0.005 285.823)',
178
- '--primary': 'oklch(0.21 0.006 285.885)',
179
- '--primary-foreground': 'oklch(0.985 0 0)',
180
154
  '--secondary': 'oklch(0.967 0.001 286.375)',
181
155
  '--secondary-foreground': 'oklch(0.21 0.006 285.885)',
182
156
  '--muted': 'oklch(0.967 0.001 286.375)',
@@ -186,7 +160,6 @@ const baseColors = {
186
160
  '--destructive': 'oklch(0.577 0.245 27.325)',
187
161
  '--border': 'oklch(0.92 0.004 286.32)',
188
162
  '--input': 'oklch(0.92 0.004 286.32)',
189
- '--ring': 'oklch(0.705 0.015 286.067)',
190
163
  },
191
164
  dark: {
192
165
  '--background': 'oklch(0.141 0.005 285.823)',
@@ -195,8 +168,6 @@ const baseColors = {
195
168
  '--card-foreground': 'oklch(0.985 0 0)',
196
169
  '--popover': 'oklch(0.274 0.006 286.033)',
197
170
  '--popover-foreground': 'oklch(0.985 0 0)',
198
- '--primary': 'oklch(0.92 0.004 286.32)',
199
- '--primary-foreground': 'oklch(0.21 0.006 285.885)',
200
171
  '--secondary': 'oklch(0.274 0.006 286.033)',
201
172
  '--secondary-foreground': 'oklch(0.985 0 0)',
202
173
  '--muted': 'oklch(0.274 0.006 286.033)',
@@ -206,17 +177,73 @@ const baseColors = {
206
177
  '--destructive': 'oklch(0.704 0.191 22.216)',
207
178
  '--border': 'oklch(1 0 0 / 10%)',
208
179
  '--input': 'oklch(1 0 0 / 15%)',
209
- '--ring': 'oklch(0.552 0.016 285.938)',
210
180
  },
211
181
  },
212
182
  };
183
+ const themeColors = {
184
+ neutral: {
185
+ light: { '--primary': 'oklch(0.205 0 0)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.708 0 0)' },
186
+ dark: { '--primary': 'oklch(0.985 0 0)', '--primary-foreground': 'oklch(0.205 0 0)', '--ring': 'oklch(0.708 0 0)' },
187
+ },
188
+ zinc: {
189
+ light: { '--primary': 'oklch(0.205 0 0)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.708 0 0)' },
190
+ dark: { '--primary': 'oklch(0.985 0 0)', '--primary-foreground': 'oklch(0.205 0 0)', '--ring': 'oklch(0.708 0 0)' },
191
+ },
192
+ slate: {
193
+ light: { '--primary': 'oklch(0.208 0.042 265.755)', '--primary-foreground': 'oklch(0.984 0.003 247.858)', '--ring': 'oklch(0.704 0.04 256.788)' },
194
+ dark: { '--primary': 'oklch(0.929 0.013 255.508)', '--primary-foreground': 'oklch(0.208 0.042 265.755)', '--ring': 'oklch(0.704 0.04 256.788)' },
195
+ },
196
+ stone: {
197
+ light: { '--primary': 'oklch(0.216 0.006 56.043)', '--primary-foreground': 'oklch(0.985 0.001 106.423)', '--ring': 'oklch(0.709 0.01 56.259)' },
198
+ dark: { '--primary': 'oklch(0.923 0.003 48.717)', '--primary-foreground': 'oklch(0.216 0.006 56.043)', '--ring': 'oklch(0.553 0.013 58.071)' },
199
+ },
200
+ gray: {
201
+ light: { '--primary': 'oklch(0.21 0.028 264.532)', '--primary-foreground': 'oklch(0.985 0.002 247.839)', '--ring': 'oklch(0.707 0.022 264.436)' },
202
+ dark: { '--primary': 'oklch(0.928 0.006 264.531)', '--primary-foreground': 'oklch(0.21 0.028 264.532)', '--ring': 'oklch(0.551 0.027 264.364)' },
203
+ },
204
+ red: {
205
+ light: { '--primary': 'oklch(0.577 0.245 27.325)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.577 0.245 27.325)' },
206
+ dark: { '--primary': 'oklch(0.577 0.245 27.325)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.577 0.245 27.325)' },
207
+ },
208
+ rose: {
209
+ light: { '--primary': 'oklch(0.645 0.246 16.439)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.645 0.246 16.439)' },
210
+ dark: { '--primary': 'oklch(0.645 0.246 16.439)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.645 0.246 16.439)' },
211
+ },
212
+ orange: {
213
+ light: { '--primary': 'oklch(0.646 0.222 41.116)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.646 0.222 41.116)' },
214
+ dark: { '--primary': 'oklch(0.646 0.222 41.116)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.646 0.222 41.116)' },
215
+ },
216
+ green: {
217
+ light: { '--primary': 'oklch(0.623 0.214 131.655)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.623 0.214 131.655)' },
218
+ dark: { '--primary': 'oklch(0.623 0.214 131.655)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.623 0.214 131.655)' },
219
+ },
220
+ blue: {
221
+ light: { '--primary': 'oklch(0.546 0.245 262.881)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.546 0.245 262.881)' },
222
+ dark: { '--primary': 'oklch(0.546 0.245 262.881)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.546 0.245 262.881)' },
223
+ },
224
+ yellow: {
225
+ light: { '--primary': 'oklch(0.82 0.185 84.67)', '--primary-foreground': 'oklch(0.205 0 0)', '--ring': 'oklch(0.82 0.185 84.67)' },
226
+ dark: { '--primary': 'oklch(0.82 0.185 84.67)', '--primary-foreground': 'oklch(0.205 0 0)', '--ring': 'oklch(0.82 0.185 84.67)' },
227
+ },
228
+ violet: {
229
+ light: { '--primary': 'oklch(0.558 0.288 302.321)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.558 0.288 302.321)' },
230
+ dark: { '--primary': 'oklch(0.558 0.288 302.321)', '--primary-foreground': 'oklch(0.985 0 0)', '--ring': 'oklch(0.558 0.288 302.321)' },
231
+ },
232
+ amber: {
233
+ light: { '--primary': 'oklch(0.77 0.16 70)', '--primary-foreground': 'oklch(0.145 0 0)', '--ring': 'oklch(0.77 0.16 70)' },
234
+ dark: { '--primary': 'oklch(0.77 0.16 70)', '--primary-foreground': 'oklch(0.145 0 0)', '--ring': 'oklch(0.77 0.16 70)' },
235
+ },
236
+ };
213
237
  function generateCssVars(vars, indent = ' ') {
214
238
  return Object.entries(vars)
215
239
  .map(([key, value]) => `${indent}${key}: ${value};`)
216
240
  .join('\n');
217
241
  }
218
- export function getStylesTemplate(baseColor = 'neutral') {
219
- const colors = baseColors[baseColor];
242
+ export function getStylesTemplate(baseColor = 'neutral', themeColor = 'zinc') {
243
+ const base = baseColors[baseColor];
244
+ const theme = themeColors[themeColor] || themeColors.neutral;
245
+ const lightVars = { ...base.light, ...theme.light };
246
+ const darkVars = { ...base.dark, ...theme.dark };
220
247
  return `@import "tailwindcss";
221
248
 
222
249
  /* Tell Tailwind v4 where to scan for classes */
@@ -227,7 +254,7 @@ export function getStylesTemplate(baseColor = 'neutral') {
227
254
 
228
255
  :root {
229
256
  --radius: 0.625rem;
230
- ${generateCssVars(colors.light)}
257
+ ${generateCssVars(lightVars)}
231
258
  --chart-1: oklch(0.646 0.222 41.116);
232
259
  --chart-2: oklch(0.6 0.118 184.704);
233
260
  --chart-3: oklch(0.398 0.07 227.392);
@@ -244,7 +271,7 @@ ${generateCssVars(colors.light)}
244
271
  }
245
272
 
246
273
  .dark {
247
- ${generateCssVars(colors.dark)}
274
+ ${generateCssVars(darkVars)}
248
275
  --chart-1: oklch(0.488 0.243 264.376);
249
276
  --chart-2: oklch(0.696 0.17 162.48);
250
277
  --chart-3: oklch(0.769 0.188 70.08);
@@ -314,6 +341,7 @@ ${generateCssVars(colors.dark)}
314
341
  }
315
342
  html {
316
343
  font-family: var(--font-sans);
344
+ font-size: 16px;
317
345
  }
318
346
  button:not(:disabled),
319
347
  [role="button"]:not(:disabled) {
@@ -8,5 +8,14 @@ import { twMerge } from 'tailwind-merge';
8
8
  export function cn(...inputs: ClassValue[]): string {
9
9
  return twMerge(clsx(inputs));
10
10
  }
11
+
12
+ /**
13
+ * Check if the current direction is RTL by reading the computed style of the element.
14
+ * This allows components to detect RTL without needing an explicit input.
15
+ */
16
+ export function isRtl(el: HTMLElement): boolean {
17
+ return getComputedStyle(el).direction === 'rtl';
18
+ }
19
+
11
20
  `;
12
21
  }
@@ -1,9 +1,10 @@
1
1
  export interface Config {
2
2
  $schema: string;
3
- style: 'default' | 'new-york';
3
+ style: 'default';
4
4
  tailwind: {
5
5
  css: string;
6
6
  baseColor: 'neutral' | 'slate' | 'stone' | 'gray' | 'zinc';
7
+ theme?: 'zinc' | 'slate' | 'stone' | 'gray' | 'neutral' | 'red' | 'rose' | 'orange' | 'green' | 'blue' | 'yellow' | 'violet' | 'amber';
7
8
  cssVariables: boolean;
8
9
  };
9
10
  aliases: {
@@ -7,6 +7,7 @@ export function getDefaultConfig() {
7
7
  tailwind: {
8
8
  css: 'src/styles.scss',
9
9
  baseColor: 'neutral',
10
+ theme: 'zinc',
10
11
  cssVariables: true,
11
12
  },
12
13
  aliases: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gilav21/shadcn-angular",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "CLI for adding shadcn-angular components to your project",
5
5
  "bin": {
6
6
  "shadcn-angular": "./dist/index.js"
@@ -32,4 +32,4 @@
32
32
  "@types/prompts": "^2.4.9",
33
33
  "typescript": "^5.5.0"
34
34
  }
35
- }
35
+ }
@@ -124,38 +124,106 @@ export async function add(components: string[], options: AddOptions) {
124
124
  ? path.join(cwd, options.path)
125
125
  : path.join(cwd, 'src/components/ui');
126
126
 
127
- // Check for existing files
128
- const existing: string[] = [];
127
+ // Check for existing files and diff
128
+ const componentsToInstall: ComponentName[] = [];
129
+ const componentsToSkip: string[] = [];
130
+ const conflictingComponents: ComponentName[] = [];
131
+ const contentCache = new Map<string, string>();
132
+
133
+ const checkSpinner = ora('Checking for conflicts...').start();
134
+
129
135
  for (const name of allComponents) {
130
136
  const component = registry[name];
137
+ let hasChanges = false;
138
+ let isFullyPresent = true;
139
+
131
140
  for (const file of component.files) {
132
141
  const targetPath = path.join(targetDir, file);
133
142
  if (await fs.pathExists(targetPath)) {
134
- existing.push(file);
143
+ const localContent = await fs.readFile(targetPath, 'utf-8');
144
+
145
+ try {
146
+ let remoteContent = await fetchComponentContent(file, options);
147
+ // Transform imports for comparison
148
+ const utilsAlias = config.aliases.utils;
149
+ remoteContent = remoteContent.replace(/(\.\.\/)+lib\/utils/g, utilsAlias);
150
+
151
+ const normalize = (str: string) => str.replace(/\s+/g, '').trim();
152
+ if (normalize(localContent) !== normalize(remoteContent)) {
153
+ hasChanges = true;
154
+ }
155
+ contentCache.set(file, remoteContent); // Cache for installation
156
+ } catch (error) {
157
+ // unexpected error fetching remote
158
+ console.warn(`Could not fetch remote content for comparison: ${file}`);
159
+ hasChanges = true; // Assume changed/unknown
160
+ }
161
+ } else {
162
+ isFullyPresent = false;
135
163
  }
136
164
  }
165
+
166
+ if (isFullyPresent && !hasChanges) {
167
+ componentsToSkip.push(name);
168
+ } else if (hasChanges) {
169
+ conflictingComponents.push(name);
170
+ } else {
171
+ componentsToInstall.push(name);
172
+ }
137
173
  }
138
174
 
139
- if (existing.length > 0 && !options.overwrite && !options.yes) {
140
- const { overwrite } = await prompts({
141
- type: 'confirm',
142
- name: 'overwrite',
143
- message: `The following files already exist: ${existing.join(', ')}. Overwrite?`,
144
- initial: false,
145
- });
146
- if (!overwrite) {
147
- console.log(chalk.dim('Installation cancelled.'));
148
- return;
175
+ checkSpinner.stop();
176
+
177
+ let componentsToOverwrite: ComponentName[] = [];
178
+
179
+ if (conflictingComponents.length > 0) {
180
+ if (options.overwrite) {
181
+ componentsToOverwrite = conflictingComponents;
182
+ } else if (options.yes) {
183
+ componentsToOverwrite = []; // Skip conflicts in non-interactive mode unless --overwrite
184
+ } else {
185
+ console.log(chalk.yellow(`\n${conflictingComponents.length} component(s) have local changes or are different from remote.`));
186
+ const { selected } = await prompts({
187
+ type: 'multiselect',
188
+ name: 'selected',
189
+ message: 'Select components to OVERWRITE (Unselected will be skipped):',
190
+ choices: conflictingComponents.map(name => ({
191
+ title: name,
192
+ value: name,
193
+ })),
194
+ hint: '- Space to select, Enter to confirm',
195
+ });
196
+ componentsToOverwrite = selected || [];
149
197
  }
150
198
  }
151
199
 
200
+ // Final list of components to process
201
+ // We process:
202
+ // 1. componentsToInstall (Brand new or partial)
203
+ // 2. componentsToOverwrite (User selected)
204
+ // We SKIP:
205
+ // 1. componentsToSkip (Identical)
206
+ // 2. conflictingComponents NOT in componentsToOverwrite
207
+
208
+ const finalComponents = [...componentsToInstall, ...componentsToOverwrite];
209
+
210
+ if (finalComponents.length === 0 && componentsToSkip.length > 0) {
211
+ console.log(chalk.green(`\nAll components are up to date! (${componentsToSkip.length} skipped)`));
212
+ return;
213
+ }
214
+
215
+ if (finalComponents.length === 0) {
216
+ console.log(chalk.dim('\nNo components to install.'));
217
+ return;
218
+ }
219
+
152
220
  const spinner = ora('Installing components...').start();
153
221
  let successCount = 0;
154
222
 
155
223
  try {
156
224
  await fs.ensureDir(targetDir);
157
225
 
158
- for (const name of allComponents) {
226
+ for (const name of finalComponents) {
159
227
  const component = registry[name];
160
228
  let componentSuccess = true;
161
229
 
@@ -163,35 +231,48 @@ export async function add(components: string[], options: AddOptions) {
163
231
  const targetPath = path.join(targetDir, file);
164
232
 
165
233
  try {
166
- let content = await fetchComponentContent(file, options);
234
+ let content = contentCache.get(file);
235
+ if (!content) {
236
+ content = await fetchComponentContent(file, options);
237
+ // Transform imports if not already transformed (cached is transformed)
238
+ const utilsAlias = config.aliases.utils;
239
+ content = content.replace(/(\.\.\/)+lib\/utils/g, utilsAlias);
240
+ }
167
241
 
168
- // Transform imports
169
- // Replace ../lib/utils (or similar relative paths) with the configured alias
170
- const utilsAlias = config.aliases.utils;
171
- content = content.replace(/(\.\.\/)+lib\/utils/g, utilsAlias);
172
242
  await fs.ensureDir(path.dirname(targetPath));
173
243
  await fs.writeFile(targetPath, content);
174
- spinner.text = `Added ${file}`;
244
+ // spinner.text = `Added ${file}`; // Too verbose?
175
245
  } catch (err: any) {
176
246
  spinner.warn(`Could not add ${file}: ${err.message}`);
177
247
  componentSuccess = false;
178
248
  }
179
249
  }
180
- if (componentSuccess) successCount++;
250
+ if (componentSuccess) {
251
+ successCount++;
252
+ spinner.text = `Added ${name}`;
253
+ }
181
254
  }
182
255
 
183
256
  if (successCount > 0) {
184
- spinner.succeed(chalk.green(`Added ${successCount} component(s)`));
257
+ spinner.succeed(chalk.green(`Success! Added ${successCount} component(s)`));
185
258
 
186
259
  console.log('\n' + chalk.dim('Components added:'));
187
- allComponents.forEach(name => {
260
+ finalComponents.forEach(name => {
188
261
  console.log(chalk.dim(' - ') + chalk.cyan(name));
189
262
  });
190
- console.log('');
191
263
  } else {
192
- spinner.fail(chalk.red('Failed to add any components.'));
264
+ spinner.info('No new components installed.');
193
265
  }
194
266
 
267
+ if (componentsToSkip.length > 0) {
268
+ console.log('\n' + chalk.dim('Components skipped (up to date):'));
269
+ componentsToSkip.forEach(name => {
270
+ console.log(chalk.dim(' - ') + chalk.gray(name));
271
+ });
272
+ }
273
+
274
+ console.log('');
275
+
195
276
  } catch (error) {
196
277
  spinner.fail('Failed to add components');
197
278
  console.error(error);
@@ -46,29 +46,70 @@ export async function init(options: InitOptions) {
46
46
  if (options.defaults || options.yes) {
47
47
  config = getDefaultConfig();
48
48
  } else {
49
+ const THEME_COLORS: Record<string, string> = {
50
+ zinc: '#71717a',
51
+ slate: '#64748b',
52
+ stone: '#78716c',
53
+ gray: '#6b7280',
54
+ neutral: '#737373',
55
+ red: '#ef4444',
56
+ rose: '#f43f5e',
57
+ orange: '#f97316', // bright orange
58
+ green: '#22c55e',
59
+ blue: '#3b82f6',
60
+ yellow: '#eab308',
61
+ violet: '#8b5cf6',
62
+ amber: '#d97706', // warm amber for preview
63
+ };
64
+
65
+ const themeChoices = [
66
+ { title: 'Zinc', value: 'zinc' },
67
+ { title: 'Slate', value: 'slate' },
68
+ { title: 'Stone', value: 'stone' },
69
+ { title: 'Gray', value: 'gray' },
70
+ { title: 'Neutral', value: 'neutral' },
71
+ { title: 'Red', value: 'red' },
72
+ { title: 'Rose', value: 'rose' },
73
+ { title: 'Orange', value: 'orange' },
74
+ { title: 'Green', value: 'green' },
75
+ { title: 'Blue', value: 'blue' },
76
+ { title: 'Yellow', value: 'yellow' },
77
+ { title: 'Violet', value: 'violet' },
78
+ { title: 'Amber', value: 'amber' },
79
+ ].map(c => ({
80
+ ...c,
81
+ title: `${chalk.hex(THEME_COLORS[c.value])('██')} ${c.title}`
82
+ }));
83
+
84
+ const baseColorChoices = [
85
+ { title: 'Neutral', value: 'neutral' },
86
+ { title: 'Slate', value: 'slate' },
87
+ { title: 'Stone', value: 'stone' },
88
+ { title: 'Gray', value: 'gray' },
89
+ { title: 'Zinc', value: 'zinc' },
90
+ ].map(c => ({
91
+ ...c,
92
+ title: `${chalk.hex(THEME_COLORS[c.value])('██')} ${c.title}`
93
+ }));
94
+
49
95
  const responses = await prompts([
96
+
50
97
  {
51
98
  type: 'select',
52
- name: 'style',
53
- message: 'Which style would you like to use?',
54
- choices: [
55
- { title: 'Default', value: 'default' },
56
- { title: 'New York', value: 'new-york' },
57
- ],
99
+ name: 'baseColor',
100
+ message: 'Which color would you like to use as base color?',
101
+ choices: baseColorChoices,
58
102
  initial: 0,
59
103
  },
60
104
  {
61
105
  type: 'select',
62
- name: 'baseColor',
63
- message: 'Which color would you like to use as base color?',
64
- choices: [
65
- { title: 'Neutral', value: 'neutral' },
66
- { title: 'Slate', value: 'slate' },
67
- { title: 'Stone', value: 'stone' },
68
- { title: 'Gray', value: 'gray' },
69
- { title: 'Zinc', value: 'zinc' },
70
- ],
71
- initial: 0,
106
+ name: 'theme',
107
+ message: 'Which color would you like to use for the main theme?',
108
+ choices: themeChoices,
109
+ initial: (prev: string) => {
110
+ const index = themeChoices.findIndex(c => c.value === prev);
111
+ return index === -1 ? 0 : index;
112
+ },
72
113
  },
73
114
  {
74
115
  type: 'text',
@@ -92,10 +133,11 @@ export async function init(options: InitOptions) {
92
133
 
93
134
  config = {
94
135
  $schema: 'https://shadcn-angular.dev/schema.json',
95
- style: responses.style,
136
+ style: 'default',
96
137
  tailwind: {
97
138
  css: responses.globalCss,
98
139
  baseColor: responses.baseColor,
140
+ theme: responses.theme,
99
141
  cssVariables: true,
100
142
  },
101
143
  aliases: {
@@ -133,7 +175,7 @@ export async function init(options: InitOptions) {
133
175
  const tailwindCssPath = path.join(stylesDir, 'tailwind.css');
134
176
 
135
177
  // Write the tailwind.css file with all Tailwind directives
136
- await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor));
178
+ await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
137
179
  spinner.text = 'Created tailwind.css';
138
180
 
139
181
  // Add import to the user's global styles file if not already present
@@ -182,6 +224,44 @@ export async function init(options: InitOptions) {
182
224
  await fs.writeJson(postcssrcPath, configContent, { spaces: 4 });
183
225
  }
184
226
 
227
+ // Configure app.config.ts with Lucide icons
228
+ spinner.text = 'Configuring icons in app.config.ts...';
229
+ const appConfigPath = path.join(cwd, 'src/app/app.config.ts');
230
+
231
+ if (await fs.pathExists(appConfigPath)) {
232
+ let appConfigContent = await fs.readFile(appConfigPath, 'utf-8');
233
+
234
+ // Add imports
235
+ if (!appConfigContent.includes('LucideAngularModule')) {
236
+ const iconImports = "import { LucideAngularModule, ArrowDown, ArrowUp, ChevronsUpDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-angular';";
237
+ appConfigContent = iconImports + '\n' + appConfigContent;
238
+ }
239
+
240
+ if (!appConfigContent.includes('importProvidersFrom')) {
241
+ appConfigContent = "import { importProvidersFrom } from '@angular/core';\n" + appConfigContent;
242
+ }
243
+
244
+ // Add provider
245
+ const providerCode = `
246
+ importProvidersFrom(LucideAngularModule.pick({
247
+ ArrowDown,
248
+ ArrowUp,
249
+ ChevronsUpDown,
250
+ ChevronLeft,
251
+ ChevronRight,
252
+ ChevronsLeft,
253
+ ChevronsRight
254
+ }))`;
255
+
256
+ if (!appConfigContent.includes('LucideAngularModule.pick')) {
257
+ appConfigContent = appConfigContent.replace(
258
+ /providers:\s*\[/,
259
+ `providers: [${providerCode},`
260
+ );
261
+ await fs.writeFile(appConfigPath, appConfigContent);
262
+ }
263
+ }
264
+
185
265
  spinner.succeed(chalk.green('Project initialized successfully!'));
186
266
 
187
267
  console.log('\n' + chalk.bold('Next steps:'));
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@ const program = new Command();
8
8
  program
9
9
  .name('shadcn-angular')
10
10
  .description('CLI for adding shadcn-angular components to your Angular project')
11
- .version('0.0.8');
11
+ .version('0.0.10');
12
12
 
13
13
  program
14
14
  .command('init')