@idealyst/components 1.2.119 → 11.2.121

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.119",
3
+ "version": "11.2.121",
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.119",
59
+ "@idealyst/theme": "^11.2.121",
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,8 +107,8 @@
107
107
  },
108
108
  "devDependencies": {
109
109
  "@idealyst/blur": "^1.2.40",
110
- "@idealyst/theme": "^1.2.119",
111
- "@idealyst/tooling": "^1.2.119",
110
+ "@idealyst/theme": "^11.2.121",
111
+ "@idealyst/tooling": "^11.2.121",
112
112
  "@mdi/react": "^1.6.1",
113
113
  "@types/react": "^19.1.0",
114
114
  "react": "^19.1.0",
@@ -18,6 +18,7 @@ const Dialog = forwardRef<View, DialogProps>(({
18
18
  animationType: _animationType = 'fade',
19
19
  avoidKeyboard = false,
20
20
  padding: paddingProp = 20,
21
+ height,
21
22
  maxContentHeight,
22
23
  contentPadding = 24,
23
24
  contentStyle,
@@ -183,14 +184,30 @@ const Dialog = forwardRef<View, DialogProps>(({
183
184
  ? Math.min(maxContentHeight, maxAvailableHeight)
184
185
  : maxAvailableHeight;
185
186
 
186
- // Dialog uses the effective max height, with flex: 1 so children can fill it
187
+ // Resolve explicit height (number or percentage string)
188
+ const resolvedHeight = typeof height === 'string'
189
+ ? height.endsWith('%')
190
+ ? (parseFloat(height) / 100) * maxAvailableHeight
191
+ : parseFloat(height)
192
+ : height;
193
+
194
+ // Dialog uses the effective max height, with a definite height when requested
195
+ // so children can resolve flex: 1 against it
187
196
  const dialogContainerStyle = {
188
197
  ...containerStyle,
189
198
  maxHeight: effectiveMaxHeight,
190
- height: maxContentHeight ? effectiveMaxHeight : undefined,
199
+ height: resolvedHeight
200
+ ? Math.min(resolvedHeight, effectiveMaxHeight)
201
+ : maxContentHeight
202
+ ? effectiveMaxHeight
203
+ : undefined,
191
204
  flex: undefined,
192
205
  };
193
206
 
207
+ // Only apply flex: 1 to content when the dialog has a definite height to flex against.
208
+ // Without a definite height, flex: 1 collapses content instead of wrapping naturally.
209
+ const hasDefiniteHeight = Boolean(resolvedHeight || maxContentHeight);
210
+
194
211
  const dialogContainer = (
195
212
  <Animated.View ref={ref as any} style={[dialogContainerStyle, style, containerAnimatedStyle]} nativeID={id} {...nativeA11yProps}>
196
213
  {(title || showCloseButton) && (
@@ -212,7 +229,7 @@ const Dialog = forwardRef<View, DialogProps>(({
212
229
  )}
213
230
  </View>
214
231
  )}
215
- <View style={[contentPadding > 0 ? { padding: contentPadding } : undefined, contentStyle]}>
232
+ <View style={[hasDefiniteHeight && { flex: 1, minHeight: 0 }, contentPadding > 0 ? { padding: contentPadding } : undefined, contentStyle]}>
216
233
  {children}
217
234
  </View>
218
235
  </Animated.View>
@@ -5,6 +5,7 @@ import {
5
5
  Pressable,
6
6
  ScrollView,
7
7
  } from 'react-native';
8
+ import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
8
9
  import { menuStyles } from './Menu.styles';
9
10
  import type { MenuProps, MenuItem as MenuItemType } from './types';
10
11
  import MenuItem from './MenuItem.native';
@@ -60,10 +61,41 @@ const Menu = forwardRef<IdealystElement, MenuProps>(({
60
61
 
61
62
  const mergedTriggerRef = useMergeRefs(ref, triggerRef);
62
63
 
64
+ // Animation shared values
65
+ const menuScale = useSharedValue(0.9);
66
+ const menuOpacity = useSharedValue(0);
67
+
68
+ const animatedMenuStyle = useAnimatedStyle(() => ({
69
+ opacity: menuOpacity.value,
70
+ transform: [{ scale: menuScale.value }],
71
+ }));
72
+
73
+ // Animate in when measured and positioned
74
+ const isMeasured = menuSize.height > 0;
75
+ const shouldShow = isMeasured && isPositioned;
76
+
77
+ useEffect(() => {
78
+ if (shouldShow) {
79
+ menuScale.value = withTiming(1, {
80
+ duration: 200,
81
+ easing: Easing.out(Easing.cubic),
82
+ });
83
+ menuOpacity.value = withTiming(1, {
84
+ duration: 150,
85
+ easing: Easing.out(Easing.ease),
86
+ });
87
+ } else {
88
+ menuScale.value = 0.9;
89
+ menuOpacity.value = 0;
90
+ }
91
+ }, [shouldShow]);
92
+
63
93
  // Reset position when menu closes
64
94
  useEffect(() => {
65
95
  if (!open) {
66
96
  resetPosition();
97
+ menuScale.value = 0.9;
98
+ menuOpacity.value = 0;
67
99
  }
68
100
  }, [open]);
69
101
 
@@ -93,10 +125,6 @@ const Menu = forwardRef<IdealystElement, MenuProps>(({
93
125
  const renderMenu = () => {
94
126
  if (!open) return null;
95
127
 
96
- // Show menu only after it has been measured AND positioned
97
- const isMeasured = menuSize.height > 0;
98
- const shouldShow = isMeasured && isPositioned;
99
-
100
128
  return (
101
129
  <Modal
102
130
  visible={true}
@@ -115,26 +143,27 @@ const Menu = forwardRef<IdealystElement, MenuProps>(({
115
143
  style={[
116
144
  (menuStyles.menu as any)({}),
117
145
  style,
118
- { opacity: shouldShow ? 1 : 0 }
119
146
  ]}
120
147
  onLayout={handleMenuLayout}
121
148
  >
122
- <ScrollView
123
- showsVerticalScrollIndicator={false}
124
- >
125
- {items.map((item, index) => {
126
-
127
- return (
128
- <MenuItem
129
- key={item.id || index}
130
- item={item}
131
- onPress={handleItemPress}
132
- size={size}
133
- testID={testID ? `${testID}-item-${item.id || index}` : undefined}
134
- />
135
- );
136
- })}
137
- </ScrollView>
149
+ <Animated.View style={animatedMenuStyle}>
150
+ <ScrollView
151
+ showsVerticalScrollIndicator={false}
152
+ >
153
+ {items.map((item, index) => {
154
+
155
+ return (
156
+ <MenuItem
157
+ key={item.id || index}
158
+ item={item}
159
+ onPress={handleItemPress}
160
+ size={size}
161
+ testID={testID ? `${testID}-item-${item.id || index}` : undefined}
162
+ />
163
+ );
164
+ })}
165
+ </ScrollView>
166
+ </Animated.View>
138
167
  </BoundedModalContent>
139
168
  </Pressable>
140
169
  </Modal>
@@ -24,19 +24,28 @@ const MenuItem = forwardRef<IdealystElement, MenuItemProps>(({ item, onPress, si
24
24
  const iconStyle = (menuItemStyles.icon as any)({});
25
25
  const labelStyle = (menuItemStyles.label as any)({});
26
26
 
27
+ // Extract icon size from theme variant (fontSize is set by $menu.iconSize)
28
+ const iconSize = iconStyle.fontSize || iconStyle.width || 20;
29
+
27
30
  const renderIcon = () => {
28
31
  if (!item.icon) return null;
29
32
 
30
33
  if (typeof item.icon === 'string') {
31
34
  return (
32
- <MaterialDesignIcons
33
- name={item.icon as any}
34
- color={iconStyle.color}
35
- style={iconStyle}
36
- />
35
+ <View style={{ width: iconSize, height: iconSize, alignItems: 'center', justifyContent: 'center', marginRight: iconStyle.marginRight || 12, flexShrink: 0 }}>
36
+ <MaterialDesignIcons
37
+ name={item.icon as any}
38
+ size={iconSize}
39
+ color={iconStyle.color}
40
+ />
41
+ </View>
37
42
  );
38
43
  } else if (isValidElement(item.icon)) {
39
- return item.icon;
44
+ return (
45
+ <View style={{ marginRight: iconStyle.marginRight || 12, flexShrink: 0 }}>
46
+ {item.icon}
47
+ </View>
48
+ );
40
49
  }
41
50
  return null;
42
51
  };
@@ -54,12 +63,8 @@ const MenuItem = forwardRef<IdealystElement, MenuItemProps>(({ item, onPress, si
54
63
  android_ripple={{ color: 'rgba(0, 0, 0, 0.1)' }}
55
64
  testID={testID}
56
65
  >
57
- {item.icon && (
58
- <View>
59
- {renderIcon()}
60
- </View>
61
- )}
62
- <Text style={labelStyle}>
66
+ {renderIcon()}
67
+ <Text style={labelStyle} numberOfLines={1}>
63
68
  {item.label}
64
69
  </Text>
65
70
  </Pressable>
@@ -17,6 +17,7 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
17
17
  minHeight,
18
18
  maxHeight,
19
19
  autoGrow = false,
20
+ fill = false,
20
21
  maxLength,
21
22
  rows = 4,
22
23
  label,
@@ -167,12 +168,12 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
167
168
  });
168
169
 
169
170
  return (
170
- <View nativeID={id} style={[containerStyleComputed, style]} testID={testID}>
171
+ <View nativeID={id} style={[containerStyleComputed, fill && { flex: 1 }, style]} testID={testID}>
171
172
  {label && (
172
173
  <Text style={labelStyleComputed}>{label}</Text>
173
174
  )}
174
175
 
175
- <View style={textareaContainerStyleComputed}>
176
+ <View style={[textareaContainerStyleComputed, fill && { flex: 1 }]}>
176
177
  <TextInput
177
178
  ref={useMergeRefs(textInputRef, ref as any)}
178
179
  {...nativeA11yProps}
@@ -183,16 +184,19 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
183
184
  textAlignVertical: autoGrow ? 'center' : 'top',
184
185
  backgroundColor: 'transparent',
185
186
  },
186
- // For autoGrow: don't set height, let it grow naturally with minHeight constraint
187
- // For fixed height: use rows-based height
188
- autoGrow
189
- ? {
190
- minHeight: minHeight ?? 44,
191
- maxHeight: maxHeight,
192
- // Force height to minHeight when empty to ensure shrinking
193
- ...(value === '' ? { height: minHeight ?? 44 } : {}),
194
- }
195
- : { height: rows * 24 },
187
+ // fill: expand to fill available space via flex
188
+ // autoGrow: don't set height, let it grow naturally with minHeight constraint
189
+ // default: use rows-based height
190
+ fill
191
+ ? { flex: 1 }
192
+ : autoGrow
193
+ ? {
194
+ minHeight: minHeight ?? 44,
195
+ maxHeight: maxHeight,
196
+ // Force height to minHeight when empty to ensure shrinking
197
+ ...(value === '' ? { height: minHeight ?? 44 } : {}),
198
+ }
199
+ : { height: rows * 24 },
196
200
  textareaStyle,
197
201
  ]}
198
202
  value={value}
@@ -21,6 +21,7 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
21
21
  minHeight,
22
22
  maxHeight,
23
23
  autoGrow = false,
24
+ fill = false,
24
25
  maxLength,
25
26
  label,
26
27
  error,
@@ -158,7 +159,7 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
158
159
  const characterCountProps = getWebProps([characterCountStyleComputed]);
159
160
 
160
161
  const adjustHeight = useCallback(() => {
161
- if (!autoGrow || !textareaRef.current) return;
162
+ if (!autoGrow || fill || !textareaRef.current) return;
162
163
 
163
164
  const textarea = textareaRef.current;
164
165
 
@@ -176,7 +177,7 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
176
177
  }
177
178
 
178
179
  textarea.style.height = `${newHeight}px`;
179
- }, [autoGrow, minHeight, maxHeight]);
180
+ }, [autoGrow, fill, minHeight, maxHeight]);
180
181
 
181
182
  useEffect(() => {
182
183
  adjustHeight();
@@ -243,17 +244,18 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
243
244
  const mergedTextareaRef = useMergeRefs(textareaRef, computedTextareaProps.ref);
244
245
 
245
246
  return (
246
- <div {...containerProps} ref={mergedRef} id={id} data-testid={testID}>
247
+ <div {...containerProps} ref={mergedRef} id={id} data-testid={testID} style={{ ...containerProps.style as any, ...(fill ? { flex: 1, display: 'flex', flexDirection: 'column' } : {}) }}>
247
248
  {label && (
248
249
  <label {...labelProps} id={labelId} htmlFor={textareaId}>{label}</label>
249
250
  )}
250
251
 
251
- <div {...textareaContainerProps}>
252
+ <div {...textareaContainerProps} style={{ ...textareaContainerProps.style as any, ...(fill ? { flex: 1, display: 'flex', flexDirection: 'column' } : {}) }}>
252
253
  <textarea
253
254
  {...computedTextareaProps}
254
255
  {...ariaProps}
255
256
  id={textareaId}
256
257
  ref={mergedTextareaRef}
258
+ style={{ ...computedTextareaProps.style as any, ...(fill ? { flex: 1 } : {}) }}
257
259
  value={value}
258
260
  onChange={handleChange}
259
261
  onFocus={handleFocus}
@@ -261,7 +263,7 @@ const TextArea = forwardRef<IdealystElement, TextAreaProps>(({
261
263
  onKeyDown={handleKeyDown}
262
264
  placeholder={placeholder}
263
265
  disabled={disabled}
264
- rows={autoGrow ? undefined : rows}
266
+ rows={fill ? undefined : autoGrow ? undefined : rows}
265
267
  maxLength={maxLength}
266
268
  />
267
269
  </div>
@@ -44,6 +44,11 @@ export interface TextAreaProps extends FormInputStyleProps, FormAccessibilityPro
44
44
  minHeight?: number;
45
45
  maxHeight?: number;
46
46
  autoGrow?: boolean;
47
+ /**
48
+ * When true, the textarea expands to fill available vertical space using flex.
49
+ * Overrides `rows` and `autoGrow` height logic. All container layers get `flex: 1`.
50
+ */
51
+ fill?: boolean;
47
52
  maxLength?: number;
48
53
  label?: string;
49
54
  error?: string;
@@ -93,4 +93,5 @@ export type {
93
93
  ImageStyleableElements,
94
94
  PressableStyleableElements,
95
95
  ScreenStyleableElements,
96
+ DatePickerCalendarStyleableElements,
96
97
  } from './types';
@@ -426,6 +426,37 @@ export type ScreenStyleableElements = {
426
426
  content: Styles;
427
427
  };
428
428
 
429
+ /**
430
+ * DatePickerCalendar styleable elements.
431
+ * @see datePickerCalendarStyles in @idealyst/datepicker
432
+ */
433
+ export type DatePickerCalendarStyleableElements = {
434
+ calendar: ElementStyle;
435
+ calendarHeader: ElementStyle;
436
+ calendarTitle: ElementStyle;
437
+ weekdayRow: ElementStyle;
438
+ weekdayCell: ElementStyle;
439
+ weekdayText: ElementStyle;
440
+ calendarGrid: ElementStyle;
441
+ dayCell: ElementStyle;
442
+ dayButton: ElementStyle;
443
+ dayText: ElementStyle;
444
+ selectedDay: ElementStyle;
445
+ selectedDayText: ElementStyle;
446
+ todayDay: ElementStyle;
447
+ navButton: ElementStyle;
448
+ titleButton: ElementStyle;
449
+ titleText: ElementStyle;
450
+ monthGrid: ElementStyle;
451
+ yearGrid: ElementStyle;
452
+ selectorItem: ElementStyle;
453
+ selectorItemSelected: ElementStyle;
454
+ selectorItemText: ElementStyle;
455
+ selectorItemTextSelected: ElementStyle;
456
+ indicatorRow: ElementStyle;
457
+ indicator: ElementStyle;
458
+ };
459
+
429
460
  // ============================================================================
430
461
  // Master Component Style Elements Map
431
462
  // ============================================================================
@@ -502,6 +533,7 @@ interface BuiltInComponentStyleElements {
502
533
  Image: ImageStyleableElements;
503
534
  Pressable: PressableStyleableElements;
504
535
  Screen: ScreenStyleableElements;
536
+ DatePickerCalendar: DatePickerCalendarStyleableElements;
505
537
  }
506
538
 
507
539
  /**
@@ -14,7 +14,7 @@ interface BoundedModalContentProps {
14
14
 
15
15
  /**
16
16
  * A wrapper component for modal content that automatically constrains its height
17
- * to fit within screen boundaries, accounting for safe areas.
17
+ * and width to fit within screen boundaries, accounting for safe areas.
18
18
  */
19
19
  export const BoundedModalContent: React.FC<BoundedModalContentProps> = ({
20
20
  children,
@@ -26,7 +26,7 @@ export const BoundedModalContent: React.FC<BoundedModalContentProps> = ({
26
26
  onLayout,
27
27
  }) => {
28
28
  const insets = useSafeAreaInsets();
29
- const { height: windowHeight } = Dimensions.get('window');
29
+ const { width: windowWidth, height: windowHeight } = Dimensions.get('window');
30
30
  const padding = 12;
31
31
 
32
32
  // Calculate dynamic maxHeight to ensure content stays within bounds
@@ -38,6 +38,9 @@ export const BoundedModalContent: React.FC<BoundedModalContentProps> = ({
38
38
  // Calculate available height: from current top position to bottom boundary
39
39
  const availableHeight = Math.max(100, bottomBound - top);
40
40
 
41
+ // Calculate max width so content doesn't overflow the right edge
42
+ const maxAvailableWidth = windowWidth - left - insets.right - padding;
43
+
41
44
  return (
42
45
  <View
43
46
  style={[
@@ -45,7 +48,7 @@ export const BoundedModalContent: React.FC<BoundedModalContentProps> = ({
45
48
  position: 'absolute',
46
49
  top,
47
50
  left,
48
- ...(width && { width }),
51
+ ...(width ? { width } : { maxWidth: maxAvailableWidth }),
49
52
  maxHeight: availableHeight,
50
53
  },
51
54
  style,