@idealyst/components 1.0.83 → 1.0.84
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.
- package/CLAUDE.md +199 -232
- package/README.md +5 -5
- package/package.json +20 -2
- package/plugin/README.md +272 -0
- package/plugin/test-cases.jsx +112 -0
- package/plugin/web-legacy.js +320 -0
- package/plugin/web.js +422 -124
- package/src/Accordion/Accordion.native.tsx +182 -0
- package/src/Accordion/Accordion.styles.tsx +260 -0
- package/src/Accordion/Accordion.web.tsx +147 -0
- package/src/Accordion/index.native.tsx +3 -0
- package/src/Accordion/index.ts +3 -0
- package/src/Accordion/index.web.tsx +3 -0
- package/src/Accordion/types.ts +23 -0
- package/src/ActivityIndicator/ActivityIndicator.native.tsx +17 -12
- package/src/ActivityIndicator/ActivityIndicator.styles.tsx +83 -109
- package/src/ActivityIndicator/ActivityIndicator.web.tsx +23 -17
- package/src/ActivityIndicator/index.ts +5 -2
- package/src/ActivityIndicator/index.web.ts +5 -2
- package/src/ActivityIndicator/types.ts +15 -10
- package/src/Alert/Alert.native.tsx +113 -0
- package/src/Alert/Alert.styles.tsx +304 -0
- package/src/Alert/Alert.web.tsx +123 -0
- package/src/Alert/index.native.ts +5 -0
- package/src/Alert/index.ts +5 -0
- package/src/Alert/index.web.ts +5 -0
- package/src/Alert/types.ts +21 -0
- package/src/Avatar/Avatar.native.tsx +8 -6
- package/src/Avatar/Avatar.styles.tsx +64 -58
- package/src/Avatar/Avatar.web.tsx +13 -8
- package/src/Avatar/index.ts +5 -2
- package/src/Avatar/index.web.ts +5 -2
- package/src/Avatar/types.ts +19 -13
- package/src/Badge/Badge.native.tsx +59 -14
- package/src/Badge/Badge.styles.tsx +125 -139
- package/src/Badge/Badge.web.tsx +72 -16
- package/src/Badge/index.ts +5 -2
- package/src/Badge/index.web.ts +5 -2
- package/src/Badge/types.ts +23 -11
- package/src/Breadcrumb/Breadcrumb.native.tsx +225 -0
- package/src/Breadcrumb/Breadcrumb.styles.tsx +234 -0
- package/src/Breadcrumb/Breadcrumb.web.tsx +268 -0
- package/src/Breadcrumb/index.native.ts +5 -0
- package/src/Breadcrumb/index.ts +5 -0
- package/src/Breadcrumb/index.web.ts +5 -0
- package/src/Breadcrumb/types.ts +56 -0
- package/src/Button/Button.native.tsx +75 -24
- package/src/Button/Button.styles.tsx +248 -205
- package/src/Button/Button.web.tsx +82 -25
- package/src/Button/index.ts +5 -5
- package/src/Button/index.web.ts +5 -3
- package/src/Button/types.ts +32 -15
- package/src/Card/Card.native.tsx +14 -11
- package/src/Card/Card.styles.tsx +146 -220
- package/src/Card/Card.web.tsx +20 -21
- package/src/Card/index.ts +5 -5
- package/src/Card/index.web.ts +5 -3
- package/src/Card/types.ts +24 -17
- package/src/Checkbox/Checkbox.native.tsx +24 -34
- package/src/Checkbox/Checkbox.styles.tsx +223 -275
- package/src/Checkbox/Checkbox.web.tsx +30 -37
- package/src/Checkbox/index.ts +5 -5
- package/src/Checkbox/index.web.ts +5 -3
- package/src/Checkbox/types.ts +26 -20
- package/src/Chip/Chip.native.tsx +126 -0
- package/src/Chip/Chip.styles.tsx +138 -0
- package/src/Chip/Chip.web.tsx +154 -0
- package/src/Chip/index.native.ts +5 -0
- package/src/Chip/index.ts +5 -0
- package/src/Chip/index.web.ts +5 -0
- package/src/Chip/types.ts +51 -0
- package/src/Dialog/Dialog.native.tsx +65 -12
- package/src/Dialog/Dialog.styles.tsx +154 -136
- package/src/Dialog/Dialog.web.tsx +16 -11
- package/src/Dialog/index.ts +5 -2
- package/src/Dialog/index.web.ts +5 -2
- package/src/Dialog/types.ts +22 -16
- package/src/Divider/Divider.native.tsx +19 -14
- package/src/Divider/Divider.styles.tsx +273 -595
- package/src/Divider/Divider.web.tsx +19 -12
- package/src/Divider/index.ts +5 -5
- package/src/Divider/index.web.ts +5 -3
- package/src/Divider/types.ts +28 -19
- package/src/Icon/Icon.native.tsx +17 -24
- package/src/Icon/Icon.styles.tsx +64 -48
- package/src/Icon/Icon.web.tsx +14 -11
- package/src/Icon/IconSvg/IconSvg.native.tsx +42 -0
- package/src/Icon/IconSvg/IconSvg.web.tsx +40 -0
- package/src/Icon/IconSvg/index.native.ts +1 -0
- package/src/Icon/IconSvg/index.ts +1 -0
- package/src/Icon/icon-resolver.native.ts +27 -0
- package/src/Icon/icon-resolver.ts +70 -0
- package/src/Icon/index.ts +5 -5
- package/src/Icon/index.web.ts +5 -3
- package/src/Icon/types.ts +17 -11
- package/src/Image/Image.native.tsx +86 -0
- package/src/Image/Image.styles.tsx +57 -0
- package/src/Image/Image.web.tsx +92 -0
- package/src/Image/index.native.ts +5 -0
- package/src/Image/index.ts +5 -0
- package/src/Image/types.ts +21 -0
- package/src/Input/Input.native.tsx +103 -26
- package/src/Input/Input.styles.tsx +240 -177
- package/src/Input/Input.web.tsx +141 -38
- package/src/Input/index.ts +5 -5
- package/src/Input/index.web.ts +5 -3
- package/src/Input/types.ts +43 -20
- package/src/List/List.native.tsx +56 -0
- package/src/List/List.styles.tsx +257 -0
- package/src/List/List.web.tsx +43 -0
- package/src/List/ListContext.tsx +16 -0
- package/src/List/ListItem.native.tsx +111 -0
- package/src/List/ListItem.web.tsx +110 -0
- package/src/List/ListSection.native.tsx +31 -0
- package/src/List/ListSection.web.tsx +33 -0
- package/src/List/index.native.tsx +5 -0
- package/src/List/index.ts +5 -0
- package/src/List/index.web.tsx +5 -0
- package/src/List/types.ts +42 -0
- package/src/Menu/Menu.native.tsx +150 -0
- package/src/Menu/Menu.styles.tsx +185 -0
- package/src/Menu/Menu.web.tsx +99 -0
- package/src/Menu/MenuItem.native.tsx +66 -0
- package/src/Menu/MenuItem.styles.tsx +119 -0
- package/src/Menu/MenuItem.web.tsx +67 -0
- package/src/Menu/index.native.ts +3 -0
- package/src/Menu/index.ts +3 -0
- package/src/Menu/index.web.ts +3 -0
- package/src/Menu/types.ts +30 -0
- package/src/Popover/Popover.native.tsx +102 -32
- package/src/Popover/Popover.styles.tsx +100 -67
- package/src/Popover/Popover.web.tsx +36 -260
- package/src/Popover/index.ts +5 -2
- package/src/Popover/index.web.ts +5 -2
- package/src/Popover/types.ts +14 -13
- package/src/Pressable/Pressable.native.tsx +7 -6
- package/src/Pressable/Pressable.web.tsx +8 -6
- package/src/Pressable/index.ts +5 -2
- package/src/Pressable/index.web.ts +5 -2
- package/src/Pressable/types.ts +11 -10
- package/src/Progress/Progress.native.tsx +179 -0
- package/src/Progress/Progress.styles.tsx +164 -0
- package/src/Progress/Progress.web.tsx +144 -0
- package/src/Progress/index.native.ts +1 -0
- package/src/Progress/index.ts +5 -0
- package/src/Progress/index.web.ts +5 -0
- package/src/Progress/types.ts +21 -0
- package/src/RadioButton/RadioButton.native.tsx +88 -0
- package/src/RadioButton/RadioButton.styles.tsx +163 -0
- package/src/RadioButton/RadioButton.web.tsx +85 -0
- package/src/RadioButton/RadioGroup.native.tsx +43 -0
- package/src/RadioButton/RadioGroup.web.tsx +49 -0
- package/src/RadioButton/index.native.ts +2 -0
- package/src/RadioButton/index.ts +2 -0
- package/src/RadioButton/index.web.ts +2 -0
- package/src/RadioButton/types.ts +29 -0
- package/src/SVGImage/SVGImage.native.tsx +9 -7
- package/src/SVGImage/SVGImage.styles.tsx +63 -55
- package/src/SVGImage/SVGImage.web.tsx +16 -13
- package/src/SVGImage/index.ts +5 -5
- package/src/SVGImage/index.web.ts +5 -2
- package/src/SVGImage/types.ts +7 -3
- package/src/Screen/Screen.native.tsx +43 -17
- package/src/Screen/Screen.styles.tsx +58 -54
- package/src/Screen/Screen.web.tsx +11 -5
- package/src/Screen/index.ts +5 -2
- package/src/Screen/index.web.ts +5 -2
- package/src/Screen/types.ts +23 -9
- package/src/Select/Select.native.tsx +140 -63
- package/src/Select/Select.styles.tsx +312 -302
- package/src/Select/Select.web.tsx +156 -316
- package/src/Select/index.ts +5 -2
- package/src/Select/index.web.ts +5 -2
- package/src/Select/types.ts +13 -7
- package/src/Skeleton/Skeleton.native.tsx +139 -0
- package/src/Skeleton/Skeleton.styles.tsx +59 -0
- package/src/Skeleton/Skeleton.web.tsx +112 -0
- package/src/Skeleton/index.native.ts +4 -0
- package/src/Skeleton/index.ts +5 -0
- package/src/Skeleton/index.web.ts +5 -0
- package/src/Skeleton/types.ts +75 -0
- package/src/Slider/Slider.native.tsx +248 -0
- package/src/Slider/Slider.styles.tsx +241 -0
- package/src/Slider/Slider.web.tsx +226 -0
- package/src/Slider/index.native.ts +3 -0
- package/src/Slider/index.ts +5 -0
- package/src/Slider/index.web.ts +5 -0
- package/src/Slider/types.ts +31 -0
- package/src/Switch/Switch.native.tsx +131 -0
- package/src/Switch/Switch.styles.tsx +169 -0
- package/src/Switch/Switch.web.tsx +121 -0
- package/src/Switch/index.native.ts +3 -0
- package/src/Switch/index.ts +5 -0
- package/src/Switch/index.web.ts +5 -0
- package/src/Switch/types.ts +21 -0
- package/src/TabBar/TabBar.native.tsx +142 -0
- package/src/TabBar/TabBar.styles.tsx +399 -0
- package/src/TabBar/TabBar.web.tsx +205 -0
- package/src/TabBar/index.native.tsx +3 -0
- package/src/TabBar/index.ts +3 -0
- package/src/TabBar/index.web.tsx +3 -0
- package/src/TabBar/types.ts +26 -0
- package/src/Table/Table.native.tsx +122 -0
- package/src/Table/Table.styles.tsx +283 -0
- package/src/Table/Table.web.tsx +112 -0
- package/src/Table/index.native.tsx +3 -0
- package/src/Table/index.ts +3 -0
- package/src/Table/index.web.tsx +3 -0
- package/src/Table/types.ts +28 -0
- package/src/Text/Text.native.tsx +12 -11
- package/src/Text/Text.styles.tsx +76 -64
- package/src/Text/Text.web.tsx +14 -9
- package/src/Text/index.ts +5 -5
- package/src/Text/index.web.ts +5 -3
- package/src/Text/types.ts +20 -13
- package/src/TextArea/TextArea.native.tsx +134 -0
- package/src/TextArea/TextArea.styles.tsx +175 -0
- package/src/TextArea/TextArea.web.tsx +156 -0
- package/src/TextArea/index.native.ts +3 -0
- package/src/TextArea/index.ts +3 -0
- package/src/TextArea/index.web.ts +3 -0
- package/src/TextArea/types.ts +30 -0
- package/src/Tooltip/Tooltip.native.tsx +165 -0
- package/src/Tooltip/Tooltip.styles.tsx +73 -0
- package/src/Tooltip/Tooltip.web.tsx +87 -0
- package/src/Tooltip/index.native.ts +3 -0
- package/src/Tooltip/index.ts +3 -0
- package/src/Tooltip/types.ts +18 -0
- package/src/Video/Video.native.tsx +105 -0
- package/src/Video/Video.styles.tsx +39 -0
- package/src/Video/Video.web.tsx +115 -0
- package/src/Video/index.native.ts +5 -0
- package/src/Video/index.ts +5 -0
- package/src/Video/types.ts +29 -0
- package/src/View/View.native.tsx +9 -14
- package/src/View/View.styles.tsx +101 -93
- package/src/View/View.web.tsx +16 -17
- package/src/View/index.ts +5 -5
- package/src/View/index.web.ts +5 -3
- package/src/View/types.ts +29 -21
- package/src/examples/AccordionExamples.tsx +126 -0
- package/src/examples/AlertExamples.tsx +280 -0
- package/src/examples/AvatarExamples.tsx +23 -23
- package/src/examples/BadgeExamples.tsx +109 -41
- package/src/examples/BreadcrumbExamples.tsx +312 -0
- package/src/examples/ButtonExamples.tsx +160 -33
- package/src/examples/CardExamples.tsx +40 -40
- package/src/examples/CheckboxExamples.tsx +12 -12
- package/src/examples/ChipExamples.tsx +197 -0
- package/src/examples/DialogExamples.tsx +22 -22
- package/src/examples/DividerExamples.tsx +49 -49
- package/src/examples/IconExamples.tsx +270 -54
- package/src/examples/ImageExamples.tsx +174 -0
- package/src/examples/InputExamples.tsx +75 -17
- package/src/examples/ListExamples.tsx +288 -0
- package/src/examples/MenuExamples.tsx +144 -0
- package/src/examples/PopoverExamples.tsx +69 -73
- package/src/examples/ProgressExamples.tsx +137 -0
- package/src/examples/RadioButtonExamples.tsx +161 -0
- package/src/examples/SVGImageExamples.tsx +19 -17
- package/src/examples/ScreenExamples.tsx +31 -31
- package/src/examples/SelectExamples.tsx +67 -67
- package/src/examples/SkeletonExamples.tsx +206 -0
- package/src/examples/SliderExamples.tsx +200 -0
- package/src/examples/SwitchExamples.tsx +182 -0
- package/src/examples/TabBarExamples.tsx +143 -0
- package/src/examples/TableExamples.tsx +280 -0
- package/src/examples/TextAreaExamples.tsx +173 -0
- package/src/examples/TextExamples.tsx +28 -32
- package/src/examples/ThemeExtensionExamples.tsx +10 -10
- package/src/examples/TooltipExamples.tsx +126 -0
- package/src/examples/VideoExamples.tsx +144 -0
- package/src/examples/ViewExamples.tsx +64 -56
- package/src/examples/index.ts +17 -3
- package/src/hooks/useMergeRefs.ts +16 -0
- package/src/hooks/useSmartPosition.native.ts +169 -0
- package/src/index.native.ts +80 -9
- package/src/index.ts +71 -1
- package/src/internal/BoundedModalContent.native.tsx +58 -0
- package/src/internal/PositionedPortal.tsx +254 -0
- package/src/internal/SafeAreaDebugOverlay.native.tsx +173 -0
- package/src/unistyles.d.ts +6 -0
- package/src/utils/buildSizeVariants.ts +16 -0
- package/src/utils/deepMerge.ts +43 -0
- package/src/utils/positionUtils.native.ts +280 -0
- package/src/utils/styleHelpers.ts +48 -0
- package/LLM-ACCESS-GUIDE.md +0 -143
- package/src/ActivityIndicator/README.md +0 -132
- package/src/Avatar/README.md +0 -139
- package/src/Badge/README.md +0 -170
- package/src/Button/Button.types.ts +0 -12
- package/src/Button/README.md +0 -262
- package/src/Card/README.md +0 -258
- package/src/Checkbox/README.md +0 -102
- package/src/Dialog/README.md +0 -210
- package/src/Divider/README.md +0 -108
- package/src/Icon/README.md +0 -81
- package/src/Input/README.md +0 -100
- package/src/SVGImage/README.md +0 -209
- package/src/Screen/README.md +0 -86
- package/src/Select/README.md +0 -166
- package/src/Text/README.md +0 -94
- package/src/View/README.md +0 -107
- package/src/examples/AllExamples.tsx +0 -88
- package/src/examples/README.md +0 -136
- package/src/examples/ValidationExamples.tsx +0 -95
- package/src/examples/extendedTheme.ts +0 -329
- package/src/theme/breakpoints.ts +0 -8
- package/src/theme/colorResolver.ts +0 -218
- package/src/theme/colors.ts +0 -315
- package/src/theme/defaultThemes.ts +0 -326
- package/src/theme/index.ts +0 -188
- package/src/theme/themeBuilder.ts +0 -602
- package/src/theme/unistyles.d.ts +0 -6
- package/src/theme/variantHelpers.ts +0 -584
- package/src/theme/variants.ts +0 -56
package/plugin/README.md
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# MDI Auto-Import Babel Plugin
|
|
2
|
+
|
|
3
|
+
Automatically imports Material Design Icons for web builds and transforms Icon components to use the imported paths.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### 1. Context-Aware String Replacement
|
|
8
|
+
The plugin only transforms strings that are actually used with the `Icon` component. This prevents false positives on common words like "home", "account", etc.
|
|
9
|
+
|
|
10
|
+
```jsx
|
|
11
|
+
// ✅ WILL transform - used with Icon
|
|
12
|
+
const iconName = "home";
|
|
13
|
+
<Icon name={iconName} />
|
|
14
|
+
|
|
15
|
+
// ❌ WON'T transform - not used with Icon
|
|
16
|
+
const pageName = "home";
|
|
17
|
+
<div>{pageName}</div>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 2. Namespace Prefix Support
|
|
21
|
+
Use the `mdi:` prefix to explicitly mark a string as an icon name. This guarantees transformation even in complex scenarios.
|
|
22
|
+
|
|
23
|
+
```jsx
|
|
24
|
+
// Always transforms, even in complex expressions
|
|
25
|
+
const icon = "mdi:home";
|
|
26
|
+
<Icon name={icon} />
|
|
27
|
+
|
|
28
|
+
// Works with conditionals
|
|
29
|
+
<Icon name={showMenu ? "mdi:menu" : "mdi:close"} />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 3. Variable Tracking with Scope Analysis
|
|
33
|
+
The plugin follows variables back to their declarations to determine if they contain icon names.
|
|
34
|
+
|
|
35
|
+
```jsx
|
|
36
|
+
// Plugin tracks that iconName is used with Icon
|
|
37
|
+
const iconName = "account"; // ✅ Will transform
|
|
38
|
+
<Icon name={iconName} />
|
|
39
|
+
|
|
40
|
+
// Plugin knows this is unrelated
|
|
41
|
+
const userName = "account"; // ❌ Won't transform
|
|
42
|
+
<div>{userName}</div>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 4. Manifest Support
|
|
46
|
+
Add frequently-used icons to a manifest file to ensure they're always available:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"icons": ["home", "menu", "close", "check", "alert"]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Icons in the manifest are always imported, even if not statically analyzable.
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
### Direct String Literals
|
|
59
|
+
The simplest case - just use the icon name directly:
|
|
60
|
+
|
|
61
|
+
```jsx
|
|
62
|
+
<Icon name="home" size="md" />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Transforms to:**
|
|
66
|
+
```jsx
|
|
67
|
+
import { mdiHome as _mdiHome } from '@mdi/js';
|
|
68
|
+
<Icon path={_mdiHome} size="md" />
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Variables
|
|
72
|
+
Use variables for dynamic icons:
|
|
73
|
+
|
|
74
|
+
```jsx
|
|
75
|
+
const iconName = "account";
|
|
76
|
+
<Icon name={iconName} size="md" />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**The plugin will:**
|
|
80
|
+
1. Detect that `iconName` is used with `Icon`
|
|
81
|
+
2. Import `mdiAccount`
|
|
82
|
+
3. Transform the component
|
|
83
|
+
|
|
84
|
+
### Namespace Prefix (Recommended for Dynamic Cases)
|
|
85
|
+
For maximum reliability, especially with computed values:
|
|
86
|
+
|
|
87
|
+
```jsx
|
|
88
|
+
const iconName = "mdi:star";
|
|
89
|
+
<Icon name={iconName} size="md" />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The `mdi:` prefix guarantees the string will be recognized as an icon name.
|
|
93
|
+
|
|
94
|
+
### Conditional Expressions
|
|
95
|
+
```jsx
|
|
96
|
+
<Icon name={isActive ? "check" : "close"} size="md" />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**The plugin will:**
|
|
100
|
+
1. Import both `mdiCheck` and `mdiClose`
|
|
101
|
+
2. Keep the component as-is (since there are multiple possible icons)
|
|
102
|
+
|
|
103
|
+
To transform conditionals, use namespace prefixes:
|
|
104
|
+
```jsx
|
|
105
|
+
<Icon name={isActive ? "mdi:check" : "mdi:close"} size="md" />
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Function Returns
|
|
109
|
+
For function calls that return icon names, add them to the manifest:
|
|
110
|
+
|
|
111
|
+
```jsx
|
|
112
|
+
// icons.manifest.json
|
|
113
|
+
{
|
|
114
|
+
"icons": ["file", "folder", "document"]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Your code
|
|
118
|
+
function getFileIcon(type) {
|
|
119
|
+
return type === 'dir' ? 'folder' : 'file';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
<Icon name={getFileIcon(fileType)} /> // Icons pre-imported from manifest
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Configuration
|
|
126
|
+
|
|
127
|
+
### Plugin Options
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
// babel.config.js
|
|
131
|
+
module.exports = {
|
|
132
|
+
plugins: [
|
|
133
|
+
['@idealyst/components/plugin/web', {
|
|
134
|
+
debug: false, // Enable debug logging
|
|
135
|
+
manifestPath: './icons.manifest.json' // Path to icon manifest
|
|
136
|
+
}]
|
|
137
|
+
]
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Manifest File Format
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"icons": [
|
|
146
|
+
"home",
|
|
147
|
+
"menu",
|
|
148
|
+
"close",
|
|
149
|
+
"account",
|
|
150
|
+
"settings"
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## How It Works
|
|
156
|
+
|
|
157
|
+
### First Pass: Variable Tracking
|
|
158
|
+
1. Scan all `<Icon>` components in the file
|
|
159
|
+
2. Track any variables used in the `name` prop
|
|
160
|
+
3. Mark these variables as "icon-related"
|
|
161
|
+
|
|
162
|
+
### Second Pass: Transformation
|
|
163
|
+
1. For each `<Icon>` component:
|
|
164
|
+
- Extract icon names from the `name` prop
|
|
165
|
+
- Handle direct strings, variables, conditionals, etc.
|
|
166
|
+
- Import the required MDI icons
|
|
167
|
+
- Transform `name="icon"` to `path={_mdiIcon}`
|
|
168
|
+
|
|
169
|
+
2. For each string literal:
|
|
170
|
+
- Check if it's icon-related (used with Icon, has `mdi:` prefix, or in manifest)
|
|
171
|
+
- If yes, import the icon
|
|
172
|
+
- Context-aware: only transforms Icon-related strings
|
|
173
|
+
|
|
174
|
+
### Third Pass: Add Imports
|
|
175
|
+
- Add all collected icon imports from `@mdi/js`
|
|
176
|
+
- Add `MdiIcon` import from `@mdi/react` if needed
|
|
177
|
+
|
|
178
|
+
## Best Practices
|
|
179
|
+
|
|
180
|
+
### ✅ DO
|
|
181
|
+
|
|
182
|
+
```jsx
|
|
183
|
+
// Use direct strings when possible
|
|
184
|
+
<Icon name="home" />
|
|
185
|
+
|
|
186
|
+
// Use namespace prefix for dynamic icons
|
|
187
|
+
const icon = "mdi:account";
|
|
188
|
+
<Icon name={icon} />
|
|
189
|
+
|
|
190
|
+
// Add common icons to manifest
|
|
191
|
+
// icons.manifest.json: { "icons": ["home", "menu"] }
|
|
192
|
+
|
|
193
|
+
// Use variables for clarity
|
|
194
|
+
const deleteIcon = "delete";
|
|
195
|
+
<Icon name={deleteIcon} />
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### ❌ DON'T
|
|
199
|
+
|
|
200
|
+
```jsx
|
|
201
|
+
// Don't rely on transformation for computed strings
|
|
202
|
+
<Icon name={`icon-${type}`} /> // Won't work
|
|
203
|
+
|
|
204
|
+
// Don't use common words without context
|
|
205
|
+
const home = "home"; // If not used with Icon, won't transform
|
|
206
|
+
<SomeOtherComponent name={home} />
|
|
207
|
+
|
|
208
|
+
// Don't use complex expressions without namespace
|
|
209
|
+
<Icon name={getIcon()} /> // Add to manifest instead
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Troubleshooting
|
|
213
|
+
|
|
214
|
+
### Icon not transforming?
|
|
215
|
+
1. Check if the string is actually used with `<Icon name={...} />`
|
|
216
|
+
2. Try adding `mdi:` prefix: `"mdi:home"`
|
|
217
|
+
3. Add the icon to `icons.manifest.json`
|
|
218
|
+
4. Enable debug mode to see what the plugin is doing
|
|
219
|
+
|
|
220
|
+
### False positives?
|
|
221
|
+
This should be rare with the enhanced plugin, but if it happens:
|
|
222
|
+
1. Ensure the variable is not used with `Icon`
|
|
223
|
+
2. Report it as a bug with the code example
|
|
224
|
+
|
|
225
|
+
### Getting warnings about dynamic expressions?
|
|
226
|
+
Add the possible icon names to the manifest file.
|
|
227
|
+
|
|
228
|
+
## Migration from Old Plugin
|
|
229
|
+
|
|
230
|
+
The enhanced plugin is backwards compatible. Existing code will continue to work.
|
|
231
|
+
|
|
232
|
+
New features you can now use:
|
|
233
|
+
- Variables with icon names (without manifest)
|
|
234
|
+
- Namespace prefix for explicit marking
|
|
235
|
+
- Better handling of common words
|
|
236
|
+
|
|
237
|
+
## Examples
|
|
238
|
+
|
|
239
|
+
See `test-cases.jsx` for comprehensive examples of all supported patterns.
|
|
240
|
+
|
|
241
|
+
## Technical Details
|
|
242
|
+
|
|
243
|
+
### Icon Name Format
|
|
244
|
+
- Input: `"home"`, `"account-circle"`, `"chevron-right"`
|
|
245
|
+
- Output: `mdiHome`, `mdiAccountCircle`, `mdiChevronRight`
|
|
246
|
+
|
|
247
|
+
The plugin converts kebab-case and snake_case to PascalCase and adds the `mdi` prefix.
|
|
248
|
+
|
|
249
|
+
### Scope Analysis
|
|
250
|
+
Uses Babel's scope API to:
|
|
251
|
+
- Track variable bindings
|
|
252
|
+
- Follow references to declarations
|
|
253
|
+
- Determine if a variable is icon-related
|
|
254
|
+
|
|
255
|
+
### Performance
|
|
256
|
+
- Single-pass analysis per file
|
|
257
|
+
- Minimal overhead during build
|
|
258
|
+
- Only processes files that import Icon component
|
|
259
|
+
|
|
260
|
+
## Contributing
|
|
261
|
+
|
|
262
|
+
To test changes to the plugin:
|
|
263
|
+
1. Edit `packages/components/plugin/web.js`
|
|
264
|
+
2. Run a build: `yarn build`
|
|
265
|
+
3. Check the transformed output in `dist/`
|
|
266
|
+
|
|
267
|
+
Enable debug mode to see detailed logs:
|
|
268
|
+
```javascript
|
|
269
|
+
{
|
|
270
|
+
debug: true
|
|
271
|
+
}
|
|
272
|
+
```
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test cases for the enhanced MDI auto-import babel plugin
|
|
3
|
+
*
|
|
4
|
+
* These examples demonstrate how the plugin handles different scenarios:
|
|
5
|
+
* 1. Direct string literals in Icon components
|
|
6
|
+
* 2. Variables with icon names
|
|
7
|
+
* 3. Namespace prefixes (mdi:iconname)
|
|
8
|
+
* 4. Conditional expressions
|
|
9
|
+
* 5. Common words that should NOT be transformed
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { Icon } from '@idealyst/components';
|
|
14
|
+
|
|
15
|
+
export function TestCases() {
|
|
16
|
+
// Case 1: Direct string literal - SHOULD transform
|
|
17
|
+
const case1 = <Icon name="home" size="md" />;
|
|
18
|
+
|
|
19
|
+
// Case 2: Variable with icon name - SHOULD transform (context-aware)
|
|
20
|
+
const iconName = "account";
|
|
21
|
+
const case2 = <Icon name={iconName} size="md" />;
|
|
22
|
+
|
|
23
|
+
// Case 3: Namespace prefix - SHOULD ALWAYS transform
|
|
24
|
+
const explicitIcon = "mdi:star";
|
|
25
|
+
const case3 = <Icon name={explicitIcon} size="md" />;
|
|
26
|
+
|
|
27
|
+
// Case 4: Direct namespace in JSX - SHOULD transform
|
|
28
|
+
const case4 = <Icon name="mdi:heart" size="md" />;
|
|
29
|
+
|
|
30
|
+
// Case 5: Conditional expression - SHOULD transform both
|
|
31
|
+
const isActive = true;
|
|
32
|
+
const case5 = <Icon name={isActive ? "check" : "close"} size="md" />;
|
|
33
|
+
|
|
34
|
+
// Case 6: Common word NOT used with Icon - should NOT transform
|
|
35
|
+
const pageName = "home"; // This is just a page name, not an icon
|
|
36
|
+
const case6 = <div>{pageName}</div>;
|
|
37
|
+
|
|
38
|
+
// Case 7: Common word in unrelated variable - should NOT transform
|
|
39
|
+
const title = "account"; // Not used with Icon
|
|
40
|
+
const case7 = <h1>{title}</h1>;
|
|
41
|
+
|
|
42
|
+
// Case 8: Variable from manifest (if manifest includes "folder")
|
|
43
|
+
const directoryIcon = "folder";
|
|
44
|
+
const case8 = <Icon name={directoryIcon} size="md" />;
|
|
45
|
+
|
|
46
|
+
// Case 9: Function that returns icon name - currently won't transform without manifest
|
|
47
|
+
function getIcon() {
|
|
48
|
+
return "file";
|
|
49
|
+
}
|
|
50
|
+
const case9 = <Icon name={getIcon()} size="md" />; // Won't transform unless "file" is in manifest
|
|
51
|
+
|
|
52
|
+
// Case 10: Complex expression with namespace prefix - SHOULD transform
|
|
53
|
+
const showDetails = false;
|
|
54
|
+
const case10 = <Icon name={showDetails ? "mdi:chevron-down" : "mdi:chevron-right"} size="md" />;
|
|
55
|
+
|
|
56
|
+
// Case 11: Template literal (static) - SHOULD transform
|
|
57
|
+
const case11 = <Icon name={`home`} size="md" />;
|
|
58
|
+
|
|
59
|
+
// Case 12: Logical expression - SHOULD transform
|
|
60
|
+
const hasError = true;
|
|
61
|
+
const case12 = <Icon name={hasError && "alert"} size="md" />;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div>
|
|
65
|
+
<h2>Icon Transform Test Cases</h2>
|
|
66
|
+
<div>Case 1 (direct literal): {case1}</div>
|
|
67
|
+
<div>Case 2 (variable): {case2}</div>
|
|
68
|
+
<div>Case 3 (namespace variable): {case3}</div>
|
|
69
|
+
<div>Case 4 (namespace direct): {case4}</div>
|
|
70
|
+
<div>Case 5 (conditional): {case5}</div>
|
|
71
|
+
<div>Case 6 (non-icon string): {case6}</div>
|
|
72
|
+
<div>Case 7 (unrelated variable): {case7}</div>
|
|
73
|
+
<div>Case 8 (manifest icon): {case8}</div>
|
|
74
|
+
<div>Case 9 (function call): {case9}</div>
|
|
75
|
+
<div>Case 10 (complex with namespace): {case10}</div>
|
|
76
|
+
<div>Case 11 (template literal): {case11}</div>
|
|
77
|
+
<div>Case 12 (logical expression): {case12}</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Expected transformations after babel plugin runs:
|
|
84
|
+
*
|
|
85
|
+
* - case1: "home" -> path={_mdiHome}
|
|
86
|
+
* - case2: "account" -> path={_mdiAccount} (because iconName is used with Icon)
|
|
87
|
+
* - case3: "mdi:star" -> path={_mdiStar} (namespace prefix)
|
|
88
|
+
* - case4: "mdi:heart" -> path={_mdiHeart} (namespace prefix)
|
|
89
|
+
* - case5: Both "check" and "close" imported, component not transformed (multiple icons)
|
|
90
|
+
* - case6: "home" NOT transformed (not used with Icon)
|
|
91
|
+
* - case7: "account" NOT transformed (not used with Icon)
|
|
92
|
+
* - case8: "folder" -> path={_mdiFolder} (if in manifest)
|
|
93
|
+
* - case9: No transform unless "file" is in manifest
|
|
94
|
+
* - case10: Both chevron icons imported and transformed (namespace prefix)
|
|
95
|
+
* - case11: "home" -> path={_mdiHome} (static template literal)
|
|
96
|
+
* - case12: "alert" imported (but component might not transform due to logical expression)
|
|
97
|
+
*
|
|
98
|
+
* Expected imports at top of file:
|
|
99
|
+
* import MdiIcon from '@mdi/react';
|
|
100
|
+
* import {
|
|
101
|
+
* mdiHome as _mdiHome,
|
|
102
|
+
* mdiAccount as _mdiAccount,
|
|
103
|
+
* mdiStar as _mdiStar,
|
|
104
|
+
* mdiHeart as _mdiHeart,
|
|
105
|
+
* mdiCheck as _mdiCheck,
|
|
106
|
+
* mdiClose as _mdiClose,
|
|
107
|
+
* mdiFolder as _mdiFolder, // if in manifest
|
|
108
|
+
* mdiChevronDown as _mdiChevronDown,
|
|
109
|
+
* mdiChevronRight as _mdiChevronRight,
|
|
110
|
+
* mdiAlert as _mdiAlert
|
|
111
|
+
* } from '@mdi/js';
|
|
112
|
+
*/
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
module.exports = function ({ types: t }, options = {}) {
|
|
2
|
+
const debug = options.debug || false;
|
|
3
|
+
const manifestPath = options.manifestPath || './icons.manifest.json';
|
|
4
|
+
|
|
5
|
+
// Debug logging function that only logs when debug is enabled
|
|
6
|
+
const debugLog = (...args) => {
|
|
7
|
+
if (debug) {
|
|
8
|
+
console.log(...args);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
debugLog('[mdi-auto-import] Plugin loaded');
|
|
13
|
+
|
|
14
|
+
const importedIcons = new Set();
|
|
15
|
+
const iconImportIdentifiers = new Map();
|
|
16
|
+
let hasIconImport = false;
|
|
17
|
+
let manifestIcons = new Set();
|
|
18
|
+
|
|
19
|
+
// Load icon manifest if it exists
|
|
20
|
+
function loadIconManifest() {
|
|
21
|
+
try {
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
// Try to resolve the manifest path relative to the current working directory
|
|
26
|
+
const fullPath = path.resolve(process.cwd(), manifestPath);
|
|
27
|
+
|
|
28
|
+
if (fs.existsSync(fullPath)) {
|
|
29
|
+
const manifestContent = fs.readFileSync(fullPath, 'utf8');
|
|
30
|
+
const manifest = JSON.parse(manifestContent);
|
|
31
|
+
|
|
32
|
+
if (manifest.icons && Array.isArray(manifest.icons)) {
|
|
33
|
+
manifest.icons.forEach(iconName => {
|
|
34
|
+
if (typeof iconName === 'string') {
|
|
35
|
+
manifestIcons.add(iconName);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
debugLog(`[mdi-auto-import] Loaded ${manifestIcons.size} icons from manifest: ${fullPath}`);
|
|
39
|
+
debugLog('[mdi-auto-import] Manifest icons:', Array.from(manifestIcons));
|
|
40
|
+
} else {
|
|
41
|
+
console.warn(`[mdi-auto-import] Invalid manifest format in ${fullPath}. Expected { "icons": ["icon-name", ...] }`);
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
debugLog(`[mdi-auto-import] No manifest found at ${fullPath}`);
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn(`[mdi-auto-import] Error loading manifest from ${manifestPath}: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatIconName(name) {
|
|
52
|
+
// Handle empty or invalid names
|
|
53
|
+
if (!name || typeof name !== 'string') {
|
|
54
|
+
throw new Error(`Invalid icon name: ${name}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const formatted = name
|
|
58
|
+
// Convert kebab-case and snake_case to PascalCase
|
|
59
|
+
.replace(/[-_]/g, ' ')
|
|
60
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
61
|
+
.split(' ')
|
|
62
|
+
.filter(part => part.length > 0)
|
|
63
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
64
|
+
.join('');
|
|
65
|
+
|
|
66
|
+
debugLog(`[mdi-auto-import] formatIconName: ${name} -> ${formatted}`);
|
|
67
|
+
return formatted;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getMdiIconName(name) {
|
|
71
|
+
const mdiName = `mdi${formatIconName(name)}`;
|
|
72
|
+
debugLog(`[mdi-auto-import] getMdiIconName: ${name} -> ${mdiName}`);
|
|
73
|
+
return mdiName;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getIconIdentifier(iconName) {
|
|
77
|
+
if (!iconImportIdentifiers.has(iconName)) {
|
|
78
|
+
iconImportIdentifiers.set(iconName, `_${iconName}`);
|
|
79
|
+
}
|
|
80
|
+
const identifier = iconImportIdentifiers.get(iconName);
|
|
81
|
+
debugLog(`[mdi-auto-import] getIconIdentifier: ${iconName} -> ${identifier}`);
|
|
82
|
+
return identifier;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Recursively extract all possible string literal values from an expression
|
|
86
|
+
function extractIconNames(expression, path) {
|
|
87
|
+
const iconNames = new Set();
|
|
88
|
+
|
|
89
|
+
function traverse(node) {
|
|
90
|
+
if (!node) return;
|
|
91
|
+
|
|
92
|
+
if (t.isStringLiteral(node)) {
|
|
93
|
+
iconNames.add(node.value);
|
|
94
|
+
debugLog(`[mdi-auto-import] Found string literal: ${node.value}`);
|
|
95
|
+
}
|
|
96
|
+
else if (t.isConditionalExpression(node)) {
|
|
97
|
+
// Handle ternary: condition ? 'icon1' : 'icon2'
|
|
98
|
+
debugLog('[mdi-auto-import] Processing conditional expression');
|
|
99
|
+
traverse(node.consequent);
|
|
100
|
+
traverse(node.alternate);
|
|
101
|
+
}
|
|
102
|
+
else if (t.isLogicalExpression(node)) {
|
|
103
|
+
// Handle logical: condition && 'icon1' || 'icon2'
|
|
104
|
+
debugLog('[mdi-auto-import] Processing logical expression');
|
|
105
|
+
traverse(node.left);
|
|
106
|
+
traverse(node.right);
|
|
107
|
+
}
|
|
108
|
+
else if (t.isTemplateLiteral(node)) {
|
|
109
|
+
// Handle template literals with no expressions (static strings)
|
|
110
|
+
if (node.expressions.length === 0 && node.quasis.length === 1) {
|
|
111
|
+
const value = node.quasis[0].value.cooked;
|
|
112
|
+
iconNames.add(value);
|
|
113
|
+
debugLog(`[mdi-auto-import] Found template literal: ${value}`);
|
|
114
|
+
} else {
|
|
115
|
+
debugLog('[mdi-auto-import] Skipping dynamic template literal');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (t.isMemberExpression(node)) {
|
|
119
|
+
// Handle object.property where object is static
|
|
120
|
+
if (t.isIdentifier(node.object) && t.isIdentifier(node.property)) {
|
|
121
|
+
debugLog(`[mdi-auto-import] Found member expression: ${node.object.name}.${node.property.name}`);
|
|
122
|
+
// We could potentially resolve this if we track object declarations
|
|
123
|
+
// For now, just warn that we found it but can't resolve it
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (t.isCallExpression(node)) {
|
|
127
|
+
debugLog('[mdi-auto-import] Found function call - cannot statically analyze');
|
|
128
|
+
console.warn(`[mdi-auto-import] Function call detected at ${path.node.loc ? `${path.node.loc.start.line}:${path.node.loc.start.column}` : 'unknown location'}. Consider adding icon names to manifest (${manifestPath}) for auto-import support.`);
|
|
129
|
+
// For function calls, we can't statically determine the result
|
|
130
|
+
// But we could potentially add runtime analysis or hints
|
|
131
|
+
}
|
|
132
|
+
else if (t.isIdentifier(node)) {
|
|
133
|
+
debugLog(`[mdi-auto-import] Found identifier: ${node.name}`);
|
|
134
|
+
// We could potentially trace variable declarations
|
|
135
|
+
// For now, just note that we found it
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
debugLog(`[mdi-auto-import] Unhandled expression type: ${node.type}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
traverse(expression);
|
|
143
|
+
return Array.from(iconNames);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
name: 'mdi-auto-import',
|
|
148
|
+
visitor: {
|
|
149
|
+
Program: {
|
|
150
|
+
enter(path) {
|
|
151
|
+
// Reset state for each file
|
|
152
|
+
importedIcons.clear();
|
|
153
|
+
iconImportIdentifiers.clear();
|
|
154
|
+
hasIconImport = false;
|
|
155
|
+
manifestIcons.clear();
|
|
156
|
+
|
|
157
|
+
// Load icon manifest
|
|
158
|
+
loadIconManifest();
|
|
159
|
+
|
|
160
|
+
// Add all manifest icons to the import list
|
|
161
|
+
manifestIcons.forEach(iconName => {
|
|
162
|
+
try {
|
|
163
|
+
const mdiIconName = getMdiIconName(iconName);
|
|
164
|
+
importedIcons.add(mdiIconName);
|
|
165
|
+
debugLog(`[mdi-auto-import] Added manifest icon to import list: ${mdiIconName}`);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error(`[mdi-auto-import] Error processing manifest icon "${iconName}": ${error.message}`);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Check if Icon is already imported from @mdi/react
|
|
172
|
+
path.node.body.forEach(node => {
|
|
173
|
+
if (t.isImportDeclaration(node) && node.source.value === '@mdi/react') {
|
|
174
|
+
debugLog('[mdi-auto-import] Found @mdi/react import');
|
|
175
|
+
const hasIconSpecifier = node.specifiers.some(spec =>
|
|
176
|
+
t.isImportDefaultSpecifier(spec) && spec.local.name === 'MdiIcon'
|
|
177
|
+
);
|
|
178
|
+
if (hasIconSpecifier) {
|
|
179
|
+
debugLog('[mdi-auto-import] MdiIcon already imported');
|
|
180
|
+
hasIconImport = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
exit(path) {
|
|
186
|
+
if (importedIcons.size === 0) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
debugLog(`[mdi-auto-import] importedIcons.size: ${importedIcons.size}`);
|
|
190
|
+
|
|
191
|
+
// Add imports at the top of the file if any icons were used
|
|
192
|
+
if (importedIcons.size > 0) {
|
|
193
|
+
debugLog('[mdi-auto-import] Adding imports for icons:', Array.from(importedIcons));
|
|
194
|
+
|
|
195
|
+
// Import individual icons from @mdi/js
|
|
196
|
+
const iconImportSpecifiers = Array.from(importedIcons).map(iconName => {
|
|
197
|
+
const identifier = getIconIdentifier(iconName);
|
|
198
|
+
return t.importSpecifier(
|
|
199
|
+
t.identifier(identifier),
|
|
200
|
+
t.identifier(iconName)
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const iconImportDeclaration = t.importDeclaration(
|
|
205
|
+
iconImportSpecifiers,
|
|
206
|
+
t.stringLiteral('@mdi/js')
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Import Icon component from @mdi/react if not already imported
|
|
210
|
+
if (!hasIconImport) {
|
|
211
|
+
debugLog('[mdi-auto-import] Adding MdiIcon import from @mdi/react');
|
|
212
|
+
const iconComponentImport = t.importDeclaration(
|
|
213
|
+
[t.importDefaultSpecifier(t.identifier('MdiIcon'))],
|
|
214
|
+
t.stringLiteral('@mdi/react')
|
|
215
|
+
);
|
|
216
|
+
path.unshiftContainer('body', iconComponentImport);
|
|
217
|
+
} else {
|
|
218
|
+
debugLog('[mdi-auto-import] MdiIcon already imported, skipping');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Add icon imports
|
|
222
|
+
path.unshiftContainer('body', iconImportDeclaration);
|
|
223
|
+
debugLog('[mdi-auto-import] Imports added successfully');
|
|
224
|
+
} else {
|
|
225
|
+
debugLog('[mdi-auto-import] No icons to import');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
JSXElement(path) {
|
|
231
|
+
const { node } = path;
|
|
232
|
+
|
|
233
|
+
// Check if this is an Icon component from @idealyst/components
|
|
234
|
+
if (
|
|
235
|
+
t.isJSXIdentifier(node.openingElement.name) &&
|
|
236
|
+
node.openingElement.name.name === 'Icon'
|
|
237
|
+
) {
|
|
238
|
+
|
|
239
|
+
// Find the name attribute
|
|
240
|
+
const nameAttr = node.openingElement.attributes.find(attr =>
|
|
241
|
+
t.isJSXAttribute(attr) &&
|
|
242
|
+
t.isJSXIdentifier(attr.name) &&
|
|
243
|
+
attr.name.name === 'name'
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (!nameAttr) {
|
|
247
|
+
debugLog('[mdi-auto-import] No name attribute found');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let iconNames = [];
|
|
252
|
+
|
|
253
|
+
// Handle both string literals and JSX expressions
|
|
254
|
+
if (nameAttr && t.isStringLiteral(nameAttr.value)) {
|
|
255
|
+
iconNames = [nameAttr.value.value];
|
|
256
|
+
debugLog(`[mdi-auto-import] Found direct string literal: ${nameAttr.value.value}`);
|
|
257
|
+
} else if (nameAttr && t.isJSXExpressionContainer(nameAttr.value)) {
|
|
258
|
+
// Handle JSX expressions with enhanced detection
|
|
259
|
+
const expression = nameAttr.value.expression;
|
|
260
|
+
iconNames = extractIconNames(expression, path);
|
|
261
|
+
|
|
262
|
+
if (iconNames.length === 0) {
|
|
263
|
+
// For dynamic expressions we can't resolve, leave a helpful comment
|
|
264
|
+
console.warn(`[mdi-auto-import] Cannot determine icon name (${nameAttr.value.expression}) for dynamic expression at ${path.node.loc ? `${path.node.loc.start.line}:${path.node.loc.start.column}` : 'unknown location'}. Consider adding icon names to manifest (${manifestPath}) for auto-import support.`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (iconNames.length > 0) {
|
|
270
|
+
debugLog(`[mdi-auto-import] Processing icons: ${iconNames.join(', ')}`);
|
|
271
|
+
|
|
272
|
+
// Process each icon name found
|
|
273
|
+
const processedIcons = [];
|
|
274
|
+
iconNames.forEach(iconName => {
|
|
275
|
+
try {
|
|
276
|
+
const mdiIconName = getMdiIconName(iconName);
|
|
277
|
+
const iconIdentifier = getIconIdentifier(mdiIconName);
|
|
278
|
+
|
|
279
|
+
// Track that we need to import this icon
|
|
280
|
+
importedIcons.add(mdiIconName);
|
|
281
|
+
processedIcons.push({ iconName, mdiIconName, iconIdentifier });
|
|
282
|
+
debugLog(`[mdi-auto-import] Added icon to import list: ${mdiIconName}`);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error(`[mdi-auto-import] Error processing icon "${iconName}": ${error.message}`);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// If we have exactly one icon, we can transform the component
|
|
289
|
+
if (processedIcons.length === 1) {
|
|
290
|
+
const { iconIdentifier } = processedIcons[0];
|
|
291
|
+
|
|
292
|
+
// Replace name="iconName" with path={iconIdentifier}
|
|
293
|
+
const pathAttr = t.jsxAttribute(
|
|
294
|
+
t.jsxIdentifier('path'),
|
|
295
|
+
t.jsxExpressionContainer(t.identifier(iconIdentifier))
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// Remove the name attribute and add the path attribute
|
|
299
|
+
node.openingElement.attributes = node.openingElement.attributes
|
|
300
|
+
.filter(attr => !(
|
|
301
|
+
t.isJSXAttribute(attr) &&
|
|
302
|
+
t.isJSXIdentifier(attr.name) &&
|
|
303
|
+
attr.name.name === 'name'
|
|
304
|
+
))
|
|
305
|
+
.concat(pathAttr);
|
|
306
|
+
|
|
307
|
+
debugLog(`[mdi-auto-import] Transformed Icon component: name="${processedIcons[0].iconName}" -> path={${iconIdentifier}}`);
|
|
308
|
+
} else if (processedIcons.length > 1) {
|
|
309
|
+
// For multiple possible icons (like conditionals), we add all imports but don't transform
|
|
310
|
+
debugLog(`[mdi-auto-import] Found multiple possible icons (${processedIcons.length}), adding imports but not transforming component`);
|
|
311
|
+
console.warn(`[mdi-auto-import] Found conditional icon usage at ${path.node.loc ? `${path.node.loc.start.line}:${path.node.loc.start.column}` : 'unknown location'}. All possible icons will be imported, but the component will not be auto-transformed. Consider manual transformation if needed.`);
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
debugLog('[mdi-auto-import] No icon names found');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
};
|