feat: initial version of native stack (#102)

This commit is contained in:
Michał Osadnik
2019-10-10 23:39:04 +02:00
committed by Satyajit Sahoo
parent 42beb660ca
commit ba3f718ab3
36 changed files with 771 additions and 112 deletions

View File

@@ -0,0 +1,4 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

View File

@@ -0,0 +1,64 @@
# `@react-navigation/native-stack`
Stack navigator for React Native using native primitives for navigation. Uses [`react-native-screens`](https://github.com/kmagiera/react-native-screens) under the hood.
Expo is currently not supported as it includes an older version of `react-native-screens`.
## Installation
Open a Terminal in your project's folder and run,
```sh
yarn add @react-navigation/core @react-navigation/native-stack
```
Now we need to install [`react-native-screens`](https://github.com/kmagiera/react-native-screens).
```sh
yarn add react-native-screens
```
To complete the linking on iOS, make sure you have [Cocoapods](https://cocoapods.org/) installed. Then run:
```sh
cd ios
pod install
cd ..
```
To finalize installation of `react-native-screens` for Android, add the following two lines to dependencies section in `android/app/build.gradle`:
```gradle
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
```
Make sure to enable `react-native-screens`. This needs to be done before our app renders. To do it, add the following code in your entry file (e.g. `App.js`):
```js
import { useScreens } from 'react-native-screens';
useScreens();
```
## Usage
```js
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<Stack.Navigator>
<Stack.Screen name="home" component={Home} options={{ title: 'Home' }} />
<Stack.Screen name="feed" component={Feed} options={{ title: 'Feed' }} />
<Stack.Screen
name="profile"
component={Profile}
options={{ title: 'Profile' }}
/>
</Stack.Navigator>
);
}
```

View File

@@ -0,0 +1,55 @@
{
"name": "@react-navigation/native-stack",
"description": "Native stack navigator component for iOS and Android",
"keywords": [
"react",
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.0",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/react-navigation/navigation-ex.git",
"directory": "packages/native-stack"
},
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/native-stack/src/index.d.ts",
"files": [
"src",
"lib"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.9"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
"del-cli": "^2.0.0",
"react-native-screens": "^2.0.0-alpha.3",
"typescript": "^3.5.3"
},
"peerDependencies": {
"@react-navigation/core": "^5.0.0-alpha.0",
"react": "*",
"react-native": "*",
"react-native-screens": "^2.0.0-alpha.3"
},
"@react-native-community/bob": {
"source": "src",
"output": "lib",
"targets": [
"commonjs",
"module",
"typescript"
]
}
}

View File

@@ -0,0 +1,14 @@
/**
* Navigators
*/
export {
default as createNativeStackNavigator,
} from './navigators/createNativeStackNavigator';
/**
* Types
*/
export {
NativeStackNavigationOptions,
NativeStackNavigationProp,
} from './types';

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { createNavigator, useNavigationBuilder } from '@react-navigation/core';
import {
StackRouter,
StackNavigationState,
StackRouterOptions,
} from '@react-navigation/routers';
import {
screensEnabled,
// eslint-disable-next-line import/no-unresolved
} from 'react-native-screens';
import StackView from '../views/StackView';
import {
NativeStackNavigatorProps,
NativeStackNavigationOptions,
} from '../types';
function NativeStackNavigator(props: NativeStackNavigatorProps) {
if (!screensEnabled()) {
throw new Error(
'Native stack is only available if React Native Screens is enabled.'
);
}
const { initialRouteName, children, screenOptions, ...rest } = props;
const { state, descriptors, navigation } = useNavigationBuilder<
StackNavigationState,
StackRouterOptions,
NativeStackNavigationOptions,
{}
>(StackRouter, {
initialRouteName,
children,
screenOptions,
});
return (
<StackView
state={state}
navigation={navigation}
descriptors={descriptors}
{...rest}
/>
);
}
export default createNavigator<
NativeStackNavigationOptions,
typeof NativeStackNavigator
>(NativeStackNavigator);

View File

@@ -0,0 +1,160 @@
import * as React from 'react';
import { StyleProp, ViewStyle } from 'react-native';
import {
DefaultNavigatorOptions,
Descriptor,
NavigationHelpers,
NavigationProp,
ParamListBase,
} from '@react-navigation/core';
import {
StackNavigationState,
StackRouterOptions,
} from '@react-navigation/routers';
export type NativeStackNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = NavigationProp<
ParamList,
RouteName,
StackNavigationState,
NativeStackNavigationOptions,
{}
> & {
/**
* Push a new screen onto the stack.
*
* @param name Name of the route for the tab.
* @param [params] Params object for the route.
*/
push<RouteName extends keyof ParamList>(
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
/**
* Pop a screen from the stack.
*/
pop(count?: number): void;
/**
* Pop to the first route in the stack, dismissing all other screens.
*/
popToTop(): void;
};
export type NativeStackNavigationHelpers = NavigationHelpers<ParamListBase, {}>;
export type NativeStackNavigationConfig = {};
export type NativeStackNavigationOptions = {
/**
* String that can be displayed in the header as a fallback for `headerTitle`.
*/
title?: string;
/**
* String to display in the header as title. Defaults to scene `title`.
*/
headerTitle?: string;
/**
* Title to display in the back button.
* Only supported on iOS.
*
* @platform ios
*/
headerBackTitle?: string;
/**
* Whether to show the header.
*/
headerShown?: boolean;
/**
* Style object for header title. Supported properties:
* - backgroundColor
*/
headerStyle?: {
backgroundColor?: string;
};
/**
* Indicating whether the navigation bar is translucent.
* Only supported on iOS.
*
* @platform ios
*/
headerTranslucent?: boolean;
/**
* Set native property to prefer large title header (like in iOS seeting).
* Only supported on iOS.
*
* @platform ios
*/
headerLargeTitle?: boolean;
/**
* Function which returns a React Element to display on the right side of the header.
*/
headerRight?: () => React.ReactNode;
/**
* Tint color for the header.
*/
headerTintColor?: string;
/**
* Style object for header title. Supported properties:
* - fontFamily
* - fontSize
* - color
*/
headerTitleStyle?: {
fontFamily?: string;
fontSize?: number;
color?: string;
};
/**
* Style object for header back title. Supported properties:
* - fontFamily
* - fontSize
*
* Only supported on iOS.
*
* @platform ios
*/
headerBackTitleStyle?: {
fontFamily?: string;
fontSize?: number;
};
/**
* Whether you can use gestures to dismiss this screen. Defaults to `true`.
* Only supported on iOS.
*
* @platform ios
*/
gestureEnabled?: boolean;
/**
* Style object for the scene content.
*/
contentStyle?: StyleProp<ViewStyle>;
/**
* How should the screen be presented.
* Only supported on iOS.
*
* @platform ios
*/
presentation?: 'modal' | 'transparentModal' | 'push';
};
export type NativeStackNavigatorProps = DefaultNavigatorOptions<
NativeStackNavigationOptions
> &
StackRouterOptions &
NativeStackNavigationConfig;
export type NativeStackDescriptor = Descriptor<
ParamListBase,
string,
StackNavigationState,
NativeStackNavigationOptions
>;
export type NativeStackDescriptorMap = {
[key: string]: NativeStackDescriptor;
};

View File

@@ -0,0 +1,64 @@
import * as React from 'react';
import {
// @ts-ignore
ScreenStackHeaderConfig,
// @ts-ignore
ScreenStackHeaderRightView,
// eslint-disable-next-line import/no-unresolved
} from 'react-native-screens';
import { Route } from '@react-navigation/core';
import { NativeStackNavigationOptions } from '../types';
type Props = NativeStackNavigationOptions & {
route: Route<string>;
};
export default function HeaderConfig(props: Props) {
const {
route,
title,
headerRight,
headerTitle,
headerBackTitle,
headerTintColor,
gestureEnabled,
headerLargeTitle,
headerTranslucent,
headerStyle = {},
headerTitleStyle = {},
headerBackTitleStyle = {},
headerShown,
} = props;
return (
<ScreenStackHeaderConfig
hidden={headerShown === false}
translucent={headerTranslucent === true}
title={
headerTitle !== undefined
? headerTitle
: title !== undefined
? title
: route.name
}
titleFontFamily={headerTitleStyle.fontFamily}
titleFontSize={headerTitleStyle.fontFamily}
titleColor={
headerTitleStyle.color !== undefined
? headerTitleStyle.color
: headerTintColor
}
backTitle={headerBackTitle}
backTitleFontFamily={headerBackTitleStyle.fontFamily}
backTitleFontSize={headerBackTitleStyle.fontSize}
color={headerTintColor}
gestureEnabled={gestureEnabled === undefined ? true : gestureEnabled}
largeTitle={headerLargeTitle}
backgroundColor={headerStyle.backgroundColor}
>
{headerRight !== undefined ? (
<ScreenStackHeaderRightView>{headerRight()}</ScreenStackHeaderRightView>
) : null}
</ScreenStackHeaderConfig>
);
}

View File

@@ -0,0 +1,62 @@
import * as React from 'react';
import { View, StyleSheet, Platform } from 'react-native';
import { StackNavigationState, StackActions } from '@react-navigation/routers';
import {
// @ts-ignore
ScreenStack,
Screen,
// eslint-disable-next-line import/no-unresolved
} from 'react-native-screens';
import HeaderConfig from './HeaderConfig';
import {
NativeStackNavigationHelpers,
NativeStackDescriptorMap,
} from '../types';
type Props = {
state: StackNavigationState;
navigation: NativeStackNavigationHelpers;
descriptors: NativeStackDescriptorMap;
};
export default function StackView({ state, navigation, descriptors }: Props) {
return (
<ScreenStack style={styles.scenes}>
{state.routes.map(route => {
const { options, render: renderScene } = descriptors[route.key];
const { presentation = 'push', contentStyle } = options;
return (
// @ts-ignore
<Screen
key={route.key}
style={StyleSheet.absoluteFill}
stackPresentation={presentation}
onDismissed={() => {
navigation.dispatch({
...StackActions.pop(),
source: route.key,
target: state.key,
});
}}
>
<HeaderConfig {...options} route={route} />
<View style={[styles.content, contentStyle]}>{renderScene()}</View>
</Screen>
);
})}
</ScreenStack>
);
}
const styles = StyleSheet.create({
content: {
flex: 1,
backgroundColor: '#eee',
marginTop: Platform.OS === 'android' ? 56 : 0,
},
scenes: {
flex: 1,
},
});

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig"
}