import type { ReactNode } from "react"; import React, { useCallback, useImperativeHandle, useRef } from "react"; import { Keyboard, Platform, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { FullWindowOverlay } from "react-native-screens"; import { BottomSheetBackdrop, BottomSheetModal, BottomSheetView, } from "@gorhom/bottom-sheet"; import type { BottomSheetBackdropProps } from "@gorhom/bottom-sheet"; import { tokens } from "@jobber/design"; import { useStyles } from "./BottomSheet.style"; import { BottomSheetOption } from "./components/BottomSheetOption"; import { useBottomSheetBackHandler } from "./hooks/useBottomSheetBackHandler"; import { AtlantisThemeContextProvider, useAtlantisTheme, } from "../AtlantisThemeContext"; import { Divider } from "../Divider"; import { Heading } from "../Heading"; import { useIsScreenReaderEnabled } from "../hooks"; import { useAtlantisI18n } from "../hooks/useAtlantisI18n"; export interface BottomSheetProps { readonly children: ReactNode; /** * Display a cancel button in the bottom sheet footer. */ readonly showCancel?: boolean; /** * Hide or show the cancel button when loading state is provided. */ readonly loading?: boolean; /** * An optional heading to display in the bottom sheet header. */ readonly heading?: string; /** * Callback that is called when the overlay is opened. */ readonly onOpen?: () => void; /** * Callback that is called when the overlay is closed. */ readonly onClose?: () => void; } export interface BottomSheetRef { open: () => void; close: () => void; } // eslint-disable-next-line max-statements export function BottomSheet({ children, showCancel, loading = false, heading, onOpen, onClose, ref, }: BottomSheetProps & { readonly ref?: React.Ref }) { const styles = useStyles(); const { effectiveTheme } = useAtlantisTheme(); const isScreenReaderEnabled = useIsScreenReaderEnabled(); const cancellable = (showCancel && !loading) || isScreenReaderEnabled; const { t } = useAtlantisI18n(); const insets = useSafeAreaInsets(); const previousIndexRef = useRef(-1); const bottomSheetRef = useRef(null); const { handleSheetPositionChange } = useBottomSheetBackHandler(bottomSheetRef); useImperativeHandle(ref, () => ({ open: () => { dismissKeyboard(); bottomSheetRef.current?.present(); }, close: () => { close(); }, })); const close = useCallback(() => { bottomSheetRef.current?.dismiss(); }, []); const handleChange = (index: number) => { // Handle Android back button handleSheetPositionChange(index); const previousIndex = previousIndexRef.current; if (previousIndex === -1 && index >= 0) { onOpen?.(); } previousIndexRef.current = index; }; return ( { previousIndexRef.current = -1; dismissKeyboard(); onClose?.(); }} keyboardBlurBehavior="restore" handleStyle={styles.handle} > {/* BottomSheetModal renders through a portal mounted outside this component's subtree, so AtlantisThemeContext is lost. Re-apply the active effective theme so descendants continue to resolve the correct themed tokens. */} {heading &&
} {children} {cancellable && (