@gilav21/shadcn-angular 0.0.16 → 0.0.18

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.
@@ -38,7 +38,7 @@ async function fetchLibFileContent(file: string): Promise<string> {
38
38
  }
39
39
  return response.text();
40
40
  }
41
-
41
+
42
42
  interface InitOptions {
43
43
  yes?: boolean;
44
44
  defaults?: boolean;
@@ -61,18 +61,18 @@ function resolveAliasOrPath(cwd: string, aliasOrPath: string): string {
61
61
  }
62
62
 
63
63
  export async function init(options: InitOptions) {
64
- console.log(chalk.bold('\nšŸŽØ Welcome to shadcn-angular!\n'));
65
-
66
- const cwd = process.cwd();
67
-
68
- // Check if this is an Angular project
69
- const angularJsonPath = path.join(cwd, 'angular.json');
70
- if (!await fs.pathExists(angularJsonPath)) {
71
- console.log(chalk.red('Error: This does not appear to be an Angular project.'));
72
- console.log(chalk.dim('Please run this command in the root of your Angular project.'));
73
- process.exit(1);
74
- }
75
-
64
+ console.log(chalk.bold('\nšŸŽØ Welcome to shadcn-angular!\n'));
65
+
66
+ const cwd = process.cwd();
67
+
68
+ // Check if this is an Angular project
69
+ const angularJsonPath = path.join(cwd, 'angular.json');
70
+ if (!await fs.pathExists(angularJsonPath)) {
71
+ console.log(chalk.red('Error: This does not appear to be an Angular project.'));
72
+ console.log(chalk.dim('Please run this command in the root of your Angular project.'));
73
+ process.exit(1);
74
+ }
75
+
76
76
  // Check if already initialized
77
77
  const componentsJsonPath = path.join(cwd, 'components.json');
78
78
  if (await fs.pathExists(componentsJsonPath)) {
@@ -87,93 +87,93 @@ export async function init(options: InitOptions) {
87
87
  if (!overwrite) {
88
88
  console.log(chalk.dim('Initialization cancelled.'));
89
89
  return;
90
- }
91
- }
92
-
90
+ }
91
+ }
92
+
93
93
  let config: Config;
94
94
  let createShortcutRegistry = true;
95
-
95
+
96
96
  if (options.defaults || options.yes) {
97
97
  config = getDefaultConfig();
98
98
  createShortcutRegistry = true;
99
99
  } else {
100
- const THEME_COLORS: Record<string, string> = {
101
- zinc: '#71717a',
102
- slate: '#64748b',
103
- stone: '#78716c',
104
- gray: '#6b7280',
105
- neutral: '#737373',
106
- red: '#ef4444',
107
- rose: '#f43f5e',
108
- orange: '#f97316', // bright orange
109
- green: '#22c55e',
110
- blue: '#3b82f6',
111
- yellow: '#eab308',
112
- violet: '#8b5cf6',
113
- amber: '#d97706', // warm amber for preview
114
- };
115
-
116
- const themeChoices = [
117
- { title: 'Zinc', value: 'zinc' },
118
- { title: 'Slate', value: 'slate' },
119
- { title: 'Stone', value: 'stone' },
120
- { title: 'Gray', value: 'gray' },
121
- { title: 'Neutral', value: 'neutral' },
122
- { title: 'Red', value: 'red' },
123
- { title: 'Rose', value: 'rose' },
124
- { title: 'Orange', value: 'orange' },
125
- { title: 'Green', value: 'green' },
126
- { title: 'Blue', value: 'blue' },
127
- { title: 'Yellow', value: 'yellow' },
128
- { title: 'Violet', value: 'violet' },
129
- { title: 'Amber', value: 'amber' },
130
- ].map(c => ({
131
- ...c,
132
- title: `${chalk.hex(THEME_COLORS[c.value])('ā–ˆā–ˆ')} ${c.title}`
133
- }));
134
-
135
- const baseColorChoices = [
136
- { title: 'Neutral', value: 'neutral' },
137
- { title: 'Slate', value: 'slate' },
138
- { title: 'Stone', value: 'stone' },
139
- { title: 'Gray', value: 'gray' },
140
- { title: 'Zinc', value: 'zinc' },
141
- ].map(c => ({
142
- ...c,
143
- title: `${chalk.hex(THEME_COLORS[c.value])('ā–ˆā–ˆ')} ${c.title}`
144
- }));
145
-
146
- const responses = await prompts([
147
-
148
- {
149
- type: 'select',
150
- name: 'baseColor',
151
- message: 'Which color would you like to use as base color?',
152
- choices: baseColorChoices,
153
- initial: 0,
154
- },
155
- {
156
- type: 'select',
157
- name: 'theme',
158
- message: 'Which color would you like to use for the main theme?',
159
- choices: themeChoices,
160
- initial: (prev: string) => {
161
- const index = themeChoices.findIndex(c => c.value === prev);
162
- return index === -1 ? 0 : index;
163
- },
164
- },
165
- {
166
- type: 'text',
167
- name: 'componentsPath',
168
- message: 'Where would you like to install components?',
169
- initial: 'src/components/ui',
170
- },
171
- {
172
- type: 'text',
173
- name: 'utilsPath',
174
- message: 'Where would you like to install utils?',
175
- initial: 'src/components/lib',
176
- },
100
+ const THEME_COLORS: Record<string, string> = {
101
+ zinc: '#71717a',
102
+ slate: '#64748b',
103
+ stone: '#78716c',
104
+ gray: '#6b7280',
105
+ neutral: '#737373',
106
+ red: '#ef4444',
107
+ rose: '#f43f5e',
108
+ orange: '#f97316', // bright orange
109
+ green: '#22c55e',
110
+ blue: '#3b82f6',
111
+ yellow: '#eab308',
112
+ violet: '#8b5cf6',
113
+ amber: '#d97706', // warm amber for preview
114
+ };
115
+
116
+ const themeChoices = [
117
+ { title: 'Zinc', value: 'zinc' },
118
+ { title: 'Slate', value: 'slate' },
119
+ { title: 'Stone', value: 'stone' },
120
+ { title: 'Gray', value: 'gray' },
121
+ { title: 'Neutral', value: 'neutral' },
122
+ { title: 'Red', value: 'red' },
123
+ { title: 'Rose', value: 'rose' },
124
+ { title: 'Orange', value: 'orange' },
125
+ { title: 'Green', value: 'green' },
126
+ { title: 'Blue', value: 'blue' },
127
+ { title: 'Yellow', value: 'yellow' },
128
+ { title: 'Violet', value: 'violet' },
129
+ { title: 'Amber', value: 'amber' },
130
+ ].map(c => ({
131
+ ...c,
132
+ title: `${chalk.hex(THEME_COLORS[c.value])('ā–ˆā–ˆ')} ${c.title}`
133
+ }));
134
+
135
+ const baseColorChoices = [
136
+ { title: 'Neutral', value: 'neutral' },
137
+ { title: 'Slate', value: 'slate' },
138
+ { title: 'Stone', value: 'stone' },
139
+ { title: 'Gray', value: 'gray' },
140
+ { title: 'Zinc', value: 'zinc' },
141
+ ].map(c => ({
142
+ ...c,
143
+ title: `${chalk.hex(THEME_COLORS[c.value])('ā–ˆā–ˆ')} ${c.title}`
144
+ }));
145
+
146
+ const responses = await prompts([
147
+
148
+ {
149
+ type: 'select',
150
+ name: 'baseColor',
151
+ message: 'Which color would you like to use as base color?',
152
+ choices: baseColorChoices,
153
+ initial: 0,
154
+ },
155
+ {
156
+ type: 'select',
157
+ name: 'theme',
158
+ message: 'Which color would you like to use for the main theme?',
159
+ choices: themeChoices,
160
+ initial: (prev: string) => {
161
+ const index = themeChoices.findIndex(c => c.value === prev);
162
+ return index === -1 ? 0 : index;
163
+ },
164
+ },
165
+ {
166
+ type: 'text',
167
+ name: 'componentsPath',
168
+ message: 'Where would you like to install components?',
169
+ initial: 'src/components/ui',
170
+ },
171
+ {
172
+ type: 'text',
173
+ name: 'utilsPath',
174
+ message: 'Where would you like to install utils?',
175
+ initial: 'src/components/lib',
176
+ },
177
177
  {
178
178
  type: 'text',
179
179
  name: 'globalCss',
@@ -187,47 +187,46 @@ export async function init(options: InitOptions) {
187
187
  initial: true,
188
188
  },
189
189
  ]);
190
-
190
+
191
191
  config = {
192
192
  $schema: 'https://shadcn-angular.dev/schema.json',
193
- style: 'default',
194
- tailwind: {
195
- css: responses.globalCss,
196
- baseColor: responses.baseColor,
197
- theme: responses.theme,
198
- cssVariables: true,
199
- },
200
- aliases: {
201
- components: responses.componentsPath.replace('src/', '@/'), // Basic heuristic
202
- utils: responses.utilsPath.replace('src/', '@/').replace('.ts', ''),
203
- ui: responses.componentsPath.replace('src/', '@/'),
193
+ style: 'default',
194
+ tailwind: {
195
+ css: responses.globalCss,
196
+ baseColor: responses.baseColor,
197
+ theme: responses.theme,
198
+ cssVariables: true,
199
+ },
200
+ aliases: {
201
+ components: responses.componentsPath.replace('src/', '@/'), // Basic heuristic
202
+ utils: responses.utilsPath.replace('src/', '@/').replace('.ts', ''),
203
+ ui: responses.componentsPath.replace('src/', '@/'),
204
204
  },
205
205
  };
206
206
  createShortcutRegistry = responses.createShortcutRegistry ?? true;
207
207
  }
208
-
209
- const spinner = ora('Initializing project...').start();
210
-
211
- try {
212
- // Write components.json
213
- await fs.writeJson(componentsJsonPath, config, { spaces: 2 });
214
- spinner.text = 'Created components.json';
215
-
216
- // Create utils directory and file
217
- // Resolve path from the config alias, assuming @/ maps to src/ logic for file creation if not provided directly
218
- // But we have the 'responses' object from CLI prompt only in the else block above!
219
- // So we should rely on config to reconstruct the path, or better yet, if we are in 'defaults' mode, check what config is.
220
- // If config came from defaults, aliases are set.
221
- // We can reverse-map alias to path: @/ -> src/
222
-
223
- const utilsPathResolved = resolveAliasOrPath(cwd, config.aliases.utils + '.ts');
224
- const utilsDir = path.dirname(utilsPathResolved); // utils usually ends in path/to/utils
225
-
226
- await fs.ensureDir(utilsDir);
227
- await fs.writeFile(utilsPathResolved, getUtilsTemplate());
208
+
209
+ const spinner = ora('Initializing project...').start();
210
+
211
+ try {
212
+ // Write components.json
213
+ await fs.writeJson(componentsJsonPath, config, { spaces: 2 });
214
+ spinner.text = 'Created components.json';
215
+
216
+ // Create utils directory and file
217
+ // Resolve path from the config alias, assuming @/ maps to src/ logic for file creation if not provided directly
218
+ // But we have the 'responses' object from CLI prompt only in the else block above!
219
+ // So we should rely on config to reconstruct the path, or better yet, if we are in 'defaults' mode, check what config is.
220
+ // If config came from defaults, aliases are set.
221
+ // We can reverse-map alias to path: @/ -> src/
222
+
223
+ const libDir = resolveAliasOrPath(cwd, config.aliases.utils);
224
+
225
+ await fs.ensureDir(libDir);
226
+ await fs.writeFile(path.join(libDir, 'utils.ts'), getUtilsTemplate());
228
227
  spinner.text = 'Created utils.ts';
229
228
 
230
- const shortcutServicePath = path.join(utilsDir, 'shortcut-binding.service.ts');
229
+ const shortcutServicePath = path.join(libDir, 'shortcut-binding.service.ts');
231
230
  const shortcutServiceContent = await fetchLibFileContent('shortcut-binding.service.ts');
232
231
  await fs.writeFile(shortcutServicePath, shortcutServiceContent);
233
232
  spinner.text = 'Created shortcut-binding.service.ts';
@@ -251,63 +250,63 @@ export async function init(options: InitOptions) {
251
250
  let userStyles = await fs.pathExists(userStylesPath)
252
251
  ? await fs.readFile(userStylesPath, 'utf-8')
253
252
  : '';
254
-
255
- const tailwindImport = '@import "./tailwind.css";';
256
- if (!userStyles.includes('tailwind.css')) {
257
- // Add import at the top of the file
258
- userStyles = tailwindImport + '\n\n' + userStyles;
259
- await fs.writeFile(userStylesPath, userStyles);
260
- spinner.text = 'Added tailwind.css import to styles';
253
+
254
+ const tailwindImport = '@import "./tailwind.css";';
255
+ if (!userStyles.includes('tailwind.css')) {
256
+ // Add import at the top of the file
257
+ userStyles = tailwindImport + '\n\n' + userStyles;
258
+ await fs.writeFile(userStylesPath, userStyles);
259
+ spinner.text = 'Added tailwind.css import to styles';
261
260
  }
262
261
 
263
262
  // Create components/ui directory
264
263
  const uiDir = resolveAliasOrPath(cwd, config.aliases.ui);
265
264
  await fs.ensureDir(uiDir);
266
265
  spinner.text = 'Created components directory';
267
-
268
- // Install dependencies
269
- spinner.text = 'Installing dependencies...';
266
+
267
+ // Install dependencies
268
+ spinner.text = 'Installing dependencies...';
270
269
  const dependencies = [
271
270
  'clsx',
272
271
  'tailwind-merge',
273
- 'class-variance-authority',
274
- 'tailwindcss',
272
+ 'class-variance-authority',
273
+ 'tailwindcss',
275
274
  'postcss',
276
275
  '@tailwindcss/postcss'
277
276
  ];
278
277
  await installPackages(dependencies, { cwd });
279
-
280
- // Setup PostCSS - create .postcssrc.json which is the preferred format for Angular
281
- spinner.text = 'Configuring PostCSS...';
282
- const postcssrcPath = path.join(cwd, '.postcssrc.json');
283
-
284
- if (!await fs.pathExists(postcssrcPath)) {
285
- const configContent = {
286
- plugins: {
287
- '@tailwindcss/postcss': {}
288
- }
289
- };
290
- await fs.writeJson(postcssrcPath, configContent, { spaces: 4 });
291
- }
292
-
293
-
294
- spinner.succeed(chalk.green('Project initialized successfully!'));
295
-
296
- console.log('\n' + chalk.bold('Next steps:'));
297
- console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx @gilav21/shadcn-angular add button'));
298
- console.log(chalk.dim(' 2. Import and use in your templates'));
299
- console.log(chalk.dim(' 3. Update your ') + chalk.bold('tsconfig.json') + chalk.dim(' paths:'));
300
- console.log(chalk.dim(' "compilerOptions": {'));
301
- console.log(chalk.dim(' "baseUrl": ".",'));
302
- console.log(chalk.dim(' "paths": {'));
303
- console.log(chalk.dim(' "@/*": ["./src/*"]'));
304
- console.log(chalk.dim(' }'));
305
- console.log(chalk.dim(' }'));
306
- console.log('');
307
-
308
- } catch (error) {
309
- spinner.fail('Failed to initialize project');
310
- console.error(error);
311
- process.exit(1);
312
- }
313
- }
278
+
279
+ // Setup PostCSS - create .postcssrc.json which is the preferred format for Angular
280
+ spinner.text = 'Configuring PostCSS...';
281
+ const postcssrcPath = path.join(cwd, '.postcssrc.json');
282
+
283
+ if (!await fs.pathExists(postcssrcPath)) {
284
+ const configContent = {
285
+ plugins: {
286
+ '@tailwindcss/postcss': {}
287
+ }
288
+ };
289
+ await fs.writeJson(postcssrcPath, configContent, { spaces: 4 });
290
+ }
291
+
292
+
293
+ spinner.succeed(chalk.green('Project initialized successfully!'));
294
+
295
+ console.log('\n' + chalk.bold('Next steps:'));
296
+ console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx @gilav21/shadcn-angular add button'));
297
+ console.log(chalk.dim(' 2. Import and use in your templates'));
298
+ console.log(chalk.dim(' 3. Update your ') + chalk.bold('tsconfig.json') + chalk.dim(' paths:'));
299
+ console.log(chalk.dim(' "compilerOptions": {'));
300
+ console.log(chalk.dim(' "baseUrl": ".",'));
301
+ console.log(chalk.dim(' "paths": {'));
302
+ console.log(chalk.dim(' "@/*": ["./src/*"]'));
303
+ console.log(chalk.dim(' }'));
304
+ console.log(chalk.dim(' }'));
305
+ console.log('');
306
+
307
+ } catch (error) {
308
+ spinner.fail('Failed to initialize project');
309
+ console.error(error);
310
+ process.exit(1);
311
+ }
312
+ }
@@ -6,6 +6,7 @@ export interface ComponentDefinition {
6
6
  files: string[]; // Relative paths to component files
7
7
  dependencies?: string[]; // Other components this depends on
8
8
  npmDependencies?: string[]; // NPM packages this depends on
9
+ libFiles?: string[]; // Lib utility files this component requires (e.g. 'xlsx.ts')
9
10
  shortcutDefinitions?: {
10
11
  exportName: string;
11
12
  componentName: string;
@@ -155,6 +156,7 @@ export const registry: Record<string, ComponentDefinition> = {
155
156
  'component-outlet',
156
157
  'icon',
157
158
  ],
159
+ libFiles: ['xlsx.ts'],
158
160
  },
159
161
  dialog: {
160
162
  name: 'dialog',
@@ -392,6 +394,7 @@ export const registry: Record<string, ComponentDefinition> = {
392
394
  'dialog',
393
395
  'scroll-area',
394
396
  ],
397
+ libFiles: ['pdf-parser.ts', 'image-validator.ts', 'svg-sanitizer.ts'],
395
398
  shortcutDefinitions: [
396
399
  {
397
400
  exportName: 'RICH_TEXT_SHORTCUT_DEFINITIONS',
@@ -6,7 +6,7 @@ import { twMerge } from 'tailwind-merge';
6
6
  * Utility function for merging Tailwind CSS classes with proper precedence
7
7
  */
8
8
  export function cn(...inputs: ClassValue[]): string {
9
- return twMerge(clsx(inputs));
9
+ return twMerge(clsx(inputs));
10
10
  }
11
11
 
12
12
  /**
@@ -17,5 +17,35 @@ export function isRtl(el: HTMLElement): boolean {
17
17
  return getComputedStyle(el).direction === 'rtl';
18
18
  }
19
19
 
20
+ /**
21
+ * Returns the bounding rect of the nearest ancestor that clips overflow
22
+ * (overflow: hidden | auto | scroll | clip on either axis).
23
+ * Falls back to the full viewport rect when no such ancestor exists.
24
+ *
25
+ * Use this instead of \`window.innerWidth/innerHeight\` when calculating
26
+ * popup collision boundaries so that containers like sidebars or
27
+ * fixed-height scroll panes are respected.
28
+ */
29
+ export function getClippingRect(element: HTMLElement): DOMRect {
30
+ let parent = element.parentElement;
31
+ while (parent && parent !== document.documentElement) {
32
+ const style = window.getComputedStyle(parent);
33
+ if (
34
+ /^(hidden|auto|scroll|clip)$/.test(style.overflowX) ||
35
+ /^(hidden|auto|scroll|clip)$/.test(style.overflowY)
36
+ ) {
37
+ return parent.getBoundingClientRect();
38
+ }
39
+ parent = parent.parentElement;
40
+ }
41
+ return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
42
+ }
43
+
44
+ /**
45
+ * Check if the user prefers reduced motion via the OS-level accessibility setting.
46
+ */
47
+ export function prefersReducedMotion(): boolean {
48
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
49
+ }
20
50
  `;
21
51
  }
@@ -29,7 +29,7 @@ export function getDefaultConfig(): Config {
29
29
  },
30
30
  aliases: {
31
31
  components: '@/components',
32
- utils: '@/components/lib/utils',
32
+ utils: '@/components/lib',
33
33
  ui: '@/components/ui',
34
34
  },
35
35
  };
@@ -24,9 +24,8 @@ function resolveProjectPath(cwd: string, inputPath: string): string {
24
24
  }
25
25
 
26
26
  function getShortcutRegistryIndexPath(cwd: string, config: Config): string {
27
- const utilsFilePath = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils) + '.ts');
28
- const utilsDir = path.dirname(utilsFilePath);
29
- return path.join(utilsDir, 'shortcut-registry.index.ts');
27
+ const libDir = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils));
28
+ return path.join(libDir, 'shortcut-registry.index.ts');
30
29
  }
31
30
 
32
31
  export async function writeShortcutRegistryIndex(
@@ -41,10 +40,7 @@ export async function writeShortcutRegistryIndex(
41
40
  .sort((a, b) => a.exportName.localeCompare(b.exportName));
42
41
 
43
42
  const uiAlias = config.aliases.ui;
44
- const utilsAliasDir = config.aliases.utils.includes('/')
45
- ? config.aliases.utils.slice(0, config.aliases.utils.lastIndexOf('/'))
46
- : config.aliases.utils;
47
- const shortcutServiceImport = `${utilsAliasDir}/shortcut-binding.service`;
43
+ const shortcutServiceImport = `${config.aliases.utils}/shortcut-binding.service`;
48
44
 
49
45
  const imports = uniqueEntries
50
46
  .map(entry => {