@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
@@ -1,215 +1,258 @@
1
1
  import { StyleSheet } from 'react-native-unistyles';
2
- import { generateIntentVariants, generateButtonCompoundVariants } from '../theme/variantHelpers';
3
-
4
- export const buttonStyles = StyleSheet.create((theme) => ({
5
- button: {
6
- // Base styles - no borders defined here
7
- alignItems: 'center',
8
- justifyContent: 'center',
9
- borderRadius: theme.borderRadius?.md || 8,
10
- fontWeight: '600',
11
- textAlign: 'center',
12
- transition: 'all 0.2s ease',
13
- // Set default color to prevent fallback to theme.colors.text.placeholder
14
- color: theme.intents?.primary?.on || '#ffffff',
15
-
16
- // All variants defined here
17
- variants: {
18
- size: {
19
- small: {
20
- paddingHorizontal: theme.spacing?.sm || 8,
21
- paddingVertical: theme.spacing?.xs || 4,
22
- minHeight: 24,
23
- },
24
- medium: {
25
- paddingHorizontal: theme.spacing?.md || 12,
26
- paddingVertical: theme.spacing?.sm || 8,
27
- minHeight: 32,
28
- },
29
- large: {
30
- paddingHorizontal: theme.spacing?.lg || 16,
31
- paddingVertical: theme.spacing?.md || 12,
32
- minHeight: 40,
33
- },
34
- },
35
- // Dynamically generated intent variants
36
- intent: generateIntentVariants(theme),
37
- variant: {
2
+ import { Theme, Intent, Size, CompoundVariants} from '@idealyst/theme';
3
+ import { buildSizeVariants } from '../utils/buildSizeVariants';
4
+
5
+ type ButtonSize = Size;
6
+ type ButtonIntent = Intent;
7
+ type ButtonType = 'contained' | 'outlined' | 'text';
8
+
9
+ export type ButtonVariants = {
10
+ size: ButtonSize;
11
+ intent: ButtonIntent;
12
+ type: ButtonType;
13
+ disabled: boolean;
14
+ }
15
+
16
+ /**
17
+ * Create intent variants (placeholder, colors handled by compound variants)
18
+ */
19
+ function createIntentVariants(theme: Theme) {
20
+ const variants: any = {};
21
+ for (const intent in theme.intents) {
22
+ variants[intent] = {};
23
+ }
24
+ return variants;
25
+ }
26
+
27
+ /**
28
+ * Create type variants (structure only, colors handled by compound variants)
29
+ */
30
+ function createTypeVariants(theme: Theme) {
31
+ return {
38
32
  contained: {
39
- // Contained buttons have no border
40
- border: 'none',
33
+ borderWidth: 0,
41
34
  },
42
35
  outlined: {
43
- backgroundColor: 'transparent',
44
- // Border is defined in compound variants
36
+ borderWidth: 2,
37
+ borderStyle: 'solid' ,
38
+ backgroundColor: 'transparent',
45
39
  },
46
40
  text: {
47
- backgroundColor: 'transparent',
48
- border: 'none',
49
- },
50
- },
51
- disabled: {
52
- true: {
53
- opacity: 0.6,
54
- },
55
- false: {
56
- opacity: 1,
57
- },
58
- },
59
- },
60
-
61
- // Dynamically generated compound variants for outlined and text variants
62
- compoundVariants: generateButtonCompoundVariants(theme),
63
-
64
- // Web-specific styles
65
- _web: {
66
- cursor: 'pointer',
67
- outline: 'none',
68
- display: 'flex',
69
- boxSizing: 'border-box',
70
- userSelect: 'none',
71
- // Fix for Chromium-based browsers (Brave, Chrome) border rendering
72
- WebkitAppearance: 'none',
73
- MozAppearance: 'none',
74
- appearance: 'none',
75
- backfaceVisibility: 'hidden',
76
- WebkitBackfaceVisibility: 'hidden',
77
- transform: 'translateZ(0)', // Force hardware acceleration
78
- _hover: {
79
- opacity: 0.9,
80
- },
81
- _active: {
82
- transform: 'scale(0.98) translateZ(0)', // Maintain hardware acceleration
83
- },
84
- _focus: {
85
- outlineOffset: '2px',
86
- },
87
- },
88
- },
89
-
90
- // Separate text style for React Native (no borders, just text properties)
91
- text: {
92
- fontWeight: '600',
93
- textAlign: 'center',
94
-
95
- variants: {
96
- size: {
97
- small: {
98
- fontSize: 14,
99
- },
100
- medium: {
101
- fontSize: 16,
102
- },
103
- large: {
104
- fontSize: 18,
105
- },
106
- },
107
- intent: {
108
- primary: {
109
- color: theme.intents?.primary?.on || '#ffffff',
110
- },
111
- success: {
112
- color: theme.intents?.success?.on || '#ffffff',
113
- },
114
- error: {
115
- color: theme.intents?.error?.on || '#ffffff',
116
- },
117
- warning: {
118
- color: theme.intents?.warning?.on || '#ffffff',
119
- },
120
- neutral: {
121
- color: theme.intents?.neutral?.on || '#ffffff',
122
- },
123
- },
124
- variant: {
125
- contained: {},
126
- outlined: {},
127
- text: {},
128
- },
129
- disabled: {
130
- true: {
131
- opacity: 0.6,
132
- },
133
- false: {
134
- opacity: 1,
135
- },
136
- },
137
- },
138
-
139
- // Compound variants for text colors in different variants
140
- compoundVariants: [
141
- // Outlined variant text colors
142
- {
143
- variant: 'outlined',
144
- intent: 'primary',
145
- styles: {
146
- color: theme.intents?.primary?.main || '#3b82f6',
147
- },
148
- },
149
- {
150
- variant: 'outlined',
151
- intent: 'success',
152
- styles: {
153
- color: theme.intents?.success?.main || '#22c55e',
154
- },
155
- },
156
- {
157
- variant: 'outlined',
158
- intent: 'error',
159
- styles: {
160
- color: theme.intents?.error?.main || '#ef4444',
161
- },
162
- },
163
- {
164
- variant: 'outlined',
165
- intent: 'warning',
166
- styles: {
167
- color: theme.intents?.warning?.main || '#f59e0b',
168
- },
169
- },
170
- {
171
- variant: 'outlined',
172
- intent: 'neutral',
173
- styles: {
174
- color: theme.intents?.neutral?.main || '#6b7280',
175
- },
176
- },
177
- // Text variant text colors
178
- {
179
- variant: 'text',
180
- intent: 'primary',
181
- styles: {
182
- color: theme.intents?.primary?.main || '#3b82f6',
41
+ borderWidth: 0,
42
+ backgroundColor: 'transparent',
183
43
  },
184
- },
185
- {
186
- variant: 'text',
187
- intent: 'success',
188
- styles: {
189
- color: theme.intents?.success?.main || '#22c55e',
190
- },
191
- },
192
- {
193
- variant: 'text',
194
- intent: 'error',
195
- styles: {
196
- color: theme.intents?.error?.main || '#ef4444',
44
+ } as const;
45
+ }
46
+
47
+ /**
48
+ * Create compound variants for intent+type combinations
49
+ */
50
+ function createButtonCompoundVariants(theme: Theme): CompoundVariants<keyof ButtonVariants> {
51
+ const compoundVariants: CompoundVariants<keyof ButtonVariants> = [];
52
+
53
+ for (const intent in theme.intents) {
54
+ const intentValue = theme.intents[intent];
55
+
56
+ // Contained + intent
57
+ compoundVariants.push({
58
+ intent,
59
+ type: 'contained',
60
+ styles: {
61
+ backgroundColor: intentValue.primary,
62
+ color: intentValue.contrast,
63
+ },
64
+ });
65
+
66
+ // Outlined + intent
67
+ compoundVariants.push({
68
+ intent,
69
+ type: 'outlined',
70
+ styles: {
71
+ color: intentValue.primary,
72
+ borderColor: intentValue.primary,
73
+ },
74
+ });
75
+
76
+ // Text + intent
77
+ compoundVariants.push({
78
+ intent,
79
+ type: 'text',
80
+ styles: {
81
+ color: intentValue.primary,
82
+ },
83
+ });
84
+ };
85
+
86
+ return compoundVariants;
87
+ }
88
+
89
+ /**
90
+ * Create icon compound variants for intent+type combinations
91
+ */
92
+ function createIconCompoundVariants(theme: Theme): CompoundVariants<keyof ButtonVariants> {
93
+ const compoundVariants: CompoundVariants<keyof ButtonVariants> = [];
94
+
95
+ for (const intent in theme.intents) {
96
+ const intentValue = theme.intents[intent as Intent];
97
+
98
+ // Contained + intent
99
+ compoundVariants.push({
100
+ intent,
101
+ type: 'contained',
102
+ styles: { color: intentValue.contrast },
103
+ });
104
+
105
+ // Outlined + intent
106
+ compoundVariants.push({
107
+ intent,
108
+ type: 'outlined',
109
+ styles: { color: intentValue.primary },
110
+ });
111
+
112
+ // Text + intent
113
+ compoundVariants.push({
114
+ intent,
115
+ type: 'text',
116
+ styles: { color: intentValue.primary },
117
+ });
118
+ }
119
+
120
+ return compoundVariants;
121
+ }
122
+
123
+ /**
124
+ * Create icon color variants dynamically based on theme, intent, and type
125
+ */
126
+ function createIconColorVariants(theme: Theme, intent: Intent) {
127
+ const intentValue = theme.intents[intent];
128
+
129
+ return {
130
+ contained: {
131
+ color: intentValue.contrast,
197
132
  },
198
- },
199
- {
200
- variant: 'text',
201
- intent: 'warning',
202
- styles: {
203
- color: theme.intents?.warning?.main || '#f59e0b',
133
+ outlined: {
134
+ color: intentValue.primary,
204
135
  },
205
- },
206
- {
207
- variant: 'text',
208
- intent: 'neutral',
209
- styles: {
210
- color: theme.intents?.neutral?.main || '#6b7280',
136
+ text: {
137
+ color: intentValue.primary,
211
138
  },
212
- },
213
- ],
214
- },
215
- }));
139
+ } as const;
140
+ }
141
+
142
+ /**
143
+ * Generate button icon styles
144
+ */
145
+ const createButtonIconStyles = (theme: Theme) => {
146
+ return ({ intent }: Partial<ButtonVariants>) => {
147
+ return {
148
+ display: 'flex',
149
+ alignItems: 'center',
150
+ justifyContent: 'center',
151
+ variants: {
152
+ size: buildSizeVariants(theme, 'button', size => ({
153
+ width: size.iconSize,
154
+ height: size.iconSize,
155
+ })),
156
+ type: createIconColorVariants(theme, intent),
157
+ },
158
+ } as const;
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Generate button text styles
164
+ */
165
+ const createButtonTextStyles = (theme: Theme) => {
166
+ return ({ intent }: Partial<ButtonVariants>) => {
167
+ return {
168
+ fontWeight: '600',
169
+ textAlign: 'center',
170
+ variants: {
171
+ size: buildSizeVariants(theme, 'button', size => ({
172
+ fontSize: size.fontSize,
173
+ })),
174
+ type: createIconColorVariants(theme, intent), // Text uses same colors as icons
175
+ disabled: {
176
+ true: { opacity: 0.6 },
177
+ false: { opacity: 1 },
178
+ },
179
+ },
180
+ } as const;
181
+ };
182
+ }
183
+
184
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
185
+ // transform on native cannot resolve function calls to extract variant structures.
186
+ export const buttonStyles = StyleSheet.create((theme: Theme) => {
187
+ return {
188
+ button: {
189
+ alignItems: 'center',
190
+ justifyContent: 'center',
191
+ borderRadius: 8,
192
+ fontWeight: '600',
193
+ textAlign: 'center',
194
+ _web: {
195
+ display: 'flex',
196
+ transition: 'all 0.1s ease',
197
+ },
198
+ variants: {
199
+ size: buildSizeVariants(theme, 'button', size => ({
200
+ paddingVertical: size.paddingVertical,
201
+ paddingHorizontal: size.paddingHorizontal,
202
+ minHeight: size.minHeight,
203
+ })),
204
+ type: createTypeVariants(theme),
205
+ disabled: {
206
+ true: { opacity: 0.6 },
207
+ false: { opacity: 1, _web: {
208
+ cursor: 'pointer',
209
+ _hover: {
210
+ opacity: 0.90,
211
+ },
212
+ _active: {
213
+ opacity: 0.75,
214
+ },
215
+ } },
216
+ } as const,
217
+ } as const,
218
+ compoundVariants: createButtonCompoundVariants(theme),
219
+ } as const,
220
+ icon: {
221
+ display: 'flex',
222
+ alignItems: 'center',
223
+ justifyContent: 'center',
224
+ variants: {
225
+ size: buildSizeVariants(theme, 'button', size => ({
226
+ width: size.iconSize,
227
+ height: size.iconSize,
228
+ })),
229
+ intent: createIntentVariants(theme),
230
+ type: createTypeVariants(theme),
231
+ } as const,
232
+ compoundVariants: createIconCompoundVariants(theme),
233
+ } as const,
234
+ iconContainer: {
235
+ display: 'flex',
236
+ flexDirection: 'row',
237
+ alignItems: 'center',
238
+ justifyContent: 'center',
239
+ gap: 4,
240
+ } as const,
241
+ text: {
242
+ fontWeight: '600',
243
+ textAlign: 'center',
244
+ variants: {
245
+ size: buildSizeVariants(theme, 'button', size => ({
246
+ fontSize: size.fontSize,
247
+ })),
248
+ intent: createIntentVariants(theme),
249
+ type: createTypeVariants(theme),
250
+ disabled: {
251
+ true: { opacity: 0.6 },
252
+ false: { opacity: 1 },
253
+ },
254
+ },
255
+ compoundVariants: createIconCompoundVariants(theme), // Text uses same colors as icons
256
+ } as const,
257
+ };
258
+ });
@@ -1,56 +1,113 @@
1
- import React from 'react';
1
+ import React, { isValidElement, forwardRef } from 'react';
2
2
  import { getWebProps } from 'react-native-unistyles/web';
3
3
  import { ButtonProps } from './types';
4
4
  import { buttonStyles } from './Button.styles';
5
+ import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
+ import useMergeRefs from '../hooks/useMergeRefs';
7
+
8
+ // Extended props to include path props added by Babel plugin
9
+ interface InternalButtonProps extends ButtonProps {
10
+ leftIconPath?: string;
11
+ rightIconPath?: string;
12
+ }
13
+
14
+ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props: InternalButtonProps, ref) => {
15
+
16
+ const {
17
+ title,
18
+ children,
19
+ onPress,
20
+ disabled = false,
21
+ type = 'contained',
22
+ intent = 'primary',
23
+ size = 'md',
24
+ leftIcon,
25
+ rightIcon,
26
+ leftIconPath,
27
+ rightIconPath,
28
+ style,
29
+ testID,
30
+ } = props;
31
+
32
+ buttonStyles.useVariants({
33
+ type,
34
+ intent,
35
+ size,
36
+ disabled
37
+ });
5
38
 
6
- const Button: React.FC<ButtonProps> = ({
7
- title,
8
- children,
9
- onPress,
10
- disabled = false,
11
- variant = 'contained',
12
- intent = 'primary',
13
- size = 'medium',
14
- style,
15
- testID,
16
- }) => {
17
39
  const handleClick = () => {
18
40
  if (!disabled && onPress) {
19
41
  onPress();
20
42
  }
21
43
  };
22
44
 
23
- // Apply variants using the correct Unistyles 3.0 pattern
24
- buttonStyles.useVariants({
25
- size: size as 'small' | 'medium' | 'large',
26
- intent: intent as 'primary' | 'success' | 'error' | 'warning' | 'neutral',
27
- variant: variant as 'contained' | 'outlined' | 'text',
28
- disabled: disabled as boolean,
29
- });
30
-
31
- // Create the style array following the official documentation pattern
45
+ // Compute dynamic styles
32
46
  const buttonStyleArray = [
33
47
  buttonStyles.button,
34
- buttonStyles.text, // Include text styles for font sizing
35
- style,
48
+ buttonStyles.text,
49
+ style as any,
36
50
  ];
37
51
 
38
52
  // Use getWebProps to generate className and ref for web
39
53
  const webProps = getWebProps(buttonStyleArray);
40
54
 
55
+ // Icon container styles
56
+ const iconContainerProps = getWebProps([buttonStyles.iconContainer]);
57
+
58
+ // Icon styles with dynamic function
59
+ const iconStyleArray = [buttonStyles.icon];
60
+ const iconProps = getWebProps(iconStyleArray);
61
+
62
+ // Helper to render icon
63
+ const renderIcon = (icon: string | React.ReactNode, iconPath?: string) => {
64
+ if (typeof icon === 'string' && iconPath) {
65
+ // Render IconSvg directly with the path from Babel plugin
66
+ // Don't pass size - let the style control the dimensions
67
+ return (
68
+ <IconSvg
69
+ path={iconPath}
70
+ {...iconProps}
71
+ aria-label={icon}
72
+ />
73
+ );
74
+ } else if (isValidElement(icon)) {
75
+ // Render custom component as-is
76
+ return icon;
77
+ }
78
+ return null;
79
+ };
80
+
41
81
  // Use children if available, otherwise use title
42
82
  const buttonContent = children || title;
43
83
 
84
+ // Determine if we need to wrap content in icon container
85
+ const hasIcons = leftIcon || rightIcon;
86
+
87
+ // Merge unistyles web ref with forwarded ref
88
+ const mergedRef = useMergeRefs(ref, webProps.ref);
89
+
44
90
  return (
45
91
  <button
46
92
  {...webProps}
93
+ ref={mergedRef}
47
94
  onClick={handleClick}
48
95
  disabled={disabled}
49
96
  data-testid={testID}
50
97
  >
51
- {buttonContent}
98
+ {hasIcons ? (
99
+ <div {...iconContainerProps}>
100
+ {leftIcon && renderIcon(leftIcon, leftIconPath)}
101
+ {buttonContent}
102
+ {rightIcon && renderIcon(rightIcon, rightIconPath)}
103
+ </div>
104
+ ) : (
105
+ buttonContent
106
+ )}
52
107
  </button>
53
108
  );
54
- };
109
+ });
110
+
111
+ Button.displayName = 'Button';
55
112
 
56
113
  export default Button;
@@ -1,5 +1,5 @@
1
- // Platform-agnostic Button export
2
- // Metro will resolve to index.native.ts for React Native
3
- // This file serves as fallback for web environments
4
- export { default } from './Button.web';
5
- export * from './types';
1
+ import ButtonComponent from './Button.web';
2
+
3
+ export default ButtonComponent;
4
+ export { ButtonComponent as Button };
5
+ export * from './types';
@@ -1,3 +1,5 @@
1
- // Web-specific Button export
2
- export { default } from './Button.web';
3
- export * from './types';
1
+ import ButtonComponent from './Button.web';
2
+
3
+ export default ButtonComponent;
4
+ export { ButtonComponent as Button };
5
+ export * from './types';
@@ -1,47 +1,64 @@
1
- import { ReactNode } from 'react';
2
- import type { IntentVariant } from '../theme/variants';
1
+ import type { ReactNode } from 'react';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+ import type { IconName } from '../Icon/icon-types';
4
+ import { Intent, Size } from '@idealyst/theme';
5
+
6
+ // Component-specific type aliases for future extensibility
7
+ export type ButtonType = 'contained' | 'outlined' | 'text';
8
+ export type ButtonIntentVariant = Intent;
9
+ export type ButtonSizeVariant = Size;
3
10
 
4
11
  export interface ButtonProps {
5
12
  /**
6
13
  * The text or content to display inside the button
7
14
  */
8
15
  children?: ReactNode;
9
-
16
+
10
17
  /**
11
18
  * The text title to display inside the button (for web)
12
19
  */
13
20
  title?: string;
14
-
21
+
15
22
  /**
16
23
  * Called when the button is pressed
17
24
  */
18
25
  onPress?: () => void;
19
-
26
+
20
27
  /**
21
28
  * Whether the button is disabled
22
29
  */
23
30
  disabled?: boolean;
24
-
31
+
25
32
  /**
26
- * The visual style variant of the button
33
+ * The visual style type of the button
27
34
  */
28
- variant?: 'contained' | 'outlined' | 'text';
29
-
35
+ type?: ButtonType;
36
+
30
37
  /**
31
38
  * The intent/color scheme of the button
32
39
  */
33
- intent?: IntentVariant;
34
-
40
+ intent?: ButtonIntentVariant;
41
+
35
42
  /**
36
43
  * The size of the button
37
44
  */
38
- size?: 'small' | 'medium' | 'large';
39
-
45
+ size?: ButtonSizeVariant;
46
+
47
+ /**
48
+ * Icon to display on the left side. Can be an icon name or custom component (ReactNode)
49
+ */
50
+ leftIcon?: IconName | ReactNode;
51
+
52
+ /**
53
+ * Icon to display on the right side. Can be an icon name or custom component (ReactNode)
54
+ */
55
+ rightIcon?: IconName | ReactNode;
56
+
40
57
  /**
41
58
  * Additional styles (platform-specific)
42
59
  */
43
- style?: any;
44
-
60
+ style?: StyleProp<ViewStyle>;
61
+
45
62
  /**
46
63
  * Test ID for testing
47
64
  */