Files
freedakgmail 5c8c70097c 0.8.0.0
2025-12-21 17:32:33 +08:00

187 lines
5.2 KiB
TypeScript

"use client"
import { useState, createContext, useContext, ReactNode } from "react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
type DialogType = "alert" | "confirm" | "prompt"
interface DialogConfig {
type: DialogType
title?: string
message: string
defaultValue?: string
confirmText?: string
cancelText?: string
variant?: "default" | "destructive"
onConfirm?: (value?: string) => void
onCancel?: () => void
}
interface DialogContextType {
showAlert: (message: string, title?: string) => Promise<void>
showConfirm: (message: string, title?: string, variant?: "default" | "destructive") => Promise<boolean>
showPrompt: (message: string, defaultValue?: string, title?: string) => Promise<string | null>
}
const DialogContext = createContext<DialogContextType | undefined>(undefined)
export function DialogProvider({ children }: { children: ReactNode }) {
const [open, setOpen] = useState(false)
const [config, setConfig] = useState<DialogConfig | null>(null)
const [inputValue, setInputValue] = useState("")
const showAlert = (message: string, title?: string): Promise<void> => {
return new Promise((resolve) => {
setConfig({
type: "alert",
title: title || "提示",
message,
confirmText: "确定",
onConfirm: () => {
setOpen(false)
// 等待模态框动画完成后再 resolve
setTimeout(() => resolve(), 150)
},
})
setOpen(true)
})
}
const showConfirm = (
message: string,
title?: string,
variant: "default" | "destructive" = "default"
): Promise<boolean> => {
return new Promise((resolve) => {
setConfig({
type: "confirm",
title: title || "确认",
message,
confirmText: "确定",
cancelText: "取消",
variant,
onConfirm: () => {
setOpen(false)
// 等待模态框动画完成后再 resolve
setTimeout(() => resolve(true), 150)
},
onCancel: () => {
setOpen(false)
// 等待模态框动画完成后再 resolve
setTimeout(() => resolve(false), 150)
},
})
setOpen(true)
})
}
const showPrompt = (
message: string,
defaultValue: string = "",
title?: string
): Promise<string | null> => {
return new Promise((resolve) => {
setInputValue(defaultValue)
setConfig({
type: "prompt",
title: title || "输入",
message,
defaultValue,
confirmText: "确定",
cancelText: "取消",
onConfirm: (value) => {
setOpen(false)
// 等待模态框动画完成后再 resolve
setTimeout(() => resolve(value || null), 150)
},
onCancel: () => {
setOpen(false)
// 等待模态框动画完成后再 resolve
setTimeout(() => resolve(null), 150)
},
})
setOpen(true)
})
}
const handleConfirm = () => {
if (config?.type === "prompt") {
config.onConfirm?.(inputValue)
} else {
config?.onConfirm?.()
}
}
const handleCancel = () => {
config?.onCancel?.()
}
return (
<DialogContext.Provider value={{ showAlert, showConfirm, showPrompt }}>
{children}
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{config?.title}</AlertDialogTitle>
<AlertDialogDescription className="whitespace-pre-wrap">
{config?.message}
</AlertDialogDescription>
</AlertDialogHeader>
{config?.type === "prompt" && (
<div className="py-4">
<Label htmlFor="prompt-input" className="sr-only">
</Label>
<Input
id="prompt-input"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleConfirm()
}
}}
autoFocus
/>
</div>
)}
<AlertDialogFooter>
{config?.type !== "alert" && (
<AlertDialogCancel onClick={handleCancel}>
{config?.cancelText || "取消"}
</AlertDialogCancel>
)}
<AlertDialogAction
onClick={handleConfirm}
className={config?.variant === "destructive" ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : ""}
>
{config?.confirmText || "确定"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</DialogContext.Provider>
)
}
export function useDialog() {
const context = useContext(DialogContext)
if (!context) {
throw new Error("useDialog must be used within a DialogProvider")
}
return context
}