Files
iNaturalistReactNative/src/components/SharedComponents/Sheets/BottomSheet.tsx
2025-12-22 18:26:38 -08:00

165 lines
4.0 KiB
TypeScript

import BottomSheet, {
BottomSheetModal, BottomSheetScrollView,
} from "@gorhom/bottom-sheet";
import classnames from "classnames";
import { BottomSheetStandardBackdrop, Heading4, INatIconButton } from "components/SharedComponents";
import { View } from "components/styledComponents";
import type { Node } from "react";
import React, {
useCallback,
useEffect,
useRef,
} from "react";
import { Dimensions } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTranslation } from "sharedHooks";
const { width } = Dimensions.get( "window" );
const marginOnWide = {
marginHorizontal: width > 500
? ( width - 500 ) / 2
: 0,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
overflow: "hidden",
};
// eslint-disable-next-line
const noHandle = ( ) => <></>;
interface Props {
children: React.JSX.Element;
hidden?: boolean;
hideCloseButton?: boolean;
headerText?: string;
onLayout?: ( event: object ) => void;
// Callback when the user presses the close button or backdrop, not whenever the sheet
// closes
onPressClose?: ( ) => void;
snapPoints?: string[];
insideModal?: boolean;
keyboardShouldPersistTaps?: string;
testID?: string;
containerClass?: string;
scrollEnabled?: boolean;
}
const StandardBottomSheet = ( {
children,
hidden,
hideCloseButton = false,
headerText,
onLayout,
onPressClose,
snapPoints,
insideModal,
keyboardShouldPersistTaps = "never",
containerClass,
testID,
scrollEnabled = true,
}: Props ): Node => {
if ( snapPoints ) {
throw new Error( "BottomSheet does not accept snapPoints as a prop." );
}
const { t } = useTranslation( );
const sheetRef = useRef<BottomSheet>( null );
const insets = useSafeAreaInsets( );
const handleClose = useCallback( ( ) => {
if ( onPressClose ) onPressClose( );
if ( insideModal ) {
sheetRef.current?.collapse( );
} else {
sheetRef.current?.dismiss( );
}
}, [insideModal, onPressClose] );
const renderBackdrop = props => (
<BottomSheetStandardBackdrop
props={props}
onPress={onPressClose}
/>
);
const handleSnapPress = useCallback( ( ) => {
if ( insideModal ) {
sheetRef.current?.expand( );
} else {
sheetRef.current?.present( );
}
}, [insideModal] );
useEffect( ( ) => {
if ( hidden ) { return; }
handleSnapPress( );
}, [hidden, handleSnapPress] );
const BottomSheetComponent = insideModal
? BottomSheet
: BottomSheetModal;
if ( hidden ) {
return null;
}
return (
<BottomSheetComponent
backdropComponent={renderBackdrop}
enableDynamicSizing
handleComponent={noHandle}
index={0}
ref={sheetRef}
style={marginOnWide}
accessible={false}
onDismiss={handleClose}
>
<BottomSheetScrollView
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
scrollEnabled={scrollEnabled}
>
<View
className={classnames(
"pt-7",
insets.bottom > 0
? "pb-7"
: null,
containerClass,
)}
onLayout={onLayout}
// Not ideal, but @gorhom/bottom-sheet components don't support
// testID
testID={testID}
>
{!headerText
? null
: (
<View className="mx-12 flex">
<Heading4
testID="bottom-sheet-header"
className="w-full text-center"
>
{headerText}
</Heading4>
</View>
)}
{children}
{!hideCloseButton && (
<INatIconButton
icon="close"
onPress={handleClose}
size={19}
className="absolute top-3.5 right-3"
accessibilityState={{ disabled: hidden }}
accessibilityLabel={t( "Close" )}
/>
)}
</View>
</BottomSheetScrollView>
</BottomSheetComponent>
);
};
export default StandardBottomSheet;