A bottom sheet component that combines Gorhom Bottom Sheet and Vaul for seamless and responsive experience across both mobile and web.
Note: This is not a standalone package. It is a wrapper around Gorhom Bottom Sheet and Vaul for use across both mobile and web. You need to install Gorhom Bottom Sheet and Vaul separately to use this component.
I recommend following the installation guide for Gorhom's Bottom Sheet
npm install @gorhom/bottom-sheet@^4
or
yarn add @gorhom/bottom-sheet@^4
Make sure to install the following peer dependencies
npm install react-native-reanimated react-native-gesture-handler
or
yarn add react-native-reanimated react-native-gesture-handler
or with expo
npx expo install react-native-reanimated react-native-gesture-handler
Make sure to install the following dependencies
npm install vaul
or
yarn add vaul
This example uses NativeWind for styling. You can use any other styling library of your choice.
Follow the installation guide here
An example usage of component is shown below:
The ui/bottom-sheet
module exports the following components:
BottomSheetModal
BottomSheetModalProvider
BottomSheetView
BottomSheetTrigger
BottomSheetHandle
BottomSheetScrollView
The files can be found in the ui/bottom-sheet
directory. Copy the files to your project and import them as shown below:
"use client";
import React, { useCallback, useMemo, useRef } from "react";
import { Text } from "ui/text";
import { View } from "ui/view";
import {
BottomSheetModal,
BottomSheetView,
BottomSheetTrigger,
BottomSheetHandle,
} from "ui/bottom-sheet";
import { Pressable, Platform } from "react-native";
import { useSharedValue } from "react-native-reanimated";
export function Home() {
const [isOpen, setIsOpen] = React.useState(false);
const animatedIndex = useSharedValue<number>(0);
const animatedPosition = useSharedValue<number>(0);
// ref
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
// bottomSheetModalRef
console.log({ bottomSheetModalRef });
// variables
const snapPoints = useMemo(() => [600, "20%", "50%", "70%", "95%"], []);
// callbacks
const handlePresentModalPress = useCallback(() => {
// bottomSheetWebRef.current?.focus();
if (isOpen) {
bottomSheetModalRef.current?.dismiss();
setIsOpen(false);
} else {
bottomSheetModalRef.current?.present();
setIsOpen(true);
}
}, [isOpen]);
const handleSheetChanges = useCallback((index: number) => {
console.log("handleSheetChanges", index);
}, []);
return (
<View className="flex flex-1 justify-center items-center">
<View className="p-1 rounded-md">
{Platform.OS !== "web" && ( // Use this condition if you want to control the modal from outside for only mobile
<Pressable onPress={handlePresentModalPress}>
<Text>Present Modal</Text>
</Pressable>
)}
<BottomSheetModal
ref={bottomSheetModalRef}
index={1}
// open={isOpen} Use this prop if you want to control the modal from outside for web
snapPoints={snapPoints}
onChange={handleSheetChanges}
handleComponent={() => (
<BottomSheetHandle
className="bg-green-300 mt-2"
animatedIndex={animatedIndex}
animatedPosition={animatedPosition}
/>
)}
>
{Platform.OS === "web" && (
<>
<BottomSheetTrigger>
<Text>Present Modal</Text>
</BottomSheetTrigger>
</>
)}
<BottomSheetView className="flex-1 items-center">
{Platform.OS === "web" && (
<BottomSheetHandle
className="bg-gray-300 mt-2"
animatedIndex={animatedIndex}
animatedPosition={animatedPosition}
/>
)}
<Text className="mt-10">Awesome 🎉</Text>
</BottomSheetView>
</BottomSheetModal>
</View>
</View>
);
}