Skip to content

sivantha96/react-native-trays

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

60 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

react-native-trays

Demo 1 Demo 2 Demo 3

A production-grade, fully open-source tray system for React Native with a React Navigation-like API. Built with TypeScript, Reanimated, and best industry practices, supporting both Expo and bare workflows.


npm version Downloads License: MIT Build Status


πŸ€” Why react-native-trays?

While there are many modal libraries for React Native, react-native-trays is designed to feel like a native extension of your app's navigation. It provides a powerful, yet familiar, useTrays hook that mimics the useNavigation hook from React Navigation. This allows you to manage contextual UI "trays"β€”like bottom sheets, alerts, or pop-upsβ€”as part of a declarative and predictable stack.

It's built from the ground up with TypeScript, Reanimated 2, and a focus on performance and developer experience.

✨ Features

  • React Navigation-like API: A familiar useTrays hook with push, pop, replace, and dismiss methods.
  • Multiple Tray Stacks: Manage independent tray flows (e.g., main vs. modal) with separate configurations.
  • Highly Customizable: Configure animations, backdrops, keyboard handling, safe area, and more, per-stack or per-tray.
  • Directional Swipe-to-Close: Intuitive swipe gestures to dismiss trays (swipe down for bottom trays, swipe up for top trays).
  • Keyboard Awareness: Trays automatically adjust their position when the keyboard appears, ensuring inputs are always visible.
  • Full TypeScript Support: Complete type safety with generics for tray props, ensuring robust and error-free code.
  • Expo & Bare Workflow Compatible: Works seamlessly in any React Native environment.
  • Production-Ready: Built with performance and reliability as top priorities.

πŸš€ Installation

# Using yarn
yarn add react-native-trays

# Or using npm
npm install react-native-trays

Required Peer Dependencies

The library requires the following peer dependencies:

# Install required peer dependencies
npm install react-native-reanimated react-native-safe-area-context react-native-gesture-handler

# Optional: For backdrop blur effect
npm install expo-blur

⚠️ Required Setup

Before using the library, you must properly set up the required dependencies:

  1. React Native Reanimated - Follow the official setup guide.
  2. React Native Safe Area Context - Follow the official setup guide.
  3. React Native Gesture Handler - Follow the official setup guide.

Make sure to wrap your app's entry point with the gestureHandlerRootHOC and SafeAreaProvider, and add the Reanimated Babel plugin to your babel.config.js.


πŸš€ Quick Start

Here's how to get started in just a few steps:

import { TrayProvider, useTrays } from 'react-native-trays';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { View, Text, Button } from 'react-native';

// 1. Define your tray components
const trays = {
  MyTray: {
    component: ({ message }) => (
      <View style={{ padding: 20, backgroundColor: 'white', borderRadius: 16 }}>
        <Text>{message}</Text>
        <Button title="Close" onPress={() => {}} />
      </View>
    ),
  },
};

// 2. Wrap your app with providers
export default function App() {
  return (
    <SafeAreaProvider>
      <TrayProvider trays={trays}>
        <HomeScreen />
      </TrayProvider>
    </SafeAreaProvider>
  );
}

// 3. Use the useTrays hook to open and close trays
function HomeScreen() {
  const { push } = useTrays('main');

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Button
        title="Open Tray"
        onPress={() => push('MyTray', { message: 'Hello from tray!' })}
      />
    </View>
  );
}

πŸ”₯ TypeScript Support

React Native Trays provides full TypeScript support with generics for type-safe tray props.

Defining Tray Props Types

Create a type map for your tray components to ensure type safety:

// Define your tray keys (enum or string literals)
enum TrayEnum {
  Details = 'DetailsTray',
  Form = 'FormTray',
  Settings = 'SettingsTray',
}

// Define props for each tray component
type DetailsTrayProps = {
  id: string;
  title: string;
};

type FormTrayProps = {
  onSubmit: (data: any) => void;
  initialValues?: Record<string, any>;
};

type SettingsTrayProps = {
  userId: string;
  preferences: Record<string, boolean>;
};

// Create a type map for all tray props
type TrayProps = {
  [TrayEnum.Details]: DetailsTrayProps;
  [TrayEnum.Form]: FormTrayProps;
  [TrayEnum.Settings]: SettingsTrayProps;
};

Using Type-Safe Hooks

Get full type safety for your tray props by creating a type map and passing it to the useTrays hook.

import { useTrays } from 'react-native-trays';

// Pass the type map to the useTrays hook
function MyComponent() {
  const { push, pop, replace } = useTrays<TrayProps>('main');

  // TypeScript will now enforce correct props for each tray
  const openDetailsTray = () => {
    push('Details', {
      id: '123',
      title: 'Product Details',
      // TypeScript error: Property 'invalid' does not exist on type 'DetailsTrayProps'
      // invalid: true,
    });
  };

  // Type-safe replace operation
  const updateForm = (newValues: Record<string, any>) => {
    replace(TrayEnum.Form, {
      onSubmit: handleSubmit,
      initialValues: newValues,
    });
  };

  return <Button title="Open Details" onPress={openDetailsTray} />;
}

πŸ“š API Reference

For a complete reference of all types, hooks, and provider props, see API.md.

For a detailed breakdown of all the available configuration options, see the TrayStackConfig documentation.


πŸ₯‘ Advanced Usage

react-native-trays is designed to be highly customizable. You can configure animations, gestures, keyboard handling, and more on a per-stack basis.

Multiple Tray Stacks

Create independent tray flows with separate configurations:

// Configure different stack configurations
const stackConfigs = {
  main: {
    backdropStyles: { backgroundColor: 'rgba(0,0,0,0.5)' },
    trayStyles: { backgroundColor: 'white', borderRadius: 16 },
  },
  modal: {
    backdropStyles: { backgroundColor: 'rgba(0,0,0,0.7)' },
    dismissOnBackdropPress: false,
  },
};

// In your component
function MyComponent() {
  const mainTrays = useTrays<MainTrayProps>('main');
  const modalTrays = useTrays<ModalTrayProps>('modal');

  return (
    <View>
      <Button title="Open Main Tray" onPress={() => mainTrays.push('InfoTray', { ... })} />
      <Button title="Open Modal Tray" onPress={() => modalTrays.push('AlertTray', { ... })} />
    </View>
  );
}

Custom Animations

Use Reanimated's animation builders for custom entry and exit animations:

import {
  SlideInUp,
  SlideOutDown,
  FadeIn,
  FadeOut,
} from 'react-native-reanimated';

<TrayProvider
  trays={trays}
  stackConfigs={{
    main: {
      enteringAnimation: SlideInUp.springify().damping(15),
      exitingAnimation: SlideOutDown.duration(300),
    },
    modal: {
      enteringAnimation: FadeIn.duration(400),
      exitingAnimation: FadeOut.duration(300),
    },
  }}
>
  <App />
</TrayProvider>;

Keyboard Awareness

Trays automatically adjust when the keyboard appears:

<TrayProvider
  trays={trays}
  stackConfigs={{
    main: {
      adjustForKeyboard: true, // default is true
    },
  }}
>
  <App />
</TrayProvider>

See TrayStackConfig for the full list of over 20 configuration options.


πŸ§‘β€πŸ’» Live Demo

Try the library instantly on Expo Snack or check EXPO_SNACK.md for more details.


🀝 Contributing

PRs and issues are welcome! Please see CONTRIBUTING.md and CODE_OF_CONDUCT.md.


πŸ“„ License

MIT Β© Sivantha

About

Family inspired Multi-Stack Tray System for React Native with a React Navigation-like API

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors 4

  •  
  •  
  •  
  •