@momo-kits/foundation 0.167.1-beta.7 → 0.167.1-duo-test.1

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.
Files changed (47) hide show
  1. package/Application/BottomSheet.tsx +115 -14
  2. package/Application/BottomTab/BottomTabBar.tsx +6 -3
  3. package/Application/BottomTab/DuoBarCompression.tsx +135 -0
  4. package/Application/BottomTab/DuoTabBar.tsx +462 -0
  5. package/Application/BottomTab/index.tsx +150 -134
  6. package/Application/Components/DuoGlassBackground.tsx +179 -0
  7. package/Application/Components/DuoQuickReturnHeader.tsx +42 -0
  8. package/Application/Components/HeaderExtendHeader.tsx +75 -28
  9. package/Application/Components/HeaderLeft.tsx +38 -0
  10. package/Application/Components/HeaderRight.tsx +142 -9
  11. package/Application/Components/HeaderTitle.tsx +61 -21
  12. package/Application/Components/OptionalGlassView.ts +11 -0
  13. package/Application/Components/OptionalScrollObserver.ts +12 -0
  14. package/Application/Components/SearchHeader.tsx +22 -15
  15. package/Application/Components/SideBarButton.tsx +139 -0
  16. package/Application/ModalScreen.tsx +16 -2
  17. package/Application/NavigationContainer.tsx +36 -50
  18. package/Application/Navigator.ts +1 -3
  19. package/Application/StackScreen.tsx +63 -9
  20. package/Application/WidgetContainer.tsx +9 -15
  21. package/Application/device.ts +29 -0
  22. package/Application/types.ts +27 -1
  23. package/Application/utils.tsx +255 -7
  24. package/Consts/index.ts +0 -1
  25. package/Consts/theme.ts +3 -4
  26. package/Input/Input.tsx +3 -4
  27. package/Input/InputMoney.tsx +31 -51
  28. package/Input/InputPhoneNumber.tsx +3 -4
  29. package/Input/InputSearch.tsx +3 -4
  30. package/Input/InputTextArea.tsx +3 -4
  31. package/Layout/Card.tsx +8 -26
  32. package/Layout/FloatingButton.tsx +3 -3
  33. package/Layout/Item.tsx +1 -10
  34. package/Layout/ReservedRegions.tsx +47 -0
  35. package/Layout/Screen.tsx +199 -52
  36. package/Layout/Section.tsx +5 -18
  37. package/Layout/index.ts +4 -4
  38. package/Layout/styles.ts +5 -0
  39. package/Layout/utils.ts +465 -5
  40. package/Popup/PopupNotify.tsx +33 -22
  41. package/Popup/PopupPromotion.tsx +20 -8
  42. package/Popup/utils.ts +32 -0
  43. package/Text/index.tsx +5 -15
  44. package/Text/utils.ts +1 -2
  45. package/package.json +3 -2
  46. package/Consts/designSystemFont.ts +0 -25
  47. package/Layout/GridSystem.tsx +0 -137
package/Layout/Card.tsx CHANGED
@@ -1,12 +1,12 @@
1
1
  import React, { useContext } from 'react';
2
- import { Dimensions, View, ViewProps } from 'react-native';
3
- import { useGridSystem } from './utils';
2
+ import { View, ViewProps } from 'react-native';
3
+ import { useGridSystem, useScreenLayout } from './utils';
4
4
  import { GridContextProps } from './types';
5
- import { GridContext, GridSystem } from './index';
5
+ import { GridContext } from './index';
6
6
  import { Image } from '../Image';
7
- import { ApplicationContext, MiniAppContext } from '../Context';
7
+ import { ApplicationContext } from '../Context';
8
8
  import styles from './styles';
9
- import { Colors, Radius, Shadow, Spacing } from '../Consts';
9
+ import { Radius, Shadow, Spacing } from '../Consts';
10
10
 
