npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@niibase/bottom-sheet-manager

v1.4.3

Published

A bottom sheet manager for react-native based on @gorhom/bottom-sheet

Downloads

494

Readme

Bottom Sheet Router & Manager

A powerful bottom sheet manager and router for React Native, inspired by react-native-actions-sheet and built on top of @gorhom/bottom-sheet.

Features

  • 🎯 Simple API - Show/hide sheets from anywhere in your app
  • 🔄 Stack Behaviors - Control how sheets stack with push, switch, or replace behaviors
  • 🧭 React Navigation Integration - Full support for React Navigation v6/v7 and Expo Router
  • 📱 iOS 18 Modal Animation - Native-like modal sheet animations
  • 🎨 TypeScript Support - Full type safety with IntelliSense
  • Performance Optimized - High-performance event system with O(1) lookups
  • 🔌 Flexible Hooks - Rich set of hooks for advanced use cases

Installation

npm install @niibase/bottom-sheet-manager

Quick Start

Basic Usage

SheetManager helps you save development time by allowing you to reuse modal sheets throughout your app without boilerplate.

import { BottomSheet, SheetManager } from "@niibase/bottom-sheet-manager";
import type { SheetProps } from "@niibase/bottom-sheet-manager";

Create your BottomSheet component:

function ExampleSheet({ id }: SheetProps<"example-sheet">) {
    return (
        <BottomSheet id={id} snapPoints={["50%", "90%"]}>
            <BottomSheet.View>
                <Text>Hello World</Text>
            </BottomSheet.View>
        </BottomSheet>
    );
}

export default ExampleSheet;

Register your sheet at module level in a sheets.ts file (never inside JSX):

import type { SheetDefinition } from "@niibase/bottom-sheet-manager";
import { registerSheet } from "@niibase/bottom-sheet-manager";

import ExampleSheet from "./ExampleSheet";

// ✅ Correct: register at module level
registerSheet("example-sheet", ExampleSheet);

// Extend types for IntelliSense
declare module "@niibase/bottom-sheet-manager" {
    interface Sheets {
        "example-sheet": SheetDefinition;
    }
}

Import sheets.ts in your app entry point, then wrap your app with SheetProvider:

import { SheetProvider } from "@niibase/bottom-sheet-manager";

import "./sheets";

function App() {
    return <SheetProvider>{/* your app components */}</SheetProvider>;
}

Show and hide sheets:

// Show a sheet
SheetManager.show("example-sheet");

// Show with payload
SheetManager.show("example-sheet", { payload: { userId: 123 } });

// Hide a sheet
SheetManager.hide("example-sheet");

// Hide and get return value
const result = await SheetManager.show("example-sheet");

// Check if a sheet is visible
SheetManager.isVisible("example-sheet"); // boolean

// Get the sheet instance directly
const instance = SheetManager.get("example-sheet");
instance?.expand();

Stack Behaviors

Control how sheets behave when opened on top of existing sheets:

  • switch (default): Dismisses the current sheet before showing the new one. Previous sheet is restored when new one closes.
  • replace: Swaps the current sheet's content with smooth crossfade animation. Previous sheet is removed from stack.
  • push: Pushes new sheet on top, creating a navigable stack. Previous sheet remains visible underneath.
// Set stack behavior per sheet
<BottomSheet
    id={id}
    stackBehavior="push" // or "switch" or "replace"
    snapPoints={["50%", "90%"]}
>
    {/* content */}
</BottomSheet>

React Navigation Integration

Full support for React Navigation v6/v7 and Expo Router. The first screen in the navigator is rendered as main content, and subsequent screens are rendered as bottom sheet modals.

With Expo Router

import { Slot, withLayoutContext } from "expo-router";

import {
    BottomSheetNavigationEventMap,
    BottomSheetNavigationOptions,
    BottomSheetNavigationState,
    createBottomSheetNavigator,
} from "@niibase/bottom-sheet-manager";

const { Navigator } = createBottomSheetNavigator();
const BottomSheet = withLayoutContext<
    BottomSheetNavigationOptions,
    typeof Navigator,
    BottomSheetNavigationState<any>,
    BottomSheetNavigationEventMap
>(Navigator);

export const unstable_settings = {
    initialRouteName: "index",
};

