@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/components",
3
- "version": "1.2.62",
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.62",
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.62",
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)({ intent, disabled, hasError });
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
- 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,
@@ -134,13 +139,22 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
134
139
  marginHorizontal,
135
140
  });
136
141
 
137
- // Get static styles (cast to any for Unistyles variant compatibility)
138
- const containerProps = getWebProps([textAreaStyles.container as any, style as any]);
139
- const labelProps = getWebProps([textAreaStyles.label as any]);
140
- const textareaContainerProps = getWebProps([textAreaStyles.textareaContainer as any]);
141
- const footerProps = getWebProps([textAreaStyles.footer as any]);
142
- const helperTextProps = getWebProps([textAreaStyles.helperText as any]);
143
- const characterCountProps = getWebProps([textAreaStyles.characterCount as any]);
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
- textAreaStyles.textarea as any,
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}
@@ -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
  */