import { BottomSheetBackdrop, BottomSheetModal, BottomSheetFooter as GBottomSheetFooter, BottomSheetView as GBottomSheetView, useBottomSheetModal, } from "@gorhom/bottom-sheet"; import { SCROLLABLE_TYPE, createBottomSheetScrollableComponent, } from "@gorhom/bottom-sheet"; import { useTheme } from "@react-navigation/native"; import * as Slot from "@rn-primitives/slot"; import * as React from "react"; import { memo } from "react"; import { Keyboard, Platform, Pressable, View } from "react-native"; import { KeyboardAwareScrollView } from "react-native-keyboard-controller"; import Reanimated from "react-native-reanimated"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { withUniwind } from "uniwind"; import { cn } from "@turbostarter/ui"; import { Text } from "./text"; import type { BottomSheetScrollViewMethods, BottomSheetScrollView as GBottomSheetScrollView, } from "@gorhom/bottom-sheet"; import type { BottomSheetBackdropProps, BottomSheetFooterProps as GBottomSheetFooterProps, } from "@gorhom/bottom-sheet"; import type { BottomSheetModalMethods } from "@gorhom/bottom-sheet/lib/typescript/types"; import type { GestureResponderEvent, ViewStyle } from "react-native"; import type { KeyboardAwareScrollViewProps } from "react-native-keyboard-controller"; interface BottomSheetContext { sheetRef: React.RefObject; } const BottomSheetContext = React.createContext(null); function BottomSheet({ ...props }: React.ComponentProps) { const sheetRef = React.useRef(null); return ( ); } function useBottomSheetContext() { const context = React.useContext(BottomSheetContext); if (!context) { throw new Error( "BottomSheet compound components cannot be rendered outside the BottomSheet component", ); } return context; } const CLOSED_INDEX = -1; type BottomSheetContentRef = React.ComponentRef; type BottomSheetContentProps = Omit< React.ComponentProps, "backdropComponent" > & { backdropProps?: Partial>; }; const BottomSheetContent = React.forwardRef< BottomSheetContentRef, BottomSheetContentProps >( ( { enablePanDownToClose = true, enableDynamicSizing = true, backdropProps, backgroundStyle, android_keyboardInputMode = "adjustResize", ...props }, ref, ) => { const insets = useSafeAreaInsets(); const { colors } = useTheme(); const { sheetRef } = useBottomSheetContext(); React.useImperativeHandle(ref, () => { if (!sheetRef.current) { return {} as BottomSheetModalMethods; } return sheetRef.current; }, [sheetRef]); const renderBackdrop = React.useCallback( (props: BottomSheetBackdropProps) => { const { pressBehavior = "close", disappearsOnIndex = CLOSED_INDEX, style, onPress, ...rest } = { ...props, ...backdropProps, }; return ( { if (Keyboard.isVisible()) { Keyboard.dismiss(); } onPress?.(); }} {...rest} /> ); }, [backdropProps], ); return ( ); }, ); function BottomSheetOpenTrigger({ onPress, asChild = false, ...props }: React.ComponentProps & { asChild?: boolean; }) { const { sheetRef } = useBottomSheetContext(); function handleOnPress(ev: GestureResponderEvent) { sheetRef.current?.present(); onPress?.(ev); } const Trigger = asChild ? Slot.Pressable : Pressable; return ; } function BottomSheetCloseTrigger({ onPress, asChild = false, ...props }: React.ComponentProps & { asChild?: boolean; }) { const { dismiss } = useBottomSheetModal(); function handleOnPress(ev: GestureResponderEvent) { dismiss(); if (Keyboard.isVisible()) { Keyboard.dismiss(); } onPress?.(ev); } const Trigger = asChild ? Slot.Pressable : Pressable; return ; } const BOTTOM_SHEET_HEADER_HEIGHT = 60; // BottomSheetHeader height function BottomSheetView({ className, children, hadHeader = false, style, ...props }: Omit, "style"> & { hadHeader?: boolean; style?: ViewStyle; }) { const insets = useSafeAreaInsets(); const paddingBottom = insets.bottom + (Platform.select({ ios: 4, android: 16, }) ?? 0) + (hadHeader ? BOTTOM_SHEET_HEADER_HEIGHT : 0); return ( {children} ); } type BottomSheetScrollViewProps = Omit< React.ComponentPropsWithoutRef, "style" > & { hadHeader?: boolean; className?: string; contentContainerClassName?: string; style?: ViewStyle; }; const BottomSheetKeyboardAwareScrollView = memo( createBottomSheetScrollableComponent< BottomSheetScrollViewMethods, BottomSheetScrollViewProps >( SCROLLABLE_TYPE.SCROLLVIEW, Reanimated.createAnimatedComponent( KeyboardAwareScrollView, ), ), ); const StyledBottomSheetKeyboardAwareScrollView = withUniwind( BottomSheetKeyboardAwareScrollView, ); function BottomSheetScrollView({ children, hadHeader = false, style, className, contentContainerClassName, ...props }: BottomSheetScrollViewProps) { const insets = useSafeAreaInsets(); const paddingBottom = insets.bottom + (Platform.select({ ios: 8, android: 16, }) ?? 0) + (hadHeader ? BOTTOM_SHEET_HEADER_HEIGHT : 0); return ( {children} ); } function BottomSheetHeader({ className, ...props }: React.ComponentProps) { return ; } /** * To be used in a useCallback function as a props to BottomSheetContent */ function BottomSheetFooter({ bottomSheetFooterProps, children, className, style, ...props }: Omit, "style"> & { bottomSheetFooterProps: GBottomSheetFooterProps; children?: React.ReactNode; style?: ViewStyle; }) { const insets = useSafeAreaInsets(); return ( {children} ); } function BottomSheetTitle({ className, ...props }: React.ComponentProps) { return ( ); } function BottomSheetDescription({ className, ...props }: React.ComponentProps) { return ( ); } function useBottomSheet() { const ref = React.useRef(null); const open = React.useCallback(() => { ref.current?.present(); }, []); const close = React.useCallback(() => { ref.current?.dismiss(); }, []); return { ref, open, close }; } export { BottomSheet, BottomSheetCloseTrigger, BottomSheetContent, BottomSheetFooter, BottomSheetScrollView, BottomSheetHeader, BottomSheetOpenTrigger, BottomSheetView, BottomSheetTitle, BottomSheetDescription, type BottomSheetContentRef, useBottomSheet, };