@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,200 +1,263 @@
1
1
  import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme, Size, CompoundVariants} from '@idealyst/theme';
3
+ import { buildSizeVariants } from '../utils/buildSizeVariants';
4
+ import { InputSize, InputType } from './types';
2
5
 
3
- export const inputStyles = StyleSheet.create((theme) => ({
4
- input: {
5
- variants: {
6
- size: {
7
- small: {
8
- height: 36,
9
- paddingHorizontal: theme.spacing?.sm || 8,
10
- paddingVertical: theme.spacing?.xs || 4,
11
- fontSize: theme.typography?.fontSize?.sm || 14,
12
- },
13
- medium: {
14
- height: 44,
15
- paddingHorizontal: theme.spacing?.md || 12,
16
- paddingVertical: theme.spacing?.sm || 8,
17
- fontSize: theme.typography?.fontSize?.md || 16,
18
- },
19
- large: {
20
- height: 52,
21
- paddingHorizontal: theme.spacing?.lg || 16,
22
- paddingVertical: theme.spacing?.md || 12,
23
- fontSize: theme.typography?.fontSize?.lg || 18,
24
- },
25
- },
26
- variant: {
27
- default: {
28
- backgroundColor: theme.colors?.surface?.primary || '#ffffff',
29
- borderWidth: 1,
30
- borderColor: theme.colors?.border?.primary || '#e5e7eb',
31
- borderStyle: 'solid',
32
- color: theme.colors?.text?.primary || '#000000',
33
-
34
- _web: {
35
- border: `1px solid ${theme.colors?.border?.primary || '#e5e7eb'}`,
36
- },
37
- },
6
+
7
+ export type InputVariants = {
8
+ size: InputSize;
9
+ type: InputType;
10
+ focused: boolean;
11
+ hasError: boolean;
12
+ disabled: boolean;
13
+ }
14
+
15
+ /**
16
+ * Create type variants for container
17
+ */
18
+ function createContainerTypeVariants(theme: Theme) {
19
+ return {
38
20
  outlined: {
39
- backgroundColor: 'transparent',
40
- borderWidth: 1,
41
- borderColor: theme.intents?.primary?.main || '#3b82f6',
42
- borderStyle: 'solid',
43
- color: theme.colors?.text?.primary || '#000000',
44
-
45
- _web: {
46
- border: `1px solid ${theme.intents?.primary?.main || '#3b82f6'}`,
47
- },
21
+ backgroundColor: 'transparent',
22
+ borderWidth: 1,
23
+ borderColor: theme.colors.border.primary,
24
+ borderStyle: 'solid' as const,
25
+ _web: {
26
+ border: `1px solid ${theme.colors.border.primary}`,
27
+ },
48
28
  },
49
29
  filled: {
50
- backgroundColor: theme.colors?.surface?.secondary || '#f9fafb',
51
- borderWidth: 0,
52
- borderColor: 'transparent',
53
- color: theme.colors?.text?.primary || '#000000',
54
-
55
- _web: {
56
- border: 'none',
57
- },
30
+ backgroundColor: theme.colors.surface.secondary,
31
+ borderWidth: 0,
32
+ _web: {
33
+ border: 'none',
34
+ },
58
35
  },
59
36
  bare: {
60
- backgroundColor: 'transparent',
61
- borderWidth: 0,
62
- borderColor: 'transparent',
63
- color: theme.colors?.text?.primary || '#000000',
64
- paddingHorizontal: 0,
65
- paddingVertical: 0,
66
-
67
- _web: {
68
- border: 'none',
69
- boxShadow: 'none',
70
- },
37
+ backgroundColor: 'transparent',
38
+ borderWidth: 0,
39
+ _web: {
40
+ border: 'none',
41
+ },
71
42
  },
72
- },
73
- focused: {
74
- true: {
75
- // Base focused styles - will be overridden by compound variants
76
- },
77
- false: {
78
- // No additional styles when not focused
79
- },
80
- },
81
- },
82
-
83
- compoundVariants: [
84
- // Default variant focus
85
- {
86
- variant: 'default',
43
+ } as const;
44
+ }
45
+
46
+ /**
47
+ * Create compound variants for focused + type + hasError combinations
48
+ */
49
+ function createFocusedCompoundVariants(theme: Theme) {
50
+ const compoundVariants = [] as CompoundVariants<keyof InputVariants>;
51
+ const focusColor = theme.intents.primary.primary;
52
+ const errorColor = theme.intents.error.primary;
53
+
54
+ // Error state takes precedence
55
+ compoundVariants.push({
87
56
  focused: true,
57
+ hasError: true,
88
58
  styles: {
89
- borderColor: theme.intents?.primary?.main || '#3b82f6',
90
- shadowColor: theme.intents?.primary?.main || '#3b82f6',
91
- shadowOffset: { width: 0, height: 0 },
92
- shadowOpacity: 0.1,
93
- shadowRadius: 2,
94
- elevation: 1,
95
- },
96
- },
97
- // Outlined variant focus
98
- {
99
- variant: 'outlined',
59
+ borderColor: errorColor,
60
+ _web: {
61
+ border: `1px solid ${errorColor}`,
62
+ boxShadow: `0 0 0 2px ${errorColor}20`,
63
+ },
64
+ },
65
+ });
66
+
67
+ // Default type + focused (no error)
68
+ compoundVariants.push({
69
+ type: 'default',
100
70
  focused: true,
71
+ hasError: false,
101
72
  styles: {
102
- backgroundColor: 'transparent',
103
- borderWidth: 1,
104
- borderColor: theme.intents?.primary?.main || '#3b82f6',
105
- borderStyle: 'solid',
106
- },
107
- },
108
- // Filled variant focus
109
- {
110
- variant: 'filled',
73
+ borderColor: focusColor,
74
+ _web: {
75
+ border: `1px solid ${focusColor}`,
76
+ boxShadow: `0 0 0 2px ${focusColor}20`,
77
+ },
78
+ },
79
+ });
80
+
81
+ // Outlined type + focused (no error)
82
+ compoundVariants.push({
83
+ type: 'outlined',
111
84
  focused: true,
85
+ hasError: false,
112
86
  styles: {
113
- backgroundColor: theme.colors?.surface?.secondary || '#f3f4f6',
114
- borderWidth: 0,
115
- shadowColor: theme.intents?.primary?.main || '#3b82f6',
116
- shadowOffset: { width: 0, height: 0 },
117
- shadowOpacity: 0.05,
118
- shadowRadius: 1,
119
- elevation: 0.5,
120
- },
121
- },
122
- // Bare variant focus (no visual changes)
123
- {
124
- variant: 'bare',
87
+ borderColor: focusColor,
88
+ _web: {
89
+ border: `2px solid ${focusColor}`,
90
+ },
91
+ },
92
+ });
93
+
94
+ // Filled type + focused (no error)
95
+ compoundVariants.push({
96
+ type: 'filled',
125
97
  focused: true,
98
+ hasError: false,
126
99
  styles: {
127
- backgroundColor: 'transparent',
128
- borderWidth: 0,
129
- borderColor: 'transparent',
130
- },
131
- },
132
- ],
133
-
134
- borderRadius: theme.borderRadius?.md || 8,
135
- fontWeight: theme.typography?.fontWeight?.regular || '400',
136
- // Web-specific styles
137
- _web: {
138
- boxSizing: 'border-box',
139
- outline: 'none',
140
- transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
141
- fontFamily: 'inherit',
142
- _focus: {
143
- borderColor: theme.intents?.primary?.main || '#3b82f6',
144
- },
145
- _hover: {
146
- borderColor: theme.intents?.primary?.main || '#3b82f6',
147
- },
100
+ _web: {
101
+ boxShadow: `0 0 0 2px ${focusColor}20`,
102
+ },
103
+ },
104
+ });
105
+
106
+ return compoundVariants;
107
+ }
108
+
109
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
110
+ // transform on native cannot resolve function calls to extract variant structures.
111
+ export const inputStyles = StyleSheet.create((theme: Theme) => {
112
+ return {
113
+ container: {
114
+ display: 'flex',
115
+ flexDirection: 'row',
116
+ alignItems: 'center',
117
+ width: '100%',
118
+ borderRadius: 8,
119
+ variants: {
120
+ size: buildSizeVariants(theme, 'input', (size) => ({
121
+ height: size.height,
122
+ paddingHorizontal: size.paddingHorizontal,
123
+ })),
124
+ type: createContainerTypeVariants(theme),
125
+ focused: {
126
+ true: {},
127
+ false: {},
128
+ },
129
+ hasError: {
130
+ true: {
131
+ borderColor: theme.intents.error.primary,
132
+ _web: {
133
+ border: `1px solid ${theme.intents.error.primary}`,
134
+ },
135
+ },
136
+ false: {},
137
+ },
138
+ disabled: {
139
+ true: {
140
+ opacity: 0.6,
141
+ backgroundColor: theme.colors.surface.secondary,
142
+ _web: {
143
+ cursor: 'not-allowed',
144
+ },
145
+ },
146
+ false: {
147
+ _web: {
148
+ cursor: 'text',
149
+ _hover: {
150
+ borderColor: theme.intents.primary.primary,
151
+ },
152
+ },
153
+ },
154
+ },
155
+ },
156
+ compoundVariants: createFocusedCompoundVariants(theme),
157
+ _web: {
158
+ boxSizing: 'border-box',
159
+ transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
160
+ },
148
161
  },
149
- },
150
- disabled: {
151
- opacity: 0.6,
152
- backgroundColor: theme.colors?.surface?.secondary || '#f3f4f6',
153
- color: theme.colors?.text?.disabled || '#9ca3af',
154
- _web: {
155
- cursor: 'not-allowed',
162
+ leftIconContainer: {
163
+ display: 'flex',
164
+ alignItems: 'center',
165
+ justifyContent: 'center',
166
+ flexShrink: 0,
167
+ variants: {
168
+ size: buildSizeVariants(theme, 'input', (size) => ({
169
+ marginRight: size.iconMargin,
170
+ })),
171
+ },
156
172
  },
157
- },
158
- error: {
159
- borderWidth: 1,
160
- borderColor: theme.intents?.error?.main || '#ef4444',
161
- borderStyle: 'solid',
162
-
163
- variants: {
164
- focused: {
165
- true: {
166
- shadowColor: theme.intents?.error?.main || '#ef4444',
167
- shadowOffset: { width: 0, height: 0 },
168
- shadowOpacity: 0.1,
169
- shadowRadius: 2,
170
- elevation: 1,
171
- },
172
- false: {
173
- // No additional styles when not focused
174
- },
175
- },
176
- variant: {
177
- default: {
178
- // Use default error styles
173
+ rightIconContainer: {
174
+ display: 'flex',
175
+ alignItems: 'center',
176
+ justifyContent: 'center',
177
+ flexShrink: 0,
178
+ variants: {
179
+ size: buildSizeVariants(theme, 'input', (size) => ({
180
+ marginLeft: size.iconMargin,
181
+ })),
179
182
  },
180
- outlined: {
181
- // Override outlined styles for error
182
- borderColor: theme.intents?.error?.main || '#ef4444',
183
+ },
184
+ leftIcon: {
185
+ color: theme.colors.text.secondary,
186
+ variants: {
187
+ size: buildSizeVariants(theme, 'input', (size) => ({
188
+ fontSize: size.iconSize,
189
+ width: size.iconSize,
190
+ height: size.iconSize,
191
+ })),
183
192
  },
184
- filled: {
185
- // Add border for error even in filled variant
186
- borderWidth: 1,
187
- borderColor: theme.intents?.error?.main || '#ef4444',
193
+ },
194
+ rightIcon: {
195
+ display: 'flex',
196
+ alignItems: 'center',
197
+ justifyContent: 'center',
198
+ flexShrink: 0,
199
+ color: theme.colors.text.secondary,
200
+ variants: {
201
+ size: buildSizeVariants(theme, 'input', (size) => ({
202
+ fontSize: size.iconSize,
203
+ width: size.iconSize,
204
+ height: size.iconSize,
205
+ })),
188
206
  },
189
- },
190
207
  },
191
-
192
- _web: {
193
- border: `1px solid ${theme.intents?.error?.main || '#ef4444'}`,
194
- _focus: {
195
- borderColor: theme.intents?.error?.main || '#ef4444',
196
- boxShadow: `0 0 0 2px ${theme.intents?.error?.main || '#ef4444'}20`,
197
- },
208
+ passwordToggle: {
209
+ display: 'flex',
210
+ alignItems: 'center',
211
+ justifyContent: 'center',
212
+ flexShrink: 0,
213
+ padding: 0,
214
+ variants: {
215
+ size: buildSizeVariants(theme, 'input', (size) => ({
216
+ marginLeft: size.iconMargin,
217
+ })),
218
+ },
219
+ _web: {
220
+ background: 'transparent',
221
+ border: 'none',
222
+ cursor: 'pointer',
223
+ _hover: {
224
+ opacity: 0.7,
225
+ },
226
+ _active: {
227
+ opacity: 0.5,
228
+ },
229
+ },
230
+ },
231
+ passwordToggleIcon: {
232
+ display: 'flex',
233
+ alignItems: 'center',
234
+ justifyContent: 'center',
235
+ flexShrink: 0,
236
+ color: theme.colors.text.secondary,
237
+ variants: {
238
+ size: buildSizeVariants(theme, 'input', (size) => ({
239
+ fontSize: size.iconSize,
240
+ width: size.iconSize,
241
+ height: size.iconSize,
242
+ })),
243
+ },
244
+ },
245
+ input: {
246
+ flex: 1,
247
+ minWidth: 0,
248
+ backgroundColor: 'transparent',
249
+ color: theme.colors.text.primary,
250
+ fontWeight: '400',
251
+ variants: {
252
+ size: buildSizeVariants(theme, 'input', (size) => ({
253
+ fontSize: size.fontSize,
254
+ })),
255
+ },
256
+ _web: {
257
+ border: 'none',
258
+ outline: 'none',
259
+ fontFamily: 'inherit',
260
+ },
198
261
  },
199
- },
200
- }));
262
+ };
263
+ });
@@ -1,7 +1,10 @@
1
- import React from 'react';
1
+ import React, { useState, isValidElement, useRef } from 'react';
2
2
  import { getWebProps } from 'react-native-unistyles/web';
3
3
  import { InputProps } from './types';
4
4
  import { inputStyles } from './Input.styles';
5
+ import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
+ import { resolveIconPath, isIconName } from '../Icon/icon-resolver';
7
+ import useMergeRefs from '../hooks/useMergeRefs';
5
8
 
6
9
  const Input = React.forwardRef<HTMLInputElement, InputProps>(({
7
10
  value,
@@ -12,22 +15,37 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
12
15
  disabled = false,
13
16
  inputType = 'text',
14
17
  secureTextEntry = false,
18
+ leftIcon,
19
+ rightIcon,
20
+ showPasswordToggle,
15
21
  autoCapitalize = 'sentences',
16
- size = 'medium',
17
- variant = 'default',
22
+ size = 'md',
23
+ type = 'outlined',
18
24
  hasError = false,
19
25
  style,
20
26
  testID,
21
27
  }, ref) => {
28
+ const [isPasswordVisible, setIsPasswordVisible] = useState(false);
29
+
30
+ // Determine if we should show password toggle
31
+ const isPasswordField = inputType === 'password' || secureTextEntry;
32
+ const shouldShowPasswordToggle = isPasswordField && (showPasswordToggle !== false);
33
+
34
+ const [isFocused, setIsFocused] = useState(false);
22
35
 
23
36
  const getInputType = () => {
37
+ // Handle password visibility
38
+ if (isPasswordField && !isPasswordVisible) {
39
+ return 'password';
40
+ }
41
+
24
42
  switch (inputType) {
25
43
  case 'email':
26
44
  return 'email';
27
45
  case 'number':
28
46
  return 'number';
29
47
  case 'password':
30
- return 'password';
48
+ return 'text'; // When visible
31
49
  case 'text':
32
50
  default:
33
51
  return 'text';
@@ -41,59 +59,144 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
41
59
  };
42
60
 
43
61
  const handleFocus = () => {
62
+ setIsFocused(true);
44
63
  if (onFocus) {
45
64
  onFocus();
46
65
  }
47
66
  };
48
67
 
49
68
  const handleBlur = () => {
69
+ setIsFocused(false);
50
70
  if (onBlur) {
51
71
  onBlur();
52
72
  }
53
73
  };
54
74
 
55
- // Apply variants using the correct Unistyles 3.0 pattern
75
+ const togglePasswordVisibility = () => {
76
+ setIsPasswordVisible(!isPasswordVisible);
77
+ };
78
+
79
+ // Apply variants for container
56
80
  inputStyles.useVariants({
57
- size: size as 'small' | 'medium' | 'large',
58
- variant: variant as 'default' | 'outlined' | 'filled' | 'bare',
81
+ size,
82
+ type,
83
+ focused: isFocused,
84
+ hasError,
85
+ disabled,
59
86
  });
60
87
 
61
- // Create the style array following the official documentation pattern
62
- const inputStyleArray = [
63
- inputStyles.input,
64
- disabled && inputStyles.disabled,
65
- hasError && inputStyles.error,
66
- style,
67
- ].filter(Boolean);
68
-
69
- // Use getWebProps for Unistyles, then manually add our ref
70
- const { ref: unistylesRef, ...webProps } = getWebProps(inputStyleArray);
71
-
72
- // Forward the ref while still providing unistyles with access
73
- const handleRef = (r: HTMLInputElement | null) => {
74
- unistylesRef.current = r;
75
- if (typeof ref === 'function') {
76
- ref(r);
77
- } else if (ref) {
78
- ref.current = r;
88
+ // Get web props for all styled elements
89
+ const containerProps = getWebProps([inputStyles.container, style]);
90
+ const leftIconContainerProps = getWebProps([inputStyles.leftIconContainer]);
91
+ const rightIconContainerProps = getWebProps([inputStyles.rightIconContainer]);
92
+ const leftIconProps = getWebProps([inputStyles.leftIcon]);
93
+ const rightIconProps = getWebProps([inputStyles.rightIcon]);
94
+ const passwordToggleProps = getWebProps([inputStyles.passwordToggle]);
95
+ const passwordToggleIconProps = getWebProps([inputStyles.passwordToggleIcon]);
96
+
97
+ // Get input props
98
+ const inputWebProps = getWebProps([inputStyles.input]);
99
+
100
+ // Merge the forwarded ref with unistyles ref for the input
101
+ const mergedInputRef = useMergeRefs(ref, inputWebProps.ref);
102
+
103
+ // Helper to render left icon
104
+ const renderLeftIcon = () => {
105
+ if (!leftIcon) return null;
106
+
107
+ if (isIconName(leftIcon)) {
108
+ const iconPath = resolveIconPath(leftIcon);
109
+ return (
110
+ <IconSvg
111
+ path={iconPath}
112
+ {...leftIconProps}
113
+ aria-label={leftIcon}
114
+ />
115
+ );
116
+ } else if (isValidElement(leftIcon)) {
117
+ return <span {...leftIconProps}>{leftIcon}</span>;
79
118
  }
119
+
120
+ return null;
121
+ };
122
+
123
+ // Helper to render right icon (not password toggle)
124
+ const renderRightIcon = () => {
125
+ if (!rightIcon) return null;
126
+
127
+ if (isIconName(rightIcon)) {
128
+ const iconPath = resolveIconPath(rightIcon);
129
+ return (
130
+ <IconSvg
131
+ path={iconPath}
132
+ {...rightIconProps}
133
+ aria-label={rightIcon}
134
+ />
135
+ );
136
+ } else if (isValidElement(rightIcon)) {
137
+ return <span {...rightIconProps}>{rightIcon}</span>;
138
+ }
139
+
140
+ return null;
141
+ };
142
+
143
+ // Helper to render password toggle icon
144
+ const renderPasswordToggleIcon = () => {
145
+ const iconName = isPasswordVisible ? 'eye-off' : 'eye';
146
+ const iconPath = resolveIconPath(iconName);
147
+ return (
148
+ <IconSvg
149
+ path={iconPath}
150
+ {...passwordToggleIconProps}
151
+ aria-label={iconName}
152
+ />
153
+ );
80
154
  };
81
155
 
82
156
  return (
83
- <input
84
- {...webProps}
85
- ref={handleRef}
86
- type={secureTextEntry ? 'password' : getInputType()}
87
- value={value}
88
- onChange={handleChange}
89
- onFocus={handleFocus}
90
- onBlur={handleBlur}
91
- placeholder={placeholder}
92
- disabled={disabled}
93
- autoCapitalize={autoCapitalize}
94
- data-testid={testID}
95
- />
157
+ <div {...containerProps} data-testid={testID}>
158
+ {/* Left Icon */}
159
+ {leftIcon && (
160
+ <span {...leftIconContainerProps}>
161
+ {renderLeftIcon()}
162
+ </span>
163
+ )}
164
+
165
+ {/* Input */}
166
+ <input
167
+ {...inputWebProps}
168
+ ref={mergedInputRef}
169
+ type={getInputType()}
170
+ value={value}
171
+ onChange={handleChange}
172
+ onFocus={handleFocus}
173
+ onBlur={handleBlur}
174
+ placeholder={placeholder}
175
+ disabled={disabled}
176
+ autoCapitalize={autoCapitalize}
177
+ />
178
+
179
+ {/* Right Icon or Password Toggle */}
180
+ {shouldShowPasswordToggle ? (
181
+ <button
182
+ {...passwordToggleProps}
183
+ onClick={togglePasswordVisibility}
184
+ disabled={disabled}
185
+ aria-label={isPasswordVisible ? 'Hide password' : 'Show password'}
186
+ type="button"
187
+ tabIndex={-1}
188
+ >
189
+ {renderPasswordToggleIcon()}
190
+ </button>
191
+ ) : rightIcon ? (
192
+ <span {...rightIconContainerProps}>
193
+ {renderRightIcon()}
194
+ </span>
195
+ ) : null}
196
+ </div>
96
197
  );
97
198
  });
98
199
 
200
+ Input.displayName = 'Input';
201
+
99
202
  export default Input;
@@ -1,5 +1,5 @@
1
- // Platform-agnostic Input export
2
- // Bundlers will resolve to index.web.ts (web) or index.native.ts (React Native)
3
- // This file serves as fallback for web environments
4
- export { default } from './Input.web';
5
- export * from './types';
1
+ import InputComponent from './Input.web';
2
+
3
+ export default InputComponent;
4
+ export { InputComponent as Input };
5
+ export * from './types';