80 lines
2.1 KiB
TypeScript
80 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
import { Modal } from "@/components/ui/modal";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
type ConfirmOpts = {
|
|
title: string;
|
|
message?: string;
|
|
confirmLabel?: string;
|
|
destructive?: boolean;
|
|
};
|
|
|
|
type ConfirmFn = (opts: ConfirmOpts) => Promise<boolean>;
|
|
|
|
const ConfirmContext = createContext<ConfirmFn | null>(null);
|
|
|
|
/** Imperative confirm — `if (await confirm({...})) { … }`. Renders a styled
|
|
* modal instead of the browser's window.confirm. Provided by ConfirmProvider. */
|
|
export function useConfirm(): ConfirmFn {
|
|
const fn = useContext(ConfirmContext);
|
|
if (!fn) throw new Error("useConfirm must be used within a ConfirmProvider");
|
|
return fn;
|
|
}
|
|
|
|
export function ConfirmProvider({ children }: { children: ReactNode }) {
|
|
const [pending, setPending] = useState<{
|
|
opts: ConfirmOpts;
|
|
resolve: (v: boolean) => void;
|
|
} | null>(null);
|
|
|
|
const confirm = useCallback<ConfirmFn>(
|
|
(opts) => new Promise<boolean>((resolve) => setPending({ opts, resolve })),
|
|
[],
|
|
);
|
|
|
|
function settle(value: boolean) {
|
|
pending?.resolve(value);
|
|
setPending(null);
|
|
}
|
|
|
|
return (
|
|
<ConfirmContext.Provider value={confirm}>
|
|
{children}
|
|
<Modal
|
|
open={!!pending}
|
|
onClose={() => settle(false)}
|
|
title={pending?.opts.title ?? ""}
|
|
>
|
|
{pending ? (
|
|
<div className="space-y-5">
|
|
{pending.opts.message ? (
|
|
<p className="text-sm text-muted-foreground">
|
|
{pending.opts.message}
|
|
</p>
|
|
) : null}
|
|
<div className="flex justify-end gap-2">
|
|
<Button variant="ghost" onClick={() => settle(false)}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant={pending.opts.destructive ? "destructive" : "primary"}
|
|
onClick={() => settle(true)}
|
|
>
|
|
{pending.opts.confirmLabel ?? "Confirm"}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</Modal>
|
|
</ConfirmContext.Provider>
|
|
);
|
|
}
|