@idealyst/components 1.2.29 → 1.2.30

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 (131) hide show
  1. package/README.md +3 -3
  2. package/package.json +3 -3
  3. package/plugin/__tests__/web.test.ts +2 -2
  4. package/src/Accordion/Accordion.native.tsx +3 -2
  5. package/src/ActivityIndicator/ActivityIndicator.native.tsx +3 -2
  6. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +25 -26
  7. package/src/ActivityIndicator/ActivityIndicator.web.tsx +2 -1
  8. package/src/Alert/Alert.native.tsx +20 -10
  9. package/src/Alert/Alert.styles.tsx +148 -86
  10. package/src/Alert/Alert.web.tsx +10 -5
  11. package/src/Alert/types.ts +53 -3
  12. package/src/Avatar/Avatar.native.tsx +3 -2
  13. package/src/Avatar/Avatar.web.tsx +2 -1
  14. package/src/Avatar/types.ts +1 -1
  15. package/src/Badge/Badge.native.tsx +18 -6
  16. package/src/Badge/Badge.styles.tsx +22 -5
  17. package/src/Badge/Badge.web.tsx +12 -4
  18. package/src/Badge/types.ts +14 -2
  19. package/src/Breadcrumb/Breadcrumb.native.tsx +3 -2
  20. package/src/Button/Button.native.tsx +16 -6
  21. package/src/Button/Button.styles.tsx +2 -2
  22. package/src/Button/Button.web.tsx +19 -15
  23. package/src/Button/types.ts +6 -10
  24. package/src/Card/Card.native.tsx +27 -3
  25. package/src/Card/Card.web.tsx +30 -4
  26. package/src/Card/types.ts +15 -0
  27. package/src/Checkbox/Checkbox.native.tsx +5 -4
  28. package/src/Checkbox/Checkbox.styles.tsx +62 -52
  29. package/src/Checkbox/Checkbox.web.tsx +4 -3
  30. package/src/Checkbox/types.ts +1 -1
  31. package/src/Chip/Chip.native.tsx +30 -7
  32. package/src/Chip/Chip.styles.tsx +142 -124
  33. package/src/Chip/Chip.web.tsx +28 -5
  34. package/src/Chip/types.ts +15 -0
  35. package/src/Dialog/Dialog.native.tsx +6 -6
  36. package/src/Dialog/Dialog.web.tsx +5 -5
  37. package/src/Dialog/types.ts +2 -2
  38. package/src/Divider/Divider.native.tsx +20 -17
  39. package/src/Divider/Divider.styles.tsx +51 -29
  40. package/src/Divider/Divider.web.tsx +5 -4
  41. package/src/Divider/types.ts +3 -3
  42. package/src/Icon/Icon.native.tsx +3 -2
  43. package/src/Icon/Icon.web.tsx +2 -1
  44. package/src/Icon/IconSvg/IconSvg.native.tsx +3 -2
  45. package/src/Image/Image.native.tsx +3 -2
  46. package/src/Input/Input.native.tsx +42 -290
  47. package/src/Input/Input.styles.tsx +1 -1
  48. package/src/Input/Input.web.tsx +37 -288
  49. package/src/Input/index.native.ts +9 -2
  50. package/src/Input/index.ts +8 -1
  51. package/src/Input/index.web.ts +8 -1
  52. package/src/Input/types.ts +1 -1
  53. package/src/List/List.native.tsx +3 -2
  54. package/src/List/ListItem.native.tsx +3 -2
  55. package/src/List/ListSection.native.tsx +3 -2
  56. package/src/Menu/Menu.native.tsx +2 -1
  57. package/src/Menu/Menu.styles.tsx +79 -29
  58. package/src/Menu/Menu.web.tsx +2 -1
  59. package/src/Menu/MenuItem.native.tsx +4 -3
  60. package/src/Menu/MenuItem.styles.tsx +81 -32
  61. package/src/Menu/MenuItem.web.tsx +2 -1
  62. package/src/Menu/docs.ts +1 -1
  63. package/src/Popover/Popover.native.tsx +2 -1
  64. package/src/Popover/Popover.web.tsx +2 -1
  65. package/src/Popover/types.ts +15 -4
  66. package/src/Pressable/Pressable.native.tsx +3 -2
  67. package/src/Pressable/Pressable.web.tsx +3 -5
  68. package/src/Progress/Progress.native.tsx +5 -4
  69. package/src/Progress/Progress.web.tsx +3 -3
  70. package/src/Progress/types.ts +3 -3
  71. package/src/RadioButton/RadioButton.native.tsx +4 -3
  72. package/src/RadioButton/RadioButton.styles.tsx +53 -33
  73. package/src/RadioButton/RadioGroup.native.tsx +3 -2
  74. package/src/SVGImage/SVGImage.native.tsx +5 -4
  75. package/src/SVGImage/SVGImage.styles.tsx +44 -10
  76. package/src/SVGImage/SVGImage.web.tsx +2 -1
  77. package/src/Screen/Screen.native.tsx +2 -1
  78. package/src/Screen/Screen.web.tsx +2 -1
  79. package/src/Select/Select.native.tsx +6 -5
  80. package/src/Select/Select.styles.tsx +1 -1
  81. package/src/Select/Select.web.tsx +4 -3
  82. package/src/Select/types.ts +1 -1
  83. package/src/Skeleton/Skeleton.native.tsx +2 -1
  84. package/src/Slider/Slider.native.tsx +9 -8
  85. package/src/Slider/Slider.web.tsx +10 -9
  86. package/src/Slider/types.ts +9 -2
  87. package/src/Switch/Switch.native.tsx +7 -6
  88. package/src/Switch/Switch.styles.tsx +35 -17
  89. package/src/Switch/Switch.web.tsx +8 -7
  90. package/src/Switch/types.ts +44 -4
  91. package/src/TabBar/TabBar.native.tsx +3 -2
  92. package/src/Text/Text.native.tsx +3 -2
  93. package/src/Text/Text.web.tsx +2 -1
  94. package/src/TextArea/TextArea.native.tsx +3 -2
  95. package/src/TextArea/TextArea.styles.tsx +2 -2
  96. package/src/TextArea/TextArea.web.tsx +2 -1
  97. package/src/TextInput/TextInput.native.tsx +300 -0
  98. package/src/TextInput/TextInput.styles.tsx +207 -0
  99. package/src/TextInput/TextInput.web.tsx +301 -0
  100. package/src/TextInput/index.native.ts +3 -0
  101. package/src/TextInput/index.ts +5 -0
  102. package/src/TextInput/index.web.ts +5 -0
  103. package/src/TextInput/types.ts +163 -0
  104. package/src/Tooltip/Tooltip.native.tsx +3 -2
  105. package/src/Video/Video.native.tsx +4 -3
  106. package/src/View/View.native.tsx +2 -1
  107. package/src/View/View.web.tsx +2 -1
  108. package/src/examples/AlertExamples.tsx +5 -5
  109. package/src/examples/ButtonExamples.tsx +12 -12
  110. package/src/examples/CardExamples.tsx +1 -1
  111. package/src/examples/CheckboxExamples.tsx +2 -2
  112. package/src/examples/ChipExamples.tsx +6 -6
  113. package/src/examples/DialogExamples.tsx +1 -1
  114. package/src/examples/DividerExamples.tsx +1 -1
  115. package/src/examples/InputExamples.tsx +1 -1
  116. package/src/examples/LinkExamples.tsx +1 -1
  117. package/src/examples/ListExamples.tsx +1 -1
  118. package/src/examples/MenuExamples.tsx +2 -2
  119. package/src/examples/ProgressExamples.tsx +1 -1
  120. package/src/examples/RadioButtonExamples.tsx +5 -5
  121. package/src/examples/SVGImageExamples.tsx +1 -1
  122. package/src/examples/SelectExamples.tsx +1 -1
  123. package/src/examples/SliderExamples.tsx +5 -5
  124. package/src/examples/SwitchExamples.tsx +2 -2
  125. package/src/examples/TableExamples.tsx +1 -1
  126. package/src/examples/TooltipExamples.tsx +2 -2
  127. package/src/extensions/index.ts +1 -0
  128. package/src/extensions/types.ts +10 -3
  129. package/src/index.ts +23 -2
  130. package/src/utils/index.ts +12 -0
  131. package/src/utils/refTypes.ts +50 -0
