gorhom / react-native-bottom-sheet

A performant interactive bottom sheet with fully configurable options 🚀
https://ui.gorhom.dev/components/bottom-sheet
MIT License
6.55k stars 733 forks source link

Getting bottom-sheet to work with expo-router tabs #1884

Open vintvgx opened 1 month ago

vintvgx commented 1 month ago

Bug

Within my react native application, I am trying to implement creating a bottom tab navigator that opens a bottom-sheet when my "Profile" tab is pressed. Due to expo's new architecture, I am using Tabs from expo-router to generate my tabs layout, so I don't have access to NavigationContainer.

I have attempted wrapping the BottomSheetModalProvider around my top level _layout (App.tsx) and that did not work. Also, tried wrapping BottomSheetModalProvider around my Tabs component and still doesn't work. My code is as followed below:

Tabs layout file

// TabLayout.js
import { Tabs } from "expo-router";
import React, { useMemo, useRef } from "react";
import { BlurView } from "expo-blur";
import { View, StyleSheet, Text, TouchableOpacity } from "react-native";
import { LinearGradient } from "expo-linear-gradient";
import {
  BottomSheetModal,
  BottomSheetModalProvider,
  BottomSheetView,
} from "@gorhom/bottom-sheet";

import { useColorScheme } from "@/hooks/useColorScheme";
import ProfileOptionsSheet from "@/components/profile/ProfileOptionSheet";

export default function TabLayout() {
  const colorScheme = useColorScheme();
  const bottomSheetModalRef = useRef<BottomSheetModal>(null);

  const handleProfilePress = () => {
    try {
      bottomSheetModalRef.current?.present();
      console.log("Display bottom sheet");
    } catch (error) {
      console.log("Error: ", error);
    }
  };

  return (
    <BottomSheetModalProvider>
      <Tabs
        screenOptions={{
          tabBarActiveTintColor: "black",
          headerShown: false,
          tabBarBackground: () => (
            <View style={styles.tabBarBackgroundContainer}>
              <BlurView
                tint="light"
                intensity={20}
                style={styles.blurViewStyle}
              />
              <LinearGradient
                colors={["#FFFFFF", "#9E9E9E"]}
                style={styles.gradientStyle}
                start={[0, 0]}
                end={[1, 0]}
                locations={[0.0, 0.3]}
              />
            </View>
          ),
          tabBarLabelStyle: {
            fontSize: 14,
            fontWeight: "bold",
          },
          tabBarStyle: {
            position: "absolute",
            overflow: "hidden",
            backgroundColor: "transparent",
            height: 70,
          },
        }}>
        <Tabs.Screen
          name="index"
          options={{
            title: "View",
            tabBarIcon: () => null,
          }}
        />
        <Tabs.Screen
          name="create"
          options={{
            title: "Toolbox",
            tabBarIcon: () => null,
            tabBarButton: (props) => (
              <TouchableOpacity {...props} onPress={handleProfilePress} />
            ),
          }}
        />
        <Tabs.Screen
          name="profile"
          options={{
            title: "Profile",
            tabBarIcon: () => null,
            // tabBarButton: (props) => (
            //   <TouchableOpacity {...props} onPress={handleProfilePress} />
            // ),
          }}
        />
      </Tabs>
      <ProfileOptionsSheet bottomSheetRef={bottomSheetModalRef} />
    </BottomSheetModalProvider>
  );
}

const styles = StyleSheet.create({
  tabBarBackgroundContainer: {
    ...StyleSheet.absoluteFillObject,
    overflow: "hidden",
  },
  blurViewStyle: {
    ...StyleSheet.absoluteFillObject,
  },
  gradientStyle: {
    ...StyleSheet.absoluteFillObject,
    opacity: 0.2, // Adjust the opacity to allow blur to show through
  },
  contentContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  option: {
    padding: 20,
  },
  optionText: {
    // fontSize: 18,
  },
});

Bottom Sheet Component

// ProfileOptionsSheet.js
import React, { useRef, useMemo, useEffect } from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import BottomSheet, {
  BottomSheetModal,
  BottomSheetView,
} from "@gorhom/bottom-sheet";

interface ProfileOptionsSheetProps {
  bottomSheetRef: React.RefObject<BottomSheetModal>;
}

export default function ProfileOptionsSheet({
  bottomSheetRef,
}: ProfileOptionsSheetProps) {
  const snapPoints = useMemo(() => ["25%", "50%"], []);

  useEffect(() => {
    console.log("Profile Option Sheet Triggered ");
  }, []);

  return (
    <BottomSheetModal ref={bottomSheetRef} index={-1} snapPoints={snapPoints}>
      <BottomSheetView>
        <View style={styles.contentContainer}>
          <TouchableOpacity style={styles.option}>
            <Text style={styles.optionText}>Subscription</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.option}>
            <Text style={styles.optionText}>Extension</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.option}>
            <Text style={styles.optionText}>About</Text>
          </TouchableOpacity>
        </View>
      </BottomSheetView>
    </BottomSheetModal>
  );
}

