Skip to content

Commit

Permalink
feat(hv-navigator): Option for custom tab bar component (#950)
Browse files Browse the repository at this point in the history
Add new prop `navigationComponents` to Hyperview root component, that
for now allows passing a key `TabBar` set to a React component that will
be used to render the tab bar of a bottom tab navigator. In the future,
we'll add more keys to configure other elements of the Navigation UI
such as the header of stack components, or top tab bar when we support
equivalent navigator.
  • Loading branch information
flochtililoch authored Oct 2, 2024
1 parent 93da132 commit d48eeef
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 16 deletions.
2 changes: 2 additions & 0 deletions src/contexts/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
import React, { ComponentType, ReactNode } from 'react';
import type { Props as ErrorProps } from 'hyperview/src/core/components/load-error';
import type { Props as LoadingProps } from 'hyperview/src/core/components/loading';
import type { NavigationComponents } from 'hyperview/src/services/navigator';

export type NavigationContextProps = {
entrypointUrl: string;
Expand All @@ -27,6 +28,7 @@ export type NavigationContextProps = {
errorScreen?: ComponentType<ErrorProps>;
loadingScreen?: ComponentType<LoadingProps>;
handleBack?: ComponentType<{ children: ReactNode }>;
navigationComponents?: NavigationComponents;
};

/**
Expand Down
55 changes: 40 additions & 15 deletions src/core/components/hv-navigator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,16 @@ import { createCustomTabNavigator } from 'hyperview/src/core/components/navigato
import { getFirstChildTag } from 'hyperview/src/services/dom/helpers';

/**
* Flag to show the navigator UIs
* Flag to show the default navigator UIs
* Example: tab bar
* NOTE: This will only be used if no footer element is provided for a tabbar
*/
const SHOW_NAVIGATION_UI = false;
const SHOW_DEFAULT_FOOTER_UI = false;

/**
* Flag to show the header UIs
*/
const SHOW_DEFAULT_HEADER_UI = false;

const Stack = createCustomStackNavigator<ParamTypes>();
const BottomTab = createCustomTabNavigator<ParamTypes>();
Expand Down Expand Up @@ -143,16 +150,18 @@ export default class HvNavigator extends PureComponent<Props> {
*/
stackScreenOptions = (route: ScreenParams): StackScreenOptions => ({
headerMode: 'screen',
headerShown: SHOW_NAVIGATION_UI,
headerShown: SHOW_DEFAULT_HEADER_UI,
title: this.getId(route.params),
});

/**
* Encapsulated options for the tab screenOptions
*/
tabScreenOptions = (route: ScreenParams): TabScreenOptions => ({
headerShown: SHOW_NAVIGATION_UI,
tabBarStyle: { display: SHOW_NAVIGATION_UI ? 'flex' : 'none' },
headerShown: SHOW_DEFAULT_HEADER_UI,
tabBarStyle: {
display: SHOW_DEFAULT_FOOTER_UI ? 'flex' : 'none',
},
title: this.getId(route.params),
});

Expand Down Expand Up @@ -307,7 +316,7 @@ export default class HvNavigator extends PureComponent<Props> {
/**
* Build the required navigator from the xml element
*/
Navigator = (): React.ReactElement => {
Navigator = (props: NavigatorService.NavigatorProps): React.ReactElement => {
if (!this.props.element) {
throw new NavigatorService.HvNavigatorError(
'No element found for navigator',
Expand All @@ -332,6 +341,8 @@ export default class HvNavigator extends PureComponent<Props> {
? selected.getAttribute('id')?.toString()
: undefined;

const { BottomTabBar } = props;

switch (type) {
case NavigatorService.NAVIGATOR_TYPE.STACK:
return (
Expand All @@ -349,6 +360,18 @@ export default class HvNavigator extends PureComponent<Props> {
id={id}
initialRouteName={selectedId}
screenOptions={({ route }) => this.tabScreenOptions(route)}
tabBar={
BottomTabBar &&
(p => (
<BottomTabBar
descriptors={p.descriptors}
id={id}
insets={p.insets}
navigation={p.navigation}
state={p.state}
/>
))
}
>
{this.buildScreens(type, this.props.element)}
</BottomTab.Navigator>
Expand All @@ -363,9 +386,9 @@ export default class HvNavigator extends PureComponent<Props> {
/**
* Build a stack navigator for a modal
*/
ModalNavigator = (props: {
doc: Document | undefined;
}): React.ReactElement => {
ModalNavigator = (
props: NavigatorService.NavigatorProps,
): React.ReactElement => {
if (!this.props.params) {
throw new NavigatorService.HvNavigatorError(
'No params found for modal screen',
Expand Down Expand Up @@ -425,17 +448,19 @@ export default class HvNavigator extends PureComponent<Props> {
};

render() {
const Navigator = this.props.params?.isModal
? this.ModalNavigator
: this.Navigator;
return (
<NavigationContext.Context.Consumer>
{() => (
{navContext => (
<Contexts.DocContext.Consumer>
{docProvider => (
<NavigatorMapContext.NavigatorMapProvider>
{this.props.params && this.props.params.isModal ? (
<this.ModalNavigator doc={docProvider?.getDoc()} />
) : (
<this.Navigator />
)}
<Navigator
BottomTabBar={navContext?.navigationComponents?.BottomTabBar}
doc={docProvider?.getDoc()}
/>
</NavigatorMapContext.NavigatorMapProvider>
)}
</Contexts.DocContext.Consumer>
Expand Down
1 change: 1 addition & 0 deletions src/core/components/hv-root/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@ export default class Hyperview extends PureComponent<Types.Props> {
fetch: this.props.fetch,
handleBack: this.props.handleBack,
loadingScreen: this.props.loadingScreen,
navigationComponents: this.props.navigationComponents,
onError: this.props.onError,
onParseAfter: this.props.onParseAfter,
onParseBefore: this.props.onParseBefore,
Expand Down
2 changes: 2 additions & 0 deletions src/core/components/hv-root/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
} from 'hyperview/src/types';
import type { Props as ErrorProps } from 'hyperview/src/core/components/load-error';
import type { Props as LoadingProps } from 'hyperview/src/core/components/loading';
import type { NavigationComponents } from 'hyperview/src/services/navigator';
import type { RefreshControlProps } from 'react-native';

/**
Expand All @@ -21,6 +22,7 @@ export type Props = {
format: string | undefined,
) => string | undefined;
refreshControl?: ComponentType<RefreshControlProps>;
navigationComponents?: NavigationComponents;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
navigation?: any;
route?: NavigatorService.Route<string, { url?: string }>;
Expand Down
7 changes: 6 additions & 1 deletion src/services/navigator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,12 @@ export class Navigator {
};
}

export type { NavigationProp, Route } from './types';
export type {
NavigationComponents,
NavigationProp,
NavigatorProps,
Route,
} from './types';
export {
CardStyleInterpolators,
createStackNavigator,
Expand Down
18 changes: 18 additions & 0 deletions src/services/navigator/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NavigationRouteParams } from 'hyperview/src/types';
import type { BottomTabBarProps as RNBottomTabBarProps } from '@react-navigation/bottom-tabs';

export const ANCHOR_ID_SEPARATOR = '#';
export const ID_CARD = 'card';
Expand Down Expand Up @@ -58,6 +59,23 @@ export type Route<
state?: NavigationState;
};

export type BottomTabBarProps = RNBottomTabBarProps & {
id: string;
};

export type BottomTabBarComponent = (
props: BottomTabBarProps,
) => JSX.Element | null;

export type NavigationComponents = {
BottomTabBar?: BottomTabBarComponent;
};

/* List of props available to navigators */
export type NavigatorProps = NavigationComponents & {
doc: Document | undefined;
};

/**
* Minimal representation of the 'NavigationState' used by react-navigation
*/
Expand Down

0 comments on commit d48eeef

Please sign in to comment.