"use client"; import { useEffect, useRef, useState, type ReactNode } from "react"; import { createPortal } from "react-dom"; import { Spinner } from "./Spinner"; /** * FR-2 ขั้นตอนที่ 1 — dialog ยืนยัน "สร้าง version ใหม่ของเอกสารนี้หรือไม่?" พร้อมชื่อ commit * message ทางเลือก (FR-13) — ใช้ร่วมกับทั้ง Save & Version และ Start Versioning * FR-18 — มี onConfirmClose = มีปุ่มที่สอง [บันทึกเวอร์ชันและปิดเอกสาร] เป็น primary ตัวเดียว * (ปุ่มยืนยันหลักกลายเป็น secondary) ส่งเฉพาะ platform ที่ปิดเอกสารได้ * พอร์ตจาก ConfirmVersionDialog.tsx ต้นฉบับ — แทน Dialog ของ Fluent ด้วย portal + Genesis * (pattern overlay เดียวกับ AnnotationSidebar) */ const DEFAULT_TITLE = "สร้าง version ใหม่?"; const DEFAULT_DESCRIPTION = "ระบบจะบันทึกไฟล์ปัจจุบันและอัปโหลดเป็น version ถัดไปของเอกสารนี้"; const DEFAULT_CONFIRM_LABEL = "สร้าง version"; const BTN_SECONDARY = "focus-ring rounded-md border border-line bg-white px-3 py-1.5 text-sm text-ink transition hover:border-primary hover:text-primary disabled:pointer-events-none disabled:opacity-50"; const BTN_PRIMARY = "focus-ring rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-white transition hover:-translate-y-px hover:bg-primary-hover hover:shadow-btn-glow disabled:pointer-events-none disabled:opacity-50"; export function ConfirmVersionDialog({ open, busy, title, description, confirmLabel, onConfirm, onConfirmClose, onDismiss, }: { open: boolean; /** กำลังอยู่ระหว่าง flow หลังกดยืนยันแล้ว — ปุ่มทั้งหมด disable + แสดง spinner + ปิดไม่ได้ */ busy: boolean; /** ปรับข้อความตอน reuse กับ flow อื่น (FR-13 — Start Versioning ใช้ dialog ตัวเดียวกัน) */ title?: string; description?: ReactNode; confirmLabel?: string; /** ได้รับ commit message ที่ผู้ใช้พิมพ์ (FR-13) — ว่างได้ */ onConfirm: (message: string) => void; /** FR-18 — submit ที่สอง "บันทึกเวอร์ชันและปิดเอกสาร" */ onConfirmClose?: (message: string) => void; onDismiss: () => void; }) { const [message, setMessage] = useState(""); const dialogRef = useRef(null); // component ถูก mount ค้างไว้ตลอด (TaskpaneApp render ไว้นอก phase branch) — ต้องเคลียร์ค่าตอนปิด // ไม่ใช่ตอน unmount ไม่งั้นข้อความรอบก่อนหลุดมารอบถัดไป useEffect(() => { if (!open) { setMessage(""); } }, [open]); // ESC = ยกเลิก (เฉพาะไม่กำลัง busy — ตอน busy ห้ามปิดได้ทุกทางตาม spec) // Tab กักไว้ใน dialog — สิ่งที่ Fluent Dialog เคยทำให้อัตโนมัติ (WCAG 2.1.2/2.4.3) useEffect(() => { if (!open) return; const onKey = (event: KeyboardEvent) => { if (event.key === "Escape" && !busy) onDismiss(); if (event.key === "Tab") { const focusables = dialogRef.current?.querySelectorAll( "button:not([disabled]), textarea:not([disabled]), [href], input, select, [tabindex]:not([tabindex='-1'])", ); const first = focusables?.item(0); const last = focusables?.item((focusables?.length ?? 0) - 1); if (!first || !last) return; if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, busy, onDismiss]); // โฟกัสเข้า dialog ตอนเปิด + คืน focus ให้ trigger เดิมตอนปิด (อีกส่วนที่ Fluent เคยทำให้) useEffect(() => { if (!open) return; const previous = document.activeElement as HTMLElement | null; dialogRef.current?.focus(); return () => { previous?.focus(); }; }, [open]); if (!open) return null; return createPortal(
!busy && onDismiss()} >
event.stopPropagation()} >

{title ?? DEFAULT_TITLE}

{description ?? DEFAULT_DESCRIPTION}