Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions example/demo-components/src/DemoScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useTheme } from './theme/colors';
import { IS_FABRIC } from './constants';
import { EmbeddedModal } from './modals/embedded/EmbeddedModal';
import { FlashModal } from './modals/flash-modal/FlashModal';
import { EdgeToEdgeModal } from './modals/edge-to-edge/EdgeToEdgeModal';

type DemoCase = {
id: string;
Expand All @@ -30,6 +31,7 @@ type DemoCase = {

export const DemoScreen = () => {
const { colors } = useTheme();
const { colors } = useTheme();

const [activeCases, setActiveCases] = useState<DemoCase[]>([]);

Expand Down Expand Up @@ -137,6 +139,13 @@ export const DemoScreen = () => {
description: 'Tap Flash: a submodal is dismissed before it renders',
Component: FlashModal,
},
{
id: 'edge-to-edge',
title: 'Edge To Edge',
description:
'Content stays within the safe area while the backdrop covers system bars.',
Component: EdgeToEdgeModal,
},
],
[openModal],
);
Expand Down Expand Up @@ -171,16 +180,22 @@ export const DemoScreen = () => {
return (
<SafeAreaView
style={[styles.safeArea, { backgroundColor: colors.background }]}
>
style={[styles.safeArea, { backgroundColor: colors.background }]}
>
<ScrollView
style={styles.scroll}
contentContainerStyle={styles.scrollContent}
>
<Typography testID='screen-top' style={styles.architecture}>
contentContainerStyle={styles.scrollContent}
>
<Typography testID='screen-top' style={styles.architecture}>
Current Architecture: {IS_FABRIC ? 'Fabric 🚀' : 'Paper ✈️'}
</Typography>

<View style={styles.casesContainer}>
{demoCases.map(({ title, description, id }) => (
{demoCases.map(({ title, description, id }) => (
<ScenarioCard
key={id}
Expand Down
8 changes: 8 additions & 0 deletions example/demo-components/src/modals.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@
"e2e": {
"scenarios": ["flash_dismiss"]
}
},
{
"id": "edge-to-edge",
"title": "Edge To Edge",
"description": "A full-screen modal whose content stays within the safe area while the backdrop covers the system bars.",
"e2e": {
"scenarios": ["simple_dismiss_by_button"]
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { FC } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { ModalView } from 'react-native-multiple-modals';
import { SafeAreaView } from 'react-native-safe-area-context';
import { AlertContent } from '../../components/alert-content/AlertContent';
import { BaseModalProps } from '../BaseModal';

type EdgeToEdgeModalProps = BaseModalProps;

export const EdgeToEdgeModal: FC<EdgeToEdgeModalProps> = props => {
return (
<ModalView
onRequestDismiss={props.onRequestDismiss}
backdropColor='#0d47a1'
contentContainerStyle={styles.contentContainer}
statusBar={{ barStyle: 'light-content', translucent: true }}
>
<SafeAreaView style={styles.body}>
<View
style={[styles.edge, styles.topEdge]}
testID={`${props.testID}-top-edge`}
>
<Text style={styles.edgeText}>↑ TOP EDGE ↑</Text>
</View>

<AlertContent {...props} />

<View
style={[styles.edge, styles.bottomEdge]}
testID={`${props.testID}-bottom-edge`}
>
<Text style={styles.edgeText}>↓ BOTTOM EDGE ↓</Text>
</View>
</SafeAreaView>
</ModalView>
);
};

const styles = StyleSheet.create({
contentContainer: {
flex: 1,
},
body: {
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
},
edge: {
alignSelf: 'stretch',
paddingVertical: 18,
alignItems: 'center',
backgroundColor: '#e53935',
},
topEdge: {},
bottomEdge: {},
edgeText: {
color: 'white',
fontWeight: '700',
fontSize: 16,
},
});
67 changes: 35 additions & 32 deletions src/ModalView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Platform, Pressable, StatusBar, StyleSheet, View } from 'react-native';
import { LayoutInspectorProvider } from './LayoutInspectorProvider';
import { ScrollContextResetter } from './ScrollContextResetter';
import { GestureHandlerRootView } from './integrations/GestureHandlerRootView';
import { SafeAreaProvider } from './integrations/SafeAreaProvider';
import RNTModalView from './newarch/NativeRNTModalView';
import type { ModalViewProps } from './types';

Expand Down Expand Up @@ -40,39 +41,41 @@ export const ModalView: FC<ModalViewProps> = ({
animationType={animationType}
>
<View collapsable={false} style={styles.flex}>
<LayoutInspectorProvider>
{isIOS && statusBar && !disableDefaultStatusBarIOS ? (
<StatusBar {...statusBar} />
) : null}
<GestureHandlerRootView style={styles.flex}>
{showBackdrop && (
<View style={[styles.backdropContainer]}>
<BackdropPressableComponent
accessibilityLabel={backdropAccessibilityLabel}
accessibilityHint={backdropAccessibilityHint}
style={styles.flex}
onPress={() => onRequestDismiss?.(DismissalSource.Backdrop)}
<SafeAreaProvider style={styles.flex}>
<LayoutInspectorProvider>
{isIOS && statusBar && !disableDefaultStatusBarIOS ? (
<StatusBar {...statusBar} />
) : null}
<GestureHandlerRootView style={styles.flex}>
{showBackdrop && (
<View style={[styles.backdropContainer]}>
<BackdropPressableComponent
accessibilityLabel={backdropAccessibilityLabel}
accessibilityHint={backdropAccessibilityHint}
style={styles.flex}
onPress={() => onRequestDismiss?.(DismissalSource.Backdrop)}
>
{renderBackdrop ? (
renderBackdrop()
) : (
<View
style={[styles.flex, { backgroundColor: backdropColor }]}
/>
)}
</BackdropPressableComponent>
</View>
)}
<ScrollContextResetter>
<View
pointerEvents='box-none'
style={[styles.content, contentContainerStyle]}
>
{renderBackdrop ? (
renderBackdrop()
) : (
<View
style={[styles.flex, { backgroundColor: backdropColor }]}
/>
)}
</BackdropPressableComponent>
</View>
)}
<ScrollContextResetter>
<View
pointerEvents='box-none'
style={[styles.content, contentContainerStyle]}
>
{children}
</View>
</ScrollContextResetter>
</GestureHandlerRootView>
</LayoutInspectorProvider>
{children}
</View>
</ScrollContextResetter>
</GestureHandlerRootView>
</LayoutInspectorProvider>
</SafeAreaProvider>
</View>
</RNTModalView>
);
Expand Down
29 changes: 29 additions & 0 deletions src/integrations/SafeAreaProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ReactNode } from 'react';

const importSafeAreaProvider = () => {
const Errors = {
SafeAreaProviderNotFound: new Error(
'SafeAreaProvider not found. Please report the issue: https://github.com/paufau/react-native-multiple-modals/issues',
),
};

try {
const SafeAreaProviderComponent =
require('react-native-safe-area-context').SafeAreaProvider;

if (!SafeAreaProviderComponent) {
throw Errors.SafeAreaProviderNotFound;
}

return SafeAreaProviderComponent;
} catch (e) {
if (e === Errors.SafeAreaProviderNotFound) {
console.error(e);
}

// Fallback component used if the library is not found
return ({ children }: { children: ReactNode }) => children;
}
};

export const SafeAreaProvider = importSafeAreaProvider();
Loading