You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
5.3 KiB
TypeScript
178 lines
5.3 KiB
TypeScript
import React from 'react';
|
|
import { Sparkles, LucideIcon } from 'lucide-react';
|
|
import LoadingSpinner from './LoadingSpinner';
|
|
import useTranslation from '../hooks/useTranslation';
|
|
|
|
export interface BoxAction {
|
|
label: string;
|
|
onClick: () => void;
|
|
type?: 'primary' | 'secondary' | 'alternative' | 'danger' | 'cancel';
|
|
icon?: React.ReactNode;
|
|
loading?: boolean;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
interface ConfirmationBoxProps {
|
|
title: string;
|
|
description?: string;
|
|
icon?: React.ReactNode;
|
|
actions?: BoxAction[];
|
|
// Legacy props for backward compatibility
|
|
confirmLabel?: string;
|
|
cancelLabel?: string;
|
|
onConfirm?: () => void;
|
|
onCancel?: () => void;
|
|
confirmLoading?: boolean;
|
|
confirmDisabled?: boolean;
|
|
variant?: 'primary' | 'danger' | 'success';
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
export default function ConfirmationBox({
|
|
title,
|
|
description,
|
|
icon,
|
|
actions,
|
|
confirmLabel,
|
|
cancelLabel,
|
|
onConfirm,
|
|
onCancel,
|
|
confirmLoading = false,
|
|
confirmDisabled = false,
|
|
variant = 'primary',
|
|
children
|
|
}: ConfirmationBoxProps) {
|
|
const { t } = useTranslation();
|
|
|
|
// Build actions array from either new actions prop or legacy props
|
|
const actionButtons = React.useMemo(() => {
|
|
if (actions && actions.length > 0) {
|
|
return actions;
|
|
}
|
|
|
|
// Fallback to legacy props
|
|
const legacyActions: BoxAction[] = [];
|
|
if (onConfirm) {
|
|
legacyActions.push({
|
|
label: confirmLabel || t('Confirm'),
|
|
onClick: onConfirm,
|
|
type: 'primary',
|
|
loading: confirmLoading,
|
|
disabled: confirmDisabled
|
|
});
|
|
}
|
|
if (onCancel) {
|
|
legacyActions.push({
|
|
label: cancelLabel || t('Cancel'),
|
|
onClick: onCancel,
|
|
type: 'cancel'
|
|
});
|
|
}
|
|
return legacyActions;
|
|
}, [actions, onConfirm, onCancel, confirmLabel, cancelLabel, confirmLoading, confirmDisabled, t]);
|
|
|
|
const variantStyles = {
|
|
primary: {
|
|
bg: 'from-indigo-50 to-purple-50',
|
|
border: 'border-indigo-200',
|
|
iconBg: 'bg-indigo-100',
|
|
iconColor: 'text-indigo-600'
|
|
},
|
|
danger: {
|
|
bg: 'from-red-50 to-orange-50',
|
|
border: 'border-red-200',
|
|
iconBg: 'bg-red-100',
|
|
iconColor: 'text-red-600'
|
|
},
|
|
success: {
|
|
bg: 'from-green-50 to-emerald-50',
|
|
border: 'border-green-200',
|
|
iconBg: 'bg-green-100',
|
|
iconColor: 'text-green-600'
|
|
}
|
|
};
|
|
|
|
const styles = variantStyles[variant];
|
|
|
|
// Button style based on action type
|
|
const getButtonStyles = (actionType?: BoxAction['type']) => {
|
|
switch (actionType) {
|
|
case 'primary':
|
|
return variant === 'success'
|
|
? 'bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white'
|
|
: variant === 'danger'
|
|
? 'bg-gradient-to-r from-red-500 to-orange-600 hover:from-red-600 hover:to-orange-700 text-white'
|
|
: 'bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white';
|
|
|
|
case 'danger':
|
|
return 'bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white';
|
|
|
|
case 'alternative':
|
|
return 'bg-gradient-to-r from-slate-500 to-slate-600 hover:from-slate-600 hover:to-slate-700 text-white';
|
|
|
|
case 'secondary':
|
|
return 'bg-slate-100 hover:bg-slate-200 text-slate-700';
|
|
|
|
case 'cancel':
|
|
default:
|
|
return 'bg-white hover:bg-gray-50 text-slate-700 border border-slate-200 hover:border-slate-300';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={`mt-6 p-6 bg-gradient-to-r ${styles.bg} border-2 border-dashed ${styles.border} rounded-2xl text-center`}>
|
|
{icon && (
|
|
<div className="flex items-center justify-center mb-3">
|
|
<div className={`p-3 ${styles.iconBg} rounded-2xl`}>
|
|
<div className={styles.iconColor}>
|
|
{icon}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<h4 className="text-lg font-semibold text-slate-800 mb-2">{title}</h4>
|
|
|
|
{description && (
|
|
<p className="text-slate-600 mb-4">{description}</p>
|
|
)}
|
|
|
|
{children && (
|
|
<div className="mb-4">
|
|
{children}
|
|
</div>
|
|
)}
|
|
|
|
{/* Action Buttons */}
|
|
{actionButtons.length > 0 && (
|
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
|
{actionButtons.map((action, index) => (
|
|
<button
|
|
key={index}
|
|
onClick={action.onClick}
|
|
disabled={action.loading || action.disabled}
|
|
className={`
|
|
inline-flex items-center px-6 py-3
|
|
${getButtonStyles(action.type)}
|
|
rounded-xl font-semibold shadow-lg
|
|
hover:shadow-xl transition-all duration-200
|
|
hover:-translate-y-0.5 active:translate-y-0
|
|
disabled:opacity-50 disabled:cursor-not-allowed
|
|
min-w-[140px] justify-center gap-2
|
|
`}
|
|
>
|
|
{action.loading ? (
|
|
<LoadingSpinner size="sm" className={action.type === 'cancel' || action.type === 'secondary' ? 'text-slate-700' : 'text-white'} />
|
|
) : (
|
|
<>
|
|
{action.icon && <span>{action.icon}</span>}
|
|
<span>{action.label}</span>
|
|
</>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |