@idealyst/components 1.1.7 → 1.1.8

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 (105) hide show
  1. package/package.json +3 -3
  2. package/src/Accordion/Accordion.native.tsx +8 -6
  3. package/src/Accordion/Accordion.styles.old.tsx +298 -0
  4. package/src/Accordion/Accordion.styles.tsx +102 -236
  5. package/src/ActivityIndicator/ActivityIndicator.styles.old.tsx +94 -0
  6. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +44 -74
  7. package/src/Alert/Alert.native.tsx +16 -6
  8. package/src/Alert/Alert.styles.old.tsx +209 -0
  9. package/src/Alert/Alert.styles.tsx +67 -149
  10. package/src/Avatar/Avatar.styles.old.tsx +99 -0
  11. package/src/Avatar/Avatar.styles.tsx +35 -80
  12. package/src/Badge/Badge.styles.old.tsx +157 -0
  13. package/src/Badge/Badge.styles.tsx +61 -121
  14. package/src/Breadcrumb/Breadcrumb.styles.old.tsx +231 -0
  15. package/src/Breadcrumb/Breadcrumb.styles.tsx +83 -200
  16. package/src/Breadcrumb/Breadcrumb.web.tsx +28 -23
  17. package/src/Button/Button.styles.tsx +89 -141
  18. package/src/Card/Card.native.tsx +7 -11
  19. package/src/Card/Card.styles.old.tsx +160 -0
  20. package/src/Card/Card.styles.tsx +105 -142
  21. package/src/Card/Card.web.tsx +5 -4
  22. package/src/Checkbox/Checkbox.native.tsx +9 -5
  23. package/src/Checkbox/Checkbox.styles.old.tsx +271 -0
  24. package/src/Checkbox/Checkbox.styles.tsx +104 -216
  25. package/src/Checkbox/Checkbox.web.tsx +6 -6
  26. package/src/Chip/Chip.styles.old.tsx +184 -0
  27. package/src/Chip/Chip.styles.tsx +34 -72
  28. package/src/Dialog/Dialog.native.tsx +7 -4
  29. package/src/Dialog/Dialog.styles.old.tsx +202 -0
  30. package/src/Dialog/Dialog.styles.tsx +69 -133
  31. package/src/Divider/Divider.styles.old.tsx +172 -0
  32. package/src/Divider/Divider.styles.tsx +62 -84
  33. package/src/Icon/Icon.native.tsx +8 -8
  34. package/src/Icon/Icon.styles.old.tsx +81 -0
  35. package/src/Icon/Icon.styles.tsx +52 -66
  36. package/src/Icon/Icon.web.tsx +43 -7
  37. package/src/Icon/IconSvg/IconSvg.web.tsx +2 -0
  38. package/src/Image/Image.styles.old.tsx +69 -0
  39. package/src/Image/Image.styles.tsx +46 -60
  40. package/src/Input/Input.native.tsx +138 -53
  41. package/src/Input/Input.styles.old.tsx +289 -0
  42. package/src/Input/Input.styles.tsx +127 -198
  43. package/src/List/List.native.tsx +5 -2
  44. package/src/List/List.styles.old.tsx +242 -0
  45. package/src/List/List.styles.tsx +179 -215
  46. package/src/List/ListItem.native.tsx +12 -6
  47. package/src/List/ListItem.web.tsx +23 -13
  48. package/src/Menu/Menu.styles.old.tsx +197 -0
  49. package/src/Menu/Menu.styles.tsx +68 -150
  50. package/src/Menu/MenuItem.native.tsx +5 -3
  51. package/src/Menu/MenuItem.styles.old.tsx +114 -0
  52. package/src/Menu/MenuItem.styles.tsx +57 -89
  53. package/src/Menu/MenuItem.web.tsx +8 -3
  54. package/src/Popover/Popover.native.tsx +10 -4
  55. package/src/Popover/Popover.styles.old.tsx +135 -0
  56. package/src/Popover/Popover.styles.tsx +51 -112
  57. package/src/Pressable/Pressable.styles.old.tsx +27 -0
  58. package/src/Pressable/Pressable.styles.tsx +35 -27
  59. package/src/Progress/Progress.styles.old.tsx +200 -0
  60. package/src/Progress/Progress.styles.tsx +75 -164
  61. package/src/RadioButton/RadioButton.native.tsx +4 -3
  62. package/src/RadioButton/RadioButton.styles.old.tsx +175 -0
  63. package/src/RadioButton/RadioButton.styles.tsx +83 -154
  64. package/src/RadioButton/RadioButton.web.tsx +2 -2
  65. package/src/SVGImage/SVGImage.styles.old.tsx +86 -0
  66. package/src/SVGImage/SVGImage.styles.tsx +35 -78
  67. package/src/Screen/Screen.native.tsx +18 -25
  68. package/src/Screen/Screen.styles.old.tsx +87 -0
  69. package/src/Screen/Screen.styles.tsx +105 -67
  70. package/src/Screen/Screen.web.tsx +1 -1
  71. package/src/Select/Select.native.tsx +42 -33
  72. package/src/Select/Select.styles.old.tsx +353 -0
  73. package/src/Select/Select.styles.tsx +223 -292
  74. package/src/Skeleton/Skeleton.styles.old.tsx +67 -0
  75. package/src/Skeleton/Skeleton.styles.tsx +29 -53
  76. package/src/Slider/Slider.styles.old.tsx +259 -0
  77. package/src/Slider/Slider.styles.tsx +153 -234
  78. package/src/Switch/Switch.native.tsx +7 -5
  79. package/src/Switch/Switch.styles.old.tsx +203 -0
  80. package/src/Switch/Switch.styles.tsx +101 -174
  81. package/src/Switch/Switch.web.tsx +5 -5
  82. package/src/TabBar/TabBar.native.tsx +3 -2
  83. package/src/TabBar/TabBar.styles.old.tsx +343 -0
  84. package/src/TabBar/TabBar.styles.tsx +145 -279
  85. package/src/Table/Table.native.tsx +18 -9
  86. package/src/Table/Table.styles.old.tsx +311 -0
  87. package/src/Table/Table.styles.tsx +152 -286
  88. package/src/Text/Text.native.tsx +1 -3
  89. package/src/Text/Text.style.demo.tsx +16 -0
  90. package/src/Text/Text.styles.old.tsx +219 -0
  91. package/src/Text/Text.styles.tsx +94 -84
  92. package/src/Text/Text.web.tsx +2 -2
  93. package/src/Text/index.ts +1 -0
  94. package/src/TextArea/TextArea.styles.old.tsx +213 -0
  95. package/src/TextArea/TextArea.styles.tsx +93 -181
  96. package/src/Tooltip/Tooltip.styles.old.tsx +82 -0
  97. package/src/Tooltip/Tooltip.styles.tsx +32 -56
  98. package/src/Video/Video.styles.old.tsx +51 -0
  99. package/src/Video/Video.styles.tsx +32 -44
  100. package/src/View/View.native.tsx +12 -14
  101. package/src/View/View.styles.old.tsx +125 -0
  102. package/src/View/View.styles.tsx +76 -106
  103. package/src/View/View.web.tsx +1 -0
  104. package/src/examples/CardExamples.tsx +0 -6
  105. package/src/extensions/extendComponent.ts +61 -0
