@idealyst/components 1.2.49 → 1.2.51

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.49",
3
+ "version": "1.2.51",
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.49",
59
+ "@idealyst/theme": "^1.2.51",
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.49",
110
+ "@idealyst/theme": "^1.2.51",
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, KeyboardAvoidingView, 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,8 @@ const Dialog = forwardRef<View, DialogProps>(({
16
17
  closeOnBackdropClick = true,
17
18
  animationType: _animationType = 'fade',
18
19
  avoidKeyboard = false,
20
+ padding: paddingProp = 20,
21
+ maxContentHeight,
19
22
  style,
20
23
  testID,
21
24
  id,
@@ -42,6 +45,33 @@ const Dialog = forwardRef<View, DialogProps>(({
42
45
  const containerScale = useSharedValue(0.9);
43
46
  const containerOpacity = useSharedValue(0);
44
47
 
48
+ // Get safe area insets
49
+ const insets = useSafeAreaInsets();
50
+
51
+ // Track keyboard height for avoidKeyboard
52
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
53
+ const { height: screenHeight } = useWindowDimensions();
54
+
55
+ useEffect(() => {
56
+ if (!avoidKeyboard || !open) return;
57
+
58
+ const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
59
+ const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
60
+
61
+ const showSub = Keyboard.addListener(showEvent, (e) => {
62
+ setKeyboardHeight(e.endCoordinates.height);
63
+ });
64
+
65
+ const hideSub = Keyboard.addListener(hideEvent, () => {
66
+ setKeyboardHeight(0);
67
+ });
68
+
69
+ return () => {
70
+ showSub.remove();
71
+ hideSub.remove();
72
+ };
73
+ }, [avoidKeyboard, open]);
74
+
45
75
  // Animate in/out when open changes
46
76
  useEffect(() => {
47
77
  if (open) {
@@ -109,11 +139,12 @@ const Dialog = forwardRef<View, DialogProps>(({
109
139
  });
110
140
 
111
141
  const containerAnimatedStyle = useAnimatedStyle(() => {
142
+ 'worklet';
112
143
  return {
113
144
  opacity: containerOpacity.value,
114
145
  transform: [
115
146
  { scale: containerScale.value },
116
- ],
147
+ ] as { scale: number }[],
117
148
  };
118
149
  });
119
150
 
@@ -124,19 +155,6 @@ const Dialog = forwardRef<View, DialogProps>(({
124
155
  const titleStyle = (dialogStyles.title as any)({});
125
156
  const closeButtonStyle = (dialogStyles.closeButton as any)({});
126
157
  const closeButtonTextStyle = (dialogStyles.closeButtonText as any)({});
127
- const contentStyle = (dialogStyles.content as any)({});
128
-
129
- // Style for custom backdrop wrapper (no default backdrop styling)
130
- const customBackdropWrapperStyle = {
131
- position: 'absolute' as const,
132
- top: 0,
133
- left: 0,
134
- right: 0,
135
- bottom: 0,
136
- display: 'flex' as const,
137
- alignItems: 'center' as const,
138
- justifyContent: 'center' as const,
139
- };
140
158
 
141
159
  // Style for custom backdrop component container (fills entire backdrop area)
142
160
  const customBackdropContainerStyle = {
@@ -147,8 +165,32 @@ const Dialog = forwardRef<View, DialogProps>(({
147
165
  bottom: 0,
148
166
  };
149
167
 
150
- const dialogContent = (
151
- <Animated.View ref={ref as any} style={[containerStyle, style, containerAnimatedStyle]} nativeID={id} {...nativeA11yProps}>
168
+ // Position offsets for the container view
169
+ // Top: always safe area + padding
170
+ // Bottom: safe area + padding (no keyboard) or keyboard + padding (with keyboard)
171
+ const topOffset = insets.top + paddingProp;
172
+ const bottomOffset = keyboardHeight > 0
173
+ ? keyboardHeight + paddingProp
174
+ : insets.bottom + paddingProp;
175
+
176
+ // Max height is the available space (used as a ceiling, children can be smaller)
177
+ const maxAvailableHeight = screenHeight - topOffset - bottomOffset;
178
+
179
+ // Use the smaller of user's preferred max height and available space
180
+ const effectiveMaxHeight = maxContentHeight
181
+ ? Math.min(maxContentHeight, maxAvailableHeight)
182
+ : maxAvailableHeight;
183
+
184
+ // Dialog uses the effective max height, with flex: 1 so children can fill it
185
+ const dialogContainerStyle = {
186
+ ...containerStyle,
187
+ maxHeight: effectiveMaxHeight,
188
+ height: maxContentHeight ? effectiveMaxHeight : undefined,
189
+ flex: undefined,
190
+ };
191
+
192
+ const dialogContainer = (
193
+ <Animated.View ref={ref as any} style={[dialogContainerStyle, style, containerAnimatedStyle]} nativeID={id} {...nativeA11yProps}>
152
194
  {(title || showCloseButton) && (
153
195
  <View style={headerStyle}>
154
196
  {title && (
@@ -168,26 +210,10 @@ const Dialog = forwardRef<View, DialogProps>(({
168
210
  )}
169
211
  </View>
170
212
  )}
171
- <View style={contentStyle}>
172
- {children}
173
- </View>
213
+ {children}
174
214
  </Animated.View>
175
215
  );
176
216
 
177
- const dialogContainer = (
178
- <TouchableWithoutFeedback onPress={(e: GestureResponderEvent) => e.stopPropagation()}>
179
- {avoidKeyboard ? (
180
- <KeyboardAvoidingView
181
- behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
182
- >
183
- {dialogContent}
184
- </KeyboardAvoidingView>
185
- ) : (
186
- dialogContent
187
- )}
188
- </TouchableWithoutFeedback>
189
- );
190
-
191
217
  return (
192
218
  <Modal
193
219
  visible={open}
@@ -197,20 +223,35 @@ const Dialog = forwardRef<View, DialogProps>(({
197
223
  statusBarTranslucent
198
224
  testID={testID}
199
225
  >
200
- <TouchableWithoutFeedback onPress={handleBackdropPress}>
201
- {BackdropComponent ? (
202
- <View style={customBackdropWrapperStyle}>
203
- <Animated.View style={[customBackdropContainerStyle, backdropAnimatedStyle]}>
226
+ {/* Backdrop layer - positioned absolute, full screen */}
227
+ {BackdropComponent ? (
228
+ <TouchableWithoutFeedback onPress={handleBackdropPress}>
229
+ <Animated.View style={[customBackdropContainerStyle, backdropAnimatedStyle]} pointerEvents="auto">
230
+ <View style={{ flex: 1 }} pointerEvents="none">
204
231
  <BackdropComponent isVisible={open} />
205
- </Animated.View>
206
- {dialogContainer}
207
- </View>
208
- ) : (
209
- <Animated.View style={[backdropStyle, backdropAnimatedStyle]}>
210
- {dialogContainer}
232
+ </View>
211
233
  </Animated.View>
212
- )}
213
- </TouchableWithoutFeedback>
234
+ </TouchableWithoutFeedback>
235
+ ) : (
236
+ <TouchableWithoutFeedback onPress={handleBackdropPress}>
237
+ <Animated.View style={[backdropStyle, backdropAnimatedStyle]} />
238
+ </TouchableWithoutFeedback>
239
+ )}
240
+ {/* Dialog content - positioned absolute, accounts for keyboard and safe areas */}
241
+ <View
242
+ style={{
243
+ position: 'absolute',
244
+ top: topOffset,
245
+ left: 0,
246
+ right: 0,
247
+ bottom: bottomOffset,
248
+ alignItems: 'center',
249
+ justifyContent: 'center',
250
+ }}
251
+ pointerEvents="box-none"
252
+ >
253
+ {dialogContainer}
254
+ </View>
214
255
  </Modal>
215
256
  );
216
257
  });
@@ -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,7 @@ const Dialog = forwardRef<HTMLDivElement, DialogProps>(({
21
21
  showCloseButton = true,
22
22
  closeOnBackdropClick = true,
23
23
  closeOnEscapeKey = true,
24
+ height,
24
25
  style,
25
26
  testID,
26
27
  id,
@@ -144,14 +145,18 @@ const Dialog = forwardRef<HTMLDivElement, DialogProps>(({
144
145
  const containerProps = getWebProps([
145
146
  (dialogStyles.container as any)({}),
146
147
  style as any,
148
+ height !== undefined ? { height, display: 'flex', flexDirection: 'column' } : null,
147
149
  isVisible
148
150
  ? { opacity: 1, transform: 'scale(1) translateY(0px)' }
149
151
  : { opacity: 0, transform: 'scale(0.96) translateY(-4px)' }
150
- ]);
152
+ ].filter(Boolean));
151
153
  const headerProps = getWebProps([(dialogStyles.header as any)({})]);
152
154
  const titleProps = getWebProps([(dialogStyles.title as any)({})]);
153
155
  const closeButtonProps = getWebProps([(dialogStyles.closeButton as any)({})]);
154
- const contentProps = getWebProps([(dialogStyles.content as any)({})]);
156
+ const contentProps = getWebProps([
157
+ (dialogStyles.content as any)({}),
158
+ height !== undefined ? { flex: 1, overflow: 'auto' } : null,
159
+ ].filter(Boolean));
155
160
 
156
161
  const mergedBackdropRef = useMergeRefs(ref, backdropProps.ref);
157
162
 
@@ -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,11 @@ 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;
99
111
  }
@@ -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';