import { useCallback, useState, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; import Modal from "./ui/Modal"; import { Button } from "./ui/Button"; import { ChoiceContext, type ChoiceFn, type ChoiceOptions } from "./confirmContext"; // The dialog behind useConfirm()/useChoice() (see confirmContext.ts for the hooks and their contract). export function ConfirmProvider({ children }: { children: ReactNode }) { const { t } = useTranslation(); const [state, setState] = useState<{ opts: ChoiceOptions; resolve: (id: string | null) => void; } | null>(null); const ask = useCallback( (opts) => new Promise((resolve) => setState({ opts, resolve })), [], ); function close(id: string | null) { state?.resolve(id); setState(null); } const choices = state?.opts.choices ?? []; // Focus Cancel when any offered action is destructive: the confirm button used to take // autoFocus unconditionally, so a stray Enter on an open dialog executed "Delete user" or // "Clear audit log" without the user ever looking at it. const anyDanger = choices.some((c) => c.danger); const multi = choices.length > 1; return ( {children} {state && ( close(null)} maxWidth={choices.length > 1 ? "max-w-md" : "max-w-sm"} >
{state.opts.message}
{/* One action: the familiar Cancel/Confirm row. Two or more: a column, because side by side they wrap raggedly and a wrapped button reads as a second, lesser group — and these are peers. Ordered least to most destructive, top to bottom. */}
{choices.map((c, i) => ( ))}
)}
); }