Skip to content

Commit

Permalink
feat: add a hook to get bottom tab bar height
Browse files Browse the repository at this point in the history
Usage:

```js
import { useBottomTabBarHeight } from '@react-navigation/stack';

// ...

const headerHeight = useBottomTabBarHeight();
```

closes #8037, closes #8536
  • Loading branch information
satya164 committed Nov 8, 2020
1 parent 5bd682f commit e08c91f
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 60 deletions.
7 changes: 7 additions & 0 deletions packages/bottom-tabs/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as React from 'react';

export default React.createContext<((height: number) => void) | undefined>(
undefined
);
3 changes: 3 additions & 0 deletions packages/bottom-tabs/src/utils/BottomTabBarHeightContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as React from 'react';

export default React.createContext<number | undefined>(undefined);
14 changes: 14 additions & 0 deletions packages/bottom-tabs/src/utils/useBottomTabBarHeight.tsx
Original file line number Diff line number Diff line change
@@ -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;
}
171 changes: 121 additions & 50 deletions packages/bottom-tabs/src/views/BottomTabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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();

Expand All @@ -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
Expand All @@ -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),
},
Expand Down Expand Up @@ -276,7 +347,7 @@ export default function BottomTabBar({
<BottomTabItem
route={route}
focused={focused}
horizontal={shouldUseHorizontalLabels()}
horizontal={hasHorizontalLabels}
onPress={onPress}
onLongPress={onLongPress}
accessibilityLabel={accessibilityLabel}
Expand Down
Loading

0 comments on commit e08c91f

Please sign in to comment.