@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.
- package/README.md +3 -3
- package/package.json +3 -3
- package/plugin/__tests__/web.test.ts +2 -2
- package/src/Accordion/Accordion.native.tsx +3 -2
- package/src/ActivityIndicator/ActivityIndicator.native.tsx +3 -2
- package/src/ActivityIndicator/ActivityIndicator.styles.tsx +25 -26
- package/src/ActivityIndicator/ActivityIndicator.web.tsx +2 -1
- package/src/Alert/Alert.native.tsx +20 -10
- package/src/Alert/Alert.styles.tsx +148 -86
- package/src/Alert/Alert.web.tsx +10 -5
- package/src/Alert/types.ts +53 -3
- package/src/Avatar/Avatar.native.tsx +3 -2
- package/src/Avatar/Avatar.web.tsx +2 -1
- package/src/Avatar/types.ts +1 -1
- package/src/Badge/Badge.native.tsx +18 -6
- package/src/Badge/Badge.styles.tsx +22 -5
- package/src/Badge/Badge.web.tsx +12 -4
- package/src/Badge/types.ts +14 -2
- package/src/Breadcrumb/Breadcrumb.native.tsx +3 -2
- package/src/Button/Button.native.tsx +16 -6
- package/src/Button/Button.styles.tsx +2 -2
- package/src/Button/Button.web.tsx +19 -15
- package/src/Button/types.ts +6 -10
- package/src/Card/Card.native.tsx +27 -3
- package/src/Card/Card.web.tsx +30 -4
- package/src/Card/types.ts +15 -0
- package/src/Checkbox/Checkbox.native.tsx +5 -4
- package/src/Checkbox/Checkbox.styles.tsx +62 -52
- package/src/Checkbox/Checkbox.web.tsx +4 -3
- package/src/Checkbox/types.ts +1 -1
- package/src/Chip/Chip.native.tsx +30 -7
- package/src/Chip/Chip.styles.tsx +142 -124
- package/src/Chip/Chip.web.tsx +28 -5
- package/src/Chip/types.ts +15 -0
- package/src/Dialog/Dialog.native.tsx +6 -6
- package/src/Dialog/Dialog.web.tsx +5 -5
- package/src/Dialog/types.ts +2 -2
- package/src/Divider/Divider.native.tsx +20 -17
- package/src/Divider/Divider.styles.tsx +51 -29
- package/src/Divider/Divider.web.tsx +5 -4
- package/src/Divider/types.ts +3 -3
- package/src/Icon/Icon.native.tsx +3 -2
- package/src/Icon/Icon.web.tsx +2 -1
- package/src/Icon/IconSvg/IconSvg.native.tsx +3 -2
- package/src/Image/Image.native.tsx +3 -2
- package/src/Input/Input.native.tsx +42 -290
- package/src/Input/Input.styles.tsx +1 -1
- package/src/Input/Input.web.tsx +37 -288
- package/src/Input/index.native.ts +9 -2
- package/src/Input/index.ts +8 -1
- package/src/Input/index.web.ts +8 -1
- package/src/Input/types.ts +1 -1
- package/src/List/List.native.tsx +3 -2
- package/src/List/ListItem.native.tsx +3 -2
- package/src/List/ListSection.native.tsx +3 -2
- package/src/Menu/Menu.native.tsx +2 -1
- package/src/Menu/Menu.styles.tsx +79 -29
- package/src/Menu/Menu.web.tsx +2 -1
- package/src/Menu/MenuItem.native.tsx +4 -3
- package/src/Menu/MenuItem.styles.tsx +81 -32
- package/src/Menu/MenuItem.web.tsx +2 -1
- package/src/Menu/docs.ts +1 -1
- package/src/Popover/Popover.native.tsx +2 -1
- package/src/Popover/Popover.web.tsx +2 -1
- package/src/Popover/types.ts +15 -4
- package/src/Pressable/Pressable.native.tsx +3 -2
- package/src/Pressable/Pressable.web.tsx +3 -5
- package/src/Progress/Progress.native.tsx +5 -4
- package/src/Progress/Progress.web.tsx +3 -3
- package/src/Progress/types.ts +3 -3
- package/src/RadioButton/RadioButton.native.tsx +4 -3
- package/src/RadioButton/RadioButton.styles.tsx +53 -33
- package/src/RadioButton/RadioGroup.native.tsx +3 -2
- package/src/SVGImage/SVGImage.native.tsx +5 -4
- package/src/SVGImage/SVGImage.styles.tsx +44 -10
- package/src/SVGImage/SVGImage.web.tsx +2 -1
- package/src/Screen/Screen.native.tsx +2 -1
- package/src/Screen/Screen.web.tsx +2 -1
- package/src/Select/Select.native.tsx +6 -5
- package/src/Select/Select.styles.tsx +1 -1
- package/src/Select/Select.web.tsx +4 -3
- package/src/Select/types.ts +1 -1
- package/src/Skeleton/Skeleton.native.tsx +2 -1
- package/src/Slider/Slider.native.tsx +9 -8
- package/src/Slider/Slider.web.tsx +10 -9
- package/src/Slider/types.ts +9 -2
- package/src/Switch/Switch.native.tsx +7 -6
- package/src/Switch/Switch.styles.tsx +35 -17
- package/src/Switch/Switch.web.tsx +8 -7
- package/src/Switch/types.ts +44 -4
- package/src/TabBar/TabBar.native.tsx +3 -2
- package/src/Text/Text.native.tsx +3 -2
- package/src/Text/Text.web.tsx +2 -1
- package/src/TextArea/TextArea.native.tsx +3 -2
- package/src/TextArea/TextArea.styles.tsx +2 -2
- package/src/TextArea/TextArea.web.tsx +2 -1
- package/src/TextInput/TextInput.native.tsx +300 -0
- package/src/TextInput/TextInput.styles.tsx +207 -0
- package/src/TextInput/TextInput.web.tsx +301 -0
- package/src/TextInput/index.native.ts +3 -0
- package/src/TextInput/index.ts +5 -0
- package/src/TextInput/index.web.ts +5 -0
- package/src/TextInput/types.ts +163 -0
- package/src/Tooltip/Tooltip.native.tsx +3 -2
- package/src/Video/Video.native.tsx +4 -3
- package/src/View/View.native.tsx +2 -1
- package/src/View/View.web.tsx +2 -1
- package/src/examples/AlertExamples.tsx +5 -5
- package/src/examples/ButtonExamples.tsx +12 -12
- package/src/examples/CardExamples.tsx +1 -1
- package/src/examples/CheckboxExamples.tsx +2 -2
- package/src/examples/ChipExamples.tsx +6 -6
- package/src/examples/DialogExamples.tsx +1 -1
- package/src/examples/DividerExamples.tsx +1 -1
- package/src/examples/InputExamples.tsx +1 -1
- package/src/examples/LinkExamples.tsx +1 -1
- package/src/examples/ListExamples.tsx +1 -1
- package/src/examples/MenuExamples.tsx +2 -2
- package/src/examples/ProgressExamples.tsx +1 -1
- package/src/examples/RadioButtonExamples.tsx +5 -5
- package/src/examples/SVGImageExamples.tsx +1 -1
- package/src/examples/SelectExamples.tsx +1 -1
- package/src/examples/SliderExamples.tsx +5 -5
- package/src/examples/SwitchExamples.tsx +2 -2
- package/src/examples/TableExamples.tsx +1 -1
- package/src/examples/TooltipExamples.tsx +2 -2
- package/src/extensions/index.ts +1 -0
- package/src/extensions/types.ts +10 -3
- package/src/index.ts +23 -2
- package/src/utils/index.ts +12 -0
- package/src/utils/refTypes.ts +50 -0
|
@@ -1,297 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
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 (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
package/src/Input/Input.web.tsx
CHANGED
|
@@ -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
|
-
*
|
|
13
|
-
*
|
|
2
|
+
* @ignore
|
|
3
|
+
* @deprecated Use TextInput instead. This component exists for backwards compatibility only.
|
|
14
4
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
140
|
-
|
|
10
|
+
// Track if we've already logged the deprecation warning
|
|
11
|
+
let hasLoggedWarning = false;
|
|
141
12
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8
|
+
|
|
9
|
+
// Re-export types from TextInput for backwards compatibility
|
|
10
|
+
export * from '../TextInput/types';
|