@momo-kits/foundation 0.168.1-beta.14 → 0.168.1-beta.15

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.
@@ -16,7 +16,6 @@ import {
16
16
  PanResponder,
17
17
  Platform,
18
18
  Pressable,
19
- StatusBar,
20
19
  StyleSheet,
21
20
  TouchableOpacity,
22
21
  useWindowDimensions,
@@ -40,37 +39,18 @@ import Animated, {
40
39
  } from 'react-native-reanimated';
41
40
  import { scheduleOnRN } from 'react-native-worklets';
42
41
  import layoutStyles from '../Layout/styles';
43
- import { DuoMetrics, InnerEdgeStyles, useScreenLayout } from '../Layout/utils';
44
42
  import { getDuoTopInset, isIPhoneDuo } from './device';
45
- import { SideBarButton } from './Components/SideBarButton';
46
43
 
47
44
  // iPhone Duo inner display: center the sheet instead of spanning the width
48
45
  const DUO_REGULAR_MIN_WIDTH = 600;
49
46
 
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
47
  const BottomSheet: React.FC<BottomSheetParams> = props => {
57
48
  const { theme, navigator } = useContext(ApplicationContext);
58
49
  const context = useContext<any>(MiniAppContext);
59
50
  const windowSize = useWindowDimensions();
60
51
  const isDuo = isIPhoneDuo();
61
- const isDuoCentered =
62
- isDuo &&
63
- Math.min(windowSize.width, windowSize.height) >= DUO_REGULAR_MIN_WIDTH;
64
- const isDuoFloating = isDuo && !isDuoCentered;
65
- const { sideBar } = useScreenLayout();
66
- const sideColumnWidth = (sideBar?.width ?? 0) - Spacing.M;
67
- const sideColumn =
68
- isDuoFloating &&
69
- sideColumnWidth >= DuoMetrics.controlSize + Spacing.M &&
70
- sideBar
71
- ? sideBar
72
- : undefined;
73
- const hasSideColumn = !!sideColumn;
52
+ const isDuoCentered = isDuo && windowSize.width >= DUO_REGULAR_MIN_WIDTH;
53
+ const isPortrait = windowSize.height > windowSize.width;
74
54
  // 'screen' is the outer display on iPhone Duo, even when unfolded
75
55
  const screenHeight = Dimensions.get('screen').height;
76
56
  const heightDevice = isDuo ? windowSize.height : screenHeight;
@@ -78,9 +58,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
78
58
  const insets = useSafeAreaInsets();
79
59
  const heightHeader = useHeaderHeight();
80
60
  const topGap = Math.max(
81
- sideColumn
82
- ? insets.top
83
- : getDuoTopInset(insets, windowSize.width, windowSize.height),
61
+ getDuoTopInset(insets, windowSize.width, windowSize.height),
84
62
  Spacing.XL,
85
63
  );
86
64
  const isIOS = Platform.OS === 'ios';
@@ -105,10 +83,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
105
83
  }: BottomSheetParams = props.route.params;
106
84
 
107
85
  const translateY = useSharedValue(heightDevice);
108
- const onDismissRef = useRef(props.route.params?.onDismiss);
109
- onDismissRef.current = props.route.params?.onDismiss;
110
86
  const [keyboardLift, setKeyboardLift] = useState(0);
111
- const [sheetTop, setSheetTop] = useState(windowSize.height);
112
87
 
113
88
  // KeyboardAvoidingView lifts the whole sheet past the top on iPhone Duo,
114
89
  // so lift it here and shrink maxHeight by the same amount instead
@@ -204,15 +179,15 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
204
179
  }
205
180
 
206
181
  /**
207
- * emit dismiss event, once: folding or rotating iPhone Duo changes the
208
- * window height while the sheet stays open
182
+ * emit dismiss event
209
183
  */
