import * as React from "react"; import { createPortal } from "react-dom"; import { useTranslation } from "react-i18next"; import { type FetcherWithComponents, useFetcher } from "react-router"; import type { SendouButtonProps } from "~/components/elements/Button"; import { SendouDialog } from "~/components/elements/Dialog"; import { useHydrated } from "~/hooks/useHydrated"; import invariant from "~/utils/invariant"; import { FormMessage } from "./FormMessage"; import { SubmitButton } from "./SubmitButton"; interface ChildProps { onPress?: () => void; type?: "button"; } export function FormWithConfirm({ fields, children, dialogHeading, description, submitButtonText, action, submitButtonTestId = "submit-button", submitButtonVariant = "destructive", fetcher: _fetcher, isOpen, onOpenChange, }: { fields?: ( | [name: string, value: string | number] | readonly [name: string, value: string | number] )[]; children?: React.ReactElement; dialogHeading: string; /** Optional explanatory text shown below the heading in the confirm dialog */ description?: React.ReactNode; submitButtonText?: string; action?: string; submitButtonTestId?: string; submitButtonVariant?: SendouButtonProps["variant"]; fetcher?: FetcherWithComponents; /** Controls the dialog open state. When provided, no child trigger is needed. */ isOpen?: boolean; onOpenChange?: (isOpen: boolean) => void; }) { const componentsFetcher = useFetcher(); const fetcher = _fetcher ?? componentsFetcher; const isHydrated = useHydrated(); const { t } = useTranslation(["common"]); const [internalOpen, setInternalOpen] = React.useState(false); const formRef = React.useRef(null); const id = React.useId(); const isControlled = isOpen !== undefined; const dialogOpen = isControlled ? isOpen : internalOpen; const openDialog = React.useCallback(() => { onOpenChange?.(true); setInternalOpen(true); }, [onOpenChange]); const closeDialog = React.useCallback(() => { onOpenChange?.(false); setInternalOpen(false); }, [onOpenChange]); invariant(!children || React.isValidElement(children)); React.useEffect(() => { if (fetcher.state === "loading") { closeDialog(); } }, [fetcher.state, closeDialog]); return ( <> {isHydrated ? // using portal here makes nesting this component in another form work createPortal( {fields?.map(([name, value]) => ( ))} , document.body, ) : null}

{dialogHeading}

{description ? ( {description} ) : null}
{submitButtonText ?? t("common:actions.delete")}
{children ? React.cloneElement(children, { onPress: openDialog, type: "button", }) : null} ); }