th3rdwave / react-navigation-bottom-sheet

MIT License
418 stars 17 forks source link

Functionality for rendering a single modal over a stack #9

Open codybrouwers opened 1 year ago

codybrouwers commented 1 year ago

Hi there, thank you for the great library, it is a great abstraction over the bottom sheet library for routing!

An issue I came across was needing to show a single bottom sheet in a transparent modal over a screen like this:

const Stack = createNativeStackNavigator();
const BottomSheet = createBottomSheetNavigator();

function MyModal() {
  return (
    <BottomSheet.Navigator>
      <BottomSheet.Screen
        component={MyModalComponent}
        name="MyModal"
        options={MyModalComponent.options}
      />
    </BottomSheet.Navigator>
  );
}

export function HomeStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        component={HomeScreen}
        name="Home"
      />
      <Stack.Group screenOptions={{ presentation: "transparentModal" }}>
        <Stack.Screen
          component={MyModal}
          name="MyModalBottomSheet"
        />
      </Stack.Group>
    </Stack.Navigator>
  );
}

If my HomeStack has lots of other screens that I don't want to be shown as bottom sheets then the above I think is the only option. But this library seems to require that the first screen in the bottom sheet navigator be your app content, not a modal. I'm not sure the best solution but I've added the below patch with a firstScreenIsModal option to always show the bottom sheet provider if true.

I'm happy to submit a PR with the below patch if you think it is a good approach but wanted to get your thoughts on it first!

Patch ```diff // in BottomSheetView.tsx + const firstScreen = descriptors[state.routes[0].key]; + const { firstScreenIsModal = false } = firstScreen.options + // Avoid rendering provider if we only have one screen. - const shouldRenderProvider = React.useRef(false); + const shouldRenderProvider = React.useRef(firstScreenIsModal); shouldRenderProvider.current = shouldRenderProvider.current || state.routes.length > 1; - const firstScreen = descriptors[state.routes[0].key]; return ( <> - {firstScreen.render()} + {firstScreenIsModal ? null : firstScreen.render()} {shouldRenderProvider.current && ( - {state.routes.slice(1).map((route) => { + {state.routes.slice(firstScreenIsModal ? 0 : 1).map((route) => { const { options, navigation, render } = descriptors[route.key]; ```
janicduplessis commented 1 year ago

I was thinking maybe passing the app content as a navigator prop instead of using the first screen would be better. This should solve this case.

codybrouwers commented 1 year ago

Yep that would work as well!

I'm not sure I'll have time to put together a PR for a little while as my solution above has solved my immediate need but will come back to it later and let you know before I try implementing your idea!