11
11
  export interface CardProps extends ViewProps {
12
12
  /**
@@ -27,12 +27,13 @@ const Card: React.FC<CardProps> = ({
27
27
  useShadow = false,
28
28
  ...props
29
29
  }) => {
30
- const { showGrid, theme } = useContext(ApplicationContext);
30
+ const { theme } = useContext(ApplicationContext);
31
31
  const { numberOfColumns } = useGridSystem();
32
+ const { contentWidth } = useScreenLayout();
32
33
 
33
34
  const gutterSize = 8;
34
35
  const margin = 12;
35
- const widthSection = Dimensions.get('window').width - margin * 2;
36
+ const widthSection = contentWidth - margin * 2;
36
37
  let sizePerSpan =
37
38
  (widthSection - margin * 2 - gutterSize * (numberOfColumns - 1)) /
38
39
  numberOfColumns -
@@ -47,23 +48,6 @@ const Card: React.FC<CardProps> = ({
47
48
  },
48
49
  };
49
50
 
50
- /**
51
- * render overlay only dev mode
52
- */
53
- const renderOverlay = () => {
54
- return (
55
- <>
56
- <GridSystem
57
- width={widthSection}
58
- gutterSize={Spacing.S}
59
- screenGrid={false}
60
- gridColor={Colors.red_01}
61
- />
62
- <View pointerEvents={'none'} style={styles.boxGridCard} />
63
- </>
64
- );
65
- };
66
-
67
51
  return (
68
52
  <GridContext.Provider value={gridContext}>
69
53
  <View
@@ -95,9 +79,7 @@ const Card: React.FC<CardProps> = ({
95
79
  ]}
96
80
  />
97
81
  )}
98
- {/* Render only allowed children as valid React elements */}
99
82
  {children}
100
- {showGrid && renderOverlay()}
101
83
  </View>
102
84
  </GridContext.Provider>
103
85
  );
@@ -10,8 +10,8 @@ import Animated, {
10
10
  useAnimatedStyle,
11
11
  useSharedValue,
12
12
  withTiming,
13
- runOnJS,
14
13
  } from 'react-native-reanimated';
14
+ import { scheduleOnRN } from 'react-native-worklets';
15
15
  import { ApplicationContext } from '../Context';
16
16
  import { Icon } from '../Icon';
17
17
  import { useScaleSize, exportFontFamily } from '../Text';
@@ -78,7 +78,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
78
78
  minWidth,
79
79
  { duration: 100 },
80
80
  finished => {
81
- if (finished) runOnJS(setShowText)(false);
81
+ if (finished) scheduleOnRN(setShowText, false);
82
82
  },
83
83
  );
84
84
  } else {
@@ -87,7 +87,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
87
87
  maxWidth.value,
88
88
  { duration: 100 },
89
89
  finished => {
90
- if (finished) runOnJS(setShowText)(true);
90
+ if (finished) scheduleOnRN(setShowText, true);
91
91
  },
92
92
  );
93
93
  }
package/Layout/Item.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, { useContext } from 'react';
2
2
  import { View, ViewProps } from 'react-native';
3
3
  import { GridContext } from './index';
4
- import { ApplicationContext, MiniAppContext } from '../Context';
4
+ import { MiniAppContext } from '../Context';
5
5
  import styles from './styles';
6
6
  import { SpanNumber } from './types';
7
7
 