@@ -3,14 +3,16 @@ import MdiIcon from '@mdi/react';
3
3
  import { IconProps } from './types';
4
4
  import { iconStyles } from './Icon.styles';
5
5
  import { getWebProps } from 'react-native-unistyles/web';
6
+ import { useUnistyles } from 'react-native-unistyles';
6
7
  import useMergeRefs from '../hooks/useMergeRefs';
8
+ import { getColorFromString, Intent, Color } from '@idealyst/theme';
7
9
 
8
10
  // Internal props that include the transformed path from Babel plugin
9
11
  interface InternalIconProps extends IconProps {
10
12
  path?: string; // Added by Babel plugin transformation
11
13
  }
12
14
 
13
- const Icon = forwardRef<HTMLDivElement, IconProps>((props: InternalIconProps, ref) => {
15
+ const Icon = forwardRef<HTMLSpanElement, IconProps>((props: InternalIconProps, ref) => {
14
16
  const {
15
17
  name,
16
18
  size = 'md',
@@ -23,26 +25,60 @@ const Icon = forwardRef<HTMLDivElement, IconProps>((props: InternalIconProps, re
23
25
  ...restProps
24
26
  } = props;
25
27
 
28
+ const { theme } = useUnistyles();
29
+
26
30
  // Check if we have a path prop (from Babel plugin transformation)
27
31
  const { path } = restProps as { path?: string };
28
- const iconProps = getWebProps(iconStyles.icon({ intent, color, size }));
32
+
33
+ // Compute size from theme
34
+ let iconSize: number;
35
+ if (typeof size === 'number') {
36
+ iconSize = size;
37
+ } else {
38
+ const themeSize = theme.sizes.icon[size as keyof typeof theme.sizes.icon];
39
+ iconSize = typeof themeSize === 'number' ? themeSize : (themeSize?.width ?? 24);
40
+ }
41
+
42
+ // Compute color from intent or color prop or default
43
+ const iconColor = intent
44
+ ? theme.intents[intent as Intent]?.primary
45
+ : color
46
+ ? getColorFromString(theme, color as Color)
47
+ : theme.colors.text.primary;
48
+
49
+ // Use getWebProps for className generation but override with computed values
50
+ const iconStyle = (iconStyles.icon as any)({ intent, color, size });
51
+ const iconProps = getWebProps([iconStyle, style]);
29
52
 
30
53
  const mergedRef = useMergeRefs(ref, iconProps.ref);
31
54
 
32
55
  // Use MDI React icon when path is provided (transformed by Babel plugin)
33
56
  return (
34
- <div
57
+ <span
35
58
  {...iconProps}
36
59
  ref={mergedRef}
37
- id={id}>
60
+ id={id}
61
+ style={{
62
+ ...iconProps.style,
63
+ fontSize: iconSize,
64
+ width: '1em',
65
+ height: '1em',
66
+ display: 'inline-flex',
67
+ alignItems: 'center',
68
+ justifyContent: 'center',
69
+ flexShrink: 0,
70
+ lineHeight: 1,
71
+ color: iconColor,
72
+ }}
73
+ >
38
74
  <MdiIcon
39
75
  path={path}
40
- size={'100%'}
41
- color={'currentColor'}
76
+ size="1em"
77
+ color="currentColor"
42
78
  data-testid={testID}
43
79
  aria-label={accessibilityLabel || name}
44
80
  />
45
- </div>
81
+ </span>
46
82
  );
47
83
  });
48
84
 
@@ -19,6 +19,7 @@ interface IconSvgProps {
19
19
 
20
20
  export const IconSvg: React.FC<IconSvgProps> = ({
21
21
  path,
22
+ size = '1em',
22
23
  color = 'currentColor',
23
24
  style,
24
25
  'aria-label': ariaLabel,
@@ -29,6 +30,7 @@ export const IconSvg: React.FC<IconSvgProps> = ({
29
30
  <MdiIcon
30
31
  style={style}
31
32
  path={path}
33
+ size={size}
32
34
  color={color}
33
35
  aria-label={ariaLabel}
34
36
  data-testid={testID}
@@ -0,0 +1,69 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme, StylesheetStyles} from '@idealyst/theme';
3
+ import { applyExtensions } from '../extensions/applyExtension';
4
+
5
+ type ImageVariants = {}
6
+
7
+ export type ExpandedImageStyles = StylesheetStyles<keyof ImageVariants>;
8
+
9
+ export type ImageStylesheet = {
10
+ container: ExpandedImageStyles;
11
+ image: ExpandedImageStyles;
12
+ placeholder: ExpandedImageStyles;
13
+ fallback: ExpandedImageStyles;
14
+ loadingIndicator: ExpandedImageStyles;
15
+ }
16
+
17
+ // Style creators for extension support
18
+ function createContainerStyles(theme: Theme) {
19
+ return () => ({
20
+ position: 'relative' as const,
21
+ overflow: 'hidden' as const,
22
+ backgroundColor: theme.colors['gray.200'],
23
+ });
24
+ }
25
+
26
+ function createImageStyles() {
27
+ return () => ({
28
+ width: '100%' as const,
29
+ height: '100%' as const,
30
+ });
31
+ }
32
+
33
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
34
+ // transform on native cannot resolve function calls to extract variant structures.
35
+ // @ts-ignore - TS language server needs restart to pick up theme structure changes
36
+ export const imageStyles = StyleSheet.create((theme: Theme) => {
37
+ // Apply extensions to main visual elements
38
+
39
+ return applyExtensions('Image', theme, {container: createContainerStyles(theme),
40
+ image: createImageStyles(),
41
+ // Additional styles (merged from return)
42
+ // Minor utility styles (not extended)
43
+ placeholder: {
44
+ position: 'absolute',
45
+ top: 0,
46
+ left: 0,
47
+ right: 0,
48
+ bottom: 0,
49
+ display: 'flex',
50
+ alignItems: 'center',
51
+ justifyContent: 'center',
52
+ backgroundColor: theme.colors['gray.200'],
53
+ },
54
+ fallback: {
55
+ position: 'absolute',
56
+ top: 0,
57
+ left: 0,
58
+ right: 0,
59
+ bottom: 0,
60
+ display: 'flex',
61
+ alignItems: 'center',
62
+ justifyContent: 'center',
63
+ backgroundColor: theme.colors['gray.300'],
64
+ color: theme.colors['gray.600'],
65
+ },
66
+ loadingIndicator: {
67
+ color: theme.colors['gray.600'],
68
+ }});
69
+ });
@@ -1,73 +1,59 @@
1
+ /**
2
+ * Image styles using defineStyle.
3
+ */
1
4
  import { StyleSheet } from 'react-native-unistyles';
2
- import { Theme, StylesheetStyles} from '@idealyst/theme';
3
- import { applyExtensions } from '../extensions/applyExtension';
5
+ import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
6
+ import type { Theme as BaseTheme } from '@idealyst/theme';
4
7
 
5
- type ImageVariants = {}
8
+ // Required: Unistyles must see StyleSheet usage in original source to process this file
9
+ void StyleSheet;
6
10
 
7
- export type ExpandedImageStyles = StylesheetStyles<keyof ImageVariants>;
11
+ // Wrap theme for $iterator support
12
+ type Theme = ThemeStyleWrapper<BaseTheme>;
8
13
 
9
- export type ImageStylesheet = {
10
- container: ExpandedImageStyles;
11
- image: ExpandedImageStyles;
12
- placeholder: ExpandedImageStyles;
13
- fallback: ExpandedImageStyles;
14
- loadingIndicator: ExpandedImageStyles;
15
- }
14
+ export type ImageDynamicProps = {};
16
15
 
17
- // Style creators for extension support
18
- function createContainerStyles(theme: Theme) {
19
- return () => ({
16
+ /**
17
+ * Image styles - simple container and image styles.
18
+ */
19
+ export const imageStyles = defineStyle('Image', (theme: Theme) => ({
20
+ container: (_props: ImageDynamicProps) => ({
20
21
  position: 'relative' as const,
21
22
  overflow: 'hidden' as const,
22
23
  backgroundColor: theme.colors['gray.200'],
23
- });
24
- }
24
+ }),
25
25
 
26
- function createImageStyles() {
27
- return () => ({
26
+ image: (_props: ImageDynamicProps) => ({
28
27
  width: '100%' as const,
29
28
  height: '100%' as const,
30
- });
31
- }
29
+ }),
32
30
 
33
- // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
34
- // transform on native cannot resolve function calls to extract variant structures.
35
- // @ts-ignore - TS language server needs restart to pick up theme structure changes
36
- export const imageStyles = StyleSheet.create((theme: Theme) => {
37
- // Apply extensions to main visual elements
38
- const extended = applyExtensions('Image', theme, {
39
- container: createContainerStyles(theme),
40
- image: createImageStyles(),
41
- });
31
+ placeholder: (_props: ImageDynamicProps) => ({
32
+ position: 'absolute' as const,
33
+ top: 0,
34
+ left: 0,
35
+ right: 0,
36
+ bottom: 0,
37
+ display: 'flex' as const,
38
+ alignItems: 'center' as const,
39
+ justifyContent: 'center' as const,
40
+ backgroundColor: theme.colors['gray.200'],
41
+ }),
42
+
43
+ fallback: (_props: ImageDynamicProps) => ({
44
+ position: 'absolute' as const,
45
+ top: 0,
46
+ left: 0,
47
+ right: 0,
48
+ bottom: 0,
49
+ display: 'flex' as const,
50
+ alignItems: 'center' as const,
51
+ justifyContent: 'center' as const,
52
+ backgroundColor: theme.colors['gray.300'],
53
+ color: theme.colors['gray.600'],
54
+ }),
42
55
 
43
- return {
44
- ...extended,
45
- // Minor utility styles (not extended)
46
- placeholder: {
47
- position: 'absolute',
48
- top: 0,
49
- left: 0,
50
- right: 0,
51
- bottom: 0,
52
- display: 'flex',
53
- alignItems: 'center',
54
- justifyContent: 'center',
55
- backgroundColor: theme.colors['gray.200'],
56
- },
57
- fallback: {
58
- position: 'absolute',
59
- top: 0,
60
- left: 0,
61
- right: 0,
62
- bottom: 0,
63
- display: 'flex',
64
- alignItems: 'center',
65
- justifyContent: 'center',
66
- backgroundColor: theme.colors['gray.300'],
67
- color: theme.colors['gray.600'],
68
- },
69
- loadingIndicator: {
70
- color: theme.colors['gray.600'],
71
- },
72
- };
73
- });
56
+ loadingIndicator: (_props: ImageDynamicProps) => ({
57
+ color: theme.colors['gray.600'],
58
+ }),
59
+ }));
@@ -1,10 +1,55 @@
1
- import React, { useState, isValidElement, useMemo } from 'react';
2
- import { View, TextInput, TouchableOpacity } from 'react-native';
1
+ import React, { useState, isValidElement, useMemo, useEffect, useRef, useCallback } from 'react';
2
+ import { View, TextInput, TouchableOpacity, Platform, TextInputProps } from 'react-native';
3
3
  import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
4
+ import { useUnistyles } from 'react-native-unistyles';
4
5
  import { InputProps } from './types';
5
6
  import { inputStyles } from './Input.styles';
6
7
  import { getNativeFormAccessibilityProps } from '../utils/accessibility';
7
8
 
9
+ // Inner TextInput component that can be memoized to prevent re-renders
10
+ // for Android secure text entry
11
+ type InnerTextInputProps = {
12
+ inputRef: React.ForwardedRef<TextInput>;
13
+ value: string | undefined;
14
+ onChangeText: ((text: string) => void) | undefined;
15
+ isAndroidSecure: boolean;
16
+ textInputProps: Omit<TextInputProps, 'value' | 'defaultValue' | 'onChangeText'>;
17
+ inputStyle: any;
18
+ };
19
+
20
+ const InnerTextInput = React.memo<InnerTextInputProps>(
21
+ ({ inputRef, value, onChangeText, isAndroidSecure, textInputProps, inputStyle }) => {
22
+ return (
23
+ <TextInput
24
+ ref={inputRef}
25
+ // For Android secure text entry, don't pass value prop at all
26
+ // Let TextInput manage its own state to preserve character reveal animation
27
+ {...(isAndroidSecure ? {} : { value })}
28
+ onChangeText={onChangeText}
29
+ style={inputStyle}
30
+ {...textInputProps}
31
+ />
32
+ );
33
+ },
34
+ (prevProps, nextProps) => {
35
+ // For Android secure text entry, skip re-renders when only value changes
36
+ if (nextProps.isAndroidSecure) {
37
+ // Only re-render if non-value props change
38
+ const valueChanged = prevProps.value !== nextProps.value;
39
+ const otherPropsChanged =
40
+ prevProps.onChangeText !== nextProps.onChangeText ||
41
+ prevProps.isAndroidSecure !== nextProps.isAndroidSecure ||
42
+ prevProps.textInputProps !== nextProps.textInputProps ||
43
+ prevProps.inputStyle !== nextProps.inputStyle;
44
+
45
+ if (valueChanged && !otherPropsChanged) {
46
+ return true; // Skip re-render
47
+ }
48
+ }
49
+ return false; // Allow re-render
50
+ }
51
+ );
52
+
8
53
  const Input = React.forwardRef<TextInput, InputProps>(({
9
54
  value,
10
55
  onChangeText,
@@ -41,11 +86,30 @@ const Input = React.forwardRef<TextInput, InputProps>(({
41
86
  const [isFocused, setIsFocused] = useState(false);
42
87
  const [isPasswordVisible, setIsPasswordVisible] = useState(false);
43
88
 
89
+ // Track if this is a secure field that needs Android workaround
90
+ const isSecureField = inputType === 'password' || secureTextEntry;
91
+ const needsAndroidSecureWorkaround = Platform.OS === 'android' && isSecureField && !isPasswordVisible;
92
+
93
+ // For Android secure text entry, we use an internal ref to track value
94
+ const internalValueRef = useRef(value ?? '');
95
+
96
+ // Sync external value changes to internal ref (for programmatic updates)
97
+ useEffect(() => {
98
+ if (value !== undefined) {
99
+ internalValueRef.current = value;
100
+ }
101
+ }, [value]);
102
+
103
+ // Get theme for icon sizes and colors
104
+ const { theme } = useUnistyles();
105
+ const iconSize = theme.sizes.input[size].iconSize;
106
+ const iconColor = theme.colors.text.secondary;
107
+
44
108
  // Determine if we should show password toggle
45
109
  const isPasswordField = inputType === 'password' || secureTextEntry;
46
110
  const shouldShowPasswordToggle = isPasswordField && (showPasswordToggle !== false);
47
111
 
48
- const getKeyboardType = () => {
112
+ const getKeyboardType = useCallback((): 'default' | 'email-address' | 'numeric' => {
49
113
  switch (inputType) {
50
114
  case 'email':
51
115
  return 'email-address';
@@ -56,42 +120,34 @@ const Input = React.forwardRef<TextInput, InputProps>(({
56
120
  default:
57
121
  return 'default';
58
122
  }
59
- };
123
+ }, [inputType]);
60
124
 
61
- const handleFocus = () => {
125
+ const handleFocus = useCallback(() => {
62
126
  setIsFocused(true);
63
- if (onFocus) {
64
- onFocus();
65
- }
66
- };
127
+ onFocus?.();
128
+ }, [onFocus]);
67
129
 
68
- const handlePress = () => {
69
- if (onPress) {
70
- onPress();
71
- }
72
- }
130
+ const handlePress = useCallback(() => {
131
+ onPress?.();
132
+ }, [onPress]);
73
133
 
74
- const handleBlur = () => {
134
+ const handleBlur = useCallback(() => {
75
135
  setIsFocused(false);
76
- if (onBlur) {
77
- onBlur();
78
- }
79
- };
136
+ onBlur?.();
137
+ }, [onBlur]);
80
138
 
81
139
  const togglePasswordVisibility = () => {
82
140
  setIsPasswordVisible(!isPasswordVisible);
83
141
  };
84
142
 
85
- // Apply variants to the stylesheet (for size and spacing)
86
- inputStyles.useVariants({
87
- size,
88
- margin,
89
- marginVertical,
90
- marginHorizontal,
91
- });
143
+ // Memoized change handler for InnerTextInput
144
+ const handleChangeText = useCallback((text: string) => {
145
+ internalValueRef.current = text;
146
+ onChangeText?.(text);
147
+ }, [onChangeText]);
92
148
 
93
- // Compute dynamic container styles
94
- const containerStyle = (inputStyles.container as any)({ type, focused: isFocused, hasError, disabled });
149
+ // Memoized input style
150
+ const inputStyle = useMemo(() => (inputStyles.input as any)({}), []);
95
151
 
96
152
  // Generate native accessibility props
97
153
  const nativeA11yProps = useMemo(() => {
@@ -119,17 +175,55 @@ const Input = React.forwardRef<TextInput, InputProps>(({
119
175
  hasError,
120
176
  ]);
121
177
 
178
+ // Memoized TextInput props (everything except value/onChangeText)
179
+ const textInputProps = useMemo(() => ({
180
+ onPress: handlePress,
181
+ placeholder,
182
+ editable: !disabled,
183
+ keyboardType: getKeyboardType(),
184
+ secureTextEntry: isSecureField && !isPasswordVisible,
185
+ autoCapitalize,
186
+ onFocus: handleFocus,
187
+ onBlur: handleBlur,
188
+ placeholderTextColor: '#999999',
189
+ ...nativeA11yProps,
190
+ }), [
191
+ handlePress,
192
+ placeholder,
193
+ disabled,
194
+ getKeyboardType,
195
+ isSecureField,
196
+ isPasswordVisible,
197
+ autoCapitalize,
198
+ handleFocus,
199
+ handleBlur,
200
+ nativeA11yProps,
201
+ ]);
202
+
203
+ // Apply variants to the stylesheet (for size and spacing)
204
+ inputStyles.useVariants({
205
+ size,
206
+ margin,
207
+ marginVertical,
208
+ marginHorizontal,
209
+ });
210
+
211
+ // Compute dynamic styles - call as functions for theme reactivity
212
+ const containerStyle = (inputStyles.container as any)({ type, focused: isFocused, hasError, disabled });
213
+ const leftIconContainerStyle = (inputStyles.leftIconContainer as any)({});
214
+ const rightIconContainerStyle = (inputStyles.rightIconContainer as any)({});
215
+ const passwordToggleStyle = (inputStyles.passwordToggle as any)({});
216
+
122
217
  // Helper to render left icon
123
218
  const renderLeftIcon = () => {
124
219
  if (!leftIcon) return null;
125
220
 
126
221
  if (typeof leftIcon === 'string') {
127
- const iconStyle = inputStyles.leftIcon;
128
222
  return (
129
223
  <MaterialCommunityIcons
130
224
  name={leftIcon}
131
- size={iconStyle.width}
132
- color={iconStyle.color}
225
+ size={iconSize}
226
+ color={iconColor}
133
227
  />
134
228
  );
135
229
  } else if (isValidElement(leftIcon)) {
@@ -144,12 +238,11 @@ const Input = React.forwardRef<TextInput, InputProps>(({
144
238
  if (!rightIcon) return null;
145
239
 
146
240
  if (typeof rightIcon === 'string') {
147
- const iconStyle = inputStyles.rightIcon;
148
241
  return (
149
242
  <MaterialCommunityIcons
150
243
  name={rightIcon}
151
- size={iconStyle.width}
152
- color={iconStyle.color}
244
+ size={iconSize}
245
+ color={iconColor}
153
246
  />
154
247
  );
155
248
  } else if (isValidElement(rightIcon)) {
@@ -163,45 +256,37 @@ const Input = React.forwardRef<TextInput, InputProps>(({
163
256
  <View style={[containerStyle, style]} testID={testID} nativeID={id}>
164
257
  {/* Left Icon */}
165
258
  {leftIcon && (
166
- <View style={inputStyles.leftIconContainer}>
259
+ <View style={leftIconContainerStyle}>
167
260
  {renderLeftIcon()}
168
261
  </View>
169
262
  )}
170
263
 
171
264
  {/* Input */}
172
- <TextInput
173
- onPress={handlePress}
174
- ref={ref}
265
+ <InnerTextInput
266
+ inputRef={ref}
175
267
  value={value}
176
- onChangeText={onChangeText}
177
- placeholder={placeholder}
178
- editable={!disabled}
179
- keyboardType={getKeyboardType()}
180
- secureTextEntry={(secureTextEntry || inputType === 'password') && !isPasswordVisible}
181
- autoCapitalize={autoCapitalize}
182
- onFocus={handleFocus}
183
- onBlur={handleBlur}
184
- style={(inputStyles.input as any)({})}
185
- placeholderTextColor="#999999"
186
- {...nativeA11yProps}
268
+ onChangeText={handleChangeText}
269
+ isAndroidSecure={needsAndroidSecureWorkaround}
270
+ inputStyle={inputStyle}
271
+ textInputProps={textInputProps}
187
272
  />
188
273
 
189
274
  {/* Right Icon or Password Toggle */}
190
275
  {shouldShowPasswordToggle ? (
191
276
  <TouchableOpacity
192
- style={inputStyles.passwordToggle}
277
+ style={passwordToggleStyle}
193
278
  onPress={togglePasswordVisibility}
194
279
  disabled={disabled}
195
280
  accessibilityLabel={isPasswordVisible ? 'Hide password' : 'Show password'}
196
281
  >
197
282
  <MaterialCommunityIcons
198
283
  name={isPasswordVisible ? 'eye-off' : 'eye'}
199
- size={inputStyles.passwordToggleIcon.width}
200
- color={inputStyles.passwordToggleIcon.color}
284
+ size={iconSize}
285
+ color={iconColor}
201
286
  />
202
287
  </TouchableOpacity>
203
288
  ) : rightIcon ? (
204
- <View style={inputStyles.rightIconContainer}>
289
+ <View style={rightIconContainerStyle}>
205
290
  {renderRightIcon()}
206
291
  </View>
207
292
  ) : null}