Files
MemeStudio/components/levels/delete-confirm-dialog.tsx
2026-05-01 08:44:56 +08:00

157 lines
5.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import Image from 'next/image'
import { AlertTriangle, Trash2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Spinner } from '@/components/ui/spinner'
import { Level } from '@/types'
interface DeleteConfirmDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
level: Level | null
/** 列表里的行号0-based用于展示 #序号 */
index?: number
isLoading?: boolean
onConfirm: () => void
}
/**
* 关卡删除二次确认弹窗。
* 展示关卡预览(双图 + 答案)让用户确认删除对象,而不是盲点。
* - 确认按钮 autoFocusEnter 直接确认Esc 由 Radix Dialog 默认关闭
* - 删除中禁用所有交互并显示 Spinner
*/
export function DeleteConfirmDialog({
open,
onOpenChange,
level,
index,
isLoading = false,
onConfirm,
}: DeleteConfirmDialogProps) {
return (
<Dialog
open={open}
onOpenChange={(next) => {
// 删除进行中不允许关闭,避免中途点取消造成状态不一致
if (isLoading && !next) return
onOpenChange(next)
}}
>
<DialogContent className="sm:max-w-md">
<DialogHeader className="items-center text-center sm:items-center sm:text-center">
{/* 红色警告图标 + 软光晕,视觉焦点 */}
<div className="relative mx-auto mb-2 flex h-14 w-14 items-center justify-center">
<div className="absolute inset-0 rounded-full bg-red-500/10" />
<div className="absolute inset-1.5 rounded-full bg-red-500/15" />
<AlertTriangle
className="relative h-7 w-7 text-red-600"
strokeWidth={2.25}
/>
</div>
<DialogTitle className="text-base"></DialogTitle>
<DialogDescription>
线
<span className="font-medium text-red-600"> </span>
</DialogDescription>
</DialogHeader>
{/* 关卡预览卡片 */}
{level && (
<div className="rounded-md border border-dashed bg-muted/40 p-3">
<div className="mb-2 flex items-center justify-between text-xs text-muted-foreground">
<span></span>
{typeof index === 'number' && index >= 0 && (
<span className="rounded bg-background px-1.5 py-0.5 font-mono text-[11px] tabular-nums">
#{index + 1}
</span>
)}
</div>
<div className="flex items-center gap-3">
<div className="flex flex-shrink-0 gap-1.5">
<div className="relative h-14 w-14 overflow-hidden rounded-md bg-background ring-1 ring-border">
{level.image1Url ? (
<Image
src={level.image1Url}
alt="图片1"
fill
className="object-cover"
sizes="56px"
/>
) : null}
</div>
<div className="relative h-14 w-14 overflow-hidden rounded-md bg-background ring-1 ring-border">
{level.image2Url ? (
<Image
src={level.image2Url}
alt="图片2"
fill
className="object-cover"
sizes="56px"
/>
) : null}
</div>
</div>
<div className="min-w-0 flex-1">
<div className="truncate text-base font-semibold leading-tight">
{level.answer}
</div>
{level.punchline ? (
<div className="mt-1 truncate text-xs text-orange-600">
{level.punchline}
</div>
) : (
<div className="mt-1 text-xs text-muted-foreground">
</div>
)}
</div>
</div>
</div>
)}
<DialogFooter className="gap-2 sm:gap-2">
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isLoading}
>
</Button>
<Button
type="button"
variant="destructive"
onClick={onConfirm}
disabled={isLoading}
autoFocus
>
{isLoading ? (
<>
<Spinner size="sm" className="mr-2" />
...
</>
) : (
<>
<Trash2 className="mr-2 h-4 w-4" />
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}