@momo-kits/foundation 0.165.1-beta.1 → 0.165.1-beta.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/BottomTab/index.tsx +42 -7
- package/Application/utils.tsx +42 -23
- package/package.json +1 -1
|
@@ -24,7 +24,12 @@ import { Icon } from '../../Icon';
|
|
|
24
24
|
import { exportFontFamily, Text } from '../../Text';
|
|
25
25
|
import StackScreen from '../StackScreen';
|
|
26
26
|
import { BottomTabProps } from '../types';
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
getOptions,
|
|
29
|
+
getStackOptions,
|
|
30
|
+
useAppState,
|
|
31
|
+
useFloatingArrowBack,
|
|
32
|
+
} from '../utils';
|
|
28
33
|
import BottomTabBar from './BottomTabBar';
|
|
29
34
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
30
35
|
|
|
@@ -153,7 +158,7 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
|
|
|
153
158
|
initialParams,
|
|
154
159
|
bottomTab: 'nested',
|
|
155
160
|
}}
|
|
156
|
-
options={{ ...getStackOptions(
|
|
161
|
+
options={{ ...getStackOptions() }}
|
|
157
162
|
/>
|
|
158
163
|
</Stack.Navigator>
|
|
159
164
|
</Animated.View>
|
|
@@ -188,7 +193,7 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
|
|
|
188
193
|
initialParams,
|
|
189
194
|
bottomTab: 'default',
|
|
190
195
|
}}
|
|
191
|
-
options={{ ...getStackOptions(
|
|
196
|
+
options={{ ...getStackOptions(), ...options }}
|
|
192
197
|
/>
|
|
193
198
|
</Stack.Navigator>
|
|
194
199
|
</NavigationContainer>
|
|
@@ -212,6 +217,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
212
217
|
const indicatorAnimated = useSharedValue(0);
|
|
213
218
|
const activeIndex = React.useRef(initialIndex > 0 ? initialIndex : 0);
|
|
214
219
|
const itemWidth = dimensions.width / tabs.length;
|
|
220
|
+
const reportFloatingArrowBack = useFloatingArrowBack();
|
|
215
221
|
|
|
216
222
|
const indicatorStyle = useAnimatedStyle(() => ({
|
|
217
223
|
width: itemWidth,
|
|
@@ -232,11 +238,38 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
232
238
|
});
|
|
233
239
|
}, [itemWidth, indicatorAnimated]);
|
|
234
240
|
|
|
241
|
+
// `initialRouteName` can start on a later tab, so report where we actually begin instead of
|
|
242
|
+
// waiting for the first tab switch to correct it.
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
reportFloatingArrowBack(activeIndex.current !== 0);
|
|
245
|
+
}, [reportFloatingArrowBack]);
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Two halves of one decision, so they are set together.
|
|
249
|
+
*
|
|
250
|
+
* On the first tab the stack card owns the swipe and runs the usual transition. On any other
|
|
251
|
+
* tab it must not: every tab lives inside that single card, and the gesture pops it with
|
|
252
|
+
* `StackActions.pop()` aimed at the stack rather than a back the tab router could claim — so
|
|
253
|
+
* the swipe would take all the tabs with it instead of returning to the first one. Turning the
|
|
254
|
+
* card gesture off there would leave the swipe doing nothing, so hand it to the host's
|
|
255
|
+
* floating-arrow interaction instead.
|
|
256
|
+
*/
|
|
257
|
+
const syncBackOwnership = (index: number) => {
|
|
258
|
+
const isFirstTab = index === 0;
|
|
259
|
+
|
|
260
|
+
navigation?.setOptions({
|
|
261
|
+
gestureEnabled: Platform.OS === 'ios' && isFirstTab,
|
|
262
|
+
});
|
|
263
|
+
reportFloatingArrowBack(!isFirstTab);
|
|
264
|
+
};
|
|
265
|
+
|
|
235
266
|
const onFocus = (e: any) => {
|
|
236
267
|
activeIndex.current = tabs.findIndex(i => e.target.includes(i.name));
|
|
237
268
|
indicatorAnimated.value = withTiming(itemWidth * activeIndex.current, {
|
|
238
269
|
duration: 200,
|
|
239
270
|
});
|
|
271
|
+
|
|
272
|
+
syncBackOwnership(activeIndex.current);
|
|
240
273
|
};
|
|
241
274
|
|
|
242
275
|
const handler: {
|
|
@@ -275,9 +308,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
275
308
|
},
|
|
276
309
|
]}
|
|
277
310
|
>
|
|
278
|
-
<Animated.View
|
|
279
|
-
style={[styles.indicatorContainer, indicatorStyle]}
|
|
280
|
-
>
|
|
311
|
+
<Animated.View style={[styles.indicatorContainer, indicatorStyle]}>
|
|
281
312
|
<View
|
|
282
313
|
style={[
|
|
283
314
|
styles.indicator,
|
|
@@ -296,7 +327,11 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
296
327
|
activeTintColor={theme.colors.primary}
|
|
297
328
|
style={[
|
|
298
329
|
{
|
|
299
|
-
height:
|
|
330
|
+
height:
|
|
331
|
+
64 +
|
|
332
|
+
(Platform.OS === 'ios'
|
|
333
|
+
? Math.min(insets.bottom, 21)
|
|
334
|
+
: insets.bottom),
|
|
300
335
|
},
|
|
301
336
|
]}
|
|
302
337
|
/>
|
package/Application/utils.tsx
CHANGED
|
@@ -59,10 +59,8 @@ const forStackTransitionWithOverlay = (
|
|
|
59
59
|
/**
|
|
60
60
|
* default options for stack screen
|
|
61
61
|
*/
|
|
62
|
-
const getStackOptions = ({
|
|
63
|
-
|
|
64
|
-
}: { swipeBack?: boolean } = {}): StackNavigationOptions => {
|
|
65
|
-
const base: StackNavigationOptions = {
|
|
62
|
+
const getStackOptions = (): StackNavigationOptions => {
|
|
63
|
+
return {
|
|
66
64
|
headerTitleAlign: 'left',
|
|
67
65
|
headerTitle: props => <HeaderTitle {...props} />,
|
|
68
66
|
headerTitleContainerStyle: {
|
|
@@ -78,25 +76,6 @@ const getStackOptions = ({
|
|
|
78
76
|
headerLeft: (props: any) => <HeaderLeft {...props} />,
|
|
79
77
|
headerRight: (props: any) => <HeaderRight {...props} />,
|
|
80
78
|
headerTintColor: Colors.black_17,
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* A bottom tab stack holds a single route, so the card transition never runs and
|
|
85
|
-
* `CardContainer` pins `gestureEnabled` to false at index 0 regardless. What does bite is
|
|
86
|
-
* `cardOverlayEnabled`: `current.progress` rests at 1, so the overlay would sit at its full
|
|
87
|
-
* 0.6 forever instead of only during a push, tinting any tab screen that paints no
|
|
88
|
-
* background of its own. This branch is the option set from before swipe back, unchanged.
|
|
89
|
-
*/
|
|
90
|
-
if (!swipeBack) {
|
|
91
|
-
return {
|
|
92
|
-
...base,
|
|
93
|
-
gestureEnabled: false,
|
|
94
|
-
...TransitionPresets.SlideFromRightIOS,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
...base,
|
|
100
79
|
gestureEnabled: Platform.OS === 'ios',
|
|
101
80
|
...TransitionPresets.SlideFromRightIOS,
|
|
102
81
|
cardStyleInterpolator: forStackTransitionWithOverlay,
|
|
@@ -355,6 +334,45 @@ const useNativeCanGoBack = () => {
|
|
|
355
334
|
);
|
|
356
335
|
};
|
|
357
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Hands the swipe back to the host's floating-arrow interaction while a bottom tab other than
|
|
339
|
+
* the first one is showing.
|
|
340
|
+
*
|
|
341
|
+
* The whole tab navigator lives inside a single stack card, and the card gesture pops that card
|
|
342
|
+
* through `StackActions.pop()` aimed at the stack — it never dispatches a back the tab router
|
|
343
|
+
* could claim. So on tab 2 a swipe would take all four tabs with it instead of returning to tab
|
|
344
|
+
* 1. The host already owns an interaction for exactly this, so report the state and let it run.
|
|
345
|
+
*
|
|
346
|
+
* Reports `false` on unmount: the flag lives on the host and would otherwise outlive the tabs
|
|
347
|
+
* that asked for it.
|
|
348
|
+
*/
|
|
349
|
+
const useFloatingArrowBack = () => {
|
|
350
|
+
const lastValue = useRef<boolean | undefined>(undefined);
|
|
351
|
+
|
|
352
|
+
const report = useCallback((shouldUse: boolean) => {
|
|
353
|
+
if (lastValue.current === shouldUse) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const setShouldUseFloatingArrowBack =
|
|
358
|
+
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack;
|
|
359
|
+
if (typeof setShouldUseFloatingArrowBack !== 'function') {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
lastValue.current = shouldUse;
|
|
364
|
+
try {
|
|
365
|
+
setShouldUseFloatingArrowBack(shouldUse);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.warn('[BottomTab] setShouldUseFloatingArrowBack failed:', error);
|
|
368
|
+
}
|
|
369
|
+
}, []);
|
|
370
|
+
|
|
371
|
+
useEffect(() => () => report(false), [report]);
|
|
372
|
+
|
|
373
|
+
return report;
|
|
374
|
+
};
|
|
375
|
+
|
|
358
376
|
/**
|
|
359
377
|
* Union of the two animation runtimes an internal foundation component might
|
|
360
378
|
* receive. Public Screen props still accept React Native Animated.Value only;
|
|
@@ -506,6 +524,7 @@ export {
|
|
|
506
524
|
useComponentId,
|
|
507
525
|
useAppState,
|
|
508
526
|
useNativeCanGoBack,
|
|
527
|
+
useFloatingArrowBack,
|
|
509
528
|
useNormalizedSharedValue,
|
|
510
529
|
SwipeBackHaptic,
|
|
511
530
|
};
|