Skip to content

sigmela/native-sheet

React Native Bottom Sheet

A React Native library for creating truly native sheet components with smooth animations and native feel across iOS and Android platforms.

Features

  • 🎨 Native sheet presentation with platform-specific animations
  • 📜 Smart ScrollView integration with automatic content sizing
  • 🔧 Customizable appearance (corner radius, backdrop opacity, background color)
  • 📱 Support for full-screen and partial height sheets on android
  • ⚡ Built for New Architecture (Fabric) only
  • 🎯 TypeScript support
  • 🔄 Part of @sigmela/router
  • 📚 Can be used standalone with react-native-screens

Installation

yarn add @sigmela/native-sheet

iOS Setup

cd ios && pod install

Android Setup

The Android setup is automatically handled by React Native's autolinking.

Usage with react-native-screens (Standalone)

You can use the library standalone with react-native-screens:

import React, { useState, useRef } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
import { ScreenStack, ScreenStackItem } from 'react-native-screens';
import { NativeSheetView, Commands } from '@sigmela/native-sheet';

const SheetContent = ({ onDismiss }) => {
  return (
    <>
      <View testID="sheet-header" style={styles.header}>
        <Text style={styles.title}>Sheet Title</Text>
        <TouchableOpacity style={styles.button} onPress={onDismiss}>
          <Text style={styles.buttonText}>Close Sheet</Text>
        </TouchableOpacity>
      </View>
      <ScrollView testID="sheet-content" style={styles.content}>
        <Text style={styles.description}>
          This is the sheet content area. You can put any content here including ScrollView, FlatList, or regular Views.
        </Text>
      </ScrollView>
    </>
  );
};

const HomeScreen = ({ onOpenSheet }) => {
  return (
    <View style={styles.container}>
      <TouchableOpacity style={styles.button} onPress={onOpenSheet}>
        <Text style={styles.buttonText}>Open Sheet</Text>
      </TouchableOpacity>
    </View>
  );
};

export default function App() {
  const [showSheet, setShowSheet] = useState(false);
  const sheetRef = useRef(null);

  const openSheet = () => setShowSheet(true);
  
  const dismissSheet = () => {
    if (sheetRef.current) {
      Commands.dismiss(sheetRef.current);
    }
  };

  const onSheetDismissed = () => {
    setShowSheet(false);
  };

  return (
    <ScreenStack style={styles.flex}>
      <ScreenStackItem
        screenId="home"
        style={StyleSheet.absoluteFill}
        headerConfig={{ title: 'Home' }}
      >
        <HomeScreen onOpenSheet={openSheet} />
      </ScreenStackItem>

      {showSheet && (
        <ScreenStackItem
          screenId="sheet"
          stackPresentation="transparentModal"
          headerConfig={{ hidden: true }}
          style={StyleSheet.absoluteFill}
          contentStyle={styles.transparentContent}
          stackAnimation="none"
          onDismissed={onSheetDismissed}
        >
          <NativeSheetView
            ref={sheetRef}
            style={styles.flex}
            onDismissed={onSheetDismissed}
            cornerRadius={18}
            containerBackgroundColor="#ffffff"
            backdropOpacity={0.5}
          >
            <SheetContent onDismiss={dismissSheet} />
          </NativeSheetView>
        </ScreenStackItem>
      )}
    </ScreenStack>
  );
}

const styles = StyleSheet.create({
  flex: {
    flex: 1,
  },
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f5f5f5',
  },
  header: {
    padding: 20,
    alignItems: 'center',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  content: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  description: {
    fontSize: 16,
    lineHeight: 24,
    color: '#666',
  },
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 20,
    paddingVertical: 12,
    borderRadius: 8,
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: '600',
  },
  transparentContent: {
    backgroundColor: 'transparent',
  },
});

API Reference

NativeSheetView Props

Prop Type Default Description
backdropOpacity number 0.4 Opacity of the backdrop behind the sheet
cornerRadius number 0 Corner radius for the sheet container
fullscreenTopInset number 0 Top inset when sheet is in fullscreen mode
containerBackgroundColor ColorValue undefined Background color of the sheet container
onAppeared () => void undefined Callback fired when sheet appears
onDismissed () => void undefined Callback fired when sheet is dismissed

Sheet Slots

The library recognizes specific testID values to optimize layout and behavior:

TestID Purpose Description
sheet-header Header area Fixed header section at the top of the sheet
sheet-content Content area Scrollable content area that automatically adjusts to available space
<NativeSheetView>
  <View testID="sheet-header">
    {/* Header content - fixed at top */}
  </View>
  <ScrollView testID="sheet-content">
    {/* Scrollable content */}
  </ScrollView>
</NativeSheetView>

Commands

Commands.dismiss(ref)

Programmatically dismiss the sheet.

  • ref: Reference to the NativeSheetView component
const sheetRef = useRef(null);

const dismissSheet = () => {
  if (sheetRef.current) {
    Commands.dismiss(sheetRef.current);
  }
};

Examples

The repository includes a complete example app demonstrating both usage patterns:

# Clone the repository
git clone https://github.com/sigmela/native-sheet.git
cd native-sheet

# Install dependencies
yarn install

# Run the example (iOS)
yarn example ios

# Run the example (Android)  
yarn example android

Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

License

MIT © sigmela

About

Truly Native Bottom Sheet for React Native

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published