Git Product home page Git Product logo

rn-navio's Introduction

🧭 Navio

Expo Snack Expo Compatible

Navio is a navigation library for React Native (compatible with Expo) built on top of React Navigation. The main goal is to improve DX by building the app layout in one place and using the power of TypeScript to provide route names autocompletion and other features.

Navio lets you easily create different kinds of apps: bottom tabs-based, simple single-screen, and apps with drawer layouts. It takes care of all boilerplate code configuration for Navigators, Screens, Stacks, Tabs, and Drawers under the hood, so you can focus on developing your app functionality.

If Navio helped you in any way, don't hesitate to ⭐️ the repo!

☣️ Navio is still a young library and may have breaking changes in the future.

Quickstart

yarn add rn-navio
React Navigation dependencies

As Navio is built on top of React Navigation, you will need to have the following libraries installed:

yarn add @react-navigation/stack @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs @react-navigation/drawer

For more information, please check the installation steps.

Playground

Expo Starter

You can bootstrap a new project with Navio (from expo-starter):

npx cli-rn new app

Expo Snack

Play with the library in the Expo Snack.

Examples

2 screens

Simple app with 2 screens.

import {Navio} from 'rn-navio';
import {Home, Settings} from '@app/screens';

const navio = Navio.build({
  screens: {Home, Settings},
  stacks: {
    Main: ['Home', 'Settings'],
  },
  root: 'Main',
});

export default () => <navio.App />;

Tabs

Tab-based app with 3 tabs.

Show code
import {Navio} from 'rn-navio';
import {Home, Example, Playground, Settings} from '@app/screens';

const navio = Navio.build({
  screens: {Home, Example, Playground, Settings},
  stacks: {
    HomeStack: ['Home', 'Example'],
    PlaygroundStack: ['Playground'],
    SettingsStack: ['Settings'],
  },
  tabs: {
    AppTabs: {
      content: {
        HomeTab: {
          stack: 'HomeStack',
          options: () => ({
            title: 'Home',
          }),
        },
        PlaygroundTab: {
          stack: 'PlaygroundStack',
          options: () => ({
            title: 'Playground',
          }),
        },
        SettingsTab: {
          stack: 'SettingsStack',
          options: () => ({
            title: 'Settings',
          }),
        },
      },
    },
  },
  root: 'AppTabs',
});

export default () => <navio.App />;

Drawer

Simple app with drawer and 3 stacks.

Show code
import {Navio} from 'rn-navio';
import {Home, Example, Playground, Settings} from '@app/screens';

const navio = Navio.build({
  screens: {Home, Example, Playground, Settings},
  stacks: {
    HomeStack: ['Home', 'Example'],
  },
  drawers: {
    AppDrawer: {
      content: {
        Main: 'HomeStack',
        Playground: ['Playground'],
        Settings: ['Settings'],
      },
    },
  },
  root: 'AppDrawer',
});

export default () => <navio.App />;

Auth flow

There are two ways of handling Auth flow with Navio: Static and Dynamic.

Static - Show code
import {Navio} from 'rn-navio';
import {Main, SignIn, SignUp} from '@app/screens';

const navio = Navio.build({
  screens: {Main, SignIn, SignUp},
  stacks: {
    MainApp: ['Main'],
    Auth: ['SignIn', 'SignUp'],
  },
  root: 'MainApp',
});

// Let's say you show `MainApp` in the beginning with limited functionality
// and have some screen with "Sign in" button. After pressing "Sign in",
// you can show `Auth` flow.
const Screen = () => {
  const {navio} = useServices();

  const onSignInPressed = () => {
    navio.setRoot('stacks', 'Auth');
  };

  return <>{Content}</>;
};

// After `Auth` flow is successfully finished, you can show `MainApp`.
navio.setRoot('stacks', 'MainApp');

export default () => <navio.App />;
Dynamic - Show code
import {Navio} from 'rn-navio';
import {Main, SignIn, SignUp} from '@app/screens';

const navio = Navio.build({
  screens: {Main, SignIn, SignUp},
  stacks: {
    MainApp: ['Main'],
    Auth: ['SignIn', 'SignUp'],
  },
  root: 'MainApp',
});

