Files
UHD_Media_Tool/apps/web/app/components/console/confirm-dialog.tsx
T
rootandClaude Opus 5.5 e5e42b1f0d feat(web): W3-1 shared console routes, nav, api/sse/query-keys and shared components
- routes.ts registers all workspaces under /console (+ detail routes, engine, audit, login)
- api.ts: cookie session first (credentials include, CSRF header), Bearer only with stored key, 401 -> /login
- sse.ts: shared EventSource with ?after=seq catch-up, backoff reconnect, per-type subscribe
- shared components: data-table, filter-bar, detail-sheet, timeline, json-view, confirm-dialog,
  form-field, kv-list, stat-tile, error/loading states; ui tabs and textarea

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 20:21:29 +08:00

86 lines
2.5 KiB
TypeScript

import { LoaderCircle } from "lucide-react";
import { type ReactNode, useState } from "react";
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "~/components/ui/alert-dialog.tsx";
import { Button } from "~/components/ui/button.tsx";
/**
* 确认弹窗。onConfirm 可返回 Promise:执行中按钮禁用并转圈,resolve 后自动关闭,reject 时保持打开
* (错误提示由调用方 toast)。受控(open/onOpenChange)或非受控(trigger)两种用法。
* <ConfirmDialog trigger={<Button variant="destructive">删除</Button>} title="删除实例?"
* description="…" confirmLabel="删除" destructive onConfirm={() => del.mutateAsync(id)} />
*/
export function ConfirmDialog({
trigger,
open: openProp,
onOpenChange,
title,
description,
confirmLabel = "确认",
cancelLabel = "取消",
destructive = false,
onConfirm,
children,
}: {
trigger?: ReactNode;
open?: boolean;
onOpenChange?: (open: boolean) => void;
title: ReactNode;
description?: ReactNode;
confirmLabel?: string;
cancelLabel?: string;
destructive?: boolean;
onConfirm: () => unknown | Promise<unknown>;
/** 描述下方的额外内容(输入确认等) */
children?: ReactNode;
}) {
const [inner, setInner] = useState(false);
const [pending, setPending] = useState(false);
const open = openProp ?? inner;
const setOpen = (o: boolean) => {
if (pending) return;
setInner(o);
onOpenChange?.(o);
};
const run = async () => {
setPending(true);
try {
await onConfirm();
setPending(false);
setInner(false);
onOpenChange?.(false);
} catch {
setPending(false);
}
};
return (
<AlertDialog open={open} onOpenChange={setOpen}>
{trigger ? <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger> : null}
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
{description ? <AlertDialogDescription>{description}</AlertDialogDescription> : null}
</AlertDialogHeader>
{children}
<AlertDialogFooter>
<AlertDialogCancel disabled={pending}>{cancelLabel}</AlertDialogCancel>
<Button variant={destructive ? "destructive" : "default"} onClick={run} disabled={pending}>
{pending ? <LoaderCircle className="animate-spin" /> : null}
{confirmLabel}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}