Files
AgentHub/client/src/components/ConfirmHost.tsx
T
nntrivi2001 17a5c7470d feat(ui): shared toast, confirm, dialog, copy and IME-safe Enter primitives
One implementation each, for every page to use instead of its own copy:

- lib/toast.ts + components/Toaster.tsx: toast.success|error|info from
  anywhere, rendered in polite/assertive live regions; an error stays until
  dismissed, a success fades, a repeat collapses.
- lib/confirm.ts + components/ConfirmHost.tsx: await confirmDialog({...})
  and confirmDiscard() through the themed ConfirmModal, replacing
  window.confirm and the actions that asked nothing.
- hooks/useDialog.ts: the modal focus contract (focus in, Tab trapped,
  Escape, focus restored), extracted from ConfirmModal; dialogs stack so
  only the top one answers keys. ConfirmModal now uses it.
- hooks/useArmed.ts: two-click confirmation that disarms after 3 s.
- hooks/useUnsavedGuard.ts: beforeunload while a draft is dirty.
- lib/clipboard.ts copyText: Clipboard API with an execCommand fallback for
  non-secure origins, outcome toasted.
- lib/keys.ts isSubmitEnter: ignores the Enter that confirms an IME
  candidate (Vietnamese Telex, CJK).
2026-10-05 13:17:09 +07:00

34 lines
1.1 KiB
TypeScript

/**
* @file ConfirmHost.tsx
* @description Renders the one pending `confirmDialog()` from `lib/confirm.ts`
* through `ConfirmModal`. Mounted once in `App.tsx`.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ConfirmModal } from "./ConfirmModal";
import { settleConfirm, subscribeConfirm, type PendingConfirm } from "../lib/confirm";
const cancel = () => settleConfirm(false);
const confirm = () => settleConfirm(true);
/** The single host for `confirmDialog()`. */
export function ConfirmHost() {
const { t } = useTranslation("common");
const [p, setP] = useState<PendingConfirm | null>(null);
useEffect(() => subscribeConfirm(setP), []);
return (
<ConfirmModal
open={p !== null}
title={p?.title ?? ""}
message={p?.message}
confirmLabel={p?.confirmLabel ?? t("confirm")}
cancelLabel={p?.cancelLabel ?? t("cancel")}
destructive={p?.destructive ?? true}
onConfirm={confirm}
onCancel={cancel}
/>
);
}