export default function Layout() {
    // SSR guard - navigator doesn't work on server
    if (typeof window === "undefined") return <Slot />;

    return (
        <BottomSheet
            screenOptions={{
                snapPoints: ["50%", "90%"],
                // See: https://gorhom.github.io/react-native-bottom-sheet/modal/props/
            }}
        />
    );
}

With React Navigation

import { createBottomSheetNavigator } from "@niibase/bottom-sheet-manager";

const { Navigator, Screen } = createBottomSheetNavigator();

function App() {
    return (
        <Navigator>
            <Screen name="Home" component={HomeScreen} />
            <Screen
                name="Details"
                component={DetailsSheet}
                options={{
                    snapPoints: ["50%", "100%"],
                    enableBlurKeyboardOnGesture: true,
                }}
            />
        </Navigator>
    );
}

Navigation Actions

Use the navigation object to control sheets programmatically:

import { useBottomSheetNavigation } from "@niibase/bottom-sheet-manager";

function MySheet() {
    const navigation = useBottomSheetNavigation();

    // Snap to a specific index
    const handleExpand = () => {
        navigation.snapTo(1); // Snap to second snap point
    };

    // Dismiss the current sheet
    const handleDismiss = () => {
        navigation.dismiss();
    };

    return (
        <View>
            <Button title="Expand" onPress={handleExpand} />
            <Button title="Dismiss" onPress={handleDismiss} />
        </View>
    );
}

Hooks

useBottomSheetNavigation

Access navigation helpers including snapTo() and dismiss():

import { useBottomSheetNavigation } from "@niibase/bottom-sheet-manager";

const navigation = useBottomSheetNavigation();
navigation.snapTo(1);
navigation.dismiss();

useSheetRef

Get a ref to control the sheet instance. Always use optional chaining (?.) since current can be null before the sheet is mounted:

import { useSheetRef } from "@niibase/bottom-sheet-manager";

function MySheet({ id }: SheetProps<"my-sheet">) {
    const ref = useSheetRef<"my-sheet">();

    // Control the sheet — use ?. since current may be null
    ref.current?.expand();
    ref.current?.collapse();
    ref.current?.snapToIndex(1);
    ref.current?.close({ value: "result" });
}

useSheetPayload

Access the payload passed when showing the sheet:

import { useSheetPayload } from "@niibase/bottom-sheet-manager";

function MySheet({ id }: SheetProps<"my-sheet">) {
    const payload = useSheetPayload<"my-sheet">();
    // payload is typed based on your SheetDefinition
}

useSheetStackBehavior

Get the current stack behavior context:

import { useSheetStackBehavior } from "@niibase/bottom-sheet-manager";

function MySheet() {
    const { behavior, isTransitioning, previousSheetId } = useSheetStackBehavior();
    // behavior: "push" | "replace" | "switch"
}

useOnSheet

Subscribe to sheet events. Takes the sheet id, an event type ("show", "hide", or "onclose"), and a listener:

import { useOnSheet } from "@niibase/bottom-sheet-manager";

function MyComponent() {
    useOnSheet("my-sheet", "show", (payload, context) => {
        console.log("Sheet shown with:", payload);
    });

    useOnSheet("my-sheet", "onclose", (returnValue, context) => {
        console.log("Sheet closed with:", returnValue);
    });
}

iOS 18 Modal Animation

Enable native-like iOS 18 modal sheet animations:

<BottomSheet
    id={id}
    iosModalSheetTypeOfAnimation={true}
    snapPoints={["50%", "90%", "100%"]}
>
    {/* At 90% snap point, content behind scales down with border radius */}
</BottomSheet>

Or in navigation options:

<Screen
    name="Details"
    component={DetailsSheet}
    options={{
        iosModalSheetTypeOfAnimation: true,
        snapPoints: ["50%", "90%", "100%"],
    }}
/>

Note: When iosModalSheetTypeOfAnimation is false (the default), no animated wrappers are added to the component tree, keeping your app's layout overhead minimal.

Advanced Features

Custom Contexts

Use separate contexts for nested sheets or modals.

Important: Context names must be unique across all SheetProvider instances in your app.

// Register at module level — not inside JSX
registerSheet("local-sheet", LocalSheet, "modal-context");

// Then use the provider where you want the sheet to appear
function MyModal() {
    return <SheetProvider context="modal-context">{/* Modal content */}</SheetProvider>;
}

