import React, { createContext, useCallback, useContext, useEffect, useRef, useState, } from 'react'; import { AlertCircle, CheckCircle2, Info, X } from 'lucide-react'; export type AppDialogVariant = 'info' | 'success' | 'warning' | 'error'; interface AlertOptions { title?: string; variant?: AppDialogVariant; confirmLabel?: string; } interface ConfirmOptions extends AlertOptions { cancelLabel?: string; } interface DialogRequest { mode: 'alert' | 'confirm'; message: string; title: string; variant: AppDialogVariant; confirmLabel: string; cancelLabel: string; resolve: (value: boolean) => void; } interface AppDialogContextValue { showAlert: (message: string, options?: AlertOptions) => Promise; showConfirm: (message: string, options?: ConfirmOptions) => Promise; showToast: (message: string, options?: { variant?: AppDialogVariant; duration?: number }) => void; } const AppDialogContext = createContext(null); const VARIANT_META: Record< AppDialogVariant, { icon: React.ReactNode; title: string; buttonClass: string } > = { info: { icon: , title: '提示', buttonClass: 'bg-indigo-600 hover:bg-indigo-700 text-white', }, success: { icon: , title: '成功', buttonClass: 'bg-emerald-600 hover:bg-emerald-700 text-white', }, warning: { icon: , title: '请注意', buttonClass: 'bg-amber-500 hover:bg-amber-600 text-white', }, error: { icon: , title: '出错了', buttonClass: 'bg-rose-600 hover:bg-rose-700 text-white', }, }; function AppDialogModal({ dialog, onClose, }: { dialog: DialogRequest; onClose: (confirmed: boolean) => void; }) { const confirmBtnRef = useRef(null); const meta = VARIANT_META[dialog.variant]; useEffect(() => { confirmBtnRef.current?.focus(); }, []); useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.preventDefault(); onClose(dialog.mode === 'alert'); } }; window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); }, [dialog.mode, onClose]); return (
onClose(dialog.mode === 'alert')} role="presentation" >
e.stopPropagation()} role="alertdialog" aria-modal="true" aria-labelledby="app-dialog-title" aria-describedby="app-dialog-message" >
{meta.icon}

{dialog.title}

{dialog.message}

{dialog.mode === 'confirm' && ( )}
); } function AppToast({ message, variant = 'success', onDismiss, }: { message: string; variant: AppDialogVariant; onDismiss: () => void; }) { const meta = VARIANT_META[variant]; useEffect(() => { const timer = window.setTimeout(onDismiss, 2000); return () => window.clearTimeout(timer); }, [onDismiss]); return (
{meta.icon} {message}
); } export function AppDialogProvider({ children }: { children: React.ReactNode }) { const [dialog, setDialog] = useState(null); const [toast, setToast] = useState<{ message: string; variant: AppDialogVariant } | null>(null); const toastTimerRef = useRef(null); const showAlert = useCallback((message: string, options?: AlertOptions) => { const variant = options?.variant ?? 'info'; return new Promise((resolve) => { setDialog({ mode: 'alert', message, title: options?.title ?? VARIANT_META[variant].title, variant, confirmLabel: options?.confirmLabel ?? '知道了', cancelLabel: '', resolve: () => resolve(), }); }); }, []); const showConfirm = useCallback((message: string, options?: ConfirmOptions) => { const variant = options?.variant ?? 'warning'; return new Promise((resolve) => { setDialog({ mode: 'confirm', message, title: options?.title ?? VARIANT_META[variant].title, variant, confirmLabel: options?.confirmLabel ?? '确定', cancelLabel: options?.cancelLabel ?? '取消', resolve, }); }); }, []); const dismissToast = useCallback(() => { if (toastTimerRef.current !== null) { window.clearTimeout(toastTimerRef.current); toastTimerRef.current = null; } setToast(null); }, []); const showToast = useCallback( (message: string, options?: { variant?: AppDialogVariant; duration?: number }) => { dismissToast(); const variant = options?.variant ?? 'success'; setToast({ message, variant }); const duration = options?.duration ?? 2000; toastTimerRef.current = window.setTimeout(() => { toastTimerRef.current = null; setToast(null); }, duration); }, [dismissToast] ); const closeDialog = useCallback((confirmed: boolean) => { setDialog((current) => { current?.resolve(confirmed); return null; }); }, []); return ( {children} {dialog && } {toast && ( )} ); } export function useAppDialog() { const ctx = useContext(AppDialogContext); if (!ctx) { throw new Error('useAppDialog must be used within AppDialogProvider'); } return ctx; }