UNPKG

szn-ui-react-native-bottom-sheet

Version:

A customizable React Native bottom sheet component with gesture support, keyboard awareness, and smooth animations.

336 lines (293 loc) 10.6 kB
# React Native Bottom Sheet A smooth, customizable bottom sheet component for React Native with gesture support and TypeScript definitions specifically built for expo SDK 54 and react native reanimated v4 . Ensure that you're using the latest version as previous versions may be broken or depreciated. ![Bottom Sheet Demo](./demo.gif) ## Features - 🎯 **TypeScript Support** - Full TypeScript definitions included - 🎨 **Customizable** - Highly customizable appearance and behavior - 📱 **Cross Platform** - Works on both iOS and Android - ✋ **Gesture Support** - Smooth drag-to-dismiss functionality - ⌨️ **Keyboard Aware** - Proper keyboard handling - 🎭 **Flexible Content** - Support for any content with scroll view - 🎪 **Animation** - Smooth enter/exit animations ## Installation ```bash npm install szn-ui-react-native-bottom-sheet ``` ### Peer Dependencies This package requires the following peer dependencies: ```bash npm install react-native-gesture-handler react-native-reanimated ``` Make sure to follow the installation instructions for: - [react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation) - [react-native-reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started) ## Usage ```tsx import React, { useState } from 'react'; import { View, Text, Button } from 'react-native'; import { BottomSheet } from "szn-ui-react-native-bottom-sheet"; const App = (): JSX.Element => { const [isVisible, setIsVisible] = useState(false); return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Open Bottom Sheet" onPress={() => setIsVisible(true)} /> <BottomSheet isVisible={isVisible} onClose={() => setIsVisible(false)} > <View style={{ padding: 20 }}> <Text>Hello from bottom sheet!</Text> </View> </BottomSheet> </View> ); }; export default App; ``` ## Props | Prop | Type | Default | Description | | ----------------- | ------------ | --------------------- | ------------------------------------------------------------------------ | | `isVisible` | `boolean` | **Required** | Controls the visibility of the bottom sheet | | `onClose` | `() => void` | **Required** | Callback when the sheet should be closed | | `children` | `ReactNode` | **Required** | Content to render inside the bottom sheet | | `height` | `number` | `SCREEN_HEIGHT * 0.9` | Height of the bottom sheet | | `backgroundColor` | `string` | `'white'` | Background color of the bottom sheet | | `showDragHandle` | `boolean` | `true` | Whether to show the drag handle | | `containerStyle` | `ViewStyle` | `undefined` | Additional styles for the sheet container | | `title` | `string` | `undefined` | Title text to display in the header | | `showHeader` | `boolean` | `false` | Whether to show the header with title and cancel button | | `cancelText` | `string` | `'Cancel'` | Text for the cancel button in header | You can customize the sheet's background using either the backgroundColor prop or via containerStyle, but if both are provided, the backgroundColor prop takes precedence. ## Examples ### With Header ```tsx <BottomSheet isVisible={isVisible} onClose={() => setIsVisible(false)} showHeader title="Settings" cancelText="Done" > <View style={{ padding: 20 }}> <Text>Settings content here</Text> </View> </BottomSheet> ``` ### If you wish to use your own custom close button instead of the cancel text prop ```tsx import React, { useRef, useState } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; import { BottomSheet, BottomSheetRef } from 'szn-ui-react-native-bottom-sheet'; import { Ionicons } from '@expo/vector-icons'; export default function MyScreen(): JSX.Element { const [isVisible, setIsVisible] = useState(false); const sheetRef = useRef<BottomSheetRef>(null); const handleOpen = () => setIsVisible(true); const handleClose = () => setIsVisible(false); return ( <> <TouchableOpacity onPress={handleOpen}> <Text>Open Bottom Sheet</Text> </TouchableOpacity> <BottomSheet ref={sheetRef} isVisible={isVisible} onClose={handleClose} showHeader={false} showDragHandle={true} height={600} > <View className="px-5 pb-3 border-b border-gray-100 flex-row justify-between items-center"> <Text className="text-lg font-semibold text-gray-900">My Custom Header</Text> <TouchableOpacity onPress={() => sheetRef.current?.dismiss()} className="p-2 bg-gray-100 rounded-full" > <Ionicons name="close" size={18} color="#374151" /> </TouchableOpacity> </View> <Text className="p-5">Sheet Content Here...</Text> </BottomSheet> </> ); } ``` ### Custom Height ```tsx <BottomSheet isVisible={isVisible} onClose={() => setIsVisible(false)} height={400} > <View style={{ padding: 20 }}> <Text>Custom height content</Text> </View> </BottomSheet> ``` ### Multi-Step Flow ```tsx const [step, setStep] = useState(1); return ( <BottomSheet isVisible={true} onClose={() => {}} height={step === 1 ? 200 : step === 2 ? 400 : 600} > {step === 1 && <StepOne onNext={() => setStep(2)} />} {step === 2 && <StepTwo onNext={() => setStep(3)} />} {step === 3 && <StepThree />} </BottomSheet> ); ``` ### Custom Background Color ```tsx <BottomSheet isVisible={isVisible} onClose={() => setIsVisible(false)} backgroundColor="#fef2f2" > <View style={{ padding: 20 }}> <Text>Light pink background</Text> </View> </BottomSheet> ``` ### Custom Styling ```tsx <BottomSheet isVisible={isVisible} onClose={() => setIsVisible(false)} containerStyle={{ backgroundColor: '#f0f0f0', borderTopLeftRadius: 30, borderTopRightRadius: 30, }} > <View style={{ padding: 20 }}> <Text>Custom styled content</Text> </View> </BottomSheet> ``` ### Full Usage Example ```tsx import { useState } from "react"; import { KeyboardAvoidingView, Platform, Pressable, ScrollView, StyleSheet, TextInput, } from "react-native"; import { BottomSheet } from "szn-ui-react-native-bottom-sheet"; import { ThemedText } from "@/components/themed-text"; import { ThemedView } from "@/components/themed-view"; import ParallaxScrollView from "@/components/parallax-scroll-view"; import { Image } from "expo-image"; import { HelloWave } from "@/components/hello-wave"; export default function HomeScreen() { const [isBottomSheetVisible, setIsBottomSheetVisible] = useState(false); const [inputValue, setInputValue] = useState(""); const openBottomSheet = () => setIsBottomSheetVisible(true); const closeBottomSheet = () => setIsBottomSheetVisible(false); return ( <> <ParallaxScrollView headerBackgroundColor={{ light: "#A1CEDC", dark: "#1D3D47" }} headerImage={ <Image source={require("@/assets/images/partial-react-logo.png")} style={styles.reactLogo} /> } > <ThemedView style={styles.titleContainer}> <ThemedText type="title">Welcome!</ThemedText> <HelloWave /> </ThemedView> <ThemedView style={styles.stepContainer}> <Pressable style={styles.bottomSheetButton} onPress={openBottomSheet}> <ThemedText style={styles.buttonText}>Open Bottom Sheet</ThemedText> </Pressable> </ThemedView> </ParallaxScrollView> <BottomSheet isVisible={isBottomSheetVisible} onClose={closeBottomSheet}> <KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : undefined} style={{ flex: 1 }} > <ScrollView contentContainerStyle={styles.bottomSheetContent}> <ThemedText type="subtitle">Bottom Sheet Test</ThemedText> <ThemedText>Your bottom sheet package is working!</ThemedText> <TextInput placeholder="Type something here..." value={inputValue} onChangeText={setInputValue} style={styles.textInput} placeholderTextColor="#999" /> <Pressable style={styles.closeButton} onPress={closeBottomSheet}> <ThemedText style={styles.buttonText}>Close</ThemedText> </Pressable> </ScrollView> </KeyboardAvoidingView> </BottomSheet> </> ); } const styles = StyleSheet.create({ titleContainer: { flexDirection: "row", alignItems: "center", gap: 8, }, stepContainer: { gap: 8, marginBottom: 8, }, reactLogo: { height: 178, width: 290, bottom: 0, left: 0, position: "absolute", }, bottomSheetButton: { backgroundColor: "#007AFF", paddingHorizontal: 20, paddingVertical: 12, borderRadius: 8, alignItems: "center", marginVertical: 20, }, buttonText: { color: "#FFFFFF", fontWeight: "600", }, bottomSheetContent: { padding: 20, alignItems: "center", gap: 16, }, textInput: { width: "100%", paddingHorizontal: 16, paddingVertical: 12, borderRadius: 8, borderWidth: 1, borderColor: "#ccc", color: "#000", }, closeButton: { backgroundColor: "#FF3B30", paddingHorizontal: 20, paddingVertical: 10, borderRadius: 6, alignItems: "center", }, }); ``` ## License MIT ## Contributing Contributions are welcome! Please feel free to submit a Pull Request.