@momo-kits/foundation 0.167.1-beta.5 → 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.
- package/Application/BottomSheet.tsx +115 -14
- package/Application/BottomTab/BottomTabBar.tsx +6 -3
- package/Application/BottomTab/DuoBarCompression.tsx +135 -0
- package/Application/BottomTab/DuoTabBar.tsx +462 -0
- package/Application/BottomTab/index.tsx +150 -134
- package/Application/Components/DuoGlassBackground.tsx +179 -0
- package/Application/Components/DuoQuickReturnHeader.tsx +42 -0
- package/Application/Components/HeaderExtendHeader.tsx +75 -28
- package/Application/Components/HeaderLeft.tsx +38 -0
- package/Application/Components/HeaderRight.tsx +142 -9
- package/Application/Components/HeaderTitle.tsx +67 -17
- package/Application/Components/OptionalGlassView.ts +11 -0
- package/Application/Components/OptionalScrollObserver.ts +12 -0
- package/Application/Components/SearchHeader.tsx +22 -15
- package/Application/Components/SideBarButton.tsx +139 -0
- package/Application/ModalScreen.tsx +16 -2
- package/Application/NavigationContainer.tsx +36 -45
- package/Application/Navigator.ts +1 -3
- package/Application/StackScreen.tsx +63 -9
- package/Application/WidgetContainer.tsx +9 -7
- package/Application/device.ts +29 -0
- package/Application/types.ts +27 -1
- package/Application/utils.tsx +255 -7
- package/Consts/index.ts +0 -1
- package/Consts/theme.ts +5 -6
- package/Input/Input.tsx +3 -4
- package/Input/InputMoney.tsx +31 -51
- package/Input/InputPhoneNumber.tsx +3 -4
- package/Input/InputSearch.tsx +3 -4
- package/Input/InputTextArea.tsx +3 -4
- package/Layout/Card.tsx +8 -26
- package/Layout/FloatingButton.tsx +3 -3
- package/Layout/Item.tsx +1 -10
- package/Layout/ReservedRegions.tsx +47 -0
- package/Layout/Screen.tsx +199 -52
- package/Layout/Section.tsx +5 -18
- package/Layout/index.ts +4 -4
- package/Layout/styles.ts +5 -0
- package/Layout/utils.ts +465 -5
- package/Popup/PopupNotify.tsx +33 -22
- package/Popup/PopupPromotion.tsx +20 -8
- package/Popup/utils.ts +32 -0
- package/Text/index.tsx +2 -11
- package/Text/utils.ts +1 -2
- package/package.json +3 -2
- package/Consts/designSystemFont.ts +0 -38
- package/Layout/GridSystem.tsx +0 -137
package/Layout/Card.tsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
|
-
import {
|
|
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
|
|
5
|
+
import { GridContext } from './index';
|
|
6
6
|
import { Image } from '../Image';
|
|
7
|
-
import { ApplicationContext
|
|
7
|
+
import { ApplicationContext } from '../Context';
|
|
8
8
|
import styles from './styles';
|
|
9
|
-
import {
|
|
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 {
|
|
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 =
|
|
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)
|
|
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)
|
|
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 {
|
|
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 {
|
|
47
|
-
|
|
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
|
|
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
|
|
208
|
-
const { theme
|
|
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 =
|
|
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 (
|
|
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: () =>
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
541
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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={
|
|
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={[
|
|
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={
|
|
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.
|
|
842
|
+
hasContentInset && styles.footerClip,
|
|
708
843
|
{
|
|
709
|
-
|
|
710
|
-
|
|
844
|
+
marginLeft: layout.insetLeft,
|
|
845
|
+
marginRight: layout.insetRight,
|
|
711
846
|
},
|
|
712
847
|
]}
|
|
713
848
|
>
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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>
|
package/Layout/Section.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
);
|