const styles = StyleSheet.create({
  contentContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
  },
  option: {
    padding: 20,
  },
  optionText: {
    fontSize: 18,
  },
});

Top level layout (App.tsx)

import { Slot, SplashScreen, Stack } from "expo-router";
import { useFonts } from "expo-font";
import { StatusBar, useColorScheme } from "react-native";
import { useEffect } from "react";
import {
  DarkTheme,
  DefaultTheme,
  ThemeProvider,
} from "@react-navigation/native";
import { Provider } from "react-redux";
import { store } from "@/redux/store";
import { AuthContextProvider } from "@/context/AuthContext";
import { CustomToast } from "@/components/CustomToast";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";

export default function RootLayout() {
  const colorScheme = useColorScheme();
  const [loaded] = useFonts({
    SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
    SFProDisplay: require("../assets/fonts/SF-Pro-Display-Regular.otf"),
    SFProDisplayBold: require("../assets/fonts/SF-Pro-Display-Bold.otf"),
    SFProDisplayMedium: require("../assets/fonts/SF-Pro-Display-Medium.otf"),
    SFProDisplaySemiBold: require("../assets/fonts/SF-Pro-Display-Semibold.otf"),
    SFProDisplayLight: require("../assets/fonts/SF-Pro-Display-Light.otf"),
    SFProDisplayThin: require("../assets/fonts/SF-Pro-Display-Thin.otf"),
    SFProDisplayUltraLight: require("../assets/fonts/SF-Pro-Display-Ultralight.otf"),
  });

  useEffect(() => {
    if (loaded) {
      SplashScreen.hideAsync();
    }
  }, [loaded]);

  if (!loaded) {
    return null;
  }
  return <RootLayoutNav />;
}

function RootLayoutNav() {
  const colorScheme = useColorScheme();

  return (
    <Provider store={store}>
      <GestureHandlerRootView style={{ flex: 1 }}>
        {/* <BottomSheetModalProvider> */}
        <CustomToast />
        <AuthContextProvider>
          <ThemeProvider
            value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
            <Stack>
              <Stack.Screen
                name="(app)/(tabs)"
                options={{ headerShown: false }}
              />
              <Stack.Screen
                name="(public)/auth"
                options={{ headerShown: false }}
              />
              <Stack.Screen
                name="(public)/onboard"
                options={{ headerShown: false }}
              />
              <Stack.Screen
                name="(public)/welcome"
                options={{ headerShown: false }}
              />
              <Stack.Screen
                name="(public)/profilepicture"
                options={{ headerShown: false }}
              />
            </Stack>
          </ThemeProvider>
        </AuthContextProvider>
        {/* </BottomSheetModalProvider> */}
      </GestureHandlerRootView>
    </Provider>
  );
}

Environment info

expo-env-info 1.2.0 environment info: System: OS: macOS 14.2.1 Shell: 5.9 - /bin/zsh Binaries: Node: 20.11.0 - /usr/local/bin/node npm: 10.2.4 - /usr/local/bin/npm Managers: CocoaPods: 1.14.3 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: DriverKit 23.5, iOS 17.5, macOS 14.5, tvOS 17.5, visionOS 1.2, watchOS 10.5 IDEs: Android Studio: 2024.1 AI-241.15989.150.2411.11948838 Xcode: 15.4/15F31d - /usr/bin/xcodebuild npmPackages: expo: ~51.0.14 => 51.0.14 expo-router: ~3.5.16 => 3.5.16 react: 18.2.0 => 18.2.0 react-dom: 18.2.0 => 18.2.0 react-native: 0.74.2 => 0.74.2 react-native-web: ~0.19.10 => 0.19.12 npmGlobalPackages: eas-cli: 9.1.0 expo-cli: 6.3.10 Expo Workflow: managed

"dependencies": { "@expo/vector-icons": "^14.0.0", "@gorhom/bottom-sheet": "^4.6.3", "@react-navigation/native": "^6.0.2", "@reduxjs/toolkit": "^2.2.5", "axios": "^1.7.2", "expo": "~51.0.14", "expo-blur": "~13.0.2", "expo-constants": "~16.0.2", "expo-font": "~12.0.7", "expo-image-picker": "~15.0.6", "expo-linear-gradient": "~13.0.2", "expo-linking": "~6.3.1", "expo-router": "~3.5.16", "expo-splash-screen": "~0.27.5", "expo-status-bar": "~1.12.1", "expo-system-ui": "~3.0.6", "expo-web-browser": "~13.0.3", "pretty-format": "^29.7.0", "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.74.2", "react-native-gesture-handler": "~2.16.1", "react-native-reanimated": "~3.10.1", "react-native-safe-area-context": "4.10.1", "react-native-screens": "3.31.1", "react-native-toast-message": "^2.2.0", "react-native-web": "~0.19.10", "react-redux": "^9.1.2", "redux-thunk": "^3.1.0" },

github-actions[bot] commented 1 month ago

@vintvgx: hello! :wave:

This issue is being automatically closed because it does not follow the issue template.

github-actions[bot] commented 5 days ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.