@idealyst/components 1.0.82 → 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.
Files changed (316) hide show
  1. package/CLAUDE.md +199 -232
  2. package/README.md +5 -5
  3. package/package.json +25 -7
  4. package/plugin/README.md +272 -0
  5. package/plugin/test-cases.jsx +112 -0
  6. package/plugin/web-legacy.js +320 -0
  7. package/plugin/web.js +422 -124
  8. package/src/Accordion/Accordion.native.tsx +182 -0
  9. package/src/Accordion/Accordion.styles.tsx +260 -0
  10. package/src/Accordion/Accordion.web.tsx +147 -0
  11. package/src/Accordion/index.native.tsx +3 -0
  12. package/src/Accordion/index.ts +3 -0
  13. package/src/Accordion/index.web.tsx +3 -0
  14. package/src/Accordion/types.ts +23 -0
  15. package/src/ActivityIndicator/ActivityIndicator.native.tsx +17 -12
  16. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +83 -109
  17. package/src/ActivityIndicator/ActivityIndicator.web.tsx +23 -17
  18. package/src/ActivityIndicator/index.ts +5 -2
  19. package/src/ActivityIndicator/index.web.ts +5 -2
  20. package/src/ActivityIndicator/types.ts +15 -10
  21. package/src/Alert/Alert.native.tsx +113 -0
  22. package/src/Alert/Alert.styles.tsx +304 -0
  23. package/src/Alert/Alert.web.tsx +123 -0
  24. package/src/Alert/index.native.ts +5 -0
  25. package/src/Alert/index.ts +5 -0
  26. package/src/Alert/index.web.ts +5 -0
  27. package/src/Alert/types.ts +21 -0
  28. package/src/Avatar/Avatar.native.tsx +8 -6
  29. package/src/Avatar/Avatar.styles.tsx +64 -58
  30. package/src/Avatar/Avatar.web.tsx +13 -8
  31. package/src/Avatar/index.ts +5 -2
  32. package/src/Avatar/index.web.ts +5 -2
  33. package/src/Avatar/types.ts +19 -13
  34. package/src/Badge/Badge.native.tsx +59 -14
  35. package/src/Badge/Badge.styles.tsx +125 -139
  36. package/src/Badge/Badge.web.tsx +72 -16
  37. package/src/Badge/index.ts +5 -2
  38. package/src/Badge/index.web.ts +5 -2
  39. package/src/Badge/types.ts +23 -11
  40. package/src/Breadcrumb/Breadcrumb.native.tsx +225 -0
  41. package/src/Breadcrumb/Breadcrumb.styles.tsx +234 -0
  42. package/src/Breadcrumb/Breadcrumb.web.tsx +268 -0
  43. package/src/Breadcrumb/index.native.ts +5 -0
  44. package/src/Breadcrumb/index.ts +5 -0
  45. package/src/Breadcrumb/index.web.ts +5 -0
  46. package/src/Breadcrumb/types.ts +56 -0
  47. package/src/Button/Button.native.tsx +75 -24
  48. package/src/Button/Button.styles.tsx +248 -205
  49. package/src/Button/Button.web.tsx +82 -25
  50. package/src/Button/index.ts +5 -5
  51. package/src/Button/index.web.ts +5 -3
  52. package/src/Button/types.ts +32 -15
  53. package/src/Card/Card.native.tsx +14 -11
  54. package/src/Card/Card.styles.tsx +146 -220
  55. package/src/Card/Card.web.tsx +20 -21
  56. package/src/Card/index.ts +5 -5
  57. package/src/Card/index.web.ts +5 -3
  58. package/src/Card/types.ts +24 -17
  59. package/src/Checkbox/Checkbox.native.tsx +24 -34
  60. package/src/Checkbox/Checkbox.styles.tsx +223 -275
  61. package/src/Checkbox/Checkbox.web.tsx +30 -37
  62. package/src/Checkbox/index.ts +5 -5
  63. package/src/Checkbox/index.web.ts +5 -3
  64. package/src/Checkbox/types.ts +26 -20
  65. package/src/Chip/Chip.native.tsx +126 -0
  66. package/src/Chip/Chip.styles.tsx +138 -0
  67. package/src/Chip/Chip.web.tsx +154 -0
  68. package/src/Chip/index.native.ts +5 -0
  69. package/src/Chip/index.ts +5 -0
  70. package/src/Chip/index.web.ts +5 -0
  71. package/src/Chip/types.ts +51 -0
  72. package/src/Dialog/Dialog.native.tsx +65 -12
  73. package/src/Dialog/Dialog.styles.tsx +154 -136
  74. package/src/Dialog/Dialog.web.tsx +16 -11
  75. package/src/Dialog/index.ts +5 -2
  76. package/src/Dialog/index.web.ts +5 -2
  77. package/src/Dialog/types.ts +22 -16
  78. package/src/Divider/Divider.native.tsx +19 -14
  79. package/src/Divider/Divider.styles.tsx +273 -595
  80. package/src/Divider/Divider.web.tsx +19 -12
  81. package/src/Divider/index.ts +5 -5
  82. package/src/Divider/index.web.ts +5 -3
  83. package/src/Divider/types.ts +28 -19
  84. package/src/Icon/Icon.native.tsx +17 -24
  85. package/src/Icon/Icon.styles.tsx +64 -48
  86. package/src/Icon/Icon.web.tsx +14 -11
  87. package/src/Icon/IconSvg/IconSvg.native.tsx +42 -0
  88. package/src/Icon/IconSvg/IconSvg.web.tsx +40 -0
  89. package/src/Icon/IconSvg/index.native.ts +1 -0
  90. package/src/Icon/IconSvg/index.ts +1 -0
  91. package/src/Icon/icon-resolver.native.ts +27 -0
  92. package/src/Icon/icon-resolver.ts +70 -0
  93. package/src/Icon/index.ts +5 -5
  94. package/src/Icon/index.web.ts +5 -3
  95. package/src/Icon/types.ts +17 -11
  96. package/src/Image/Image.native.tsx +86 -0
  97. package/src/Image/Image.styles.tsx +57 -0
  98. package/src/Image/Image.web.tsx +92 -0
  99. package/src/Image/index.native.ts +5 -0
  100. package/src/Image/index.ts +5 -0
  101. package/src/Image/types.ts +21 -0
  102. package/src/Input/Input.native.tsx +103 -26
  103. package/src/Input/Input.styles.tsx +240 -177
  104. package/src/Input/Input.web.tsx +141 -38
  105. package/src/Input/index.ts +5 -5
  106. package/src/Input/index.web.ts +5 -3
  107. package/src/Input/types.ts +43 -20
  108. package/src/List/List.native.tsx +56 -0
  109. package/src/List/List.styles.tsx +257 -0
  110. package/src/List/List.web.tsx +43 -0
  111. package/src/List/ListContext.tsx +16 -0
  112. package/src/List/ListItem.native.tsx +111 -0
  113. package/src/List/ListItem.web.tsx +110 -0
  114. package/src/List/ListSection.native.tsx +31 -0
  115. package/src/List/ListSection.web.tsx +33 -0
  116. package/src/List/index.native.tsx +5 -0
  117. package/src/List/index.ts +5 -0
  118. package/src/List/index.web.tsx +5 -0
  119. package/src/List/types.ts +42 -0
  120. package/src/Menu/Menu.native.tsx +150 -0
  121. package/src/Menu/Menu.styles.tsx +185 -0
  122. package/src/Menu/Menu.web.tsx +99 -0
  123. package/src/Menu/MenuItem.native.tsx +66 -0
  124. package/src/Menu/MenuItem.styles.tsx +119 -0
  125. package/src/Menu/MenuItem.web.tsx +67 -0
  126. package/src/Menu/index.native.ts +3 -0
  127. package/src/Menu/index.ts +3 -0
  128. package/src/Menu/index.web.ts +3 -0
  129. package/src/Menu/types.ts +30 -0
  130. package/src/Popover/Popover.native.tsx +102 -32
  131. package/src/Popover/Popover.styles.tsx +100 -67
  132. package/src/Popover/Popover.web.tsx +36 -260
  133. package/src/Popover/index.ts +5 -2
  134. package/src/Popover/index.web.ts +5 -2
  135. package/src/Popover/types.ts +14 -13
  136. package/src/Pressable/Pressable.native.tsx +7 -6
  137. package/src/Pressable/Pressable.web.tsx +8 -6
  138. package/src/Pressable/index.ts +5 -2
  139. package/src/Pressable/index.web.ts +5 -2
  140. package/src/Pressable/types.ts +11 -10
  141. package/src/Progress/Progress.native.tsx +179 -0
  142. package/src/Progress/Progress.styles.tsx +164 -0
  143. package/src/Progress/Progress.web.tsx +144 -0
  144. package/src/Progress/index.native.ts +1 -0
  145. package/src/Progress/index.ts +5 -0
  146. package/src/Progress/index.web.ts +5 -0
  147. package/src/Progress/types.ts +21 -0
  148. package/src/RadioButton/RadioButton.native.tsx +88 -0
  149. package/src/RadioButton/RadioButton.styles.tsx +163 -0
  150. package/src/RadioButton/RadioButton.web.tsx +85 -0
  151. package/src/RadioButton/RadioGroup.native.tsx +43 -0
  152. package/src/RadioButton/RadioGroup.web.tsx +49 -0
  153. package/src/RadioButton/index.native.ts +2 -0
  154. package/src/RadioButton/index.ts +2 -0
  155. package/src/RadioButton/index.web.ts +2 -0
  156. package/src/RadioButton/types.ts +29 -0
  157. package/src/SVGImage/SVGImage.native.tsx +9 -7
  158. package/src/SVGImage/SVGImage.styles.tsx +63 -55
  159. package/src/SVGImage/SVGImage.web.tsx +16 -13
  160. package/src/SVGImage/index.ts +5 -5
  161. package/src/SVGImage/index.web.ts +5 -2
  162. package/src/SVGImage/types.ts +7 -3
  163. package/src/Screen/Screen.native.tsx +43 -17
  164. package/src/Screen/Screen.styles.tsx +58 -54
  165. package/src/Screen/Screen.web.tsx +11 -5
  166. package/src/Screen/index.ts +5 -2
  167. package/src/Screen/index.web.ts +5 -2
  168. package/src/Screen/types.ts +23 -9
  169. package/src/Select/Select.native.tsx +347 -0
  170. package/src/Select/Select.styles.tsx +335 -0
  171. package/src/Select/Select.web.tsx +276 -0
  172. package/src/Select/index.native.ts +2 -0
  173. package/src/Select/index.ts +5 -0
  174. package/src/Select/index.web.ts +5 -0
  175. package/src/Select/types.ts +124 -0
  176. package/src/Skeleton/Skeleton.native.tsx +139 -0
  177. package/src/Skeleton/Skeleton.styles.tsx +59 -0
  178. package/src/Skeleton/Skeleton.web.tsx +112 -0
  179. package/src/Skeleton/index.native.ts +4 -0
  180. package/src/Skeleton/index.ts +5 -0
  181. package/src/Skeleton/index.web.ts +5 -0
  182. package/src/Skeleton/types.ts +75 -0
  183. package/src/Slider/Slider.native.tsx +248 -0
  184. package/src/Slider/Slider.styles.tsx +241 -0
  185. package/src/Slider/Slider.web.tsx +226 -0
  186. package/src/Slider/index.native.ts +3 -0
  187. package/src/Slider/index.ts +5 -0
  188. package/src/Slider/index.web.ts +5 -0
  189. package/src/Slider/types.ts +31 -0
  190. package/src/Switch/Switch.native.tsx +131 -0
  191. package/src/Switch/Switch.styles.tsx +169 -0
  192. package/src/Switch/Switch.web.tsx +121 -0
  193. package/src/Switch/index.native.ts +3 -0
  194. package/src/Switch/index.ts +5 -0
  195. package/src/Switch/index.web.ts +5 -0
  196. package/src/Switch/types.ts +21 -0
  197. package/src/TabBar/TabBar.native.tsx +142 -0
  198. package/src/TabBar/TabBar.styles.tsx +399 -0
  199. package/src/TabBar/TabBar.web.tsx +205 -0
  200. package/src/TabBar/index.native.tsx +3 -0
  201. package/src/TabBar/index.ts +3 -0
  202. package/src/TabBar/index.web.tsx +3 -0
  203. package/src/TabBar/types.ts +26 -0
  204. package/src/Table/Table.native.tsx +122 -0
  205. package/src/Table/Table.styles.tsx +283 -0
  206. package/src/Table/Table.web.tsx +112 -0
  207. package/src/Table/index.native.tsx +3 -0
  208. package/src/Table/index.ts +3 -0
  209. package/src/Table/index.web.tsx +3 -0
  210. package/src/Table/types.ts +28 -0
  211. package/src/Text/Text.native.tsx +12 -11
  212. package/src/Text/Text.styles.tsx +76 -64
  213. package/src/Text/Text.web.tsx +14 -9
  214. package/src/Text/index.ts +5 -5
  215. package/src/Text/index.web.ts +5 -3
  216. package/src/Text/types.ts +20 -13
  217. package/src/TextArea/TextArea.native.tsx +134 -0
  218. package/src/TextArea/TextArea.styles.tsx +175 -0
  219. package/src/TextArea/TextArea.web.tsx +156 -0
  220. package/src/TextArea/index.native.ts +3 -0
  221. package/src/TextArea/index.ts +3 -0
  222. package/src/TextArea/index.web.ts +3 -0
  223. package/src/TextArea/types.ts +30 -0
  224. package/src/Tooltip/Tooltip.native.tsx +165 -0
  225. package/src/Tooltip/Tooltip.styles.tsx +73 -0
  226. package/src/Tooltip/Tooltip.web.tsx +87 -0
  227. package/src/Tooltip/index.native.ts +3 -0
  228. package/src/Tooltip/index.ts +3 -0
  229. package/src/Tooltip/types.ts +18 -0
  230. package/src/Video/Video.native.tsx +105 -0
  231. package/src/Video/Video.styles.tsx +39 -0
  232. package/src/Video/Video.web.tsx +115 -0
  233. package/src/Video/index.native.ts +5 -0
  234. package/src/Video/index.ts +5 -0
  235. package/src/Video/types.ts +29 -0
  236. package/src/View/View.native.tsx +9 -14
  237. package/src/View/View.styles.tsx +101 -93
  238. package/src/View/View.web.tsx +16 -17
  239. package/src/View/index.ts +5 -5
  240. package/src/View/index.web.ts +5 -3
  241. package/src/View/types.ts +29 -21
  242. package/src/examples/AccordionExamples.tsx +126 -0
  243. package/src/examples/AlertExamples.tsx +280 -0
  244. package/src/examples/AvatarExamples.tsx +23 -23
  245. package/src/examples/BadgeExamples.tsx +109 -41
  246. package/src/examples/BreadcrumbExamples.tsx +312 -0
  247. package/src/examples/ButtonExamples.tsx +160 -33
  248. package/src/examples/CardExamples.tsx +40 -40
  249. package/src/examples/CheckboxExamples.tsx +12 -12
  250. package/src/examples/ChipExamples.tsx +197 -0
  251. package/src/examples/DialogExamples.tsx +22 -22
  252. package/src/examples/DividerExamples.tsx +49 -49
  253. package/src/examples/IconExamples.tsx +270 -54
  254. package/src/examples/ImageExamples.tsx +174 -0
  255. package/src/examples/InputExamples.tsx +75 -17
  256. package/src/examples/ListExamples.tsx +288 -0
  257. package/src/examples/MenuExamples.tsx +144 -0
  258. package/src/examples/PopoverExamples.tsx +69 -73
  259. package/src/examples/ProgressExamples.tsx +137 -0
  260. package/src/examples/RadioButtonExamples.tsx +161 -0
  261. package/src/examples/SVGImageExamples.tsx +19 -17
  262. package/src/examples/ScreenExamples.tsx +31 -31
  263. package/src/examples/SelectExamples.tsx +423 -0
  264. package/src/examples/SkeletonExamples.tsx +206 -0
  265. package/src/examples/SliderExamples.tsx +200 -0
  266. package/src/examples/SwitchExamples.tsx +182 -0
  267. package/src/examples/TabBarExamples.tsx +143 -0
  268. package/src/examples/TableExamples.tsx +280 -0
  269. package/src/examples/TextAreaExamples.tsx +173 -0
  270. package/src/examples/TextExamples.tsx +28 -32
  271. package/src/examples/ThemeExtensionExamples.tsx +10 -10
  272. package/src/examples/TooltipExamples.tsx +126 -0
  273. package/src/examples/VideoExamples.tsx +144 -0
  274. package/src/examples/ViewExamples.tsx +64 -56
  275. package/src/examples/index.ts +18 -3
  276. package/src/hooks/useMergeRefs.ts +16 -0
  277. package/src/hooks/useSmartPosition.native.ts +169 -0
  278. package/src/index.native.ts +80 -9
  279. package/src/index.ts +75 -1
  280. package/src/internal/BoundedModalContent.native.tsx +58 -0
  281. package/src/internal/PositionedPortal.tsx +254 -0
  282. package/src/internal/SafeAreaDebugOverlay.native.tsx +173 -0
  283. package/src/unistyles.d.ts +6 -0
  284. package/src/utils/buildSizeVariants.ts +16 -0
  285. package/src/utils/deepMerge.ts +43 -0
  286. package/src/utils/positionUtils.native.ts +280 -0
  287. package/src/utils/styleHelpers.ts +48 -0
  288. package/LLM-ACCESS-GUIDE.md +0 -143
  289. package/src/ActivityIndicator/README.md +0 -132
  290. package/src/Avatar/README.md +0 -139
  291. package/src/Badge/README.md +0 -170
  292. package/src/Button/Button.types.ts +0 -12
  293. package/src/Button/README.md +0 -262
  294. package/src/Card/README.md +0 -258
  295. package/src/Checkbox/README.md +0 -102
  296. package/src/Dialog/README.md +0 -210
  297. package/src/Divider/README.md +0 -108
  298. package/src/Icon/README.md +0 -81
  299. package/src/Input/README.md +0 -100
  300. package/src/SVGImage/README.md +0 -209
  301. package/src/Screen/README.md +0 -86
  302. package/src/Text/README.md +0 -94
  303. package/src/View/README.md +0 -107
  304. package/src/examples/AllExamples.tsx +0 -84
  305. package/src/examples/README.md +0 -136
  306. package/src/examples/ValidationExamples.tsx +0 -95
  307. package/src/examples/extendedTheme.ts +0 -329
  308. package/src/theme/breakpoints.ts +0 -8
  309. package/src/theme/colorResolver.ts +0 -218
  310. package/src/theme/colors.ts +0 -315
  311. package/src/theme/defaultThemes.ts +0 -326
  312. package/src/theme/index.ts +0 -188
  313. package/src/theme/themeBuilder.ts +0 -602
  314. package/src/theme/unistyles.d.ts +0 -6
  315. package/src/theme/variantHelpers.ts +0 -584
  316. package/src/theme/variants.ts +0 -56
@@ -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
+ };