6
6
TabRouter ,
7
7
useNavigationBuilder ,
8
8
} from "@react-navigation/native" ;
9
- import { Text } from "react-native" ;
9
+ import { Platform , Text } from "react-native" ;
10
10
11
- import React , { useEffect , useRef } from "react" ;
11
+ import React , { useEffect , useMemo , useRef , useState } from "react" ;
12
12
import { View , Dimensions } from "react-native" ;
13
13
14
14
import { useSafeAreaInsets } from "react-native-safe-area-context" ;
@@ -22,10 +22,14 @@ import colorsList from "@/utils/data/colors.json";
22
22
23
23
import Reanimated , {
24
24
useAnimatedStyle ,
25
- useSharedValue ,
26
25
withTiming ,
26
+ withSpring ,
27
+ interpolate ,
28
+ useSharedValue ,
29
+ Easing ,
27
30
} from "react-native-reanimated" ;
28
31
import * as Haptics from "expo-haptics" ;
32
+ import getCorners from "@/utils/ui/corner-radius" ;
29
33
30
34
type DescriptorOptions = {
31
35
tabBarLabel ?: string ;
@@ -432,41 +436,124 @@ const BottomTabNavigator: React.ComponentType<any> = ({
432
436
} ) => {
433
437
const dims = Dimensions . get ( "window" ) ;
434
438
const tablet = dims . width > 600 ;
439
+ const mainNavigator = useRef ( null ) ;
440
+
441
+ // Track previous index to determine direction
442
+ const [ previousIndex , setPreviousIndex ] = useState ( 0 ) ;
443
+
444
+ // Animation shared values
445
+ const slideAnim = useSharedValue ( 0 ) ;
446
+ const fadeAnim = useSharedValue ( 1 ) ;
447
+ const [ isAnimating , setIsAnimating ] = useState ( false ) ;
448
+
449
+ const corners = useMemo ( ( ) => getCorners ( ) , [ ] ) ;
450
+
451
+ const {
452
+ state,
453
+ descriptors,
454
+ navigation,
455
+ NavigationContent
456
+ } = useNavigationBuilder ( TabRouter , {
457
+ initialRouteName,
458
+ backBehavior,
459
+ children,
460
+ screenOptions,
461
+ } ) ;
462
+
463
+ // Handle tab change animations
464
+ useEffect ( ( ) => {
465
+ if ( Platform . OS !== "ios" ) return ;
466
+ if ( state . index === previousIndex ) return ;
435
467
436
- const { state, descriptors, navigation, NavigationContent } =
437
- useNavigationBuilder ( TabRouter , {
438
- initialRouteName,
439
- backBehavior,
440
- children,
441
- screenOptions,
468
+ // Determine animation direction
469
+ const isMovingForward = state . index > previousIndex ;
470
+ setIsAnimating ( true ) ;
471
+
472
+ // Reset animations with direction
473
+ fadeAnim . value = 0 ;
474
+ slideAnim . value = isMovingForward ? 80 : - 80 ;
475
+
476
+ fadeAnim . value = withTiming ( 1 , {
477
+ duration : 200 ,
478
+ easing : Easing . inOut ( Easing . ease ) ,
479
+ } ) ;
480
+ slideAnim . value = withTiming ( 0 , {
481
+ duration : 400 ,
482
+ easing : Easing . out ( Easing . exp ) ,
442
483
} ) ;
443
484
485
+ // Update previous index
486
+ setPreviousIndex ( state . index ) ;
487
+ setTimeout ( ( ) => {
488
+ setIsAnimating ( false ) ;
489
+ } , 300 ) ;
490
+ } , [ state . index , dims . width ] ) ;
491
+
492
+ // Create animated styles
493
+ const animatedStyles = useAnimatedStyle ( ( ) => {
494
+ return {
495
+ opacity : fadeAnim . value ,
496
+ transform : [
497
+ {
498
+ translateX : slideAnim . value ,
499
+ } ,
500
+ ] ,
501
+ } ;
502
+ } ) ;
503
+
444
504
return (
445
505
< NavigationContent >
446
506
< View
447
507
style = { [
448
508
{
449
509
flex : 1 ,
510
+ overflow : "hidden" , // Prevent content from showing outside bounds during animation
450
511
} ,
451
512
tablet && {
452
513
flexDirection : "row-reverse" ,
453
514
} ,
454
515
] }
455
516
>
456
- < BottomTabView
457
- { ...rest }
458
- state = { state }
459
- navigation = { navigation }
460
- descriptors = { descriptors }
461
- />
462
- { ! tablet ?
463
- < BasePapillonBar state = { state } descriptors = { descriptors } navigation = { navigation } />
464
- :
465
- < LargePapillonBar state = { state } descriptors = { descriptors } navigation = { navigation } />
466
- }
517
+ < Reanimated . View
518
+ ref = { mainNavigator }
519
+ style = { [
520
+ {
521
+ flex : 1 ,
522
+ } ,
523
+ Platform . OS === "ios" && isAnimating && {
524
+ borderTopLeftRadius : corners ,
525
+ borderTopRightRadius : corners ,
526
+ borderCurve : "continuous" ,
527
+ overflow : "hidden" ,
528
+ } ,
529
+ animatedStyles ,
530
+ ] }
531
+ >
532
+ < BottomTabView
533
+ { ...rest }
534
+ state = { state }
535
+ navigation = { navigation }
536
+ descriptors = { descriptors }
537
+ />
538
+ </ Reanimated . View >
539
+
540
+ { ! tablet ? (
541
+ < BasePapillonBar
542
+ state = { state }
543
+ descriptors = { descriptors }
544
+ navigation = { navigation }
545
+ />
546
+ ) : (
547
+ < LargePapillonBar
548
+ state = { state }
549
+ descriptors = { descriptors }
550
+ navigation = { navigation }
551
+ />
552
+ ) }
467
553
</ View >
468
554
</ NavigationContent >
469
555
) ;
470
556
} ;
471
557
558
+
472
559
export default createNavigatorFactory ( BottomTabNavigator ) ;
0 commit comments