Compare commits
9 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
151055cf5a | ||
|
|
52172453df | ||
|
|
7bc385e4f3 | ||
|
|
6ac4d40140 | ||
|
|
dbe961ba5b | ||
|
|
05d4e4d3be | ||
|
|
48b2e77730 | ||
|
|
e08c91ff0a | ||
|
|
5bd682f0bf |
@@ -68,14 +68,9 @@ module.exports = {
|
||||
enhanceMiddleware: (middleware) => {
|
||||
return (req, res, next) => {
|
||||
// When an asset is imported outside the project root, it has wrong path on Android
|
||||
// This happens for the back button in stack, so we fix the path to correct one
|
||||
const assets = '/packages/stack/src/views/assets';
|
||||
|
||||
if (req.url.startsWith(assets)) {
|
||||
req.url = req.url.replace(
|
||||
assets,
|
||||
'/assets/../packages/stack/src/views/assets'
|
||||
);
|
||||
// So we fix the path to correct one
|
||||
if (/\/packages\/.+\.png\?.+$/.test(req.url)) {
|
||||
req.url = `/assets/../${req.url}`;
|
||||
}
|
||||
|
||||
return middleware(req, res, next);
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
Provider as PaperProvider,
|
||||
DefaultTheme as PaperLightTheme,
|
||||
DarkTheme as PaperDarkTheme,
|
||||
Appbar,
|
||||
List,
|
||||
Divider,
|
||||
Text,
|
||||
@@ -28,10 +27,7 @@ import {
|
||||
PathConfigMap,
|
||||
NavigationContainerRef,
|
||||
} from '@react-navigation/native';
|
||||
import {
|
||||
createDrawerNavigator,
|
||||
DrawerScreenProps,
|
||||
} from '@react-navigation/drawer';
|
||||
import { createDrawerNavigator } from '@react-navigation/drawer';
|
||||
import {
|
||||
createStackNavigator,
|
||||
StackScreenProps,
|
||||
@@ -65,8 +61,7 @@ if (Platform.OS !== 'web') {
|
||||
enableScreens();
|
||||
|
||||
type RootDrawerParamList = {
|
||||
Root: undefined;
|
||||
Another: undefined;
|
||||
Examples: undefined;
|
||||
};
|
||||
|
||||
const SCREENS = {
|
||||
@@ -231,50 +226,49 @@ export default function App() {
|
||||
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
|
||||
prefixes: LinkingPrefixes,
|
||||
config: {
|
||||
screens: {
|
||||
Root: {
|
||||
path: '',
|
||||
initialRouteName: 'Home',
|
||||
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
|
||||
(acc, name) => {
|
||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||
const path = name
|
||||
.replace(/([A-Z]+)/g, '-$1')
|
||||
.replace(/^-/, '')
|
||||
.toLowerCase();
|
||||
initialRouteName: 'Home',
|
||||
screens: Object.keys(SCREENS).reduce<PathConfigMap>(
|
||||
(acc, name) => {
|
||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||
const path = name
|
||||
.replace(/([A-Z]+)/g, '-$1')
|
||||
.replace(/^-/, '')
|
||||
.toLowerCase();
|
||||
|
||||
acc[name] = {
|
||||
path,
|
||||
screens: {
|
||||
Article: {
|
||||
path: 'article/:author?',
|
||||
parse: {
|
||||
author: (author) =>
|
||||
author.charAt(0).toUpperCase() +
|
||||
author.slice(1).replace(/-/g, ' '),
|
||||
},
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.toLowerCase().replace(/\s/g, '-'),
|
||||
},
|
||||
},
|
||||
Albums: 'music',
|
||||
Chat: 'chat',
|
||||
Contacts: 'people',
|
||||
NewsFeed: 'feed',
|
||||
Dialog: 'dialog',
|
||||
acc[name] = {
|
||||
path,
|
||||
screens: {
|
||||
Article: {
|
||||
path: 'article/:author?',
|
||||
parse: {
|
||||
author: (author) =>
|
||||
author.charAt(0).toUpperCase() +
|
||||
author.slice(1).replace(/-/g, ' '),
|
||||
},
|
||||
};
|
||||
|
||||
return acc;
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.toLowerCase().replace(/\s/g, '-'),
|
||||
},
|
||||
},
|
||||
Albums: 'music',
|
||||
Chat: 'chat',
|
||||
Contacts: 'people',
|
||||
NewsFeed: 'feed',
|
||||
Dialog: 'dialog',
|
||||
},
|
||||
{
|
||||
Home: '',
|
||||
NotFound: '*',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
return acc;
|
||||
},
|
||||
},
|
||||
{
|
||||
Home: {
|
||||
screens: {
|
||||
Examples: '',
|
||||
},
|
||||
},
|
||||
NotFound: '*',
|
||||
}
|
||||
),
|
||||
},
|
||||
}}
|
||||
fallback={<Text>Loading…</Text>}
|
||||
@@ -283,35 +277,28 @@ export default function App() {
|
||||
`${options?.title ?? route?.name} - React Navigation Example`,
|
||||
}}
|
||||
>
|
||||
<Drawer.Navigator drawerType={isLargeScreen ? 'permanent' : undefined}>
|
||||
<Drawer.Screen
|
||||
name="Root"
|
||||
<Stack.Navigator
|
||||
screenOptions={{
|
||||
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="Home"
|
||||
options={{
|
||||
title: 'Examples',
|
||||
drawerIcon: ({ size, color }) => (
|
||||
<MaterialIcons size={size} color={color} name="folder" />
|
||||
),
|
||||
headerShown: false,
|
||||
}}
|
||||
>
|
||||
{({ navigation }: DrawerScreenProps<RootDrawerParamList>) => (
|
||||
<Stack.Navigator
|
||||
screenOptions={{
|
||||
headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
|
||||
}}
|
||||
{() => (
|
||||
<Drawer.Navigator
|
||||
drawerType={isLargeScreen ? 'permanent' : undefined}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="Home"
|
||||
<Drawer.Screen
|
||||
name="Examples"
|
||||
options={{
|
||||
title: 'Examples',
|
||||
headerLeft: isLargeScreen
|
||||
? undefined
|
||||
: () => (
|
||||
<Appbar.Action
|
||||
color={theme.colors.text}
|
||||
icon="menu"
|
||||
onPress={() => navigation.toggleDrawer()}
|
||||
/>
|
||||
),
|
||||
drawerIcon: ({ size, color }) => (
|
||||
<MaterialIcons size={size} color={color} name="folder" />
|
||||
),
|
||||
}}
|
||||
>
|
||||
{({ navigation }: StackScreenProps<RootStackParamList>) => (
|
||||
@@ -352,26 +339,24 @@ export default function App() {
|
||||
)}
|
||||
</ScrollView>
|
||||
)}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name="NotFound"
|
||||
component={NotFound}
|
||||
options={{ title: 'Oops!' }}
|
||||
/>
|
||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||
(name) => (
|
||||
<Stack.Screen
|
||||
key={name}
|
||||
name={name}
|
||||
getComponent={() => SCREENS[name].component}
|
||||
options={{ title: SCREENS[name].title }}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Stack.Navigator>
|
||||
</Drawer.Screen>
|
||||
</Drawer.Navigator>
|
||||
)}
|
||||
</Drawer.Screen>
|
||||
</Drawer.Navigator>
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name="NotFound"
|
||||
component={NotFound}
|
||||
options={{ title: 'Oops!' }}
|
||||
/>
|
||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map((name) => (
|
||||
<Stack.Screen
|
||||
key={name}
|
||||
name={name}
|
||||
getComponent={() => SCREENS[name].component}
|
||||
options={{ title: SCREENS[name].title }}
|
||||
/>
|
||||
))}
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
</PaperProvider>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.11.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.7...@react-navigation/bottom-tabs@5.11.0) (2020-11-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a hook to get bottom tab bar height ([e08c91f](https://github.com/react-navigation/react-navigation/commit/e08c91ff0a3df13dc6e6096a3e95f60722e6946b)), closes [#8037](https://github.com/react-navigation/react-navigation/issues/8037) [#8536](https://github.com/react-navigation/react-navigation/issues/8536)
|
||||
* add a tabBarBadgeStyle option to customize the badge ([6ac4d40](https://github.com/react-navigation/react-navigation/commit/6ac4d40140189a29d857c4d1203bced6929f7baf))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.10.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@5.10.6...@react-navigation/bottom-tabs@5.10.7) (2020-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/bottom-tabs",
|
||||
"description": "Bottom tab navigator following iOS design guidelines",
|
||||
"version": "5.10.7",
|
||||
"version": "5.11.0",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -41,7 +41,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/native": "^5.8.7",
|
||||
"@react-navigation/native": "^5.8.8",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.53",
|
||||
|
||||
@@ -9,6 +9,13 @@ export { default as createBottomTabNavigator } from './navigators/createBottomTa
|
||||
export { default as BottomTabView } from './views/BottomTabView';
|
||||
export { default as BottomTabBar } from './views/BottomTabBar';
|
||||
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
export { default as BottomTabBarHeightContext } from './utils/BottomTabBarHeightContext';
|
||||
|
||||
export { default as useBottomTabBarHeight } from './utils/useBottomTabBarHeight';
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
|
||||
@@ -109,6 +109,12 @@ export type BottomTabNavigationOptions = {
|
||||
*/
|
||||
tabBarBadge?: number | string;
|
||||
|
||||
/**
|
||||
* Custom style for the tab bar badge.
|
||||
* You can specify a background color or text color here.
|
||||
*/
|
||||
tabBarBadgeStyle?: StyleProp<TextStyle>;
|
||||
|
||||
/**
|
||||
* Accessibility label for the tab button. This is read by the screen reader when the user taps the tab.
|
||||
* It's recommended to set this if you don't have a label for the tab.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default React.createContext<((height: number) => void) | undefined>(
|
||||
undefined
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default React.createContext<number | undefined>(undefined);
|
||||
14
packages/bottom-tabs/src/utils/useBottomTabBarHeight.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import BottomTabBarHeightContext from './BottomTabBarHeightContext';
|
||||
|
||||
export default function useFloatingBottomTabBarHeight() {
|
||||
const height = React.useContext(BottomTabBarHeightContext);
|
||||
|
||||
if (height === undefined) {
|
||||
throw new Error(
|
||||
"Couldn't find the bottom tab bar height. Are you inside a screen in Bottom Tab Navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
@@ -5,20 +5,25 @@ import {
|
||||
StyleSheet,
|
||||
Platform,
|
||||
LayoutChangeEvent,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
import {
|
||||
NavigationContext,
|
||||
NavigationRouteContext,
|
||||
TabNavigationState,
|
||||
ParamListBase,
|
||||
CommonActions,
|
||||
useTheme,
|
||||
useLinkBuilder,
|
||||
} from '@react-navigation/native';
|
||||
import { useSafeArea } from 'react-native-safe-area-context';
|
||||
import { useSafeArea, EdgeInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import BottomTabItem from './BottomTabItem';
|
||||
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
||||
import useWindowDimensions from '../utils/useWindowDimensions';
|
||||
import useIsKeyboardShown from '../utils/useIsKeyboardShown';
|
||||
import type { BottomTabBarProps } from '../types';
|
||||
import type { BottomTabBarProps, LabelPosition } from '../types';
|
||||
|
||||
type Props = BottomTabBarProps & {
|
||||
activeTintColor?: string;
|
||||
@@ -31,13 +36,93 @@ const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
|
||||
|
||||
const useNativeDriver = Platform.OS !== 'web';
|
||||
|
||||
type Options = {
|
||||
state: TabNavigationState<ParamListBase>;
|
||||
layout: { height: number; width: number };
|
||||
dimensions: { height: number; width: number };
|
||||
tabStyle: StyleProp<ViewStyle>;
|
||||
labelPosition: LabelPosition | undefined;
|
||||
adaptive: boolean | undefined;
|
||||
};
|
||||
|
||||
const shouldUseHorizontalLabels = ({
|
||||
state,
|
||||
layout,
|
||||
dimensions,
|
||||
adaptive = true,
|
||||
labelPosition,
|
||||
tabStyle,
|
||||
}: Options) => {
|
||||
if (labelPosition) {
|
||||
return labelPosition === 'beside-icon';
|
||||
}
|
||||
|
||||
if (!adaptive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (layout.width >= 768) {
|
||||
// Screen size matches a tablet
|
||||
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
|
||||
|
||||
const flattenedStyle = StyleSheet.flatten(tabStyle);
|
||||
|
||||
if (flattenedStyle) {
|
||||
if (typeof flattenedStyle.width === 'number') {
|
||||
maxTabItemWidth = flattenedStyle.width;
|
||||
} else if (typeof flattenedStyle.maxWidth === 'number') {
|
||||
maxTabItemWidth = flattenedStyle.maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return state.routes.length * maxTabItemWidth <= layout.width;
|
||||
} else {
|
||||
return dimensions.width > dimensions.height;
|
||||
}
|
||||
};
|
||||
|
||||
const getPaddingBottom = (insets: EdgeInsets) =>
|
||||
Math.max(insets.bottom - Platform.select({ ios: 4, default: 0 }), 0);
|
||||
|
||||
export const getTabBarHeight = ({
|
||||
dimensions,
|
||||
insets,
|
||||
style,
|
||||
...rest
|
||||
}: Options & {
|
||||
insets: EdgeInsets;
|
||||
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
}) => {
|
||||
// @ts-ignore
|
||||
const customHeight = StyleSheet.flatten(style)?.height;
|
||||
|
||||
if (typeof customHeight === 'number') {
|
||||
return customHeight;
|
||||
}
|
||||
|
||||
const isLandscape = dimensions.width > dimensions.height;
|
||||
const horizontalLabels = shouldUseHorizontalLabels({ dimensions, ...rest });
|
||||
const paddingBottom = getPaddingBottom(insets);
|
||||
|
||||
if (
|
||||
Platform.OS === 'ios' &&
|
||||
!Platform.isPad &&
|
||||
isLandscape &&
|
||||
horizontalLabels
|
||||
) {
|
||||
return COMPACT_TABBAR_HEIGHT + paddingBottom;
|
||||
}
|
||||
|
||||
return DEFAULT_TABBAR_HEIGHT + paddingBottom;
|
||||
};
|
||||
|
||||
export default function BottomTabBar({
|
||||
state,
|
||||
navigation,
|
||||
descriptors,
|
||||
activeBackgroundColor,
|
||||
activeTintColor,
|
||||
adaptive = true,
|
||||
adaptive,
|
||||
allowFontScaling,
|
||||
inactiveBackgroundColor,
|
||||
inactiveTintColor,
|
||||
@@ -60,6 +145,8 @@ export default function BottomTabBar({
|
||||
const dimensions = useWindowDimensions();
|
||||
const isKeyboardShown = useIsKeyboardShown();
|
||||
|
||||
const onHeightChange = React.useContext(BottomTabBarHeightCallbackContext);
|
||||
|
||||
const shouldShowTabBar =
|
||||
focusedOptions.tabBarVisible !== false &&
|
||||
!(keyboardHidesTabBar && isKeyboardShown);
|
||||
@@ -120,11 +207,19 @@ export default function BottomTabBar({
|
||||
width: dimensions.width,
|
||||
});
|
||||
|
||||
const isLandscape = () => dimensions.width > dimensions.height;
|
||||
|
||||
const handleLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
|
||||
const topBorderWidth =
|
||||
// @ts-ignore
|
||||
StyleSheet.flatten([styles.tabBar, style])?.borderTopWidth;
|
||||
|
||||
onHeightChange?.(
|
||||
height +
|
||||
paddingBottom +
|
||||
(typeof topBorderWidth === 'number' ? topBorderWidth : 0)
|
||||
);
|
||||
|
||||
setLayout((layout) => {
|
||||
if (height === layout.height && width === layout.width) {
|
||||
return layout;
|
||||
@@ -138,34 +233,6 @@ export default function BottomTabBar({
|
||||
};
|
||||
|
||||
const { routes } = state;
|
||||
const shouldUseHorizontalLabels = () => {
|
||||
if (labelPosition) {
|
||||
return labelPosition === 'beside-icon';
|
||||
}
|
||||
|
||||
if (!adaptive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (layout.width >= 768) {
|
||||
// Screen size matches a tablet
|
||||
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
|
||||
|
||||
const flattenedStyle = StyleSheet.flatten(tabStyle);
|
||||
|
||||
if (flattenedStyle) {
|
||||
if (typeof flattenedStyle.width === 'number') {
|
||||
maxTabItemWidth = flattenedStyle.width;
|
||||
} else if (typeof flattenedStyle.maxWidth === 'number') {
|
||||
maxTabItemWidth = flattenedStyle.maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return routes.length * maxTabItemWidth <= layout.width;
|
||||
} else {
|
||||
return isLandscape();
|
||||
}
|
||||
};
|
||||
|
||||
const defaultInsets = useSafeArea();
|
||||
|
||||
@@ -176,22 +243,26 @@ export default function BottomTabBar({
|
||||
left: safeAreaInsets?.left ?? defaultInsets.left,
|
||||
};
|
||||
|
||||
const paddingBottom = Math.max(
|
||||
insets.bottom - Platform.select({ ios: 4, default: 0 }),
|
||||
0
|
||||
);
|
||||
const paddingBottom = getPaddingBottom(insets);
|
||||
const tabBarHeight = getTabBarHeight({
|
||||
state,
|
||||
insets,
|
||||
dimensions,
|
||||
layout,
|
||||
adaptive,
|
||||
labelPosition,
|
||||
tabStyle,
|
||||
style,
|
||||
});
|
||||
|
||||
const getDefaultTabBarHeight = () => {
|
||||
if (
|
||||
Platform.OS === 'ios' &&
|
||||
!Platform.isPad &&
|
||||
isLandscape() &&
|
||||
shouldUseHorizontalLabels()
|
||||
) {
|
||||
return COMPACT_TABBAR_HEIGHT;
|
||||
}
|
||||
return DEFAULT_TABBAR_HEIGHT;
|
||||
};
|
||||
const hasHorizontalLabels = shouldUseHorizontalLabels({
|
||||
state,
|
||||
dimensions,
|
||||
layout,
|
||||
adaptive,
|
||||
labelPosition,
|
||||
tabStyle,
|
||||
});
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
@@ -218,7 +289,7 @@ export default function BottomTabBar({
|
||||
position: isTabBarHidden ? 'absolute' : (null as any),
|
||||
},
|
||||
{
|
||||
height: getDefaultTabBarHeight() + paddingBottom,
|
||||
height: tabBarHeight,
|
||||
paddingBottom,
|
||||
paddingHorizontal: Math.max(insets.left, insets.right),
|
||||
},
|
||||
@@ -276,7 +347,7 @@ export default function BottomTabBar({
|
||||
<BottomTabItem
|
||||
route={route}
|
||||
focused={focused}
|
||||
horizontal={shouldUseHorizontalLabels()}
|
||||
horizontal={hasHorizontalLabels}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
@@ -290,6 +361,7 @@ export default function BottomTabBar({
|
||||
button={options.tabBarButton}
|
||||
icon={options.tabBarIcon}
|
||||
badge={options.tabBarBadge}
|
||||
badgeStyle={options.tabBarBadgeStyle}
|
||||
label={label}
|
||||
showLabel={showLabel}
|
||||
labelStyle={labelStyle}
|
||||
|
||||
@@ -47,6 +47,10 @@ type Props = {
|
||||
* Text to show in a badge on the tab icon.
|
||||
*/
|
||||
badge?: number | string;
|
||||
/**
|
||||
* Custom style for the badge.
|
||||
*/
|
||||
badgeStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* URL to use for the link to the tab.
|
||||
*/
|
||||
@@ -122,6 +126,7 @@ export default function BottomTabBarItem({
|
||||
label,
|
||||
icon,
|
||||
badge,
|
||||
badgeStyle,
|
||||
to,
|
||||
button = ({
|
||||
children,
|
||||
@@ -235,6 +240,7 @@ export default function BottomTabBarItem({
|
||||
route={route}
|
||||
horizontal={horizontal}
|
||||
badge={badge}
|
||||
badgeStyle={badgeStyle}
|
||||
activeOpacity={activeOpacity}
|
||||
inactiveOpacity={inactiveOpacity}
|
||||
activeTintColor={activeTintColor}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
|
||||
import {
|
||||
NavigationHelpersContext,
|
||||
@@ -9,9 +15,13 @@ import {
|
||||
} from '@react-navigation/native';
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
|
||||
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
|
||||
import SafeAreaProviderCompat, {
|
||||
initialSafeAreaInsets,
|
||||
} from './SafeAreaProviderCompat';
|
||||
import ResourceSavingScene from './ResourceSavingScene';
|
||||
import BottomTabBar from './BottomTabBar';
|
||||
import BottomTabBar, { getTabBarHeight } from './BottomTabBar';
|
||||
import BottomTabBarHeightCallbackContext from '../utils/BottomTabBarHeightCallbackContext';
|
||||
import BottomTabBarHeightContext from '../utils/BottomTabBarHeightContext';
|
||||
import type {
|
||||
BottomTabNavigationConfig,
|
||||
BottomTabDescriptorMap,
|
||||
@@ -27,6 +37,7 @@ type Props = BottomTabNavigationConfig & {
|
||||
|
||||
type State = {
|
||||
loaded: string[];
|
||||
tabBarHeight: number;
|
||||
};
|
||||
|
||||
function SceneContent({
|
||||
@@ -67,9 +78,28 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
state: State = {
|
||||
loaded: [this.props.state.routes[this.props.state.index].key],
|
||||
};
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const { state, tabBarOptions } = this.props;
|
||||
|
||||
const dimensions = Dimensions.get('window');
|
||||
const tabBarHeight = getTabBarHeight({
|
||||
state,
|
||||
dimensions,
|
||||
layout: { width: dimensions.width, height: 0 },
|
||||
insets: initialSafeAreaInsets,
|
||||
adaptive: tabBarOptions?.adaptive,
|
||||
labelPosition: tabBarOptions?.labelPosition,
|
||||
tabStyle: tabBarOptions?.tabStyle,
|
||||
style: tabBarOptions?.style,
|
||||
});
|
||||
|
||||
this.state = {
|
||||
loaded: [state.routes[state.index].key],
|
||||
tabBarHeight: tabBarHeight,
|
||||
};
|
||||
}
|
||||
|
||||
private renderTabBar = () => {
|
||||
const {
|
||||
@@ -87,6 +117,16 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
private handleTabBarHeightChange = (height: number) => {
|
||||
this.setState((state) => {
|
||||
if (state.tabBarHeight !== height) {
|
||||
return { tabBarHeight: height };
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
state,
|
||||
@@ -97,7 +137,7 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
sceneContainerStyle,
|
||||
} = this.props;
|
||||
const { routes } = state;
|
||||
const { loaded } = this.state;
|
||||
const { loaded, tabBarHeight } = this.state;
|
||||
|
||||
return (
|
||||
<NavigationHelpersContext.Provider value={navigation}>
|
||||
@@ -133,13 +173,19 @@ export default class BottomTabView extends React.Component<Props, State> {
|
||||
isFocused={isFocused}
|
||||
style={sceneContainerStyle}
|
||||
>
|
||||
{descriptor.render()}
|
||||
<BottomTabBarHeightContext.Provider value={tabBarHeight}>
|
||||
{descriptor.render()}
|
||||
</BottomTabBarHeightContext.Provider>
|
||||
</SceneContent>
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
})}
|
||||
</ScreenContainer>
|
||||
{this.renderTabBar()}
|
||||
<BottomTabBarHeightCallbackContext.Provider
|
||||
value={this.handleTabBarHeightChange}
|
||||
>
|
||||
{this.renderTabBar()}
|
||||
</BottomTabBarHeightCallbackContext.Provider>
|
||||
</View>
|
||||
</SafeAreaProviderCompat>
|
||||
</NavigationHelpersContext.Provider>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
// The provider component for safe area initializes asynchornously
|
||||
// Until the insets are available, there'll be blank screen
|
||||
// To avoid the blank screen, we specify some initial values
|
||||
const initialSafeAreaInsets = {
|
||||
export const initialSafeAreaInsets = {
|
||||
// Approximate values which are good enough for most cases
|
||||
top: getStatusBarHeight(true),
|
||||
bottom: getBottomSpace(),
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
StyleProp,
|
||||
TextStyle,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
import type { Route } from '@react-navigation/native';
|
||||
import Badge from './Badge';
|
||||
|
||||
@@ -7,6 +13,7 @@ type Props = {
|
||||
route: Route<string>;
|
||||
horizontal: boolean;
|
||||
badge?: string | number;
|
||||
badgeStyle?: StyleProp<TextStyle>;
|
||||
activeOpacity: number;
|
||||
inactiveOpacity: number;
|
||||
activeTintColor: string;
|
||||
@@ -22,6 +29,7 @@ type Props = {
|
||||
export default function TabBarIcon({
|
||||
horizontal,
|
||||
badge,
|
||||
badgeStyle,
|
||||
activeOpacity,
|
||||
inactiveOpacity,
|
||||
activeTintColor,
|
||||
@@ -56,6 +64,7 @@ export default function TabBarIcon({
|
||||
style={[
|
||||
styles.badge,
|
||||
horizontal ? styles.badgeHorizontal : styles.badgeVertical,
|
||||
badgeStyle,
|
||||
]}
|
||||
size={(size * 3) / 4}
|
||||
>
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.7...@react-navigation/compat@5.3.8) (2020-11-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/compat@5.3.6...@react-navigation/compat@5.3.7) (2020-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/compat",
|
||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||
"version": "5.3.7",
|
||||
"version": "5.3.8",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/native": "^5.8.7",
|
||||
"@react-navigation/native": "^5.8.8",
|
||||
"@types/react": "^16.9.53",
|
||||
"react": "~16.13.1",
|
||||
"typescript": "^4.0.3"
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.14.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.1...@react-navigation/core@5.14.2) (2020-11-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* throw if the same pattern resolves to multiple screens ([48b2e77](https://github.com/react-navigation/react-navigation/commit/48b2e777307908e8b3fcb49d8555b610dc0e38f2))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.14.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@5.14.0...@react-navigation/core@5.14.1) (2020-11-08)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"description": "Core utilities for building navigators",
|
||||
"version": "5.14.1",
|
||||
"version": "5.14.2",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -35,7 +35,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.6.1",
|
||||
"@react-navigation/routers": "^5.6.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"nanoid": "^3.1.15",
|
||||
"query-string": "^6.13.6",
|
||||
|
||||
@@ -2673,6 +2673,47 @@ it('uses nearest parent wildcard match for unmatched paths', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if two screens map to the same pattern', () => {
|
||||
const path = '/bar/42/baz/test';
|
||||
|
||||
expect(() =>
|
||||
getStateFromPath(path, {
|
||||
screens: {
|
||||
Foo: {
|
||||
screens: {
|
||||
Bar: {
|
||||
path: '/bar/:id/',
|
||||
screens: {
|
||||
Baz: 'baz',
|
||||
},
|
||||
},
|
||||
Bax: '/bar/:id/baz',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
).toThrow(
|
||||
"Found conflicting screens with the same pattern. The pattern 'bar/:id/baz' resolves to both 'Foo > Bax' and 'Foo > Bar > Baz'. Patterns must be unique and cannot resolve to more than one screen."
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
getStateFromPath(path, {
|
||||
screens: {
|
||||
Foo: {
|
||||
screens: {
|
||||
Bar: {
|
||||
path: '/bar/:id/',
|
||||
screens: {
|
||||
Baz: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('throws if wildcard is specified with legacy config', () => {
|
||||
const path = '/bar/42/baz/test';
|
||||
const config = {
|
||||
|
||||
@@ -239,6 +239,10 @@ export default function getPathFromState(
|
||||
// Object.fromEntries is not available in older iOS versions
|
||||
const fromEntries = <K extends string, V>(entries: (readonly [K, V])[]) =>
|
||||
entries.reduce((acc, [k, v]) => {
|
||||
if (acc.hasOwnProperty(k)) {
|
||||
throw new Error(`A value for key '${k}' already exists in the object.`);
|
||||
}
|
||||
|
||||
acc[k] = v;
|
||||
return acc;
|
||||
}, {} as Record<K, V>);
|
||||
|
||||
@@ -119,6 +119,12 @@ export default function getStateFromPath(
|
||||
// - the most exhaustive ones are always at the beginning
|
||||
// - patterns with wildcard are always at the end
|
||||
|
||||
// If 2 patterns are same, move the one with less route names up
|
||||
// This is an error state, so it's only useful for consistent error messages
|
||||
if (a.pattern === b.pattern) {
|
||||
return b.routeNames.join('>').localeCompare(a.routeNames.join('>'));
|
||||
}
|
||||
|
||||
// If one of the patterns starts with the other, it's more exhaustive
|
||||
// So move it up
|
||||
if (a.pattern.startsWith(b.pattern)) {
|
||||
@@ -155,6 +161,35 @@ export default function getStateFromPath(
|
||||
return bWildcardIndex - aWildcardIndex;
|
||||
});
|
||||
|
||||
// Check for duplicate patterns in the config
|
||||
configs.reduce<Record<string, RouteConfig>>((acc, config) => {
|
||||
if (acc[config.pattern]) {
|
||||
const a = acc[config.pattern].routeNames;
|
||||
const b = config.routeNames;
|
||||
|
||||
// It's not a problem if the path string omitted from a inner most screen
|
||||
// For example, it's ok if a path resolves to `A > B > C` or `A > B`
|
||||
const intersects =
|
||||
a.length > b.length
|
||||
? b.every((it, i) => a[i] === it)
|
||||
: a.every((it, i) => b[i] === it);
|
||||
|
||||
if (!intersects) {
|
||||
throw new Error(
|
||||
`Found conflicting screens with the same pattern. The pattern '${
|
||||
config.pattern
|
||||
}' resolves to both '${a.join(' > ')}' and '${b.join(
|
||||
' > '
|
||||
)}'. Patterns must be unique and cannot resolve to more than one screen.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.assign(acc, {
|
||||
[config.pattern]: config,
|
||||
});
|
||||
}, {});
|
||||
|
||||
if (remaining === '/') {
|
||||
// We need to add special handling of empty path so navigation to empty path also works
|
||||
// When handling empty path, we should only look at the root level config
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.1.16](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.15...@react-navigation/devtools@5.1.16) (2020-11-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.15](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@5.1.14...@react-navigation/devtools@5.1.15) (2020-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/devtools
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/devtools",
|
||||
"description": "Developer tools for React Navigation",
|
||||
"version": "5.1.15",
|
||||
"version": "5.1.16",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -36,7 +36,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^5.14.1",
|
||||
"@react-navigation/core": "^5.14.2",
|
||||
"deep-equal": "^2.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,6 +3,23 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.11.0](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.10.7...@react-navigation/drawer@5.11.0) (2020-11-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* try fixing drawer blink on Android ([5217245](https://github.com/react-navigation/react-navigation/commit/52172453dfb71822c2fb0f5947d00bac4a840d07))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a getIsDrawerOpenFromState utility to drawer ([5bd682f](https://github.com/react-navigation/react-navigation/commit/5bd682f0bf6b28a95fb3e7fc9e1974057a877cb0))
|
||||
* add option to show a header in drawer navigator screens ([dbe961b](https://github.com/react-navigation/react-navigation/commit/dbe961ba5bb243e8da4d889c3c7dd6ed1de287c4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.10.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@5.10.6...@react-navigation/drawer@5.10.7) (2020-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/drawer",
|
||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||
"version": "5.10.7",
|
||||
"version": "5.11.0",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -46,7 +46,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/native": "^5.8.7",
|
||||
"@react-navigation/native": "^5.8.8",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
|
||||
@@ -17,6 +17,7 @@ export { default as DrawerContentScrollView } from './views/DrawerContentScrollV
|
||||
*/
|
||||
export { default as DrawerGestureContext } from './utils/DrawerGestureContext';
|
||||
|
||||
export { default as getIsDrawerOpenFromState } from './utils/getIsDrawerOpenFromState';
|
||||
export { default as useIsDrawerOpen } from './utils/useIsDrawerOpen';
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,8 @@ export type Scene = {
|
||||
color?: string;
|
||||
};
|
||||
|
||||
export type Layout = { width: number; height: number };
|
||||
|
||||
export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
/**
|
||||
* Position of the drawer on the screen. Defaults to `left`.
|
||||
@@ -94,12 +96,95 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
|
||||
detachInactiveScreens?: boolean;
|
||||
};
|
||||
|
||||
export type DrawerNavigationOptions = {
|
||||
export type DrawerHeaderOptions = {
|
||||
/**
|
||||
* String or a function that returns a React Element to be used by the header.
|
||||
* Defaults to scene `title`.
|
||||
* It receives `allowFontScaling`, `tintColor`, `style` and `children` in the options object as an argument.
|
||||
* The title string is passed in `children`.
|
||||
*/
|
||||
headerTitle?:
|
||||
| string
|
||||
| ((props: {
|
||||
/**
|
||||
* Whether title font should scale to respect Text Size accessibility settings.
|
||||
*/
|
||||
allowFontScaling?: boolean;
|
||||
/**
|
||||
* Tint color for the header.
|
||||
*/
|
||||
tintColor?: string;
|
||||
/**
|
||||
* Content of the title element. Usually the title string.
|
||||
*/
|
||||
children?: string;
|
||||
/**
|
||||
* Style object for the title element.
|
||||
*/
|
||||
style?: StyleProp<TextStyle>;
|
||||
}) => React.ReactNode);
|
||||
/**
|
||||
* How to align the the header title.
|
||||
* Defaults to `center` on iOS and `left` on Android.
|
||||
*/
|
||||
headerTitleAlign?: 'left' | 'center';
|
||||
/**
|
||||
* Style object for the title component.
|
||||
*/
|
||||
headerTitleStyle?: StyleProp<TextStyle>;
|
||||
/**
|
||||
* Whether header title font should scale to respect Text Size accessibility settings. Defaults to `false`.
|
||||
*/
|
||||
headerTitleAllowFontScaling?: boolean;
|
||||
/**
|
||||
* Function which returns a React Element to display on the left side of the header.
|
||||
*/
|
||||
headerLeft?: (props: { tintColor?: string }) => React.ReactNode;
|
||||
/**
|
||||
* Accessibility label for the header left button.
|
||||
*/
|
||||
headerLeftAccessibilityLabel?: string;
|
||||
/**
|
||||
* Function which returns a React Element to display on the right side of the header.
|
||||
*/
|
||||
headerRight?: (props: { tintColor?: string }) => React.ReactNode;
|
||||
/**
|
||||
* Color for material ripple (Android >= 5.0 only).
|
||||
*/
|
||||
headerPressColorAndroid?: string;
|
||||
/**
|
||||
* Tint color for the header.
|
||||
*/
|
||||
headerTintColor?: string;
|
||||
/**
|
||||
* Style object for the header. You can specify a custom background color here, for example.
|
||||
*/
|
||||
headerStyle?: StyleProp<ViewStyle>;
|
||||
/**
|
||||
* Extra padding to add at the top of header to account for translucent status bar.
|
||||
* By default, it uses the top value from the safe area insets of the device.
|
||||
* Pass 0 or a custom value to disable the default behaviour, and customize the height.
|
||||
*/
|
||||
headerStatusBarHeight?: number;
|
||||
};
|
||||
|
||||
export type DrawerNavigationOptions = DrawerHeaderOptions & {
|
||||
/**
|
||||
* Title text for the screen.
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* Function that given `HeaderProps` returns a React Element to display as a header.
|
||||
*/
|
||||
header?: (props: DrawerHeaderProps) => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Whether to show the header. The header is not shown by default.
|
||||
* Setting this to `true` shows the header.
|
||||
*/
|
||||
headerShown?: boolean;
|
||||
|
||||
/**
|
||||
* Title string of a screen displayed in the drawer
|
||||
* or a function that given { focused: boolean, color: string } returns a React.Node
|
||||
@@ -187,6 +272,20 @@ export type DrawerContentOptions = {
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
export type DrawerHeaderProps = {
|
||||
/**
|
||||
* Layout of the screen.
|
||||
*/
|
||||
layout: Layout;
|
||||
/**
|
||||
* Object representing the current scene, such as the route object and descriptor.
|
||||
*/
|
||||
scene: {
|
||||
route: Route<string>;
|
||||
descriptor: DrawerDescriptor;
|
||||
};
|
||||
};
|
||||
|
||||
export type DrawerNavigationEventMap = {
|
||||
/**
|
||||
* Event which fires when the drawer opens.
|
||||
|
||||
16
packages/drawer/src/utils/getIsDrawerOpenFromState.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import type {
|
||||
DrawerNavigationState,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/native';
|
||||
|
||||
export default function getIsDrawerOpenFromState(
|
||||
state: DrawerNavigationState<ParamListBase>
|
||||
): boolean {
|
||||
if (state.history == null) {
|
||||
throw new Error(
|
||||
"Couldn't find the drawer status in the state object. Is it a valid state object of drawer navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
return state.history.some((it) => it.type === 'drawer');
|
||||
}
|
||||
@@ -57,9 +57,10 @@ const DIRECTION_LEFT = 1;
|
||||
const DIRECTION_RIGHT = -1;
|
||||
|
||||
const SWIPE_DISTANCE_THRESHOLD_DEFAULT = 60;
|
||||
|
||||
const SWIPE_DISTANCE_MINIMUM = 5;
|
||||
|
||||
const DEFAULT_DRAWER_WIDTH = '80%';
|
||||
|
||||
const SPRING_CONFIG = {
|
||||
stiffness: 1000,
|
||||
damping: 500,
|
||||
@@ -202,7 +203,8 @@ export default class DrawerView extends React.Component<Props> {
|
||||
|
||||
private getDrawerWidth = (): number => {
|
||||
const { drawerStyle, dimensions } = this.props;
|
||||
const { width } = StyleSheet.flatten(drawerStyle);
|
||||
const { width = DEFAULT_DRAWER_WIDTH } =
|
||||
StyleSheet.flatten(drawerStyle) || {};
|
||||
|
||||
if (typeof width === 'string' && width.endsWith('%')) {
|
||||
// Try to calculate width if a percentage is given
|
||||
@@ -246,7 +248,7 @@ export default class DrawerView extends React.Component<Props> {
|
||||
private containerWidth = new Value<number>(this.props.dimensions.width);
|
||||
private drawerWidth = new Value<number>(this.initialDrawerWidth);
|
||||
private drawerOpacity = new Value<number>(
|
||||
this.initialDrawerWidth || this.props.drawerType === 'permanent' ? 1 : 0
|
||||
this.props.drawerType === 'permanent' ? 1 : 0
|
||||
);
|
||||
private drawerPosition = new Value<number>(
|
||||
this.props.drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT
|
||||
@@ -730,7 +732,7 @@ const styles = StyleSheet.create({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: '80%',
|
||||
width: DEFAULT_DRAWER_WIDTH,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
|
||||
@@ -19,16 +19,19 @@ import {
|
||||
import { GestureHandlerRootView } from './GestureHandler';
|
||||
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
|
||||
import ResourceSavingScene from './ResourceSavingScene';
|
||||
import Header from './Header';
|
||||
import DrawerContent from './DrawerContent';
|
||||
import Drawer from './Drawer';
|
||||
import DrawerOpenContext from '../utils/DrawerOpenContext';
|
||||
import DrawerPositionContext from '../utils/DrawerPositionContext';
|
||||
import useWindowDimensions from '../utils/useWindowDimensions';
|
||||
import getIsDrawerOpenFromState from '../utils/getIsDrawerOpenFromState';
|
||||
import type {
|
||||
DrawerDescriptorMap,
|
||||
DrawerNavigationConfig,
|
||||
DrawerNavigationHelpers,
|
||||
DrawerContentComponentProps,
|
||||
DrawerHeaderProps,
|
||||
} from '../types';
|
||||
|
||||
type Props = DrawerNavigationConfig & {
|
||||
@@ -90,7 +93,7 @@ export default function DrawerView({
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
|
||||
const isDrawerOpen = getIsDrawerOpenFromState(state);
|
||||
|
||||
const handleDrawerOpen = React.useCallback(() => {
|
||||
navigation.dispatch({
|
||||
@@ -169,6 +172,11 @@ export default function DrawerView({
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
header = (props: DrawerHeaderProps) => <Header {...props} />,
|
||||
headerShown = true,
|
||||
} = descriptor.options;
|
||||
|
||||
return (
|
||||
<ResourceSavingScene
|
||||
key={route.key}
|
||||
@@ -176,6 +184,12 @@ export default function DrawerView({
|
||||
isVisible={isFocused}
|
||||
enabled={detachInactiveScreens}
|
||||
>
|
||||
{headerShown
|
||||
? header({
|
||||
layout: dimensions,
|
||||
scene: { route, descriptor },
|
||||
})
|
||||
: null}
|
||||
{descriptor.render()}
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
|
||||
240
packages/drawer/src/views/Header.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
import * as React from 'react';
|
||||
import { Text, View, Image, StyleSheet, Platform } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { DrawerActions, useTheme } from '@react-navigation/native';
|
||||
import TouchableItem from './TouchableItem';
|
||||
import type { Layout, DrawerHeaderProps } from '../types';
|
||||
|
||||
export const getDefaultHeaderHeight = (
|
||||
layout: Layout,
|
||||
statusBarHeight: number
|
||||
): number => {
|
||||
const isLandscape = layout.width > layout.height;
|
||||
|
||||
let headerHeight;
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
if (isLandscape && !Platform.isPad) {
|
||||
headerHeight = 32;
|
||||
} else {
|
||||
headerHeight = 44;
|
||||
}
|
||||
} else if (Platform.OS === 'android') {
|
||||
headerHeight = 56;
|
||||
} else {
|
||||
headerHeight = 64;
|
||||
}
|
||||
|
||||
return headerHeight + statusBarHeight;
|
||||
};
|
||||
|
||||
export default function HeaderSegment({ scene, layout }: DrawerHeaderProps) {
|
||||
const insets = useSafeAreaInsets();
|
||||
const { colors } = useTheme();
|
||||
|
||||
const {
|
||||
title,
|
||||
headerTitle,
|
||||
headerTitleAlign = Platform.select({
|
||||
ios: 'center',
|
||||
default: 'left',
|
||||
}),
|
||||
headerLeft,
|
||||
headerLeftAccessibilityLabel,
|
||||
headerRight,
|
||||
headerTitleAllowFontScaling,
|
||||
headerTitleStyle,
|
||||
headerTintColor,
|
||||
headerPressColorAndroid,
|
||||
headerStyle,
|
||||
headerStatusBarHeight = insets.top,
|
||||
} = scene.descriptor.options;
|
||||
|
||||
const currentTitle =
|
||||
typeof headerTitle !== 'function' && headerTitle !== undefined
|
||||
? headerTitle
|
||||
: title !== undefined
|
||||
? title
|
||||
: scene.route.name;
|
||||
|
||||
const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight);
|
||||
|
||||
const leftButton = headerLeft ? (
|
||||
headerLeft({ tintColor: headerTintColor })
|
||||
) : (
|
||||
<TouchableItem
|
||||
accessible
|
||||
accessibilityRole="button"
|
||||
accessibilityComponentType="button"
|
||||
accessibilityLabel={headerLeftAccessibilityLabel}
|
||||
accessibilityTraits="button"
|
||||
delayPressIn={0}
|
||||
onPress={() =>
|
||||
scene.descriptor.navigation.dispatch(DrawerActions.toggleDrawer())
|
||||
}
|
||||
style={styles.touchable}
|
||||
pressColor={headerPressColorAndroid}
|
||||
hitSlop={Platform.select({
|
||||
ios: undefined,
|
||||
default: { top: 16, right: 16, bottom: 16, left: 16 },
|
||||
})}
|
||||
borderless
|
||||
>
|
||||
<Image
|
||||
style={[
|
||||
styles.icon,
|
||||
headerTintColor ? { tintColor: headerTintColor } : null,
|
||||
]}
|
||||
source={require('./assets/toggle-drawer-icon.png')}
|
||||
fadeDuration={0}
|
||||
/>
|
||||
</TouchableItem>
|
||||
);
|
||||
const rightButton = headerRight
|
||||
? headerRight({ tintColor: headerTintColor })
|
||||
: null;
|
||||
|
||||
return (
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
{
|
||||
height: defaultHeight,
|
||||
backgroundColor: colors.card,
|
||||
borderBottomColor: colors.border,
|
||||
shadowColor: colors.border,
|
||||
},
|
||||
styles.container,
|
||||
headerStyle,
|
||||
]}
|
||||
>
|
||||
<View pointerEvents="none" style={{ height: headerStatusBarHeight }} />
|
||||
<View pointerEvents="box-none" style={styles.content}>
|
||||
{leftButton ? (
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={[styles.left, { left: insets.left }]}
|
||||
>
|
||||
{leftButton}
|
||||
</View>
|
||||
) : null}
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
headerTitleAlign === 'left'
|
||||
? {
|
||||
position: 'absolute',
|
||||
left: (leftButton ? 72 : 16) + insets.left,
|
||||
right: (rightButton ? 72 : 16) + insets.right,
|
||||
}
|
||||
: {
|
||||
marginHorizontal:
|
||||
(leftButton ? 32 : 16) +
|
||||
Math.max(insets.left, insets.right),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{typeof headerTitle === 'function' ? (
|
||||
headerTitle({
|
||||
children: currentTitle,
|
||||
allowFontScaling: headerTitleAllowFontScaling,
|
||||
tintColor: headerTintColor,
|
||||
style: headerTitleStyle,
|
||||
})
|
||||
) : (
|
||||
<Text
|
||||
accessibilityRole="header"
|
||||
aria-level="1"
|
||||
numberOfLines={1}
|
||||
allowFontScaling={headerTitleAllowFontScaling}
|
||||
style={[
|
||||
styles.title,
|
||||
{ color: headerTintColor ?? colors.text },
|
||||
styles.title,
|
||||
headerTitleStyle,
|
||||
]}
|
||||
>
|
||||
{currentTitle}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
{rightButton ? (
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={[styles.right, { right: insets.right }]}
|
||||
>
|
||||
{rightButton}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
...Platform.select({
|
||||
android: {
|
||||
elevation: 4,
|
||||
},
|
||||
ios: {
|
||||
shadowOpacity: 0.85,
|
||||
shadowRadius: 0,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: StyleSheet.hairlineWidth,
|
||||
},
|
||||
},
|
||||
default: {
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
},
|
||||
}),
|
||||
zIndex: 1,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
title: Platform.select({
|
||||
ios: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
},
|
||||
android: {
|
||||
fontSize: 20,
|
||||
fontFamily: 'sans-serif-medium',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
default: {
|
||||
fontSize: 18,
|
||||
fontWeight: '500',
|
||||
},
|
||||
}),
|
||||
icon: {
|
||||
height: 24,
|
||||
width: 24,
|
||||
margin: 3,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
touchable: {
|
||||
marginHorizontal: 11,
|
||||
},
|
||||
left: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
right: {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
});
|
||||
BIN
packages/drawer/src/views/assets/toggle-drawer-icon.png
Normal file
|
After Width: | Height: | Size: 116 B |
|
After Width: | Height: | Size: 106 B |
BIN
packages/drawer/src/views/assets/toggle-drawer-icon@1.5x.ios.png
Normal file
|
After Width: | Height: | Size: 159 B |
|
After Width: | Height: | Size: 84 B |
BIN
packages/drawer/src/views/assets/toggle-drawer-icon@1x.ios.png
Normal file
|
After Width: | Height: | Size: 108 B |
|
After Width: | Height: | Size: 100 B |
BIN
packages/drawer/src/views/assets/toggle-drawer-icon@2x.ios.png
Normal file
|
After Width: | Height: | Size: 163 B |
|
After Width: | Height: | Size: 126 B |
BIN
packages/drawer/src/views/assets/toggle-drawer-icon@3x.ios.png
Normal file
|
After Width: | Height: | Size: 212 B |
|
After Width: | Height: | Size: 116 B |
BIN
packages/drawer/src/views/assets/toggle-drawer-icon@4x.ios.png
Normal file
|
After Width: | Height: | Size: 219 B |
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.7...@react-navigation/material-bottom-tabs@5.3.8) (2020-11-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@5.3.6...@react-navigation/material-bottom-tabs@5.3.7) (2020-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-bottom-tabs",
|
||||
"description": "Integration for bottom navigation component from react-native-paper",
|
||||
"version": "5.3.7",
|
||||
"version": "5.3.8",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -42,7 +42,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/native": "^5.8.7",
|
||||
"@react-navigation/native": "^5.8.8",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.3.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.7...@react-navigation/material-top-tabs@5.3.8) (2020-11-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@5.3.6...@react-navigation/material-top-tabs@5.3.7) (2020-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-top-tabs",
|
||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||
"version": "5.3.7",
|
||||
"version": "5.3.8",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/native": "^5.8.7",
|
||||
"@react-navigation/native": "^5.8.8",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.8.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.7...@react-navigation/native@5.8.8) (2020-11-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.8.7](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@5.8.6...@react-navigation/native@5.8.7) (2020-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/native",
|
||||
"description": "React Native integration for React Navigation",
|
||||
"version": "5.8.7",
|
||||
"version": "5.8.8",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"react-navigation",
|
||||
@@ -37,7 +37,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^5.14.1",
|
||||
"@react-navigation/core": "^5.14.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"nanoid": "^3.1.15"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.6.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@5.6.1...@react-navigation/routers@5.6.2) (2020-11-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/routers
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.6.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@5.6.0...@react-navigation/routers@5.6.1) (2020-11-08)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/routers
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/routers",
|
||||
"description": "Routers to help build custom navigators",
|
||||
"version": "5.6.1",
|
||||
"version": "5.6.2",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
|
||||
@@ -75,7 +75,7 @@ const isDrawerOpen = (
|
||||
state:
|
||||
| DrawerNavigationState<ParamListBase>
|
||||
| PartialState<DrawerNavigationState<ParamListBase>>
|
||||
) => Boolean(state.history?.find((it) => it.type === 'drawer'));
|
||||
) => Boolean(state.history?.some((it) => it.type === 'drawer'));
|
||||
|
||||
const openDrawer = (
|
||||
state: DrawerNavigationState<ParamListBase>
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [5.12.5](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.12.4...@react-navigation/stack@5.12.5) (2020-11-09)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.12.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@5.12.3...@react-navigation/stack@5.12.4) (2020-11-08)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/stack",
|
||||
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
||||
"version": "5.12.4",
|
||||
"version": "5.12.5",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -46,7 +46,7 @@
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-native-community/masked-view": "^0.1.10",
|
||||
"@react-navigation/native": "^5.8.7",
|
||||
"@react-navigation/native": "^5.8.8",
|
||||
"@testing-library/react-native": "^7.1.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.53",
|
||||
|
||||