Files
react-navigation/example/StackNavigator.tsx
2019-07-18 15:14:47 +02:00

307 lines
7.2 KiB
TypeScript

/* eslint-disable react-native/no-inline-styles */
import * as React from 'react';
import shortid from 'shortid';
import {
useNavigationBuilder,
NavigationProp,
CommonAction,
ParamListBase,
Router,
createNavigator,
} from '../src/index';
type Props = {
initialRouteName?: string;
children: React.ReactNode;
};
type Action =
| {
type: 'PUSH';
payload: { name: string; params?: object };
}
| {
type: 'POP';
payload: { count: number };
}
| { type: 'POP_TO_TOP' };
export type StackNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = NavigationProp<ParamList, RouteName> & {
/**
* 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 void
? [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;
};
const StackRouter: Router<CommonAction | Action> = {
getInitialState({
routeNames,
initialRouteName = routeNames[0],
initialParamsList,
}) {
const index = routeNames.indexOf(initialRouteName);
return {
key: `stack-${shortid()}`,
index,
routeNames,
routes: routeNames.slice(0, index + 1).map(name => ({
name,
key: `${name}-${shortid()}`,
params: initialParamsList[name],
})),
};
},
getRehydratedState({ routeNames, partialState }) {
let state = partialState;
if (state.routeNames === undefined || state.key === undefined) {
state = {
...state,
routeNames,
key: `stack-${shortid()}`,
};
}
return state;
},
getStateForRouteNamesChange(state, { routeNames }) {
return {
...state,
routeNames,
routes: state.routes.filter(route => routeNames.includes(route.name)),
};
},
getStateForAction(state, action) {
switch (action.type) {
case 'PUSH':
if (state.routeNames.includes(action.payload.name)) {
return {
...state,
index: state.index + 1,
routes: [
...state.routes,
{
key: `${action.payload.name}-${shortid()}`,
name: action.payload.name,
params: action.payload.params,
},
],
};
}
return null;
case 'POP':
if (state.index > 0) {
return {
...state,
index: state.index - 1,
routes: state.routes.slice(
0,
Math.max(state.routes.length - action.payload.count, 1)
),
};
}
return null;
case 'POP_TO_TOP':
return StackRouter.getStateForAction(state, {
type: 'POP',
payload: { count: state.routes.length - 1 },
});
case 'NAVIGATE':
if (state.routeNames.includes(action.payload.name)) {
// If the route already exists, navigate to that
let index = -1;
if (state.routes[state.index].name === action.payload.name) {
index = state.index;
} else {
for (let i = state.routes.length - 1; i >= 0; i--) {
if (state.routes[i].name === action.payload.name) {
index = i;
break;
}
}
}
if (index === -1) {
return StackRouter.getStateForAction(state, {
type: 'PUSH',
payload: action.payload,
});
}
return {
...state,
index,
routes: [
...state.routes.slice(0, index),
action.payload.params !== undefined
? {
...state.routes[index],
params: {
...state.routes[index].params,
...action.payload.params,
},
}
: state.routes[index],
],
};
}
return null;
case 'REPLACE': {
return {
...state,
routes: state.routes.map((route, i) =>
i === state.index
? {
key: `${action.payload.name}-${shortid()}`,
name: action.payload.name,
params: action.payload.params,
}
: route
),
};
}
case 'GO_BACK':
return StackRouter.getStateForAction(state, {
type: 'POP',
payload: { count: 1 },
});
case 'RESET': {
if (
action.payload.key === undefined ||
action.payload.key === state.key
) {
return {
...action.payload,
key: state.key,
routeNames: state.routeNames,
};
}
return null;
}
default:
return null;
}
},
getStateForChildUpdate(state, { update, focus, key }) {
const index = state.routes.findIndex(r => r.key === key);
if (index === -1) {
return state;
}
return {
...state,
index: focus ? index : state.index,
routes: focus
? [
...state.routes.slice(0, index),
{ ...state.routes[index], state: update },
]
: state.routes.map((route, i) =>
i === index ? { ...route, state: update } : route
),
};
},
shouldActionPropagateToChildren(action) {
return action.type === 'NAVIGATE';
},
shouldActionChangeFocus(action) {
return action.type === 'NAVIGATE';
},
actionCreators: {
push(name: string, params?: object) {
return { type: 'PUSH', payload: { name, params } };
},
pop(count: number = 1) {
return { type: 'POP', payload: { count } };
},
popToTop() {
return { type: 'POP_TO_TOP' };
},
},
};
export function StackNavigator(props: Props) {
const { state, descriptors } = useNavigationBuilder(StackRouter, props);
return (
<div style={{ position: 'relative' }}>
{state.routes.map((route, i) => (
<div
key={route.key}
style={{
position: 'absolute',
margin: 20,
left: i * 20,
top: i * 20,
padding: 10,
height: 480,
width: 320,
backgroundColor: 'white',
borderRadius: 3,
boxShadow:
'0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',
}}
>
{descriptors[route.key].render()}
</div>
))}
<div
style={{
position: 'absolute',
left: 40,
width: 120,
padding: 10,
backgroundColor: 'tomato',
borderRadius: 3,
boxShadow: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',
}}
>
{descriptors[state.routes[state.index].key].options.title}
</div>
</div>
);
}
export default createNavigator(StackNavigator);