// Let's say you want to react on changes from auth provider (stores, hook, etc.)
// and show root app depending on that value.
export default (): JSX.Element => {
  const {authData} = useAuth();
  const isLoggedIn = !!authData;

  return (
    <SomeProviders>
      <navio.App initialRouteName={isLoggedIn ? 'MainApp' : 'Auth'} />
    </SomeProviders>
  );
};

Hide tabs

Hide tabs on a specific screen.

As React Navigation suggests in the docs, we need to define a stack that we want to "push over" tabs.

Show code
import {Navio} from 'rn-navio';
import {Home, Product, Playground, Settings} from '@app/screens';

const navio = Navio.build({
  screens: {
    Home,
    Settings,

    ProductPage: {
      component: Product,
      options: {
        headerShown: false,
      },
    },
  },
  stacks: {
    ProductPageStack: {
      screens: ['ProductPage'],
      containerOptions: {
        headerShown: true,
        title: 'Product page',
      },
    },
  },
  tabs: {
    AppTabs: {
      content: {
        Home: ['Home'],
        Settings: ['Settings'],
      },
    },
  },
  root: 'AppTabs',
});

// Now you can push `ProductPageStack` from tabs and it will be without tabs.
navio.stacks.push('ProductPageStack');

Drawer with tabs

Opens app with main drawer and tabs inside one of the drawer content. It can be used to build Twitter like app's layout.

Show code
import {Navio} from 'rn-navio';
import {Home, Settings} from '@app/screens';

const navio = Navio.build({
  screens: {Home, Settings},
  tabs: {
    SomeTabs: {
      content: {
        Home: {
          stack: ['Home'],
        },
        Settings: {
          stack: ['Settings'],
        },
      },
    },
  },
  drawers: {
    MainDrawer: {
      content: {
        Main: {
          stack: ['Home'],
        },
        Tabs: {
          tabs: 'SomeTabs',
        },
      },
    },
  },
  root: 'MainDrawer',
});

export default () => <navio.App />;

Tabs with drawer

Opens 2 tabs app with a drawer inside one of the tab. It can be used for showing product categories or similar.

Show code
import {Navio} from 'rn-navio';
import {Home, PlaygroundFlashList, PlaygroundExpoImage, Settings} from '@app/screens';

const navio = Navio.build({
  screens: {Home, Settings},
  tabs: {
    SomeTabs: {
      content: {
        Home: {
          stack: ['Home'],
        },
        Settings: {
          drawer: 'DrawerForTabs',
        },
      },
    },
  },
  drawers: {
    DrawerForTabs: {
      content: {
        FlashList: {
          stack: ['PlaygroundFlashList'],
          options: {
            title: 'Flash List',
            drawerPosition: 'right',
          },
        },
        ExpoImage: {
          stack: ['PlaygroundExpoImage'],
          options: {
            title: 'Expo Image',
            drawerPosition: 'right',
          },
        },
      },
    },
  },
  root: 'MainDrawer',
});

export default () => <navio.App />;

Drawer with custom content

Opens app with main drawer with custom content.

Show code
import {Navio} from 'rn-navio';
import {Home, Settings} from '@app/screens';

const navio = Navio.build({
  screens: {Home, Settings},
  drawers: {
    MainDrawer: {
      content: {
        Main: {
          stack: ['Home'],
        },
        Settings: {
          stack: ['Settings'],
        },
      },

      navigatorProps: {
        drawerContent: (props: any) => (
          <DrawerContentScrollView {...props}>
            <DrawerItemList {...props} />

            <View style={{height: 1, backgroundColor: 'black'}} />
            <DrawerItem label="Close drawer" onPress={() => navio.drawers.close()} />
          </DrawerContentScrollView>
        ),
      },
    },
  },
  root: 'MainDrawer',
});

export default () => <navio.App />;

Advanced example with all available props can be found at expo-starter

Layout

Navio requires only screens prop to build an app layout. stacks, tabs, drawers, modals, root, hooks and defaultOptions are optional and used for more advanced app layouts.

Screens

These are main bricks of your app with Navio. You can reuse them for any stack you want to build.

A screen can be defined by passing a plain React component. If you'd like to pass some options which describe the screen, then you can pass an object as well.

Definition
type Screens = Record<string, Screen>;

type Screen =
  | NavioScreen<Props>
  | {
      component: NavioScreen<Props>;
      options?: BaseOptions<NativeStackNavigationOptions>;
    };