210
184
  useEffect(() => {
185
+ translateY.value = heightDevice;
211
186
  openAnimation();
212
187
  return () => {
213
- onDismissRef.current?.(action.current);
188
+ props.route.params?.onDismiss?.(action.current);
214
189
  };
215
- }, [openAnimation]);
190
+ }, [heightDevice, openAnimation, props.route.params, translateY]);
216
191
 
217
192
  /**
218
193
  * handler dismiss
@@ -253,42 +228,13 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
253
228
  return () => backHandler.remove();
254
229
  }, [barrierDismissible, onDismiss]);
255
230
 
256
- const onPressBack = useCallback(() => {
257
- onDismiss(true, leftOptions?.onPressIconLeft);
258
- }, [leftOptions, onDismiss]);
259
-
260
- const onPressClose = useCallback(() => {
261
- action.current = 'icon_close';
262
- onDismiss(true);
263
- }, [onDismiss]);
264
-
265
- const showBack = !!leftOptions?.onPressIconLeft;
266
- const iconBack = leftOptions?.iconLeft ?? 'ic_back';
267
- const sideColumnCount = showBack ? 2 : 1;
268
- const sideColumnHeight =
269
- sideColumnCount * DuoMetrics.controlSize +
270
- (sideColumnCount - 1) * Spacing.M;
271
- const coversStatusBar = !!sideColumn && sheetTop < sideColumn.top;
272
-
273
- useEffect(() => {
274
- if (!coversStatusBar) {
275
- return;
276
- }
277
- const entry = StatusBar.pushStackEntry({
278
- barStyle: theme.dark ? 'light-content' : 'dark-content',
279
- animated: true,
280
- });
281
- return () => StatusBar.popStackEntry(entry);
282
- }, [coversStatusBar, theme.dark]);
283
-
284
231
  /**
285
232
  * render header
286
233
  */
287
234
  const renderHeader = useCallback(() => {
288
- const showButtons = !hasSideColumn;
289
235
  return (
290
236
  <View {...panResponder.panHandlers}>
291
- <View style={{ height: INDICATOR_HEIGHT }}>
237
+ <View style={{ height: 16 }}>
292
238
  <View style={styles.indicator} />
293
239
  </View>
294
240
  <View
@@ -300,9 +246,14 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
300
246
  },
301
247
  ]}
302
248
  >
303
- {showButtons && showBack ? (
304
- <TouchableOpacity style={styles.iconButton} onPress={onPressBack}>
305
- <Icon source={iconBack} />
249
+ {leftOptions?.onPressIconLeft ? (
250
+ <TouchableOpacity
251
+ style={styles.iconButton}
252
+ onPress={() => {
253
+ onDismiss(true, leftOptions.onPressIconLeft);
254
+ }}
255
+ >
256
+ <Icon source={leftOptions.iconLeft ?? 'ic_back'} />
306
257
  </TouchableOpacity>
307
258
  ) : null}
308
259
 
@@ -310,15 +261,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
310
261
  <View style={Styles.flex}>{options.header}</View>
311
262
  ) : (
312
263
  <>
313
- {showButtons && !leftOptions ? (
314
- <View style={styles.iconButton} />
315
- ) : null}
264
+ {!leftOptions ? <View style={styles.iconButton} /> : null}
316
265
  <Text
317
- style={[
318
- Styles.flex,
319
- styles.title,
320
- !showButtons && styles.titleInset,
321
- ]}
266
+ style={[Styles.flex, { textAlign: 'center' }]}
322
267
  numberOfLines={1}
323
268
  typography="header_default_bold"
324
269
  >
@@ -326,63 +271,28 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
326
271
  </Text>
327
272
  </>
328
273
  )}
329
- {showButtons ? (
330
- <TouchableOpacity style={styles.iconButton} onPress={onPressClose}>
331
- <Icon source="navigation_close" />
332
- </TouchableOpacity>
333
- ) : null}
274
+ <TouchableOpacity
275
+ style={styles.iconButton}
276
+ onPress={() => {
277
+ action.current = 'icon_close';
278
+ onDismiss(true);
279
+ }}
280
+ >
281
+ <Icon source="navigation_close" />
282
+ </TouchableOpacity>
334
283
  </View>
