@momo-kits/foundation 0.168.1-rn862.1 → 0.169.2
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.hooks.ts +195 -0
- package/Application/BottomSheet.parts.tsx +147 -0
- package/Application/BottomSheet.styles.ts +43 -0
- package/Application/BottomSheet.tsx +76 -279
- package/Application/BottomTab/ActiveIndicator.tsx +90 -0
- package/Application/BottomTab/BottomTabBar.styles.ts +65 -0
- package/Application/BottomTab/BottomTabBar.tsx +229 -276
- package/Application/BottomTab/BottomTabInset.tsx +14 -0
- package/Application/BottomTab/CurvedContainer.tsx +153 -0
- package/Application/BottomTab/CustomBottomTabItem.tsx +132 -108
- package/Application/BottomTab/DuoBarCompression.tsx +4 -12
- package/Application/BottomTab/DuoRailTabBar.tsx +227 -0
- package/Application/BottomTab/DuoTabBar.styles.ts +81 -0
- package/Application/BottomTab/DuoTabBar.tsx +29 -495
- package/Application/BottomTab/TabBarIcon.tsx +160 -72
- package/Application/BottomTab/TabScreen.tsx +164 -0
- package/Application/BottomTab/index.tsx +84 -351
- package/Application/BottomTab/metrics.ts +164 -0
- package/Application/BottomTab/tabItems.ts +46 -0
- package/Application/BottomTab/usePressLift.ts +32 -0
- package/Application/Components/DuoGlassBackground.tsx +7 -9
- package/Application/Components/HeaderAnimated.tsx +1 -1
- package/Application/Components/HeaderBackground.tsx +1 -1
- package/Application/Components/HeaderExtendHeader.tsx +17 -11
- package/Application/Components/HeaderRight.styles.ts +144 -0
- package/Application/Components/HeaderRight.tsx +6 -528
- package/Application/Components/HeaderTitle.shared.ts +43 -0
- package/Application/Components/HeaderTitle.styles.ts +125 -0
- package/Application/Components/HeaderTitle.tsx +12 -576
- package/Application/Components/HeaderToolkitAction.tsx +208 -0
- package/Application/Components/NavigationButton.tsx +9 -6
- package/Application/Components/SearchHeader.tsx +1 -1
- package/Application/Components/SwipeBackHaptic.tsx +95 -0
- package/Application/Components/TitleJourney.tsx +110 -0
- package/Application/Components/TitleLocation.tsx +73 -0
- package/Application/Components/TitleUser.tsx +304 -0
- package/Application/Components/useToolkitActions.tsx +233 -0
- package/Application/ModalIconButton.tsx +66 -0
- package/Application/ModalScreen.tsx +39 -18
- package/Application/Navigation.ts +1 -1
- package/Application/NavigationContainer.tsx +7 -8
- package/Application/StackScreen.tracking.ts +158 -0
- package/Application/StackScreen.tsx +10 -137
- package/Application/WidgetContainer.tsx +10 -3
- package/Application/__tests__/modalLayout.test.ts +128 -0
- package/Application/hooks.ts +137 -0
- package/Application/index.ts +13 -1
- package/Application/modalLayout.ts +177 -0
- package/Application/navigationOptions.tsx +217 -0
- package/Application/types.header.ts +116 -0
- package/Application/types.tabs.ts +48 -0
- package/Application/types.theme.ts +87 -0
- package/Application/types.ts +12 -244
- package/Application/utils.ts +107 -0
- package/Badge/BadgeRibbon.tsx +3 -3
- package/Badge/styles.ts +2 -0
- package/Button/index.tsx +16 -136
- package/Button/styles.ts +1 -1
- package/Button/utils.ts +139 -0
- package/CheckBox/index.tsx +1 -6
- package/Divider/DashDivider.tsx +10 -11
- package/Divider/index.tsx +18 -8
- package/Image/index.tsx +1 -46
- package/Input/Input.tsx +7 -8
- package/Input/InputDropDown.tsx +3 -4
- package/Input/InputMoney.tsx +38 -164
- package/Input/InputMoneyView.tsx +187 -0
- package/Input/InputOTP.tsx +1 -1
- package/Input/InputPhoneNumber.tsx +5 -1
- package/Input/InputSearch.tsx +2 -108
- package/Input/InputTextArea.tsx +1 -5
- package/Input/SearchTextTyping.tsx +104 -0
- package/Input/common.tsx +4 -4
- package/Input/moneyUtils.ts +17 -0
- package/Input/styles.ts +26 -2
- package/Layout/FloatingButton.tsx +22 -21
- package/Layout/ReservedRegions.tsx +21 -4
- package/Layout/Screen.tsx +74 -670
- package/Layout/Section.tsx +1 -2
- package/Layout/index.ts +10 -4
- package/Layout/layoutMetrics.ts +58 -0
- package/Layout/layoutTypes.ts +86 -0
- package/Layout/screenContent.tsx +90 -0
- package/Layout/screenFooter.tsx +53 -0
- package/Layout/screenHeader.tsx +315 -0
- package/Layout/screenQuickReturn.tsx +67 -0
- package/Layout/screenScroll.ts +110 -0
- package/Layout/screenTypes.ts +134 -0
- package/Layout/styles.ts +2 -0
- package/Layout/utils.ts +10 -144
- package/Loader/DotLoader.tsx +7 -3
- package/Loader/ProgressBar.tsx +2 -6
- package/Loader/Spinner.tsx +7 -3
- package/Loader/styles.ts +4 -0
- package/Popup/PopupNotify.tsx +70 -64
- package/Popup/PopupPromotion.tsx +5 -4
- package/Popup/types.ts +7 -0
- package/Popup/utils.ts +46 -24
- package/Radio/index.tsx +2 -10
- package/Radio/styles.ts +6 -0
- package/Skeleton/index.tsx +1 -9
- package/Skeleton/styles.ts +1 -0
- package/Tag/index.tsx +3 -3
- package/Text/index.tsx +11 -2
- package/Title/index.tsx +2 -9
- package/Title/styles.ts +6 -0
- package/package.json +21 -22
- package/Application/device.ts +0 -29
- package/Application/utils.tsx +0 -497
|
@@ -1,69 +1,50 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useCallback,
|
|
3
|
-
useContext,
|
|
4
|
-
useEffect,
|
|
5
|
-
useRef,
|
|
6
|
-
useState,
|
|
7
|
-
} from 'react';
|
|
1
|
+
import React, { useCallback, useContext, useEffect, useRef } from 'react';
|
|
8
2
|
import {
|
|
9
|
-
BackHandler,
|
|
10
|
-
Dimensions,
|
|
11
|
-
Keyboard,
|
|
12
3
|
KeyboardAvoidingView,
|
|
13
|
-
KeyboardEvent,
|
|
14
|
-
LayoutAnimation,
|
|
15
4
|
Modal,
|
|
16
5
|
PanResponder,
|
|
17
6
|
Platform,
|
|
18
7
|
Pressable,
|
|
19
8
|
StyleSheet,
|
|
20
|
-
TouchableOpacity,
|
|
21
|
-
useWindowDimensions,
|
|
22
9
|
View,
|
|
23
10
|
} from 'react-native';
|
|
24
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
25
11
|
import { ApplicationContext, MiniAppContext } from '../Context';
|
|
26
12
|
import { BottomSheetParams } from './types';
|
|
27
|
-
import { Colors, Radius, Spacing, Styles } from '../Consts';
|
|
28
|
-
import { Text } from '../Text';
|
|
29
|
-
import { Icon } from '../Icon';
|
|
30
|
-
import { useHeaderHeight } from '@react-navigation/elements';
|
|
31
13
|
import Animated, {
|
|
32
14
|
Easing,
|
|
33
|
-
Extrapolation,
|
|
34
|
-
interpolate,
|
|
35
|
-
useAnimatedStyle,
|
|
36
15
|
useSharedValue,
|
|
37
16
|
withSpring,
|
|
38
17
|
withTiming,
|
|
39
18
|
} from 'react-native-reanimated';
|
|
40
19
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
41
|
-
import
|
|
42
|
-
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
20
|
+
import { DUO_ORIENTATIONS, styles } from './BottomSheet.styles';
|
|
21
|
+
import {
|
|
22
|
+
useBottomSheetLayout,
|
|
23
|
+
useDuoKeyboardLift,
|
|
24
|
+
useSheetDismiss,
|
|
25
|
+
useSheetAnimatedStyles,
|
|
26
|
+
} from './BottomSheet.hooks';
|
|
27
|
+
import {
|
|
28
|
+
getSheetStyle,
|
|
29
|
+
renderSheetFooter,
|
|
30
|
+
renderSheetHeader,
|
|
31
|
+
} from './BottomSheet.parts';
|
|
46
32
|
|
|
47
33
|
const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
48
34
|
const { theme, navigator } = useContext(ApplicationContext);
|
|
49
35
|
const context = useContext<any>(MiniAppContext);
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
36
|
+
const {
|
|
37
|
+
windowSize,
|
|
38
|
+
isDuo,
|
|
39
|
+
isFoldable,
|
|
40
|
+
placement,
|
|
41
|
+
sheet,
|
|
42
|
+
scrimOpacity,
|
|
43
|
+
heightDevice,
|
|
44
|
+
bottomInset,
|
|
45
|
+
keyboardOffset,
|
|
46
|
+
} = useBottomSheetLayout();
|
|
57
47
|
const action = useRef<undefined | string>(undefined);
|
|
58
|
-
const insets = useSafeAreaInsets();
|
|
59
|
-
const heightHeader = useHeaderHeight();
|
|
60
|
-
const topGap = Math.max(
|
|
61
|
-
getDuoTopInset(insets, windowSize.width, windowSize.height),
|
|
62
|
-
Spacing.XL,
|
|
63
|
-
);
|
|
64
|
-
const isIOS = Platform.OS === 'ios';
|
|
65
|
-
const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
|
|
66
|
-
const keyboardOffset = heightHeader - bottomInset;
|
|
67
48
|
|
|
68
49
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
69
50
|
|
|
@@ -83,43 +64,15 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
83
64
|
}: BottomSheetParams = props.route.params;
|
|
84
65
|
|
|
85
66
|
const translateY = useSharedValue(heightDevice);
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// so lift it here and shrink maxHeight by the same amount instead
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
if (!isDuo || !useKeyboardAvoidingView) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const onKeyboardChange = (e: KeyboardEvent) => {
|
|
95
|
-
const overlap = windowSize.height - e.endCoordinates.screenY;
|
|
96
|
-
const lift = Math.max(
|
|
97
|
-
overlap - bottomInset - (keyboardVerticalOffset ?? 0),
|
|
98
|
-
0,
|
|
99
|
-
);
|
|
100
|
-
if (e.duration) {
|
|
101
|
-
LayoutAnimation.configureNext({
|
|
102
|
-
duration: e.duration,
|
|
103
|
-
update: {
|
|
104
|
-
duration: e.duration,
|
|
105
|
-
type: LayoutAnimation.Types[e.easing] || 'keyboard',
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
setKeyboardLift(lift);
|
|
110
|
-
};
|
|
111
|
-
const subscriptions = [
|
|
112
|
-
Keyboard.addListener('keyboardWillChangeFrame', onKeyboardChange),
|
|
113
|
-
Keyboard.addListener('keyboardWillHide', onKeyboardChange),
|
|
114
|
-
];
|
|
115
|
-
return () => subscriptions.forEach(sub => sub.remove());
|
|
116
|
-
}, [
|
|
117
|
-
bottomInset,
|
|
67
|
+
const onDismissRef = useRef(props.route.params?.onDismiss);
|
|
68
|
+
onDismissRef.current = props.route.params?.onDismiss;
|
|
69
|
+
const keyboardLift = useDuoKeyboardLift(
|
|
118
70
|
isDuo,
|
|
119
|
-
keyboardVerticalOffset,
|
|
120
71
|
useKeyboardAvoidingView,
|
|
121
72
|
windowSize.height,
|
|
122
|
-
|
|
73
|
+
bottomInset,
|
|
74
|
+
keyboardVerticalOffset,
|
|
75
|
+
);
|
|
123
76
|
|
|
124
77
|
const openAnimation = useCallback(() => {
|
|
125
78
|
translateY.value = withTiming(0, {
|
|
@@ -179,145 +132,46 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
179
132
|
}
|
|
180
133
|
|
|
181
134
|
/**
|
|
182
|
-
* emit dismiss event
|
|
135
|
+
* emit dismiss event, once: folding or rotating iPhone Duo changes the
|
|
136
|
+
* window height while the sheet stays open
|
|
183
137
|
*/
|
|
184
138
|
useEffect(() => {
|
|
185
|
-
translateY.value = heightDevice;
|
|
186
139
|
openAnimation();
|
|
187
140
|
return () => {
|
|
188
|
-
|
|
141
|
+
onDismissRef.current?.(action.current);
|
|
189
142
|
};
|
|
190
|
-
}, [
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
closeAnimation(() => {
|
|
201
|
-
navigator?.pop();
|
|
202
|
-
scheduleOnRN(() => {
|
|
203
|
-
callback?.();
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
},
|
|
207
|
-
[barrierDismissible, closeAnimation, navigator],
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* on request close
|
|
212
|
-
*/
|
|
213
|
-
const onRequestClose = useCallback(
|
|
214
|
-
(callback?: () => void) => {
|
|
215
|
-
onDismiss(true, callback);
|
|
216
|
-
},
|
|
217
|
-
[onDismiss],
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
useEffect(() => {
|
|
221
|
-
const backHandler = BackHandler.addEventListener(
|
|
222
|
-
'hardwareBackPress',
|
|
223
|
-
() => {
|
|
224
|
-
onDismiss();
|
|
225
|
-
return true;
|
|
226
|
-
},
|
|
227
|
-
);
|
|
228
|
-
return () => backHandler.remove();
|
|
229
|
-
}, [barrierDismissible, onDismiss]);
|
|
143
|
+
}, [openAnimation]);
|
|
144
|
+
|
|
145
|
+
const { onDismiss, onRequestClose, onPressBack, onPressClose } =
|
|
146
|
+
useSheetDismiss({
|
|
147
|
+
barrierDismissible,
|
|
148
|
+
closeAnimation,
|
|
149
|
+
navigator,
|
|
150
|
+
leftOptions,
|
|
151
|
+
action,
|
|
152
|
+
});
|
|
230
153
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
*/
|
|
234
|
-
const renderHeader = useCallback(() => {
|
|
235
|
-
return (
|
|
236
|
-
<View {...panResponder.panHandlers}>
|
|
237
|
-
<View style={{ height: 16 }}>
|
|
238
|
-
<View style={styles.indicator} />
|
|
239
|
-
</View>
|
|
240
|
-
<View
|
|
241
|
-
style={[
|
|
242
|
-
styles.header,
|
|
243
|
-
useDivider && {
|
|
244
|
-
borderBottomColor: theme.colors.border.default,
|
|
245
|
-
borderBottomWidth: 1,
|
|
246
|
-
},
|
|
247
|
-
]}
|
|
248
|
-
>
|
|
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'} />
|
|
257
|
-
</TouchableOpacity>
|
|
258
|
-
) : null}
|
|
154
|
+
const showBack = !!leftOptions?.onPressIconLeft;
|
|
155
|
+
const iconBack = leftOptions?.iconLeft ?? 'ic_back';
|
|
259
156
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
) : (
|
|
263
|
-
<>
|
|
264
|
-
{!leftOptions ? <View style={styles.iconButton} /> : null}
|
|
265
|
-
<Text
|
|
266
|
-
style={[Styles.flex, { textAlign: 'center' }]}
|
|
267
|
-
numberOfLines={1}
|
|
268
|
-
typography="header_default_bold"
|
|
269
|
-
>
|
|
270
|
-
{options.title ?? ''}
|
|
271
|
-
</Text>
|
|
272
|
-
</>
|
|
273
|
-
)}
|
|
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>
|
|
283
|
-
</View>
|
|
284
|
-
</View>
|
|
285
|
-
);
|
|
286
|
-
}, [
|
|
287
|
-
onDismiss,
|
|
288
|
-
leftOptions,
|
|
289
|
-
options.header,
|
|
290
|
-
options.title,
|
|
291
|
-
panResponder.panHandlers,
|
|
292
|
-
theme.colors.border.default,
|
|
157
|
+
const headerParams = {
|
|
158
|
+
panHandlers: panResponder.panHandlers,
|
|
293
159
|
useDivider,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const animatedOverlayStyle = useAnimatedStyle(() => {
|
|
303
|
-
return {
|
|
304
|
-
opacity: interpolate(
|
|
305
|
-
translateY.value,
|
|
306
|
-
[0, heightDevice],
|
|
307
|
-
[1, 0],
|
|
308
|
-
Extrapolation.CLAMP,
|
|
309
|
-
),
|
|
310
|
-
};
|
|
311
|
-
});
|
|
312
|
-
|
|
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,
|
|
160
|
+
theme,
|
|
161
|
+
showBack,
|
|
162
|
+
onPressBack,
|
|
163
|
+
onPressClose,
|
|
164
|
+
iconBack,
|
|
165
|
+
options,
|
|
166
|
+
leftOptions,
|
|
319
167
|
};
|
|
320
168
|
|
|
169
|
+
const { animatedStyle, animatedOverlayStyle } = useSheetAnimatedStyles(
|
|
170
|
+
translateY,
|
|
171
|
+
heightDevice,
|
|
172
|
+
scrimOpacity,
|
|
173
|
+
);
|
|
174
|
+
|
|
321
175
|
const screen = (
|
|
322
176
|
<Screen
|
|
323
177
|
{...props}
|
|
@@ -333,8 +187,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
333
187
|
onRequestClose={() => {
|
|
334
188
|
onDismiss();
|
|
335
189
|
}}
|
|
336
|
-
style={StyleSheet.
|
|
190
|
+
style={StyleSheet.absoluteFillObject}
|
|
337
191
|
isModalKit={true}
|
|
192
|
+
supportedOrientations={isDuo ? DUO_ORIENTATIONS : undefined}
|
|
338
193
|
>
|
|
339
194
|
<KeyboardAvoidingView
|
|
340
195
|
behavior={Platform.select({
|
|
@@ -347,7 +202,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
347
202
|
style={styles.container}
|
|
348
203
|
>
|
|
349
204
|
<Pressable
|
|
350
|
-
style={StyleSheet.
|
|
205
|
+
style={StyleSheet.absoluteFillObject}
|
|
351
206
|
onPress={() => {
|
|
352
207
|
action.current = 'touch';
|
|
353
208
|
onDismiss();
|
|
@@ -359,54 +214,30 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
359
214
|
<Animated.View
|
|
360
215
|
style={[
|
|
361
216
|
animatedStyle,
|
|
362
|
-
{
|
|
217
|
+
getSheetStyle({
|
|
363
218
|
backgroundColor,
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
},
|
|
381
|
-
showBaseLineDebug && styles.debugBaseLine,
|
|
219
|
+
isDuo,
|
|
220
|
+
isFoldable,
|
|
221
|
+
placement,
|
|
222
|
+
sheet,
|
|
223
|
+
keyboardLift,
|
|
224
|
+
showBaseLineDebug,
|
|
225
|
+
}),
|
|
382
226
|
]}
|
|
383
227
|
>
|
|
384
|
-
{
|
|
228
|
+
{isFoldable ? (
|
|
385
229
|
// content shrinks so a tall sheet keeps its header and footer
|
|
386
|
-
<View style={
|
|
387
|
-
{
|
|
230
|
+
<View style={styles.shrink}>
|
|
231
|
+
{renderSheetHeader(headerParams)}
|
|
388
232
|
<View style={styles.shrink}>{screen}</View>
|
|
389
233
|
</View>
|
|
390
234
|
) : (
|
|
391
235
|
<>
|
|
392
|
-
{
|
|
236
|
+
{renderSheetHeader(headerParams)}
|
|
393
237
|
{screen}
|
|
394
238
|
</>
|
|
395
239
|
)}
|
|
396
|
-
{footerComponent
|
|
397
|
-
<View
|
|
398
|
-
style={[
|
|
399
|
-
layoutStyles.shadow,
|
|
400
|
-
{
|
|
401
|
-
paddingBottom: Spacing.S,
|
|
402
|
-
backgroundColor: theme.colors.background.surface,
|
|
403
|
-
},
|
|
404
|
-
sideInsetStyle,
|
|
405
|
-
]}
|
|
406
|
-
>
|
|
407
|
-
{footerComponent}
|
|
408
|
-
</View>
|
|
409
|
-
)}
|
|
240
|
+
{renderSheetFooter({ footerComponent, theme })}
|
|
410
241
|
{useBottomInset && (
|
|
411
242
|
<View
|
|
412
243
|
style={{
|
|
@@ -421,38 +252,4 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
421
252
|
);
|
|
422
253
|
};
|
|
423
254
|
|
|
424
|
-
const styles = StyleSheet.create({
|
|
425
|
-
container: {
|
|
426
|
-
flex: 1,
|
|
427
|
-
justifyContent: 'flex-end',
|
|
428
|
-
},
|
|
429
|
-
overlay: {
|
|
430
|
-
flex: 1,
|
|
431
|
-
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
432
|
-
},
|
|
433
|
-
indicator: {
|
|
434
|
-
width: 40,
|
|
435
|
-
height: 4,
|
|
436
|
-
borderRadius: Radius.S,
|
|
437
|
-
backgroundColor: Colors.black_06,
|
|
438
|
-
position: 'absolute',
|
|
439
|
-
alignSelf: 'center',
|
|
440
|
-
top: Spacing.S,
|
|
441
|
-
},
|
|
442
|
-
header: {
|
|
443
|
-
height: 56,
|
|
444
|
-
flexDirection: 'row',
|
|
445
|
-
alignItems: 'center',
|
|
446
|
-
},
|
|
447
|
-
iconButton: {
|
|
448
|
-
width: 24,
|
|
449
|
-
height: 24,
|
|
450
|
-
alignItems: 'center',
|
|
451
|
-
justifyContent: 'center',
|
|
452
|
-
marginHorizontal: Spacing.M,
|
|
453
|
-
},
|
|
454
|
-
shrink: { flexShrink: 1 },
|
|
455
|
-
debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
|
|
456
|
-
});
|
|
457
|
-
|
|
458
255
|
export default BottomSheet;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React, { useContext, useEffect, useRef } from 'react';
|
|
2
|
+
import { StyleProp, StyleSheet, ViewStyle } from 'react-native';
|
|
3
|
+
import Animated, {
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
useSharedValue,
|
|
6
|
+
withTiming,
|
|
7
|
+
} from 'react-native-reanimated';
|
|
8
|
+
import { ApplicationContext } from '../../Context';
|
|
9
|
+
import { getIndicatorShadow, TabBarMotion } from './metrics';
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
axis: 'x' | 'y';
|
|
13
|
+
// position of the selected item along the axis, undefined until it is measured
|
|
14
|
+
offset?: number;
|
|
15
|
+
// horizontal bar: width of the selected item
|
|
16
|
+
extent?: number;
|
|
17
|
+
radius: number;
|
|
18
|
+
style: StyleProp<ViewStyle>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* one active indicator per bar (Background/tonal with its inner shadows) sliding to the
|
|
23
|
+
* selected item; it lands in place on the first measure and slides from then on
|
|
24
|
+
*/
|
|
25
|
+
const ActiveIndicator: React.FC<Props> = ({
|
|
26
|
+
axis,
|
|
27
|
+
offset,
|
|
28
|
+
extent,
|
|
29
|
+
radius,
|
|
30
|
+
style,
|
|
31
|
+
}) => {
|
|
32
|
+
const { theme } = useContext(ApplicationContext);
|
|
33
|
+
const position = useSharedValue(offset ?? 0);
|
|
34
|
+
const size = useSharedValue(extent ?? 0);
|
|
35
|
+
const visible = useSharedValue(offset === undefined ? 0 : 1);
|
|
36
|
+
const placed = useRef(offset !== undefined);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (offset === undefined) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
size.value = extent ?? 0;
|
|
43
|
+
if (!placed.current) {
|
|
44
|
+
placed.current = true;
|
|
45
|
+
position.value = offset;
|
|
46
|
+
visible.value = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
position.value = withTiming(offset, TabBarMotion.indicator);
|
|
50
|
+
}, [offset, extent, position, size, visible]);
|
|
51
|
+
|
|
52
|
+
const animatedStyle = useAnimatedStyle(() =>
|
|
53
|
+
axis === 'x'
|
|
54
|
+
? {
|
|
55
|
+
opacity: visible.value,
|
|
56
|
+
width: size.value,
|
|
57
|
+
transform: [{ translateX: position.value }],
|
|
58
|
+
}
|
|
59
|
+
: {
|
|
60
|
+
opacity: visible.value,
|
|
61
|
+
transform: [{ translateY: position.value }],
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Animated.View
|
|
67
|
+
pointerEvents={'none'}
|
|
68
|
+
style={[
|
|
69
|
+
styles.indicator,
|
|
70
|
+
{
|
|
71
|
+
borderRadius: radius,
|
|
72
|
+
backgroundColor: theme.colors.background.tonal,
|
|
73
|
+
boxShadow: getIndicatorShadow(theme.colors.primary),
|
|
74
|
+
},
|
|
75
|
+
style,
|
|
76
|
+
animatedStyle,
|
|
77
|
+
]}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const styles = StyleSheet.create({
|
|
83
|
+
indicator: {
|
|
84
|
+
position: 'absolute',
|
|
85
|
+
left: 0,
|
|
86
|
+
top: 0,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export { ActiveIndicator };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { Colors, Spacing } from '../../Consts';
|
|
3
|
+
import { CTA_SHADOW, TabBarMetrics } from './metrics';
|
|
4
|
+
|
|
5
|
+
export const styles = StyleSheet.create({
|
|
6
|
+
bar: {
|
|
7
|
+
position: 'absolute',
|
|
8
|
+
left: 0,
|
|
9
|
+
right: 0,
|
|
10
|
+
bottom: 0,
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
},
|
|
13
|
+
// transparent strip that keeps the rising center action touchable
|
|
14
|
+
barCtaRise: {
|
|
15
|
+
paddingTop: TabBarMetrics.ctaLift,
|
|
16
|
+
},
|
|
17
|
+
container: {
|
|
18
|
+
height: TabBarMetrics.height,
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
flex: 1,
|
|
22
|
+
flexDirection: 'row',
|
|
23
|
+
alignItems: 'center',
|
|
24
|
+
gap: TabBarMetrics.itemGap,
|
|
25
|
+
paddingVertical: TabBarMetrics.paddingVertical,
|
|
26
|
+
paddingHorizontal: TabBarMetrics.paddingHorizontal,
|
|
27
|
+
},
|
|
28
|
+
indicator: {
|
|
29
|
+
height: TabBarMetrics.indicatorHeight,
|
|
30
|
+
},
|
|
31
|
+
ctaSlot: {
|
|
32
|
+
width: TabBarMetrics.ctaSlotWidth,
|
|
33
|
+
alignSelf: 'stretch',
|
|
34
|
+
},
|
|
35
|
+
// the button rises ctaLift above the container top, the label ends on the item baseline
|
|
36
|
+
cta: {
|
|
37
|
+
position: 'absolute',
|
|
38
|
+
left: 0,
|
|
39
|
+
right: 0,
|
|
40
|
+
top: -TabBarMetrics.ctaLift - TabBarMetrics.paddingVertical,
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
gap: TabBarMetrics.ctaLabelGap,
|
|
43
|
+
},
|
|
44
|
+
ctaButton: {
|
|
45
|
+
boxShadow: CTA_SHADOW,
|
|
46
|
+
width: TabBarMetrics.ctaSize,
|
|
47
|
+
height: TabBarMetrics.ctaSize,
|
|
48
|
+
borderRadius: TabBarMetrics.ctaSize / 2,
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
},
|
|
52
|
+
// custom label content over the label text, centered and unclipped
|
|
53
|
+
ctaLabelOverlay: {
|
|
54
|
+
...StyleSheet.absoluteFillObject,
|
|
55
|
+
alignItems: 'center',
|
|
56
|
+
justifyContent: 'center',
|
|
57
|
+
},
|
|
58
|
+
ctaLabel: {
|
|
59
|
+
maxWidth: TabBarMetrics.ctaSlotWidth,
|
|
60
|
+
minHeight: TabBarMetrics.ctaLabelHeight,
|
|
61
|
+
paddingHorizontal: Spacing.XXS,
|
|
62
|
+
justifyContent: 'center',
|
|
63
|
+
},
|
|
64
|
+
debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
|
|
65
|
+
});
|