@momo-kits/foundation 0.168.1-beta.3 → 0.168.1-beta.30
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/Application/BottomSheet.tsx +164 -41
- package/Application/BottomTab/DuoTabBar.tsx +191 -151
- package/Application/BottomTab/useLiquidSelection.ts +374 -0
- package/Application/Components/DuoGlassBackground.tsx +5 -8
- package/Application/ModalScreen.tsx +12 -8
- package/Application/device.ts +39 -2
- package/Application/utils.tsx +5 -9
- package/Input/InputMoney.tsx +98 -54
- package/Input/styles.ts +9 -0
- package/Layout/Screen.tsx +18 -39
- package/Layout/utils.ts +1 -0
- package/Popup/PopupNotify.tsx +20 -10
- package/Popup/utils.ts +11 -17
- package/package.json +7 -7
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
PanResponder,
|
|
17
17
|
Platform,
|
|
18
18
|
Pressable,
|
|
19
|
+
StatusBar,
|
|
19
20
|
StyleSheet,
|
|
20
21
|
TouchableOpacity,
|
|
21
22
|
useWindowDimensions,
|
|
@@ -39,18 +40,39 @@ import Animated, {
|
|
|
39
40
|
} from 'react-native-reanimated';
|
|
40
41
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
41
42
|
import layoutStyles from '../Layout/styles';
|
|
42
|
-
import {
|
|
43
|
+
import { DuoMetrics, InnerEdgeStyles, useScreenLayout } from '../Layout/utils';
|
|
44
|
+
import { getDuoTopInset, isFoldableDevice } from './device';
|
|
45
|
+
import { SideBarButton } from './Components/SideBarButton';
|
|
43
46
|
|
|
44
|
-
//
|
|
47
|
+
// foldable inner display: center the sheet instead of spanning the width
|
|
45
48
|
const DUO_REGULAR_MIN_WIDTH = 600;
|
|
46
49
|
|
|
50
|
+
const INDICATOR_HEIGHT = 16;
|
|
51
|
+
const HEADER_HEIGHT = 56;
|
|
52
|
+
const DUO_ORIENTATIONS = ['portrait', 'landscape'];
|
|
53
|
+
const SIDE_COLUMN_OFFSET =
|
|
54
|
+
INDICATOR_HEIGHT + (HEADER_HEIGHT - DuoMetrics.controlSize) / 2;
|
|
55
|
+
|
|
56
|
+
const getSideInset = (inset: number) => Math.max(inset - Spacing.M, 0);
|
|
57
|
+
|
|
47
58
|
const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
48
59
|
const { theme, navigator } = useContext(ApplicationContext);
|
|
49
60
|
const context = useContext<any>(MiniAppContext);
|
|
50
61
|
const windowSize = useWindowDimensions();
|
|
51
|
-
const isDuo =
|
|
52
|
-
const isDuoCentered =
|
|
53
|
-
|
|
62
|
+
const isDuo = isFoldableDevice();
|
|
63
|
+
const isDuoCentered =
|
|
64
|
+
isDuo &&
|
|
65
|
+
Math.min(windowSize.width, windowSize.height) >= DUO_REGULAR_MIN_WIDTH;
|
|
66
|
+
const isDuoFloating = isDuo && !isDuoCentered;
|
|
67
|
+
const { sideBar } = useScreenLayout();
|
|
68
|
+
const sideColumnWidth = (sideBar?.width ?? 0) - Spacing.M;
|
|
69
|
+
const sideColumn =
|
|
70
|
+
isDuoFloating &&
|
|
71
|
+
sideColumnWidth >= DuoMetrics.controlSize + Spacing.M &&
|
|
72
|
+
sideBar
|
|
73
|
+
? sideBar
|
|
74
|
+
: undefined;
|
|
75
|
+
const hasSideColumn = !!sideColumn;
|
|
54
76
|
// 'screen' is the outer display on iPhone Duo, even when unfolded
|
|
55
77
|
const screenHeight = Dimensions.get('screen').height;
|
|
56
78
|
const heightDevice = isDuo ? windowSize.height : screenHeight;
|
|
@@ -58,12 +80,20 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
58
80
|
const insets = useSafeAreaInsets();
|
|
59
81
|
const heightHeader = useHeaderHeight();
|
|
60
82
|
const topGap = Math.max(
|
|
61
|
-
|
|
83
|
+
sideColumn
|
|
84
|
+
? insets.top
|
|
85
|
+
: getDuoTopInset(insets, windowSize.width, windowSize.height),
|
|
62
86
|
Spacing.XL,
|
|
63
87
|
);
|
|
88
|
+
const sideInsetLeft = isDuoFloating ? getSideInset(insets.left) : 0;
|
|
89
|
+
const sideInsetRight = isDuoFloating ? getSideInset(insets.right) : 0;
|
|
90
|
+
const indicatorShift = (sideInsetRight - sideInsetLeft) / 2;
|
|
64
91
|
const isIOS = Platform.OS === 'ios';
|
|
65
92
|
const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
|
|
66
93
|
const keyboardOffset = heightHeader - bottomInset;
|
|
94
|
+
// Android shrinks the root for the keyboard but keeps the window height and
|
|
95
|
+
// fires no keyboardWill* events, so only the root can bound the sheet
|
|
96
|
+
const fitsResizedRoot = isDuo && !isIOS;
|
|
67
97
|
|
|
68
98
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
69
99
|
|
|
@@ -83,7 +113,10 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
83
113
|
}: BottomSheetParams = props.route.params;
|
|
84
114
|
|
|
85
115
|
const translateY = useSharedValue(heightDevice);
|
|
116
|
+
const onDismissRef = useRef(props.route.params?.onDismiss);
|
|
117
|
+
onDismissRef.current = props.route.params?.onDismiss;
|
|
86
118
|
const [keyboardLift, setKeyboardLift] = useState(0);
|
|
119
|
+
const [sheetTop, setSheetTop] = useState(windowSize.height);
|
|
87
120
|
|
|
88
121
|
// KeyboardAvoidingView lifts the whole sheet past the top on iPhone Duo,
|
|
89
122
|
// so lift it here and shrink maxHeight by the same amount instead
|
|
@@ -179,15 +212,15 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
179
212
|
}
|
|
180
213
|
|
|
181
214
|
/**
|
|
182
|
-
* emit dismiss event
|
|
215
|
+
* emit dismiss event, once: folding or rotating a foldable device changes
|
|
216
|
+
* the window height while the sheet stays open
|
|
183
217
|
*/
|
|
184
218
|
useEffect(() => {
|
|
185
|
-
translateY.value = heightDevice;
|
|
186
219
|
openAnimation();
|
|
187
220
|
return () => {
|
|
188
|
-
|
|
221
|
+
onDismissRef.current?.(action.current);
|
|
189
222
|
};
|
|
190
|
-
}, [
|
|
223
|
+
}, [openAnimation]);
|
|
191
224
|
|
|
192
225
|
/**
|
|
193
226
|
* handler dismiss
|
|
@@ -228,14 +261,50 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
228
261
|
return () => backHandler.remove();
|
|
229
262
|
}, [barrierDismissible, onDismiss]);
|
|
230
263
|
|
|
264
|
+
const onPressBack = useCallback(() => {
|
|
265
|
+
onDismiss(true, leftOptions?.onPressIconLeft);
|
|
266
|
+
}, [leftOptions, onDismiss]);
|
|
267
|
+
|
|
268
|
+
const onPressClose = useCallback(() => {
|
|
269
|
+
action.current = 'icon_close';
|
|
270
|
+
onDismiss(true);
|
|
271
|
+
}, [onDismiss]);
|
|
272
|
+
|
|
273
|
+
const showBack = !!leftOptions?.onPressIconLeft;
|
|
274
|
+
const iconBack = leftOptions?.iconLeft ?? 'ic_back';
|
|
275
|
+
const sideColumnCount = showBack ? 2 : 1;
|
|
276
|
+
const sideColumnHeight =
|
|
277
|
+
sideColumnCount * DuoMetrics.controlSize +
|
|
278
|
+
(sideColumnCount - 1) * Spacing.M;
|
|
279
|
+
const coversStatusBar = !!sideColumn && sheetTop < sideColumn.top;
|
|
280
|
+
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
if (!coversStatusBar) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const entry = StatusBar.pushStackEntry({
|
|
286
|
+
barStyle: theme.dark ? 'light-content' : 'dark-content',
|
|
287
|
+
animated: true,
|
|
288
|
+
});
|
|
289
|
+
return () => StatusBar.popStackEntry(entry);
|
|
290
|
+
}, [coversStatusBar, theme.dark]);
|
|
291
|
+
|
|
231
292
|
/**
|
|
232
293
|
* render header
|
|
233
294
|
*/
|
|
234
295
|
const renderHeader = useCallback(() => {
|
|
296
|
+
const showButtons = !hasSideColumn;
|
|
235
297
|
return (
|
|
236
298
|
<View {...panResponder.panHandlers}>
|
|
237
|
-
<View style={{ height:
|
|
238
|
-
<View
|
|
299
|
+
<View style={{ height: INDICATOR_HEIGHT }}>
|
|
300
|
+
<View
|
|
301
|
+
style={[
|
|
302
|
+
styles.indicator,
|
|
303
|
+
indicatorShift !== 0 && {
|
|
304
|
+
transform: [{ translateX: indicatorShift }],
|
|
305
|
+
},
|
|
306
|
+
]}
|
|
307
|
+
/>
|
|
239
308
|
</View>
|
|
240
309
|
<View
|
|
241
310
|
style={[
|
|
@@ -246,14 +315,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
246
315
|
},
|
|
247
316
|
]}
|
|
248
317
|
>
|
|
249
|
-
{
|
|
250
|
-
<TouchableOpacity
|
|
251
|
-
|
|
252
|
-
onPress={() => {
|
|
253
|
-
onDismiss(true, leftOptions.onPressIconLeft);
|
|
254
|
-
}}
|
|
255
|
-
>
|
|
256
|
-
<Icon source={leftOptions.iconLeft ?? 'ic_back'} />
|
|
318
|
+
{showButtons && showBack ? (
|
|
319
|
+
<TouchableOpacity style={styles.iconButton} onPress={onPressBack}>
|
|
320
|
+
<Icon source={iconBack} />
|
|
257
321
|
</TouchableOpacity>
|
|
258
322
|
) : null}
|
|
259
323
|
|
|
@@ -261,9 +325,15 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
261
325
|
<View style={Styles.flex}>{options.header}</View>
|
|
262
326
|
) : (
|
|
263
327
|
<>
|
|
264
|
-
{!leftOptions ?
|
|
328
|
+
{showButtons && !leftOptions ? (
|
|
329
|
+
<View style={styles.iconButton} />
|
|
330
|
+
) : null}
|
|
265
331
|
<Text
|
|
266
|
-
style={[
|
|
332
|
+
style={[
|
|
333
|
+
Styles.flex,
|
|
334
|
+
styles.title,
|
|
335
|
+
!showButtons && styles.titleInset,
|
|
336
|
+
]}
|
|
267
337
|
numberOfLines={1}
|
|
268
338
|
typography="header_default_bold"
|
|
269
339
|
>
|
|
@@ -271,28 +341,64 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
271
341
|
</Text>
|
|
272
342
|
</>
|
|
273
343
|
)}
|
|
274
|
-
|
|
275
|
-
style={styles.iconButton}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}}
|
|
280
|
-
>
|
|
281
|
-
<Icon source="navigation_close" />
|
|
282
|
-
</TouchableOpacity>
|
|
344
|
+
{showButtons ? (
|
|
345
|
+
<TouchableOpacity style={styles.iconButton} onPress={onPressClose}>
|
|
346
|
+
<Icon source="navigation_close" />
|
|
347
|
+
</TouchableOpacity>
|
|
348
|
+
) : null}
|
|
283
349
|
</View>
|
|
284
350
|
</View>
|
|
285
351
|
);
|
|
286
352
|
}, [
|
|
287
|
-
|
|
353
|
+
hasSideColumn,
|
|
354
|
+
iconBack,
|
|
355
|
+
indicatorShift,
|
|
288
356
|
leftOptions,
|
|
357
|
+
onPressBack,
|
|
358
|
+
onPressClose,
|
|
289
359
|
options.header,
|
|
290
360
|
options.title,
|
|
291
361
|
panResponder.panHandlers,
|
|
362
|
+
showBack,
|
|
292
363
|
theme.colors.border.default,
|
|
293
364
|
useDivider,
|
|
294
365
|
]);
|
|
295
366
|
|
|
367
|
+
/**
|
|
368
|
+
* iPhone Duo folded: back and close move to the side bar column, drawn on
|
|
369
|
+
* the part of the sheet under the side safe area. They line up with the
|
|
370
|
+
* header row, stay below the status bar cluster on a tall sheet and above a
|
|
371
|
+
* camera at the bottom end of the strip.
|
|
372
|
+
*/
|
|
373
|
+
const renderSideColumn = () => {
|
|
374
|
+
if (!sideColumn) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
const lowestTop =
|
|
378
|
+
windowSize.height - sideColumn.bottom - sideColumnHeight - sheetTop;
|
|
379
|
+
const top = Math.min(
|
|
380
|
+
Math.max(sideColumn.top - sheetTop, SIDE_COLUMN_OFFSET),
|
|
381
|
+
lowestTop,
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
return (
|
|
385
|
+
<View
|
|
386
|
+
pointerEvents={'box-none'}
|
|
387
|
+
style={[
|
|
388
|
+
styles.sideColumn,
|
|
389
|
+
sideColumn.edge === 'right' ? styles.alignRight : styles.alignLeft,
|
|
390
|
+
InnerEdgeStyles[sideColumn.edge],
|
|
391
|
+
{ top, width: sideColumnWidth },
|
|
392
|
+
]}
|
|
393
|
+
>
|
|
394
|
+
{showBack ? (
|
|
395
|
+
<SideBarButton icon={iconBack} onPress={onPressBack} />
|
|
396
|
+
) : null}
|
|
397
|
+
<SideBarButton icon="navigation_close" onPress={onPressClose} />
|
|
398
|
+
</View>
|
|
399
|
+
);
|
|
400
|
+
};
|
|
401
|
+
|
|
296
402
|
const animatedStyle = useAnimatedStyle(() => {
|
|
297
403
|
return {
|
|
298
404
|
transform: [{ translateY: translateY.value }],
|
|
@@ -310,12 +416,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
310
416
|
};
|
|
311
417
|
});
|
|
312
418
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const sideInsetStyle = needsSideInset && {
|
|
317
|
-
paddingLeft: insets.left,
|
|
318
|
-
paddingRight: insets.right,
|
|
419
|
+
const sideInsetStyle = isDuoFloating && {
|
|
420
|
+
paddingLeft: sideInsetLeft,
|
|
421
|
+
paddingRight: sideInsetRight,
|
|
319
422
|
};
|
|
320
423
|
|
|
321
424
|
const screen = (
|
|
@@ -335,6 +438,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
335
438
|
}}
|
|
336
439
|
style={StyleSheet.absoluteFillObject}
|
|
337
440
|
isModalKit={true}
|
|
441
|
+
supportedOrientations={isDuo ? DUO_ORIENTATIONS : undefined}
|
|
338
442
|
>
|
|
339
443
|
<KeyboardAvoidingView
|
|
340
444
|
behavior={Platform.select({
|
|
@@ -357,18 +461,28 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
357
461
|
</Pressable>
|
|
358
462
|
|
|
359
463
|
<Animated.View
|
|
464
|
+
onLayout={
|
|
465
|
+
isDuoFloating ? e => setSheetTop(e.nativeEvent.layout.y) : undefined
|
|
466
|
+
}
|
|
360
467
|
style={[
|
|
361
468
|
animatedStyle,
|
|
362
469
|
{
|
|
363
470
|
backgroundColor,
|
|
364
|
-
borderTopLeftRadius: Radius.M,
|
|
365
|
-
borderTopRightRadius: Radius.M,
|
|
471
|
+
borderTopLeftRadius: isDuo ? Radius.XL : Radius.M,
|
|
472
|
+
borderTopRightRadius: isDuo ? Radius.XL : Radius.M,
|
|
366
473
|
overflow: 'hidden',
|
|
367
474
|
},
|
|
368
475
|
isDuo && {
|
|
369
476
|
maxHeight: windowSize.height - topGap - keyboardLift,
|
|
370
477
|
marginBottom: keyboardLift,
|
|
371
478
|
},
|
|
479
|
+
fitsResizedRoot && styles.shrink,
|
|
480
|
+
fitsResizedRoot && { marginTop: topGap },
|
|
481
|
+
isDuoFloating && { marginHorizontal: Spacing.M },
|
|
482
|
+
sideColumn && {
|
|
483
|
+
minHeight:
|
|
484
|
+
SIDE_COLUMN_OFFSET + sideColumnHeight + sideColumn.bottom,
|
|
485
|
+
},
|
|
372
486
|
isDuoCentered && {
|
|
373
487
|
// keeps the portrait width in landscape, like system sheets
|
|
374
488
|
width: Math.min(
|
|
@@ -415,6 +529,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
415
529
|
}}
|
|
416
530
|
/>
|
|
417
531
|
)}
|
|
532
|
+
{renderSideColumn()}
|
|
418
533
|
</Animated.View>
|
|
419
534
|
</KeyboardAvoidingView>
|
|
420
535
|
</Container>
|
|
@@ -440,10 +555,12 @@ const styles = StyleSheet.create({
|
|
|
440
555
|
top: Spacing.S,
|
|
441
556
|
},
|
|
442
557
|
header: {
|
|
443
|
-
height:
|
|
558
|
+
height: HEADER_HEIGHT,
|
|
444
559
|
flexDirection: 'row',
|
|
445
560
|
alignItems: 'center',
|
|
446
561
|
},
|
|
562
|
+
title: { textAlign: 'center' },
|
|
563
|
+
titleInset: { paddingHorizontal: Spacing.M },
|
|
447
564
|
iconButton: {
|
|
448
565
|
width: 24,
|
|
449
566
|
height: 24,
|
|
@@ -452,6 +569,12 @@ const styles = StyleSheet.create({
|
|
|
452
569
|
marginHorizontal: Spacing.M,
|
|
453
570
|
},
|
|
454
571
|
shrink: { flexShrink: 1 },
|
|
572
|
+
sideColumn: {
|
|
573
|
+
position: 'absolute',
|
|
574
|
+
gap: Spacing.M,
|
|
575
|
+
},
|
|
576
|
+
alignRight: { right: 0 },
|
|
577
|
+
alignLeft: { left: 0 },
|
|
455
578
|
debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
|
|
456
579
|
});
|
|
457
580
|
|