import { createContext, useCallback, useContext, type ReactNode } from "react"; // App-styled, promise-based replacement for window.confirm. Wrap the tree in // once, then anywhere: const confirm = useConfirm(); // if (await confirm({ message: "…", danger: true })) { … } // // For a decision with more than two outcomes use `useChoice()` instead — never chain two // confirms, because the second dialog's Cancel/Escape/backdrop is then indistinguishable from // a deliberate answer (the playlist delete used to read a dismissal as "delete here only" and // went ahead and deleted). // // The context + hooks live here, apart from the dialog in ConfirmProvider, so that file exports only // components (Fast Refresh). interface ConfirmOptions { title?: string; message: ReactNode; confirmLabel?: string; cancelLabel?: string; danger?: boolean; } interface Choice { id: string; label: string; danger?: boolean; } export interface ChoiceOptions { title?: string; message: ReactNode; cancelLabel?: string; /** Rendered left→right after Cancel. Resolves to the clicked choice's `id`. */ choices: Choice[]; } export type ChoiceFn = (opts: ChoiceOptions) => Promise; type ConfirmFn = (opts: ConfirmOptions) => Promise; export const ChoiceContext = createContext(() => Promise.resolve(null)); /** Two outcomes: resolves true on confirm, false on cancel/Escape/backdrop. */ export function useConfirm(): ConfirmFn { const ask = useContext(ChoiceContext); return useCallback( async (opts: ConfirmOptions) => { const picked = await ask({ title: opts.title, message: opts.message, cancelLabel: opts.cancelLabel, choices: [{ id: "confirm", label: opts.confirmLabel ?? "", danger: opts.danger }], }); return picked === "confirm"; }, [ask], ); } /** Three or more outcomes. Resolves to the chosen id, or **null** when the user cancels, * presses Escape or clicks the backdrop — dismissal is never one of the actions. */ export function useChoice(): ChoiceFn { return useContext(ChoiceContext); }