@idealyst/components 1.2.62 → 1.2.64
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 +17 -2
- package/src/TextArea/TextArea.styles.tsx +40 -2
- package/src/TextArea/TextArea.web.tsx +32 -8
- 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.64",
|
|
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.64",
|
|
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.64",
|
|
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
|
|
|
@@ -133,8 +146,8 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
133
146
|
// Get dynamic styles - call as functions for theme reactivity
|
|
134
147
|
const containerStyleComputed = (textAreaStyles.container as any)({});
|
|
135
148
|
const labelStyleComputed = (textAreaStyles.label as any)({ disabled });
|
|
136
|
-
const textareaContainerStyleComputed = (textAreaStyles.textareaContainer as any)({});
|
|
137
|
-
const textareaStyleComputed = (textAreaStyles.textarea as any)({
|
|
149
|
+
const textareaContainerStyleComputed = (textAreaStyles.textareaContainer as any)({ type, focused: isFocused, hasError, disabled });
|
|
150
|
+
const textareaStyleComputed = (textAreaStyles.textarea as any)({ autoGrow, disabled });
|
|
138
151
|
const footerStyleComputed = (textAreaStyles.footer as any)({});
|
|
139
152
|
const helperTextStyleComputed = (textAreaStyles.helperText as any)({ hasError });
|
|
140
153
|
const characterCountStyleComputed = (textAreaStyles.characterCount as any)({
|
|
@@ -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,
|
|
@@ -134,13 +139,22 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
134
139
|
marginHorizontal,
|
|
135
140
|
});
|
|
136
141
|
|
|
137
|
-
// Get
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const
|
|
142
|
+
// Get dynamic styles - call as functions for theme reactivity
|
|
143
|
+
const containerStyleComputed = (textAreaStyles.container as any)({});
|
|
144
|
+
const labelStyleComputed = (textAreaStyles.label as any)({ disabled });
|
|
145
|
+
const textareaContainerStyleComputed = (textAreaStyles.textareaContainer as any)({ type, focused: isFocused, hasError, disabled });
|
|
146
|
+
const textareaStyleComputed = (textAreaStyles.textarea as any)({ autoGrow, disabled });
|
|
147
|
+
const footerStyleComputed = (textAreaStyles.footer as any)({});
|
|
148
|
+
const helperTextStyleComputed = (textAreaStyles.helperText as any)({ hasError });
|
|
149
|
+
const characterCountStyleComputed = (textAreaStyles.characterCount as any)({ isNearLimit, isAtLimit });
|
|
150
|
+
|
|
151
|
+
// Convert to web props
|
|
152
|
+
const containerProps = getWebProps([containerStyleComputed, style as any]);
|
|
153
|
+
const labelProps = getWebProps([labelStyleComputed]);
|
|
154
|
+
const textareaContainerProps = getWebProps([textareaContainerStyleComputed]);
|
|
155
|
+
const footerProps = getWebProps([footerStyleComputed]);
|
|
156
|
+
const helperTextProps = getWebProps([helperTextStyleComputed]);
|
|
157
|
+
const characterCountProps = getWebProps([characterCountStyleComputed]);
|
|
144
158
|
|
|
145
159
|
const adjustHeight = () => {
|
|
146
160
|
if (!autoGrow || !textareaRef.current) return;
|
|
@@ -182,10 +196,18 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
182
196
|
onChange?.(newValue);
|
|
183
197
|
};
|
|
184
198
|
|
|
199
|
+
const handleFocus = () => {
|
|
200
|
+
setIsFocused(true);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const handleBlur = () => {
|
|
204
|
+
setIsFocused(false);
|
|
205
|
+
};
|
|
206
|
+
|
|
185
207
|
const showFooter = (error || helperText) || (showCharacterCount && maxLength);
|
|
186
208
|
|
|
187
209
|
const computedTextareaProps = getWebProps([
|
|
188
|
-
|
|
210
|
+
textareaStyleComputed,
|
|
189
211
|
textareaStyle as any,
|
|
190
212
|
{ resize } as any, // Apply resize as inline style since it's CSS-only
|
|
191
213
|
minHeight ? { minHeight: `${minHeight}px` } : null,
|
|
@@ -210,6 +232,8 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
|
|
|
210
232
|
ref={mergedTextareaRef}
|
|
211
233
|
value={value}
|
|
212
234
|
onChange={handleChange}
|
|
235
|
+
onFocus={handleFocus}
|
|
236
|
+
onBlur={handleBlur}
|
|
213
237
|
placeholder={placeholder}
|
|
214
238
|
disabled={disabled}
|
|
215
239
|
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
|
*/
|