Skip to content

Commit

Permalink
feat: added dynamic sizing (#1513)(with @Eli-Nathan & @ororsatti)
Browse files Browse the repository at this point in the history
* feat: added dynamic sizing

* chore: updated dynamic sizing example

* chore: removed commented code

* chore: added deprecated tag to useBottomSheetDynamicSnapPoints

* chore: added extra description for snap points prop
gorhom authored Sep 10, 2023
1 parent 43de6d7 commit 7330c7c
Showing 12 changed files with 178 additions and 93 deletions.
21 changes: 3 additions & 18 deletions example/app/src/screens/advanced/DynamicSnapPointExample.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import BottomSheet, {
BottomSheetView,
useBottomSheetDynamicSnapPoints,
} from '@gorhom/bottom-sheet';
import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Button } from '../../components/button';

const DynamicSnapPointExample = () => {
// state
const [count, setCount] = useState(0);
const initialSnapPoints = useMemo(() => ['CONTENT_HEIGHT'], []);

// hooks
const { bottom: safeBottomArea } = useSafeAreaInsets();
const bottomSheetRef = useRef<BottomSheet>(null);
const {
animatedHandleHeight,
animatedSnapPoints,
animatedContentHeight,
handleContentLayout,
} = useBottomSheetDynamicSnapPoints(initialSnapPoints);

// callbacks
const handleIncreaseContentPress = useCallback(() => {
@@ -59,16 +49,11 @@ const DynamicSnapPointExample = () => {
<Button label="Close" onPress={handleClosePress} />
<BottomSheet
ref={bottomSheetRef}
snapPoints={animatedSnapPoints}
handleHeight={animatedHandleHeight}
contentHeight={animatedContentHeight}
enableDynamicSizing={true}
enablePanDownToClose={true}
animateOnMount={true}
>
<BottomSheetView
style={contentContainerStyle}
onLayout={handleContentLayout}
>
<BottomSheetView style={contentContainerStyle}>
<Text style={styles.message}>
Could this sheet resize to its content height ?
</Text>
22 changes: 3 additions & 19 deletions example/app/src/screens/modal/DynamicSnapPointExample.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import {
BottomSheetModal,
BottomSheetView,
useBottomSheetDynamicSnapPoints,
} from '@gorhom/bottom-sheet';
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 initialSnapPoints = useMemo(() => ['CONTENT_HEIGHT'], []);

// hooks
const { bottom: safeBottomArea } = useSafeAreaInsets();
const bottomSheetRef = useRef<BottomSheetModal>(null);
const {
animatedHandleHeight,
animatedSnapPoints,
animatedContentHeight,
handleContentLayout,
} = useBottomSheetDynamicSnapPoints(initialSnapPoints);

// callbacks
const handleIncreaseContentPress = useCallback(() => {
@@ -62,15 +51,10 @@ const DynamicSnapPointExample = () => {
<Button label="Dismiss" onPress={handleDismissPress} />
<BottomSheetModal
ref={bottomSheetRef}
snapPoints={animatedSnapPoints}
handleHeight={animatedHandleHeight}
contentHeight={animatedContentHeight}
enableDynamicSizing={true}
enablePanDownToClose={true}
>
<BottomSheetView
style={contentContainerStyle}
onLayout={handleContentLayout}
>
<BottomSheetView style={contentContainerStyle}>
<Text style={styles.message}>
Could this sheet modal resize to its content height ?
</Text>
46 changes: 31 additions & 15 deletions src/components/bottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -74,6 +74,7 @@ import {
DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE,
INITIAL_CONTAINER_OFFSET,
INITIAL_VALUE,
DEFAULT_DYNAMIC_SIZING,
} from './constants';
import type { BottomSheetMethods, Insets } from '../../types';
import type { BottomSheetProps, AnimateToPositionType } from './types';
@@ -104,6 +105,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
enableHandlePanningGesture = DEFAULT_ENABLE_HANDLE_PANNING_GESTURE,
enableOverDrag = DEFAULT_ENABLE_OVER_DRAG,
enablePanDownToClose = DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE,
enableDynamicSizing = DEFAULT_DYNAMIC_SIZING,
overDragResistanceFactor = DEFAULT_OVER_DRAG_RESISTANCE_FACTOR,

// styles
@@ -128,6 +130,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
containerOffset: _providedContainerOffset,
topInset = 0,
bottomInset = 0,
maxDynamicContentSize,

// animated callback shared values
animatedPosition: _providedAnimatedPosition,
@@ -185,12 +188,14 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
_providedHandleHeight ?? INITIAL_HANDLE_HEIGHT
);
const animatedFooterHeight = useSharedValue(0);
const animatedContentHeight = useSharedValue(INITIAL_CONTAINER_HEIGHT);
const animatedSnapPoints = useNormalizedSnapPoints(
_providedSnapPoints,
animatedContainerHeight,
topInset,
bottomInset,
$modal
animatedContentHeight,
animatedHandleHeight,
enableDynamicSizing,
maxDynamicContentSize
);
const animatedHighestSnapPoint = useDerivedValue(
() => animatedSnapPoints.value[animatedSnapPoints.value.length - 1]
@@ -388,7 +393,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
return SCROLLABLE_STATE.LOCKED;
});
// dynamic
const animatedContentHeight = useDerivedValue(() => {
const animatedContentHeightMax = useDerivedValue(() => {
const keyboardHeightInContainer = animatedKeyboardHeightInContainer.value;
const handleHeight = Math.max(0, animatedHandleHeight.value);
let contentHeight = animatedSheetHeight.value - handleHeight;
@@ -807,9 +812,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
*/
const nextPosition = normalizeSnapPoint(
position,
animatedContainerHeight.value,
topInset,
bottomInset
animatedContainerHeight.value
);

/**
@@ -1054,6 +1057,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
const internalContextVariables = useMemo(
() => ({
enableContentPanningGesture,
enableDynamicSizing,
overDragResistanceFactor,
enableOverDrag,
enablePanDownToClose,
@@ -1121,6 +1125,7 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
overDragResistanceFactor,
enableOverDrag,
enablePanDownToClose,
enableDynamicSizing,
_providedSimultaneousHandlers,
_providedWaitFor,
_providedActiveOffsetX,
@@ -1175,6 +1180,17 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
[_providedStyle, containerAnimatedStyle]
);
const contentContainerAnimatedStyle = useAnimatedStyle(() => {
/**
* if dynamic sizing is enabled, and content height
* is still not set, then we exit method.
*/
if (
enableDynamicSizing &&
animatedContentHeight.value === INITIAL_CONTAINER_HEIGHT
) {
return {};
}

/**
* if content height was provided, then we skip setting
* calculated height.
@@ -1185,11 +1201,11 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(

return {
height: animate({
point: animatedContentHeight.value,
point: animatedContentHeightMax.value,
configs: _providedAnimationConfigs,
}),
};
}, [animatedContentHeight, _providedContentHeight]);
}, [animatedContentHeightMax, enableDynamicSizing, animatedContentHeight]);
const contentContainerStyle = useMemo(
() => [styles.contentContainer, contentContainerAnimatedStyle],
[contentContainerAnimatedStyle]
@@ -1664,18 +1680,18 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
// topInset,
// bottomInset,
animatedSheetState,
animatedScrollableState,
animatedScrollableOverrideState,
// animatedScrollableState,
// animatedScrollableOverrideState,
// isScrollableRefreshable,
// animatedScrollableContentOffsetY,
// keyboardState,
// animatedIndex,
// animatedCurrentIndex,
// animatedPosition,
// animatedContainerHeight,
// animatedSheetHeight,
// animatedHandleHeight,
// animatedContentHeight,
animatedContainerHeight,
animatedSheetHeight,
animatedHandleHeight,
animatedContentHeight,
// // keyboardHeight,
// isLayoutCalculated,
// isContentHeightFixed,
2 changes: 2 additions & 0 deletions src/components/bottomSheet/constants.ts
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ const DEFAULT_ENABLE_HANDLE_PANNING_GESTURE = true;
const DEFAULT_ENABLE_OVER_DRAG = true;
const DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE = false;
const DEFAULT_ANIMATE_ON_MOUNT = true;
const DEFAULT_DYNAMIC_SIZING = false;

// keyboard
const DEFAULT_KEYBOARD_BEHAVIOR = KEYBOARD_BEHAVIOR.interactive;
@@ -39,6 +40,7 @@ export {
DEFAULT_ENABLE_HANDLE_PANNING_GESTURE,
DEFAULT_ENABLE_OVER_DRAG,
DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE,
DEFAULT_DYNAMIC_SIZING,
DEFAULT_ANIMATE_ON_MOUNT,
// keyboard
DEFAULT_KEYBOARD_BEHAVIOR,
18 changes: 17 additions & 1 deletion src/components/bottomSheet/types.d.ts
Original file line number Diff line number Diff line change
@@ -42,13 +42,15 @@ export interface BottomSheetProps
/**
* Points for the bottom sheet to snap to. It accepts array of number, string or mix.
* String values should be a percentage.
*
* ⚠️ This prop is required unless you set `enableDynamicSizing` to `true`.
* @example
* snapPoints={[200, 500]}
* snapPoints={[200, '%50']}
* snapPoints={['%100']}
* @type Array<string | number>
*/
snapPoints: Array<string | number> | SharedValue<Array<string | number>>;
snapPoints?: Array<string | number> | SharedValue<Array<string | number>>;
/**
* Defines how violently sheet has to be stopped while over dragging.
* @type number
@@ -85,6 +87,13 @@ export interface BottomSheetProps
* @default false
*/
enablePanDownToClose?: boolean;
/**
* Enable dynamic sizing for content view and scrollable
* content size.
* @type boolean
* @default false
*/
enableDynamicSizing?: boolean;
/**
* To start the sheet closed and snap to initial index when it's mounted.
* @type boolean
@@ -133,6 +142,13 @@ export interface BottomSheetProps
* @default 0
*/
bottomInset?: number;
/**
* Max dynamic content size height to limit the bottom sheet height
* from exceeding a provided size.
* @type number
* @default container height
*/
maxDynamicContentSize?: number;
//#endregion

//#region keyboard
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import {
useScrollHandler,
useScrollableSetter,
useBottomSheetInternal,
useStableCallback,
} from '../../hooks';
import {
GESTURE_SOURCE,
@@ -41,6 +42,7 @@ export function createBottomSheetScrollableComponent<T, P>(
onScroll,
onScrollBeginDrag,
onScrollEndDrag,
onContentSizeChange,
...rest
}: any = props;

@@ -61,6 +63,7 @@ export function createBottomSheetScrollableComponent<T, P>(
enableContentPanningGesture,
animatedFooterHeight,
animatedScrollableState,
animatedContentHeight,
} = useBottomSheetInternal();
//#endregion

@@ -77,6 +80,18 @@ export function createBottomSheetScrollableComponent<T, P>(
);
//#endregion

//#region callbacks
const handleContentSizeChange = useStableCallback(
(contentWidth: number, contentHeight: number) => {
animatedContentHeight.value = contentHeight;

if (onContentSizeChange) {
onContentSizeChange(contentWidth, contentHeight);
}
}
);
//#endregion

//#region styles
const containerAnimatedStyle = useAnimatedStyle(
() => ({
@@ -124,6 +139,7 @@ export function createBottomSheetScrollableComponent<T, P>(
overScrollMode={overScrollMode}
keyboardDismissMode={keyboardDismissMode}
onScroll={scrollHandler}
onContentSizeChange={handleContentSizeChange}
style={containerStyle}
/>
</NativeViewGestureHandler>
@@ -174,6 +190,7 @@ export function createBottomSheetScrollableComponent<T, P>(
progressViewOffset={progressViewOffset}
refreshControl={refreshControl}
onScroll={scrollHandler}
onContentSizeChange={handleContentSizeChange}
style={containerStyle}
/>
</NativeViewGestureHandler>
Loading

0 comments on commit 7330c7c

Please sign in to comment.