import {
NativeStackView,
type NativeStackDescriptorMap,
type NativeStackNavigationOptions,
type NativeStackViewState,
StackRouter,
createStandardRouterNavigator,
type NavigatorContentProps,
} from 'expo-router';
import type { ReactNode } from 'react';
import { Pressable, StyleSheet, View } from 'react-native';
type Options = NativeStackNavigationOptions & { modal?: boolean };
function YourModal({ children, onDismiss }: { children: ReactNode; onDismiss: () => void }) {
return (
<View style={styles.overlay}>
<Pressable onPress={onDismiss} style={StyleSheet.absoluteFill} />
<View style={styles.dialog}>{children}</View>
</View>
);
}
function ModalStackContent({ state, descriptors, actions }: NavigatorContentProps<Options>) {
const activeRoutes = state.routes.slice(0, state.index + 1);
const lastNonModalRouteIndex = activeRoutes.findLastIndex(
route => !descriptors[route.key].options.modal
);
const hasModals = lastNonModalRouteIndex < state.index;
const nativeStackDescriptors = descriptors as unknown as NativeStackDescriptorMap;
if (!hasModals) {
return (
<NativeStackView state={state as NativeStackViewState} descriptors={nativeStackDescriptors} />
);
}
const baseStackRoutes = activeRoutes.slice(0, lastNonModalRouteIndex + 1);
const baseStackPreloadedRoutes = state.routes.slice(state.index + 1);
const baseStackState = {
...state,
index: lastNonModalRouteIndex,
routes: [...baseStackRoutes, ...baseStackPreloadedRoutes],
} as NativeStackViewState;
const modalRoutes = activeRoutes.slice(lastNonModalRouteIndex + 1);
return (
<View style={{ flex: 1 }}>
<NativeStackView state={baseStackState} descriptors={nativeStackDescriptors} />
{modalRoutes.map(route => (
<YourModal key={route.key} onDismiss={actions.back}>
{descriptors[route.key].render()}
</YourModal>
))}
</View>
);
}
export const ModalStack = createStandardRouterNavigator(ModalStackContent, StackRouter);
const styles = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.4)',
padding: 24,
},
dialog: { width: '100%', maxWidth: 640, backgroundColor: 'white', padding: 24 },
});