@@ -23,19 +23,11 @@ const Item: React.FC<ItemProps> = ({
23
23
  children,
24
24
  style,
25
25
  }) => {
26
- const { showGrid } = useContext(ApplicationContext);
27
26
  const context = useContext<any>(MiniAppContext);
28
27
  const grid = useContext(GridContext);
29
28
 
30
29
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
31
30
 
32
- /**
33
- * render overlay view only dev mode
34
- */
35
- const renderOverlay = () => {
36
- return <View pointerEvents={'none'} style={styles.boxGrid} />;
37
- };
38
-
39
31
  return (
40
32
  <View
41
33
  style={[
@@ -49,7 +41,6 @@ const Item: React.FC<ItemProps> = ({
49
41
  ]}
50
42
  >
51
43
  {children}
52
- {showGrid && renderOverlay()}
53
44
  </View>
54
45
  );
55
46
  };
@@ -0,0 +1,47 @@
1
+ import React, { createContext, ReactNode, useState } from 'react';
2
+ import { Platform, StyleSheet } from 'react-native';
3
+
4
+ export type ReservedRegion = {
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ };
10
+
11
+ // native probe is optional so apps without @momo-kits/reserved-regions still bundle
12
+ let ReservedRegionsView: React.ComponentType<any> | undefined;
13
+ try {
14
+ ReservedRegionsView =
15
+ require('@momo-kits/reserved-regions').ReservedRegionsView;
16
+ } catch {
17
+ ReservedRegionsView = undefined;
18
+ }
19
+
20
+ /**
21
+ * screen regions the system currently occludes, in window coordinates
22
+ */
23
+ const ReservedRegionsContext = createContext<ReservedRegion[]>([]);
24
+
25
+ /**
26
+ * tracks the system occlusion regions (iOS 27.1+), e.g. the iPhone Duo status bar
27
+ * cluster growing for a call or a Live Activity
28
+ */
29
+ const ReservedRegionsProvider: React.FC<{ children: ReactNode }> = ({
30
+ children,
31
+ }) => {
32
+ const [regions, setRegions] = useState<ReservedRegion[]>([]);
33
+
34
+ return (
35
+ <ReservedRegionsContext.Provider value={regions}>
36
+ {children}
37
+ {Platform.OS === 'ios' && !!ReservedRegionsView && (
38
+ <ReservedRegionsView
39
+ style={StyleSheet.absoluteFill}
40
+ onRegionsChange={setRegions}
41
+ />
42
+ )}
43
+ </ReservedRegionsContext.Provider>
44
+ );
45
+ };
46
+
47
+ export { ReservedRegionsContext, ReservedRegionsProvider };
package/Layout/Screen.tsx CHANGED
@@ -21,7 +21,6 @@ import {
21
21
  ScrollViewProps,
22
22
  StatusBar,
23
23
  StatusBarStyle,
24
- useWindowDimensions,
25
24
  View,
26
25
  ViewProps,
27
26
  } from 'react-native';
@@ -43,8 +42,12 @@ import { FloatingButton, FloatingButtonProps } from './FloatingButton';
43
42
  import styles from './styles';
44
43
  import { HeaderType } from './types';
45
44
  import { InputRef } from '../Input';
46
- import { exportHeaderTitle, getOptions } from '../Application/utils';
47
- import { validateChildren } from './utils';
45
+ import {
46
+ exportHeaderTitle,
47
+ getOptions,
48
+ useDuoQuickReturn,
49
+ } from '../Application/utils';
50
+ import { getHeaderMargin, useScreenLayout, validateChildren } from './utils';
48
51
  import Section from './Section';
49
52
  import {
50
53
  HeaderBackground,
@@ -53,6 +56,11 @@ import {
53
56
  HeaderTitle,
54
57
  SearchHeader,
55
58
  } from '../Application';
59
+ import {
60
+ DuoGlassBackground,
61
+ DuoScrollGradient,
62
+ } from '../Application/Components/DuoGlassBackground';
63
+ import { useDuoTabBarInset } from '../Application/BottomTab/DuoTabBar';
56
64
 
57
65
  export interface ScreenProps extends ViewProps {
58
66
  /**
@@ -118,7 +126,8 @@ export interface ScreenProps extends ViewProps {
118
126
  layoutOffset?: -8 | -24 | -56 | number;
119
127
 
120
128
  /**
121
- * Optional. Width of the right header.
129
+ * Optional. Width of the right header. Defaults to the toolkit width (73), or 0 when
130
+ * iPhone Duo floats the toolkit into its side bar.
122
131
  */
123
132
  headerRightWidth?: 0 | 73 | 110 | number;
124
133
 
@@ -187,7 +196,7 @@ const Screen = forwardRef(
187
196
  footerComponent: Footer,
188
197
  marginFooter = true,
189
198
  layoutOffset = -56,
190
- headerRightWidth = 73,
199
+ headerRightWidth,
191
200
  backgroundColor,
192
201
  headerType = 'default',
193
202
  floatingButtonProps,
@@ -204,8 +213,8 @@ const Screen = forwardRef(
204
213
  ref: any,
205
214
  ) => {
206
215
  const screenRef = useRef<View | ScrollView>(null);
207
- const { width: widthDevice } = useWindowDimensions();
208
- const { theme, navigator } = useContext(ApplicationContext);
216
+ const layout = useScreenLayout();
217
+ const { theme } = useContext(ApplicationContext);
209
218
  const screen: any = useContext(ScreenContext);
210
219
  const insets = useSafeAreaInsets();
211
220
  const frame = useSafeAreaFrame();
@@ -245,15 +254,37 @@ const Screen = forwardRef(
245
254
  }, [customAnimatedValue, internalShared]);
246
255
 
247
256
  const currentTint = useRef<string | undefined>(undefined);
248
-
257
+
249
258
  const isTab = !!screen?.bottomTab;
259
+ // Duo floating tab bar overlays the tab content instead of taking layout space
260
+ const duoTabInset = useDuoTabBarInset();
250
261
 
251
262
  let handleScroll;
252
263
  let Component: any = View;
253
264
 
254
- const bottomInset = Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
265
+ const bottomInset =
266
+ Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
267
+ const hasSideBar = !!layout.sideBar;
268
+ // iPhone Duo: content scrolls under a glass header instead of starting below it
269
+ const useDuoGlass =
270
+ hasSideBar &&
271
+ (headerType === 'default' || headerType === 'extended') &&
272
+ !animatedHeader &&
273
+ !inputSearchProps;
274
+
275
+ // iPhone Duo: the header leaves with the content and returns on a reverse scroll
276
+ const useQuickReturn =
277
+ hasSideBar && scrollable && (useDuoGlass || !!animatedHeader);
278
+ const { quickReturnOptions, snap: snapQuickReturnHeader } =
279
+ useDuoQuickReturn(sharedValue, useQuickReturn, heightHeader);
280
+
255
281
  let keyboardOffset = heightHeader - bottomInset;
256
- if (headerType === 'extended' || animatedHeader || inputSearchProps) {
282
+ if (
283
+ headerType === 'extended' ||
284
+ animatedHeader ||
285
+ inputSearchProps ||
286
+ useDuoGlass
287
+ ) {
257
288
  keyboardOffset = -bottomInset;
258
289
  }
259
290
 
@@ -261,6 +292,12 @@ const Screen = forwardRef(
261
292
  * inject params for screen tracking
262
293
  */
263
294
  screen?.onSetParams?.(trackingParams);
295
+ // the screen drives its header, the stack screen's scroll observer stays off
296
+ screen?.claimHeader?.();
297
+ useEffect(() => {
298
+ screen?.syncHeader?.();
299
+ // eslint-disable-next-line react-hooks/exhaustive-deps
300
+ }, []);
264
301
 
265
302
  /**
266
303
  * export options for screen
@@ -280,9 +317,13 @@ const Screen = forwardRef(
280
317
 
281
318
  case 'extended':
282
319
  options = {
320
+ ...quickReturnOptions,
283
321
  headerShown: true,
284
322
  headerTransparent: true,
285
- headerBackground: () => null,
323
+ headerBackground: () =>
324
+ hasSideBar ? (
325
+ <DuoGlassBackground animatedValue={sharedValue} />
326
+ ) : null,
286
327
  headerTitle: (props: any) => <HeaderTitle {...props} />,
287
328
  };
288
329
  if (inputSearchProps) {
@@ -305,6 +346,19 @@ const Screen = forwardRef(
305
346
  break;
306
347
 
307
348
  default:
349
+ if (hasSideBar && !inputSearchProps) {
350
+ options = {
351
+ ...quickReturnOptions,
352
+ headerTransparent: true,
353
+ headerTintColor: Colors.black_17,
354
+ headerShown: true,
355
+ headerBackground: () => (
356
+ <DuoGlassBackground animatedValue={sharedValue} />
357
+ ),
358
+ headerTitle: (props: any) => <HeaderTitle {...props} />,
359
+ };
360
+ break;
361
+ }
308
362
  options = {
309
363
  headerTransparent: false,
310
364
  headerTintColor: Colors.black_17,
@@ -339,7 +393,7 @@ const Screen = forwardRef(
339
393
  }
340
394
  }
341
395
 
342
- navigation?.instance?.setOptions(options);
396
+ navigation?.instance?.setOptions({ header: undefined, ...options });
343
397
  },
344
398
  [
345
399
  gradientColor,
@@ -348,6 +402,8 @@ const Screen = forwardRef(
348
402
  navigation?.instance,
349
403
  sharedValue,
350
404
  useShadowHeader,
405
+ hasSideBar,
406
+ quickReturnOptions,
351
407
  ],
352
408
  );
353
409
 
@@ -363,18 +419,26 @@ const Screen = forwardRef(
363
419
  }
364
420
 
365
421
  options = {
422
+ ...quickReturnOptions,
366
423
  headerTintColor: currentTint.current ?? Colors.black_17,
367
424
  headerTransparent: true,
368
- headerBackground: (props: any) => (
369
- <HeaderBackground
370
- {...props}
371
- animatedValue={sharedValue}
372
- useGradient={false}
373
- useShadowHeader={useShadowHeader}
374
- headerBackground={headerBackground}
375
- gradientColor={gradientColor}
376
- />
377
- ),
425
+ headerBackground: (props: any) =>
426
+ hasSideBar ? (
427
+ // glass shows once the banner has scrolled under the header
428
+ <DuoGlassBackground
429
+ animatedValue={sharedValue}
430
+ fadeStart={Math.max(210 + layoutOffset - heightHeader, 0)}
431
+ />
432
+ ) : (
433
+ <HeaderBackground
434
+ {...props}
435
+ animatedValue={sharedValue}
436
+ useGradient={false}
437
+ useShadowHeader={useShadowHeader}
438
+ headerBackground={headerBackground}
439
+ gradientColor={gradientColor}
440
+ />
441
+ ),
378
442
  headerTitle: (props: any) => {
379
443
  return <HeaderTitle {...props} />;
380
444
  },
@@ -395,6 +459,10 @@ const Screen = forwardRef(
395
459
  navigation?.instance,
396
460
  sharedValue,
397
461
  useShadowHeader,
462
+ hasSideBar,
463
+ layoutOffset,
464
+ heightHeader,
465
+ quickReturnOptions,
398
466
  ],
399
467
  );
400
468
 
@@ -418,14 +486,24 @@ const Screen = forwardRef(
418
486
  ),
419
487
  };
420
488
 
421
- navigation?.instance?.setOptions(options);
489
+ navigation?.instance?.setOptions({ header: undefined, ...options });
422
490
  }, [navigation?.instance, sharedValue]);
423
491
 
424
492
  /**
425
493
  * export search header
426
494
  */
427
495
  const setSearchHeader = (params: SearchHeaderProps) => {
428
- const searchWidth = widthDevice - 24;
496
+ let searchWidth = layout.width - 24;
497
+ let searchLeft = Spacing.M;
498
+ if (layout.sideBar) {
499
+ // back floats in the side bar: the search spans the content width
500
+ searchWidth = layout.contentWidth - Spacing.M * 2;
501
+ searchLeft = Spacing.M - getHeaderMargin(layout.width);
502
+ }
503
+ if (layout.statusBarClearance) {
504
+ searchWidth -=
505
+ layout.statusBarClearance.left + layout.statusBarClearance.right;
506
+ }
429
507
  const options: StackNavigationOptions = {
430
508
  headerRight: undefined,
431
509
  headerTitleContainerStyle: {
@@ -437,10 +515,10 @@ const Screen = forwardRef(
437
515
  marginHorizontal: 0,
438
516
  marginVertical: 0,
439
517
  margin: 0,
440
- left: Spacing.M,
518
+ left: searchLeft,
441
519
  },
442
520
  headerLeft: (props: any) =>
443
- params?.hiddenBack ? null : <HeaderLeft {...props} />,
521
+ params?.hiddenBack ? null : <HeaderLeft {...props} floating />,
444
522
  headerTitle: () => (
445
523
  <SearchHeader {...params} animatedValue={sharedValue} />
446
524
  ),
@@ -536,12 +614,16 @@ const Screen = forwardRef(
536
614
  );
537
615
  }
538
616
 
539
- /**
540
- * handle scroll end
541
- * @param e
542
- */
617
+ const handleMomentumScrollEnd = (
618
+ e: NativeSyntheticEvent<NativeScrollEvent>,
619
+ ) => {
620
+ snapQuickReturnHeader(e.nativeEvent.contentOffset.y);
621
+ scrollViewProps?.onMomentumScrollEnd?.(e);
622
+ };
623
+
543
624
  const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
544
625
  const offsetY = e.nativeEvent.contentOffset.y;
626
+ snapQuickReturnHeader(offsetY);
545
627
  if (inputSearchProps && offsetY < 100 && offsetY > 0) {
546
628
  Animated.timing(animatedValue.current, {
547
629
  toValue: 0,
@@ -591,6 +673,8 @@ const Screen = forwardRef(
591
673
  key={`ScreenItem${index}`}
592
674
  style={{
593
675
  marginTop: space,
676
+ marginLeft: layout.insetLeft,
677
+ marginRight: layout.insetRight,
594
678
  flex: item.props?.expanded ? 1 : undefined,
595
679
  }}
596
680
  onLayout={item.props?.onLayout}
@@ -611,6 +695,8 @@ const Screen = forwardRef(
611
695
  key={`ScreenItem`}
612
696
  style={{
613
697
  marginTop: space,
698
+ marginLeft: layout.insetLeft,
699
+ marginRight: layout.insetRight,
614
700
  flex: item?.props?.expanded ? 1 : undefined,
615
701
  }}
616
702
  onLayout={item?.props?.onLayout}
@@ -637,6 +723,15 @@ const Screen = forwardRef(
637
723
  return newHeight;
638
724
  };
639
725
 
726
+ // without the grid, the content container keeps clear of the horizontal insets.
727
+ // Only set when there are insets, so a caller's paddingHorizontal is never overridden.
728
+ const hasContentInset = layout.insetLeft > 0 || layout.insetRight > 0;
729
+ const contentInsetStyle = !useGridLayout &&
730
+ hasContentInset && {
731
+ paddingLeft: layout.insetLeft,
732
+ paddingRight: layout.insetRight,
733
+ };
734
+
640
735
  return (
641
736
  <View
642
737
  style={[
@@ -649,21 +744,36 @@ const Screen = forwardRef(
649
744
  },
650
745
  ]}
651
746
  >
652
- <HeaderExtendHeader
653
- headerType={headerType}
654
- heightHeader={heightHeader}
655
- headerRightWidth={headerRightWidth}
656
- animatedValue={sharedValue}
657
- inputSearchProps={inputSearchProps}
658
- navigation={navigation}
659
- inputSearchRef={inputSearchRef}
660
- useShadowHeader={useShadowHeader}
661
- gradientColor={gradientColor}
662
- headerBackground={headerBackground}
663
- />
747
+ {useDuoGlass ? (
748
+ <DuoScrollGradient
749
+ animatedValue={sharedValue}
750
+ gradientColor={gradientColor}
751
+ headerBackground={headerBackground}
752
+ heightHeader={heightHeader}
753
+ extended={headerType === 'extended'}
754
+ followScroll={useQuickReturn}
755
+ />
756
+ ) : (
757
+ <HeaderExtendHeader
758
+ headerType={headerType}
759
+ heightHeader={heightHeader}
760
+ headerRightWidth={headerRightWidth}
761
+ animatedValue={sharedValue}
762
+ inputSearchProps={inputSearchProps}
763
+ navigation={navigation}
764
+ inputSearchRef={inputSearchRef}
765
+ useShadowHeader={useShadowHeader}
766
+ gradientColor={gradientColor}
767
+ headerBackground={headerBackground}
768
+ />
769
+ )}
664
770
 
665
771
  <KeyboardAvoidingView
666
- style={Styles.flex}
772
+ style={[
773
+ Styles.flex,
774
+ useDuoGlass &&
775
+ (!scrollable || !!Header) && { paddingTop: heightHeader },
776
+ ]}
667
777
  enabled={enableKeyboardAvoidingView}
668
778
  keyboardVerticalOffset={keyboardVerticalOffset ?? keyboardOffset}
669
779
  behavior={Platform.select({
@@ -679,10 +789,24 @@ const Screen = forwardRef(
679
789
  showsVerticalScrollIndicator={false}
680
790
  onScroll={handleScroll}
681
791
  onScrollEndDrag={handleScrollEnd}
792
+ onMomentumScrollEnd={handleMomentumScrollEnd}
682
793
  scrollEventThrottle={16}
683
- style={[Styles.flex]}
794
+ style={[
795
+ Styles.flex,
796
+ !scrollable && contentInsetStyle,
797
+ !scrollable &&
798
+ !Footer &&
799
+ isTab &&
800
+ !!duoTabInset && { paddingBottom: duoTabInset },
801
+ ]}
684
802
  contentContainerStyle={[
685
803
  scrollable && !Footer && !isTab && { paddingBottom: bottomInset },
804
+ scrollable &&
805
+ !Footer &&
806
+ isTab &&
807
+ !!duoTabInset && { paddingBottom: duoTabInset },
808
+ scrollable && contentInsetStyle,
809
+ useDuoGlass && !Header && { paddingTop: heightHeader },
686
810
  scrollViewProps?.contentContainerStyle,
687
811
  ]}
688
812
  >
@@ -692,28 +816,51 @@ const Screen = forwardRef(
692
816
  </Component>
693
817
 
694
818
  {floatingButtonProps && (
695
- <View>
819
+ <View
820
+ style={{
821
+ marginLeft: layout.insetLeft,
822
+ marginRight: layout.insetRight,
823
+ }}
824
+ >
696
825
  <FloatingButton
697
826
  {...floatingButtonProps}
698
827
  animatedValue={sharedValue}
699
- bottom={Footer || isTab ? 12 : bottomInset + Spacing.S}
828
+ bottom={
829
+ Footer || isTab
830
+ ? 12 + (Footer ? 0 : duoTabInset)
831
+ : bottomInset + Spacing.S
832
+ }
700
833
  />
701
834
  </View>
702
835
  )}
703
836
 
704
837
  {Footer && (
838
+ // with side insets, the clip keeps the shadow off the side bar, only the top one shows
705
839
  <View
840
+ pointerEvents={'box-none'}
706
841
  style={[
707
- styles.shadow,
842
+ hasContentInset && styles.footerClip,
708
843
  {
709
- paddingBottom: bottomInset + Spacing.S,
710
- backgroundColor: theme.colors.background.surface,
844
+ marginLeft: layout.insetLeft,
845
+ marginRight: layout.insetRight,
711
846
  },
712
847
  ]}
713
848
  >
714
- {/* The footer inset is a caller decision, not a platform one — a footer
715
- that carries its own horizontal padding passes `marginFooter={false}`. */}
716
- <Section useMargin={marginFooter}>{Footer}</Section>
849
+ <View
850
+ style={[
851
+ styles.shadow,
852
+ {
853
+ paddingBottom:
854
+ (isTab && duoTabInset ? duoTabInset : bottomInset) +
855
+ Spacing.S,
856
+ backgroundColor: theme.colors.background.surface,
857
+ },
858
+ ]}
859
+ >
860
+ {/* The footer inset is a caller decision, not a platform one — a footer
861
+ that carries its own horizontal padding passes `marginFooter={false}`. */}
862
+ <Section useMargin={marginFooter}>{Footer}</Section>
863
+ </View>
717
864
  </View>
718
865
  )}
719
866
  </KeyboardAvoidingView>
@@ -1,9 +1,8 @@
1
- import React, { useContext } from 'react';
2
- import { Dimensions, View, ViewProps } from 'react-native';
3
- import { useGridSystem } from './utils';
1
+ import React from 'react';
2
+ import { View, ViewProps } from 'react-native';
3
+ import { useGridSystem, useScreenLayout } from './utils';
4
4
  import { GridContextProps } from './types';
5
5
  import { GridContext } from './index';
6
- import { ApplicationContext, MiniAppContext } from '../Context';
7
6
  import styles from './styles';
8
7
  import { Image } from '../Image';
9
8
  import { Spacing } from '../Consts';
@@ -34,18 +33,15 @@ const Section: React.FC<SectionProps> = ({
34
33
  style,
35
34
  ...props
36
35
  }) => {
37
- const { showGrid } = useContext(ApplicationContext);
38
36
  const { numberOfColumns } = useGridSystem();
37
+ const { contentWidth } = useScreenLayout();
39
38
 
40
39
  const gutterSize = 12;
41
40
  let margin = 0;
42
41
  if (useMargin) {
43
42
  margin = 12;
44
43
  }
45
- let widthSection = Dimensions.get('window').width;
46
- if (useMargin) {
47
- widthSection = widthSection - margin * 2;
48
- }
44
+ const widthSection = contentWidth - margin * 2;
49
45
 
50
46
  const sizePerSpan =
51
47
  (widthSection - gutterSize * (numberOfColumns - 1)) / numberOfColumns -
@@ -60,13 +56,6 @@ const Section: React.FC<SectionProps> = ({
60
56
  },
61
57
  };
62
58
 
63
- /**
64
- * render overlay only dev mode
65
- */
66
- const renderOverlay = () => {
67
- return <View pointerEvents={'none'} style={styles.boxGridSection} />;
68
- };
69
-
70
59
  return (
71
60
  <GridContext.Provider value={gridContext}>
72
61
  <View
@@ -90,9 +79,7 @@ const Section: React.FC<SectionProps> = ({
90
79
  style={styles.imageBackground}
91
80
  />
92
81
  )}
93
- {/* Render only allowed children as valid React elements */}
94
82
  {children}
95
- {showGrid && renderOverlay()}
96
83
  </View>
97
84
  </GridContext.Provider>
98
85
  );