/** * [INPUT]: 依赖 react 的 createContext/useCallback/useContext/useEffect/useRef/useState、react-dom 的 createPortal; * controls.css 的 .zzm-dialog-overlay/-panel/-title/-description/-actions 皮肤 + .zzm-btn 键;extracted.css 的定位补齐 * [OUTPUT]: 对外提供 ConfirmPanel(纯面板,可内联陈列)、ConfirmDialog(受控模态)、ConfirmProvider + useConfirm()(Promise 式 confirm) * [POS]: src/extracted 的确认窗。edu 的 DialogProvider(appearance="v4") 依赖 shadcn dialog.jsx 与 Tailwind; * 这里保留同一 API 形状(confirm({title, description, tone, confirmText}) → Promise)与同一套 .zzm-dialog-* 皮肤, * 但去掉 Tailwind/Radix——Esc、点遮罩=取消,打开聚焦确认键,关闭归还焦点。朱键只用于破坏性确认 * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md */ import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' const TONE_CLASS = { destructive: ' zzm-btn--accent', primary: ' zzm-btn--primary', default: ' zzm-btn--primary' } export function ConfirmPanel({ title, description, confirmText = '确认', cancelText = '取消', tone = 'default', onConfirm, onCancel, inline = false, confirmRef, }) { return (

{title}

{description ?

{description}

: null}
) } export function ConfirmDialog({ open, container, onCancel, ...panel }) { const confirmRef = useRef(null) useEffect(() => { if (!open) return undefined const back = document.activeElement confirmRef.current?.focus() const onKey = (e) => { if (e.key === 'Escape') onCancel?.() } window.addEventListener('keydown', onKey) return () => { window.removeEventListener('keydown', onKey) back?.focus?.() } }, [open, onCancel]) if (!open || typeof document === 'undefined') return null // Portal 留在 .zzm-v4 作用域内,才能继承键与输入槽的设计语言 const host = container || document.querySelector('.zzm-v4') || document.body return createPortal( <>
, host, ) } const ConfirmCtx = createContext(null) export function ConfirmProvider({ children }) { const [req, setReq] = useState(null) const confirm = useCallback((opts) => new Promise((resolve) => setReq({ ...opts, resolve })), []) const close = (ok) => { req?.resolve(ok) setReq(null) } return ( {children} close(true)} onCancel={() => close(false)} /> ) } /** const confirm = useConfirm(); if (await confirm({ title, description, tone: 'destructive' })) … */ export function useConfirm() { const confirm = useContext(ConfirmCtx) if (!confirm) throw new Error('useConfirm 必须在 内使用') return confirm }