Skip to content

Commit b16a5e9

Browse files
authored
feat(SwitchNavigator): Added SwitchNavigator (#117)
1 parent 5b70a1f commit b16a5e9

23 files changed

Lines changed: 315 additions & 72 deletions

.releaserc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"extends": [
3+
"@bluebase/code-standards/releaserc"
4+
]
5+
}

bluebase/expo/apps/plugin-settings-app/Screens/Home.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const HomeScreen = (props: any) => {
3838
onPress={navigate('WrappedSettings')}
3939
/>
4040
<Divider />
41+
<List.Item title="Modal" onPress={navigate('Modal')} />
42+
<Divider />
4143
<List.Item
4244
title="Params"
4345
description="See Params play"

bluebase/expo/apps/plugin-settings-app/index.tsx

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,6 @@ const plugin = createPlugin({
3232
path: '/',
3333
exact: true,
3434
screen: SettingsScreen,
35-
// navigationOptions: {
36-
// },
37-
38-
options: {
39-
title: 'Settings',
40-
presentation: 'modal',
41-
},
4235
},
4336
{
4437
name: 'WrappedSettings',
@@ -76,6 +69,62 @@ const plugin = createPlugin({
7669
],
7770
},
7871
},
72+
73+
{
74+
name: 'Modal',
75+
path: '/modal',
76+
77+
options: {
78+
headerShown: false,
79+
contentStyle: {
80+
backgroundColor: 'black',
81+
},
82+
},
83+
84+
navigator: {
85+
type: 'native-stack',
86+
// mode: 'modal',
87+
88+
routes: [
89+
{
90+
name: 'Modal1',
91+
path: 'modal1',
92+
exact: true,
93+
94+
// eslint-disable-next-line react/display-name
95+
screen: (props: any) => (
96+
<ComponentState
97+
title="Title 1"
98+
description="This screen is in a modal"
99+
actionOnPress={() => props.navigation.navigate('Modal2')}
100+
actionTitle="Modal 2"
101+
/>
102+
),
103+
104+
navigationOptions: {
105+
// stackPresentation: 'modal',
106+
title: 'Modal 1 Screen',
107+
},
108+
},
109+
{
110+
name: 'Modal2',
111+
path: 'modal2',
112+
exact: true,
113+
114+
// eslint-disable-next-line react/display-name
115+
screen: () => (
116+
<ComponentState title="Title 2" description="This screen is in a modal" />
117+
),
118+
119+
navigationOptions: {
120+
stackPresentation: 'modal',
121+
title: 'Modal 2 Screen',
122+
} as any,
123+
},
124+
],
125+
},
126+
},
127+
79128
{
80129
name: 'Params',
81130
path: '/params',

src/components/Navigator/Navigator.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
import { RouteConfigWithResolveSubRoutes, ScreenProps } from '../../types';
12
import {
23
createNavigatorScreenComponent,
34
getNavigatorFn,
45
resolveNavigatorScreenOptions,
56
resolveRouteOptions,
7+
stubNavigationObject,
68
} from '../../helpers';
79
import { resolveThunk, useBlueBase, useIntl, useTheme } from '@bluebase/core';
810

911
import { NavigatorProps as CoreNavigatorProps } from '@bluebase/components';
1012
import React from 'react';
11-
import { RouteConfigWithResolveSubRoutes } from '../../types';
1213

1314
export interface NavigatorProps extends CoreNavigatorProps {}
1415

@@ -18,11 +19,12 @@ export interface NavigatorProps extends CoreNavigatorProps {}
1819
* @param props
1920
*/
2021
export const Navigator = (props: NavigatorProps) => {
22+
const { type, routes, ...rest } = props;
23+
2124
const BB = useBlueBase();
22-
const theme = useTheme();
25+
const themes = useTheme();
2326
const intl = useIntl();
24-
25-
const { type, routes } = props;
27+
const screenProps: ScreenProps = { BB, intl, themes, theme: themes.theme };
2628

2729
const NavigatorComponent = getNavigatorFn(type);
2830

@@ -31,14 +33,14 @@ export const Navigator = (props: NavigatorProps) => {
3133
}
3234

3335
// If routes is a thunk, resolve it
34-
const resolvedRoutes = resolveThunk<RouteConfigWithResolveSubRoutes[]>(routes as any, {
35-
BB,
36-
intl,
37-
theme,
38-
});
36+
const resolvedRoutes = resolveThunk<RouteConfigWithResolveSubRoutes[]>(
37+
routes as any,
38+
screenProps
39+
);
3940

40-
function renderRoute(route: RouteConfigWithResolveSubRoutes) {
41-
const options = resolveRouteOptions(route, BB);
41+
const renderRoute = (route: RouteConfigWithResolveSubRoutes) => {
42+
// We're not able to resovle navigation object here. Open to better ideas.
43+
const options = resolveRouteOptions(route, { navigation: stubNavigationObject, screenProps });
4244

4345
return (
4446
<NavigatorComponent.Screen
@@ -48,10 +50,10 @@ export const Navigator = (props: NavigatorProps) => {
4850
options={options}
4951
/>
5052
);
51-
}
53+
};
5254

5355
return (
54-
<NavigatorComponent.Navigator screenOptions={resolveNavigatorScreenOptions(props, BB)}>
56+
<NavigatorComponent.Navigator screenOptions={resolveNavigatorScreenOptions(props)} {...rest}>
5557
{resolvedRoutes.map(renderRoute)}
5658
</NavigatorComponent.Navigator>
5759
);

src/components/Navigator/__tests__/Navigator.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BlueBaseApp } from '@bluebase/core';
22
import { NavigationNativeContainer } from '@react-navigation/native';
33
import { Navigator } from '..';
44
import React from 'react';
5+
import { ScreenView } from '../../ScreenView';
56
import { Text } from 'react-native';
67
import { mount } from 'enzyme';
78
import { waitForElement } from 'enzyme-async-helpers';
@@ -11,7 +12,7 @@ describe('Navigator', () => {
1112
const SettingsScreen = () => <Text>Settings</Text>;
1213

1314
const wrapper = mount(
14-
<BlueBaseApp components={{ Navigator }}>
15+
<BlueBaseApp components={{ Navigator, ScreenView }}>
1516
<NavigationNativeContainer>
1617
<Navigator
1718
type="stack"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { navigationToActionObject, resolveScreenComponent } from '../../helpers';
2+
import { useNavigation, useRoute } from '@react-navigation/native';
3+
4+
import React from 'react';
5+
import { RouteConfig } from '@bluebase/components';
6+
import { useBlueBase } from '@bluebase/core';
7+
8+
export interface ScreenViewProps {
9+
children?: React.ReactNode;
10+
route: RouteConfig;
11+
}
12+
13+
export const ScreenView: React.ComponentType<ScreenViewProps> = ({
14+
children,
15+
route,
16+
...rest
17+
}: ScreenViewProps) => {
18+
const state = useRoute();
19+
const navObj = useNavigation();
20+
const navigation = navigationToActionObject(navObj, state);
21+
22+
const BB = useBlueBase();
23+
24+
const ScreenComponent = resolveScreenComponent(route, BB);
25+
26+
return (
27+
<ScreenComponent {...rest} navigation={navigation}>
28+
{children}
29+
</ScreenComponent>
30+
);
31+
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
const mockedNavigation: any = {
2+
addListener: jest.fn(),
3+
closeDrawer: jest.fn(),
4+
dangerouslyGetParent: jest.fn(),
5+
dismiss: jest.fn(),
6+
dispatch: jest.fn(),
7+
getParam: jest.fn(),
8+
goBack: jest.fn(),
9+
isFocused: jest.fn(),
10+
navigate: jest.fn(),
11+
openDrawer: jest.fn(),
12+
pop: jest.fn(),
13+
popToTop: jest.fn(),
14+
push: jest.fn(),
15+
replace: jest.fn(),
16+
setParams: jest.fn(),
17+
toggleDrawer: jest.fn(),
18+
};
19+
20+
const mockedRoute = {
21+
index: 0,
22+
isTransitioning: false,
23+
key: 'kjdkj',
24+
name: 'Home',
25+
params: { foo: 'bar' },
26+
path: '/',
27+
routes: [
28+
{
29+
index: 1,
30+
isTransitioning: false,
31+
key: 'jsdfl',
32+
path: '/settings',
33+
routeName: 'Settings',
34+
routes: [],
35+
},
36+
],
37+
};
38+
39+
jest.mock('@react-navigation/native', () => {
40+
const actual = jest.requireActual('@react-navigation/native');
41+
return {
42+
...actual,
43+
44+
useNavigation: () => mockedNavigation,
45+
useRoute: () => mockedRoute,
46+
};
47+
});
48+
49+
import { BlueBaseApp } from '@bluebase/core';
50+
import Plugin from '../../../';
51+
import React from 'react';
52+
import { ScreenView } from '../ScreenView';
53+
import { Text } from 'react-native';
54+
import { mount } from 'enzyme';
55+
import { waitForElement } from 'enzyme-async-helpers';
56+
57+
describe('ScreenView', () => {
58+
it('should render SettingsScreen', async () => {
59+
const SettingsScreen = () => <Text>Settings</Text>;
60+
const children = jest.fn().mockReturnValue(null);
61+
62+
const wrapper = mount(
63+
<BlueBaseApp plugins={[Plugin]}>
64+
<ScreenView
65+
route={{
66+
name: 'Settings',
67+
path: '/',
68+
screen: SettingsScreen,
69+
70+
options: {
71+
title: 'Settings',
72+
},
73+
}}
74+
>
75+
{children}
76+
</ScreenView>
77+
</BlueBaseApp>
78+
);
79+
80+
await waitForElement(wrapper, ScreenView);
81+
expect(wrapper.find(SettingsScreen).exists()).toBe(true);
82+
});
83+
});

src/components/ScreenView/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ScreenView';

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './Navigation';
33
export * from './NavigationActions';
44
export * from './NavigationProvider';
55
export * from './Navigator';
6+
export * from './ScreenView';

src/helpers/__tests__/resolveNavigatorScreenOptions.test.tsx

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,26 @@
1-
import { BlueBase } from '@bluebase/core';
21
import { resolveNavigatorScreenOptions } from '..';
32

43
describe('resolveNavigatorScreenOptions', () => {
54
it('should resolve defaultNavigationOptions', async () => {
6-
const BB = new BlueBase();
5+
const opts = resolveNavigatorScreenOptions({
6+
defaultNavigationOptions: { headerBackTitle: 'Foo' },
7+
headerMode: 'none',
8+
} as any);
79

8-
const opts = resolveNavigatorScreenOptions(
9-
{ defaultNavigationOptions: { headerBackTitle: 'Foo' }, headerMode: 'none' } as any,
10-
BB
11-
);
12-
13-
expect(opts).toMatchObject({ headerBackTitle: 'Foo', headerMode: 'none' });
10+
expect(opts).toMatchObject({ headerBackTitle: 'Foo' });
1411
});
1512

1613
it('should recognise "screenOptions" prop', async () => {
17-
const BB = new BlueBase();
18-
19-
const opts = resolveNavigatorScreenOptions(
20-
{ screenOptions: { headerBackTitle: 'Foo' }, headerMode: 'none' } as any,
21-
BB
22-
);
14+
const opts = resolveNavigatorScreenOptions({
15+
screenOptions: { headerBackTitle: 'Foo' },
16+
headerMode: 'none',
17+
} as any);
2318

24-
expect(opts).toMatchObject({ headerBackTitle: 'Foo', headerMode: 'none' });
19+
expect(opts).toMatchObject({ headerBackTitle: 'Foo' });
2520
});
2621

2722
it('should return empty object if nothing suitable is provided', async () => {
28-
const BB = new BlueBase();
29-
30-
const opts = resolveNavigatorScreenOptions({} as any, BB);
23+
const opts = resolveNavigatorScreenOptions({} as any);
3124

3225
expect(opts).toMatchObject({});
3326
});

0 commit comments

Comments
 (0)