@momo-kits/foundation 0.165.1-beta.4 → 0.165.1-duotest.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/BottomTab/BottomTabBar.tsx +6 -3
- package/Application/BottomTab/index.tsx +14 -14
- package/Application/Components/HeaderAnimated.tsx +18 -2
- package/Application/Components/HeaderExtendHeader.tsx +7 -6
- package/Application/Components/HeaderLeft.tsx +1 -1
- package/Application/Components/SearchHeader.tsx +4 -8
- package/Application/NavigationContainer.tsx +10 -1
- package/Application/types.ts +3 -0
- package/Button/index.tsx +12 -0
- package/Consts/colors+spacing+radius.ts +8 -0
- package/Consts/theme.ts +1 -1
- package/Layout/Card.tsx +6 -4
- package/Layout/FloatingButton.tsx +4 -2
- package/Layout/Screen.tsx +37 -3
- package/Layout/Section.tsx +7 -11
- package/Layout/utils.ts +16 -1
- package/Loader/DotLoader.tsx +2 -0
- package/Loader/Spinner.tsx +2 -0
- package/package.json +1 -1
|
@@ -58,11 +58,13 @@ export default function BottomTabBar({
|
|
|
58
58
|
const { theme } = useContext(ApplicationContext);
|
|
59
59
|
const { routes } = state;
|
|
60
60
|
const widthDevice = useWindowDimensions()?.width;
|
|
61
|
+
const insets = useSafeAreaInsets();
|
|
61
62
|
const useFloating = floatingButton && state.routes.length % 2 === 0;
|
|
62
|
-
const itemWidth =
|
|
63
|
+
const itemWidth =
|
|
64
|
+
(widthDevice - insets.left - insets.right) /
|
|
65
|
+
(state.routes.length + (useFloating ? 1 : 0));
|
|
63
66
|
// const buildLink = useLinkBuilder();
|
|
64
67
|
const indicatorAnimated = useSharedValue(0);
|
|
65
|
-
const insets = useSafeAreaInsets();
|
|
66
68
|
|
|
67
69
|
const indicatorStyle = useAnimatedStyle(() => ({
|
|
68
70
|
width: itemWidth,
|
|
@@ -246,7 +248,8 @@ export default function BottomTabBar({
|
|
|
246
248
|
},
|
|
247
249
|
{
|
|
248
250
|
paddingBottom: paddingBottom,
|
|
249
|
-
|
|
251
|
+
paddingLeft: insets.left,
|
|
252
|
+
paddingRight: insets.right,
|
|
250
253
|
},
|
|
251
254
|
style,
|
|
252
255
|
]}
|
|
@@ -233,18 +233,17 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
233
233
|
});
|
|
234
234
|
}, [itemWidth, indicatorAnimated]);
|
|
235
235
|
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// );
|
|
236
|
+
// Focus, not mount: pushing a screen from a tab leaves BottomTab mounted but blurred, and
|
|
237
|
+
// onFocus only fires on tab changes.
|
|
238
|
+
useFocusEffect(
|
|
239
|
+
React.useCallback(() => {
|
|
240
|
+
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(
|
|
241
|
+
activeIndex.current !== 0,
|
|
242
|
+
);
|
|
243
|
+
return () =>
|
|
244
|
+
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(false);
|
|
245
|
+
}, []),
|
|
246
|
+
);
|
|
248
247
|
|
|
249
248
|
const onFocus = (e: any) => {
|
|
250
249
|
activeIndex.current = tabs.findIndex(i => e.target.includes(i.name));
|
|
@@ -253,10 +252,11 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
253
252
|
});
|
|
254
253
|
|
|
255
254
|
const isFirstTab = activeIndex.current === 0;
|
|
255
|
+
const gestureEnabled = Platform.OS === 'ios' && isFirstTab;
|
|
256
256
|
navigation?.setOptions({
|
|
257
|
-
gestureEnabled:
|
|
257
|
+
gestureEnabled: gestureEnabled,
|
|
258
258
|
});
|
|
259
|
-
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(!
|
|
259
|
+
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(!gestureEnabled);
|
|
260
260
|
};
|
|
261
261
|
|
|
262
262
|
const handler: {
|
|
@@ -10,10 +10,20 @@ import { Image } from '../../Image';
|
|
|
10
10
|
import { Colors } from '../../Consts';
|
|
11
11
|
import { useNormalizedSharedValue } from '../utils';
|
|
12
12
|
|
|
13
|
+
const ASPECT_RATIOS: Record<
|
|
14
|
+
NonNullable<HeaderAnimatedProps['aspectRatio']>,
|
|
15
|
+
number
|
|
16
|
+
> = {
|
|
17
|
+
'16/9': 16 / 9,
|
|
18
|
+
'3/2': 3 / 2,
|
|
19
|
+
'1/1': 1 / 1,
|
|
20
|
+
};
|
|
21
|
+
|
|
13
22
|
const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
14
23
|
animatedValue,
|
|
15
24
|
image,
|
|
16
25
|
useScale = true,
|
|
26
|
+
aspectRatio = '16/9',
|
|
17
27
|
children,
|
|
18
28
|
}) => {
|
|
19
29
|
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
@@ -40,7 +50,13 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
|
40
50
|
});
|
|
41
51
|
|
|
42
52
|
return (
|
|
43
|
-
<Animated.View
|
|
53
|
+
<Animated.View
|
|
54
|
+
style={[
|
|
55
|
+
styles.container,
|
|
56
|
+
{ aspectRatio: ASPECT_RATIOS[aspectRatio] },
|
|
57
|
+
animatedStyle,
|
|
58
|
+
]}
|
|
59
|
+
>
|
|
44
60
|
{children ?? (
|
|
45
61
|
<Image
|
|
46
62
|
source={{
|
|
@@ -54,7 +70,7 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
|
54
70
|
};
|
|
55
71
|
|
|
56
72
|
const styles = StyleSheet.create({
|
|
57
|
-
container: { width: '100%'
|
|
73
|
+
container: { width: '100%' },
|
|
58
74
|
verifiedDot: {
|
|
59
75
|
width: 4,
|
|
60
76
|
height: 4,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { Ref, useContext } from 'react';
|
|
2
2
|
import LinearGradient from 'react-native-linear-gradient';
|
|
3
|
-
import {
|
|
3
|
+
import { Platform, StyleSheet, View } from 'react-native';
|
|
4
4
|
import Animated, {
|
|
5
5
|
Extrapolation,
|
|
6
6
|
interpolate,
|
|
@@ -18,12 +18,11 @@ import {
|
|
|
18
18
|
AnimatedCompatValue,
|
|
19
19
|
useNormalizedSharedValue,
|
|
20
20
|
} from '../utils';
|
|
21
|
+
import { useHorizontalLayout } from '../../Layout/utils';
|
|
21
22
|
|
|
22
23
|
const SCREEN_PADDING = 12;
|
|
23
24
|
const BACK_WIDTH = 28;
|
|
24
25
|
|
|
25
|
-
const { width: screenWidth } = Dimensions.get('window');
|
|
26
|
-
|
|
27
26
|
const HeaderExtendHeader: React.FC<{
|
|
28
27
|
headerType?: HeaderType;
|
|
29
28
|
animatedValue: AnimatedCompatValue;
|
|
@@ -52,6 +51,8 @@ const HeaderExtendHeader: React.FC<{
|
|
|
52
51
|
const gradientColor = customGradientColor ?? theme.colors.gradient;
|
|
53
52
|
const headerBackground = customBackground ?? theme.assets?.headerBackground;
|
|
54
53
|
const leftPosition = inputSearchProps?.leftPosition || BACK_WIDTH + 20;
|
|
54
|
+
const layout = useHorizontalLayout(SCREEN_PADDING);
|
|
55
|
+
const { insets } = layout;
|
|
55
56
|
|
|
56
57
|
let useShadowHeader = useShadowHeaderProp;
|
|
57
58
|
if (inputSearchProps && Platform.OS === 'android') {
|
|
@@ -79,7 +80,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
79
80
|
translateX: interpolate(
|
|
80
81
|
sharedValue.value,
|
|
81
82
|
[0, 100],
|
|
82
|
-
[
|
|
83
|
+
[layout.marginLeft, leftPosition + insets.left],
|
|
83
84
|
Extrapolation.CLAMP,
|
|
84
85
|
),
|
|
85
86
|
},
|
|
@@ -88,8 +89,8 @@ const HeaderExtendHeader: React.FC<{
|
|
|
88
89
|
sharedValue.value,
|
|
89
90
|
[0, 100],
|
|
90
91
|
[
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
layout.width,
|
|
93
|
+
layout.width + SCREEN_PADDING - leftPosition - headerRightWidth,
|
|
93
94
|
],
|
|
94
95
|
Extrapolation.CLAMP,
|
|
95
96
|
),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BackHandler, StyleSheet, View } from 'react-native';
|
|
2
|
-
import { HeaderBackProps } from '../types';
|
|
2
|
+
import type { HeaderBackProps } from '../types';
|
|
3
3
|
import React, { useCallback, useContext, useEffect } from 'react';
|
|
4
4
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
5
5
|
import { PopupNotify } from '../../Popup';
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { Colors, Radius, Spacing, Styles } from '../../Consts';
|
|
2
2
|
import { InputRef, InputSearch } from '../../Input';
|
|
3
|
-
import {
|
|
4
|
-
Dimensions,
|
|
5
|
-
StyleSheet,
|
|
6
|
-
TouchableOpacity,
|
|
7
|
-
View,
|
|
8
|
-
} from 'react-native';
|
|
3
|
+
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
9
4
|
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
|
|
10
5
|
import React, { useContext } from 'react';
|
|
11
6
|
import { SearchHeaderProps } from '../types';
|
|
12
7
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
13
8
|
import { Text } from '../../Text';
|
|
14
9
|
import { useNormalizedSharedValue } from '../utils';
|
|
10
|
+
import { useHorizontalLayout } from '../../Layout/utils';
|
|
15
11
|
|
|
16
12
|
const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
17
13
|
(
|
|
@@ -27,7 +23,7 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
27
23
|
const { theme, navigator } = useContext(ApplicationContext);
|
|
28
24
|
const context = useContext<any>(MiniAppContext);
|
|
29
25
|
const BACK_WIDTH = 28;
|
|
30
|
-
const { width:
|
|
26
|
+
const { width: safeWidth } = useHorizontalLayout(0);
|
|
31
27
|
|
|
32
28
|
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
33
29
|
const leftPosition = props?.leftPosition ?? BACK_WIDTH + 20;
|
|
@@ -77,7 +73,7 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
77
73
|
style={[
|
|
78
74
|
Styles.flex,
|
|
79
75
|
renderButtons && {
|
|
80
|
-
maxWidth:
|
|
76
|
+
maxWidth: safeWidth - leftPosition - 12 - headerRightWidth,
|
|
81
77
|
},
|
|
82
78
|
]}
|
|
83
79
|
>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
SafeAreaInsetsContext,
|
|
3
4
|
SafeAreaProvider,
|
|
4
5
|
useSafeAreaInsets,
|
|
5
6
|
} from 'react-native-safe-area-context';
|
|
@@ -25,6 +26,7 @@ import { HeaderLeft } from './Components/HeaderLeft';
|
|
|
25
26
|
import { HeaderRight } from './Components/HeaderRight';
|
|
26
27
|
import { HeaderTitle } from './Components/HeaderTitle';
|
|
27
28
|
import { HeaderBackground } from './Components/HeaderBackground';
|
|
29
|
+
import { type EdgeInsets } from 'react-native-safe-area-context';
|
|
28
30
|
|
|
29
31
|
const Stack = createStackNavigator();
|
|
30
32
|
|
|
@@ -61,8 +63,15 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
61
63
|
},
|
|
62
64
|
};
|
|
63
65
|
|
|
66
|
+
const parentInsets = useContext(SafeAreaInsetsContext);
|
|
67
|
+
|
|
64
68
|
return (
|
|
65
|
-
<SafeAreaProvider
|
|
69
|
+
<SafeAreaProvider
|
|
70
|
+
initialMetrics={{
|
|
71
|
+
insets: parentInsets as EdgeInsets,
|
|
72
|
+
frame: { x: 0, y: 0, width: 0, height: 0 },
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
66
75
|
<MiniAppContext.Provider value={mergedContext}>
|
|
67
76
|
<Navigation
|
|
68
77
|
screen={screen}
|
package/Application/types.ts
CHANGED
|
@@ -229,6 +229,8 @@ export type HeaderTitleProps = {
|
|
|
229
229
|
data: TitleUserProps | TitleLocationProps | TitleJourneyProps;
|
|
230
230
|
};
|
|
231
231
|
|
|
232
|
+
export type HeaderType = 'default' | 'extended' | 'none';
|
|
233
|
+
|
|
232
234
|
export interface NavigationOptions
|
|
233
235
|
extends Omit<StackNavigationOptions, 'headerRight' | 'headerTitle'> {
|
|
234
236
|
preventBack?: PopupNotifyProps;
|
|
@@ -302,6 +304,7 @@ export interface HeaderAnimatedProps extends ViewProps {
|
|
|
302
304
|
animatedValue: Animated.Value | SharedValue<number>;
|
|
303
305
|
image: string;
|
|
304
306
|
useScale?: boolean;
|
|
307
|
+
aspectRatio?: '16/9' | '3/2' | '1/1';
|
|
305
308
|
}
|
|
306
309
|
|
|
307
310
|
export type BottomTabItemProps = {
|
package/Button/index.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { FC, useContext, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
Platform,
|
|
3
4
|
StyleSheet,
|
|
4
5
|
TouchableOpacity,
|
|
5
6
|
TouchableOpacityProps,
|
|
@@ -283,6 +284,17 @@ const Button: FC<ButtonProps> = ({
|
|
|
283
284
|
{ keypath: 'base pink 2.Ellipse 1.Stroke 1', color: textColor },
|
|
284
285
|
]}
|
|
285
286
|
style={[style, { borderRadius: Spacing.M }]}
|
|
287
|
+
webStyle={
|
|
288
|
+
Platform.OS === 'web'
|
|
289
|
+
? {
|
|
290
|
+
width: iconSize,
|
|
291
|
+
height: iconSize,
|
|
292
|
+
marginRight: isLeft ? space : 0,
|
|
293
|
+
marginLeft: isLeft ? 0 : space,
|
|
294
|
+
borderRadius: Spacing.M,
|
|
295
|
+
}
|
|
296
|
+
: undefined
|
|
297
|
+
}
|
|
286
298
|
/>
|
|
287
299
|
);
|
|
288
300
|
}
|
|
@@ -144,6 +144,14 @@ const Colors = {
|
|
|
144
144
|
red_09: '#fee8ea',
|
|
145
145
|
red_10: '#fef4f4',
|
|
146
146
|
red_11: '#fef8f8',
|
|
147
|
+
opacity_white_20: '#ffffff33',
|
|
148
|
+
opacity_white_40: '#ffffff66',
|
|
149
|
+
opacity_white_60: '#ffffff99',
|
|
150
|
+
opacity_white_80: '#ffffffcc',
|
|
151
|
+
opacity_black_20: '#00000033',
|
|
152
|
+
opacity_black_40: '#00000066',
|
|
153
|
+
opacity_black_60: '#00000099',
|
|
154
|
+
opacity_black_80: '#000000cc',
|
|
147
155
|
};
|
|
148
156
|
const Spacing = {
|
|
149
157
|
XXS: 2,
|
package/Consts/theme.ts
CHANGED
package/Layout/Card.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { useGridSystem } from './utils';
|
|
2
|
+
import { View, ViewProps } from 'react-native';
|
|
3
|
+
import { useGridSystem, useHorizontalLayout } from './utils';
|
|
4
4
|
import { GridContextProps } from './types';
|
|
5
5
|
import { GridContext, GridSystem } from './index';
|
|
6
6
|
import { Image } from '../Image';
|
|
@@ -32,7 +32,8 @@ const Card: React.FC<CardProps> = ({
|
|
|
32
32
|
|
|
33
33
|
const gutterSize = 8;
|
|
34
34
|
const margin = 12;
|
|
35
|
-
const
|
|
35
|
+
const layout = useHorizontalLayout(margin);
|
|
36
|
+
const widthSection = layout.width;
|
|
36
37
|
let sizePerSpan =
|
|
37
38
|
(widthSection - margin * 2 - gutterSize * (numberOfColumns - 1)) /
|
|
38
39
|
numberOfColumns -
|
|
@@ -76,7 +77,8 @@ const Card: React.FC<CardProps> = ({
|
|
|
76
77
|
styles.card,
|
|
77
78
|
{
|
|
78
79
|
width: widthSection,
|
|
79
|
-
|
|
80
|
+
marginLeft: layout.marginLeft,
|
|
81
|
+
marginRight: layout.marginRight,
|
|
80
82
|
gap: gutterSize,
|
|
81
83
|
},
|
|
82
84
|
!!backgroundImage && { backgroundColor: undefined },
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
AnimatedCompatValue,
|
|
20
20
|
useNormalizedSharedValue,
|
|
21
21
|
} from '../Application/utils';
|
|
22
|
+
import { useHorizontalLayout } from './utils';
|
|
22
23
|
|
|
23
24
|
export interface FloatingButtonProps {
|
|
24
25
|
label?: string;
|
|
@@ -55,6 +56,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
55
56
|
const lastOffset = useSharedValue(0);
|
|
56
57
|
const lastDirection = useSharedValue<string | null>(null);
|
|
57
58
|
const [showText, setShowText] = useState(true);
|
|
59
|
+
const { marginRight } = useHorizontalLayout(12);
|
|
58
60
|
|
|
59
61
|
useEffect(() => {
|
|
60
62
|
if (!label) return;
|
|
@@ -118,7 +120,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
118
120
|
style={[
|
|
119
121
|
styles.container,
|
|
120
122
|
{
|
|
121
|
-
right: position === 'right' ?
|
|
123
|
+
right: position === 'right' ? marginRight : undefined,
|
|
122
124
|
alignSelf: position === 'center' ? 'center' : 'flex-end',
|
|
123
125
|
bottom,
|
|
124
126
|
},
|
|
@@ -135,7 +137,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
135
137
|
style={[
|
|
136
138
|
styles.container,
|
|
137
139
|
{
|
|
138
|
-
right: position === 'right' ?
|
|
140
|
+
right: position === 'right' ? marginRight : undefined,
|
|
139
141
|
alignSelf: position === 'center' ? 'center' : 'flex-end',
|
|
140
142
|
height: size === 'small' ? 36 : 48,
|
|
141
143
|
backgroundColor: theme.colors.primary,
|
package/Layout/Screen.tsx
CHANGED
|
@@ -26,7 +26,10 @@ import {
|
|
|
26
26
|
ViewProps,
|
|
27
27
|
} from 'react-native';
|
|
28
28
|
import { useSharedValue, withTiming } from 'react-native-reanimated';
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
useSafeAreaFrame,
|
|
31
|
+
useSafeAreaInsets,
|
|
32
|
+
} from 'react-native-safe-area-context';
|
|
30
33
|
import { ApplicationContext, ScreenContext } from '../Context';
|
|
31
34
|
import Navigation from '../Application/Navigation';
|
|
32
35
|
import {
|
|
@@ -96,6 +99,14 @@ export interface ScreenProps extends ViewProps {
|
|
|
96
99
|
*/
|
|
97
100
|
footerComponent?: ReactNode;
|
|
98
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Optional. If `true`, the footer is inset by the standard horizontal margin
|
|
104
|
+
* (12pt each side). Set `false` for a footer that should span the full width —
|
|
105
|
+
* e.g. one that already carries its own horizontal padding.
|
|
106
|
+
* Defaults to `true`, on every platform.
|
|
107
|
+
*/
|
|
108
|
+
marginFooter?: boolean;
|
|
109
|
+
|
|
99
110
|
/**
|
|
100
111
|
* Optional. Background color of the screen.
|
|
101
112
|
*/
|
|
@@ -104,7 +115,7 @@ export interface ScreenProps extends ViewProps {
|
|
|
104
115
|
/**
|
|
105
116
|
* Optional. layout card offset overlap header banner.
|
|
106
117
|
*/
|
|
107
|
-
layoutOffset?: -8 | -24 | -56;
|
|
118
|
+
layoutOffset?: -8 | -24 | -56 | number;
|
|
108
119
|
|
|
109
120
|
/**
|
|
110
121
|
* Optional. Width of the right header.
|
|
@@ -174,6 +185,7 @@ const Screen = forwardRef(
|
|
|
174
185
|
scrollViewProps,
|
|
175
186
|
headerComponent: Header,
|
|
176
187
|
footerComponent: Footer,
|
|
188
|
+
marginFooter = true,
|
|
177
189
|
layoutOffset = -56,
|
|
178
190
|
headerRightWidth = 73,
|
|
179
191
|
backgroundColor,
|
|
@@ -196,6 +208,7 @@ const Screen = forwardRef(
|
|
|
196
208
|
const { theme, navigator } = useContext(ApplicationContext);
|
|
197
209
|
const screen: any = useContext(ScreenContext);
|
|
198
210
|
const insets = useSafeAreaInsets();
|
|
211
|
+
const frame = useSafeAreaFrame();
|
|
199
212
|
const heightHeader = useHeaderHeight();
|
|
200
213
|
|
|
201
214
|
/**
|
|
@@ -608,12 +621,31 @@ const Screen = forwardRef(
|
|
|
608
621
|
}
|
|
609
622
|
};
|
|
610
623
|
|
|
624
|
+
// Cap the screen container height so a scrollable body with a sticky footer
|
|
625
|
+
// can't push the footer past the visible frame (web previews measure the
|
|
626
|
+
// safe-area frame from the seeded SafeAreaProvider metrics). Subtract the
|
|
627
|
+
// bottom tab bar when present and the header unless it's an animated/extended
|
|
628
|
+
// header (those overlay rather than offset the body).
|
|
629
|
+
const getHeight = () => {
|
|
630
|
+
let newHeight = frame.height;
|
|
631
|
+
if (isTab) {
|
|
632
|
+
newHeight -= 64 + Math.min(insets.bottom, 21);
|
|
633
|
+
}
|
|
634
|
+
if (!animatedHeader && headerType !== 'extended') {
|
|
635
|
+
newHeight -= heightHeader;
|
|
636
|
+
}
|
|
637
|
+
return newHeight;
|
|
638
|
+
};
|
|
639
|
+
|
|
611
640
|
return (
|
|
612
641
|
<View
|
|
613
642
|
style={[
|
|
614
643
|
Styles.flex,
|
|
615
644
|
{
|
|
616
645
|
backgroundColor: backgroundColor ?? theme.colors.background.default,
|
|
646
|
+
// Web-only cap so a scrollable body with a sticky footer can't push
|
|
647
|
+
// the footer past the frame. Native handles this without a cap.
|
|
648
|
+
maxHeight: Platform.OS === 'web' ? getHeight() : undefined,
|
|
617
649
|
},
|
|
618
650
|
]}
|
|
619
651
|
>
|
|
@@ -679,7 +711,9 @@ const Screen = forwardRef(
|
|
|
679
711
|
},
|
|
680
712
|
]}
|
|
681
713
|
>
|
|
682
|
-
|
|
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>
|
|
683
717
|
</View>
|
|
684
718
|
)}
|
|
685
719
|
</KeyboardAvoidingView>
|
package/Layout/Section.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { useGridSystem } from './utils';
|
|
2
|
+
import { useWindowDimensions, View, ViewProps } from 'react-native';
|
|
3
|
+
import { useGridSystem, useHorizontalLayout } from './utils';
|
|
4
4
|
import { GridContextProps } from './types';
|
|
5
5
|
import { GridContext } from './index';
|
|
6
6
|
import { ApplicationContext, MiniAppContext } from '../Context';
|
|
@@ -38,14 +38,9 @@ const Section: React.FC<SectionProps> = ({
|
|
|
38
38
|
const { numberOfColumns } = useGridSystem();
|
|
39
39
|
|
|
40
40
|
const gutterSize = 12;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
let widthSection = Dimensions.get('window').width;
|
|
46
|
-
if (useMargin) {
|
|
47
|
-
widthSection = widthSection - margin * 2;
|
|
48
|
-
}
|
|
41
|
+
const layout = useHorizontalLayout(12);
|
|
42
|
+
const { width: windowWidth } = useWindowDimensions();
|
|
43
|
+
const widthSection = useMargin ? layout.width : windowWidth;
|
|
49
44
|
|
|
50
45
|
const sizePerSpan =
|
|
51
46
|
(widthSection - gutterSize * (numberOfColumns - 1)) / numberOfColumns -
|
|
@@ -77,7 +72,8 @@ const Section: React.FC<SectionProps> = ({
|
|
|
77
72
|
styles.protectedStyle,
|
|
78
73
|
{
|
|
79
74
|
width: widthSection,
|
|
80
|
-
|
|
75
|
+
marginLeft: useMargin ? layout.marginLeft : 0,
|
|
76
|
+
marginRight: useMargin ? layout.marginRight : 0,
|
|
81
77
|
flexDirection: 'row',
|
|
82
78
|
flexWrap: 'wrap',
|
|
83
79
|
gap: gutterSize,
|
package/Layout/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { Fragment, ReactElement } from 'react';
|
|
2
2
|
import { useWindowDimensions } from 'react-native';
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* validate children type
|
|
@@ -44,4 +45,18 @@ const useGridSystem = () => {
|
|
|
44
45
|
return { numberOfColumns };
|
|
45
46
|
};
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
// Side insets are 0 on portrait iPhones; iPhone Duo and landscape report them
|
|
49
|
+
const useHorizontalLayout = (margin: number) => {
|
|
50
|
+
const { width } = useWindowDimensions();
|
|
51
|
+
const insets = useSafeAreaInsets();
|
|
52
|
+
const marginLeft = margin + insets.left;
|
|
53
|
+
const marginRight = margin + insets.right;
|
|
54
|
+
return {
|
|
55
|
+
marginLeft,
|
|
56
|
+
marginRight,
|
|
57
|
+
width: width - marginLeft - marginRight,
|
|
58
|
+
insets,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export { useGridSystem, useHorizontalLayout, validateChildren };
|
package/Loader/DotLoader.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { FC, useContext } from 'react';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
2
3
|
import AnimatedLottieView from 'lottie-react-native';
|
|
3
4
|
import animation from '../Assets/DotAnimation.json';
|
|
4
5
|
import { ApplicationContext } from '../Context';
|
|
@@ -29,6 +30,7 @@ const DotLoader: FC<LoaderProps> = ({ color, style }) => {
|
|
|
29
30
|
<AnimatedLottieView
|
|
30
31
|
source={source}
|
|
31
32
|
style={[{ width: 52, height: 18 }, style]}
|
|
33
|
+
webStyle={Platform.OS === 'web' ? { width: 52, height: 18 } : undefined}
|
|
32
34
|
autoPlay
|
|
33
35
|
/>
|
|
34
36
|
);
|
package/Loader/Spinner.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { FC, useContext } from 'react';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
2
3
|
import AnimatedLottieView from 'lottie-react-native';
|
|
3
4
|
import animation from '../Assets/SpinnerAnimation.json';
|
|
4
5
|
import { hexToRGBA } from './utils';
|
|
@@ -21,6 +22,7 @@ const Spinner: FC<LoaderProps> = ({ color, style }) => {
|
|
|
21
22
|
<AnimatedLottieView
|
|
22
23
|
source={source}
|
|
23
24
|
style={[{ width: 24, height: 24 }, style]}
|
|
25
|
+
webStyle={Platform.OS === 'web' ? { width: 24, height: 24 } : undefined}
|
|
24
26
|
autoPlay
|
|
25
27
|
/>
|
|
26
28
|
);
|