Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: dynamic snap points on modals #113

Merged
merged 3 commits into from
Dec 14, 2020
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
9 changes: 9 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ const App = () => {
}}
getComponent={() => require('./screens/modal/StackExample').default}
/>
<Stack.Screen
name="Modal/DynamicSnapPointExample"
options={{
title: 'Dynamic Snap Point',
}}
getComponent={() =>
require('./screens/modal/DynamicSnapPointExample').default
}
/>
{/* advanced examples */}
<Stack.Screen
name="Advanced/NavigatorExample"
Expand Down
4 changes: 4 additions & 0 deletions example/src/screens/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const data = [
name: 'Stack Modals',
slug: 'Modal/StackExample',
},
{
name: 'Dynamic Snap Point',
slug: 'Modal/DynamicSnapPointExample',
},
],
},
{
Expand Down
144 changes: 144 additions & 0 deletions example/src/screens/modal/DynamicSnapPointExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Button from '../../components/button';
import withModalProvider from '../withModalProvider';

const DynamicSnapPointExample = () => {
// state
const [count, setCount] = useState(0);
const [contentHeight, setContentHeight] = useState(0);

// hooks
const bottomSheetRef = useRef<BottomSheetModal>(null);
const { bottom: safeBottomArea } = useSafeAreaInsets();

// variables
const snapPoints = useMemo(() => [contentHeight], [contentHeight]);

// callbacks
const handleIncreaseContentPress = useCallback(() => {
setCount(state => state + 1);
}, []);
const handleDecreaseContentPress = useCallback(() => {
setCount(state => Math.max(state - 1, 0));
}, []);

const handlePresentPress = useCallback(() => {
bottomSheetRef.current?.present();
}, []);
const handleDismissPress = useCallback(() => {
bottomSheetRef.current?.dismiss();
}, []);
const handleOnLayout = useCallback(
({
nativeEvent: {
layout: { height },
},
}) => {
setContentHeight(height);
},
[]
);

// styles
const contentContainerStyle = useMemo(
() => ({
...styles.contentContainerStyle,
paddingBottom: safeBottomArea,
}),
[safeBottomArea]
);
const emojiContainerStyle = useMemo(
() => ({
...styles.emojiContainer,
height: 50 * count,
}),
[count]
);

// renders
const renderBackground = useCallback(
() => <View style={styles.background} />,
[]
);

return (
<View style={styles.container}>
<Button
label="Present"
style={styles.buttonContainer}
onPress={handlePresentPress}
/>
<Button
label="Dismiss"
style={styles.buttonContainer}
onPress={handleDismissPress}
/>
<BottomSheetModal
ref={bottomSheetRef}
index={0}
snapPoints={snapPoints}
backgroundComponent={renderBackground}
>
<BottomSheetView
style={contentContainerStyle}
onLayout={handleOnLayout}
>
<Text style={styles.message}>
Could this sheet modal resize to its content height ?
</Text>
<View style={emojiContainerStyle}>
<Text style={styles.emoji}>😍</Text>
</View>
<Button
label="Yes"
style={styles.buttonContainer}
onPress={handleIncreaseContentPress}
/>
<Button
label="Maybe"
style={styles.buttonContainer}
onPress={handleDecreaseContentPress}
/>
</BottomSheetView>
</BottomSheetModal>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
},
background: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'white',
},
buttonContainer: {
marginBottom: 6,
},
contentContainerStyle: {
paddingTop: 12,
paddingHorizontal: 24,
backgroundColor: 'white',
},
message: {
fontSize: 24,
fontWeight: '600',
marginBottom: 12,
},
emoji: {
fontSize: 156,
textAlign: 'center',
alignSelf: 'center',
},
emojiContainer: {
overflow: 'hidden',
justifyContent: 'center',
},
});

export default withModalProvider(DynamicSnapPointExample);
1 change: 1 addition & 0 deletions example/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type AppStackParamsList = {
['Modal/SimpleExample']: undefined;
['Modal/BackdropExample']: undefined;
['Modal/StackExample']: undefined;
['Modal/DynamicSnapPointExample']: undefined;
// Advanced
['Advanced/NavigatorExample']: undefined;
['Advanced/CustomHandleExample']: undefined;
Expand Down
6 changes: 3 additions & 3 deletions src/components/bottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
[handleHeight]
);
const safeContainerHeight = useMemo(
() => containerHeight || WINDOW_HEIGHT,
[containerHeight]
() => _providedContainerHeight || containerHeight || WINDOW_HEIGHT,
[_providedContainerHeight, containerHeight]
);

// conditions
Expand Down Expand Up @@ -508,7 +508,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
//#endregion

//#region render
// console.log('BottomSheet', 'render', shouldMeasureContainerHeight);
// console.log('BottomSheet', 'render', snapPoints, safeHandleHeight);
return (
<BottomSheetProvider value={externalContextVariables}>
<BottomSheetBackdropContainer
Expand Down
7 changes: 7 additions & 0 deletions src/hooks/useNormalizedSnapPoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export const useNormalizedSnapPoints = (
topInset
);
return normalizedSnapPoints.map(normalizedSnapPoint => {
/**
* we subset handleHeight from the `normalizedSnapPoint` to make
* sure that sheets and its handle will be out of the screen.
*/
if (normalizedSnapPoint === 0 && handleHeight !== 0) {
normalizedSnapPoint = normalizedSnapPoint - handleHeight;
}
return Math.ceil(
Math.max(containerHeight - normalizedSnapPoint - handleHeight, topInset)
);
Expand Down