335
284
  </View>
336
285
  );
337
286
  }, [
338
- hasSideColumn,
339
- iconBack,
287
+ onDismiss,
340
288
  leftOptions,
341
- onPressBack,
342
- onPressClose,
343
289
  options.header,
344
290
  options.title,
345
291
  panResponder.panHandlers,
346
- showBack,
347
292
  theme.colors.border.default,
348
293
  useDivider,
349
294
  ]);
350
295
 
351
- /**
352
- * iPhone Duo folded: back and close move to the side bar column, drawn on
353
- * the part of the sheet under the side safe area. They line up with the
354
- * header row, stay below the status bar cluster on a tall sheet and above a
355
- * camera at the bottom end of the strip.
356
- */
357
- const renderSideColumn = () => {
358
- if (!sideColumn) {
359
- return null;
360
- }
361
- const lowestTop =
362
- windowSize.height - sideColumn.bottom - sideColumnHeight - sheetTop;
363
- const top = Math.min(
364
- Math.max(sideColumn.top - sheetTop, SIDE_COLUMN_OFFSET),
365
- lowestTop,
366
- );
367
-
368
- return (
369
- <View
370
- pointerEvents={'box-none'}
371
- style={[
372
- styles.sideColumn,
373
- sideColumn.edge === 'right' ? styles.alignRight : styles.alignLeft,
374
- InnerEdgeStyles[sideColumn.edge],
375
- { top, width: sideColumnWidth },
376
- ]}
377
- >
378
- {showBack ? (
379
- <SideBarButton icon={iconBack} onPress={onPressBack} />
380
- ) : null}
381
- <SideBarButton icon="navigation_close" onPress={onPressClose} />
382
- </View>
383
- );
384
- };
385
-
386
296
  const animatedStyle = useAnimatedStyle(() => {
387
297
  return {
388
298
  transform: [{ translateY: translateY.value }],
@@ -400,9 +310,12 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
400
310
  };
401
311
  });
402
312
 
403
- const sideInsetStyle = isDuoFloating && {
404
- paddingLeft: Math.max(insets.left - Spacing.M, 0),
405
- paddingRight: Math.max(insets.right - Spacing.M, 0),
313
+ // landscape side buttons span the full height, so inset the content; in
314
+ // portrait the side status bar only covers the top corner
315
+ const needsSideInset = isDuo && !isDuoCentered && !isPortrait;
316
+ const sideInsetStyle = needsSideInset && {
317
+ paddingLeft: insets.left,
318
+ paddingRight: insets.right,
406
319
  };
407
320
 
408
321
  const screen = (
@@ -422,7 +335,6 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
422
335
  }}
423
336
  style={StyleSheet.absoluteFillObject}
424
337
  isModalKit={true}
425
- supportedOrientations={isDuo ? DUO_ORIENTATIONS : undefined}
426
338
  >
427
339
  <KeyboardAvoidingView
