@momo-kits/foundation 0.168.1-beta.3 → 0.168.1-beta.31

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.
@@ -39,28 +39,28 @@ import Animated, {
39
39
  } from 'react-native-reanimated';
40
40
  import { scheduleOnRN } from 'react-native-worklets';
41
41
  import layoutStyles from '../Layout/styles';
42
- import { getDuoTopInset, isIPhoneDuo } from './device';
42
+ import { isIPhoneDuo } from './device';
43
+ import { useModalLayout } from './modalLayout';
43
44
 
44
- // iPhone Duo inner display: center the sheet instead of spanning the width
45
- const DUO_REGULAR_MIN_WIDTH = 600;
45
+ const INDICATOR_HEIGHT = 16;
46
+ const HEADER_HEIGHT = 56;
47
+ const DUO_ORIENTATIONS = ['portrait', 'landscape'];
48
+ // foldable spec: icon button 32, icon 20
49
+ const FOLDABLE_ICON_BUTTON_SIZE = 32;
50
+ const FOLDABLE_ICON_SIZE = 20;
46
51
 
47
52
  const BottomSheet: React.FC<BottomSheetParams> = props => {
48
53
  const { theme, navigator } = useContext(ApplicationContext);
49
54
  const context = useContext<any>(MiniAppContext);
50
55
  const windowSize = useWindowDimensions();
51
56
  const isDuo = isIPhoneDuo();
52
- const isDuoCentered = isDuo && windowSize.width >= DUO_REGULAR_MIN_WIDTH;
53
- const isPortrait = windowSize.height > windowSize.width;
57
+ const { isFoldable, placement, sheet, scrimOpacity } = useModalLayout();
54
58
  // 'screen' is the outer display on iPhone Duo, even when unfolded
55
59
  const screenHeight = Dimensions.get('screen').height;
56
60
  const heightDevice = isDuo ? windowSize.height : screenHeight;
57
61
  const action = useRef<undefined | string>(undefined);
58
62
  const insets = useSafeAreaInsets();
59
63
  const heightHeader = useHeaderHeight();
60
- const topGap = Math.max(
61
- getDuoTopInset(insets, windowSize.width, windowSize.height),
62
- Spacing.XL,
63
- );
64
64
  const isIOS = Platform.OS === 'ios';
65
65
  const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
66
66
  const keyboardOffset = heightHeader - bottomInset;
@@ -83,6 +83,8 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
83
83
  }: BottomSheetParams = props.route.params;
84
84
 
85
85
  const translateY = useSharedValue(heightDevice);
86
+ const onDismissRef = useRef(props.route.params?.onDismiss);
87
+ onDismissRef.current = props.route.params?.onDismiss;
86
88
  const [keyboardLift, setKeyboardLift] = useState(0);
87
89
 
88
90
  // KeyboardAvoidingView lifts the whole sheet past the top on iPhone Duo,
@@ -179,15 +181,15 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
179
181
  }
180
182
 
181
183
  /**
182
- * emit dismiss event
184
+ * emit dismiss event, once: folding or rotating iPhone Duo changes the
185
+ * window height while the sheet stays open
183
186
  */
184
187
  useEffect(() => {
185
- translateY.value = heightDevice;
186
188
  openAnimation();
187
189
  return () => {
188
- props.route.params?.onDismiss?.(action.current);
190
+ onDismissRef.current?.(action.current);
189
191
  };
190
- }, [heightDevice, openAnimation, props.route.params, translateY]);
192
+ }, [openAnimation]);
191
193
 
192
194
  /**
193
195
  * handler dismiss
@@ -228,13 +230,30 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
228
230
  return () => backHandler.remove();
229
231
  }, [barrierDismissible, onDismiss]);
230
232
 
233
+ const onPressBack = useCallback(() => {
234
+ onDismiss(true, leftOptions?.onPressIconLeft);
235
+ }, [leftOptions, onDismiss]);
236
+
237
+ const onPressClose = useCallback(() => {
238
+ action.current = 'icon_close';
239
+ onDismiss(true);
240
+ }, [onDismiss]);
241
+
242
+ const showBack = !!leftOptions?.onPressIconLeft;
243
+ const iconBack = leftOptions?.iconLeft ?? 'ic_back';
244
+
231
245
  /**
232
246
  * render header
233
247
  */
234
248
  const renderHeader = useCallback(() => {
249
+ const iconButtonStyle = [
250
+ styles.iconButton,
251
+ isFoldable && styles.foldableIconButton,
252
+ ];
253
+ const iconSize = isFoldable ? FOLDABLE_ICON_SIZE : undefined;
235
254
  return (
236
255
  <View {...panResponder.panHandlers}>
237
- <View style={{ height: 16 }}>
256
+ <View style={{ height: INDICATOR_HEIGHT }}>
238
257
  <View style={styles.indicator} />
239
258
  </View>
240
259
  <View
@@ -246,14 +265,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
246
265
  },
247
266
  ]}
248
267
  >
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'} />
268
+ {showBack ? (
269
+ <TouchableOpacity style={iconButtonStyle} onPress={onPressBack}>
270
+ <Icon source={iconBack} size={iconSize} />
257
271
  </TouchableOpacity>
258
272
  ) : null}
259
273
 
@@ -261,9 +275,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
261
275
  <View style={Styles.flex}>{options.header}</View>
