@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.
@@ -105,67 +105,139 @@ export async function add(components, options) {
105
105
  const targetDir = options.path
106
106
  ? path.join(cwd, options.path)
107
107
  : path.join(cwd, 'src/components/ui');
108
- // Check for existing files
109
- const existing = [];
108
+ // Check for existing files and diff
109
+ const componentsToInstall = [];
110
+ const componentsToSkip = [];
111
+ const conflictingComponents = [];
112
+ const contentCache = new Map();
113
+ const checkSpinner = ora('Checking for conflicts...').start();
110
114
  for (const name of allComponents) {
111
115
  const component = registry[name];
116
+ let hasChanges = false;
117
+ let isFullyPresent = true;
112
118
  for (const file of component.files) {
113
119
  const targetPath = path.join(targetDir, file);
114
120
  if (await fs.pathExists(targetPath)) {
115
- existing.push(file);
121
+ const localContent = await fs.readFile(targetPath, 'utf-8');
122
+ try {
123
+ let remoteContent = await fetchComponentContent(file, options);
124
+ // Transform imports for comparison
125
+ const utilsAlias = config.aliases.utils;
126
+ remoteContent = remoteContent.replace(/(\.\.\/)+lib\/utils/g, utilsAlias);
127
+ const normalize = (str) => str.replace(/\s+/g, '').trim();
128
+ if (normalize(localContent) !== normalize(remoteContent)) {
129
+ hasChanges = true;
130
+ }
131
+ contentCache.set(file, remoteContent); // Cache for installation
132
+ }
133
+ catch (error) {
134
+ // unexpected error fetching remote
135
+ console.warn(`Could not fetch remote content for comparison: ${file}`);
136
+ hasChanges = true; // Assume changed/unknown
137
+ }
138
+ }
139
+ else {
140
+ isFullyPresent = false;
116
141
  }
117
142
  }
143
+ if (isFullyPresent && !hasChanges) {
144
+ componentsToSkip.push(name);
145
+ }
146
+ else if (hasChanges) {
147
+ conflictingComponents.push(name);
148
+ }
149
+ else {
150
+ componentsToInstall.push(name);
151
+ }
118
152
  }
119
- if (existing.length > 0 && !options.overwrite && !options.yes) {
120
- const { overwrite } = await prompts({
121
- type: 'confirm',
122
- name: 'overwrite',
123
- message: `The following files already exist: ${existing.join(', ')}. Overwrite?`,
124
- initial: false,
125
- });
126
- if (!overwrite) {
127
- console.log(chalk.dim('Installation cancelled.'));
128
- return;
153
+ checkSpinner.stop();
154
+ let componentsToOverwrite = [];
155
+ if (conflictingComponents.length > 0) {
156
+ if (options.overwrite) {
157
+ componentsToOverwrite = conflictingComponents;
158
+ }
159
+ else if (options.yes) {
160
+ componentsToOverwrite = []; // Skip conflicts in non-interactive mode unless --overwrite
161
+ }
162
+ else {
163
+ console.log(chalk.yellow(`\n${conflictingComponents.length} component(s) have local changes or are different from remote.`));
164
+ const { selected } = await prompts({
165
+ type: 'multiselect',
166
+ name: 'selected',
167
+ message: 'Select components to OVERWRITE (Unselected will be skipped):',
168
+ choices: conflictingComponents.map(name => ({
169
+ title: name,
170
+ value: name,
171
+ })),
172
+ hint: '- Space to select, Enter to confirm',
173
+ });
174
+ componentsToOverwrite = selected || [];
129
175
  }
130
176
  }
177
+ // Final list of components to process
178
+ // We process:
179
+ // 1. componentsToInstall (Brand new or partial)
180
+ // 2. componentsToOverwrite (User selected)
181
+ // We SKIP:
182
+ // 1. componentsToSkip (Identical)
183
+ // 2. conflictingComponents NOT in componentsToOverwrite
184
+ const finalComponents = [...componentsToInstall, ...componentsToOverwrite];
185
+ if (finalComponents.length === 0 && componentsToSkip.length > 0) {
186
+ console.log(chalk.green(`\nAll components are up to date! (${componentsToSkip.length} skipped)`));
187
+ return;
188
+ }
189
+ if (finalComponents.length === 0) {
190
+ console.log(chalk.dim('\nNo components to install.'));
191
+ return;
192
+ }
131
193
  const spinner = ora('Installing components...').start();
132
194
  let successCount = 0;
133
195
  try {
134
196
  await fs.ensureDir(targetDir);
135
- for (const name of allComponents) {
197
+ for (const name of finalComponents) {
136
198
  const component = registry[name];
137
199
  let componentSuccess = true;
138
200
  for (const file of component.files) {
139
201
  const targetPath = path.join(targetDir, file);
140
202
  try {
141
- let content = await fetchComponentContent(file, options);
142
- // Transform imports
143
- // Replace ../lib/utils (or similar relative paths) with the configured alias
144
- const utilsAlias = config.aliases.utils;
145
- content = content.replace(/(\.\.\/)+lib\/utils/g, utilsAlias);
203
+ let content = contentCache.get(file);
204
+ if (!content) {
205
+ content = await fetchComponentContent(file, options);
206
+ // Transform imports if not already transformed (cached is transformed)
207
+ const utilsAlias = config.aliases.utils;
208
+ content = content.replace(/(\.\.\/)+lib\/utils/g, utilsAlias);
209
+ }
146
210
  await fs.ensureDir(path.dirname(targetPath));
147
211
  await fs.writeFile(targetPath, content);
148
- spinner.text = `Added ${file}`;
212
+ // spinner.text = `Added ${file}`; // Too verbose?
149
213
  }
150
214
  catch (err) {
151
215
  spinner.warn(`Could not add ${file}: ${err.message}`);
152
216
  componentSuccess = false;
153
217
  }
154
218
  }
155
- if (componentSuccess)
219
+ if (componentSuccess) {
156
220
  successCount++;
221
+ spinner.text = `Added ${name}`;
222
+ }
157
223
  }
158
224
  if (successCount > 0) {
159
- spinner.succeed(chalk.green(`Added ${successCount} component(s)`));
225
+ spinner.succeed(chalk.green(`Success! Added ${successCount} component(s)`));
160
226
  console.log('\n' + chalk.dim('Components added:'));
161
- allComponents.forEach(name => {
227
+ finalComponents.forEach(name => {
162
228
  console.log(chalk.dim(' - ') + chalk.cyan(name));
163
229
  });
164
- console.log('');
165
230
  }
166
231
  else {
167
- spinner.fail(chalk.red('Failed to add any components.'));
232
+ spinner.info('No new components installed.');
233
+ }
234
+ if (componentsToSkip.length > 0) {
235
+ console.log('\n' + chalk.dim('Components skipped (up to date):'));
236
+ componentsToSkip.forEach(name => {
237
+ console.log(chalk.dim(' - ') + chalk.gray(name));
238
+ });
168
239
  }
240
+ console.log('');
169
241
  }
170
242
  catch (error) {
171
243
  spinner.fail('Failed to add components');
@@ -36,29 +36,66 @@ export async function init(options) {
36
36
  config = getDefaultConfig();
37
37
  }
38
38
  else {
39
+ const THEME_COLORS = {
40
+ zinc: '#71717a',
41
+ slate: '#64748b',
42
+ stone: '#78716c',
43
+ gray: '#6b7280',
44
+ neutral: '#737373',
45
+ red: '#ef4444',
46
+ rose: '#f43f5e',
47
+ orange: '#f97316', // bright orange
48
+ green: '#22c55e',
49
+ blue: '#3b82f6',
50
+ yellow: '#eab308',
51
+ violet: '#8b5cf6',
52
+ amber: '#d97706', // warm amber for preview
53
+ };
54
+ const themeChoices = [
55
+ { title: 'Zinc', value: 'zinc' },
56
+ { title: 'Slate', value: 'slate' },
57
+ { title: 'Stone', value: 'stone' },
58
+ { title: 'Gray', value: 'gray' },
59
+ { title: 'Neutral', value: 'neutral' },
60
+ { title: 'Red', value: 'red' },
61
+ { title: 'Rose', value: 'rose' },
62
+ { title: 'Orange', value: 'orange' },
63
+ { title: 'Green', value: 'green' },
64
+ { title: 'Blue', value: 'blue' },
65
+ { title: 'Yellow', value: 'yellow' },
66
+ { title: 'Violet', value: 'violet' },
67
+ { title: 'Amber', value: 'amber' },
68
+ ].map(c => ({
69
+ ...c,
70
+ title: `${chalk.hex(THEME_COLORS[c.value])('██')} ${c.title}`
71
+ }));
72
+ const baseColorChoices = [
73
+ { title: 'Neutral', value: 'neutral' },
74
+ { title: 'Slate', value: 'slate' },
75
+ { title: 'Stone', value: 'stone' },
76
+ { title: 'Gray', value: 'gray' },
77
+ { title: 'Zinc', value: 'zinc' },
78
+ ].map(c => ({
79
+ ...c,
80
+ title: `${chalk.hex(THEME_COLORS[c.value])('██')} ${c.title}`
81
+ }));
39
82
  const responses = await prompts([
40
83
  {
41
84
  type: 'select',
42
- name: 'style',
43
- message: 'Which style would you like to use?',
44
- choices: [
45
- { title: 'Default', value: 'default' },
46
- { title: 'New York', value: 'new-york' },
47
- ],
85
+ name: 'baseColor',
86
+ message: 'Which color would you like to use as base color?',
87
+ choices: baseColorChoices,
48
88
  initial: 0,
49
89
  },
50
90
  {
51
91
  type: 'select',
52
- name: 'baseColor',
53
- message: 'Which color would you like to use as base color?',
54
- choices: [
55
- { title: 'Neutral', value: 'neutral' },
56
- { title: 'Slate', value: 'slate' },
57
- { title: 'Stone', value: 'stone' },
58
- { title: 'Gray', value: 'gray' },
59
- { title: 'Zinc', value: 'zinc' },
60
- ],
61
- initial: 0,
92
+ name: 'theme',
93
+ message: 'Which color would you like to use for the main theme?',
94
+ choices: themeChoices,
95
+ initial: (prev) => {
96
+ const index = themeChoices.findIndex(c => c.value === prev);
97
+ return index === -1 ? 0 : index;
98
+ },
62
99
  },
63
100
  {
64
101
  type: 'text',
@@ -81,10 +118,11 @@ export async function init(options) {
81
118
  ]);
82
119
  config = {
83
120
  $schema: 'https://shadcn-angular.dev/schema.json',
84
- style: responses.style,
121
+ style: 'default',
85
122
  tailwind: {
86
123
  css: responses.globalCss,
87
124
  baseColor: responses.baseColor,
125
+ theme: responses.theme,
88
126
  cssVariables: true,
89
127
  },
90
128
  aliases: {
@@ -115,7 +153,7 @@ export async function init(options) {
115
153
  const stylesDir = path.dirname(path.join(cwd, config.tailwind.css));
116
154
  const tailwindCssPath = path.join(stylesDir, 'tailwind.css');
117
155
  // Write the tailwind.css file with all Tailwind directives
118
- await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor));
156
+ await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
119
157
  spinner.text = 'Created tailwind.css';
120
158
  // Add import to the user's global styles file if not already present
121
159
  const userStylesPath = path.join(cwd, config.tailwind.css);
@@ -157,6 +195,35 @@ export async function init(options) {
157
195
  };
158
196
  await fs.writeJson(postcssrcPath, configContent, { spaces: 4 });
159
197
  }
198
+ // Configure app.config.ts with Lucide icons
199
+ spinner.text = 'Configuring icons in app.config.ts...';
200
+ const appConfigPath = path.join(cwd, 'src/app/app.config.ts');
201
+ if (await fs.pathExists(appConfigPath)) {
202
+ let appConfigContent = await fs.readFile(appConfigPath, 'utf-8');
203
+ // Add imports
204
+ if (!appConfigContent.includes('LucideAngularModule')) {
205
+ const iconImports = "import { LucideAngularModule, ArrowDown, ArrowUp, ChevronsUpDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-angular';";
206
+ appConfigContent = iconImports + '\n' + appConfigContent;
207
+ }
208
+ if (!appConfigContent.includes('importProvidersFrom')) {
209
+ appConfigContent = "import { importProvidersFrom } from '@angular/core';\n" + appConfigContent;
210
+ }
211
+ // Add provider
212
+ const providerCode = `
213
+ importProvidersFrom(LucideAngularModule.pick({
214
+ ArrowDown,
215
+ ArrowUp,
216
+ ChevronsUpDown,
217
+ ChevronLeft,
218
+ ChevronRight,
219
+ ChevronsLeft,
220
+ ChevronsRight
221
+ }))`;
222
+ if (!appConfigContent.includes('LucideAngularModule.pick')) {
223
+ appConfigContent = appConfigContent.replace(/providers:\s*\[/, `providers: [${providerCode},`);
224
+ await fs.writeFile(appConfigPath, appConfigContent);
225
+ }
226
+ }
160
227
  spinner.succeed(chalk.green('Project initialized successfully!'));
161
228
  console.log('\n' + chalk.bold('Next steps:'));
162
229
  console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx @gilav21/shadcn-angular add button'));
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ const program = new Command();
6
6
  program
7
7
  .name('shadcn-angular')
8
8
  .description('CLI for adding shadcn-angular components to your Angular project')
9
- .version('0.0.8');
9
+ .version('0.0.10');
10
10
  program
11
11
  .command('init')
12
12
  .description('Initialize shadcn-angular in your project')
@@ -7,6 +7,11 @@ export const registry = {
7
7
  name: 'accordion',
8
8
  files: ['accordion.component.ts'],
9
9
  },
10
+ autocomplete: {
11
+ name: 'autocomplete',
12
+ files: ['autocomplete.component.ts', 'highlight.pipe.ts'],
13
+ dependencies: ['popover', 'command', 'badge'],
14
+ },
10
15
  alert: {
11
16
  name: 'alert',
12
17
  files: ['alert.component.ts'],
@@ -61,6 +66,15 @@ export const registry = {
61
66
  name: 'collapsible',
62
67
  files: ['collapsible.component.ts'],
63
68
  },
69
+ 'color-picker': {
70
+ name: 'color-picker',
71
+ files: ['color-picker.component.ts'],
72
+ dependencies: ['popover', 'input', 'tabs'],
73
+ },
74
+ confetti: {
75
+ name: 'confetti',
76
+ files: ['confetti.directive.ts'],
77
+ },
64
78
  command: {
65
79
  name: 'command',
66
80
  files: ['command.component.ts'],
@@ -75,6 +89,25 @@ export const registry = {
75
89
  files: ['date-picker.component.ts'],
76
90
  dependencies: ['calendar'],
77
91
  },
92
+ 'data-table': {
93
+ name: 'data-table',
94
+ files: [
95
+ 'data-table/data-table.component.ts',
96
+ 'data-table/data-table-column-header.component.ts',
97
+ 'data-table/data-table-pagination.component.ts',
98
+ 'data-table/data-table.types.ts',
99
+ 'data-table/cell-host.directive.ts',
100
+ ],
101
+ dependencies: [
102
+ 'table',
103
+ 'input',
104
+ 'button',
105
+ 'checkbox',
106
+ 'select',
107
+ 'pagination',
108
+ 'popover',
109
+ ],
110
+ },
78
111
  dialog: {
79
112
  name: 'dialog',
80
113
  files: ['dialog.component.ts'],
@@ -95,6 +128,11 @@ export const registry = {
95
128
  name: 'field',
96
129
  files: ['field.component.ts'],
97
130
  },
131
+ 'file-upload': {
132
+ name: 'file-upload',
133
+ files: ['file-upload.component.ts'],
134
+ dependencies: ['button', 'progress'],
135
+ },
98
136
  'hover-card': {
99
137
  name: 'hover-card',
100
138
  files: ['hover-card.component.ts'],
@@ -105,7 +143,8 @@ export const registry = {
105
143
  },
106
144
  'input-group': {
107
145
  name: 'input-group',
108
- files: ['input-group.component.ts'],
146
+ files: ['input-group.component.ts', 'input-group.token.ts'],
147
+ dependencies: ['input'],
109
148
  },
110
149
  'input-otp': {
111
150
  name: 'input-otp',
@@ -131,6 +170,10 @@ export const registry = {
131
170
  name: 'navigation-menu',
132
171
  files: ['navigation-menu.component.ts'],
133
172
  },
173
+ 'number-ticker': {
174
+ name: 'number-ticker',
175
+ files: ['number-ticker.component.ts'],
176
+ },
134
177
  pagination: {
135
178
  name: 'pagination',
136
179
  files: ['pagination.component.ts'],
@@ -147,6 +190,10 @@ export const registry = {
147
190
  name: 'radio-group',
148
191
  files: ['radio-group.component.ts'],
149
192
  },
193
+ rating: {
194
+ name: 'rating',
195
+ files: ['rating.component.ts'],
196
+ },
150
197
  resizable: {
151
198
  name: 'resizable',
152
199
  files: ['resizable.component.ts'],
@@ -183,6 +230,10 @@ export const registry = {
183
230
  name: 'spinner',
184
231
  files: ['spinner.component.ts'],
185
232
  },
233
+ stepper: {
234
+ name: 'stepper',
235
+ files: ['stepper.component.ts'],
236
+ },
186
237
  switch: {
187
238
  name: 'switch',
188
239
  files: ['switch.component.ts'],
@@ -199,6 +250,10 @@ export const registry = {
199
250
  name: 'textarea',
200
251
  files: ['textarea.component.ts'],
201
252
  },
253
+ timeline: {
254
+ name: 'timeline',
255
+ files: ['timeline.component.ts'],
256
+ },
202
257
  toast: {
203
258
  name: 'toast',
204
259
  files: ['toast.component.ts'],
@@ -215,6 +270,10 @@ export const registry = {
215
270
  name: 'tooltip',
216
271
  files: ['tooltip.component.ts'],
217
272
  },
273
+ tree: {
274
+ name: 'tree',
275
+ files: ['tree.component.ts'],
276
+ },
218
277
  'speed-dial': {
219
278
  name: 'speed-dial',
220
279
  files: ['speed-dial.component.ts'],
@@ -223,7 +282,7 @@ export const registry = {
223
282
  'chip-list': {
224
283
  name: 'chip-list',
225
284
  files: ['chip-list.component.ts'],
226
- dependencies: ['badge', 'button'],
285
+ dependencies: ['badge', 'button', 'input', 'input-group'],
227
286
  },
228
287
  'emoji-picker': {
229
288
  name: 'emoji-picker',
@@ -250,4 +309,69 @@ export const registry = {
250
309
  'scroll-area',
251
310
  ],
252
311
  },
312
+ // Chart Components
313
+ 'pie-chart': {
314
+ name: 'pie-chart',
315
+ files: [
316
+ 'charts/pie-chart.component.ts',
317
+ 'charts/chart.types.ts',
318
+ 'charts/chart.utils.ts',
319
+ ],
320
+ },
321
+ 'pie-chart-drilldown': {
322
+ name: 'pie-chart-drilldown',
323
+ files: [
324
+ 'charts/pie-chart-drilldown.component.ts',
325
+ 'charts/chart.types.ts',
326
+ 'charts/chart.utils.ts',
327
+ ],
328
+ },
329
+ 'bar-chart': {
330
+ name: 'bar-chart',
331
+ files: [
332
+ 'charts/bar-chart.component.ts',
333
+ 'charts/chart.types.ts',
334
+ 'charts/chart.utils.ts',
335
+ ],
336
+ },
337
+ 'bar-chart-drilldown': {
338
+ name: 'bar-chart-drilldown',
339
+ files: [
340
+ 'charts/bar-chart-drilldown.component.ts',
341
+ 'charts/chart.types.ts',
342
+ 'charts/chart.utils.ts',
343
+ ],
344
+ },
345
+ 'stacked-bar-chart': {
346
+ name: 'stacked-bar-chart',
347
+ files: [
348
+ 'charts/stacked-bar-chart.component.ts',
349
+ 'charts/chart.types.ts',
350
+ 'charts/chart.utils.ts',
351
+ ],
352
+ },
353
+ 'column-range-chart': {
354
+ name: 'column-range-chart',
355
+ files: [
356
+ 'charts/column-range-chart.component.ts',
357
+ 'charts/chart.types.ts',
358
+ 'charts/chart.utils.ts',
359
+ ],
360
+ },
361
+ 'bar-race-chart': {
362
+ name: 'bar-race-chart',
363
+ files: [
364
+ 'charts/bar-race-chart.component.ts',
365
+ 'charts/chart.types.ts',
366
+ 'charts/chart.utils.ts',
367
+ ],
368
+ },
369
+ 'org-chart': {
370
+ name: 'org-chart',
371
+ files: [
372
+ 'charts/org-chart.component.ts',
373
+ 'charts/chart.types.ts',
374
+ 'charts/chart.utils.ts',
375
+ ],
376
+ },
253
377
  };
@@ -1,3 +1,4 @@
1
1
  type BaseColor = 'neutral' | 'slate' | 'stone' | 'gray' | 'zinc';
2
- export declare function getStylesTemplate(baseColor?: BaseColor): string;
2
+ type ThemeColor = 'zinc' | 'slate' | 'stone' | 'gray' | 'neutral' | 'red' | 'rose' | 'orange' | 'green' | 'blue' | 'yellow' | 'violet' | 'amber';
3
+ export declare function getStylesTemplate(baseColor?: BaseColor, themeColor?: ThemeColor): string;
3
4
  export {};