@idealyst/components 1.2.61 → 1.2.63
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 +3 -3
- package/src/TextArea/TextArea.native.tsx +15 -0
- package/src/TextArea/TextArea.styles.tsx +40 -2
- package/src/TextArea/TextArea.web.tsx +15 -0
- package/src/TextArea/types.ts +6 -0
- package/src/TextInput/TextInput.native.tsx +11 -0
- package/src/TextInput/TextInput.web.tsx +10 -0
- package/src/TextInput/types.ts +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/components",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.63",
|
|
4
4
|
"description": "Shared component library for React and React Native",
|
|
5
5
|
"documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/components#readme",
|
|
6
6
|
"readme": "README.md",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"publish:npm": "npm publish"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@idealyst/theme": "^1.2.
|
|
59
|
+
"@idealyst/theme": "^1.2.63",
|
|
60
60
|
"@mdi/js": ">=7.0.0",
|
|
61
61
|
"@mdi/react": ">=1.0.0",
|
|
62
62
|
"@react-native-vector-icons/common": ">=12.0.0",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
109
|
"@idealyst/blur": "^1.2.40",
|
|
110
|
-
"@idealyst/theme": "^1.2.
|
|
110
|
+
"@idealyst/theme": "^1.2.63",
|
|
111
111
|
"@idealyst/tooling": "^1.2.30",
|
|
112
112
|
"@mdi/react": "^1.6.1",
|
|
113
113
|
"@types/react": "^19.1.0",
|
|
@@ -23,6 +23,7 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
23
23
|
showCharacterCount = false,
|
|
24
24
|
intent = 'primary',
|
|
25
25
|
size = 'md',
|
|
26
|
+
type = 'outlined',
|
|
26
27
|
// Spacing variants from FormInputStyleProps
|
|
27
28
|
margin,
|
|
28
29
|
marginVertical,
|
|
@@ -44,6 +45,7 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
44
45
|
accessibilityErrorMessage,
|
|
45
46
|
}, ref) => {
|
|
46
47
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
48
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
47
49
|
const [contentHeight, setContentHeight] = useState<number | undefined>(undefined);
|
|
48
50
|
|
|
49
51
|
const value = controlledValue !== undefined ? controlledValue : internalValue;
|
|
@@ -89,8 +91,11 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
89
91
|
textAreaStyles.useVariants({
|
|
90
92
|
size,
|
|
91
93
|
intent,
|
|
94
|
+
type,
|
|
95
|
+
focused: isFocused,
|
|
92
96
|
disabled,
|
|
93
97
|
hasError,
|
|
98
|
+
autoGrow,
|
|
94
99
|
resize: 'none',
|
|
95
100
|
isNearLimit: maxLength ? value.length >= maxLength * 0.9 : false,
|
|
96
101
|
isAtLimit: maxLength ? value.length >= maxLength : false,
|
|
@@ -111,6 +116,14 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
111
116
|
onChange?.(newValue);
|
|
112
117
|
};
|
|
113
118
|
|
|
119
|
+
const handleFocus = () => {
|
|
120
|
+
setIsFocused(true);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const handleBlur = () => {
|
|
124
|
+
setIsFocused(false);
|
|
125
|
+
};
|
|
126
|
+
|
|
114
127
|
const handleContentSizeChange = (e: NativeSyntheticEvent<TextInputContentSizeChangeEventData>) => {
|
|
115
128
|
if (!autoGrow) return;
|
|
116
129
|
|
|
@@ -164,6 +177,8 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
164
177
|
]}
|
|
165
178
|
value={value}
|
|
166
179
|
onChangeText={handleChange}
|
|
180
|
+
onFocus={handleFocus}
|
|
181
|
+
onBlur={handleBlur}
|
|
167
182
|
onContentSizeChange={handleContentSizeChange}
|
|
168
183
|
placeholder={placeholder}
|
|
169
184
|
editable={!disabled}
|
|
@@ -12,11 +12,16 @@ void StyleSheet;
|
|
|
12
12
|
// Wrap theme for $iterator support
|
|
13
13
|
type Theme = ThemeStyleWrapper<BaseTheme>;
|
|
14
14
|
|
|
15
|
+
type TextAreaType = 'outlined' | 'filled' | 'bare';
|
|
16
|
+
|
|
15
17
|
export type TextAreaVariants = {
|
|
16
18
|
size: Size;
|
|
17
19
|
intent: Intent;
|
|
20
|
+
type: TextAreaType;
|
|
21
|
+
focused: boolean;
|
|
18
22
|
disabled: boolean;
|
|
19
23
|
hasError: boolean;
|
|
24
|
+
autoGrow: boolean;
|
|
20
25
|
isNearLimit: boolean;
|
|
21
26
|
isAtLimit: boolean;
|
|
22
27
|
margin?: ViewStyleSize;
|
|
@@ -70,19 +75,45 @@ export const textAreaStyles = defineStyle('TextArea', (theme: Theme) => ({
|
|
|
70
75
|
position: 'relative' as const,
|
|
71
76
|
width: '100%',
|
|
72
77
|
borderWidth: 1,
|
|
78
|
+
borderStyle: 'solid' as const,
|
|
73
79
|
borderColor: theme.colors.border.primary,
|
|
74
80
|
borderRadius: theme.radii.md,
|
|
75
81
|
backgroundColor: theme.colors.surface.primary,
|
|
76
82
|
overflow: 'hidden' as const,
|
|
77
83
|
variants: {
|
|
84
|
+
type: {
|
|
85
|
+
outlined: {
|
|
86
|
+
backgroundColor: theme.colors.surface.primary,
|
|
87
|
+
borderColor: theme.colors.border.primary,
|
|
88
|
+
},
|
|
89
|
+
filled: {
|
|
90
|
+
backgroundColor: theme.colors.surface.secondary,
|
|
91
|
+
borderColor: 'transparent',
|
|
92
|
+
},
|
|
93
|
+
bare: {
|
|
94
|
+
backgroundColor: 'transparent',
|
|
95
|
+
borderWidth: 0,
|
|
96
|
+
borderColor: 'transparent',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
focused: {
|
|
100
|
+
true: {
|
|
101
|
+
borderColor: theme.intents.primary.primary,
|
|
102
|
+
},
|
|
103
|
+
false: {},
|
|
104
|
+
},
|
|
78
105
|
disabled: {
|
|
79
|
-
true: { opacity: 0.8 },
|
|
106
|
+
true: { opacity: 0.8, _web: { cursor: 'not-allowed' } },
|
|
80
107
|
false: { opacity: 1 },
|
|
81
108
|
},
|
|
109
|
+
hasError: {
|
|
110
|
+
true: { borderColor: theme.intents.danger.primary },
|
|
111
|
+
false: {},
|
|
112
|
+
},
|
|
82
113
|
},
|
|
83
114
|
_web: {
|
|
84
115
|
boxSizing: 'border-box',
|
|
85
|
-
|
|
116
|
+
transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
|
|
86
117
|
},
|
|
87
118
|
}),
|
|
88
119
|
|
|
@@ -98,6 +129,13 @@ export const textAreaStyles = defineStyle('TextArea', (theme: Theme) => ({
|
|
|
98
129
|
lineHeight: theme.sizes.$textarea.lineHeight,
|
|
99
130
|
minHeight: theme.sizes.$textarea.minHeight,
|
|
100
131
|
},
|
|
132
|
+
autoGrow: {
|
|
133
|
+
true: {
|
|
134
|
+
// Use input height as minHeight when autoGrow is enabled
|
|
135
|
+
minHeight: theme.sizes.$input.height,
|
|
136
|
+
},
|
|
137
|
+
false: {},
|
|
138
|
+
},
|
|
101
139
|
disabled: {
|
|
102
140
|
true: {
|
|
103
141
|
opacity: 0.5,
|
|
@@ -28,6 +28,7 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
28
28
|
showCharacterCount = false,
|
|
29
29
|
intent = 'primary',
|
|
30
30
|
size = 'md',
|
|
31
|
+
type = 'outlined',
|
|
31
32
|
// Spacing variants from FormInputStyleProps
|
|
32
33
|
margin,
|
|
33
34
|
marginVertical,
|
|
@@ -55,6 +56,7 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
55
56
|
accessibilityAutoComplete,
|
|
56
57
|
}, ref) => {
|
|
57
58
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
59
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
58
60
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
59
61
|
|
|
60
62
|
const value = controlledValue !== undefined ? controlledValue : internalValue;
|
|
@@ -125,8 +127,11 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
125
127
|
textAreaStyles.useVariants({
|
|
126
128
|
size,
|
|
127
129
|
intent,
|
|
130
|
+
type,
|
|
131
|
+
focused: isFocused,
|
|
128
132
|
disabled,
|
|
129
133
|
hasError,
|
|
134
|
+
autoGrow,
|
|
130
135
|
isNearLimit,
|
|
131
136
|
isAtLimit,
|
|
132
137
|
margin,
|
|
@@ -182,6 +187,14 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
182
187
|
onChange?.(newValue);
|
|
183
188
|
};
|
|
184
189
|
|
|
190
|
+
const handleFocus = () => {
|
|
191
|
+
setIsFocused(true);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const handleBlur = () => {
|
|
195
|
+
setIsFocused(false);
|
|
196
|
+
};
|
|
197
|
+
|
|
185
198
|
const showFooter = (error || helperText) || (showCharacterCount && maxLength);
|
|
186
199
|
|
|
187
200
|
const computedTextareaProps = getWebProps([
|
|
@@ -210,6 +223,8 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
210
223
|
ref={mergedTextareaRef}
|
|
211
224
|
value={value}
|
|
212
225
|
onChange={handleChange}
|
|
226
|
+
onFocus={handleFocus}
|
|
227
|
+
onBlur={handleBlur}
|
|
213
228
|
placeholder={placeholder}
|
|
214
229
|
disabled={disabled}
|
|
215
230
|
rows={autoGrow ? undefined : rows}
|
package/src/TextArea/types.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { FormAccessibilityProps } from '../utils/accessibility';
|
|
|
7
7
|
export type TextAreaIntentVariant = Intent;
|
|
8
8
|
export type TextAreaSizeVariant = Size;
|
|
9
9
|
export type TextAreaResizeVariant = 'none' | 'vertical' | 'horizontal' | 'both';
|
|
10
|
+
export type TextAreaType = 'outlined' | 'filled' | 'bare';
|
|
10
11
|
|
|
11
12
|
export interface TextAreaProps extends FormInputStyleProps, FormAccessibilityProps {
|
|
12
13
|
value?: string;
|
|
@@ -26,6 +27,11 @@ export interface TextAreaProps extends FormInputStyleProps, FormAccessibilityPro
|
|
|
26
27
|
showCharacterCount?: boolean;
|
|
27
28
|
intent?: TextAreaIntentVariant;
|
|
28
29
|
size?: TextAreaSizeVariant;
|
|
30
|
+
/**
|
|
31
|
+
* Visual style type of the textarea
|
|
32
|
+
* @default 'outlined'
|
|
33
|
+
*/
|
|
34
|
+
type?: TextAreaType;
|
|
29
35
|
style?: StyleProp<ViewStyle>;
|
|
30
36
|
textareaStyle?: StyleProp<TextStyle>;
|
|
31
37
|
testID?: string;
|
|
@@ -83,6 +83,9 @@ const TextInput = React.forwardRef<IdealystElement, TextInputProps>(({
|
|
|
83
83
|
accessibilityRole,
|
|
84
84
|
accessibilityRequired,
|
|
85
85
|
accessibilityInvalid,
|
|
86
|
+
// Submit handling
|
|
87
|
+
onSubmitEditing,
|
|
88
|
+
returnKeyType = 'default',
|
|
86
89
|
}, ref) => {
|
|
87
90
|
const [isFocused, setIsFocused] = useState(false);
|
|
88
91
|
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
|
@@ -141,6 +144,10 @@ const TextInput = React.forwardRef<IdealystElement, TextInputProps>(({
|
|
|
141
144
|
setIsPasswordVisible(!isPasswordVisible);
|
|
142
145
|
};
|
|
143
146
|
|
|
147
|
+
const handleSubmitEditing = useCallback(() => {
|
|
148
|
+
onSubmitEditing?.();
|
|
149
|
+
}, [onSubmitEditing]);
|
|
150
|
+
|
|
144
151
|
// Memoized change handler for InnerTextInput
|
|
145
152
|
const handleChangeText = useCallback((text: string) => {
|
|
146
153
|
internalValueRef.current = text;
|
|
@@ -186,6 +193,8 @@ const TextInput = React.forwardRef<IdealystElement, TextInputProps>(({
|
|
|
186
193
|
autoCapitalize,
|
|
187
194
|
onFocus: handleFocus,
|
|
188
195
|
onBlur: handleBlur,
|
|
196
|
+
onSubmitEditing: handleSubmitEditing,
|
|
197
|
+
returnKeyType,
|
|
189
198
|
placeholderTextColor: '#999999',
|
|
190
199
|
...nativeA11yProps,
|
|
191
200
|
}), [
|
|
@@ -198,6 +207,8 @@ const TextInput = React.forwardRef<IdealystElement, TextInputProps>(({
|
|
|
198
207
|
autoCapitalize,
|
|
199
208
|
handleFocus,
|
|
200
209
|
handleBlur,
|
|
210
|
+
handleSubmitEditing,
|
|
211
|
+
returnKeyType,
|
|
201
212
|
nativeA11yProps,
|
|
202
213
|
]);
|
|
203
214
|
|
|
@@ -55,6 +55,9 @@ const TextInput = React.forwardRef<IdealystElement, TextInputProps>(({
|
|
|
55
55
|
accessibilityInvalid,
|
|
56
56
|
accessibilityErrorMessage,
|
|
57
57
|
accessibilityAutoComplete,
|
|
58
|
+
onSubmitEditing,
|
|
59
|
+
// returnKeyType is native-only, ignored on web
|
|
60
|
+
returnKeyType: _returnKeyType,
|
|
58
61
|
}, ref) => {
|
|
59
62
|
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
|
60
63
|
|
|
@@ -124,6 +127,12 @@ const TextInput = React.forwardRef<IdealystElement, TextInputProps>(({
|
|
|
124
127
|
setIsPasswordVisible(!isPasswordVisible);
|
|
125
128
|
};
|
|
126
129
|
|
|
130
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
131
|
+
if (e.key === 'Enter' && onSubmitEditing) {
|
|
132
|
+
onSubmitEditing();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
127
136
|
// Apply variants (for size, type, and spacing)
|
|
128
137
|
textInputStyles.useVariants({
|
|
129
138
|
size,
|
|
@@ -276,6 +285,7 @@ const TextInput = React.forwardRef<IdealystElement, TextInputProps>(({
|
|
|
276
285
|
onChange={handleChange}
|
|
277
286
|
onFocus={handleFocus}
|
|
278
287
|
onBlur={handleBlur}
|
|
288
|
+
onKeyDown={handleKeyDown}
|
|
279
289
|
placeholder={placeholder}
|
|
280
290
|
disabled={disabled}
|
|
281
291
|
autoCapitalize={autoCapitalize}
|
package/src/TextInput/types.ts
CHANGED
|
@@ -16,6 +16,13 @@ export type TextInputType = 'outlined' | 'filled' | 'bare';
|
|
|
16
16
|
*/
|
|
17
17
|
export type TextInputMode = 'text' | 'email' | 'password' | 'number';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Return key type for mobile keyboard.
|
|
21
|
+
* Controls what the return/submit button on the keyboard displays.
|
|
22
|
+
* @platform native
|
|
23
|
+
*/
|
|
24
|
+
export type ReturnKeyType = 'done' | 'go' | 'next' | 'search' | 'send' | 'default';
|
|
25
|
+
|
|
19
26
|
/**
|
|
20
27
|
* Single-line text input field with support for icons, validation states, and multiple visual styles.
|
|
21
28
|
* Includes built-in password visibility toggle and platform-specific keyboard handling.
|
|
@@ -139,6 +146,20 @@ export interface TextInputProps extends FormInputStyleProps, FormAccessibilityPr
|
|
|
139
146
|
*/
|
|
140
147
|
hasError?: boolean;
|
|
141
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Called when the user submits the input (presses Enter on web, or the return key on mobile).
|
|
151
|
+
* Use this for form submission or moving to the next field.
|
|
152
|
+
*/
|
|
153
|
+
onSubmitEditing?: () => void;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Determines how the return key on mobile keyboard should look.
|
|
157
|
+
* Has no effect on web.
|
|
158
|
+
* @platform native
|
|
159
|
+
* @default 'default'
|
|
160
|
+
*/
|
|
161
|
+
returnKeyType?: ReturnKeyType;
|
|
162
|
+
|
|
142
163
|
/**
|
|
143
164
|
* Additional styles (platform-specific)
|
|
144
165
|
*/
|