262
276
  ) : (
263
277
  <>
264
- {!leftOptions ? <View style={styles.iconButton} /> : null}
278
+ {!leftOptions ? <View style={iconButtonStyle} /> : null}
265
279
  <Text
266
- style={[Styles.flex, { textAlign: 'center' }]}
280
+ style={[Styles.flex, styles.title]}
267
281
  numberOfLines={1}
268
282
  typography="header_default_bold"
269
283
  >
@@ -271,24 +285,22 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
271
285
  </Text>
272
286
  </>
273
287
  )}
274
- <TouchableOpacity
275
- style={styles.iconButton}
276
- onPress={() => {
277
- action.current = 'icon_close';
278
- onDismiss(true);
279
- }}
280
- >
281
- <Icon source="navigation_close" />
288
+ <TouchableOpacity style={iconButtonStyle} onPress={onPressClose}>
289
+ <Icon source="navigation_close" size={iconSize} />
282
290
  </TouchableOpacity>
283
291
  </View>
284
292
  </View>
285
293
  );
286
294
  }, [
287
- onDismiss,
295
+ iconBack,
296
+ isFoldable,
288
297
  leftOptions,
298
+ onPressBack,
299
+ onPressClose,
289
300
  options.header,
290
301
  options.title,
291
302
  panResponder.panHandlers,
303
+ showBack,
292
304
  theme.colors.border.default,
293
305
  useDivider,
294
306
  ]);
@@ -304,20 +316,12 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
304
316
  opacity: interpolate(
305
317
  translateY.value,
306
318
  [0, heightDevice],
307
- [1, 0],
319
+ [scrimOpacity, 0],
308
320
  Extrapolation.CLAMP,
309
321
  ),
310
322
  };
311
323
  });
312
324
 
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,
319
- };
320
-
321
325
  const screen = (
322
326
  <Screen
323
327
  {...props}
@@ -335,6 +339,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
335
339
  }}
336
340
  style={StyleSheet.absoluteFillObject}
337
341
  isModalKit={true}
342
+ supportedOrientations={isDuo ? DUO_ORIENTATIONS : undefined}
338
343
  >
339
344
  <KeyboardAvoidingView
340
345
  behavior={Platform.select({
@@ -361,29 +366,27 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
361
366
  animatedStyle,
362
367
  {
363
368
  backgroundColor,
364
- borderTopLeftRadius: Radius.M,
365
- borderTopRightRadius: Radius.M,
369
+ borderTopLeftRadius: sheet.radius,
370
+ borderTopRightRadius: sheet.radius,
366
371
  overflow: 'hidden',
367
372
  },
368
- isDuo && {
369
- maxHeight: windowSize.height - topGap - keyboardLift,
370
- marginBottom: keyboardLift,
371
- },
372
- isDuoCentered && {
373
- // keeps the portrait width in landscape, like system sheets
374
- width: Math.min(
375
- Math.min(windowSize.width, windowSize.height) - Spacing.L * 2,
376
- windowSize.width -
377
- Math.max(insets.left, insets.right, Spacing.L) * 2,
378
- ),
379
- alignSelf: 'center',
380
- },
373
+ isDuo && { marginBottom: keyboardLift },
374
+ isFoldable &&
375
+ sheet.width !== undefined &&
376
+ sheet.maxHeight !== undefined && {
377
+ // centered in the placement area, clear of the iPhone Duo
378
+ // control column and on the right half when half folded
379
+ width: sheet.width,
380
+ marginLeft: placement.x + (placement.width - sheet.width) / 2,
381
+ maxHeight: sheet.maxHeight - keyboardLift,
382
+ minHeight: sheet.minHeight,
383
+ },
381
384
  showBaseLineDebug && styles.debugBaseLine,
382
385
  ]}
383
386
  >
384
- {isDuo ? (
387
+ {isFoldable ? (
385
388
  // content shrinks so a tall sheet keeps its header and footer
386
- <View style={[styles.shrink, sideInsetStyle]}>
389
+ <View style={styles.shrink}>
387
390
  {renderHeader()}
388
391
  <View style={styles.shrink}>{screen}</View>
389
392
  </View>
@@ -401,7 +404,6 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
401
404
  paddingBottom: Spacing.S,
402
405
  backgroundColor: theme.colors.background.surface,
403
406
  },
404
- sideInsetStyle,
405
407
  ]}
406
408
  >
407
409
  {footerComponent}
@@ -428,7 +430,7 @@ const styles = StyleSheet.create({
428
430
  },
429
431
  overlay: {
430
432
  flex: 1,
431
- backgroundColor: 'rgba(0, 0, 0, 0.6)',
433
+ backgroundColor: Colors.black_20,
432
434
  },
433
435
  indicator: {
434
436
  width: 40,
@@ -440,10 +442,11 @@ const styles = StyleSheet.create({
440
442
  top: Spacing.S,
441
443
  },
442
444
  header: {
443
- height: 56,
445
+ height: HEADER_HEIGHT,
444
446
  flexDirection: 'row',
445
447
  alignItems: 'center',
446
448
  },
449
+ title: { textAlign: 'center' },
447
450
  iconButton: {
448
451
  width: 24,
449
452
  height: 24,
@@ -451,6 +454,10 @@ const styles = StyleSheet.create({
451
454
  justifyContent: 'center',
452
455
  marginHorizontal: Spacing.M,
453
456
  },
457
+ foldableIconButton: {
458
+ width: FOLDABLE_ICON_BUTTON_SIZE,
459
+ height: FOLDABLE_ICON_BUTTON_SIZE,
460
+ },
454
461
  shrink: { flexShrink: 1 },
455
462
  debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
456
463
  });