/** * Promise-based confirmation dialog (replacement for `window.confirm`). * * Use the imperative `useConfirm()` hook to show a dialog and await the * user's choice. Internally rendered through a portal so it works above * any other content. */ import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react' import type { ReactNode } from 'react' import { createPortal } from 'react-dom' import { useFocusTrap } from '../hooks/useFocusTrap' interface ConfirmOptions { title: string message?: ReactNode confirmLabel?: string cancelLabel?: string variant?: 'default' | 'danger' } type Resolver = (value: boolean) => void interface ConfirmContextValue { confirm: (opts: ConfirmOptions) => Promise } const ConfirmContext = createContext(null) // eslint-disable-next-line react-refresh/only-export-components export function useConfirm(): (opts: ConfirmOptions) => Promise { const ctx = useContext(ConfirmContext) if (!ctx) throw new Error('useConfirm must be used inside ') return ctx.confirm } export function ConfirmProvider({ children }: { children: ReactNode }) { const [opts, setOpts] = useState(null) const resolverRef = useRef(null) const dialogRef = useFocusTrap(opts !== null) const confirm = useCallback((options: ConfirmOptions) => { return new Promise((resolve) => { resolverRef.current = resolve setOpts(options) }) }, []) const close = (result: boolean) => { resolverRef.current?.(result) resolverRef.current = null setOpts(null) } const value = useMemo(() => ({ confirm }), [confirm]) return ( {children} {opts !== null && createPortal(
close(false)} aria-hidden />
{ if (e.key === 'Escape') close(false) if (e.key === 'Enter') close(true) }} >

{opts.title}

{opts.message && (
{opts.message}
)}
, document.body, )} ) }