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

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>
);
}