@idealyst/components 1.2.50 → 1.2.52

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.50",
3
+ "version": "1.2.52",
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.50",
59
+ "@idealyst/theme": "^1.2.52",
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.50",
110
+ "@idealyst/theme": "^1.2.52",
111
111
  "@idealyst/tooling": "^1.2.30",
112
112
  "@mdi/react": "^1.6.1",
113
113
  "@types/react": "^19.1.0",
@@ -1,6 +1,7 @@
1
- import { useEffect, forwardRef, useMemo } from 'react';
2
- import { Modal, View, Text, TouchableOpacity, TouchableWithoutFeedback, BackHandler, GestureResponderEvent, Keyboard, Platform } from 'react-native';
1
+ import { useEffect, forwardRef, useMemo, useState } from 'react';
2
+ import { Modal, View, Text, TouchableOpacity, TouchableWithoutFeedback, BackHandler, Platform, Keyboard, useWindowDimensions } from 'react-native';
3
3
  import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
4
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
4
5
  import { DialogProps } from './types';
5
6
  import { dialogStyles } from './Dialog.styles';
6
7
  import { getNativeInteractiveAccessibilityProps } from '../utils/accessibility';
@@ -16,6 +17,9 @@ const Dialog = forwardRef<View, DialogProps>(({
16
17
  closeOnBackdropClick = true,
17
18
  animationType: _animationType = 'fade',
18
19
  avoidKeyboard = false,
20
+ padding: paddingProp = 20,
21
+ maxContentHeight,
22
+ contentPadding = 24,
19
23
  style,
20
24
  testID,
21
25
  id,
@@ -41,32 +45,31 @@ const Dialog = forwardRef<View, DialogProps>(({
41
45
  const backdropOpacity = useSharedValue(0);
42
46
  const containerScale = useSharedValue(0.9);
43
47
  const containerOpacity = useSharedValue(0);
44
- const keyboardOffset = useSharedValue(0);
45
48
 
46
- // Listen for keyboard events and animate offset
49
+ // Get safe area insets
50
+ const insets = useSafeAreaInsets();
51
+
52
+ // Track keyboard height for avoidKeyboard
53
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
54
+ const { height: screenHeight } = useWindowDimensions();
55
+
47
56
  useEffect(() => {
48
57
  if (!avoidKeyboard || !open) return;
49
58
 
50
59
  const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
51
60
  const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
52
61
 
53
- const showSubscription = Keyboard.addListener(showEvent, (e) => {
54
- keyboardOffset.value = withTiming(e.endCoordinates.height / 2, {
55
- duration: Platform.OS === 'ios' ? e.duration : 250,
56
- easing: Easing.out(Easing.cubic),
57
- });
62
+ const showSub = Keyboard.addListener(showEvent, (e) => {
63
+ setKeyboardHeight(e.endCoordinates.height);
58
64
  });
59
65
 
60
- const hideSubscription = Keyboard.addListener(hideEvent, (e) => {
61
- keyboardOffset.value = withTiming(0, {
62
- duration: Platform.OS === 'ios' ? (e.duration ?? 250) : 250,
63
- easing: Easing.out(Easing.cubic),
64
- });
66
+ const hideSub = Keyboard.addListener(hideEvent, () => {
67
+ setKeyboardHeight(0);
65
68
  });
66
69
 
67
70
  return () => {
68
- showSubscription.remove();
69
- hideSubscription.remove();
71
+ showSub.remove();
72
+ hideSub.remove();
70
73
  };
71
74
  }, [avoidKeyboard, open]);
72
75
 
@@ -142,8 +145,7 @@ const Dialog = forwardRef<View, DialogProps>(({
142
145
  opacity: containerOpacity.value,
143
146
  transform: [
144
147
  { scale: containerScale.value },
145
- { translateY: -keyboardOffset.value },
146
- ] as { scale: number }[] & { translateY: number }[],
148
+ ] as { scale: number }[],
147
149
  };
148
150
  });
149
151
 
@@ -154,19 +156,6 @@ const Dialog = forwardRef<View, DialogProps>(({
154
156
  const titleStyle = (dialogStyles.title as any)({});
155
157
  const closeButtonStyle = (dialogStyles.closeButton as any)({});
156
158
  const closeButtonTextStyle = (dialogStyles.closeButtonText as any)({});
157
- const contentStyle = (dialogStyles.content as any)({});
158
-
159
- // Style for custom backdrop wrapper (no default backdrop styling)
160
- const customBackdropWrapperStyle = {
161
- position: 'absolute' as const,
162
- top: 0,
163
- left: 0,
164
- right: 0,
165
- bottom: 0,
166
- display: 'flex' as const,
167
- alignItems: 'center' as const,
168
- justifyContent: 'center' as const,
169
- };
170
159
 
171
160
  // Style for custom backdrop component container (fills entire backdrop area)
172
161
  const customBackdropContainerStyle = {
@@ -177,33 +166,55 @@ const Dialog = forwardRef<View, DialogProps>(({
177
166
  bottom: 0,
178
167
  };
179
168
 
169
+ // Position offsets for the container view
170
+ // Top: always safe area + padding
171
+ // Bottom: safe area + padding (no keyboard) or keyboard + padding (with keyboard)
172
+ const topOffset = insets.top + paddingProp;
173
+ const bottomOffset = keyboardHeight > 0
174
+ ? keyboardHeight + paddingProp
175
+ : insets.bottom + paddingProp;
176
+
177
+ // Max height is the available space (used as a ceiling, children can be smaller)
178
+ const maxAvailableHeight = screenHeight - topOffset - bottomOffset;
179
+
180
+ // Use the smaller of user's preferred max height and available space
181
+ const effectiveMaxHeight = maxContentHeight
182
+ ? Math.min(maxContentHeight, maxAvailableHeight)
183
+ : maxAvailableHeight;
184
+
185
+ // Dialog uses the effective max height, with flex: 1 so children can fill it
186
+ const dialogContainerStyle = {
187
+ ...containerStyle,
188
+ maxHeight: effectiveMaxHeight,
189
+ height: maxContentHeight ? effectiveMaxHeight : undefined,
190
+ flex: undefined,
191
+ };
192
+
180
193
  const dialogContainer = (
181
- <TouchableWithoutFeedback onPress={(e: GestureResponderEvent) => e.stopPropagation()}>
182
- <Animated.View ref={ref as any} style={[containerStyle, style, containerAnimatedStyle]} nativeID={id} {...nativeA11yProps}>
183
- {(title || showCloseButton) && (
184
- <View style={headerStyle}>
185
- {title && (
186
- <Text style={titleStyle}>
187
- {title}
188
- </Text>
189
- )}
190
- {showCloseButton && (
191
- <TouchableOpacity
192
- style={closeButtonStyle}
193
- onPress={handleClosePress}
194
- accessibilityLabel="Close dialog"
195
- accessibilityRole="button"
196
- >
197
- <Text style={closeButtonTextStyle}>×</Text>
198
- </TouchableOpacity>
199
- )}
200
- </View>
201
- )}
202
- <View style={contentStyle}>
203
- {children}
194
+ <Animated.View ref={ref as any} style={[dialogContainerStyle, style, containerAnimatedStyle]} nativeID={id} {...nativeA11yProps}>
195
+ {(title || showCloseButton) && (
196
+ <View style={headerStyle}>
197
+ {title && (
198
+ <Text style={titleStyle}>
199
+ {title}
200
+ </Text>
201
+ )}
202
+ {showCloseButton && (
203
+ <TouchableOpacity
204
+ style={closeButtonStyle}
205
+ onPress={handleClosePress}
206
+ accessibilityLabel="Close dialog"
207
+ accessibilityRole="button"
208
+ >
209
+ <Text style={closeButtonTextStyle}>×</Text>
210
+ </TouchableOpacity>
211
+ )}
204
212
  </View>
205
- </Animated.View>
206
- </TouchableWithoutFeedback>
213
+ )}
214
+ <View style={contentPadding > 0 ? { padding: contentPadding } : undefined}>
215
+ {children}
216
+ </View>
217
+ </Animated.View>
207
218
  );
208
219
 
209
220
  return (
@@ -215,20 +226,36 @@ const Dialog = forwardRef<View, DialogProps>(({
215
226
  statusBarTranslucent
216
227
  testID={testID}
217
228
  >
218
- <TouchableWithoutFeedback onPress={handleBackdropPress}>
219
- {BackdropComponent ? (
220
- <View style={customBackdropWrapperStyle}>
221
- <Animated.View style={[customBackdropContainerStyle, backdropAnimatedStyle]}>
229
+ {/* Backdrop layer - positioned absolute, full screen */}
230
+ {BackdropComponent ? (
231
+ <TouchableWithoutFeedback onPress={handleBackdropPress}>
232
+ <Animated.View style={[customBackdropContainerStyle, backdropAnimatedStyle]} pointerEvents="auto">
233
+ <View style={{ flex: 1 }} pointerEvents="none">
222
234
  <BackdropComponent isVisible={open} />
223
- </Animated.View>
224
- {dialogContainer}
225
- </View>
226
- ) : (
227
- <Animated.View style={[backdropStyle, backdropAnimatedStyle]}>
228
- {dialogContainer}
235
+ </View>
229
236
  </Animated.View>
230
- )}
231
- </TouchableWithoutFeedback>
237
+ </TouchableWithoutFeedback>
238
+ ) : (
239
+ <TouchableWithoutFeedback onPress={handleBackdropPress}>
240
+ <Animated.View style={[backdropStyle, backdropAnimatedStyle]} />
241
+ </TouchableWithoutFeedback>
242
+ )}
243
+ {/* Dialog content - positioned absolute, accounts for keyboard and safe areas */}
244
+ <View
245
+ style={{
246
+ position: 'absolute',
247
+ top: topOffset,
248
+ left: 0,
249
+ right: 0,
250
+ bottom: bottomOffset,
251
+ alignItems: 'center',
252
+ justifyContent: 'center',
253
+ zIndex: 1001,
254
+ }}
255
+ pointerEvents="box-none"
256
+ >
257
+ {dialogContainer}
258
+ </View>
232
259
  </Modal>
233
260
  );
234
261
  });
@@ -64,6 +64,7 @@ export const dialogStyles = defineStyle('Dialog', (theme: Theme) => ({
64
64
  shadowRadius: 20,
65
65
  elevation: 10,
66
66
  maxHeight: '90%',
67
+ flexDirection: 'column' as const,
67
68
  ...sizeStyles,
68
69
  ...typeStyles,
69
70
  _web: {
@@ -127,6 +128,7 @@ export const dialogStyles = defineStyle('Dialog', (theme: Theme) => ({
127
128
  }),
128
129
 
129
130
  content: (_props: DialogDynamicProps) => ({
131
+ flex: 1,
130
132
  _web: {
131
133
  overflow: 'visible',
132
134
  maxHeight: 'none',
@@ -21,6 +21,8 @@ const Dialog = forwardRef<HTMLDivElement, DialogProps>(({
21
21
  showCloseButton = true,
22
22
  closeOnBackdropClick = true,
23
23
  closeOnEscapeKey = true,
24
+ height,
25
+ contentPadding = 24,
24
26
  style,
25
27
  testID,
26
28
  id,
@@ -144,14 +146,19 @@ const Dialog = forwardRef<HTMLDivElement, DialogProps>(({
144
146
  const containerProps = getWebProps([
145
147
  (dialogStyles.container as any)({}),
146
148
  style as any,
149
+ height !== undefined ? { height, display: 'flex', flexDirection: 'column' } : null,
147
150
  isVisible
148
151
  ? { opacity: 1, transform: 'scale(1) translateY(0px)' }
149
152
  : { opacity: 0, transform: 'scale(0.96) translateY(-4px)' }
150
- ]);
153
+ ].filter(Boolean));
151
154
  const headerProps = getWebProps([(dialogStyles.header as any)({})]);
152
155
  const titleProps = getWebProps([(dialogStyles.title as any)({})]);
153
156
  const closeButtonProps = getWebProps([(dialogStyles.closeButton as any)({})]);
154
- const contentProps = getWebProps([(dialogStyles.content as any)({})]);
157
+ const contentProps = getWebProps([
158
+ (dialogStyles.content as any)({}),
159
+ height !== undefined ? { flex: 1, overflow: 'auto' } : null,
160
+ contentPadding > 0 ? { padding: contentPadding } : null,
161
+ ].filter(Boolean));
155
162
 
156
163
  const mergedBackdropRef = useMergeRefs(ref, backdropProps.ref);
157
164
 
@@ -73,6 +73,11 @@ export interface DialogProps extends BaseProps, InteractiveAccessibilityProps {
73
73
  */
74
74
  animationType?: DialogAnimationType;
75
75
 
76
+ /**
77
+ * Maximum height for the dialog content area (native only)
78
+ */
79
+ maxContentHeight?: number;
80
+
76
81
  /**
77
82
  * Additional styles (platform-specific)
78
83
  */
@@ -96,4 +101,18 @@ export interface DialogProps extends BaseProps, InteractiveAccessibilityProps {
96
101
  * @default false
97
102
  */
98
103
  avoidKeyboard?: boolean;
104
+
105
+ /**
106
+ * Fixed height for the dialog container.
107
+ * Can be a number (pixels) or a string (e.g., '50%', '400px').
108
+ * When set, children can use flex: 1 to fill the available space.
109
+ */
110
+ height?: number | string;
111
+
112
+ /**
113
+ * Padding for the dialog content area.
114
+ * Set to 0 to disable padding for custom layouts.
115
+ * @default 24
116
+ */
117
+ contentPadding?: number;
99
118
  }
@@ -16,6 +16,9 @@ export * from './Pressable/types';
16
16
  export { default as Input } from './Input';
17
17
  export * from './Input/types';
18
18
 
19
+ export { default as TextInput } from './TextInput';
20
+ export * from './TextInput/types';
21
+
19
22
  // New primitive components
20
23
  export { default as Checkbox } from './Checkbox';
21
24
  export * from './Checkbox/types';
@@ -115,6 +118,7 @@ export type { IconButtonProps } from './IconButton/types';
115
118
  export type { TextProps } from './Text/types';
116
119
  export type { ViewProps } from './View/types';
117
120
  export type { InputProps } from './Input/types';
121
+ export type { TextInputProps } from './TextInput/types';
118
122
  export type { CheckboxProps } from './Checkbox/types';
119
123
  export type { CardProps } from './Card/types';
120
124
  export type { DividerProps } from './Divider/types';