@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/components",
3
- "version": "1.2.61",
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.61",
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.61",
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
- border: `1px solid ${theme.colors.border.primary}`,
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}
@@ -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}
@@ -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
  */