Skip to content

Commit ed8b86b

Browse files
DawidVanderhoven-TomTomdawidvdhgorhom
authored
feat: added bottom inset (#237)
* feat: add a bottom inset option * fix: remove debug * feat: add footer example and fix modal behaviour * fix: remove subtraction * fix: modal handle shows when stacked and minimized * fix: set top and bottom of the root container as not to break other modals * chore: updated normalize snap points calculating * refactor: container styling * fix: dismissing modals when unmounting * chore: updated exmaple * chore: updated bottom sheet props description Co-authored-by: Dawid van der Hoven <[email protected]> Co-authored-by: Mo Gorhom <[email protected]>
1 parent 99a16b1 commit ed8b86b

File tree

12 files changed

+256
-26
lines changed

12 files changed

+256
-26
lines changed

example/src/App.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ const App = () => {
7878
}}
7979
getComponent={() => require('./screens/modal/StackExample').default}
8080
/>
81+
<Stack.Screen
82+
name="Modal/StackWithBottomInsetExample"
83+
options={{
84+
title: 'Stack Modals With Footer Example',
85+
}}
86+
getComponent={() =>
87+
require('./screens/modal/StackWithBottomInsetExample').default
88+
}
89+
/>
8190
<Stack.Screen
8291
name="Modal/DynamicSnapPointExample"
8392
options={{

example/src/screens/Root.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ const data = [
4141
name: 'Stack Modals',
4242
slug: 'Modal/StackExample',
4343
},
44+
{
45+
name: 'Stack Modals With Footer',
46+
slug: 'Modal/StackWithBottomInsetExample',
47+
},
4448
{
4549
name: 'Dynamic Snap Point',
4650
slug: 'Modal/DynamicSnapPointExample',
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import React, { useCallback, useMemo, useRef } from 'react';
2+
import { View, StyleSheet } from 'react-native';
3+
import { BottomSheetModal, useBottomSheetModal } from '@gorhom/bottom-sheet';
4+
import Button from '../../components/button';
5+
import ContactListContainer from '../../components/contactListContainer';
6+
import withModalProvider from '../withModalProvider';
7+
8+
const footerHeight = 100;
9+
10+
const StackExample = () => {
11+
// hooks
12+
const { dismiss, dismissAll } = useBottomSheetModal();
13+
14+
// refs
15+
const bottomSheetModalARef = useRef<BottomSheetModal>(null);
16+
const bottomSheetModalBRef = useRef<BottomSheetModal>(null);
17+
const bottomSheetModalCRef = useRef<BottomSheetModal>(null);
18+
19+
// variables
20+
const snapPoints = useMemo(() => ['25%', '50%'], []);
21+
22+
// callbacks
23+
const handlePresentAPress = useCallback(() => {
24+
if (bottomSheetModalARef.current) {
25+
bottomSheetModalARef.current.present();
26+
}
27+
}, []);
28+
const handleDismissAPress = useCallback(() => {
29+
if (bottomSheetModalARef.current) {
30+
bottomSheetModalARef.current.dismiss();
31+
}
32+
}, []);
33+
const handlePresentBPress = useCallback(() => {
34+
if (bottomSheetModalBRef.current) {
35+
bottomSheetModalBRef.current.present();
36+
}
37+
}, []);
38+
const handleDismissBPress = useCallback(() => {
39+
if (bottomSheetModalBRef.current) {
40+
bottomSheetModalBRef.current.dismiss();
41+
}
42+
}, []);
43+
const handlePresentCPress = useCallback(() => {
44+
if (bottomSheetModalCRef.current) {
45+
bottomSheetModalCRef.current.present();
46+
}
47+
}, []);
48+
const handleDismissCPress = useCallback(() => {
49+
if (bottomSheetModalCRef.current) {
50+
bottomSheetModalCRef.current.dismiss();
51+
}
52+
}, []);
53+
const handleDismissAllPress = useCallback(() => {
54+
dismissAll();
55+
}, [dismissAll]);
56+
57+
const handleDismissByHookPress = useCallback(() => {
58+
dismiss('A');
59+
}, [dismiss]);
60+
61+
// renders
62+
63+
const renderBottomSheetContent = useCallback(
64+
(title, onPress) => (
65+
<ContactListContainer
66+
title={title}
67+
type="FlatList"
68+
onItemPress={onPress}
69+
/>
70+
),
71+
[]
72+
);
73+
return (
74+
<>
75+
<View style={styles.container}>
76+
<Button
77+
label="Present Modal A"
78+
style={styles.buttonContainer}
79+
onPress={handlePresentAPress}
80+
/>
81+
<Button
82+
label="Dismiss Modal A"
83+
style={styles.buttonContainer}
84+
onPress={handleDismissAPress}
85+
/>
86+
<Button
87+
label="Present Modal B"
88+
style={styles.buttonContainer}
89+
onPress={handlePresentBPress}
90+
/>
91+
<Button
92+
label="Dismiss Modal B"
93+
style={styles.buttonContainer}
94+
onPress={handleDismissBPress}
95+
/>
96+
<Button
97+
label="Present Modal C"
98+
style={styles.buttonContainer}
99+
onPress={handlePresentCPress}
100+
/>
101+
<Button
102+
label="Dismiss Modal C"
103+
style={styles.buttonContainer}
104+
onPress={handleDismissCPress}
105+
/>
106+
107+
<Button
108+
label="Dismiss A By Hook"
109+
style={styles.buttonContainer}
110+
onPress={handleDismissByHookPress}
111+
/>
112+
113+
<BottomSheetModal
114+
name="A"
115+
ref={bottomSheetModalARef}
116+
snapPoints={snapPoints}
117+
bottomInset={footerHeight}
118+
children={renderBottomSheetContent('Modal A', handlePresentBPress)}
119+
/>
120+
121+
<BottomSheetModal
122+
name="B"
123+
ref={bottomSheetModalBRef}
124+
snapPoints={snapPoints}
125+
bottomInset={footerHeight}
126+
children={renderBottomSheetContent('Modal B', handlePresentCPress)}
127+
/>
128+
129+
<BottomSheetModal
130+
name="C"
131+
ref={bottomSheetModalCRef}
132+
index={1}
133+
snapPoints={snapPoints}
134+
dismissOnPanDown={false}
135+
bottomInset={footerHeight}
136+
children={renderBottomSheetContent('Modal C', handleDismissCPress)}
137+
/>
138+
</View>
139+
<View style={styles.footer}>
140+
<Button
141+
label="Dismiss All Modal"
142+
style={styles.buttonContainer}
143+
onPress={handleDismissAllPress}
144+
/>
145+
</View>
146+
</>
147+
);
148+
};
149+
150+
const styles = StyleSheet.create({
151+
container: {
152+
flex: 1,
153+
padding: 24,
154+
},
155+
buttonContainer: {
156+
marginBottom: 6,
157+
},
158+
footer: {
159+
height: footerHeight,
160+
position: 'absolute',
161+
bottom: 0,
162+
width: '100%',
163+
zIndex: 10,
164+
backgroundColor: 'white',
165+
padding: 24,
166+
167+
shadowColor: 'black',
168+
shadowOffset: {
169+
width: 0,
170+
height: -20,
171+
},
172+
shadowOpacity: 0.1,
173+
shadowRadius: 10,
174+
elevation: 16,
175+
},
176+
});
177+
178+
export default withModalProvider(StackExample);

example/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type AppStackParamsList = {
1010
['Modal/SimpleExample']: undefined;
1111
['Modal/BackdropExample']: undefined;
1212
['Modal/StackExample']: undefined;
13+
['Modal/StackWithBottomInsetExample']: undefined;
1314
['Modal/DynamicSnapPointExample']: undefined;
1415
// Advanced
1516
['Advanced/NavigatorExample']: undefined;

src/components/bottomSheet/BottomSheet.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
9494
handleHeight: _providedHandleHeight,
9595
containerHeight: _providedContainerHeight,
9696
topInset = 0,
97+
bottomInset = 0,
9798
enableContentPanningGesture = DEFAULT_ENABLE_CONTENT_PANNING_GESTURE,
9899
enableHandlePanningGesture = DEFAULT_ENABLE_HANDLE_PANNING_GESTURE,
99100
animateOnMount = DEFAULT_ANIMATE_ON_MOUNT,
@@ -183,7 +184,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
183184

184185
const snapPoints = useNormalizedSnapPoints(
185186
_providedSnapPoints,
186-
topInset,
187+
topInset + bottomInset,
187188
safeContainerHeight,
188189
safeHandleHeight
189190
);
@@ -451,14 +452,20 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
451452
{
452453
translateY: cond(
453454
animatedIsLayoutReady,
454-
position,
455+
sub(position, bottomInset),
455456
safeContainerHeight
456457
),
457458
},
458459
],
459460
},
460461
],
461-
[safeContainerHeight, _providedStyle, position, animatedIsLayoutReady]
462+
[
463+
safeContainerHeight,
464+
_providedStyle,
465+
position,
466+
animatedIsLayoutReady,
467+
bottomInset,
468+
]
462469
);
463470
const contentContainerStyle = useMemo(
464471
() => ({
@@ -595,13 +602,13 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
595602
//#endregion
596603

597604
//#region render
598-
// console.log(
599-
// 'BottomSheet',
600-
// 'render',
605+
// console.log('BottomSheet', 'render', {
601606
// snapPoints,
602-
// sheetHeight,
603-
// safeHandleHeight
604-
// );
607+
// shouldMeasureContainerHeight,
608+
// safeContainerHeight,
609+
// topInset,
610+
// bottomInset,
611+
// });
605612
return (
606613
<BottomSheetProvider value={externalContextVariables}>
607614
<BottomSheetBackdropContainer
@@ -614,6 +621,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
614621
key="BottomSheetContainer"
615622
shouldMeasureHeight={shouldMeasureContainerHeight}
616623
onMeasureHeight={handleOnContainerMeasureHeight}
624+
topInset={topInset}
625+
bottomInset={bottomInset}
617626
>
618627
<BottomSheetContentWrapper
619628
key="BottomSheetContentWrapper"

src/components/bottomSheet/types.d.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,19 @@ export type BottomSheetProps = {
3838
*/
3939
containerHeight?: number;
4040
/**
41-
* Top inset value helps to calculate percentage snap points values,
42-
* usually comes from `@react-navigation/stack` hook `useHeaderHeight` or from `react-native-safe-area-context` hook `useSafeArea`.
41+
* Top inset to be added to the bottom sheet container,
42+
* usually comes from `@react-navigation/stack` hook `useHeaderHeight`
43+
* or from `react-native-safe-area-context` hook `useSafeArea`.
4344
* @type number
4445
* @default 0
4546
*/
4647
topInset?: number;
48+
/**
49+
* Bottom inset to be added to the bottom sheet container.
50+
* @type number
51+
* @default 0
52+
*/
53+
bottomInset?: number;
4754
/**
4855
* Enable content panning gesture interaction.
4956
* @type boolean

src/components/bottomSheetContainer/BottomSheetContainer.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { memo, useCallback } from 'react';
2-
import { View } from 'react-native';
1+
import React, { memo, useCallback, useMemo } from 'react';
2+
import { View, ViewStyle } from 'react-native';
33
import isEqual from 'lodash.isequal';
44
import type { BottomSheetContainerProps } from './types';
55
import { styles } from './styles';
@@ -8,6 +8,8 @@ const BottomSheetContainerComponent = ({
88
shouldMeasureHeight,
99
onMeasureHeight,
1010
children,
11+
topInset = 0,
12+
bottomInset = 0,
1113
}: BottomSheetContainerProps) => {
1214
//#region callbacks
1315
const handleOnLayout = useCallback(
@@ -22,12 +24,23 @@ const BottomSheetContainerComponent = ({
2224
);
2325
//#endregion
2426

27+
const containerStyle: ViewStyle[] = useMemo(
28+
() => [
29+
styles.container,
30+
{
31+
top: topInset,
32+
bottom: bottomInset,
33+
},
34+
],
35+
[bottomInset, topInset]
36+
);
37+
2538
//#region render
2639
// console.log('BottomSheetContainer', 'render', shouldMeasureHeight);
2740
return (
2841
<View
2942
pointerEvents="box-none"
30-
style={styles.container}
43+
style={containerStyle}
3144
onLayout={shouldMeasureHeight ? handleOnLayout : undefined}
3245
children={children}
3346
/>

src/components/bottomSheetContainer/styles.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ import { StyleSheet } from 'react-native';
33
export const styles = StyleSheet.create({
44
container: {
55
...StyleSheet.absoluteFillObject,
6+
overflow: 'hidden',
67
},
78
});

src/components/bottomSheetContainer/types.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ export interface BottomSheetContainerProps {
44
shouldMeasureHeight: boolean;
55
onMeasureHeight: (height: number) => void;
66
children: ReactNode;
7+
topInset?: number;
8+
bottomInset?: nummber;
79
}

0 commit comments

Comments
 (0)