Sheet Instance Methods

Control sheets programmatically:

// Get the sheet instance
const instance = SheetManager.get("example-sheet");

// Expand to maximum snap point
instance?.expand();

// Collapse to minimum snap point
instance?.collapse();

// Snap to specific index
instance?.snapToIndex(1);

// Snap to specific position
instance?.snapToPosition("75%");

// Close with return value
instance?.close({ value: { success: true } });

Checking Visibility

// Check if a sheet is currently open
const isOpen = SheetManager.isVisible("example-sheet");

// Check in a specific context
const isOpen = SheetManager.isVisible("example-sheet", "modal-context");

Animation Configuration

Customize animations:

instance?.expand({
    animationConfigs: {
        type: "spring",
        damping: 20,
        stiffness: 90,
    },
});

Reset (for Testing)

import { SheetManager } from "@niibase/bottom-sheet-manager";

// In your test setup/teardown
afterEach(() => {
    SheetManager.reset();
});

API Reference

SheetManager

Global manager for showing and hiding sheets.

| Method | Signature | Description | | ----------- | ------------------------------------------------------ | --------------------------------------------- | | show | show(id, options?) → Promise<ReturnValue> | Show a sheet, optionally with payload | | hide | hide(id, options?) → Promise<ReturnValue> | Hide a specific sheet | | hideAll | hideAll(id?) → void | Hide all open sheets | | get | get(id, context?) → BottomSheetInstance \| undefined | Get the sheet's imperative instance | | isVisible | isVisible(id, context?) → boolean | Check if a sheet is currently visible | | replace | replace(id, options?) → Promise<ReturnValue> | Show with replace stack behavior | | push | push(id, options?) → Promise<ReturnValue> | Show with push stack behavior | | pop | pop() → void | Close top-most pushed sheet | | reset | reset() → void | Reset all internal state (useful for testing) |

BottomSheet Props

All props from @gorhom/bottom-sheet are supported, plus:

| Prop | Type | Default | Description | | ------------------------------ | --------------------------------- | ---------- | ------------------------------------------------------------------------------------- | | id | SheetID<SheetIds> | - | Unique identifier for the sheet | | stackBehavior | "push" \| "replace" \| "switch" | "switch" | How sheets stack when opened | | iosModalSheetTypeOfAnimation | boolean | false | Enable iOS 18 modal animation | | clickThrough | boolean | false | Allow tapping through backdrop | | opacity | number | 0.45 | Backdrop opacity | | hardwareBackPressToClose | boolean | true | Close on hardware back button (Android) | | onClose | (data?) => ReturnValue \| void | - | Callback when sheet closes; return a value to override what's sent back to the caller | | onBeforeShow | (data?) => void | - | Callback before sheet shows |

SheetProvider Props

| Prop | Type | Default | Description | | ------------- | ---------------- | ----------- | -------------------------------------------------------------------------------------------------- | | context | string | "global" | Unique name for this provider context | | statusBar | StatusBarStyle | "default" | Status bar style when a full-screen sheet is open | | scaleConfig | object | - | Controls the iOS 18-style scale/translate/radius animation applied to the content behind the sheet |

BottomSheetNavigationOptions

Screen options for navigation-based sheets:

| Option | Type | Default | Description | | ------------------------------ | ------------------------- | --------- | ------------------------------ | | snapPoints | Array<string \| number> | ['66%'] | Snap points for the sheet | | clickThrough | boolean | false | Allow tapping through backdrop | | iosModalSheetTypeOfAnimation | boolean | false | Enable iOS 18 modal animation | | opacity | number | 0.45 | Backdrop opacity |

Navigation Actions

import { BottomSheetActions } from "@niibase/bottom-sheet-manager";

// Snap to index
navigation.dispatch(BottomSheetActions.snapTo(1));

// Dismiss sheet
navigation.dispatch(BottomSheetActions.dismiss());

// Remove from stack
navigation.dispatch(BottomSheetActions.remove());

Examples

The source code for the example (showcase) app is under the /example directory. It includes:

  • Basic sheet usage
  • Stack behavior demos (push, replace, switch) including mixed stacks
  • React Navigation integration
  • iOS modal animation examples
  • Navigation actions and hooks usage

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT


Built with ❤️ by @divineniiquaye using React Native and @gorhom/bottom-sheet.

⬆ Back to Top