Example
import {ScreenOne} from './screens/one.tsx';

const navio = Navio.build({
  screens: {
    One: ScreenOne,
    Two: () => {
      return <></>;
    }
    Three: {
      component: ScreenOne,
      options: (props) => ({
        title: 'ThreeOne'
      })
    }
  },
});

Stacks

Stacks are built using Screens that have been defined before. IDEs should help with autocompletion for better DX.

A stack can be defined by passing an array of Screens. If you'd like to pass some options down to stack navigator, then you can pass an object.

Definition
type Stacks = Record<string, Stack>;

type Stack =
  | ScreenName[]
  | {
      screens: ScreenName[];
      containerOptions?: ContainerOptions;
      navigatorProps?: NativeStackNavigatorProps;
    };
Example
const navio = Navio.build({
  stacks: {
    MainStack: ['One', 'Two'],
    ExampleStack: {
      screens: ['Three'],
      navigatorProps: {
        screenListeners: {
          focus: () => {},
        },
      },
    },
  },
});

Tabs

Tabs are built using Screens, Stacks, and Drawers that have been defined before.

Tabs can be defined by passing an object with content and, optionally, props for navigator.

Content can take as a value one of Stacks, Drawers, array of Screens, or an object that describes stack and options for bottom tab (describing title, icon, etc.).

Definition
type Tabs = Record<string, Tab>;

type Tab = {
  content: Record<string, ContentValue>;
  containerOptions?: ContainerOptions;
  navigatorProps?: any;
};

type ContentValue = {
  stack?: StackDefinition;
  drawer?: DrawerDefinition;
  options?: BaseOptions<BottomTabNavigationOptions>;
};
Example
const navio = Navio.build({
  tabs: {
    AppTabs: {
      content: {
        MainTab: {
          stack: ['One', 'Two'],
          options: () => ({
            title: 'Main',
          }),
        },
        ExampleTab: {
          stack: 'ExampleStack',
          options: () => ({
            title: 'Example',
          }),
        },
      },
    },
  },
});

Drawers

Drawers are built using Screens, Stacks, and Tabs that have been defined before.

Drawers can be defined by passing an object with content and, optionally, props for navigator.

Content can take as a value one of Stacks, Tabs, array of Screens, or an object that describes stack and options for bottom tab (describing title, icon, etc.).

Definition
type Drawers = Record<string, Drawer>;

type Drawer = {
  content: Record<string, ContentValue>;
  containerOptions?: ContainerOptions;
  navigatorProps?: any;
};

type DrawerContentValue = {
  stack?: StackDefinition;
  tabs?: TabsDefinition;
  options?: BaseOptions<DrawerNavigationOptions>;
};
Example
const navio = Navio.build({
  drawers: {
    MainDrawer: {
      content: {
        Main: 'MainStack',
        Example: 'ExampleStack',
        Playground: ['One', 'Two', 'Three'],
      },
    },
  },
});

Modals

Modals are built using Screens and Stacks that have been defined before. You can show/present them at any point of time while using the app.

A modal can be defined by passing an array of Screens or a name of Stacks.

Definition
type Modals = Record<string, Modal>;

type Modal = StackDefinition;
Example
const navio = Navio.build({
  modals: {
    ExampleModal: 'ExampleStack',
  },
});

Root

This is a root name of the app. It can be one of Stacks, Tabs or Drawers.

You can change the root of the app later by navio.setRoot('drawers', 'MainDrawer') or by changing initialRouteName of <navio.Root /> component.

Definition
type RootName = StacksName | TabsName | DrawersName;
Example
const navio = Navio.build({
  root: 'AppTabs',
});

Hooks

List of hooks that will be run on each generated Stacks, Tabs or Drawers navigators. Useful for dark mode or language change.

Definition
type Hooks = Function[];
Example
const navio = Navio.build({
  hooks: [useAppearance],
});

Default options

Default options that will be applied per each Stacks's, Tabs's, Drawer's, or Modal's screens and containers generated within the app.

Note All containers and Tabs's and Drawer's screens options have headerShown: false by default (in order to hide unnecessary navigation headers). You can always change them which might be useful if you want to have a native < Back button when hiding tabs (pushing new Stack).

