80 lines
3.4 KiB
TypeScript
80 lines
3.4 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { CustomButton } from './CustomButton';
|
|
|
|
export interface CustomConfirmationOptions {
|
|
title: string;
|
|
message: string;
|
|
confirmLabel?: string;
|
|
danger?: boolean;
|
|
}
|
|
|
|
export interface CustomConfirmationModalProps {
|
|
state: (CustomConfirmationOptions & { onConfirm: () => void }) | null;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export const CustomConfirmationModal: React.FC<CustomConfirmationModalProps> = ({ state, onClose }) => {
|
|
useEffect(() => {
|
|
if (!state) return;
|
|
const onKey = (e: KeyboardEvent) => e.key === 'Escape' && onClose();
|
|
document.addEventListener('keydown', onKey);
|
|
return () => document.removeEventListener('keydown', onKey);
|
|
}, [state, onClose]);
|
|
|
|
if (!state) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-[200] flex items-center justify-center p-4" onMouseDown={onClose}>
|
|
{/* Backdrop */}
|
|
<div
|
|
className="absolute inset-0 bg-[#0f172a]/30 backdrop-blur-[2px]"
|
|
style={{ animation: 'toastIn 0.2s ease-out' }}
|
|
/>
|
|
|
|
{/* Modal Content */}
|
|
<div
|
|
style={{ width: 440, animation: 'slideUp 0.25s cubic-bezier(0.16, 1, 0.3, 1)' }}
|
|
className="relative flex max-h-[90vh] flex-col overflow-hidden rounded-[16px] bg-[#ffffff] shadow-[0_24px_48px_rgba(16,24,40,0.18)] ring-1 ring-[#ebedf0]"
|
|
onMouseDown={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="flex flex-col gap-2.5 p-7 pb-6">
|
|
<div className="flex items-center gap-3">
|
|
{state.danger ? (
|
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-[#fdecec] text-[#dc2626]">
|
|
<svg width="22" height="22" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
</div>
|
|
) : (
|
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-[#eef4ff] text-[#2563eb]">
|
|
<svg width="22" height="22" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
)}
|
|
<h2 className="text-[17.5px] font-bold tracking-tight text-[#18212e]">{state.title}</h2>
|
|
</div>
|
|
<p className="pl-[52px] text-[13.5px] leading-relaxed text-[#5b6573]">{state.message}</p>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-end gap-3 bg-[#f6f7f9] px-7 py-5 border-t border-[#ebedf0]">
|
|
<CustomButton variant="ghost" onClick={onClose} className="rounded-[8px] font-semibold px-4 text-[#5b6573]">
|
|
Cancel
|
|
</CustomButton>
|
|
<CustomButton
|
|
variant={state.danger ? 'danger' : 'primary'}
|
|
onClick={() => { state.onConfirm(); onClose(); }}
|
|
className={`rounded-[8px] px-5 font-semibold text-white shadow-sm transition-colors ${
|
|
state.danger
|
|
? 'bg-[#dc2626] hover:bg-[#b91c1c]'
|
|
: 'bg-[#2563eb] hover:bg-[#1d4ed8]'
|
|
}`}
|
|
>
|
|
{state.confirmLabel ?? 'Confirm'}
|
|
</CustomButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|