Bottom Sheet

A bottom sheet helps people perform a scoped task that's closely related to their current context.

Apple
Apple
import { Icon } from '@roninoss/icons';
import { Stack } from 'expo-router';
import { View, Text, ScrollView, Pressable, Button, Platform } from 'react-native';

import { Sheet, useSheetRef } from '~/components/nativewindui/Sheet';
import { useColorScheme } from '~/lib/useColorScheme';

export default function SheetScreen() {
  const { colors, isDarkColorScheme } = useColorScheme();
  const bottomSheetModalRef = useSheetRef();
  return (
    <>
      <Stack.Screen
        options={{
          title: 'NativeWindUI',
          headerSearchBarOptions: {
            hideWhenScrolling: false,
          },
          headerLargeTitle: true,
          headerRight() {
            return (
              <Pressable className="opacity-80 active:opacity-40">
                <View className="opacity-90">
                  <Icon name="cog-outline" color={colors.foreground} />
                </View>
              </Pressable>
            );
          },
        }}
      />
      <ScrollView contentInsetAdjustmentBehavior="automatic" className="p-4">
        <View className="border-border bg-card gap-4 rounded-xl border p-4 pb-6 shadow-sm shadow-black/10 dark:shadow-none">
          <Text className="text-foreground text-center text-sm font-medium tracking-wider opacity-60">
            Bottom Sheet
          </Text>
          <Button
            color={isDarkColorScheme && Platform.OS === 'ios' ? 'white' : 'black'}
            title="Open Bottom Sheet"
            onPress={() => bottomSheetModalRef.current?.present()}
          />
        </View>
      </ScrollView>
      <Sheet ref={bottomSheetModalRef} snapPoints={[200]}>
        <View className="flex-1 items-center justify-center pb-8">
          <Text className="text-foreground">@gorhom/bottom-sheet 🎉</Text>
        </View>
      </Sheet>
    </>
  );
}

Installation

1

Run the following command:

npx nwui-cli@latest add bottom-sheet
2

Edit _layout.tsx

Next, wrap your root component with a GestureHandlerRootView (with a style of flex: 1) and a BottomSheetModalProvider.

~/app/_layout.tsx
    import '../global.css';
    import 'expo-dev-client';

+   import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';    
    import { StatusBar } from 'expo-status-bar';
+   import { GestureHandlerRootView } from 'react-native-gesture-handler';
    // YOUR_OTHER_IMPORTS

    import { useColorScheme, useInitialAndroidBarSync } from '~/lib/useColorScheme';
    import { NAV_THEME } from '~/theme';

    export {    
      // Catch any errors thrown by the Layout component.
      ErrorBoundary,
    } from 'expo-router';

    export default function RootLayout() {    
      useInitialAndroidBarSync();
      const { colorScheme, isDarkColorScheme } = useColorScheme();
    
      return (
        <>
          <StatusBar
            key={root-status-bar-${isDarkColorScheme ? 'light' : 'dark'}}
            style={isDarkColorScheme ? 'light' : 'dark'}
          />
+         <GestureHandlerRootView style={{ flex: 1 }}>
+             <BottomSheetModalProvider>
                <NavThemeProvider value={NAV_THEME[colorScheme]}>
                  {/* YOUR_ROOT_NAVIGATOR */}
                </NavThemeProvider>
+             </BottomSheetModalProvider>
+          </GestureHandlerRootView>
        </>
      );
    }
🚀
Ship.
1

Add the following dependencies to your project:

npx expo install @gorhom/bottom-sheet react-native-gesture-handler
2

Edit _layout.tsx

Next, wrap your root component with a GestureHandlerRootView (with a style of flex: 1) and a BottomSheetModalProvider.

~/app/_layout.tsx
    import '../global.css';
    import 'expo-dev-client';

+   import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';    
    import { StatusBar } from 'expo-status-bar';
+   import { GestureHandlerRootView } from 'react-native-gesture-handler';
    // YOUR_OTHER_IMPORTS

    import { useColorScheme, useInitialAndroidBarSync } from '~/lib/useColorScheme';
    import { NAV_THEME } from '~/theme';

    export {    
      // Catch any errors thrown by the Layout component.
      ErrorBoundary,
    } from 'expo-router';

    export default function RootLayout() {    
      useInitialAndroidBarSync();
      const { colorScheme, isDarkColorScheme } = useColorScheme();
    
      return (
        <>
          <StatusBar
            key={root-status-bar-${isDarkColorScheme ? 'light' : 'dark'}}
            style={isDarkColorScheme ? 'light' : 'dark'}
          />
+         <GestureHandlerRootView style={{ flex: 1 }}>
+             <BottomSheetModalProvider>
                <NavThemeProvider value={NAV_THEME[colorScheme]}>
                  {/* YOUR_ROOT_NAVIGATOR */}
                </NavThemeProvider>
+             </BottomSheetModalProvider>
+          </GestureHandlerRootView>
        </>
      );
    }
3

Copy/paste the following code to the specified file path:

~/components/nativewindui/Sheet.tsx
import {
  BottomSheetBackdrop,
  BottomSheetBackdropProps,
  BottomSheetModal,
} from '@gorhom/bottom-sheet';
import * as React from 'react';

import { useColorScheme } from '~/lib/useColorScheme';

const Sheet = React.forwardRef<
  BottomSheetModal,
  React.ComponentPropsWithoutRef<typeof BottomSheetModal>
>(({ index = 0, backgroundStyle, style, handleIndicatorStyle, ...props }, ref) => {
  const { colors } = useColorScheme();

  const renderBackdrop = React.useCallback(
    (props: BottomSheetBackdropProps) => <BottomSheetBackdrop {...props} disappearsOnIndex={-1} />,
    []
  );
  return (
    <BottomSheetModal
      ref={ref}
      index={0}
      backgroundStyle={
        backgroundStyle ?? {
          backgroundColor: colors.card,
        }
      }
      style={
        style ?? {
          borderWidth: 1,
          borderColor: colors.grey5,
          borderTopStartRadius: 16,
          borderTopEndRadius: 16,
        }
      }
      handleIndicatorStyle={
        handleIndicatorStyle ?? {
          backgroundColor: colors.grey4,
        }
      }
      backdropComponent={renderBackdrop}
      {...props}
    />
  );
});

function useSheetRef() {
  return React.useRef<BottomSheetModal>(null);
}

export { Sheet, useSheetRef };
🛸
Ship.

Usage

import { Sheet, useSheetRef } from '~/components/nativewindui/Sheet';
const bottomSheetModalRef = useSheetRef();

React.useEffect(() => {
  bottomSheetModalRef.current?.present();
}, []);

return (
  <Sheet ref={bottomSheetModalRef} snapPoints={[200]}>
    {/* content */}
  </Sheet>
);

Props

See the @gorhom/bottom-sheet documentation for all of the props.

© Ronin Technologies LLC 2024