@idealyst/components 1.1.8 → 1.1.9

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.
Files changed (46) hide show
  1. package/package.json +3 -3
  2. package/plugin/web.js +280 -532
  3. package/src/Accordion/Accordion.web.tsx +1 -3
  4. package/src/Alert/Alert.web.tsx +3 -4
  5. package/src/Badge/Badge.web.tsx +8 -15
  6. package/src/Breadcrumb/Breadcrumb.web.tsx +4 -8
  7. package/src/Button/Button.native.tsx +14 -21
  8. package/src/Button/Button.styles.tsx +15 -0
  9. package/src/Button/Button.web.tsx +9 -19
  10. package/src/Checkbox/Checkbox.web.tsx +1 -2
  11. package/src/Chip/Chip.web.tsx +3 -5
  12. package/src/Dialog/Dialog.web.tsx +3 -3
  13. package/src/Dialog/types.ts +1 -1
  14. package/src/Icon/Icon.web.tsx +22 -17
  15. package/src/Icon/IconRegistry.native.ts +41 -0
  16. package/src/Icon/IconRegistry.ts +107 -0
  17. package/src/Icon/IconSvg/IconSvg.web.tsx +26 -5
  18. package/src/Icon/icon-resolver.ts +12 -43
  19. package/src/Icon/index.native.ts +2 -1
  20. package/src/Icon/index.ts +1 -0
  21. package/src/Icon/index.web.ts +1 -0
  22. package/src/Input/Input.styles.tsx +56 -83
  23. package/src/Input/Input.web.tsx +5 -8
  24. package/src/List/ListItem.native.tsx +6 -7
  25. package/src/List/ListItem.web.tsx +3 -3
  26. package/src/Menu/MenuItem.web.tsx +3 -5
  27. package/src/Screen/Screen.native.tsx +1 -1
  28. package/src/Screen/Screen.styles.tsx +3 -6
  29. package/src/Screen/Screen.web.tsx +1 -1
  30. package/src/Select/Select.styles.tsx +31 -48
  31. package/src/Select/Select.web.tsx +45 -33
  32. package/src/Slider/Slider.web.tsx +2 -4
  33. package/src/Switch/Switch.native.tsx +2 -2
  34. package/src/Switch/Switch.web.tsx +2 -3
  35. package/src/Table/Table.native.tsx +168 -65
  36. package/src/Table/Table.styles.tsx +26 -33
  37. package/src/Table/Table.web.tsx +169 -70
  38. package/src/Text/Text.web.tsx +1 -0
  39. package/src/TextArea/TextArea.native.tsx +21 -8
  40. package/src/TextArea/TextArea.styles.tsx +15 -27
  41. package/src/TextArea/TextArea.web.tsx +17 -6
  42. package/src/View/View.native.tsx +33 -3
  43. package/src/View/View.web.tsx +4 -21
  44. package/src/View/types.ts +31 -3
  45. package/src/examples/ButtonExamples.tsx +20 -0
  46. package/src/index.ts +1 -1
package/plugin/web.js CHANGED
@@ -1,614 +1,362 @@
1
1
  /**
2
- * Enhanced MDI Auto-Import Babel Plugin v2.0
2
+ * MDI Icon Registry Babel Plugin
3
+ *
4
+ * This plugin scans for icon names used in components and registers them
5
+ * with the IconRegistry at build time. Icons are looked up from the registry
6
+ * at runtime.
3
7
  *
4
8
  * Features:
5
- * 1. Context-aware string replacement - only transforms strings used with Icon component
6
- * 2. Namespace prefix support - "mdi:iconname" guarantees transformation
7
- * 3. Variable tracking with scope analysis - follows variables back to their declarations
8
- * 4. No false positives - only transforms Icon-related strings
9
- * 5. Button/Badge/Breadcrumb/Menu icon prop transformation with path injection
9
+ * 1. Static analysis - scans JSX for icon names
10
+ * 2. Config icons - force-include icons via babel config
11
+ * 3. Registry population - generates code to register discovered icons
12
+ * 4. Validates against actual @mdi/js exports
13
+ *
14
+ * Config options:
15
+ * - debug: boolean - enable debug logging
16
+ * - icons: string[] - array of icon names to always include
10
17
  */
11
18
 