@@ -1,297 +1,49 @@
1
- import React, { useState, isValidElement, useMemo, useEffect, useRef, useCallback } from 'react';
2
- import { View, TextInput, TouchableOpacity, Platform, TextInputProps } from 'react-native';
3
- import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons';
4
- import { useUnistyles } from 'react-native-unistyles';
5
- import { InputProps } from './types';
6
- import { inputStyles } from './Input.styles';
7
- import { getNativeFormAccessibilityProps } from '../utils/accessibility';
1
+ /**
2
+ * @ignore
3
+ * @deprecated Use TextInput instead. This component exists for backwards compatibility only.
4
+ */
5
+ import React, { useEffect, useRef } from 'react';
6
+ import TextInput from '../TextInput/TextInput.native';
7
+ import type { TextInputProps } from '../TextInput/types';
8
+ import type { IdealystElement } from '../utils/refTypes';
9
+
10
+ // Track if we've already logged the deprecation warning
11
+ let hasLoggedWarning = false;
12
+
13
+ /**
14
+ * @ignore
15
+ * @deprecated Use TextInput instead. Input is maintained for backwards compatibility only.
16
+ *
17
+ * Migration:
18
+ * - Replace `<Input />` with `<TextInput />`
19
+ * - Replace `inputType` prop with `inputMode` (React Native only)
20
+ *
21
+ * @example
22
+ * // Before
23
+ * import { Input } from '@idealyst/components';
24
+ * <Input inputType="email" />
25
+ *
26
+ * // After
27
+ * import { TextInput } from '@idealyst/components';
28
+ * <TextInput inputMode="email" />
29
+ */
30
+ const Input = React.forwardRef<IdealystElement, TextInputProps>((props, ref) => {
31
+ const hasRenderedRef = useRef(false);
8
32
 
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
-
53
- const Input = React.forwardRef<TextInput, InputProps>(({
54
- value,
55
- onChangeText,
56
- onFocus,
57
- onBlur,
58
- onPress,
59
- placeholder,
60
- disabled = false,
61
- inputType = 'text',
62
- secureTextEntry = false,
63
- leftIcon,
64
- rightIcon,
65
- showPasswordToggle,
66
- autoCapitalize = 'sentences',
67
- size = 'md',
68
- type = 'outlined',
69
- hasError = false,
70
- // Spacing variants from FormInputStyleProps
71
- margin,
72
- marginVertical,
73
- marginHorizontal,
74
- style,
75
- testID,
76
- id,
77
- // Accessibility props
78
- accessibilityLabel,
79
- accessibilityHint,
80
- accessibilityDisabled,
81
- accessibilityHidden,
82
- accessibilityRole,
83
- accessibilityRequired,
84
- accessibilityInvalid,
85
- }, ref) => {
86
- const [isFocused, setIsFocused] = useState(false);
87
- const [isPasswordVisible, setIsPasswordVisible] = useState(false);
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
33
  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
-
108
- // Determine if we should show password toggle
109
- const isPasswordField = inputType === 'password' || secureTextEntry;
110
- const shouldShowPasswordToggle = isPasswordField && (showPasswordToggle !== false);
111
-
112
- const getKeyboardType = useCallback((): 'default' | 'email-address' | 'numeric' => {
113
- switch (inputType) {
114
- case 'email':
115
- return 'email-address';
116
- case 'number':
117
- return 'numeric';
118
- case 'password':
119
- case 'text':
120
- default:
121
- return 'default';
122
- }
123
- }, [inputType]);
124
-
125
- const handleFocus = useCallback(() => {
126
- setIsFocused(true);
127
- onFocus?.();
128
- }, [onFocus]);
129
-
130
- const handlePress = useCallback(() => {
131
- onPress?.();
132
- }, [onPress]);
133
-
134
- const handleBlur = useCallback(() => {
135
- setIsFocused(false);
136
- onBlur?.();
137
- }, [onBlur]);
138
-
139
- const togglePasswordVisibility = () => {
140
- setIsPasswordVisible(!isPasswordVisible);
141
- };
142
-
143
- // Memoized change handler for InnerTextInput
144
- const handleChangeText = useCallback((text: string) => {
145
- internalValueRef.current = text;
146
- onChangeText?.(text);
147
- }, [onChangeText]);
148
-
149
- // Memoized input style
150
- const inputStyle = useMemo(() => (inputStyles.input as any)({}), []);
151
-
152
- // Generate native accessibility props
153
- const nativeA11yProps = useMemo(() => {
154
- // Derive invalid state from hasError or explicit accessibilityInvalid
155
- const isInvalid = accessibilityInvalid ?? hasError;
156
-
157
- return getNativeFormAccessibilityProps({
158
- accessibilityLabel,
159
- accessibilityHint,
160
- accessibilityDisabled: accessibilityDisabled ?? disabled,
161
- accessibilityHidden,
162
- accessibilityRole: accessibilityRole ?? 'textbox',
163
- accessibilityRequired,
164
- accessibilityInvalid: isInvalid,
165
- });
166
- }, [
167
- accessibilityLabel,
168
- accessibilityHint,
169
- accessibilityDisabled,
170
- disabled,
171
- accessibilityHidden,
172
- accessibilityRole,
173
- accessibilityRequired,
174
- accessibilityInvalid,
175
- hasError,
176
- ]);
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
-
217
- // Helper to render left icon
218
- const renderLeftIcon = () => {
219
- if (!leftIcon) return null;
220
-
221
- if (typeof leftIcon === 'string') {
222
- return (
223
- <MaterialDesignIcons
224
- name={leftIcon}
225
- size={iconSize}
226
- color={iconColor}
227
- />
34
+ if (!hasRenderedRef.current && !hasLoggedWarning) {
35
+ hasRenderedRef.current = true;
36
+ hasLoggedWarning = true;
37
+ console.warn(
38
+ 'Input is deprecated and maintained for compatibility only. Please use TextInput instead.\n' +
39
+ 'Migration: Replace <Input /> with <TextInput /> and inputType with inputMode.'
228
40
  );
229
- } else if (isValidElement(leftIcon)) {
230
- return leftIcon;
231
41
  }
42
+ }, []);
232
43
 
233
- return null;
234
- };
235
-
236
- // Helper to render right icon (not password toggle)
237
- const renderRightIcon = () => {
238
- if (!rightIcon) return null;
239
-
240
- if (typeof rightIcon === 'string') {
241
- return (
242
- <MaterialDesignIcons
243
- name={rightIcon}
244
- size={iconSize}
245
- color={iconColor}
246
- />
247
- );
248
- } else if (isValidElement(rightIcon)) {
249
- return rightIcon;
250
- }
251
-
252
- return null;
253
- };
254
-
255
- return (
256
- <View style={[containerStyle, style]} testID={testID} nativeID={id}>
257
- {/* Left Icon */}
258
- {leftIcon && (
259
- <View style={leftIconContainerStyle}>
260
- {renderLeftIcon()}
261
- </View>
262
- )}
263
-
264
- {/* Input */}
265
- <InnerTextInput
266
- inputRef={ref}
267
- value={value}
268
- onChangeText={handleChangeText}
269
- isAndroidSecure={needsAndroidSecureWorkaround}
270
- inputStyle={inputStyle}
271
- textInputProps={textInputProps}
272
- />
273
-
274
- {/* Right Icon or Password Toggle */}
275
- {shouldShowPasswordToggle ? (
276
- <TouchableOpacity
277
- style={passwordToggleStyle}
278
- onPress={togglePasswordVisibility}
279
- disabled={disabled}
280
- accessibilityLabel={isPasswordVisible ? 'Hide password' : 'Show password'}
281
- >
282
- <MaterialDesignIcons
283
- name={isPasswordVisible ? 'eye-off' : 'eye'}
284
- size={iconSize}
285
- color={iconColor}
286
- />
287
- </TouchableOpacity>
288
- ) : rightIcon ? (
289
- <View style={rightIconContainerStyle}>
290
- {renderRightIcon()}
291
- </View>
292
- ) : null}
293
- </View>
294
- );
44
+ return <TextInput ref={ref as any} {...props} />;
295
45
  });
296
46
 
297
- export default Input;
47
+ Input.displayName = 'Input';
48
+
49
+ export default Input;
@@ -67,7 +67,7 @@ export const inputStyles = defineStyle('Input', (theme: Theme) => ({
67
67
  false: { opacity: 1 }
68
68
  },
69
69
  hasError: {
70
- true: { borderColor: theme.intents.error.primary },
70
+ true: { borderColor: theme.intents.danger.primary },
71
71
  false: { borderColor: theme.colors.border.primary },
72
72
  },
73
73
  // $iterator expands for each input size
@@ -1,298 +1,47 @@
1
- import React, { isValidElement, useState, useMemo, useRef } from 'react';
2
- import { getWebProps } from 'react-native-unistyles/web';
3
- import { useUnistyles } from 'react-native-unistyles';
4
- import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
5
- import { isIconName } from '../Icon/icon-resolver';
6
- import useMergeRefs from '../hooks/useMergeRefs';
7
- import { inputStyles } from './Input.styles';
8
- import { InputProps } from './types';
9
- import { getWebFormAriaProps } from '../utils/accessibility';
10
-
11
1
  /**
12
- * Text input field with support for icons, password visibility toggle, and validation states.
13
- * Available in outlined and filled variants with multiple sizes.
2
+ * @ignore
3
+ * @deprecated Use TextInput instead. This component exists for backwards compatibility only.
14
4
  */
15
- const Input = React.forwardRef<HTMLInputElement, InputProps>(({
16
- value,
17
- onChangeText,
18
- onFocus,
19
- onBlur,
20
- onPress,
21
- placeholder,
22
- disabled = false,
23
- inputType = 'text',
24
- secureTextEntry = false,
25
- leftIcon,
26
- rightIcon,
27
- showPasswordToggle,
28
- autoCapitalize = 'sentences',
29
- size = 'md',
30
- type = 'outlined',
31
- hasError = false,
32
- // Spacing variants from FormInputStyleProps
33
- margin,
34
- marginVertical,
35
- marginHorizontal,
36
- style,
37
- testID,
38
- id,
39
- // Accessibility props
40
- accessibilityLabel,
41
- accessibilityHint,
42
- accessibilityDisabled,
43
- accessibilityHidden,
44
- accessibilityRole,
45
- accessibilityLabelledBy,
46
- accessibilityDescribedBy,
47
- accessibilityControls,
48
- accessibilityExpanded,
49
- accessibilityPressed,
50
- accessibilityOwns,
51
- accessibilityHasPopup,
52
- accessibilityRequired,
53
- accessibilityInvalid,
54
- accessibilityErrorMessage,
55
- accessibilityAutoComplete,
56
- }, ref) => {
57
- const [isPasswordVisible, setIsPasswordVisible] = useState(false);
58
-
59
- // Determine if we should show password toggle
60
- const isPasswordField = inputType === 'password' || secureTextEntry;
61
- const shouldShowPasswordToggle = isPasswordField && (showPasswordToggle !== false);
62
-
63
- // Get theme for icon sizes and colors
64
- const { theme } = useUnistyles();
65
- const iconSize = theme.sizes.input[size].iconSize;
66
- const iconColor = theme.colors.text.secondary;
67
-
68
- const [isFocused, setIsFocused] = useState(false);
69
-
70
- const getInputType = () => {
71
- // Handle password visibility
72
- if (isPasswordField && !isPasswordVisible) {
73
- return 'password';
74
- }
75
-
76
- switch (inputType) {
77
- case 'email':
78
- return 'email';
79
- case 'number':
80
- return 'number';
81
- case 'password':
82
- return 'text'; // When visible
83
- case 'text':
84
- default:
85
- return 'text';
86
- }
87
- };
88
-
89
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
90
- if (onChangeText) {
91
- onChangeText(e.target.value);
92
- }
93
- };
94
-
95
- const handlePress = (e: React.MouseEvent<HTMLDivElement>) => {
96
- // For web compatibility, we can trigger onFocus when pressed
97
- e.preventDefault();
98
- e.stopPropagation();
99
- if (onPress) {
100
- onPress();
101
- }
102
- }
103
-
104
- const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
105
- e.preventDefault();
106
- e.stopPropagation();
107
- setIsFocused(true);
108
- if (onFocus) {
109
- onFocus();
110
- }
111
- };
112
-
113
- const handleBlur = () => {
114
- setIsFocused(false);
115
- if (onBlur) {
116
- onBlur();
117
- }
118
- };
119
-
120
- const togglePasswordVisibility = () => {
121
- setIsPasswordVisible(!isPasswordVisible);
122
- };
123
-
124
- // Apply variants (for size and spacing)
125
- inputStyles.useVariants({
126
- size,
127
- margin,
128
- marginVertical,
129
- marginHorizontal,
130
- });
131
-
132
- // Get web props for all styled elements (all styles are dynamic functions)
133
- const dynamicContainerStyle = (inputStyles.container as any)({ type, focused: isFocused, hasError, disabled });
134
- const {ref: containerStyleRef, ...containerProps} = getWebProps([dynamicContainerStyle, style]);
135
- const leftIconContainerProps = getWebProps([(inputStyles.leftIconContainer as any)({})]);
136
- const rightIconContainerProps = getWebProps([(inputStyles.rightIconContainer as any)({})]);
137
- const passwordToggleProps = getWebProps([(inputStyles.passwordToggle as any)({})]);
5
+ import React, { useEffect, useRef } from 'react';
6
+ import TextInput from '../TextInput/TextInput.web';
7
+ import type { TextInputProps } from '../TextInput/types';
8
+ import type { IdealystElement } from '../utils/refTypes';
138
9
 
139
- // Get input props
140
- const inputWebProps = getWebProps([(inputStyles.input as any)({})]);
10
+ // Track if we've already logged the deprecation warning
11
+ let hasLoggedWarning = false;
141
12
 
142
- // Generate accessibility props
143
- const ariaProps = useMemo(() => {
144
- // Derive invalid state from hasError or explicit accessibilityInvalid
145
- const isInvalid = accessibilityInvalid ?? hasError;
146
-
147
- return getWebFormAriaProps({
148
- accessibilityLabel,
149
- accessibilityHint,
150
- accessibilityDisabled: accessibilityDisabled ?? disabled,
151
- accessibilityHidden,
152
- accessibilityRole: accessibilityRole ?? 'textbox',
153
- accessibilityLabelledBy,
154
- accessibilityDescribedBy,
155
- accessibilityControls,
156
- accessibilityExpanded,
157
- accessibilityPressed,
158
- accessibilityOwns,
159
- accessibilityHasPopup,
160
- accessibilityRequired,
161
- accessibilityInvalid: isInvalid,
162
- accessibilityErrorMessage,
163
- accessibilityAutoComplete,
164
- });
165
- }, [
166
- accessibilityLabel,
167
- accessibilityHint,
168
- accessibilityDisabled,
169
- disabled,
170
- accessibilityHidden,
171
- accessibilityRole,
172
- accessibilityLabelledBy,
173
- accessibilityDescribedBy,
174
- accessibilityControls,
175
- accessibilityExpanded,
176
- accessibilityPressed,
177
- accessibilityOwns,
178
- accessibilityHasPopup,
179
- accessibilityRequired,
180
- accessibilityInvalid,
181
- hasError,
182
- accessibilityErrorMessage,
183
- accessibilityAutoComplete,
184
- ]);
185
-
186
- // Merge the forwarded ref with unistyles ref for the input
187
- const mergedInputRef = useMergeRefs(ref, inputWebProps.ref);
188
-
189
- // Helper to render left icon
190
- const renderLeftIcon = () => {
191
- if (!leftIcon) return null;
192
-
193
- if (isIconName(leftIcon)) {
194
- return (
195
- <IconSvg
196
- name={leftIcon}
197
- size={iconSize}
198
- color={iconColor}
199
- aria-label={leftIcon}
200
- />
201
- );
202
- } else if (isValidElement(leftIcon)) {
203
- return <span {...leftIconContainerProps}>{leftIcon}</span>;
204
- }
205
-
206
- return null;
207
- };
208
-
209
- // Helper to render right icon (not password toggle)
210
- const renderRightIcon = () => {
211
- if (!rightIcon) return null;
212
-
213
- if (isIconName(rightIcon)) {
214
- return (
215
- <IconSvg
216
- name={rightIcon}
217
- size={iconSize}
218
- color={iconColor}
219
- aria-label={rightIcon}
220
- />
13
+ /**
14
+ * @ignore
15
+ * @deprecated Use TextInput instead. Input is maintained for backwards compatibility only.
16
+ *
17
+ * Migration:
18
+ * - Replace `<Input />` with `<TextInput />`
19
+ * - Replace `inputType` prop with `inputMode` (React Native only)
20
+ *
21
+ * @example
22
+ * // Before
23
+ * import { Input } from '@idealyst/components';
24
+ * <Input inputType="email" />
25
+ *
26
+ * // After
27
+ * import { TextInput } from '@idealyst/components';
28
+ * <TextInput inputMode="email" />
29
+ */
30
+ const Input = React.forwardRef<IdealystElement, TextInputProps>((props, ref) => {
31
+ const hasRenderedRef = useRef(false);
32
+
33
+ useEffect(() => {
34
+ if (!hasRenderedRef.current && !hasLoggedWarning) {
35
+ hasRenderedRef.current = true;
36
+ hasLoggedWarning = true;
37
+ console.warn(
38
+ 'Input is deprecated and maintained for compatibility only. Please use TextInput instead.\n' +
39
+ 'Migration: Replace <Input /> with <TextInput /> and inputType with inputMode.'
221
40
  );
222
- } else if (isValidElement(rightIcon)) {
223
- return <span {...rightIconContainerProps}>{rightIcon}</span>;
224
41
  }
42
+ }, []);
225
43
 
226
- return null;
227
- };
228
-
229
- // Helper to render password toggle icon
230
- const renderPasswordToggleIcon = () => {
231
- const iconName = isPasswordVisible ? 'eye-off' : 'eye';
232
- return (
233
- <IconSvg
234
- name={iconName}
235
- size={iconSize}
236
- color={iconColor}
237
- aria-label={iconName}
238
- />
239
- );
240
- };
241
-
242
- const containerRef = useRef<HTMLDivElement>(null);
243
-
244
- const handleContainerPress = (e: React.MouseEvent<HTMLDivElement>) => {
245
- e.preventDefault();
246
- e.stopPropagation();
247
- containerRef.current?.focus();
248
- }
249
-
250
- const mergedContainerRef = useMergeRefs(containerRef, containerStyleRef);
251
-
252
- return (
253
- <div onClick={handleContainerPress} ref={mergedContainerRef} {...containerProps} id={id} data-testid={testID}>
254
- {/* Left Icon */}
255
- {leftIcon && (
256
- <span {...leftIconContainerProps}>
257
- {renderLeftIcon()}
258
- </span>
259
- )}
260
-
261
- {/* Input */}
262
- <input
263
- {...inputWebProps}
264
- {...ariaProps}
265
- ref={mergedInputRef}
266
- type={getInputType()}
267
- value={value}
268
- onClick={handlePress}
269
- onChange={handleChange}
270
- onFocus={handleFocus}
271
- onBlur={handleBlur}
272
- placeholder={placeholder}
273
- disabled={disabled}
274
- autoCapitalize={autoCapitalize}
275
- />
276
-
277
- {/* Right Icon or Password Toggle */}
278
- {shouldShowPasswordToggle ? (
279
- <button
280
- {...passwordToggleProps}
281
- onClick={togglePasswordVisibility}
282
- disabled={disabled}
283
- aria-label={isPasswordVisible ? 'Hide password' : 'Show password'}
284
- type="button"
285
- tabIndex={-1}
286
- >
287
- {renderPasswordToggleIcon()}
288
- </button>
289
- ) : rightIcon ? (
290
- <span {...rightIconContainerProps}>
291
- {renderRightIcon()}
292
- </span>
293
- ) : null}
294
- </div>
295
- );
44
+ return <TextInput ref={ref} {...props} />;
296
45
  });
297
46
 
298
47
  Input.displayName = 'Input';
@@ -1,3 +1,10 @@
1
- // React Native-specific Input export
1
+ /**
2
+ * @ignore
3
+ * @deprecated Use TextInput instead. Input is maintained for backwards compatibility only.
4
+ */
5
+
6
+ // React Native-specific deprecated Input export
2
7
  export { default } from './Input.native';
3
- export * from './types';
8
+
9
+ // Re-export types from TextInput for backwards compatibility
10
+ export * from '../TextInput/types';