import { ReactNode } from "react" import { Modal as RNModal, ModalProps as RNModalProps, Platform, Pressable, StyleProp, TextStyle, View, ViewStyle, } from "react-native" import { KeyboardAwareScrollView } from "react-native-keyboard-controller" import { useSafeAreaInsets } from "react-native-safe-area-context" import { useAppTheme } from "@/theme/context" import type { ThemedStyle } from "@/theme/types" import { s } from "@/utils/responsive" import { Button, ButtonProps } from "./Button" import { Icon } from "./Icon" import { Text, TextProps } from "./Text" type Presets = "center" | "bottom" | "fullscreen" export interface ModalProps extends Omit { /** * Whether the modal is visible. */ visible: boolean /** * Callback when the modal is requested to close. */ onClose: () => void /** * Modal position preset. * - `center`: Centered dialog (default) * - `bottom`: Bottom sheet style, slides up from bottom * - `fullscreen`: Full screen modal * @default "center" */ preset?: Presets /** * Title text which is looked up via i18n. */ titleTx?: TextProps["tx"] /** * Title text to display if not using `titleTx`. */ title?: string /** * Optional title options to pass to i18n. */ titleTxOptions?: TextProps["txOptions"] /** * Style overrides for the title text. */ titleStyle?: StyleProp /** * Whether to show the close button in the header. * @default true */ showCloseButton?: boolean /** * Whether to close the modal when pressing the overlay. * @default true */ closeOnOverlayPress?: boolean /** * The content to display inside the modal. */ children?: ReactNode /** * Style overrides for the overlay. */ overlayStyle?: StyleProp /** * Style overrides for the content container. */ contentStyle?: StyleProp /** * Props for the confirm button (right button). * If provided, the footer buttons will be shown. */ confirmButtonProps?: ButtonProps /** * Props for the cancel button (left button). * If provided along with confirmButtonProps, both buttons will be shown. */ cancelButtonProps?: ButtonProps /** * Custom footer component. * Overrides confirmButtonProps and cancelButtonProps. */ FooterComponent?: ReactNode /** * Animation type for the modal. * Defaults based on preset: "fade" for center, "slide" for bottom/fullscreen */ animationType?: "none" | "slide" | "fade" /** * Maximum width of the modal content (only applies to "center" preset). * @default 400 */ maxWidth?: number } /** * A reusable modal component with overlay, title, close button, and footer actions. * Supports theming, internationalization, and multiple position presets. * @param {ModalProps} props - The props for the `Modal` component. * @returns {JSX.Element} The rendered `Modal` component. * @example * // Center modal (default) * setIsVisible(false)} * titleTx="common:confirm" * > * * * * @example * // Bottom sheet modal * setIsVisible(false)} * preset="bottom" * titleTx="profile:editProfile" * > * * */ export function Modal(props: ModalProps) { const { visible, onClose, preset = "center", titleTx, title, titleTxOptions, titleStyle: $titleStyleOverride, showCloseButton = true, closeOnOverlayPress = true, children, overlayStyle: $overlayStyleOverride, contentStyle: $contentStyleOverride, confirmButtonProps, cancelButtonProps, FooterComponent, animationType, maxWidth = s(400), ...rest } = props const { themed, theme } = useAppTheme() const insets = useSafeAreaInsets() const hasTitleArea = !!(titleTx || title || showCloseButton) const hasFooter = !!(FooterComponent || confirmButtonProps) // Determine animation type based on preset const resolvedAnimationType = animationType ?? (preset === "center" ? "fade" : "slide") // Build overlay style based on preset const $overlayStyle: StyleProp = [ themed($overlayBase), preset === "center" && themed($overlayCenter), preset === "bottom" && $overlayBottom, preset === "fullscreen" && $overlayFullscreen, $overlayStyleOverride, ] // Build content container style based on preset const $contentContainerStyle: StyleProp = [ themed($contentBase), preset === "center" && [themed($contentCenter), { maxWidth }], preset === "bottom" && [themed($contentBottom), { paddingBottom: insets.bottom || s(24) }], preset === "fullscreen" && [ themed($contentFullscreen), { paddingTop: insets.top, paddingBottom: insets.bottom }, ], $contentStyleOverride, ] const $titleStyle: StyleProp = [ themed($titleBase), preset === "bottom" && $titleBottom, $titleStyleOverride, ] const renderHeader = () => hasTitleArea ? ( {(titleTx || title) && ( )} {showCloseButton && ( )} ) : null const renderFooter = () => hasFooter ? ( {FooterComponent ? ( FooterComponent ) : ( <> {cancelButtonProps && (