19
+ // Load @mdi/js exports for validation
20
+ let mdiExports = null;
21
+ try {
22
+ mdiExports = require('@mdi/js');
23
+ } catch (e) {
24
+ console.warn('[mdi-registry-plugin] Could not load @mdi/js for validation. Icons will not be validated.');
25
+ }
26
+
12
27
  module.exports = function ({ types: t }, options = {}) {
13
28
  const debug = options.debug || false;
14
- const manifestPath = options.manifestPath || './icons.manifest.json';
29
+ const configIcons = options.icons || [];
15
30
 
16
- // Debug logging function that only logs when debug is enabled
17
31
  const debugLog = (...args) => {
18
32
  if (debug) {
19
- console.log(...args);
33
+ console.log('[mdi-registry-plugin]', ...args);
20
34
  }
21
35
  };
22
36
 
23
- debugLog('[mdi-auto-import-enhanced] Plugin loaded');
24
-
25
- const importedIcons = new Set();
26
- const iconImportIdentifiers = new Map();
27
- let hasIconImport = false;
28
- let manifestIcons = new Set();
29
-
30
- // Track variables that are used with Icon component
31
- const iconRelatedVariables = new Set();
32
-
33
- // Load icon manifest if it exists
34
- function loadIconManifest() {
35
- try {
36
- const fs = require('fs');
37
- const path = require('path');
38
-
39
- const fullPath = path.resolve(process.cwd(), manifestPath);
40
-
41
- if (fs.existsSync(fullPath)) {
42
- const manifestContent = fs.readFileSync(fullPath, 'utf8');
43
- const manifest = JSON.parse(manifestContent);
44
-
45
- if (manifest.icons && Array.isArray(manifest.icons)) {
46
- manifest.icons.forEach(iconName => {
47
- if (typeof iconName === 'string') {
48
- manifestIcons.add(iconName);
49
- }
50
- });
51
- debugLog(`[mdi-auto-import-enhanced] Loaded ${manifestIcons.size} icons from manifest: ${fullPath}`);
52
- } else {
53
- console.warn(`[mdi-auto-import-enhanced] Invalid manifest format in ${fullPath}. Expected { "icons": ["icon-name", ...] }`);
54
- }
55
- }
56
- } catch (error) {
57
- console.warn(`[mdi-auto-import-enhanced] Error loading manifest from ${manifestPath}: ${error.message}`);
58
- }
59
- }
37
+ debugLog('Plugin loaded with config icons:', configIcons);
38
+ debugLog('@mdi/js loaded:', mdiExports ? 'yes' : 'no');
60
39
 
61
- function formatIconName(name) {
40
+ /**
41
+ * Convert kebab-case icon name to MDI import name
42
+ * e.g., "home" -> "mdiHome", "account-circle" -> "mdiAccountCircle"
43
+ */
44
+ function toMdiImportName(name) {
62
45
  if (!name || typeof name !== 'string') {
63
- throw new Error(`Invalid icon name: ${name}`);
46
+ return null;
64
47
  }
65
48
 
66
- // Strip mdi: prefix if it exists (safety check, should already be stripped)
67
- const cleanName = name.startsWith('mdi:') ? name.substring(4) : name;
49
+ // Strip mdi: prefix if present
50
+ const cleanName = name.startsWith('mdi:') ? name.slice(4) : name;
68
51
 
69
- // Check if the name contains only valid characters (letters, numbers, hyphens, underscores)
70
- if (!/^[a-zA-Z0-9-_]+$/.test(cleanName)) {
71
- debugLog(`[mdi-auto-import-enhanced] formatIconName: Invalid icon name "${name}" (contains special characters), using "help-circle" as fallback`);
72
- return 'HelpCircle';
52
+ // Validate: only allow alphanumeric and hyphens
53
+ if (!/^[a-zA-Z0-9-]+$/.test(cleanName)) {
54
+ debugLog(`Invalid icon name format: "${name}"`);
55
+ return null;
73
56
  }
74
57
 
75
- const formatted = cleanName
76
- .replace(/[-_:]/g, ' ') // Also handle colons
77
- .replace(/([a-z])([A-Z])/g, '$1 $2')
78
- .split(' ')
79
- .filter(part => part.length > 0)
58
+ // Convert kebab-case to PascalCase and prefix with mdi
59
+ const pascalCase = cleanName
60
+ .split('-')
80
61
  .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
81
62
  .join('');
82
63
 
83
- debugLog(`[mdi-auto-import-enhanced] formatIconName: ${name} -> ${formatted}`);
84
- return formatted;
85
- }
86
-
87
- function getMdiIconName(name) {
88
- // First ensure the name has mdi: prefix stripped (safety check)
89
- const cleanName = name.startsWith('mdi:') ? name.substring(4) : name;
90
- const mdiName = `mdi${formatIconName(cleanName)}`;
91
- debugLog(`[mdi-auto-import-enhanced] getMdiIconName: ${name} -> ${mdiName}`);
92
- return mdiName;
93
- }
64
+ const mdiName = `mdi${pascalCase}`;
94
65
 
95
- function getIconIdentifier(iconName) {
96
- if (!iconImportIdentifiers.has(iconName)) {
97
- // Sanitize the icon name to create a valid JavaScript identifier
98
- // Remove any characters that aren't valid in identifiers
99
- const sanitized = iconName.replace(/[^a-zA-Z0-9_$]/g, '');
100
- iconImportIdentifiers.set(iconName, `_${sanitized}`);
66
+ // Validate against actual @mdi/js exports
67
+ if (mdiExports && !(mdiName in mdiExports)) {
68
+ debugLog(`Icon "${name}" (${mdiName}) not found in @mdi/js`);
69
+ return null;
101
70
  }
102
- const identifier = iconImportIdentifiers.get(iconName);
103
- debugLog(`[mdi-auto-import-enhanced] getIconIdentifier: ${iconName} -> ${identifier}`);
104
- return identifier;
71
+
72
+ return mdiName;
105
73
  }
106
74
 
107
75
  /**
108
- * Extract icon name from string, handling namespace prefix
109
- * Returns null if not a valid icon string
76
+ * Normalize icon name for registry key (lowercase, no prefix)
77
+ * Returns null for invalid icon names
110
78
  */
111
- function extractIconName(str) {
112
- if (!str || typeof str !== 'string') return null;
79
+ function normalizeIconName(name) {
80
+ if (!name || typeof name !== 'string') return null;
81
+ const clean = name.startsWith('mdi:') ? name.slice(4) : name;
82
+ const normalized = clean.toLowerCase();
83
+
84
+ // Only allow alphanumeric and hyphens
85
+ if (!/^[a-zA-Z0-9-]+$/.test(normalized)) {
86
+ return null;
87
+ }
113
88
 
114
- // Handle namespace prefix: "mdi:home" -> "home"
115
- if (str.startsWith('mdi:')) {
116
- return str.substring(4);
89
+ // Validate by checking if toMdiImportName succeeds
90
+ const mdiName = toMdiImportName(normalized);
91
+ if (!mdiName) {
92
+ return null;
117
93
  }
118
94
 
119
- return str;
95
+ return normalized;
120
96
  }
121
97
 
122
98
  /**
123
- * Check if a string literal is icon-related based on context
99
+ * Extract icon name from a string value
124
100
  */
125
- function isIconRelatedString(path, stringValue) {
126
- // Always transform if it has the mdi: prefix
127
- if (stringValue.startsWith('mdi:')) {
128
- debugLog(`[mdi-auto-import-enhanced] String "${stringValue}" has mdi: prefix - will transform`);
129
- return true;
130
- }
131
-
132
- // Check if this string is in the manifest
133
- if (manifestIcons.has(stringValue)) {
134
- debugLog(`[mdi-auto-import-enhanced] String "${stringValue}" is in manifest - will transform`);
135
- return true;
136
- }
101
+ function extractIconName(str) {
102
+ if (!str || typeof str !== 'string') return null;
103
+ // Strip mdi: prefix if present
104
+ return str.startsWith('mdi:') ? str.slice(4) : str;
105
+ }
137
106
 
138
- // Icon prop names to check
139
- const iconPropNames = ['name', 'leftIcon', 'rightIcon', 'icon'];
140
-
141
- // Walk up the tree to find context
142
- let currentPath = path;
143
- while (currentPath) {
144
- const node = currentPath.node;
145
-
146
- // Check if we're in a JSXAttribute with an icon-related prop name
147
- if (t.isJSXAttribute(currentPath.parent)) {
148
- const attr = currentPath.parent;
149
- if (t.isJSXIdentifier(attr.name) && iconPropNames.includes(attr.name.name)) {
150
- // Now check if the parent JSXOpeningElement is Icon, Button, Badge, etc.
151
- const openingElement = currentPath.parentPath.parent;
152
- if (t.isJSXOpeningElement(openingElement)) {
153
- if (t.isJSXIdentifier(openingElement.name)) {
154
- const componentName = openingElement.name.name;
155
- debugLog(`[mdi-auto-import-enhanced] String "${stringValue}" is in ${componentName}.${attr.name.name} prop - will transform`);
156
- return true;
107
+ return {
108
+ name: 'mdi-registry-plugin',
109
+ visitor: {
110
+ Program: {
111
+ enter(path, state) {
112
+ // Initialize state for this file
113
+ state.iconNames = new Set();
114
+ state.hasIconUsage = false;
115
+
116
+ debugLog('Processing file:', state.filename || 'unknown');
117
+
118
+ // Add config icons
119
+ configIcons.forEach(iconName => {
120
+ const normalized = normalizeIconName(iconName);
121
+ if (normalized) {
122
+ state.iconNames.add(normalized);
123
+ debugLog(`Added config icon: ${normalized}`);
157
124
  }
158
- }
159
- }
160
- }
125
+ });
126
+ },
161
127
 
162
- // Check if we're assigned to a variable that's used with Icon
163
- if (t.isVariableDeclarator(currentPath.parent)) {
164
- const declarator = currentPath.parent;
165
- if (t.isIdentifier(declarator.id)) {
166
- const varName = declarator.id.name;
167
- if (iconRelatedVariables.has(varName)) {
168
- debugLog(`[mdi-auto-import-enhanced] String "${stringValue}" is in icon-related variable "${varName}" - will transform`);
169
- return true;
128
+ exit(path, state) {
129
+ // If no icons found, nothing to do
130
+ if (state.iconNames.size === 0) {
131
+ debugLog('No icons found, skipping registration');
132
+ return;
170
133
  }
171
- }
172
- }
173
-
174
- currentPath = currentPath.parentPath;
175
- }
176
134
 
177
- return false;
178
- }
135
+ debugLog(`Found ${state.iconNames.size} icons:`, Array.from(state.iconNames));
179
136
 
180
- /**
181
- * Track variables that are used with Icon component
182
- * This runs in a first pass to identify which variables are icon-related
183
- */
184
- function trackIconRelatedVariables(programPath) {
185
- // Components that accept icon props
186
- const componentsWithIconProps = {
187
- 'Icon': ['name'],
188
- 'Button': ['leftIcon', 'rightIcon'],
189
- 'Badge': ['icon'],
190
- 'Breadcrumb': ['icon'],
191
- 'Menu': ['icon']
192
- };
193
-
194
- programPath.traverse({
195
- JSXElement(path) {
196
- const { node } = path;
137
+ // Build the import specifiers for @mdi/js
138
+ const iconImports = [];
139
+ const registryEntries = [];
197
140
 
198
- if (t.isJSXIdentifier(node.openingElement.name)) {
199
- const componentName = node.openingElement.name.name;
200
- const iconProps = componentsWithIconProps[componentName];
201
-
202
- if (iconProps) {
203
- // Find all icon-related attributes
204
- iconProps.forEach(propName => {
205
- const attr = node.openingElement.attributes.find(attr =>
206
- t.isJSXAttribute(attr) &&
207
- t.isJSXIdentifier(attr.name) &&
208
- attr.name.name === propName
141
+ state.iconNames.forEach(iconName => {
142
+ const mdiName = toMdiImportName(iconName);
143
+ if (mdiName) {
144
+ // Create unique local identifier
145
+ const localId = `_${mdiName}`;
146
+ iconImports.push(
147
+ t.importSpecifier(t.identifier(localId), t.identifier(mdiName))
209
148
  );
149
+ // Create registry entry: 'icon-name': _mdiIconName
150
+ registryEntries.push(
151
+ t.objectProperty(
152
+ t.stringLiteral(iconName),
153
+ t.identifier(localId)
154
+ )
155
+ );
156
+ }
157
+ });
210
158
 
211
- if (attr && t.isJSXExpressionContainer(attr.value)) {
212
- const expression = attr.value.expression;
213
-
214
- // Track any identifiers used in the icon prop
215
- if (t.isIdentifier(expression)) {
216
- iconRelatedVariables.add(expression.name);
217
- debugLog(`[mdi-auto-import-enhanced] Tracked icon-related variable: ${expression.name} (from ${componentName}.${propName})`);
218
-
219
- // Follow the binding to find its declaration
220
- const binding = path.scope.getBinding(expression.name);
221
- if (binding && binding.path.isVariableDeclarator()) {
222
- const init = binding.path.node.init;
223
- if (t.isStringLiteral(init)) {
224
- debugLog(`[mdi-auto-import-enhanced] Variable ${expression.name} = "${init.value}"`);
225
- }
226
- }
227
- }
228
-
229
- // Track variables in ternaries and logical expressions
230
- path.traverse({
231
- Identifier(idPath) {
232
- // Only track top-level identifiers, not property accesses
233
- if (!t.isMemberExpression(idPath.parent)) {
234
- iconRelatedVariables.add(idPath.node.name);
235
- debugLog(`[mdi-auto-import-enhanced] Tracked icon-related variable in expression: ${idPath.node.name} (from ${componentName}.${propName})`);
236
- }
237
- }
238
- });
239
- }
240
- });
159
+ if (iconImports.length === 0) {
160
+ debugLog('No valid icons to register');
161
+ return;
241
162
  }
242
- }
243
- }
244
- });
245
- }
246
163
 
247
- /**
248
- * Extract all icon names from an expression, now with context awareness
249
- */
250
- function extractIconNames(expression, path) {
251
- const iconNames = new Set();
252
-
253
- function traverse(node, nodePath) {
254
- if (!node) return;
164
+ // Create: import { mdiHome as _mdiHome, ... } from '@mdi/js';
165
+ const mdiImport = t.importDeclaration(
166
+ iconImports,
167
+ t.stringLiteral('@mdi/js')
168
+ );
255
169
 
256
- if (t.isStringLiteral(node)) {
257
- const iconName = extractIconName(node.value);
258
- if (iconName) {
259
- iconNames.add(iconName);
260
- debugLog(`[mdi-auto-import-enhanced] Found icon name: ${iconName} (from "${node.value}")`);
261
- }
262
- }
263
- else if (t.isConditionalExpression(node)) {
264
- debugLog('[mdi-auto-import-enhanced] Processing conditional expression');
265
- traverse(node.consequent, nodePath);
266
- traverse(node.alternate, nodePath);
267
- }
268
- else if (t.isLogicalExpression(node)) {
269
- debugLog('[mdi-auto-import-enhanced] Processing logical expression');
270
- traverse(node.left, nodePath);
271
- traverse(node.right, nodePath);
272
- }
273
- else if (t.isTemplateLiteral(node)) {
274
- if (node.expressions.length === 0 && node.quasis.length === 1) {
275
- const value = node.quasis[0].value.cooked;
276
- const iconName = extractIconName(value);
277
- if (iconName) {
278
- iconNames.add(iconName);
279
- debugLog(`[mdi-auto-import-enhanced] Found icon name in template literal: ${iconName}`);
280
- }
281
- }
282
- }
283
- else if (t.isIdentifier(node)) {
284
- debugLog(`[mdi-auto-import-enhanced] Following identifier: ${node.name}`);
285
-
286
- // Try to resolve the identifier to its value
287
- const binding = path.scope.getBinding(node.name);
288
- if (binding && binding.path.isVariableDeclarator()) {
289
- const init = binding.path.node.init;
290
- if (init) {
291
- traverse(init, binding.path);
170
+ // Determine the import path for IconRegistry
171
+ // If we're inside the components package, use relative import to avoid circular deps
172
+ const filename = state.filename || '';
173
+ const isInsideComponentsPackage = filename.includes('packages/components/src') ||
174
+ filename.includes('packages\\components\\src');
175
+
176
+ let registryImportPath = '@idealyst/components';
177
+ if (isInsideComponentsPackage) {
178
+ // Calculate relative path to Icon/IconRegistry
179
+ const path = require('path');
180
+ const fileDir = path.dirname(filename);
181
+ const registryPath = path.join(filename.split('packages/components/src')[0] || filename.split('packages\\components\\src')[0], 'packages/components/src/Icon/IconRegistry');
182
+ let relativePath = path.relative(fileDir, registryPath).replace(/\\/g, '/');
183
+ if (!relativePath.startsWith('.')) {
184
+ relativePath = './' + relativePath;
185
+ }
186
+ registryImportPath = relativePath;
187
+ debugLog(`Using relative import for IconRegistry: ${registryImportPath}`);
292
188
  }
293
- }
294
- }
295
- }
296
189
 
297
- traverse(expression, path);
298
- return Array.from(iconNames);
299
- }
190
+ // Create: import { IconRegistry } from '...' ;
191
+ const registryImport = t.importDeclaration(
192
+ [t.importSpecifier(t.identifier('IconRegistry'), t.identifier('IconRegistry'))],
193
+ t.stringLiteral(registryImportPath)
194
+ );
300
195
 
301
- return {
302
- name: 'mdi-auto-import-enhanced',
303
- visitor: {
304
- Program: {
305
- enter(path) {
306
- // Reset state for each file
307
- importedIcons.clear();
308
- iconImportIdentifiers.clear();
309
- hasIconImport = false;
310
- manifestIcons.clear();
311
- iconRelatedVariables.clear();
312
-
313
- // Load icon manifest
314
- loadIconManifest();
315
-
316
- // Add all manifest icons to the import list
317
- manifestIcons.forEach(iconName => {
318
- try {
319
- const mdiIconName = getMdiIconName(iconName);
320
- importedIcons.add(mdiIconName);
321
- debugLog(`[mdi-auto-import-enhanced] Added manifest icon to import list: ${mdiIconName}`);
322
- } catch (error) {
323
- console.error(`[mdi-auto-import-enhanced] Error processing manifest icon "${iconName}": ${error.message}`);
196
+ // Create: IconRegistry.registerMany({ 'home': _mdiHome, ... });
197
+ const registerCall = t.expressionStatement(
198
+ t.callExpression(
199
+ t.memberExpression(
200
+ t.identifier('IconRegistry'),
201
+ t.identifier('registerMany')
202
+ ),
203
+ [t.objectExpression(registryEntries)]
204
+ )
205
+ );
206
+
207
+ // Insert at the top of the file (after existing imports)
208
+ // Find the last import declaration
209
+ let lastImportIndex = -1;
210
+ path.node.body.forEach((node, index) => {
211
+ if (t.isImportDeclaration(node)) {
212
+ lastImportIndex = index;
324
213
  }
325
214
  });
326
215
 
327
- // First pass: track which variables are icon-related
328
- trackIconRelatedVariables(path);
216
+ // Insert after last import, or at the beginning if no imports
217
+ const insertIndex = lastImportIndex + 1;
329
218
 
330
- // Check if Icon is already imported from @mdi/react
331
- path.node.body.forEach(node => {
332
- if (t.isImportDeclaration(node) && node.source.value === '@mdi/react') {
333
- debugLog('[mdi-auto-import-enhanced] Found @mdi/react import');
334
- const hasIconSpecifier = node.specifiers.some(spec =>
335
- t.isImportDefaultSpecifier(spec) && spec.local.name === 'MdiIcon'
336
- );
337
- if (hasIconSpecifier) {
338
- debugLog('[mdi-auto-import-enhanced] MdiIcon already imported');
339
- hasIconImport = true;
340
- }
341
- }
342
- });
343
- },
344
- exit(path) {
345
- if (importedIcons.size === 0) {
346
- return;
347
- }
348
- debugLog(`[mdi-auto-import-enhanced] importedIcons.size: ${importedIcons.size}`);
349
-
350
- // Add imports at the top of the file if any icons were used
351
- if (importedIcons.size > 0) {
352
- debugLog('[mdi-auto-import-enhanced] Adding imports for icons:', Array.from(importedIcons));
353
-
354
- // Import individual icons from @mdi/js
355
- const iconImportSpecifiers = Array.from(importedIcons).map(iconName => {
356
- const identifier = getIconIdentifier(iconName);
357
- return t.importSpecifier(
358
- t.identifier(identifier),
359
- t.identifier(iconName)
360
- );
361
- });
362
-
363
- const iconImportDeclaration = t.importDeclaration(
364
- iconImportSpecifiers,
365
- t.stringLiteral('@mdi/js')
366
- );
367
-
368
- // Import Icon component from @mdi/react if not already imported
369
- if (!hasIconImport) {
370
- debugLog('[mdi-auto-import-enhanced] Adding MdiIcon import from @mdi/react');
371
- const iconComponentImport = t.importDeclaration(
372
- [t.importDefaultSpecifier(t.identifier('MdiIcon'))],
373
- t.stringLiteral('@mdi/react')
374
- );
375
- path.unshiftContainer('body', iconComponentImport);
376
- } else {
377
- debugLog('[mdi-auto-import-enhanced] MdiIcon already imported, skipping');
378
- }
219
+ // Insert in reverse order so they end up in correct order
220
+ path.node.body.splice(insertIndex, 0, mdiImport);
221
+ path.node.body.splice(insertIndex, 0, registryImport);
222
+ path.node.body.splice(insertIndex + 2, 0, registerCall);
379
223
 
380
- // Add icon imports
381
- path.unshiftContainer('body', iconImportDeclaration);
382
- debugLog('[mdi-auto-import-enhanced] Imports added successfully');
383
- } else {
384
- debugLog('[mdi-auto-import-enhanced] No icons to import');
385
- }
224
+ debugLog('Injected registration code');
386
225
  }
387
226
  },
388
227
 
389
- JSXElement(path) {
228
+ // Track icon names in JSX elements
229
+ JSXElement(path, state) {
390
230
  const { node } = path;
391
231
 
392
- // Handle Badge, Button, Breadcrumb, Menu components with icon props
393
- if (t.isJSXIdentifier(node.openingElement.name)) {
394
- const componentName = node.openingElement.name.name;
395
- const iconPropMapping = {
396
- 'Button': { props: ['leftIcon', 'rightIcon'], pathProps: ['leftIconPath', 'rightIconPath'] },
397
- 'Badge': { props: ['icon'], pathProps: ['iconPath'] },
398
- 'Breadcrumb': { props: ['icon'], pathProps: ['iconPath'] },
399
- 'Menu': { props: ['icon'], pathProps: ['iconPath'] }
400
- };
401
-
402
- const iconConfig = iconPropMapping[componentName];
403
- if (iconConfig) {
404
- debugLog(`[mdi-auto-import-enhanced] JSXElement visitor - Found ${componentName}`);
405
-
406
- // Process each icon prop
407
- iconConfig.props.forEach((propName, index) => {
408
- const attr = node.openingElement.attributes.find(attr =>
409
- t.isJSXAttribute(attr) &&
410
- t.isJSXIdentifier(attr.name) &&
411
- attr.name.name === propName
412
- );
413
-
414
- if (attr && t.isStringLiteral(attr.value)) {
415
- const stringValue = attr.value.value;
416
- const iconName = extractIconName(stringValue);
417
-
418
- if (iconName) {
419
- debugLog(`[mdi-auto-import-enhanced] - Found ${propName}="${stringValue}" (StringLiteral)`);
420
-
421
- try {
422
- const mdiIconName = getMdiIconName(iconName);
423
- const iconIdentifier = getIconIdentifier(mdiIconName);
424
- importedIcons.add(mdiIconName);
425
- debugLog(`[mdi-auto-import-enhanced] - Added icon from ${componentName}.${propName}: ${mdiIconName}`);
426
-
427
- // Add the corresponding path prop (e.g., leftIconPath, rightIconPath, iconPath)
428
- const pathPropName = iconConfig.pathProps[index];
429
- const pathAttr = t.jsxAttribute(
430
- t.jsxIdentifier(pathPropName),
431
- t.jsxExpressionContainer(t.identifier(iconIdentifier))
432
- );
433
-
434
- // Add the path attribute to the component
435
- node.openingElement.attributes.push(pathAttr);
436
- debugLog(`[mdi-auto-import-enhanced] - Added ${pathPropName}={${iconIdentifier}} to ${componentName}`);
437
- } catch (error) {
438
- console.error(`[mdi-auto-import-enhanced] Error processing icon "${iconName}" from ${componentName}.${propName}: ${error.message}`);
439
- }
440
- }
441
- } else if (attr) {
442
- const attrType = attr.value?.type || 'unknown';
443
- debugLog(`[mdi-auto-import-enhanced] - Found ${propName} (${attrType})`);
444
- }
445
- });
446
- }
232
+ if (!t.isJSXIdentifier(node.openingElement.name)) {
233
+ return;
447
234
  }
448
235
 
449
- // Check if this is an Icon component from @idealyst/components
450
- if (
451
- t.isJSXIdentifier(node.openingElement.name) &&
452
- node.openingElement.name.name === 'Icon'
453
- ) {
454
-
455
- // Find the name attribute
456
- const nameAttr = node.openingElement.attributes.find(attr =>
236
+ const componentName = node.openingElement.name.name;
237
+
238
+ // Map of components to their icon prop names
239
+ const iconPropMap = {
240
+ 'Icon': ['name'],
241
+ 'IconSvg': ['name'], // Internal component also uses name prop now
242
+ 'Button': ['leftIcon', 'rightIcon'],
243
+ 'Badge': ['icon'],
244
+ 'Breadcrumb': ['icon'],
245
+ 'Menu': ['icon'],
246
+ 'MenuItem': ['icon'],
247
+ 'ListItem': ['leading', 'trailing'],
248
+ 'Alert': ['icon'],
249
+ 'Chip': ['icon', 'deleteIcon'],
250
+ 'Input': ['leftIcon', 'rightIcon'],
251
+ };
252
+
253
+ const iconProps = iconPropMap[componentName];
254
+ if (!iconProps) return;
255
+
256
+ state.hasIconUsage = true;
257
+
258
+ // Check each icon prop
259
+ iconProps.forEach(propName => {
260
+ const attr = node.openingElement.attributes.find(attr =>
457
261
  t.isJSXAttribute(attr) &&
458
262
  t.isJSXIdentifier(attr.name) &&
459
- attr.name.name === 'name'
263
+ attr.name.name === propName
460
264
  );
461
265
 
462
- if (!nameAttr) {
463
- debugLog('[mdi-auto-import-enhanced] No name attribute found');
464
- return;
465
- }
266
+ if (!attr) return;
466
267
 
467
- let iconNames = [];
468
-
469
- // Handle both string literals and JSX expressions
470
- if (nameAttr && t.isStringLiteral(nameAttr.value)) {
471
- const iconName = extractIconName(nameAttr.value.value);
268
+ // Handle string literal: icon="home"
269
+ if (t.isStringLiteral(attr.value)) {
270
+ const iconName = extractIconName(attr.value.value);
472
271
  if (iconName) {
473
- iconNames = [iconName];
474
- debugLog(`[mdi-auto-import-enhanced] Found direct string literal: ${iconName}`);
475
- }
476
- } else if (nameAttr && t.isJSXExpressionContainer(nameAttr.value)) {
477
- // Handle JSX expressions with enhanced detection
478
- const expression = nameAttr.value.expression;
479
- iconNames = extractIconNames(expression, path);
480
-
481
- if (iconNames.length === 0) {
482
- debugLog(`[mdi-auto-import-enhanced] Cannot determine icon name for dynamic expression`);
483
- return;
272
+ const normalized = normalizeIconName(iconName);
273
+ if (normalized) {
274
+ state.iconNames.add(normalized);
275
+ debugLog(`Found icon in ${componentName}.${propName}: ${normalized}`);
276
+ }
484
277
  }
485
278
  }
486
-
487
- if (iconNames.length > 0) {
488
- debugLog(`[mdi-auto-import-enhanced] Processing icons: ${iconNames.join(', ')}`);
489
-
490
- // Process each icon name found
491
- const processedIcons = [];
492
- iconNames.forEach(iconName => {
493
- try {
494
- const mdiIconName = getMdiIconName(iconName);
495
- const iconIdentifier = getIconIdentifier(mdiIconName);
496
-
497
- // Track that we need to import this icon
498
- importedIcons.add(mdiIconName);
499
- processedIcons.push({ iconName, mdiIconName, iconIdentifier });
500
- debugLog(`[mdi-auto-import-enhanced] Added icon to import list: ${mdiIconName}`);
501
- } catch (error) {
502
- console.error(`[mdi-auto-import-enhanced] Error processing icon "${iconName}": ${error.message}`);
279
+ // Handle JSX expression: icon={"home"} or icon={variable}
280
+ else if (t.isJSXExpressionContainer(attr.value)) {
281
+ const expr = attr.value.expression;
282
+
283
+ if (t.isStringLiteral(expr)) {
284
+ const iconName = extractIconName(expr.value);
285
+ if (iconName) {
286
+ const normalized = normalizeIconName(iconName);
287
+ if (normalized) {
288
+ state.iconNames.add(normalized);
289
+ debugLog(`Found icon in ${componentName}.${propName}: ${normalized}`);
290
+ }
291
+ }
292
+ }
293
+ // Handle ternary: condition ? "icon1" : "icon2"
294
+ else if (t.isConditionalExpression(expr)) {
295
+ [expr.consequent, expr.alternate].forEach(branch => {
296
+ if (t.isStringLiteral(branch)) {
297
+ const iconName = extractIconName(branch.value);
298
+ if (iconName) {
299
+ const normalized = normalizeIconName(iconName);
300
+ if (normalized) {
301
+ state.iconNames.add(normalized);
302
+ debugLog(`Found icon in ternary: ${normalized}`);
303
+ }
304
+ }
305
+ }
306
+ });
307
+ }
308
+ // Handle logical expression: condition && "icon"
309
+ else if (t.isLogicalExpression(expr)) {
310
+ [expr.left, expr.right].forEach(side => {
311
+ if (t.isStringLiteral(side)) {
312
+ const iconName = extractIconName(side.value);
313
+ if (iconName) {
314
+ const normalized = normalizeIconName(iconName);
315
+ if (normalized) {
316
+ state.iconNames.add(normalized);
317
+ debugLog(`Found icon in logical expr: ${normalized}`);
318
+ }
319
+ }
320
+ }
321
+ });
322
+ }
323
+ // Handle variable reference - try to resolve
324
+ else if (t.isIdentifier(expr)) {
325
+ const binding = path.scope.getBinding(expr.name);
326
+ if (binding && binding.path.isVariableDeclarator()) {
327
+ const init = binding.path.node.init;
328
+ if (t.isStringLiteral(init)) {
329
+ const iconName = extractIconName(init.value);
330
+ if (iconName) {
331
+ const normalized = normalizeIconName(iconName);
332
+ if (normalized) {
333
+ state.iconNames.add(normalized);
334
+ debugLog(`Found icon via variable ${expr.name}: ${normalized}`);
335
+ }
336
+ }
337
+ }
503
338
  }
504
- });
505
-
506
- // If we have exactly one icon, we can transform the component
507
- if (processedIcons.length === 1) {
508
- const { iconIdentifier } = processedIcons[0];
509
-
510
- // Replace name="iconName" with path={iconIdentifier}
511
- const pathAttr = t.jsxAttribute(
512
- t.jsxIdentifier('path'),
513
- t.jsxExpressionContainer(t.identifier(iconIdentifier))
514
- );
515
-
516
- // Remove the name attribute and add the path attribute
517
- node.openingElement.attributes = node.openingElement.attributes
518
- .filter(attr => !(
519
- t.isJSXAttribute(attr) &&
520
- t.isJSXIdentifier(attr.name) &&
521
- attr.name.name === 'name'
522
- ))
523
- .concat(pathAttr);
524
-
525
- debugLog(`[mdi-auto-import-enhanced] Transformed Icon component: name="${processedIcons[0].iconName}" -> path={${iconIdentifier}}`);
526
- } else if (processedIcons.length > 1) {
527
- debugLog(`[mdi-auto-import-enhanced] Found multiple possible icons (${processedIcons.length}), adding imports but not transforming component`);
528
339
  }
529
- } else {
530
- debugLog('[mdi-auto-import-enhanced] No icon names found');
531
340
  }
532
- }
341
+ });
533
342
  },
534
343
 
535
- /**
536
- * Handle JSX attributes with string values for icon props
537
- * This handles: <Badge icon="information" />, <Button leftIcon="plus" />
538
- */
539
- JSXAttribute(path) {
344
+ // Also check for icon names in object properties (for menu items, list items, etc.)
345
+ ObjectProperty(path, state) {
540
346
  const { node } = path;
541
347
 
542
- debugLog(`[mdi-auto-import-enhanced] JSXAttribute visitor called for: ${node.name ? node.name.name || 'unknown' : 'no-name'}`);
543
-
544
- // Check if this is an icon-related attribute
545
- if (!t.isJSXIdentifier(node.name)) {
546
- return;
547
- }
548
-
549
- const attrName = node.name.name;
550
- const iconPropNames = ['name', 'leftIcon', 'rightIcon', 'icon'];
551
-
552
- if (!iconPropNames.includes(attrName)) {
553
- return;
554
- }
555
-
556
- // Check if the value is a string literal
557
- if (!t.isStringLiteral(node.value)) {
558
- return;
559
- }
560
-
561
- const stringValue = node.value.value;
562
- const iconName = extractIconName(stringValue);
563
-
564
- if (!iconName) {
565
- return;
566
- }
567
-
568
- // Check if the parent element is a component that supports icons
569
- const openingElement = path.parentPath.node;
570
- if (!t.isJSXOpeningElement(openingElement) || !t.isJSXIdentifier(openingElement.name)) {
571
- return;
572
- }
573
-
574
- const componentName = openingElement.name.name;
575
- const supportedComponents = ['Icon', 'Button', 'Badge', 'Breadcrumb', 'Menu'];
576
-
577
- if (!supportedComponents.includes(componentName)) {
578
- return;
579
- }
580
-
581
- try {
582
- const mdiIconName = getMdiIconName(iconName);
583
- importedIcons.add(mdiIconName);
584
- debugLog(`[mdi-auto-import-enhanced] Added icon from ${componentName}.${attrName}="${stringValue}": ${mdiIconName}`);
585
- } catch (error) {
586
- console.error(`[mdi-auto-import-enhanced] Error processing icon "${iconName}" from ${componentName}.${attrName}: ${error.message}`);
587
- }
588
- },
348
+ // Check if this is an icon-related property
349
+ const iconPropNames = ['icon', 'leftIcon', 'rightIcon', 'leading', 'trailing', 'deleteIcon'];
589
350
 
590
- /**
591
- * Second pass: Transform string literals that are icon-related
592
- * This handles cases like: const iconName = "home"; <Icon name={iconName} />
593
- */
594
- StringLiteral(path) {
595
- // Skip if we're already in a JSX attribute (handled above)
596
- if (t.isJSXAttribute(path.parent)) {
597
- return;
598
- }
599
-
600
- const stringValue = path.node.value;
601
-
602
- // Check if this string is icon-related based on context
603
- if (isIconRelatedString(path, stringValue)) {
604
- const iconName = extractIconName(stringValue);
605
- if (iconName) {
606
- try {
607
- const mdiIconName = getMdiIconName(iconName);
608
- importedIcons.add(mdiIconName);
609
- debugLog(`[mdi-auto-import-enhanced] Added icon from context-aware string: ${mdiIconName}`);
610
- } catch (error) {
611
- console.error(`[mdi-auto-import-enhanced] Error processing icon string "${iconName}": ${error.message}`);
351
+ if (t.isIdentifier(node.key) && iconPropNames.includes(node.key.name)) {
352
+ if (t.isStringLiteral(node.value)) {
353
+ const iconName = extractIconName(node.value.value);
354
+ if (iconName) {
355
+ const normalized = normalizeIconName(iconName);
356
+ if (normalized) {
357
+ state.iconNames.add(normalized);
358
+ debugLog(`Found icon in object property ${node.key.name}: ${normalized}`);
359
+ }
612
360
  }
613
361
  }
614
362
  }