428
340
  behavior={Platform.select({
@@ -445,26 +357,18 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
445
357
  </Pressable>
446
358
 
447
359
  <Animated.View
448
- onLayout={
449
- isDuoFloating ? e => setSheetTop(e.nativeEvent.layout.y) : undefined
450
- }
451
360
  style={[
452
361
  animatedStyle,
453
362
  {
454
363
  backgroundColor,
455
- borderTopLeftRadius: isDuo ? Radius.XL : Radius.M,
456
- borderTopRightRadius: isDuo ? Radius.XL : Radius.M,
364
+ borderTopLeftRadius: Radius.M,
365
+ borderTopRightRadius: Radius.M,
457
366
  overflow: 'hidden',
458
367
  },
459
368
  isDuo && {
460
369
  maxHeight: windowSize.height - topGap - keyboardLift,
461
370
  marginBottom: keyboardLift,
462
371
  },
463
- isDuoFloating && { marginHorizontal: Spacing.M },
464
- sideColumn && {
465
- minHeight:
466
- SIDE_COLUMN_OFFSET + sideColumnHeight + sideColumn.bottom,
467
- },
468
372
  isDuoCentered && {
469
373
  // keeps the portrait width in landscape, like system sheets
470
374
  width: Math.min(
@@ -511,7 +415,6 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
511
415
  }}
512
416
  />
513
417
  )}
514
- {renderSideColumn()}
515
418
  </Animated.View>
516
419
  </KeyboardAvoidingView>
517
420
  </Container>
@@ -537,12 +440,10 @@ const styles = StyleSheet.create({
537
440
  top: Spacing.S,
538
441
  },
539
442
  header: {
540
- height: HEADER_HEIGHT,
443
+ height: 56,
541
444
  flexDirection: 'row',
542
445
  alignItems: 'center',
543
446
  },
544
- title: { textAlign: 'center' },
545
- titleInset: { paddingHorizontal: Spacing.M },
546
447
  iconButton: {
547
448
  width: 24,
548
449
  height: 24,
@@ -551,12 +452,6 @@ const styles = StyleSheet.create({
551
452
  marginHorizontal: Spacing.M,
552
453
  },
553
454
  shrink: { flexShrink: 1 },
554
- sideColumn: {
555
- position: 'absolute',
556
- gap: Spacing.M,
557
- },
558
- alignRight: { right: 0 },
559
- alignLeft: { left: 0 },
560
455
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
561
456
  });
562
457
 
@@ -18,7 +18,7 @@ import { Styles } from '../Consts';
18
18
  import Navigation from './Navigation';
19
19
  import { ModalParams } from './types';
20
20
  import BottomSheet from './BottomSheet';
21
- import { getDuoModalBounds, isIPhoneDuo } from './device';
21
+ import { getDuoTopInset, isIPhoneDuo } from './device';
22
22
  import { scheduleOnRN } from 'react-native-worklets';
23
23
 
24
24
  const ModalScreen: React.FC<any> = props => {
@@ -42,7 +42,6 @@ const Modal: React.FC<ModalParams> = props => {
42
42
  const Component = useRef(screen).current;
43
43
  const insets = useSafeAreaInsets();
44
44
  const windowSize = useWindowDimensions();
45
- const isDuo = isIPhoneDuo();
46
45
 
47
46
  /**
48
47
  * Fire auto_popup_displayed tracking event on mount
@@ -136,7 +135,6 @@ const Modal: React.FC<ModalParams> = props => {
136
135
  onRequestClose={onDismiss}
137
136
  style={StyleSheet.absoluteFillObject}
138
137
  isModalKit={true}
139
- supportedOrientations={isDuo ? DUO_ORIENTATIONS : undefined}
140
138
  >
141
139
  <KeyboardAvoidingView
142
140
  style={Styles.flexCenter}
@@ -154,13 +152,14 @@ const Modal: React.FC<ModalParams> = props => {
154
152
  opacity,
155
153
  transform: [{ scale }],
156
154
  },
157
- isDuo && styles.shrink,
158
- isDuo && {
159
- marginTop: insets.top,
155
+ // center below the side status bar and above the home indicator
156
+ isIPhoneDuo() && {
157
+ marginTop: getDuoTopInset(
158
+ insets,
159
+ windowSize.width,
160
+ windowSize.height,
161
+ ),
160
162
  marginBottom: insets.bottom,
161
- marginLeft: insets.left,
162
- marginRight: insets.right,
163
- ...getDuoModalBounds(insets, windowSize.width, windowSize.height),
164
163
  },
165
164
  modalStyle,
166
165
  ]}
@@ -172,14 +171,11 @@ const Modal: React.FC<ModalParams> = props => {
172
171
  );
173
172
  };
174
173
 
175
- const DUO_ORIENTATIONS = ['portrait', 'landscape'];
176
-
177
174
  const styles = StyleSheet.create({
178
175
  overlayContent: {
179
176
  flex: 1,
180
177
  backgroundColor: 'rgba(0, 0, 0, 0.6)',
181
178
  },
182
- shrink: { flexShrink: 1 },
183
179
  });
184
180
 
185
181
  export default ModalScreen;
@@ -1,13 +1,9 @@
1
1
  import { Dimensions, Platform, ScaledSize } from 'react-native';
2
2
  import type { EdgeInsets } from 'react-native-safe-area-context';
3
- import { Spacing } from '../Consts';
4
3
 
5
4
  // widest short side of any single-display iPhone
6
5
  const IPHONE_MAX_SHORT_SIDE = 440;
7
6
 
8
- const DUO_MODAL_MAX_WIDTH = 440;
9
- const DUO_MODAL_MAX_HEIGHT = 640;
10
-
11
7
  // iPhone Duo reports idiom "phone"; 'screen' stays on the outer display, so it
12
8
  // still matches when Split View narrows the window
13
9
  const isIPhoneDuo = () => {
@@ -30,23 +26,4 @@ const getDuoTopInset = (insets: EdgeInsets, width: number, height: number) =>
30
26
  height > width ? Math.max(insets.left, insets.right) * 2 : 0,
31
27
  );
32
28
 
33
- /**
34
- * Max modal size on iPhone Duo: inside the safe area, so clear of the side
35
- * toolbar strip, and capped so it stays a dialog on the inner display.
36
- */
37
- const getDuoModalBounds = (
38
- insets: EdgeInsets,
39
- width: number,
40
- height: number,
41
- ) => ({
42
- maxWidth: Math.min(
43
- width - insets.left - insets.right - Spacing.XL * 2,
44
- DUO_MODAL_MAX_WIDTH,
45
- ),
46
- maxHeight: Math.min(
47
- height - insets.top - insets.bottom - Spacing.XL * 2,
48
- DUO_MODAL_MAX_HEIGHT,
49
- ),
50
- });
51
-
52
- export { isIPhoneDuo, getDuoTopInset, getDuoModalBounds };
29
+ export { isIPhoneDuo, getDuoTopInset };
package/Layout/utils.ts CHANGED
@@ -510,7 +510,6 @@ const useGridSystem = () => {
510
510
  export {
511
511
  DuoMetrics,
512
512
  HeaderMetrics,
513
- InnerEdgeStyles,
514
513
  getHeaderMargin,
515
514
  getHeaderRowOptions,
516
515
  useGridSystem,
@@ -8,7 +8,7 @@ import { Image } from '../Image';
8
8
  import { Text, useScaleSize } from '../Text';
9
9
  import { PopupNotifyProps } from './types';
10
10
  import { scheduleOnRN } from 'react-native-worklets';
11
- import { getPopupRadius, usePopupMaxHeight, usePopupWidth } from './utils';
11
+ import { usePopupMaxHeight, usePopupWidth } from './utils';
12
12
 
13
13
  const PopupNotify: React.FC<PopupNotifyProps> = ({
14
14
  image,
@@ -26,7 +26,6 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
26
26
  const [scrollContent, setScrollContent] = useState(false);
27
27
  const popupWidth = usePopupWidth();
28
28
  const maxHeight = usePopupMaxHeight();
29
- const radius = getPopupRadius();
30
29
  const scaledMaxHeight = useScaleSize(172);
31
30
 
32
31
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
@@ -171,15 +170,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
171
170
 
172
171
  const body = (
173
172
  <>
174
- {!!image && (
175
- <Image
176
- source={{ uri: image }}
177
- style={[
178
- styles.image,
179
- { borderTopLeftRadius: radius, borderTopRightRadius: radius },
180
- ]}
181
- />
182
- )}
173
+ {!!image && <Image source={{ uri: image }} style={styles.image} />}
183
174
  <View style={styles.content}>
184
175
  <Text
185
176
  typography={'header_default_bold'}
@@ -227,9 +218,9 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
227
218
  <View
228
219
  accessibilityLabel={'popup_notify'}
229
220
  style={[
221
+ styles.container,
230
222
  {
231
223
  backgroundColor: theme.colors.background.surface,
232
- borderRadius: radius,
233
224
  width: popupWidth,
234
225
  maxHeight,
235
226
  },
@@ -240,13 +231,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
240
231
  body
241
232
  ) : (
242
233
  // scrolls when iPhone Duo landscape is too short; actions stay visible
243
- <ScrollView
244
- style={[
245
- styles.body,
246
- { borderTopLeftRadius: radius, borderTopRightRadius: radius },
247
- ]}
248
- bounces={false}
249
- >
234
+ <ScrollView style={styles.body} bounces={false}>
250
235
  {body}
251
236
  </ScrollView>
252
237
  )}
@@ -257,12 +242,17 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
257
242
  };
258
243
 
259
244
  const styles = StyleSheet.create({
245
+ container: {
246
+ borderRadius: Radius.L,
247
+ },
260
248
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
261
249
  image: {
250
+ borderTopLeftRadius: Radius.L,
251
+ borderTopRightRadius: Radius.L,
262
252
  aspectRatio: 1.7777,
263
253
  overflow: 'hidden',
264
254
  },
265
- body: { flexShrink: 1, overflow: 'hidden' },
255
+ body: { flexShrink: 1 },
266
256
  content: { padding: Spacing.XL },
267
257
  description: {
268
258
  marginTop: Spacing.S,
package/Popup/utils.ts CHANGED
@@ -1,26 +1,32 @@
1
1
  import { Dimensions, useWindowDimensions } from 'react-native';
2
2
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
3
- import { Radius, Spacing } from '../Consts';
4
- import { getDuoModalBounds, isIPhoneDuo } from '../Application/device';
3
+ import { Spacing } from '../Consts';
4
+ import { getDuoTopInset, isIPhoneDuo } from '../Application/device';
5
+
6
+ // width of a system alert on the iPhone Duo inner display, in both orientations
7
+ const DUO_POPUP_MAX_WIDTH = 440;
5
8
 
6
9
  const usePopupWidth = () => {
7
- const insets = useSafeAreaInsets();
8
- const { width, height } = useWindowDimensions();
10
+ const windowSize = useWindowDimensions();
9
11
  if (!isIPhoneDuo()) {
10
12
  return Dimensions.get('window').width - Spacing.XL * 2;
11
13
  }
12
- return getDuoModalBounds(insets, width, height).maxWidth;
14
+ return Math.min(windowSize.width - Spacing.XL * 2, DUO_POPUP_MAX_WIDTH);
13
15
  };
14
16
 
17
+ // room left for a popup on iPhone Duo, where landscape is too short for it
15
18
  const usePopupMaxHeight = () => {
16
19
  const insets = useSafeAreaInsets();
17
20
  const { width, height } = useWindowDimensions();
18
21
  if (!isIPhoneDuo()) {
19
22
  return undefined;
20
23
  }
21
- return getDuoModalBounds(insets, width, height).maxHeight;
24
+ return (
25
+ height -
26
+ getDuoTopInset(insets, width, height) -
27
+ insets.bottom -
28
+ Spacing.XL * 2
29
+ );
22
30
  };
23
31
 
24
- const getPopupRadius = () => (isIPhoneDuo() ? Radius.XL : Radius.L);
25
-
26
- export { usePopupWidth, usePopupMaxHeight, getPopupRadius };
32
+ export { usePopupWidth, usePopupMaxHeight };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.168.1-beta.14",
3
+ "version": "0.168.1-beta.15",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},