@idealyst/components 1.2.68 → 1.2.70

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/README.md CHANGED
@@ -99,7 +99,7 @@ The library includes 14 core components organized by category:
99
99
 
100
100
  | Component | Description | Key Props |
101
101
  |-----------|-------------|-----------|
102
- | **[Card](src/Card/README.md)** | Container for grouped content | `variant`, `padding`, `clickable`, `intent` |
102
+ | **[Card](src/Card/README.md)** | Container for grouped content | `variant`, `padding`, `onPress`, `intent` |
103
103
  | **[Badge](src/Badge/README.md)** | Status indicators and count displays | `variant`, `color`, `size` |
104
104
  | **[Avatar](src/Avatar/README.md)** | User profile pictures with fallback | `src`, `fallback`, `size`, `shape` |
105
105
 
@@ -175,7 +175,7 @@ import { View, Card, Text, Avatar, Badge } from '@idealyst/components';
175
175
 
176
176
  <View spacing="md">
177
177
  {items.map(item => (
178
- <Card key={item.id} clickable onPress={() => navigate(item)}>
178
+ <Card key={item.id} onPress={() => navigate(item)}>
179
179
  <View spacing="sm" style={{ flexDirection: 'row', alignItems: 'center' }}>
180
180
  <Avatar src={item.avatar} fallback={item.initials} />
181
181
  <View spacing="xs" style={{ flex: 1 }}>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/components",
3
- "version": "1.2.68",
3
+ "version": "1.2.70",
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.68",
59
+ "@idealyst/theme": "^1.2.70",
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.68",
110
+ "@idealyst/theme": "^1.2.70",
111
111
  "@idealyst/tooling": "^1.2.30",
112
112
  "@mdi/react": "^1.6.1",
113
113
  "@types/react": "^19.1.0",
@@ -1,21 +1,16 @@
1
- import { forwardRef, useMemo, useEffect, useRef } from 'react';
1
+ import { forwardRef, useMemo } from 'react';
2
2
  import { View, Pressable } from 'react-native';
3
3
  import { CardProps } from './types';
4
4
  import { cardStyles } from './Card.styles';
5
5
  import { getNativeInteractiveAccessibilityProps } from '../utils/accessibility';
6
6
  import type { IdealystElement } from '../utils/refTypes';
7
7
 
8
- // Track if we've logged the onClick deprecation warning (log once per session)
9
- let hasLoggedOnClickWarning = false;
10
-
11
8
  const Card = forwardRef<IdealystElement, CardProps>(({
12
9
  children,
13
10
  type = 'elevated',
14
11
  radius = 'md',
15
12
  intent: _intent = 'neutral',
16
- clickable = false,
17
13
  onPress,
18
- onClick,
19
14
  disabled = false,
20
15
  onLayout,
21
16
  // Spacing variants from ContainerStyleProps
@@ -38,21 +33,8 @@ const Card = forwardRef<IdealystElement, CardProps>(({
38
33
  accessibilityRole,
39
34
  accessibilityPressed,
40
35
  }, ref) => {
41
- const hasWarnedRef = useRef(false);
42
-
43
- // Warn about onClick usage (deprecated)
44
- useEffect(() => {
45
- if (onClick && !hasWarnedRef.current && !hasLoggedOnClickWarning) {
46
- hasWarnedRef.current = true;
47
- hasLoggedOnClickWarning = true;
48
- console.warn(
49
- '[Card] onClick is deprecated. Use onPress instead.\n' +
50
- 'Card is a cross-platform component that follows React Native conventions.\n' +
51
- 'onClick will be removed in a future version.\n\n' +
52
- 'Migration: Replace onClick={handler} with onPress={handler}'
53
- );
54
- }
55
- }, [onClick]);
36
+ // Derive pressable from whether onPress is provided
37
+ const pressable = !!onPress;
56
38
 
57
39
  // Generate native accessibility props
58
40
  const nativeA11yProps = useMemo(() => {
@@ -61,16 +43,16 @@ const Card = forwardRef<IdealystElement, CardProps>(({
61
43
  accessibilityHint,
62
44
  accessibilityDisabled: accessibilityDisabled ?? disabled,
63
45
  accessibilityHidden,
64
- accessibilityRole: accessibilityRole ?? (clickable ? 'button' : 'none'),
46
+ accessibilityRole: accessibilityRole ?? (pressable ? 'button' : 'none'),
65
47
  accessibilityPressed,
66
48
  });
67
- }, [accessibilityLabel, accessibilityHint, accessibilityDisabled, disabled, accessibilityHidden, accessibilityRole, clickable, accessibilityPressed]);
49
+ }, [accessibilityLabel, accessibilityHint, accessibilityDisabled, disabled, accessibilityHidden, accessibilityRole, pressable, accessibilityPressed]);
68
50
 
69
51
  // Apply variants
70
52
  cardStyles.useVariants({
71
53
  type,
72
54
  radius,
73
- clickable,
55
+ pressable,
74
56
  disabled,
75
57
  background,
76
58
  gap,
@@ -85,11 +67,8 @@ const Card = forwardRef<IdealystElement, CardProps>(({
85
67
  // Get card style
86
68
  const cardStyle = (cardStyles.card as any)({});
87
69
 
88
- // Use appropriate component based on clickable state
89
- const Component = clickable ? Pressable : View;
90
-
91
- // Prefer onPress, fall back to deprecated onClick
92
- const pressHandler = onPress ?? onClick;
70
+ // Use appropriate component based on pressable state
71
+ const Component = pressable ? Pressable : View;
93
72
 
94
73
  const componentProps = {
95
74
  ref,
@@ -98,8 +77,8 @@ const Card = forwardRef<IdealystElement, CardProps>(({
98
77
  testID,
99
78
  onLayout,
100
79
  ...nativeA11yProps,
101
- ...(clickable && {
102
- onPress: disabled ? undefined : pressHandler,
80
+ ...(pressable && {
81
+ onPress: disabled ? undefined : onPress,
103
82
  disabled,
104
83
  android_ripple: { color: 'rgba(0, 0, 0, 0.1)' },
105
84
  }),
@@ -20,7 +20,7 @@ export type CardVariants = {
20
20
  type: CardType;
21
21
  radius: Radius;
22
22
  intent: CardIntent;
23
- clickable: boolean;
23
+ pressable: boolean;
24
24
  disabled: boolean;
25
25
  background: Surface;
26
26
  gap: CardPadding;
@@ -78,7 +78,7 @@ export const cardStyles = defineStyle('Card', (theme: Theme) => ({
78
78
  background: {
79
79
  backgroundColor: theme.colors.$surface,
80
80
  },
81
- clickable: {
81
+ pressable: {
82
82
  true: {
83
83
  _web: {
84
84
  cursor: 'pointer',
@@ -1,4 +1,4 @@
1
- import { forwardRef, useMemo, useEffect, useRef } from 'react';
1
+ import { forwardRef, useMemo } from 'react';
2
2
  import { getWebProps } from 'react-native-unistyles/web';
3
3
  import { CardProps } from './types';
4
4
  import { cardStyles } from './Card.styles';
@@ -8,12 +8,9 @@ import { getWebInteractiveAriaProps } from '../utils/accessibility';
8
8
  import type { IdealystElement } from '../utils/refTypes';
9
9
  import { flattenStyle } from '../utils/flattenStyle';
10
10
 
11
- // Track if we've logged the onClick deprecation warning (log once per session)
12
- let hasLoggedOnClickWarning = false;
13
-
14
11
  /**
15
12
  * Container component for grouping related content with elevation and styling options.
16
- * Supports elevated, outlined, and filled variants with optional click interaction.
13
+ * Supports elevated, outlined, and filled variants with optional press interaction.
17
14
  */
18
15
  const Card = forwardRef<IdealystElement, CardProps>(({
19
16
  children,
@@ -22,9 +19,7 @@ const Card = forwardRef<IdealystElement, CardProps>(({
22
19
  radius = 'md',
23
20
  intent: _intent,
24
21
  background,
25
- clickable = false,
26
22
  onPress,
27
- onClick,
28
23
  disabled = false,
29
24
  onLayout,
30
25
  // Spacing variants from ContainerStyleProps
@@ -46,25 +41,14 @@ const Card = forwardRef<IdealystElement, CardProps>(({
46
41
  accessibilityRole,
47
42
  accessibilityPressed,
48
43
  }, ref) => {
49
- const hasWarnedRef = useRef(false);
50
44
  const layoutRef = useWebLayout<HTMLElement>(onLayout);
51
45
 
52
- // Warn about onClick usage (deprecated)
53
- useEffect(() => {
54
- if (onClick && !hasWarnedRef.current && !hasLoggedOnClickWarning) {
55
- hasWarnedRef.current = true;
56
- hasLoggedOnClickWarning = true;
57
- console.warn(
58
- '[Card] onClick is deprecated. Use onPress instead.\n' +
59
- 'Card is a cross-platform component that follows React Native conventions.\n' +
60
- 'onClick will be removed in a future version.\n\n' +
61
- 'Migration: Replace onClick={handler} with onPress={handler}'
62
- );
63
- }
64
- }, [onClick]);
46
+ // Derive pressable from whether onPress is provided
47
+ const pressable = !!onPress;
65
48
 
66
49
  // variant is an alias for type - variant takes precedence if both are set
67
50
  const type = variant ?? typeProp ?? 'elevated';
51
+
68
52
  // Generate ARIA props
69
53
  const ariaProps = useMemo(() => {
70
54
  return getWebInteractiveAriaProps({
@@ -72,20 +56,16 @@ const Card = forwardRef<IdealystElement, CardProps>(({
72
56
  accessibilityHint,
73
57
  accessibilityDisabled: accessibilityDisabled ?? disabled,
74
58
  accessibilityHidden,
75
- accessibilityRole: accessibilityRole ?? (clickable ? 'button' : 'region'),
59
+ accessibilityRole: accessibilityRole ?? (pressable ? 'button' : 'region'),
76
60
  accessibilityPressed,
77
61
  });
78
- }, [accessibilityLabel, accessibilityHint, accessibilityDisabled, disabled, accessibilityHidden, accessibilityRole, clickable, accessibilityPressed]);
62
+ }, [accessibilityLabel, accessibilityHint, accessibilityDisabled, disabled, accessibilityHidden, accessibilityRole, pressable, accessibilityPressed]);
79
63
 
80
64
  const handleClick = (e: React.MouseEvent) => {
81
65
  e.preventDefault();
82
66
  e.stopPropagation();
83
- if (!disabled && clickable) {
84
- // Prefer onPress, fall back to deprecated onClick
85
- const handler = onPress ?? onClick;
86
- if (handler) {
87
- handler();
88
- }
67
+ if (!disabled && onPress) {
68
+ onPress();
89
69
  }
90
70
  };
91
71
 
@@ -93,7 +73,7 @@ const Card = forwardRef<IdealystElement, CardProps>(({
93
73
  cardStyles.useVariants({
94
74
  type,
95
75
  radius,
96
- clickable,
76
+ pressable,
97
77
  disabled,
98
78
  background,
99
79
  gap,
@@ -113,8 +93,8 @@ const Card = forwardRef<IdealystElement, CardProps>(({
113
93
 
114
94
  const mergedRef = useMergeRefs(ref, webProps.ref, layoutRef);
115
95
 
116
- // Use appropriate HTML element based on clickable state
117
- const Component: any = clickable ? 'button' : 'div';
96
+ // Use appropriate HTML element based on pressable state
97
+ const Component: any = pressable ? 'button' : 'div';
118
98
 
119
99
  return (
120
100
  <Component
@@ -122,8 +102,8 @@ const Card = forwardRef<IdealystElement, CardProps>(({
122
102
  {...ariaProps}
123
103
  ref={mergedRef as any}
124
104
  id={id}
125
- onClick={clickable ? handleClick : undefined}
126
- disabled={clickable && disabled}
105
+ onClick={pressable ? handleClick : undefined}
106
+ disabled={pressable && disabled}
127
107
  data-testid={testID}
128
108
  >
129
109
  {children}
package/src/Card/types.ts CHANGED
@@ -47,30 +47,11 @@ export interface CardProps extends ContainerStyleProps, InteractiveAccessibility
47
47
  background?: CardBackgroundVariant;
48
48
 
49
49
  /**
50
- * Whether the card is clickable
51
- */
52
- clickable?: boolean;
53
-
54
- /**
55
- * Called when the card is pressed (if clickable)
50
+ * Called when the card is pressed.
51
+ * Providing this prop automatically makes the card pressable with hover effects.
56
52
  */
57
53
  onPress?: () => void;
58
54
 
59
- /**
60
- * @deprecated Use `onPress` instead. This is a cross-platform component - use React Native conventions.
61
- *
62
- * Using `onClick` will trigger a console warning in development.
63
- * This prop exists only for migration convenience and will be removed in a future version.
64
- *
65
- * @example
66
- * // ❌ Don't use onClick
67
- * <Card clickable onClick={() => {}} />
68
- *
69
- * // ✅ Use onPress instead
70
- * <Card clickable onPress={() => {}} />
71
- */
72
- onClick?: () => void;
73
-
74
55
  /**
75
56
  * Whether the card is disabled
76
57
  */
@@ -63,15 +63,8 @@ const IconButton = forwardRef<IdealystElement, IconButtonProps>((props, ref) =>
63
63
  // Gradient is only applicable to contained buttons
64
64
  const showGradient = gradient && type === 'contained';
65
65
 
66
- // Map button size to icon size
67
- const iconSizeMap: Record<string, number> = {
68
- xs: 12,
69
- sm: 14,
70
- md: 16,
71
- lg: 18,
72
- xl: 20,
73
- };
74
- const iconSize = iconSizeMap[size] ?? 16;
66
+ // Get icon size from the computed icon style (from theme)
67
+ const iconSize = iconStyle?.width ?? iconStyle?.height ?? 24;
75
68
 
76
69
  // Generate native accessibility props
77
70
  const nativeA11yProps = useMemo(() => {
@@ -209,7 +202,10 @@ const IconButton = forwardRef<IdealystElement, IconButtonProps>((props, ref) =>
209
202
  </View>
210
203
  </View>
211
204
  )}
212
- {renderIcon()}
205
+ {/* Centered icon container */}
206
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
207
+ {renderIcon()}
208
+ </View>
213
209
  </TouchableOpacity>
214
210
  );
215
211
  });
@@ -117,9 +117,13 @@ const IconButton = forwardRef<IdealystElement, IconButtonProps>((props, ref) =>
117
117
  const webProps = getWebProps(buttonStyleArray);
118
118
 
119
119
  // Icon styles with dynamic function
120
- const iconStyleArray = [(iconButtonStyles.icon as any)(dynamicProps)];
120
+ const iconStyleComputed = (iconButtonStyles.icon as any)(dynamicProps);
121
+ const iconStyleArray = [iconStyleComputed];
121
122
  const iconProps = getWebProps(iconStyleArray);
122
123
 
124
+ // Extract icon size from computed styles for explicit sizing
125
+ const iconSize = iconStyleComputed?.width ?? iconStyleComputed?.height ?? 24;
126
+
123
127
  // Spinner styles that match the icon color
124
128
  const spinnerStyleArray = [(iconButtonStyles.spinner as any)(dynamicProps)];
125
129
  const spinnerProps = getWebProps(spinnerStyleArray);
@@ -130,6 +134,7 @@ const IconButton = forwardRef<IdealystElement, IconButtonProps>((props, ref) =>
130
134
  return (
131
135
  <IconSvg
132
136
  name={icon}
137
+ size={iconSize}
133
138
  {...iconProps}
134
139
  aria-label={icon}
135
140
  />
@@ -1,10 +1,11 @@
1
- import { useState, forwardRef, useMemo, useEffect } from 'react';
2
- import { View, TextInput, NativeSyntheticEvent, TextInputContentSizeChangeEventData } from 'react-native';
1
+ import { useState, forwardRef, useMemo, useEffect, useRef } from 'react';
2
+ import { View, TextInput, NativeSyntheticEvent, TextInputContentSizeChangeEventData, TextInput as RNTextInput } from 'react-native';
3
3
  import { textAreaStyles } from './TextArea.styles';
4
4
  import Text from '../Text';
5
5
  import type { TextAreaProps } from './types';
6
6
  import { getNativeFormAccessibilityProps } from '../utils/accessibility';
7
7
  import type { IdealystElement } from '../utils/refTypes';
8
+ import useMergeRefs from '../hooks/useMergeRefs';
8
9
 
9
10
  const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
10
11
  value: controlledValue,
@@ -47,8 +48,17 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
47
48
  const [internalValue, setInternalValue] = useState(defaultValue);
48
49
  const [isFocused, setIsFocused] = useState(false);
49
50
  const [contentHeight, setContentHeight] = useState<number | undefined>(undefined);
51
+ const textInputRef = useRef<RNTextInput>(null);
50
52
 
51
53
  const value = controlledValue !== undefined ? controlledValue : internalValue;
54
+
55
+ // When value is cleared externally, trigger a layout recalculation via setNativeProps
56
+ useEffect(() => {
57
+ if (autoGrow && value === '' && textInputRef.current) {
58
+ // Force the TextInput to recalculate its layout by setting the text again
59
+ textInputRef.current.setNativeProps({ text: '' });
60
+ }
61
+ }, [value, autoGrow]);
52
62
  const hasError = Boolean(error);
53
63
 
54
64
  // Generate native accessibility props
@@ -141,14 +151,6 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
141
151
  setContentHeight(newHeight);
142
152
  };
143
153
 
144
- // Reset contentHeight when value is cleared externally (controlled component)
145
- // This forces the TextInput to recalculate its size
146
- useEffect(() => {
147
- if (autoGrow && value === '') {
148
- setContentHeight(undefined);
149
- }
150
- }, [value, autoGrow]);
151
-
152
154
  const showFooter = (error || helperText) || (showCharacterCount && maxLength);
153
155
 
154
156
  // Get dynamic styles - call as functions for theme reactivity
@@ -171,7 +173,7 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
171
173
 
172
174
  <View style={textareaContainerStyleComputed}>
173
175
  <TextInput
174
- ref={ref as any}
176
+ ref={useMergeRefs(textInputRef, ref as any)}
175
177
  {...nativeA11yProps}
176
178
  style={[
177
179
  textareaStyleComputed,
@@ -180,15 +182,10 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
180
182
  textAlignVertical: autoGrow ? 'center' : 'top',
181
183
  backgroundColor: 'transparent',
182
184
  },
183
- // For autoGrow: use contentHeight if available, otherwise minHeight constraint
185
+ // For autoGrow: don't set height, let it grow naturally with minHeight constraint
184
186
  // For fixed height: use rows-based height
185
187
  autoGrow
186
- ? {
187
- minHeight: minHeight ?? 44,
188
- maxHeight: maxHeight,
189
- // Set explicit height when we have a calculated contentHeight
190
- ...(contentHeight !== undefined && { height: contentHeight }),
191
- }
188
+ ? { minHeight: minHeight ?? 44, maxHeight: maxHeight }
192
189
  : { height: rows * 24 },
193
190
  textareaStyle,
194
191
  ]}
@@ -117,27 +117,25 @@ export const CardExamples = () => {
117
117
  </View>
118
118
  </View>
119
119
 
120
- {/* Clickable Cards */}
120
+ {/* Pressable Cards */}
121
121
  <View gap="md">
122
122
  <Text typography="subtitle1">Interactive Cards</Text>
123
123
  <View gap="sm">
124
- <Card
125
- clickable
126
- onPress={() => handleCardPress('clickable')}
127
- type="outlined"
124
+ <Card
125
+ onPress={() => handleCardPress('pressable')}
126
+ type="outlined"
128
127
  padding="md"
129
128
  >
130
- <Text weight="semibold">Clickable Card</Text>
129
+ <Text weight="semibold">Pressable Card</Text>
131
130
  <Text typography="caption" color="secondary">
132
- Click me to see interaction
131
+ Press me to see interaction
133
132
  </Text>
134
133
  </Card>
135
-
136
- <Card
137
- clickable
138
- disabled
139
- onPress={() => handleCardPress('disabled')}
140
- type="outlined"
134
+
135
+ <Card
136
+ disabled
137
+ onPress={() => handleCardPress('disabled')}
138
+ type="outlined"
141
139
  padding="md"
142
140
  >
143
141
  <Text weight="semibold">Disabled Card</Text>