@momo-kits/foundation 0.165.1-beta.1 → 0.165.1-beta.3
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 +45 -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,41 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
232
238
|
});
|
|
233
239
|
}, [itemWidth, indicatorAnimated]);
|
|
234
240
|
|
|
241
|
+
// Tied to focus, not mount: a screen pushed from a tab leaves BottomTab mounted but blurred,
|
|
242
|
+
// and the flag has to be off there. Also covers `initialRouteName` starting on a later tab.
|
|
243
|
+
useFocusEffect(
|
|
244
|
+
React.useCallback(() => {
|
|
245
|
+
reportFloatingArrowBack(activeIndex.current !== 0);
|
|
246
|
+
return () => reportFloatingArrowBack(false);
|
|
247
|
+
}, [reportFloatingArrowBack]),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Two halves of one decision, so they are set together.
|
|
252
|
+
*
|
|
253
|
+
* On the first tab the stack card owns the swipe and runs the usual transition. On any other
|
|
254
|
+
* tab it must not: every tab lives inside that single card, and the gesture pops it with
|
|
255
|
+
* `StackActions.pop()` aimed at the stack rather than a back the tab router could claim — so
|
|
256
|
+
* the swipe would take all the tabs with it instead of returning to the first one. Turning the
|
|
257
|
+
* card gesture off there would leave the swipe doing nothing, so hand it to the host's
|
|
258
|
+
* floating-arrow interaction instead.
|
|
259
|
+
*/
|
|
260
|
+
const syncBackOwnership = (index: number) => {
|
|
261
|
+
const isFirstTab = index === 0;
|
|
262
|
+
|
|
263
|
+
navigation?.setOptions({
|
|
264
|
+
gestureEnabled: Platform.OS === 'ios' && isFirstTab,
|
|
265
|
+
});
|
|
266
|
+
reportFloatingArrowBack(!isFirstTab);
|
|
267
|
+
};
|
|
268
|
+
|
|
235
269
|
const onFocus = (e: any) => {
|
|
236
270
|
activeIndex.current = tabs.findIndex(i => e.target.includes(i.name));
|
|
237
271
|
indicatorAnimated.value = withTiming(itemWidth * activeIndex.current, {
|
|
238
272
|
duration: 200,
|
|
239
273
|
});
|
|
274
|
+
|
|
275
|
+
syncBackOwnership(activeIndex.current);
|
|
240
276
|
};
|
|
241
277
|
|
|
242
278
|
const handler: {
|
|
@@ -275,9 +311,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
275
311
|
},
|
|
276
312
|
]}
|
|
277
313
|
>
|
|
278
|
-
<Animated.View
|
|
279
|
-
style={[styles.indicatorContainer, indicatorStyle]}
|
|
280
|
-
>
|
|
314
|
+
<Animated.View style={[styles.indicatorContainer, indicatorStyle]}>
|
|
281
315
|
<View
|
|
282
316
|
style={[
|
|
283
317
|
styles.indicator,
|
|
@@ -296,7 +330,11 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
296
330
|
activeTintColor={theme.colors.primary}
|
|
297
331
|
style={[
|
|
298
332
|
{
|
|
299
|
-
height:
|
|
333
|
+
height:
|
|
334
|
+
64 +
|
|
335
|
+
(Platform.OS === 'ios'
|
|
336
|
+
? Math.min(insets.bottom, 21)
|
|
337
|
+
: insets.bottom),
|
|
300
338
|
},
|
|
301
339
|
]}
|
|
302
340
|
/>
|
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
|
};
|