101 lines
4.1 KiB
TypeScript
101 lines
4.1 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect } from 'react';
|
|
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
|
|
|
|
interface ConfirmDialogProps {
|
|
open: boolean;
|
|
title: string;
|
|
message: string;
|
|
confirmLabel?: string;
|
|
cancelLabel?: string;
|
|
tone?: 'danger' | 'primary';
|
|
isLoading?: boolean;
|
|
onConfirm: () => void;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
// Styled replacement for the native confirm() dialog so destructive actions
|
|
// match the rest of the panel.
|
|
export default function ConfirmDialog({
|
|
open,
|
|
title,
|
|
message,
|
|
confirmLabel = 'تأیید',
|
|
cancelLabel = 'انصراف',
|
|
tone = 'danger',
|
|
isLoading = false,
|
|
onConfirm,
|
|
onCancel,
|
|
}: ConfirmDialogProps) {
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
if (event.key === 'Escape' && !isLoading) onCancel();
|
|
};
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
}, [open, isLoading, onCancel]);
|
|
|
|
if (!open) return null;
|
|
|
|
const confirmClass =
|
|
tone === 'danger'
|
|
? 'bg-red-600 hover:bg-red-700 focus:ring-red-500/30 shadow-red-500/20'
|
|
: 'bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500/30 shadow-indigo-500/20';
|
|
const iconWrapClass =
|
|
tone === 'danger'
|
|
? 'bg-red-50 text-red-600 dark:bg-red-500/10 dark:text-red-400'
|
|
: 'bg-indigo-50 text-indigo-600 dark:bg-indigo-500/10 dark:text-indigo-400';
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 dark:bg-black/60 p-4"
|
|
onClick={() => {
|
|
if (!isLoading) onCancel();
|
|
}}
|
|
>
|
|
<div
|
|
className="bg-white dark:bg-gray-900 rounded-2xl shadow-xl ring-1 ring-gray-200 dark:ring-gray-800 w-full max-w-md"
|
|
dir="rtl"
|
|
onClick={(event) => event.stopPropagation()}
|
|
role="alertdialog"
|
|
aria-modal="true"
|
|
aria-label={title}
|
|
>
|
|
<div className="px-6 py-5 flex items-start gap-4">
|
|
<div className={`flex-shrink-0 h-11 w-11 rounded-xl flex items-center justify-center ${iconWrapClass}`}>
|
|
<ExclamationTriangleIcon className="h-6 w-6" />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<h3 className="text-base font-semibold text-gray-900 dark:text-gray-100">{title}</h3>
|
|
<p className="mt-1.5 text-sm text-gray-500 dark:text-gray-400 leading-6">{message}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-3 px-6 py-4 border-t border-gray-100 dark:border-gray-800">
|
|
<button
|
|
type="button"
|
|
onClick={onCancel}
|
|
disabled={isLoading}
|
|
className="px-5 py-2.5 rounded-xl border border-gray-200 dark:border-gray-700 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 transition-colors"
|
|
>
|
|
{cancelLabel}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={onConfirm}
|
|
disabled={isLoading}
|
|
className={`inline-flex items-center gap-2 px-5 py-2.5 rounded-xl text-white text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 disabled:opacity-50 transition-colors shadow-sm ${confirmClass}`}
|
|
>
|
|
{isLoading && (
|
|
<span className="h-4 w-4 border-2 border-white/40 border-t-white rounded-full animate-spin" />
|
|
)}
|
|
{isLoading ? 'در حال انجام...' : confirmLabel}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|