@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.
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
-
//
|
|
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
|
|
54
|
-
|
|
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
|
|
61
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
182
|
-
|
|
183
|
-
{
|
|
184
|
-
|
|
185
|
-
{
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
>
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
</
|
|
224
|
-
{dialogContainer}
|
|
225
|
-
</View>
|
|
226
|
-
) : (
|
|
227
|
-
<Animated.View style={[backdropStyle, backdropAnimatedStyle]}>
|
|
228
|
-
{dialogContainer}
|
|
235
|
+
</View>
|
|
229
236
|
</Animated.View>
|
|
230
|
-
|
|
231
|
-
|
|
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([
|
|
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
|
|
package/src/Dialog/types.ts
CHANGED
|
@@ -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
|
}
|
package/src/index.native.ts
CHANGED
|
@@ -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';
|