374 lines
12 KiB
TypeScript
374 lines
12 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
|
|
export type DialogType =
|
|
| "info"
|
|
| "warning"
|
|
| "error"
|
|
| "success"
|
|
| "delete"
|
|
| "confirm";
|
|
|
|
interface DialogConfig {
|
|
title: string;
|
|
icon: React.ReactNode;
|
|
confirmButtonColor: "red" | "orange" | "blue" | "green";
|
|
headerBgColor: string;
|
|
iconBgColor: string;
|
|
iconColor: string;
|
|
subtitle?: string;
|
|
}
|
|
|
|
interface ConfirmDialogProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onConfirm: () => void;
|
|
type?: DialogType;
|
|
title?: string;
|
|
message: string | React.ReactNode;
|
|
confirmText?: string;
|
|
cancelText?: string;
|
|
icon?: React.ReactNode;
|
|
showWarning?: boolean;
|
|
}
|
|
|
|
/**
|
|
* ConfirmDialog Component
|
|
* Componente reutilizável para exibir diálogos de confirmação customizados
|
|
* Mantém o tema do projeto com cores e estilos consistentes
|
|
* Suporta diferentes tipos: info, warning, error, success, delete, confirm
|
|
*
|
|
* @param isOpen - Controla se o dialog está aberto
|
|
* @param onClose - Callback chamado ao fechar/cancelar
|
|
* @param onConfirm - Callback chamado ao confirmar
|
|
* @param type - Tipo do diálogo (info, warning, error, success, delete, confirm)
|
|
* @param title - Título do dialog (opcional, será definido pelo tipo se não fornecido)
|
|
* @param message - Mensagem principal do dialog
|
|
* @param confirmText - Texto do botão de confirmação (opcional, será definido pelo tipo se não fornecido)
|
|
* @param cancelText - Texto do botão de cancelamento (padrão: "Cancelar")
|
|
* @param icon - Ícone customizado (opcional, será definido pelo tipo se não fornecido)
|
|
* @param showWarning - Se deve mostrar subtítulo de atenção (padrão: true para warning/error/delete)
|
|
*/
|
|
const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
onConfirm,
|
|
type = "confirm",
|
|
title,
|
|
message,
|
|
confirmText,
|
|
cancelText = "Cancelar",
|
|
icon,
|
|
showWarning,
|
|
}) => {
|
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
const [shouldRender, setShouldRender] = useState(false);
|
|
|
|
// Configurações por tipo de diálogo
|
|
const getDialogConfig = (dialogType: DialogType): DialogConfig => {
|
|
const configs: Record<DialogType, DialogConfig> = {
|
|
info: {
|
|
title: "Informação",
|
|
icon: (
|
|
<svg
|
|
className="w-6 h-6 text-blue-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2.5"
|
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
),
|
|
confirmButtonColor: "blue",
|
|
headerBgColor: "bg-[#002147]",
|
|
iconBgColor: "bg-blue-500/20",
|
|
iconColor: "text-blue-400",
|
|
subtitle: "Informação",
|
|
},
|
|
warning: {
|
|
title: "Atenção",
|
|
icon: (
|
|
<svg
|
|
className="w-6 h-6 text-orange-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2.5"
|
|
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>
|
|
),
|
|
confirmButtonColor: "orange",
|
|
headerBgColor: "bg-[#002147]",
|
|
iconBgColor: "bg-orange-500/20",
|
|
iconColor: "text-orange-400",
|
|
subtitle: "Atenção",
|
|
},
|
|
error: {
|
|
title: "Erro",
|
|
icon: (
|
|
<svg
|
|
className="w-6 h-6 text-red-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2.5"
|
|
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
),
|
|
confirmButtonColor: "red",
|
|
headerBgColor: "bg-[#002147]",
|
|
iconBgColor: "bg-red-500/20",
|
|
iconColor: "text-red-400",
|
|
subtitle: "Erro",
|
|
},
|
|
success: {
|
|
title: "Sucesso",
|
|
icon: (
|
|
<svg
|
|
className="w-6 h-6 text-green-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2.5"
|
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
),
|
|
confirmButtonColor: "green",
|
|
headerBgColor: "bg-[#002147]",
|
|
iconBgColor: "bg-green-500/20",
|
|
iconColor: "text-green-400",
|
|
subtitle: "Sucesso",
|
|
},
|
|
delete: {
|
|
title: "Confirmar Exclusão",
|
|
icon: (
|
|
<svg
|
|
className="w-6 h-6 text-red-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2.5"
|
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
/>
|
|
</svg>
|
|
),
|
|
confirmButtonColor: "red",
|
|
headerBgColor: "bg-[#002147]",
|
|
iconBgColor: "bg-red-500/20",
|
|
iconColor: "text-red-400",
|
|
subtitle: "Atenção",
|
|
},
|
|
confirm: {
|
|
title: "Confirmar Ação",
|
|
icon: (
|
|
<svg
|
|
className="w-6 h-6 text-orange-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2.5"
|
|
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
),
|
|
confirmButtonColor: "orange",
|
|
headerBgColor: "bg-[#002147]",
|
|
iconBgColor: "bg-orange-500/20",
|
|
iconColor: "text-orange-400",
|
|
subtitle: "Confirmação",
|
|
},
|
|
};
|
|
|
|
return configs[dialogType];
|
|
};
|
|
|
|
// Obter configuração do tipo
|
|
const config = getDialogConfig(type as DialogType);
|
|
const finalTitle = title || config.title;
|
|
const finalIcon = icon || config.icon;
|
|
const finalConfirmText =
|
|
confirmText ||
|
|
(type === "delete"
|
|
? "Excluir"
|
|
: type === "success" || type === "error" || type === "info"
|
|
? "OK"
|
|
: "Confirmar");
|
|
const shouldShowWarning =
|
|
showWarning !== undefined
|
|
? showWarning
|
|
: ["warning", "error", "delete"].includes(type);
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
setShouldRender(true);
|
|
// Pequeno delay para garantir que o DOM está pronto antes de iniciar a animação
|
|
setTimeout(() => setIsAnimating(true), 10);
|
|
} else {
|
|
// Iniciar animação de saída
|
|
setIsAnimating(false);
|
|
// Remover do DOM após a animação terminar
|
|
const timer = setTimeout(() => setShouldRender(false), 300);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [isOpen]);
|
|
|
|
// Não renderizar se não deve estar visível
|
|
if (!shouldRender) return null;
|
|
|
|
const handleConfirm = () => {
|
|
setIsAnimating(false);
|
|
setTimeout(() => {
|
|
onConfirm();
|
|
onClose();
|
|
}, 300);
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setIsAnimating(false);
|
|
setTimeout(() => {
|
|
onClose();
|
|
}, 300);
|
|
};
|
|
|
|
// Cores do botão de confirmação
|
|
const confirmButtonClasses = {
|
|
red: "bg-red-500 hover:bg-red-600 shadow-red-500/20",
|
|
orange: "bg-orange-500 hover:bg-orange-600 shadow-orange-500/20",
|
|
blue: "bg-blue-500 hover:bg-blue-600 shadow-blue-500/20",
|
|
green: "bg-green-500 hover:bg-green-600 shadow-green-500/20",
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-[200] flex items-center justify-center">
|
|
{/* Overlay */}
|
|
<div
|
|
className={`absolute inset-0 bg-[#001f3f]/60 backdrop-blur-sm transition-opacity duration-300 ${
|
|
isAnimating ? "opacity-100" : "opacity-0"
|
|
}`}
|
|
onClick={handleCancel}
|
|
></div>
|
|
|
|
{/* Dialog - Altura consistente em todos os dispositivos */}
|
|
<div
|
|
className={`relative bg-white rounded-3xl shadow-2xl max-w-md w-full mx-4 h-auto max-h-[90vh] flex flex-col transform transition-all duration-300 ${
|
|
isAnimating ? "scale-100 opacity-100" : "scale-95 opacity-0"
|
|
}`}
|
|
>
|
|
{/* Header */}
|
|
<div
|
|
className={`p-4 lg:p-6 ${config.headerBgColor} text-white rounded-t-3xl relative overflow-hidden`}
|
|
>
|
|
<div className="relative z-10">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
{finalIcon && (
|
|
<div
|
|
className={`w-12 h-12 ${config.iconBgColor} rounded-2xl flex items-center justify-center`}
|
|
>
|
|
{finalIcon}
|
|
</div>
|
|
)}
|
|
<div className="flex-1">
|
|
<h3 className="text-xl font-black">{finalTitle}</h3>
|
|
{shouldShowWarning && config.subtitle && (
|
|
<p
|
|
className={`text-xs ${config.iconColor} font-bold uppercase tracking-wider mt-0.5`}
|
|
>
|
|
{config.subtitle}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={`absolute right-[-10%] top-[-10%] w-32 h-32 ${
|
|
type === "info"
|
|
? "bg-blue-400/10"
|
|
: type === "warning"
|
|
? "bg-orange-400/10"
|
|
: type === "error"
|
|
? "bg-red-400/10"
|
|
: type === "success"
|
|
? "bg-green-400/10"
|
|
: type === "delete"
|
|
? "bg-red-400/10"
|
|
: "bg-orange-400/10"
|
|
} rounded-full blur-2xl`}
|
|
></div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="p-4 lg:p-6 overflow-y-auto custom-scrollbar flex-1">
|
|
{typeof message === "string" ? (
|
|
<p className="text-slate-600 text-sm leading-relaxed whitespace-pre-line">
|
|
{message}
|
|
</p>
|
|
) : (
|
|
<div className="text-slate-600 text-sm leading-relaxed">
|
|
{message}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="p-4 lg:p-6 pt-0 flex gap-3">
|
|
{/* Mostrar botão de cancelar se:
|
|
- For tipo info (permite dois botões) OU
|
|
- Não for tipo success/error E finalConfirmText não é "OK"
|
|
*/}
|
|
{type === "info" ||
|
|
(type !== "success" &&
|
|
type !== "error" &&
|
|
finalConfirmText !== "OK") ? (
|
|
<button
|
|
onClick={handleCancel}
|
|
className="flex-1 py-3 px-4 bg-white border-2 border-slate-300 text-slate-700 font-bold uppercase text-xs tracking-wider rounded-xl hover:bg-slate-50 transition-all"
|
|
>
|
|
{cancelText}
|
|
</button>
|
|
) : null}
|
|
<button
|
|
onClick={handleConfirm}
|
|
className={`${
|
|
(type === "success" || type === "error") &&
|
|
finalConfirmText === "OK"
|
|
? "w-full"
|
|
: "flex-1"
|
|
} py-3 px-4 bg-blue-600 text-white font-bold uppercase text-xs tracking-wider rounded-xl hover:bg-blue-700 transition-all shadow-lg active:scale-95`}
|
|
>
|
|
{finalConfirmText}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ConfirmDialog;
|