@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 +4 -4
- package/src/Dialog/Dialog.native.tsx +20 -3
- package/src/Menu/Menu.native.tsx +50 -21
- package/src/Menu/MenuItem.native.tsx +17 -12
- package/src/TextArea/TextArea.native.tsx +16 -12
- package/src/TextArea/TextArea.web.tsx +7 -5
- package/src/TextArea/types.ts +5 -0
- package/src/extensions/index.ts +1 -0
- package/src/extensions/types.ts +32 -0
- package/src/internal/BoundedModalContent.native.tsx +6 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/components",
|
|
3
|
-
"version": "
|
|
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": "^
|
|
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": "^
|
|
111
|
-
"@idealyst/tooling": "^
|
|
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
|
-
//
|
|
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:
|
|
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>
|
package/src/Menu/Menu.native.tsx
CHANGED
|
@@ -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
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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
|
-
{
|
|
58
|
-
|
|
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
|
-
//
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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>
|
package/src/TextArea/types.ts
CHANGED
|
@@ -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;
|
package/src/extensions/index.ts
CHANGED
package/src/extensions/types.ts
CHANGED
|
@@ -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
|
|
51
|
+
...(width ? { width } : { maxWidth: maxAvailableWidth }),
|
|
49
52
|
maxHeight: availableHeight,
|
|
50
53
|
},
|
|
51
54
|
style,
|