@devvie/bottom-sheet
Version:
The 😎smart , 📦tiny , and 🎗flexible bottom sheet your app craves 🚀
128 lines (118 loc) • 4.94 kB
text/typescript
import { useEffect, useRef, type ComponentRef, type RefObject } from 'react';
import {
Animated,
type EmitterSubscription,
Keyboard,
useWindowDimensions,
} from 'react-native';
import type { HeightAnimationDriver, UseHandleKeyboardEvents } from './types.d';
/**
* Handles keyboard pop out by adjusting sheet's layout when a `TextInput` within
* the sheet receives focus.
*
* @param {boolean} keyboardHandlingEnabled Determines whether this hook will go on to handle keyboard
* @param {number} sheetHeight Initial sheet's height before keyboard pop out
* @param {boolean} sheetOpen Indicates whether the sheet is open or closed
* @param {HeightAnimationDriver} heightAnimationDriver Animator function to be called with new
* sheet height when keyboard is out so it can adjust the sheet height with animation
* @param {RefObject<ComponentRef<typeof Animated.View> | null>} contentWrapperRef Reference to the content wrapper view
* @return {{removeKeyboardListeners:Function;} | null} An Object with an unsubscriber function or `null`
* when `keyboardHandlingEnabled` is false
*/
const useHandleKeyboardEvents: UseHandleKeyboardEvents = (
keyboardHandlingEnabled: boolean,
sheetHeight: number,
sheetOpen: boolean,
heightAnimationDriver: HeightAnimationDriver,
contentWrapperRef: RefObject<ComponentRef<typeof Animated.View> | null>,
openDuration = 400,
closeDuration = 200,
containerHeight?: number
) => {
const SCREEN_HEIGHT = useWindowDimensions().height;
const keyboardHideSubscription = useRef<EmitterSubscription | null>(null);
const keyboardShowSubscription = useRef<EmitterSubscription | null>(null);
const unsubscribe = () => {
keyboardHideSubscription.current?.remove?.();
keyboardShowSubscription.current?.remove?.();
};
useEffect(() => {
if (keyboardHandlingEnabled) {
keyboardShowSubscription.current = Keyboard.addListener(
'keyboardDidShow',
({ endCoordinates: { height: keyboardHeight } }) => {
if (sheetOpen) {
// Exaggeration of the actual height (24) of the autocorrect view
// that appears atop android keyboard
const keyboardAutoCorrectViewHeight = 50;
contentWrapperRef.current?.measure?.(
(
_x: number,
_y: number,
_width: number,
_height: number,
_pageX: number,
pageY: number
) => {
const sheetYOffset = pageY; // Y offset of the sheet after keyboard is out
const actualSheetHeight = SCREEN_HEIGHT - sheetYOffset; // new height of the sheet (after keyboard offset)
/**
* We determine soft input/keyboard mode based on the difference between the new sheet height and the
* initial height after keyboard is opened. If there's no much difference, it's adjustPan
* (keyboard overlays sheet), else it's adjustResize (sheet is pushed up)
*/
const sheetIsOverlayed =
actualSheetHeight - sheetHeight <
keyboardAutoCorrectViewHeight;
const remainingSpace = SCREEN_HEIGHT - keyboardHeight;
/**
* this is 50% of the remaining space (SCREEN_HEIGHT - keyboardHeight) that remains atop the keybaord
*/
const fiftyPercent = 0.5 * remainingSpace;
const minSheetHeight = 50;
// allow very short sheet e.g 10 to increase to 50 and
// very long to clamp withing availablle space;
let newSheetHeight = Math.max(
minSheetHeight,
Math.min(sheetHeight, fiftyPercent)
);
if (sheetIsOverlayed) newSheetHeight += keyboardHeight;
// Clamp to container height (or screen height fallback) to prevent overshooting
const maxAllowedHeight = containerHeight ?? SCREEN_HEIGHT;
if (newSheetHeight > maxAllowedHeight) {
newSheetHeight = maxAllowedHeight;
}
heightAnimationDriver(newSheetHeight, openDuration).start();
}
);
}
}
);
keyboardHideSubscription.current = Keyboard.addListener(
'keyboardDidHide',
(_) => {
if (sheetOpen)
heightAnimationDriver(sheetHeight, closeDuration).start();
}
);
return unsubscribe;
}
return;
}, [
keyboardHandlingEnabled,
sheetHeight,
SCREEN_HEIGHT,
sheetOpen,
heightAnimationDriver,
contentWrapperRef,
openDuration,
closeDuration,
containerHeight,
]);
return keyboardHandlingEnabled
? {
removeKeyboardListeners: unsubscribe,
}
: null;
};
export default useHandleKeyboardEvents;