Definition
type DefaultOptions = {
  stacks?: {
    screen?: StackScreenOptions;
    container?: ContainerOptions;
  };
  tabs?: {
    screen?: TabScreenOptions;
    container?: ContainerOptions;
  };
  drawers?: {
    screen?: DrawerScreenOptions;
    container?: ContainerOptions;
  };
  modals?: {
    container?: ContainerOptions;
  };
};
Example
const navio = Navio.build({
  defaultOptions: {
    stacks: {
      screen: {
        headerShadowVisible: false,
        headerTintColor: Colors.primary,
      },
      container: {
        headerShown: true,
      },
    },
    tabs: {
      screen: tabDefaultOptions,
    },
    drawer: {
      screen: drawerDefaultOptions,
    },
  },
});

App

Navio generates root component for the app after the layout is defined. It can be used to directly pass it to registerRootComponent() or to wrap with extra providers or add more logic before the app's start up.

const navio = Navio.build({...});

export default () => <navio.App />

You can change the root of the app by navio.setRoot('drawers', 'MainDrawer') or by changing initialRouteName of <navio.App /> component.

Navigation

Navio provides a colleciton of actions to perform navigation within the app.

Common

navio.N

Current navigation instance (from React Navigation). You can perform any of these actions.

.push(name, params?)

Adds a route on top of the stack and navigates forward to it.

.goBack()

Allows to go back to the previous route in history.

.setParams(name, params)

Allows to update params for a certain route.

.setRoot(as, rootName)

Sets a new app root. It can be used to switch between Stacks, Tabs, and Drawers.

Stacks

Stacks-related actions.

.stacks.push(name)

Adds a route on top of the stack and navigates forward to it. It can hide tab bar.

.stacks.pop(count?)

Takes you back to a previous screen in the stack.

.stacks.popToTop()

Takes you back to the first screen in the stack, dismissing all the others.

.stacks.setRoot(name)

Sets a new app root from stacks.

Tabs

Tabs-related actions.

.tabs.jumpTo(name)

Can be used to jump to an existing route in the tab navigator.

.tabs.updateOptions(name, options)

Updates options for a given tab. Can be used to change badge count.

.tabs.setRoot(name)

Sets a new app root from tabs.

Drawers

Drawers-related actions.

.drawers.open()

Can be used to open the drawer pane.

.drawers.close()

Can be used to close the drawer pane.

.drawers.toggle()

Can be used to open the drawer pane if closed, or close if open.

.drawers.jumpTo(name)

Can be used to jump to an existing route in the drawer navigator.

.drawers.updateOptions(name, options)

Updates options for a given drawer menu content. Can be used to change its title.

.drawers.setRoot(name)

Sets a new app root from drawers.

Modals

Modals-related actions.

.modals.show()

Can be used to show an existing modal.

TypeScript

Navio is developed in TypeScript from the beginning. TypeScript helps with autocompletion and to achieve better DX. There are still some issues (could be found at index.tsx). So if you are a TypeScript expert, please open an issue for help.

Autocompletion

In order to use full power of TS autocompletion, you'll need to define all layout components (could be just empty object). I don't know how to fix that at the moment.

const navio = Navio.build({
  screens: {Home, Settings},
  stacks: {MainStack: ['Main', 'Settings']},
  root: '...', // 🚫 won't help w/ autocompletion
});

const navio = Navio.build({
  screens: {Home, Settings},
  stacks: {MainStack: ['Main', 'Settings']},
  drawers: {},
  tabs: {},
  root: '...', // ✅ will help w/ autocompletion
});

Navio + React Navigation

Navio can be used among with React Navigation. All hooks, actions, deep linking, and other stuff from React Navigation should work fine with Navio.

If you've found any diffilculties with using Navio and React Navigation, feel free to open an issue for a discussion.

Enhancements

There are still some things I would like to add to the library:

  • .updateOptions() for specific tab and drawer.
  • Tabs can be placed inside Drawer and vice versa.
  • Make deeplinking easier by providing linking prop to screens.
  • Improve docs. Deeplinking section, etc. Based on this issue.
  • Make Navio universal by adding RNN and rnn-screens.
  • Extend Navio funtionality and app layout.
  • Easy integration of Navio with React Navigation (eg. navio.Stack())
  • TypeScript issues @ index.tsx file.

Feel free to open an issue for any suggestions.

License

This project is MIT licensed

rn-navio's People

Contributors

kanzitelli avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.