@idealyst/components 1.1.6 → 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.
- package/package.json +8 -3
- package/src/Accordion/Accordion.native.tsx +22 -14
- package/src/Accordion/Accordion.styles.old.tsx +298 -0
- package/src/Accordion/Accordion.styles.tsx +139 -248
- package/src/Accordion/Accordion.web.tsx +12 -7
- package/src/ActivityIndicator/ActivityIndicator.native.tsx +3 -2
- package/src/ActivityIndicator/ActivityIndicator.styles.old.tsx +94 -0
- package/src/ActivityIndicator/ActivityIndicator.styles.tsx +43 -62
- package/src/ActivityIndicator/ActivityIndicator.web.tsx +2 -2
- package/src/Alert/Alert.native.tsx +26 -15
- package/src/Alert/Alert.styles.old.tsx +209 -0
- package/src/Alert/Alert.styles.tsx +108 -281
- package/src/Alert/Alert.web.tsx +6 -10
- package/src/Avatar/Avatar.native.tsx +5 -2
- package/src/Avatar/Avatar.styles.old.tsx +99 -0
- package/src/Avatar/Avatar.styles.tsx +47 -62
- package/src/Avatar/Avatar.web.tsx +2 -2
- package/src/Badge/Badge.native.tsx +2 -2
- package/src/Badge/Badge.styles.old.tsx +157 -0
- package/src/Badge/Badge.styles.tsx +69 -108
- package/src/Badge/Badge.web.tsx +6 -6
- package/src/Breadcrumb/Breadcrumb.native.tsx +12 -5
- package/src/Breadcrumb/Breadcrumb.styles.old.tsx +231 -0
- package/src/Breadcrumb/Breadcrumb.styles.tsx +93 -209
- package/src/Breadcrumb/Breadcrumb.web.tsx +39 -27
- package/src/Button/Button.native.tsx +39 -14
- package/src/Button/Button.styles.tsx +99 -253
- package/src/Button/Button.web.tsx +10 -8
- package/src/Card/Card.native.tsx +8 -4
- package/src/Card/Card.styles.old.tsx +160 -0
- package/src/Card/Card.styles.tsx +107 -142
- package/src/Card/Card.web.tsx +6 -4
- package/src/Checkbox/Checkbox.native.tsx +14 -6
- package/src/Checkbox/Checkbox.styles.old.tsx +271 -0
- package/src/Checkbox/Checkbox.styles.tsx +109 -197
- package/src/Checkbox/Checkbox.web.tsx +7 -7
- package/src/Chip/Chip.native.tsx +5 -5
- package/src/Chip/Chip.styles.old.tsx +184 -0
- package/src/Chip/Chip.styles.tsx +34 -22
- package/src/Chip/Chip.web.tsx +5 -5
- package/src/Dialog/Dialog.native.tsx +16 -7
- package/src/Dialog/Dialog.styles.old.tsx +202 -0
- package/src/Dialog/Dialog.styles.tsx +108 -132
- package/src/Dialog/Dialog.web.tsx +4 -4
- package/src/Divider/Divider.native.tsx +29 -42
- package/src/Divider/Divider.styles.old.tsx +172 -0
- package/src/Divider/Divider.styles.tsx +116 -242
- package/src/Divider/Divider.web.tsx +17 -14
- package/src/Icon/Icon.native.tsx +12 -4
- package/src/Icon/Icon.styles.old.tsx +81 -0
- package/src/Icon/Icon.styles.tsx +52 -60
- package/src/Icon/Icon.web.tsx +43 -7
- package/src/Icon/IconSvg/IconSvg.web.tsx +2 -0
- package/src/Image/Image.styles.old.tsx +69 -0
- package/src/Image/Image.styles.tsx +45 -43
- package/src/Input/Input.native.tsx +140 -56
- package/src/Input/Input.styles.old.tsx +289 -0
- package/src/Input/Input.styles.tsx +177 -228
- package/src/Input/Input.web.tsx +5 -8
- package/src/Link/Link.native.tsx +4 -1
- package/src/List/List.native.tsx +5 -2
- package/src/List/List.styles.old.tsx +242 -0
- package/src/List/List.styles.tsx +178 -240
- package/src/List/ListItem.native.tsx +16 -8
- package/src/List/ListItem.web.tsx +26 -15
- package/src/Menu/Menu.native.tsx +1 -1
- package/src/Menu/Menu.styles.old.tsx +197 -0
- package/src/Menu/Menu.styles.tsx +90 -156
- package/src/Menu/Menu.web.tsx +2 -2
- package/src/Menu/MenuItem.native.tsx +9 -5
- package/src/Menu/MenuItem.styles.old.tsx +114 -0
- package/src/Menu/MenuItem.styles.tsx +71 -104
- package/src/Menu/MenuItem.web.tsx +23 -5
- package/src/Popover/Popover.native.tsx +10 -4
- package/src/Popover/Popover.styles.old.tsx +135 -0
- package/src/Popover/Popover.styles.tsx +46 -96
- package/src/Popover/Popover.web.tsx +1 -1
- package/src/Pressable/Pressable.native.tsx +3 -1
- package/src/Pressable/Pressable.styles.old.tsx +27 -0
- package/src/Pressable/Pressable.styles.tsx +35 -20
- package/src/Pressable/Pressable.web.tsx +1 -1
- package/src/Progress/Progress.native.tsx +15 -6
- package/src/Progress/Progress.styles.old.tsx +200 -0
- package/src/Progress/Progress.styles.tsx +69 -118
- package/src/Progress/Progress.web.tsx +10 -9
- package/src/RadioButton/RadioButton.native.tsx +10 -4
- package/src/RadioButton/RadioButton.styles.old.tsx +175 -0
- package/src/RadioButton/RadioButton.styles.tsx +81 -145
- package/src/RadioButton/RadioButton.web.tsx +4 -4
- package/src/SVGImage/SVGImage.styles.old.tsx +86 -0
- package/src/SVGImage/SVGImage.styles.tsx +35 -66
- package/src/Screen/Screen.native.tsx +30 -27
- package/src/Screen/Screen.styles.old.tsx +87 -0
- package/src/Screen/Screen.styles.tsx +120 -71
- package/src/Screen/Screen.web.tsx +2 -2
- package/src/Select/Select.native.tsx +44 -29
- package/src/Select/Select.styles.old.tsx +353 -0
- package/src/Select/Select.styles.tsx +244 -293
- package/src/Select/Select.web.tsx +5 -5
- package/src/Skeleton/Skeleton.styles.old.tsx +67 -0
- package/src/Skeleton/Skeleton.styles.tsx +31 -43
- package/src/Slider/Slider.native.tsx +9 -5
- package/src/Slider/Slider.styles.old.tsx +259 -0
- package/src/Slider/Slider.styles.tsx +157 -227
- package/src/Slider/Slider.web.tsx +5 -5
- package/src/Switch/Switch.native.tsx +11 -5
- package/src/Switch/Switch.styles.old.tsx +203 -0
- package/src/Switch/Switch.styles.tsx +103 -149
- package/src/Switch/Switch.web.tsx +8 -8
- package/src/TabBar/TabBar.native.tsx +24 -31
- package/src/TabBar/TabBar.styles.old.tsx +343 -0
- package/src/TabBar/TabBar.styles.tsx +204 -494
- package/src/TabBar/TabBar.web.tsx +21 -33
- package/src/Table/Table.native.tsx +18 -9
- package/src/Table/Table.styles.old.tsx +311 -0
- package/src/Table/Table.styles.tsx +151 -278
- package/src/Table/Table.web.tsx +1 -1
- package/src/Text/Text.native.tsx +1 -4
- package/src/Text/Text.style.demo.tsx +16 -0
- package/src/Text/Text.styles.old.tsx +219 -0
- package/src/Text/Text.styles.tsx +94 -78
- package/src/Text/Text.web.tsx +2 -2
- package/src/Text/index.ts +1 -0
- package/src/TextArea/TextArea.styles.old.tsx +213 -0
- package/src/TextArea/TextArea.styles.tsx +101 -157
- package/src/Tooltip/Tooltip.native.tsx +2 -2
- package/src/Tooltip/Tooltip.styles.old.tsx +82 -0
- package/src/Tooltip/Tooltip.styles.tsx +38 -53
- package/src/Tooltip/Tooltip.web.tsx +2 -2
- package/src/Video/Video.styles.old.tsx +51 -0
- package/src/Video/Video.styles.tsx +32 -28
- package/src/View/View.native.tsx +12 -12
- package/src/View/View.styles.old.tsx +125 -0
- package/src/View/View.styles.tsx +84 -103
- package/src/View/View.web.tsx +14 -2
- package/src/examples/CardExamples.tsx +0 -6
- package/src/extensions/applyExtension.ts +210 -0
- package/src/extensions/extendComponent.ts +438 -0
- package/src/extensions/index.ts +102 -0
- package/src/extensions/types.ts +497 -0
- package/src/globals.ts +16 -0
- package/src/index.native.ts +4 -0
- package/src/index.ts +28 -0
- package/src/utils/deepMerge.ts +54 -2
|
@@ -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,43 +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
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
};
|
|
127
|
+
onFocus?.();
|
|
128
|
+
}, [onFocus]);
|
|
67
129
|
|
|
68
|
-
const handlePress = () => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
}
|
|
130
|
+
const handlePress = useCallback(() => {
|
|
131
|
+
onPress?.();
|
|
132
|
+
}, [onPress]);
|
|
73
133
|
|
|
74
|
-
const handleBlur = () => {
|
|
134
|
+
const handleBlur = useCallback(() => {
|
|
75
135
|
setIsFocused(false);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
};
|
|
136
|
+
onBlur?.();
|
|
137
|
+
}, [onBlur]);
|
|
80
138
|
|
|
81
139
|
const togglePasswordVisibility = () => {
|
|
82
140
|
setIsPasswordVisible(!isPasswordVisible);
|
|
83
141
|
};
|
|
84
142
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
marginVertical,
|
|
94
|
-
marginHorizontal,
|
|
95
|
-
});
|
|
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)({}), []);
|
|
96
151
|
|
|
97
152
|
// Generate native accessibility props
|
|
98
153
|
const nativeA11yProps = useMemo(() => {
|
|
@@ -120,17 +175,55 @@ const Input = React.forwardRef<TextInput, InputProps>(({
|
|
|
120
175
|
hasError,
|
|
121
176
|
]);
|
|
122
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
|
+
|
|
123
217
|
// Helper to render left icon
|
|
124
218
|
const renderLeftIcon = () => {
|
|
125
219
|
if (!leftIcon) return null;
|
|
126
220
|
|
|
127
221
|
if (typeof leftIcon === 'string') {
|
|
128
|
-
const iconStyle = inputStyles.leftIcon;
|
|
129
222
|
return (
|
|
130
223
|
<MaterialCommunityIcons
|
|
131
224
|
name={leftIcon}
|
|
132
|
-
size={
|
|
133
|
-
color={
|
|
225
|
+
size={iconSize}
|
|
226
|
+
color={iconColor}
|
|
134
227
|
/>
|
|
135
228
|
);
|
|
136
229
|
} else if (isValidElement(leftIcon)) {
|
|
@@ -145,12 +238,11 @@ const Input = React.forwardRef<TextInput, InputProps>(({
|
|
|
145
238
|
if (!rightIcon) return null;
|
|
146
239
|
|
|
147
240
|
if (typeof rightIcon === 'string') {
|
|
148
|
-
const iconStyle = inputStyles.rightIcon;
|
|
149
241
|
return (
|
|
150
242
|
<MaterialCommunityIcons
|
|
151
243
|
name={rightIcon}
|
|
152
|
-
size={
|
|
153
|
-
color={
|
|
244
|
+
size={iconSize}
|
|
245
|
+
color={iconColor}
|
|
154
246
|
/>
|
|
155
247
|
);
|
|
156
248
|
} else if (isValidElement(rightIcon)) {
|
|
@@ -161,48 +253,40 @@ const Input = React.forwardRef<TextInput, InputProps>(({
|
|
|
161
253
|
};
|
|
162
254
|
|
|
163
255
|
return (
|
|
164
|
-
<View style={[
|
|
256
|
+
<View style={[containerStyle, style]} testID={testID} nativeID={id}>
|
|
165
257
|
{/* Left Icon */}
|
|
166
258
|
{leftIcon && (
|
|
167
|
-
<View style={
|
|
259
|
+
<View style={leftIconContainerStyle}>
|
|
168
260
|
{renderLeftIcon()}
|
|
169
261
|
</View>
|
|
170
262
|
)}
|
|
171
263
|
|
|
172
264
|
{/* Input */}
|
|
173
|
-
<
|
|
174
|
-
|
|
175
|
-
ref={ref}
|
|
265
|
+
<InnerTextInput
|
|
266
|
+
inputRef={ref}
|
|
176
267
|
value={value}
|
|
177
|
-
onChangeText={
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
secureTextEntry={(secureTextEntry || inputType === 'password') && !isPasswordVisible}
|
|
182
|
-
autoCapitalize={autoCapitalize}
|
|
183
|
-
onFocus={handleFocus}
|
|
184
|
-
onBlur={handleBlur}
|
|
185
|
-
style={inputStyles.input}
|
|
186
|
-
placeholderTextColor="#999999"
|
|
187
|
-
{...nativeA11yProps}
|
|
268
|
+
onChangeText={handleChangeText}
|
|
269
|
+
isAndroidSecure={needsAndroidSecureWorkaround}
|
|
270
|
+
inputStyle={inputStyle}
|
|
271
|
+
textInputProps={textInputProps}
|
|
188
272
|
/>
|
|
189
273
|
|
|
190
274
|
{/* Right Icon or Password Toggle */}
|
|
191
275
|
{shouldShowPasswordToggle ? (
|
|
192
276
|
<TouchableOpacity
|
|
193
|
-
style={
|
|
277
|
+
style={passwordToggleStyle}
|
|
194
278
|
onPress={togglePasswordVisibility}
|
|
195
279
|
disabled={disabled}
|
|
196
280
|
accessibilityLabel={isPasswordVisible ? 'Hide password' : 'Show password'}
|
|
197
281
|
>
|
|
198
282
|
<MaterialCommunityIcons
|
|
199
283
|
name={isPasswordVisible ? 'eye-off' : 'eye'}
|
|
200
|
-
size={
|
|
201
|
-
color={
|
|
284
|
+
size={iconSize}
|
|
285
|
+
color={iconColor}
|
|
202
286
|
/>
|
|
203
287
|
</TouchableOpacity>
|
|
204
288
|
) : rightIcon ? (
|
|
205
|
-
<View style={
|
|
289
|
+
<View style={rightIconContainerStyle}>
|
|
206
290
|
{renderRightIcon()}
|
|
207
291
|
</View>
|
|
208
292
|
) : null}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
2
|
+
import { Theme, Size } from '@idealyst/theme';
|
|
3
|
+
import { buildSizeVariants } from '../utils/buildSizeVariants';
|
|
4
|
+
import {
|
|
5
|
+
buildMarginVariants,
|
|
6
|
+
buildMarginVerticalVariants,
|
|
7
|
+
buildMarginHorizontalVariants,
|
|
8
|
+
} from '../utils/buildViewStyleVariants';
|
|
9
|
+
import { InputSize, InputType } from './types';
|
|
10
|
+
import { applyExtensions } from '../extensions/applyExtension';
|
|
11
|
+
|
|
12
|
+
export type InputVariants = {
|
|
13
|
+
size: InputSize;
|
|
14
|
+
type: InputType;
|
|
15
|
+
focused: boolean;
|
|
16
|
+
hasError: boolean;
|
|
17
|
+
disabled: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type InputDynamicProps = {
|
|
21
|
+
type?: InputType;
|
|
22
|
+
focused?: boolean;
|
|
23
|
+
hasError?: boolean;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get container border/background styles based on type, focused, hasError, disabled
|
|
29
|
+
*/
|
|
30
|
+
function getContainerDynamicStyles(theme: Theme, props: InputDynamicProps) {
|
|
31
|
+
const { type = 'outlined', focused = false, hasError = false, disabled = false } = props;
|
|
32
|
+
const focusColor = theme.intents.primary.primary;
|
|
33
|
+
const errorColor = theme.intents.error.primary;
|
|
34
|
+
|
|
35
|
+
// Base styles by type
|
|
36
|
+
let backgroundColor = 'transparent';
|
|
37
|
+
let borderWidth = 1;
|
|
38
|
+
let borderColor = theme.colors.border.primary;
|
|
39
|
+
let borderStyle = 'solid' as const;
|
|
40
|
+
|
|
41
|
+
if (type === 'filled') {
|
|
42
|
+
backgroundColor = theme.colors.surface.secondary;
|
|
43
|
+
borderWidth = 0;
|
|
44
|
+
} else if (type === 'bare') {
|
|
45
|
+
backgroundColor = 'transparent';
|
|
46
|
+
borderWidth = 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Error state takes precedence
|
|
50
|
+
if (hasError) {
|
|
51
|
+
borderColor = errorColor;
|
|
52
|
+
borderWidth = 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Focus state (error still takes precedence for color)
|
|
56
|
+
if (focused && !hasError) {
|
|
57
|
+
borderColor = focusColor;
|
|
58
|
+
borderWidth = 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Disabled state
|
|
62
|
+
if (disabled) {
|
|
63
|
+
backgroundColor = theme.colors.surface.secondary;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
backgroundColor,
|
|
68
|
+
borderWidth,
|
|
69
|
+
borderColor,
|
|
70
|
+
borderStyle,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create dynamic container styles
|
|
76
|
+
*/
|
|
77
|
+
function createContainerStyles(theme: Theme) {
|
|
78
|
+
return (props: InputDynamicProps) => {
|
|
79
|
+
const { type = 'outlined', focused = false, hasError = false, disabled = false } = props;
|
|
80
|
+
const dynamicStyles = getContainerDynamicStyles(theme, props);
|
|
81
|
+
const focusColor = theme.intents.primary.primary;
|
|
82
|
+
const errorColor = theme.intents.error.primary;
|
|
83
|
+
|
|
84
|
+
// Web-specific border and shadow
|
|
85
|
+
let webBorder = `1px solid ${dynamicStyles.borderColor}`;
|
|
86
|
+
let webBoxShadow = 'none';
|
|
87
|
+
|
|
88
|
+
if (type === 'filled' || type === 'bare') {
|
|
89
|
+
webBorder = 'none';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (hasError) {
|
|
93
|
+
webBorder = `1px solid ${errorColor}`;
|
|
94
|
+
if (focused) {
|
|
95
|
+
webBoxShadow = `0 0 0 2px ${errorColor}20`;
|
|
96
|
+
}
|
|
97
|
+
} else if (focused) {
|
|
98
|
+
webBorder = `1px solid ${focusColor}`;
|
|
99
|
+
webBoxShadow = `0 0 0 2px ${focusColor}20`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
display: 'flex',
|
|
104
|
+
flexDirection: 'row',
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
width: '100%',
|
|
107
|
+
minWidth: 0,
|
|
108
|
+
borderRadius: 8,
|
|
109
|
+
...dynamicStyles,
|
|
110
|
+
opacity: disabled ? 0.6 : 1,
|
|
111
|
+
variants: {
|
|
112
|
+
size: buildSizeVariants(theme, 'input', (size) => ({
|
|
113
|
+
height: size.height,
|
|
114
|
+
paddingHorizontal: size.paddingHorizontal,
|
|
115
|
+
})),
|
|
116
|
+
// Spacing variants from FormInputStyleProps
|
|
117
|
+
margin: buildMarginVariants(theme),
|
|
118
|
+
marginVertical: buildMarginVerticalVariants(theme),
|
|
119
|
+
marginHorizontal: buildMarginHorizontalVariants(theme),
|
|
120
|
+
},
|
|
121
|
+
_web: {
|
|
122
|
+
boxSizing: 'border-box',
|
|
123
|
+
transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
|
|
124
|
+
border: webBorder,
|
|
125
|
+
boxShadow: webBoxShadow,
|
|
126
|
+
cursor: disabled ? 'not-allowed' : 'text',
|
|
127
|
+
},
|
|
128
|
+
} as const;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create left icon container styles
|
|
134
|
+
*/
|
|
135
|
+
function createLeftIconContainerStyles(theme: Theme) {
|
|
136
|
+
return () => ({
|
|
137
|
+
display: 'flex' as const,
|
|
138
|
+
alignItems: 'center' as const,
|
|
139
|
+
justifyContent: 'center' as const,
|
|
140
|
+
flexShrink: 0,
|
|
141
|
+
variants: {
|
|
142
|
+
size: buildSizeVariants(theme, 'input', (size) => ({
|
|
143
|
+
marginRight: size.iconMargin,
|
|
144
|
+
})),
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create right icon container styles
|
|
151
|
+
*/
|
|
152
|
+
function createRightIconContainerStyles(theme: Theme) {
|
|
153
|
+
return () => ({
|
|
154
|
+
display: 'flex' as const,
|
|
155
|
+
alignItems: 'center' as const,
|
|
156
|
+
justifyContent: 'center' as const,
|
|
157
|
+
flexShrink: 0,
|
|
158
|
+
variants: {
|
|
159
|
+
size: buildSizeVariants(theme, 'input', (size) => ({
|
|
160
|
+
marginLeft: size.iconMargin,
|
|
161
|
+
})),
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Create left icon styles
|
|
168
|
+
*/
|
|
169
|
+
function createLeftIconStyles(theme: Theme) {
|
|
170
|
+
return () => ({
|
|
171
|
+
color: theme.colors.text.secondary,
|
|
172
|
+
variants: {
|
|
173
|
+
size: buildSizeVariants(theme, 'input', (size) => ({
|
|
174
|
+
fontSize: size.iconSize,
|
|
175
|
+
width: size.iconSize,
|
|
176
|
+
height: size.iconSize,
|
|
177
|
+
})),
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create right icon styles
|
|
184
|
+
*/
|
|
185
|
+
function createRightIconStyles(theme: Theme) {
|
|
186
|
+
return () => ({
|
|
187
|
+
display: 'flex' as const,
|
|
188
|
+
alignItems: 'center' as const,
|
|
189
|
+
justifyContent: 'center' as const,
|
|
190
|
+
flexShrink: 0,
|
|
191
|
+
color: theme.colors.text.secondary,
|
|
192
|
+
variants: {
|
|
193
|
+
size: buildSizeVariants(theme, 'input', (size) => ({
|
|
194
|
+
fontSize: size.iconSize,
|
|
195
|
+
width: size.iconSize,
|
|
196
|
+
height: size.iconSize,
|
|
197
|
+
})),
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create password toggle styles
|
|
204
|
+
*/
|
|
205
|
+
function createPasswordToggleStyles(theme: Theme) {
|
|
206
|
+
return () => ({
|
|
207
|
+
display: 'flex' as const,
|
|
208
|
+
alignItems: 'center' as const,
|
|
209
|
+
justifyContent: 'center' as const,
|
|
210
|
+
flexShrink: 0,
|
|
211
|
+
padding: 0,
|
|
212
|
+
variants: {
|
|
213
|
+
size: buildSizeVariants(theme, 'input', (size) => ({
|
|
214
|
+
marginLeft: size.iconMargin,
|
|
215
|
+
})),
|
|
216
|
+
},
|
|
217
|
+
_web: {
|
|
218
|
+
background: 'transparent',
|
|
219
|
+
border: 'none',
|
|
220
|
+
cursor: 'pointer',
|
|
221
|
+
_hover: {
|
|
222
|
+
opacity: 0.7,
|
|
223
|
+
},
|
|
224
|
+
_active: {
|
|
225
|
+
opacity: 0.5,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Create password toggle icon styles
|
|
233
|
+
*/
|
|
234
|
+
function createPasswordToggleIconStyles(theme: Theme) {
|
|
235
|
+
return () => ({
|
|
236
|
+
display: 'flex' as const,
|
|
237
|
+
alignItems: 'center' as const,
|
|
238
|
+
justifyContent: 'center' as const,
|
|
239
|
+
flexShrink: 0,
|
|
240
|
+
color: theme.colors.text.secondary,
|
|
241
|
+
variants: {
|
|
242
|
+
size: buildSizeVariants(theme, 'input', (size) => ({
|
|
243
|
+
fontSize: size.iconSize,
|
|
244
|
+
width: size.iconSize,
|
|
245
|
+
height: size.iconSize,
|
|
246
|
+
})),
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Create input styles
|
|
253
|
+
*/
|
|
254
|
+
function createInputStyles(theme: Theme) {
|
|
255
|
+
return () => ({
|
|
256
|
+
flex: 1,
|
|
257
|
+
minWidth: 0,
|
|
258
|
+
backgroundColor: 'transparent',
|
|
259
|
+
color: theme.colors.text.primary,
|
|
260
|
+
fontWeight: '400' as const,
|
|
261
|
+
variants: {
|
|
262
|
+
size: buildSizeVariants(theme, 'input', (size) => ({
|
|
263
|
+
fontSize: size.fontSize,
|
|
264
|
+
})),
|
|
265
|
+
},
|
|
266
|
+
_web: {
|
|
267
|
+
border: 'none',
|
|
268
|
+
outline: 'none',
|
|
269
|
+
fontFamily: 'inherit',
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
|
|
275
|
+
// transform on native cannot resolve function calls to extract variant structures.
|
|
276
|
+
export const inputStyles = StyleSheet.create((theme: Theme) => {
|
|
277
|
+
// Apply extensions to main visual elements
|
|
278
|
+
|
|
279
|
+
return applyExtensions('Input', theme, {container: createContainerStyles(theme),
|
|
280
|
+
input: createInputStyles(theme),
|
|
281
|
+
// Additional styles (merged from return)
|
|
282
|
+
// Minor utility styles (not extended)
|
|
283
|
+
leftIconContainer: createLeftIconContainerStyles(theme)(),
|
|
284
|
+
rightIconContainer: createRightIconContainerStyles(theme)(),
|
|
285
|
+
leftIcon: createLeftIconStyles(theme)(),
|
|
286
|
+
rightIcon: createRightIconStyles(theme)(),
|
|
287
|
+
passwordToggle: createPasswordToggleStyles(theme)(),
|
|
288
|
+
passwordToggleIcon: createPasswordToggleIconStyles(theme)()});
|
|
289
|
+
});
|